Initial commit

This commit is contained in:
njdaoyehu 2024-11-14 13:43:41 +08:00
commit 0b1166c718
744 changed files with 63940 additions and 0 deletions

4
.browserslistrc Normal file
View File

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

107
.commitlintrc.js Normal file
View File

@ -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: 自定义',
},
};

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
node_modules/
dist/
.vscode/

19
.editorconfig Normal file
View File

@ -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

2
.env Normal file
View File

@ -0,0 +1,2 @@
# spa-title
VITE_GLOB_APP_TITLE=中核集团预埋件检测系统

23
.env.analyze Normal file
View File

@ -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

14
.env.development Normal file
View File

@ -0,0 +1,14 @@
# Whether to open mock
VITE_USE_MOCK=false
# public path
VITE_PUBLIC_PATH=/
# Basic interface address SPA
VITE_GLOB_API_URL=http://localhost:3920
# File upload address optional
VITE_GLOB_UPLOAD_URL=http://localhost:3910/v1/system/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

22
.env.docker Normal file
View File

@ -0,0 +1,22 @@
# Whether to open mock
VITE_USE_MOCK = false
# public path
VITE_PUBLIC_PATH = /
# timeout(seconds)
VITE_TIMEOUT = 15
# Delete console
VITE_DROP_CONSOLE = true
# 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'
VITE_GLOB_API_URL="__vg_base_url"
# File upload address optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/files/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

20
.env.production Normal file
View File

@ -0,0 +1,20 @@
# Whether to open mock
VITE_USE_MOCK=false
# 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://localhost:3910
# File upload address optional
VITE_GLOB_UPLOAD_URL=http://localhost:3910/v1/system/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

21
.env.test Normal file
View File

@ -0,0 +1,21 @@
NODE_ENV=production
# 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=

15
.eslintignore Normal file
View File

@ -0,0 +1,15 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

7
.eslintrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
root: true,
extends: ['@vben'],
rules: {
"unused-imports/no-unused-imports": "off"
}
};

11
.gitattributes vendored Normal file
View File

@ -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

34
.gitignore vendored Normal file
View File

@ -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

6
.gitpod.yml Normal file
View File

@ -0,0 +1,6 @@
ports:
- port: 3344
onOpen: open-preview
tasks:
- init: pnpm install
command: pnpm run dev

7
.npmrc Normal file
View File

@ -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

12
.prettierignore Normal file
View File

@ -0,0 +1,12 @@
dist
.local
.output.js
node_modules
**/*.svg
**/*.sh
public
.npmrc
*-lock.yaml

19
.prettierrc.js Normal file
View File

@ -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',
},
},
],
};

2
.stylelintignore Normal file
View File

@ -0,0 +1,2 @@
dist
public

4
.stylelintrc.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/stylelint-config'],
};

1262
CHANGELOG.en_US.md Normal file

File diff suppressed because it is too large Load Diff

1586
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

1317
CHANGELOG.zh_CN.md Normal file

File diff suppressed because it is too large Load Diff

1
CNAME Normal file
View File

@ -0,0 +1 @@
vben.vvbin.cn

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
# node 构建
FROM node:16-alpine as build-stage
# 署名
MAINTAINER Adoin 'adoin@qq.com'
WORKDIR /app
COPY . ./
# 设置 node 阿里镜像
RUN npm config set registry https://registry.npm.taobao.org
# 设置--max-old-space-size
ENV NODE_OPTIONS=--max-old-space-size=16384
# 设置阿里镜像、pnpm、依赖、编译
RUN npm install pnpm -g && \
pnpm install --frozen-lockfile && \
pnpm build:docker
# node部分结束
RUN echo "🎉 编 🎉 译 🎉 成 🎉 功 🎉"
# nginx 部署
FROM nginx:1.23.3-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html/dist
COPY --from=build-stage /app/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
## 将/usr/share/nginx/html/dist/assets/index.js 和/usr/share/nginx/html/dist/_app.config.js中的"$vg_base_url"替换为环境变量中的VG_BASE_URL,$vg_sub_domain 替换成VG_SUB_DOMAIN$vg_default_user替换成VG_DEFAULT_USER$vg_default_password替换成VG_DEFAULT_PASSWORD 而后启动nginx
CMD sed -i "s|__vg_base_url|$VG_BASE_URL|g" /usr/share/nginx/html/dist/assets/index.js && \
sed -i "s|__vg_base_url|$VG_BASE_URL|g" /usr/share/nginx/html/dist/_app.config.js && \
nginx -g 'daemon off;'
RUN echo "🎉 架 🎉 设 🎉 成 🎉 功 🎉"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-present, Vben
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

184
README.md Normal file
View File

@ -0,0 +1,184 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://anncwb.github.io/anncwb/images/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue vben admin</h1>
</div>
**English** | [中文](./README.zh-CN.md)
## Introduction
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite4`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
## Feature
- **State of The Art Development**Use front-end front-end technology development such as Vue3/vite2
- **TypeScript**: Application-level JavaScript language
- **Theming**: Configurable themes
- **International**Built-in complete internationalization program
- **Mock Server** Built-in mock data scheme
- **Authority** Built-in complete dynamic routing permission generation scheme.
- **Component** Multiple commonly used components are encapsulated twice
## Preview
- [vue-vben-admin](https://vben.vvbin.cn/) - Full version Chinese site
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - Full version of the github site
- [vben-admin-thin-next](https://vben.vvbin.cn/thin/next/) - Simplified Chinese site
- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) -Simplified github site
Test account: vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
### Use Gitpod
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin)
## Documentation
[Document](https://doc.vvbin.cn/)
## Preparation
- [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment
- [Vite](https://vitejs.dev/) - Familiar with vite features
- [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax
- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript`
- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax
- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router
- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui basic use
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax
## Install and use
- Get the project code
```bash
git clone https://github.com/anncwb/vue-vben-admin.git
```
- Installation dependencies
```bash
cd vue-vben-admin
pnpm install
```
- run
```bash
pnpm serve
```
- build
```bash
pnpm build
```
- docker
### The dockerFile is located in the project root directory and supports differential deployment
#### build image
```bash
docker build -t vue-vben-admin .
```
#### Environment variables are dynamically used to achieve differentiated container deployment. Different VG_BASE_URL environment variables point to different back-end service addresses. In the following example, http://localhost:3333 is used as the back-end service address and the container is mapped to port 6666
```bash
docker run --name vue-vben-admin -d -p 6666:80 -e VG_BASE_URL=http://localhost:3333 vue-vben-admin
```
Then you can navigate http://localhost:6666
## Change Log
[CHANGELOG](./CHANGELOG.zh_CN.md)
## Project
- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - full version
- [vue-vben-admin-thin-next](https://github.com/anncwb/vben-admin-thin-next) - Simplified version
## How to contribute
You are very welcome to join[Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) Or submit a Pull Request。
**Pull Request:**
1. Fork code!
2. Create your own branch: `git checkout -b feat/xxxx`
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
4. Push your branch: `git push origin feat/xxxx`
5. submit`pull request`
## Git Contribution submission specification
- reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` Add new features
- `fix` Fix the problem/BUG
- `style` The code style is related and does not affect the running result
- `perf` Optimization/performance improvement
- `refactor` Refactor
- `revert` Undo edit
- `test` Test related
- `docs` Documentation/notes
- `chore` Dependency update/scaffolding configuration modification etc.
- `workflow` Workflow improvements
- `ci` Continuous integration
- `types` Type definition file changes
- `wip` In development
## Related warehouse
If these plugins are helpful to you, you can give a star support
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - Used for local and development environment data mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - Used for html template conversion and compression
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - Used to pack input .gz|.brotil files
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - Used to quickly generate svg sprite
## Browser support
The `Chrome 80+` browser is recommended for local development
Support modern browsers, not IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Maintainer
[@Vben](https://github.com/anncwb)
## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
![donate](https://anncwb.github.io/anncwb/images/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Discord
- [github discussions](https://github.com/anncwb/vue-vben-admin/discussions)
- [Discord](https://discord.gg/8GuAdwDhj6)
## License
[MIT © Vben-2020](./LICENSE)

190
README.zh-CN.md Normal file
View File

@ -0,0 +1,190 @@
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://anncwb.github.io/anncwb/images/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue vben admin</h1>
</div>
**中文** | [English](./README.md)
## 简介
Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite4`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
## 特性
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**:可配置的主题
- **国际化**:内置完善的国际化方案
- **Mock 数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
## 预览
- [vue-vben-admin](https://vben.vvbin.cn/) - 完整版中文站点
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - 完整版 github 站点
- [vben-admin-thin-next](https://vben.vvbin.cn/thin/next/) - 简化版中文站点
- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) - 简化版 github 站点
测试账号: vben/123456
<p align="center">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</p>
### 使用 Gitpod
在 Gitpod适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin)
## 文档
[文档地址](https://doc.vvbin.cn/)
## 准备
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装使用
- 获取项目代码
```bash
git clone https://github.com/anncwb/vue-vben-admin.git
```
- 安装依赖
```bash
cd vue-vben-admin
pnpm install
```
- 运行
```bash
pnpm serve
```
- 打包
```bash
pnpm build
```
- docker
### dockerFile 位于项目根目录下 并且支持差异化部署
#### 构建镜像
```bash
docker build -t vue-vben-admin .
```
#### 动态使用环境变量实现容器差异化部署,通过不同的 VG_BASE_URL 环境变量,指向不同的后端服务地址,下面例子使用 http://localhost:3333 作为后端服务地址,并且将容器映射到 6666 端口
```bash
docker run --name vue-vben-admin -d -p 6666:80 -e VG_BASE_URL=http://localhost:3333 vue-vben-admin
```
而后可以打开 http://localhost:6666 访问
## 更新日志
[CHANGELOG](./CHANGELOG.zh_CN.md)
## 项目地址
- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - 完整版
- [vue-vben-admin-thin-next](https://github.com/anncwb/vben-admin-thin-next) - 简化版
## 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
**Pull Request:**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feat/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat/xxxx`
5. 提交`pull request`
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
## 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 相关仓库
如果这些插件对你有帮助,可以给一个 star 支持下
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图
## 后台整合示例
- [lamp-cloud](https://github.com/zuihou/lamp-cloud) - 基于 SpringCloud Alibaba 的微服务中后台快速开发平台
- [matecloud](https://github.com/matevip/matecloud) - MateCloud 微服务脚手架,基于 Spring Cloud 2020.0.3、SpringBoot 2.5.3 的全开源平台
## 维护者
[@Vben](https://github.com/anncwb)
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
![donate](https://anncwb.github.io/anncwb/images/sponsor.png)
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## 交流
`Vue-vben-Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。
- QQ 群 `569291866`
## License
[MIT © Vben-2020](./LICENSE)

1
_app.config.js Normal file
View File

@ -0,0 +1 @@
window.__PRODUCTION__667A80FD89C6989176D163A75E7353F0__CONF__={"VITE_GLOB_API_URL":"http://146.56.195.129:9088/api","VITE_GLOB_UPLOAD_URL":"http://146.56.195.129:9088/api/v1/system/upload","VITE_GLOB_API_URL_PREFIX":"","VITE_GLOB_APP_TITLE":"智能视频监控平台"};Object.freeze(window.__PRODUCTION__667A80FD89C6989176D163A75E7353F0__CONF__);Object.defineProperty(window, "__PRODUCTION__667A80FD89C6989176D163A75E7353F0__CONF__", {configurable: false,writable: false,});

View File

View File

@ -0,0 +1,15 @@
# Test Server
It is used to start the test interface service, which can test the upload, websocket, login and other interfaces.
## Usage
```bash
cd ./test/server
pnpm install
pnpm run start
```

View File

@ -0,0 +1,18 @@
import FileService from '../service/FileService';
class FileController {
private service: FileService = new FileService();
upload = async (ctx) => {
const files = ctx.request.files.file;
console.log(files);
if (files.length === undefined) {
this.service.upload(ctx, files, false);
} else {
this.service.upload(ctx, files, true);
}
};
}
export default new FileController();

View File

@ -0,0 +1,15 @@
import UserService from '../service/UserService';
class UserController {
private service: UserService = new UserService();
login = async (ctx) => {
ctx.body = await this.service.login();
};
getUserInfoById = async (ctx) => {
ctx.body = await this.service.getUserInfoById();
};
}
export default new UserController();

View File

@ -0,0 +1,18 @@
const { name } = require('./package.json');
const path = require('path');
module.exports = {
apps: [
{
name,
script: path.resolve(__dirname, './dist/index.js'),
instances: require('os').cpus().length,
autorestart: true,
watch: true,
env_production: {
NODE_ENV: 'production',
PORT: 8080,
},
},
],
};

63
apps/test-server/index.ts Normal file
View File

@ -0,0 +1,63 @@
import Koa from 'koa';
import path from 'path';
import Router from 'koa-router';
import body from 'koa-body';
import cors from 'koa2-cors';
import koaStatic from 'koa-static';
import websockify from 'koa-websocket';
import route from 'koa-route';
import AppRoutes from './routes';
const PORT = 3300;
const app = websockify(new Koa());
app.ws.use(function (ctx, next) {
ctx.websocket.send('connection succeeded!');
return next(ctx);
});
app.ws.use(
route.all('/test', function (ctx) {
// ctx.websocket.send('Hello World');
ctx.websocket.on('message', function (message) {
// do something with the message from client
if (message !== 'ping') {
const data = JSON.stringify({
id: Math.ceil(Math.random() * 1000),
time: new Date().getTime(),
res: `${message}`,
});
ctx.websocket.send(data);
}
console.log(message);
});
}),
);
const router = new Router();
// router
AppRoutes.forEach((route) => router[route.method](route.path, route.action));
app.use(cors());
app.use(
body({
encoding: 'gzip',
multipart: true,
formidable: {
// uploadDir: path.join(__dirname, '/upload/'), // 设置文件上传目录
keepExtensions: true,
maxFieldsSize: 20 * 1024 * 1024,
},
}),
);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(koaStatic(path.join(__dirname)));
app.listen(PORT, () => {
console.log(`Application started successfully: http://localhost:${PORT}`);
});

View File

@ -0,0 +1,8 @@
{
"watch": ["src"],
"ext": "ts",
"exec": "ts-node -r tsconfig-paths/register index.ts",
"events": {
"restart": "clear"
}
}

View File

@ -0,0 +1,36 @@
{
"name": "server",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"compile": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ",
"prod": "npx pm2 start ecosystem.config.js --env production",
"restart": "pm2 restart ecosystem.config.js --env production",
"start": "nodemon",
"stop": "npx pm2 stop ecosystem.config.js"
},
"dependencies": {
"fs-extra": "^11.1.1",
"koa": "^2.14.2",
"koa-body": "^6.0.1",
"koa-bodyparser": "^4.4.1",
"koa-route": "^3.2.0",
"koa-router": "^12.0.0",
"koa-static": "^5.0.0",
"koa-websocket": "^7.0.0",
"koa2-cors": "^2.0.6"
},
"devDependencies": {
"@types/koa": "^2.13.6",
"@types/koa-bodyparser": "^5.0.2",
"@types/koa-router": "^7.4.4",
"@types/node": "^20.4.0",
"nodemon": "^2.0.22",
"pm2": "^5.3.0",
"rimraf": "^5.0.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"tsup": "^7.1.0",
"typescript": "^5.1.6"
}
}

View File

@ -0,0 +1,23 @@
import UserController from './controller/UserController';
import FileController from './controller/FileController';
export default [
// user
{
path: '/login',
method: 'post',
action: UserController.login,
},
{
path: '/getUserInfoById',
method: 'get',
action: UserController.getUserInfoById,
},
// file
{
path: '/upload',
method: 'post',
action: FileController.upload,
},
];

View File

@ -0,0 +1,54 @@
import path from 'path';
import fs from 'fs-extra';
const uploadUrl = 'http://localhost:3300/static/upload';
const filePath = path.join(__dirname, '../static/upload/');
fs.ensureDir(filePath);
export default class UserService {
async upload(ctx, files, isMultiple) {
let fileReader, fileResource, writeStream;
const fileFunc = function (file) {
fileReader = fs.createReadStream(file.path);
fileResource = filePath + `/${file.name}`;
console.log(fileResource);
writeStream = fs.createWriteStream(fileResource);
fileReader.pipe(writeStream);
};
const returnFunc = function (flag) {
if (flag) {
let url = '';
for (let i = 0; i < files.length; i++) {
url += uploadUrl + `/${files[i].name},`;
}
url = url.replace(/,$/gi, '');
ctx.body = {
url: url,
code: 0,
message: 'upload Success!',
};
} else {
ctx.body = {
url: uploadUrl + `/${files.name}`,
code: 0,
message: 'upload Success!',
};
}
};
console.log(isMultiple, files.length);
if (isMultiple) {
for (let i = 0; i < files.length; i++) {
const f1 = files[i];
fileFunc(f1);
}
} else {
fileFunc(files);
}
fs.ensureDir(filePath);
returnFunc(isMultiple);
}
}

View File

@ -0,0 +1,25 @@
import { Result } from '../utils';
const fakeUserInfo = {
userId: '1',
username: 'vben',
realName: 'Vben Admin',
desc: 'manager',
password: '123456',
token: 'fakeToken1',
roles: [
{
roleName: 'Super Admin',
value: 'super',
},
],
};
export default class UserService {
async login() {
return Result.success(fakeUserInfo);
}
async getUserInfoById() {
return Result.success(fakeUserInfo);
}
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node-server.json",
"compilerOptions": {
"noImplicitAny": false
}
}

View File

@ -0,0 +1,9 @@
export class Result {
static success(data: any) {
return {
code: 0,
success: true,
result: data,
};
}
}

24
embedded.js Normal file
View File

@ -0,0 +1,24 @@
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'dist', 'index.html');
// 读取 HTML 文件
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('读取文件出错:', err);
return;
}
const result = data.replace(
/<script crossorigin src="\/_app\.config\.js"><\/script>/g,
'<script crossorigin src="./_app.config.js"></script>'
);
fs.writeFile(filePath, result, 'utf8', (err) => {
if (err) {
console.error('写入文件出错:', err);
return;
}
console.log('html处理成功');
});
});

164
index.html Normal file
View File

@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="en" 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="<%= VITE_PUBLIC_PATH %>favicon.ico" />
<style>
#app {
//background-image: url('<%= VITE_PUBLIC_PATH %>bg.png');
//background-size: cover;
//background-position: center center;
background-color: #0d1540;
//background-color: rgba(45, 119, 243, 0.4);
//background-blend-mode: overlay;
}
</style>
</head>
<body>
<div id="app">
<style>
html[data-theme='dark'] .app-loading {
background-color: transparent;
}
html[data-theme='dark'] .app-loading .app-loading-title {
color: transparent;
}
.app-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: transparent;
}
.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="<%= VITE_PUBLIC_PATH %>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>

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/eslint-config/strict'],
};

View File

@ -0,0 +1,10 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index', 'src/strict'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@ -0,0 +1,49 @@
{
"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",
"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.3.0",
"@typescript-eslint/parser": "^6.3.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.17.0",
"vue-eslint-parser": "^9.3.1"
}
}

View File

@ -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': [
'warn',
{
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' },
};

View File

@ -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',
},
};

View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"include": ["src"]
}

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/eslint-config/strict'],
};

View File

@ -0,0 +1,10 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@ -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",
"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.24",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.8",
"stylelint": "^15.10.1",
"stylelint-config-property-sort-order-smacss": "^9.1.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-recommended-scss": "^12.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^10.0.0",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^3.0.0"
}
}

View File

@ -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: {
'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,
'string-quotes': 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'],
};

View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"include": ["src"]
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"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/**"]
}

View File

@ -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"]
}

View File

@ -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": "./"
}
}

View File

@ -0,0 +1,25 @@
{
"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",
"files": [
"base.json",
"node.json",
"vue-app.json",
"node-server.json"
],
"dependencies": {
"@types/node": "^20.4.0",
"vite": "^4.4.0"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Vue Application",
"extends": "./base.json",
"compilerOptions": {
"jsx": "preserve",
"lib": ["ESNext", "DOM"],
"noImplicitAny": false
}
}

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/eslint-config/strict'],
};

View File

@ -0,0 +1,10 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@ -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",
"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.0",
"vite": "^4.4.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"ant-design-vue": "^4.0.3",
"dayjs": "^1.11.9",
"dotenv": "^16.3.1",
"fs-extra": "^11.1.1",
"less": "^4.1.3",
"picocolors": "^1.0.0",
"pkg-types": "^1.0.3",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"unocss": "^0.53.4",
"unplugin-config": "^0.0.13",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-dts": "^3.1.0",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.9.2",
"vite-plugin-svg-icons": "^2.0.1"
}
}

View File

@ -0,0 +1,119 @@
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 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') + '/',
},
// @/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/[name].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 };

View File

@ -0,0 +1,27 @@
import { presetTypography, presetUno } from 'unocss';
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({
presets: [presetUno(), presetTypography()],
}),
],
});
export { commonConfig };

View File

@ -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 };

View File

@ -0,0 +1,2 @@
export * from './config/application';
export * from './config/package';

View File

@ -0,0 +1,23 @@
import GenerateConfig from 'unplugin-config/vite';
import { type PluginOption } from 'vite';
import { getEnvConfig } from '../utils/env';
import { strToHex } from '../utils/hash';
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js';
// This constant sets the output directory for the Vite package
export async function createConfigPluginConfig(
shouldGenerateConfig: boolean,
): Promise<PluginOption> {
const config = await getEnvConfig();
// LINK /src/utils/env.ts -> getVariableName
const APP_NAME = strToHex(config?.VITE_GLOB_APP_TITLE ?? '__APP');
// https://github.com/kirklin/unplugin-config
return GenerateConfig({
disabledConfig: !shouldGenerateConfig,
globConfigFileName: GLOBAL_CONFIG_FILE_NAME,
appName: APP_NAME,
envConfigPrefix: 'VITE_GLOB_',
publicPath: config?.VITE_PUBLIC_PATH ?? ''
});
}

View File

@ -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;
}

View File

@ -0,0 +1,13 @@
/**
* Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html
*/
import type { PluginOption } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) {
const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild,
});
return htmlPlugin;
}

View File

@ -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 { createConfigPluginConfig } 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, enableMock, compress, enableAnalyze }: Options) {
const vitePlugins: (PluginOption | PluginOption[])[] = [vue(), vueJsx()];
const appConfigPlugin = await createConfigPluginConfig(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 };

View File

@ -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();
`,
});
}

View File

@ -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;
}

View File

@ -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/stat.html',
open: true,
gzipSize: true,
brotliSize: true,
}) as PluginOption;
}

View File

@ -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;
}

View File

@ -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 };

View File

@ -0,0 +1,52 @@
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
'app-content-background': '#fafafa', // Link color
};
}

View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/ts-config/node.json",
"include": ["src"]
}

View File

@ -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);
}

62
mock/_util.ts Normal file
View File

@ -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;
}

71
mock/demo/account.ts Normal file
View File

@ -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[];

325
mock/demo/api-cascader.ts Normal file
View File

@ -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[];

28
mock/demo/select-demo.ts Normal file
View File

@ -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[];

202
mock/demo/system.ts Normal file
View File

@ -0,0 +1,202 @@
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)',
'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[];

55
mock/demo/table-demo.ts Normal file
View File

@ -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[];

38
mock/demo/tree-demo.ts Normal file
View File

@ -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[];

270
mock/sys/menu.ts Normal file
View File

@ -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 MockMethod[];

122
mock/sys/user.ts Normal file
View File

@ -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[];

35
nginx.conf Normal file
View File

@ -0,0 +1,35 @@
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
location / {
root /usr/share/nginx/html/dist;
try_files $uri $uri/ /index.html;
index index.html;
# Enable CORS
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
}
}

146
package.json Normal file
View File

@ -0,0 +1,146 @@
{
"name": "vben-admin",
"version": "2.10.1.build0307",
"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"
},
"scripts": {
"bootstrap": "pnpm install",
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build && node embedded.js",
"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/",
"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": "^6.1.0",
"@iconify/iconify": "^3.1.1",
"@vben/hooks": "workspace:*",
"@vue/shared": "^3.3.4",
"@vueuse/core": "^10.2.1",
"@vueuse/shared": "^10.2.1",
"@zxcvbn-ts/core": "^3.0.2",
"ant-design-vue": "^4.0.3",
"axios": "^1.4.0",
"codemirror": "^5.65.12",
"cropperjs": "^1.5.13",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.9",
"echarts": "^5.4.2",
"lodash-es": "^4.17.21",
"lodash.get": "^4.4.2",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.1",
"pinia": "2.1.4",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"resize-observer-polyfill": "^1.5.1",
"showdown": "^2.1.0",
"sortablejs": "^1.15.0",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vue-json-pretty": "^2.2.4",
"vue-router": "^4.2.3",
"vue-types": "^5.1.0",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.4.5",
"vxe-table-plugin-export-xlsx": "^3.0.4",
"xe-utils": "^3.5.11"
},
"devDependencies": {
"@commitlint/cli": "^17.6.6",
"@commitlint/config-conventional": "^17.6.6",
"@iconify/json": "^2.2.87",
"@purge-icons/generated": "^0.9.0",
"@types/codemirror": "^5.60.8",
"@types/crypto-js": "^4.1.1",
"@types/intro.js": "^5.1.1",
"@types/lodash-es": "^4.17.7",
"@types/lodash.get": "^4.4.7",
"@types/mockjs": "^1.0.7",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.1",
"@types/qs": "^6.9.7",
"@types/showdown": "^2.0.1",
"@types/sortablejs": "^1.15.1",
"@vben/eslint-config": "workspace:*",
"@vben/stylelint-config": "workspace:*",
"@vben/ts-config": "workspace:*",
"@vben/types": "workspace:*",
"@vben/vite-config": "workspace:*",
"@vue/compiler-sfc": "^3.3.4",
"@vue/test-utils": "^2.4.0",
"cross-env": "^7.0.3",
"cz-git": "^1.6.1",
"czg": "^1.6.1",
"lint-staged": "13.2.3",
"prettier": "^2.8.8",
"prettier-plugin-packagejson": "^2.4.4",
"rimraf": "^5.0.1",
"turbo": "^1.10.7",
"typescript": "^5.1.6",
"unbuild": "^1.2.1",
"vite": "^4.4.0",
"vite-plugin-mock": "^2.9.6",
"vue-tsc": "^1.8.4"
},
"packageManager": "pnpm@9.0.6",
"engines": {
"node": ">=16.15.1",
"pnpm": ">=8.1.0"
}
}

0
packages/.gitkeep Normal file
View File

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@vben/eslint-config/strict'],
};

View File

@ -0,0 +1,10 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
entries: ['src/index'],
declaration: true,
rollup: {
emitCJS: true,
},
});

View File

@ -0,0 +1,38 @@
{
"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,
"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.2.1",
"vue": "^3.3.4"
},
"devDependencies": {
"@vben/types": "workspace:*"
}
}

View File

@ -0,0 +1,6 @@
export * from './onMountedOrActivated';
export * from './useAttrs';
export * from './useRefs';
export * from './useScrollTo';
export * from './useWindowSizeFn';
export { useTimeoutFn } from '@vueuse/core';

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -0,0 +1,60 @@
import { shallowRef, unref } from 'vue';
interface UseScrollToOptions {
el: any;
to: number;
duration?: number;
callback?: () => any;
}
function easeInOutQuad(t: number, b: number, c: number, d: number) {
t /= d / 2;
if (t < 1) {
return (c / 2) * t * t + b;
}
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
}
function move(el: HTMLElement, amount: number) {
el.scrollTop = amount;
}
const position = (el: HTMLElement) => {
return el.scrollTop;
};
function useScrollTo({ el, to, duration = 500, callback }: UseScrollToOptions) {
const isActiveRef = shallowRef(false);
const start = position(el);
const change = to - start;
const increment = 20;
let currentTime = 0;
const animateScroll = function () {
if (!unref(isActiveRef)) {
return;
}
currentTime += increment;
const val = easeInOutQuad(currentTime, start, change, duration);
move(el, val);
if (currentTime < duration && unref(isActiveRef)) {
requestAnimationFrame(animateScroll);
} else {
if (callback && typeof callback === 'function') {
callback();
}
}
};
const run = () => {
isActiveRef.value = true;
animateScroll();
};
const stop = () => {
isActiveRef.value = false;
};
return { start: run, stop };
}
export { useScrollTo, type UseScrollToOptions };

View File

@ -0,0 +1,40 @@
import { type AnyFunction } from '@vben/types';
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core';
interface UseWindowSizeOptions {
wait?: number;
once?: boolean;
immediate?: boolean;
listenerOptions?: AddEventListenerOptions | boolean;
}
function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) {
const { wait = 150, immediate } = options;
let handler = () => {
fn();
};
const handleSize = useDebounceFn(handler, wait);
handler = handleSize;
const start = () => {
if (immediate) {
handler();
}
window.addEventListener('resize', handler);
};
const stop = () => {
window.removeEventListener('resize', handler);
};
tryOnMounted(() => {
start();
});
tryOnUnmounted(() => {
stop();
});
return { start, stop };
}
export { useWindowSizeFn, type UseWindowSizeOptions };

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