mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-25 16:16:20 +08:00
Compare commits
147 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fa5803f8c7 | ||
![]() |
aa6168fe20 | ||
![]() |
855a410557 | ||
![]() |
b1559e2cad | ||
![]() |
c5d24e07f0 | ||
![]() |
1cf2a81f2a | ||
![]() |
5665fd62a9 | ||
![]() |
b7554fdb74 | ||
![]() |
3b62afe110 | ||
![]() |
dbdd811705 | ||
![]() |
a6b65b58a1 | ||
![]() |
556575f501 | ||
![]() |
e4778757ad | ||
![]() |
b43fe7adbc | ||
![]() |
4aaddef06f | ||
![]() |
ce6b25d03b | ||
![]() |
74a2f6209f | ||
![]() |
99ddc3598a | ||
![]() |
d3fd22dbbd | ||
![]() |
bf2f6390ad | ||
![]() |
bd024cc521 | ||
![]() |
4ac08e5ae5 | ||
![]() |
b0c2ca5393 | ||
![]() |
1ac8c56c6b | ||
![]() |
f4149c2f1d | ||
![]() |
3ed49c3f8f | ||
![]() |
356f132610 | ||
![]() |
1c668f21bf | ||
![]() |
a244dcd261 | ||
![]() |
aaf2fde3cf | ||
![]() |
607a24632a | ||
![]() |
89d7a19f3f | ||
![]() |
a9017da294 | ||
![]() |
1c1ce4b0e3 | ||
![]() |
e31525a803 | ||
![]() |
7b26c5994c | ||
![]() |
248f9d5e81 | ||
![]() |
a2a78f40da | ||
![]() |
2c74e790cb | ||
![]() |
b660f96220 | ||
![]() |
5dd3babc4e | ||
![]() |
5425dc241f | ||
![]() |
44f2b1c644 | ||
![]() |
573d395b69 | ||
![]() |
df1fceb291 | ||
![]() |
9650122736 | ||
![]() |
c99ef68b7b | ||
![]() |
a95ba47b74 | ||
![]() |
3fd193eb8b | ||
![]() |
6fbc552ed1 | ||
![]() |
247665513a | ||
![]() |
351e1e55c2 | ||
![]() |
8a27f5f277 | ||
![]() |
befb508f7a | ||
![]() |
e6c820792a | ||
![]() |
30ccbfa695 | ||
![]() |
7e12259ec4 | ||
![]() |
244eeb18aa | ||
![]() |
1108d78a06 | ||
![]() |
ebe2047ae0 | ||
![]() |
c7f4e6a459 | ||
![]() |
833b31129b | ||
![]() |
86d5752ed7 | ||
![]() |
9babbc43fc | ||
![]() |
a2451be5bc | ||
![]() |
eea414e04b | ||
![]() |
0bd98b3c27 | ||
![]() |
a065de4fbc | ||
![]() |
fa5ecb090f | ||
![]() |
4f9c711012 | ||
![]() |
12924fb3fa | ||
![]() |
16b4b6d57c | ||
![]() |
c28224f3f8 | ||
![]() |
3b0b8d0baa | ||
![]() |
b30270a3fb | ||
![]() |
cb64e5d24c | ||
![]() |
a4e70b9efe | ||
![]() |
4e3e721650 | ||
![]() |
c6e135195a | ||
![]() |
1262e13067 | ||
![]() |
c659c14c5a | ||
![]() |
5ad5c8cdc7 | ||
![]() |
27cb958c2e | ||
![]() |
155ad45848 | ||
![]() |
7535db377f | ||
![]() |
a0fdceeae7 | ||
![]() |
c6e5c0f5f1 | ||
![]() |
ca997c15ca | ||
![]() |
4c381596a9 | ||
![]() |
7bcdb46148 | ||
![]() |
d33ccd042f | ||
![]() |
08f479f3e1 | ||
![]() |
c054d73fe6 | ||
![]() |
1f287145f4 | ||
![]() |
9c43c74131 | ||
![]() |
c516d39225 | ||
![]() |
a1283c1322 | ||
![]() |
cc88e1a66c | ||
![]() |
6aa3f934d0 | ||
![]() |
7ca007ecd5 | ||
![]() |
361a189f6a | ||
![]() |
19fd49e22d | ||
![]() |
1e8fab3fe5 | ||
![]() |
7e0456cc6c | ||
![]() |
c118e83a2b | ||
![]() |
31d44ad372 | ||
![]() |
f810a0892d | ||
![]() |
5de89b5ec5 | ||
![]() |
0347c83620 | ||
![]() |
eb0fdb2cfc | ||
![]() |
e154d1366c | ||
![]() |
34237ef033 | ||
![]() |
b13c4a81fc | ||
![]() |
c46b04d548 | ||
![]() |
afacf68825 | ||
![]() |
60a3b6a9f9 | ||
![]() |
b97d588392 | ||
![]() |
6e716c5607 | ||
![]() |
a969d2091a | ||
![]() |
e285947716 | ||
![]() |
cfbd5e9851 | ||
![]() |
122db78e84 | ||
![]() |
5c69b3d5a8 | ||
![]() |
335f30c887 | ||
![]() |
6a9bd686d5 | ||
![]() |
6890dd7201 | ||
![]() |
762e5dee14 | ||
![]() |
fa18365c21 | ||
![]() |
feadf64ee3 | ||
![]() |
553ee9c7ae | ||
![]() |
9f8e010534 | ||
![]() |
14ba72dd1c | ||
![]() |
be0d35394c | ||
![]() |
b14a15e66b | ||
![]() |
ae5f5cb13c | ||
![]() |
ccaa84c305 | ||
![]() |
357beabaeb | ||
![]() |
7469312ffc | ||
![]() |
401fcaf325 | ||
![]() |
5e8ef2f64f | ||
![]() |
8e5a6b7ce5 | ||
![]() |
279977b817 | ||
![]() |
a5ed79fc94 | ||
![]() |
660024c6f3 | ||
![]() |
d25df8321e | ||
![]() |
6b30c9f7bb | ||
![]() |
aedb8e53aa |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.vscode/
|
3
.env
3
.env
@@ -1,5 +1,2 @@
|
||||
# spa-title
|
||||
VITE_GLOB_APP_TITLE = Vben Admin
|
||||
|
||||
# spa shortname
|
||||
VITE_GLOB_APP_SHORT_NAME = vue_vben_admin
|
||||
|
@@ -4,10 +4,6 @@ VITE_USE_MOCK = true
|
||||
# public path
|
||||
VITE_PUBLIC_PATH = /
|
||||
|
||||
# Cross-domain proxy, you can configure multiple
|
||||
# Please note that no line breaks
|
||||
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/basic-api
|
||||
|
||||
|
22
.env.docker
Normal file
22
.env.docker
Normal 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=
|
@@ -1,4 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
};
|
||||
|
36
.github/workflows/deploy.yml
vendored
36
.github/workflows/deploy.yml
vendored
@@ -60,6 +60,8 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# - uses: NullVoxPopuli/action-setup-pnpm@v2
|
||||
|
||||
- name: Sed Config Base
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -67,22 +69,28 @@ jobs:
|
||||
sed -i "s#VITE_DROP_CONSOLE\s*=.*#VITE_DROP_CONSOLE = true#g" ./.env.production
|
||||
cat ./.env.production
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16.x'
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
# - name: Get yarn cache directory path
|
||||
# id: yarn-cache-dir-path
|
||||
# run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
#
|
||||
# - name: Cache dependencies
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-yarn-
|
||||
|
||||
- name: Set SSH Environment
|
||||
env:
|
||||
@@ -100,8 +108,8 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
run: |
|
||||
yarn install
|
||||
yarn run build
|
||||
pnpm install --no-frozen-lockfile
|
||||
pnpm build
|
||||
touch dist/.nojekyll
|
||||
cp dist/index.html dist/404.html
|
||||
|
||||
|
36
.github/workflows/node.js.yml
vendored
Normal file
36
.github/workflows/node.js.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, thin]
|
||||
pull_request:
|
||||
branches: [main, thin]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
@@ -8,3 +8,5 @@ node_modules
|
||||
|
||||
public
|
||||
.npmrc
|
||||
|
||||
*-lock.yaml
|
||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -104,7 +104,6 @@
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"cSpell.words": [
|
||||
"vben",
|
||||
"windi",
|
||||
"browserslist",
|
||||
"tailwindcss",
|
||||
"esnext",
|
||||
|
26
Dockerfile
Normal file
26
Dockerfile
Normal 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 "🎉 架 🎉 设 🎉 成 🎉 功 🎉"
|
22
README.md
22
README.md
@@ -9,7 +9,7 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite2`, `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.
|
||||
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
|
||||
|
||||
@@ -54,7 +54,7 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
|
||||
- [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://2x.antdv.com/docs/vue/introduce-cn/) - ui basic use
|
||||
- [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
|
||||
@@ -86,6 +86,24 @@ pnpm serve
|
||||
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)
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
## 简介
|
||||
|
||||
Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
|
||||
Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite4`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
|
||||
|
||||
## 特性
|
||||
|
||||
@@ -54,7 +54,7 @@ Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3
|
||||
- [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://2x.antdv.com/docs/vue/introduce-cn/) - ui 基本使用
|
||||
- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui 基本使用
|
||||
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
|
||||
|
||||
## 安装使用
|
||||
@@ -86,6 +86,24 @@ pnpm serve
|
||||
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)
|
||||
|
@@ -11,9 +11,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.1",
|
||||
"koa": "^2.14.1",
|
||||
"koa": "^2.14.2",
|
||||
"koa-body": "^6.0.1",
|
||||
"koa-bodyparser": "^4.4.0",
|
||||
"koa-bodyparser": "^4.4.1",
|
||||
"koa-route": "^3.2.0",
|
||||
"koa-router": "^12.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
@@ -24,13 +24,13 @@
|
||||
"@types/koa": "^2.13.6",
|
||||
"@types/koa-bodyparser": "^5.0.2",
|
||||
"@types/koa-router": "^7.4.4",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/node": "^20.4.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"pm2": "^5.3.0",
|
||||
"rimraf": "^4.4.1",
|
||||
"rimraf": "^5.0.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsup": "^6.7.0",
|
||||
"typescript": "^5.0.3"
|
||||
"tsup": "^7.1.0",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
@@ -1,107 +0,0 @@
|
||||
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: 自定义',
|
||||
},
|
||||
};
|
@@ -1,15 +0,0 @@
|
||||
|
||||
*.sh
|
||||
node_modules
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
/public
|
||||
/docs
|
||||
.husky
|
||||
.local
|
||||
/bin
|
||||
Dockerfile
|
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben'],
|
||||
extends: ['@vben/eslint-config/strict'],
|
||||
};
|
||||
|
@@ -1,10 +0,0 @@
|
||||
dist
|
||||
.local
|
||||
.output.js
|
||||
node_modules
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
public
|
||||
.npmrc
|
@@ -1,19 +0,0 @@
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
public
|
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben/stylelint-config'],
|
||||
};
|
@@ -2,7 +2,7 @@ import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
entries: ['src/index'],
|
||||
entries: ['src/index', 'src/strict'],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
|
@@ -17,6 +17,11 @@
|
||||
"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",
|
||||
@@ -31,12 +36,14 @@
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.10.0",
|
||||
"vue-eslint-parser": "^9.1.1"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ export default {
|
||||
createDefaultProgram: false,
|
||||
extraFileExtensions: ['.vue'],
|
||||
},
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
plugins: ['vue', '@typescript-eslint', 'import'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
@@ -25,21 +25,15 @@ export default {
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'no-case-declarations': 'off',
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': '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',
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'import/first': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-duplicates': 'error',
|
||||
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
@@ -47,15 +41,19 @@ export default {
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'@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',
|
||||
@@ -78,5 +76,16 @@ export default {
|
||||
},
|
||||
],
|
||||
'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' },
|
||||
};
|
||||
|
57
internal/eslint-config/src/strict.ts
Normal file
57
internal/eslint-config/src/strict.ts
Normal 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',
|
||||
},
|
||||
};
|
@@ -1,107 +0,0 @@
|
||||
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: 自定义',
|
||||
},
|
||||
};
|
@@ -1,15 +0,0 @@
|
||||
|
||||
*.sh
|
||||
node_modules
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
/public
|
||||
/docs
|
||||
.husky
|
||||
.local
|
||||
/bin
|
||||
Dockerfile
|
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben'],
|
||||
extends: ['@vben/eslint-config/strict'],
|
||||
};
|
||||
|
@@ -1,10 +0,0 @@
|
||||
dist
|
||||
.local
|
||||
.output.js
|
||||
node_modules
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
public
|
||||
.npmrc
|
@@ -1,19 +0,0 @@
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
public
|
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben/stylelint-config'],
|
||||
};
|
@@ -31,18 +31,18 @@
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss": "^8.4.21",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.7",
|
||||
"stylelint": "^15.4.0",
|
||||
"prettier": "^2.8.8",
|
||||
"stylelint": "^15.10.1",
|
||||
"stylelint-config-property-sort-order-smacss": "^9.1.0",
|
||||
"stylelint-config-recommended": "^11.0.0",
|
||||
"stylelint-config-recommended-scss": "^9.0.1",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-recommended-scss": "^12.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.4.0",
|
||||
"stylelint-config-standard": "^32.0.0",
|
||||
"stylelint-config-standard-scss": "^7.0.1",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-config-standard-scss": "^10.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"stylelint-prettier": "^3.0.0"
|
||||
}
|
||||
|
@@ -22,6 +22,9 @@ export default {
|
||||
},
|
||||
],
|
||||
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': [
|
||||
|
@@ -15,10 +15,11 @@
|
||||
"files": [
|
||||
"base.json",
|
||||
"node.json",
|
||||
"vue.json",
|
||||
"vue-app.json",
|
||||
"node-server.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/node": "^18.15.11"
|
||||
"@types/node": "^20.4.0",
|
||||
"vite": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Vue Library",
|
||||
"display": "Vue Application",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
@@ -1,107 +0,0 @@
|
||||
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: 自定义',
|
||||
},
|
||||
};
|
@@ -1,15 +0,0 @@
|
||||
|
||||
*.sh
|
||||
node_modules
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
/public
|
||||
/docs
|
||||
.husky
|
||||
.local
|
||||
/bin
|
||||
Dockerfile
|
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben'],
|
||||
extends: ['@vben/eslint-config/strict'],
|
||||
};
|
||||
|
@@ -1,10 +0,0 @@
|
||||
dist
|
||||
.local
|
||||
.output.js
|
||||
node_modules
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
public
|
||||
.npmrc
|
@@ -1,19 +0,0 @@
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
public
|
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben/stylelint-config'],
|
||||
};
|
@@ -31,24 +31,28 @@
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"dependencies": {
|
||||
"vite": "^4.3.0-beta.1"
|
||||
"@ant-design/colors": "^7.0.0",
|
||||
"vite": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"ant-design-vue": "^3.2.16",
|
||||
"dayjs": "^1.11.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"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.2",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"sass": "^1.60.0",
|
||||
"pkg-types": "^1.0.3",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.63.6",
|
||||
"unocss": "^0.53.4",
|
||||
"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",
|
||||
"vite-plugin-windicss": "^1.8.10"
|
||||
"vite-plugin-svg-icons": "^2.0.1"
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,18 @@
|
||||
import { type UserConfig, defineConfig, mergeConfig, loadEnv } from 'vite';
|
||||
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';
|
||||
import { createPlugins } from '../plugins';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface DefineOptions {
|
||||
overrides?: UserConfig;
|
||||
options?: {};
|
||||
options?: {
|
||||
//
|
||||
};
|
||||
}
|
||||
|
||||
function defineApplicationConfig(defineOptions: DefineOptions = {}) {
|
||||
@@ -17,7 +21,10 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
|
||||
return defineConfig(async ({ command, mode }) => {
|
||||
const root = process.cwd();
|
||||
const isBuild = command === 'build';
|
||||
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_ENABLE_ANALYZE } = loadEnv(mode, root);
|
||||
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({
|
||||
@@ -31,13 +38,7 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
|
||||
const pathResolve = (pathname: string) => resolve(root, '.', pathname);
|
||||
|
||||
const applicationConfig: UserConfig = {
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'@iconify/iconify',
|
||||
'ant-design-vue/es/locale/zh_CN',
|
||||
'ant-design-vue/es/locale/en_US',
|
||||
],
|
||||
},
|
||||
base: VITE_PUBLIC_PATH,
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
@@ -72,9 +73,11 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
|
||||
cssTarget: 'chrome80',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 入口文件名
|
||||
entryFileNames: 'assets/[name].js',
|
||||
manualChunks: {
|
||||
vue: ['vue', 'pinia', 'vue-router'],
|
||||
antdv: ['ant-design-vue', '@ant-design/icons-vue'],
|
||||
antd: ['ant-design-vue', '@ant-design/icons-vue'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -90,7 +93,7 @@ function defineApplicationConfig(defineOptions: DefineOptions = {}) {
|
||||
plugins,
|
||||
};
|
||||
|
||||
const mergedConfig = mergeConfig(commonConfig, applicationConfig);
|
||||
const mergedConfig = mergeConfig(commonConfig(mode), applicationConfig);
|
||||
|
||||
return mergeConfig(mergedConfig, overrides);
|
||||
});
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { presetTypography, presetUno } from 'unocss';
|
||||
import UnoCSS from 'unocss/vite';
|
||||
import { type UserConfig } from 'vite';
|
||||
|
||||
const commonConfig: UserConfig = {
|
||||
const commonConfig: (mode: string) => UserConfig = (mode) => ({
|
||||
server: {
|
||||
host: true,
|
||||
},
|
||||
esbuild: {
|
||||
drop: ['console', 'debugger'],
|
||||
drop: mode === 'production' ? ['console', 'debugger'] : [],
|
||||
},
|
||||
build: {
|
||||
reportCompressedSize: false,
|
||||
@@ -15,6 +17,11 @@ const commonConfig: UserConfig = {
|
||||
maxParallelFileOps: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
plugins: [
|
||||
UnoCSS({
|
||||
presets: [presetUno(), presetTypography()],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export { commonConfig };
|
||||
|
@@ -1,5 +1,42 @@
|
||||
function definePackageConfig() {
|
||||
// TODO:
|
||||
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 };
|
||||
|
@@ -1 +1,2 @@
|
||||
export * from './config/application';
|
||||
export * from './config/package';
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import colors from 'picocolors';
|
||||
import { readPackageJSON } from 'pkg-types';
|
||||
import { type PluginOption } from 'vite';
|
||||
|
||||
import { getEnvConfig } from '../utils/env';
|
||||
import { createContentHash } from '../utils/hash';
|
||||
import { readPackageJSON } from 'pkg-types';
|
||||
import colors from 'picocolors';
|
||||
|
||||
const GLOBAL_CONFIG_FILE_NAME = '_app.config.js';
|
||||
const PLUGIN_NAME = 'app-config';
|
||||
@@ -26,7 +27,8 @@ async function createAppConfigPlugin({
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
async configResolved(_config) {
|
||||
const appTitle = _config?.env?.VITE_GLOB_APP_SHORT_NAME ?? '';
|
||||
const appTitle = _config?.env?.VITE_GLOB_APP_TITLE ?? '';
|
||||
// appTitle = appTitle.replace(/\s/g, '_').replace(/-/g, '_');
|
||||
publicPath = _config.base;
|
||||
source = await getConfigSource(appTitle);
|
||||
},
|
||||
@@ -35,7 +37,7 @@ async function createAppConfigPlugin({
|
||||
|
||||
const appConfigSrc = `${
|
||||
publicPath || '/'
|
||||
}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}}`;
|
||||
}${GLOBAL_CONFIG_FILE_NAME}?v=${version}-${createContentHash(source)}`;
|
||||
|
||||
return {
|
||||
html,
|
||||
@@ -72,7 +74,15 @@ async function createAppConfigPlugin({
|
||||
* @param env
|
||||
*/
|
||||
const getVariableName = (title: string) => {
|
||||
return `__PRODUCTION__${title || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
|
||||
function strToHex(str: string) {
|
||||
const result: string[] = [];
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
const hex = str.charCodeAt(i).toString(16);
|
||||
result.push(('000' + hex).slice(-4));
|
||||
}
|
||||
return result.join('').toUpperCase();
|
||||
}
|
||||
return `__PRODUCTION__${strToHex(title) || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
|
||||
};
|
||||
|
||||
async function getConfigSource(appTitle: string) {
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { type PluginOption } from 'vite';
|
||||
import { configHtmlPlugin } from './html';
|
||||
import { configMockPlugin } from './mock';
|
||||
import { configCompressPlugin } from './compress';
|
||||
import { configVisualizerConfig } from './visualizer';
|
||||
import { configSvgIconsPlugin } from './svgSprite';
|
||||
import { createAppConfigPlugin } from './appConfig';
|
||||
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 windiCSS from 'vite-plugin-windicss';
|
||||
|
||||
import { createAppConfigPlugin } from './appConfig';
|
||||
import { configCompressPlugin } from './compress';
|
||||
import { configHtmlPlugin } from './html';
|
||||
import { configMockPlugin } from './mock';
|
||||
import { configSvgIconsPlugin } from './svgSprite';
|
||||
import { configVisualizerConfig } from './visualizer';
|
||||
|
||||
interface Options {
|
||||
isBuild: boolean;
|
||||
@@ -24,9 +24,6 @@ async function createPlugins({ isBuild, root, enableMock, compress, enableAnalyz
|
||||
const appConfigPlugin = await createAppConfigPlugin({ root, isBuild });
|
||||
vitePlugins.push(appConfigPlugin);
|
||||
|
||||
// vite-plugin-windicss
|
||||
vitePlugins.push(windiCSS());
|
||||
|
||||
// vite-plugin-html
|
||||
vitePlugins.push(configHtmlPlugin({ isBuild }));
|
||||
|
||||
|
@@ -3,9 +3,10 @@
|
||||
* https://github.com/anncwb/vite-plugin-svg-icons
|
||||
*/
|
||||
|
||||
import { createSvgIconsPlugin } from '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({
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Package file volume analysis
|
||||
*/
|
||||
import { type PluginOption } from 'vite';
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import { type PluginOption } from 'vite';
|
||||
|
||||
export function configVisualizerConfig() {
|
||||
return visualizer({
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
import { readFile } from 'fs-extra';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/**
|
||||
* 获取当前环境下生效的配置文件名
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { generate } from '@ant-design/colors';
|
||||
import { resolve } from 'node:path';
|
||||
// @ts-ignore
|
||||
|
||||
import { generate } from '@ant-design/colors';
|
||||
// @ts-ignore: typo
|
||||
import { getThemeVariables } from 'ant-design-vue/dist/theme';
|
||||
|
||||
const primaryColor = '#0960bd';
|
||||
|
@@ -16,14 +16,14 @@ import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||
// return pre;
|
||||
// }, [] as any[]);
|
||||
|
||||
const modules = import.meta.globEager('./**/*.ts');
|
||||
const modules = import.meta.glob('./**/*.ts', { eager: true });
|
||||
|
||||
const mockModules: any[] = [];
|
||||
Object.keys(modules).forEach((key) => {
|
||||
if (key.includes('/_')) {
|
||||
return;
|
||||
}
|
||||
mockModules.push(...modules[key].default);
|
||||
mockModules.push(...(modules as Recordable)[key].default);
|
||||
});
|
||||
|
||||
/**
|
||||
|
38
nginx.conf
Normal file
38
nginx.conf
Normal file
@@ -0,0 +1,38 @@
|
||||
#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;
|
||||
}
|
||||
if ($request_filename ~* ^.*?.(html|htm|js)$) {
|
||||
add_header Cache-Control no-cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
package.json
88
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vben-admin",
|
||||
"version": "2.9.1",
|
||||
"version": "2.10.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vbenjs/vue-vben-admin/issues"
|
||||
@@ -17,10 +17,11 @@
|
||||
},
|
||||
"scripts": {
|
||||
"bootstrap": "pnpm install",
|
||||
"build": "NODE_ENV=production pnpm vite build",
|
||||
"build:analyze": "pnpm vite build --mode analyze",
|
||||
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build",
|
||||
"build:analyze": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode analyze",
|
||||
"build:docker": "vite build --mode docker",
|
||||
"build:no-cache": "pnpm clean:cache && npm run build",
|
||||
"build:test": "pnpm vite build --mode test",
|
||||
"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",
|
||||
@@ -28,7 +29,7 @@
|
||||
"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/",
|
||||
"lint:stylelint": "stylelint \"**/*.{vue,css,less,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"prepare": "husky install",
|
||||
"preview": "npm run build && vite preview",
|
||||
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
|
||||
@@ -66,22 +67,21 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.0.0",
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"@iconify/iconify": "^3.1.0",
|
||||
"@logicflow/core": "^1.2.1",
|
||||
"@logicflow/extension": "^1.2.1",
|
||||
"@vue/runtime-core": "^3.2.47",
|
||||
"@vue/shared": "^3.2.47",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@vueuse/shared": "^9.13.0",
|
||||
"@zxcvbn-ts/core": "^2.2.1",
|
||||
"ant-design-vue": "^3.2.17",
|
||||
"axios": "^1.3.4",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@logicflow/core": "^1.2.9",
|
||||
"@logicflow/extension": "^1.2.9",
|
||||
"@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.2",
|
||||
"axios": "^1.4.0",
|
||||
"codemirror": "^5.65.12",
|
||||
"cropperjs": "^1.5.13",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"dayjs": "^1.11.9",
|
||||
"echarts": "^5.4.2",
|
||||
"exceljs": "^4.3.0",
|
||||
"intro.js": "^7.0.1",
|
||||
@@ -89,62 +89,62 @@
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pinia": "2.0.33",
|
||||
"pinia": "2.1.4",
|
||||
"print-js": "^1.6.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qs": "^6.11.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"showdown": "^2.1.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tinymce": "^5.10.7",
|
||||
"vditor": "^3.9.1",
|
||||
"vue": "^3.2.47",
|
||||
"vditor": "^3.9.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-json-pretty": "^2.2.4",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-types": "^5.0.2",
|
||||
"vue-router": "^4.2.3",
|
||||
"vue-types": "^5.1.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-table": "^4.3.11",
|
||||
"vxe-table": "^4.4.5",
|
||||
"vxe-table-plugin-export-xlsx": "^3.0.4",
|
||||
"xe-utils": "^3.5.7",
|
||||
"xe-utils": "^3.5.11",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.5.1",
|
||||
"@iconify/json": "^2.2.45",
|
||||
"@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.7",
|
||||
"@types/codemirror": "^5.60.8",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/mockjs": "^1.0.7",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/showdown": "^2.0.0",
|
||||
"@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:*",
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue/compiler-sfc": "^3.2.47",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"@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",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "13.2.0",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier-plugin-packagejson": "^2.4.3",
|
||||
"rimraf": "^4.4.1",
|
||||
"turbo": "^1.8.8",
|
||||
"typescript": "^5.0.3",
|
||||
"unbuild": "^1.2.0",
|
||||
"vite": "^4.3.0-beta.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.2.0"
|
||||
"vue-tsc": "^1.8.4"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.0",
|
||||
"engines": {
|
||||
|
4
packages/hooks/.eslintrc.js
Normal file
4
packages/hooks/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben/eslint-config/strict'],
|
||||
};
|
10
packages/hooks/build.config.ts
Normal file
10
packages/hooks/build.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
entries: ['src/index'],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
});
|
38
packages/hooks/package.json
Normal file
38
packages/hooks/package.json
Normal 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:*"
|
||||
}
|
||||
}
|
6
packages/hooks/src/index.ts
Normal file
6
packages/hooks/src/index.ts
Normal 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';
|
25
packages/hooks/src/onMountedOrActivated.ts
Normal file
25
packages/hooks/src/onMountedOrActivated.ts
Normal 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 };
|
@@ -1,6 +1,7 @@
|
||||
import { type Recordable } from '@vben/types';
|
||||
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
interface Params {
|
||||
|
||||
interface UseAttrsOptions {
|
||||
excludeListeners?: boolean;
|
||||
excludeKeys?: string[];
|
||||
excludeDefaultKeys?: boolean;
|
||||
@@ -9,15 +10,15 @@ interface Params {
|
||||
const DEFAULT_EXCLUDE_KEYS = ['class', 'style'];
|
||||
const LISTENER_PREFIX = /^on[A-Z]/;
|
||||
|
||||
export function entries<T>(obj: Recordable<T>): [string, T][] {
|
||||
function entries<T>(obj: Recordable<T>): [string, T][] {
|
||||
return Object.keys(obj).map((key: string) => [key, obj[key]]);
|
||||
}
|
||||
|
||||
export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
|
||||
function useAttrs(options: UseAttrsOptions = {}): Recordable<any> {
|
||||
const instance = getCurrentInstance();
|
||||
if (!instance) return {};
|
||||
|
||||
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params;
|
||||
const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = options;
|
||||
const attrs = shallowRef({});
|
||||
const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []);
|
||||
|
||||
@@ -31,10 +32,12 @@ export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
|
||||
}
|
||||
|
||||
return acm;
|
||||
}, {} as Recordable);
|
||||
}, {} as Recordable<any>);
|
||||
|
||||
attrs.value = res;
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
export { useAttrs, type UseAttrsOptions };
|
24
packages/hooks/src/useRefs.ts
Normal file
24
packages/hooks/src/useRefs.ts
Normal 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 };
|
@@ -1,35 +1,34 @@
|
||||
import { isFunction, isUnDef } from '/@/utils/is';
|
||||
import { ref, unref } from 'vue';
|
||||
import { shallowRef, unref } from 'vue';
|
||||
|
||||
export interface ScrollToParams {
|
||||
interface UseScrollToOptions {
|
||||
el: any;
|
||||
to: number;
|
||||
duration?: number;
|
||||
callback?: () => any;
|
||||
}
|
||||
|
||||
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
|
||||
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;
|
||||
};
|
||||
const move = (el: HTMLElement, amount: number) => {
|
||||
}
|
||||
|
||||
function move(el: HTMLElement, amount: number) {
|
||||
el.scrollTop = amount;
|
||||
};
|
||||
}
|
||||
|
||||
const position = (el: HTMLElement) => {
|
||||
return el.scrollTop;
|
||||
};
|
||||
export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams) {
|
||||
const isActiveRef = ref(false);
|
||||
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;
|
||||
duration = isUnDef(duration) ? 500 : duration;
|
||||
|
||||
const animateScroll = function () {
|
||||
if (!unref(isActiveRef)) {
|
||||
@@ -41,7 +40,7 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
|
||||
if (currentTime < duration && unref(isActiveRef)) {
|
||||
requestAnimationFrame(animateScroll);
|
||||
} else {
|
||||
if (callback && isFunction(callback)) {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
@@ -57,3 +56,5 @@ export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams
|
||||
|
||||
return { start: run, stop };
|
||||
}
|
||||
|
||||
export { useScrollTo, type UseScrollToOptions };
|
@@ -1,12 +1,15 @@
|
||||
import { type AnyFunction } from '@vben/types';
|
||||
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core';
|
||||
|
||||
interface WindowSizeOptions {
|
||||
interface UseWindowSizeOptions {
|
||||
wait?: number;
|
||||
once?: boolean;
|
||||
immediate?: boolean;
|
||||
listenerOptions?: AddEventListenerOptions | boolean;
|
||||
}
|
||||
|
||||
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
|
||||
function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) {
|
||||
const { wait = 150, immediate } = options;
|
||||
let handler = () => {
|
||||
fn();
|
||||
};
|
||||
@@ -14,7 +17,7 @@ export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOp
|
||||
handler = handleSize;
|
||||
|
||||
const start = () => {
|
||||
if (options && options.immediate) {
|
||||
if (immediate) {
|
||||
handler();
|
||||
}
|
||||
window.addEventListener('resize', handler);
|
||||
@@ -31,5 +34,7 @@ export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOp
|
||||
tryOnUnmounted(() => {
|
||||
stop();
|
||||
});
|
||||
return [start, stop];
|
||||
return { start, stop };
|
||||
}
|
||||
|
||||
export { useWindowSizeFn, type UseWindowSizeOptions };
|
5
packages/hooks/tsconfig.json
Normal file
5
packages/hooks/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/vue-app.json",
|
||||
"include": ["src"]
|
||||
}
|
4
packages/types/.eslintrc.js
Normal file
4
packages/types/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@vben/eslint-config/strict'],
|
||||
};
|
10
packages/types/build.config.ts
Normal file
10
packages/types/build.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
entries: ['src/index'],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
});
|
31
packages/types/package.json
Normal file
31
packages/types/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@vben/types",
|
||||
"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/types"
|
||||
},
|
||||
"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 ."
|
||||
}
|
||||
}
|
1
packages/types/src/index.ts
Normal file
1
packages/types/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './utils';
|
58
packages/types/src/utils.ts
Normal file
58
packages/types/src/utils.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 任意类型的异步函数
|
||||
*/
|
||||
type AnyPromiseFunction = (...arg: any[]) => PromiseLike<any>;
|
||||
|
||||
/**
|
||||
* 任意类型的普通函数
|
||||
*/
|
||||
type AnyNormalFunction = (...arg: any[]) => any;
|
||||
|
||||
/**
|
||||
* 任意类型的函数
|
||||
*/
|
||||
type AnyFunction = AnyNormalFunction | AnyPromiseFunction;
|
||||
|
||||
/**
|
||||
* T | null 包装
|
||||
*/
|
||||
type Nullable<T> = T | null;
|
||||
|
||||
/**
|
||||
* T | Not null 包装
|
||||
*/
|
||||
type NonNullable<T> = T extends null | undefined ? never : T;
|
||||
|
||||
/**
|
||||
* 字符串类型对象
|
||||
*/
|
||||
type Recordable<T> = Record<string, T>;
|
||||
|
||||
/**
|
||||
* 字符串类型对象(只读)
|
||||
*/
|
||||
interface ReadonlyRecordable<T = any> {
|
||||
readonly [key: string]: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* setTimeout 返回值类型
|
||||
*/
|
||||
type TimeoutHandle = ReturnType<typeof setTimeout>;
|
||||
|
||||
/**
|
||||
* setInterval 返回值类型
|
||||
*/
|
||||
type IntervalHandle = ReturnType<typeof setInterval>;
|
||||
|
||||
export {
|
||||
type AnyFunction,
|
||||
type AnyNormalFunction,
|
||||
type AnyPromiseFunction,
|
||||
type IntervalHandle,
|
||||
type NonNullable,
|
||||
type Nullable,
|
||||
type ReadonlyRecordable,
|
||||
type Recordable,
|
||||
type TimeoutHandle,
|
||||
};
|
5
packages/types/tsconfig.json
Normal file
5
packages/types/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/ts-config/vue-app.json",
|
||||
"include": ["src"]
|
||||
}
|
6120
pnpm-lock.yaml
generated
6120
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
12
src/App.vue
12
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ConfigProvider :locale="getAntdLocale">
|
||||
<ConfigProvider :locale="getAntdLocale" :theme="isDark ? darkTheme : {}">
|
||||
<AppProvider>
|
||||
<RouterView />
|
||||
</AppProvider>
|
||||
@@ -8,14 +8,18 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ConfigProvider } from 'ant-design-vue';
|
||||
import { AppProvider } from '/@/components/Application';
|
||||
import { useTitle } from '/@/hooks/web/useTitle';
|
||||
import { useLocale } from '/@/locales/useLocale';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
import { useTitle } from '@/hooks/web/useTitle';
|
||||
import { useLocale } from '@/locales/useLocale';
|
||||
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import { useDarkModeTheme } from '@/hooks/setting/useDarkModeTheme';
|
||||
|
||||
// support Multi-language
|
||||
const { getAntdLocale } = useLocale();
|
||||
|
||||
const { isDark, darkTheme } = useDarkModeTheme();
|
||||
|
||||
// Listening to page changes and dynamically changing site titles
|
||||
useTitle();
|
||||
</script>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { BasicFetchResult } from '/@/api/model/baseModel';
|
||||
|
||||
export interface DemoOptionsItem {
|
||||
label: string;
|
||||
value: string;
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface selectParams {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { DemoOptionsItem, selectParams } from './model/optionsModel';
|
||||
|
||||
enum Api {
|
||||
OPTIONS_LIST = '/select/getDemoOptions',
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { RouteMeta } from 'vue-router';
|
||||
|
||||
export interface RouteItem {
|
||||
path: string;
|
||||
component: any;
|
||||
|
@@ -17,7 +17,7 @@ export interface RoleInfo {
|
||||
export interface LoginResultModel {
|
||||
userId: string | number;
|
||||
token: string;
|
||||
role: RoleInfo;
|
||||
roles: RoleInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -4,7 +4,7 @@ import appLogo from './src/AppLogo.vue';
|
||||
import appProvider from './src/AppProvider.vue';
|
||||
import appSearch from './src/search/AppSearch.vue';
|
||||
import appLocalePicker from './src/AppLocalePicker.vue';
|
||||
// import appDarkModeToggle from './src/AppDarkModeToggle.vue';
|
||||
import appDarkModeToggle from './src/AppDarkModeToggle.vue';
|
||||
|
||||
export { useAppProviderContext } from './src/useAppContext';
|
||||
|
||||
@@ -12,4 +12,4 @@ export const AppLogo = withInstall(appLogo);
|
||||
export const AppProvider = withInstall(appProvider);
|
||||
export const AppSearch = withInstall(appSearch);
|
||||
export const AppLocalePicker = withInstall(appLocalePicker);
|
||||
// export const AppDarkModeToggle = withInstall(appDarkModeToggle);
|
||||
export const AppDarkModeToggle = withInstall(appDarkModeToggle);
|
||||
|
@@ -22,7 +22,7 @@
|
||||
import type { DropMenu } from '/@/components/Dropdown';
|
||||
import { ref, watchEffect, unref, computed } from 'vue';
|
||||
import { Dropdown } from '/@/components/Dropdown';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
import { useLocale } from '/@/locales/useLocale';
|
||||
import { localeList } from '/@/settings/localeSetting';
|
||||
|
||||
|
@@ -14,6 +14,7 @@
|
||||
import AppSearchKeyItem from './AppSearchKeyItem.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { prefixCls } = useDesign('app-search-footer');
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
@@ -4,7 +4,8 @@
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
|
||||
defineProps({
|
||||
icon: String,
|
||||
});
|
||||
|
@@ -61,11 +61,11 @@
|
||||
import { computed, unref, ref, watch, nextTick } from 'vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import AppSearchFooter from './AppSearchFooter.vue';
|
||||
import Icon from '/@/components/Icon';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
// @ts-ignore
|
||||
import vClickOutside from '/@/directives/clickOutside';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useRefs } from '/@/hooks/core/useRefs';
|
||||
import { useRefs } from '@vben/hooks';
|
||||
import { useMenuSearch } from './useMenuSearch';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useAppInject } from '/@/hooks/web/useAppInject';
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
const { prefixCls } = useDesign('app-search-modal');
|
||||
const [refs, setRefs] = useRefs();
|
||||
const { refs, setRefs } = useRefs();
|
||||
const { getIsMobile } = useAppInject();
|
||||
|
||||
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } =
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import type { Menu } from '/@/router/types';
|
||||
import { type Menu } from '/@/router/types';
|
||||
import { type AnyFunction } from '@vben/types';
|
||||
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
|
||||
import { getMenus } from '/@/router/menus';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { filter, forEach } from '/@/utils/helper/treeHelper';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo';
|
||||
import { useScrollTo } from '@vben/hooks';
|
||||
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
@@ -26,7 +27,7 @@ function createSearchReg(key: string) {
|
||||
return new RegExp(str);
|
||||
}
|
||||
|
||||
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
|
||||
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref, emit: AnyFunction) {
|
||||
const searchResult = ref<SearchResult[]>([]);
|
||||
const keyword = ref('');
|
||||
const activeIndex = ref(-1);
|
||||
|
@@ -9,7 +9,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
const props = defineProps({
|
||||
|
@@ -8,20 +8,18 @@
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { Button } from 'ant-design-vue';
|
||||
export default defineComponent({
|
||||
import { computed, unref } from 'vue';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
import { buttonProps } from './props';
|
||||
import { useAttrs } from '@vben/hooks';
|
||||
|
||||
defineOptions({
|
||||
name: 'AButton',
|
||||
extends: Button,
|
||||
inheritAttrs: false,
|
||||
});
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { computed, unref } from 'vue';
|
||||
import Icon from '/@/components/Icon/src/Icon.vue';
|
||||
import { buttonProps } from './props';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
|
||||
const props = defineProps(buttonProps);
|
||||
// get component class
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
||||
import { omit } from 'lodash-es';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useAttrs } from '@vben/hooks';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const props = {
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
return () => {
|
||||
const bindValues = omit(unref(getBindValues), 'icon');
|
||||
const btnBind = omit(bindValues, 'title') as Recordable;
|
||||
const btnBind = omit(bindValues, 'title') as any;
|
||||
if (btnBind.disabled) btnBind.color = '';
|
||||
const Button = h(BasicButton, btnBind, extendSlots(slots));
|
||||
|
||||
|
@@ -22,5 +22,5 @@ export const buttonProps = {
|
||||
* @default: 14
|
||||
*/
|
||||
iconSize: { type: Number, default: 14 },
|
||||
onClick: { type: Function as PropType<(...args) => any>, default: null },
|
||||
onClick: { type: [Function, Array] as PropType<(() => any) | (() => any)[]>, default: null },
|
||||
};
|
||||
|
@@ -61,7 +61,7 @@
|
||||
|
||||
<CardMeta>
|
||||
<template #title>
|
||||
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
|
||||
<TypographyParagraph :content="item.name" :ellipsis="{ tooltip: item.address }" />
|
||||
</template>
|
||||
<template #avatar>
|
||||
<Avatar :src="item.avatar" />
|
||||
@@ -90,9 +90,10 @@
|
||||
import { Button } from '/@/components/Button';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { useSlider, grid } from './data';
|
||||
|
||||
const ListItem = List.Item;
|
||||
const CardMeta = Card.Meta;
|
||||
const TypographyText = Typography.Text;
|
||||
const TypographyParagraph = Typography.Paragraph;
|
||||
// 获取slider属性
|
||||
const sliderProp = computed(() => useSlider(4));
|
||||
// 组件接收参数
|
||||
|
@@ -6,8 +6,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
const emit = defineEmits(['mounted', 'clickOutside']);
|
||||
const wrap = ref<ElRef>(null);
|
||||
const wrap = ref(null);
|
||||
|
||||
onClickOutside(wrap, () => {
|
||||
emit('clickOutside');
|
||||
|
@@ -3,10 +3,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue';
|
||||
import {
|
||||
type PropType,
|
||||
ref,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
watchEffect,
|
||||
watch,
|
||||
unref,
|
||||
nextTick,
|
||||
} from 'vue';
|
||||
import type { Nullable } from '@vben/types';
|
||||
import { useWindowSizeFn } from '@vben/hooks';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||||
import CodeMirror from 'codemirror';
|
||||
import { MODE } from './../typing';
|
||||
// css
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import collapseContainer from './src/collapse/CollapseContainer.vue';
|
||||
import scrollContainer from './src/ScrollContainer.vue';
|
||||
import lazyContainer from './src/LazyContainer.vue';
|
||||
|
||||
export const CollapseContainer = withInstall(collapseContainer);
|
||||
export const ScrollContainer = withInstall(scrollContainer);
|
||||
export const LazyContainer = withInstall(lazyContainer);
|
||||
|
||||
export * from './src/typing';
|
||||
|
@@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<transition-group
|
||||
class="h-full w-full"
|
||||
v-bind="$attrs"
|
||||
ref="elRef"
|
||||
:name="transitionName"
|
||||
:tag="tag"
|
||||
mode="out-in"
|
||||
>
|
||||
<div key="component" v-if="isInit">
|
||||
<slot :loading="loading"></slot>
|
||||
</div>
|
||||
<div key="skeleton" v-else>
|
||||
<slot name="skeleton" v-if="$slots.skeleton"></slot>
|
||||
<Skeleton v-else />
|
||||
</div>
|
||||
</transition-group>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
|
||||
|
||||
interface State {
|
||||
isInit: boolean;
|
||||
loading: boolean;
|
||||
intersectionObserverInstance: IntersectionObserver | null;
|
||||
}
|
||||
|
||||
const props = {
|
||||
/**
|
||||
* Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
|
||||
*/
|
||||
timeout: { type: Number },
|
||||
/**
|
||||
* The viewport where the component is located.
|
||||
* If the component is scrolling in the page container, the viewport is the container
|
||||
*/
|
||||
viewport: {
|
||||
type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>,
|
||||
default: () => null,
|
||||
},
|
||||
/**
|
||||
* Preload threshold, css unit
|
||||
*/
|
||||
threshold: { type: String, default: '0px' },
|
||||
/**
|
||||
* The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
|
||||
*/
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'vertical',
|
||||
validator: (v) => ['vertical', 'horizontal'].includes(v),
|
||||
},
|
||||
/**
|
||||
* The label name of the outer container that wraps the component
|
||||
*/
|
||||
tag: { type: String, default: 'div' },
|
||||
maxWaitingTime: { type: Number, default: 80 },
|
||||
/**
|
||||
* transition name
|
||||
*/
|
||||
transitionName: { type: String, default: 'lazy-container' },
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LazyContainer',
|
||||
components: { Skeleton },
|
||||
inheritAttrs: false,
|
||||
props,
|
||||
emits: ['init'],
|
||||
setup(props, { emit }) {
|
||||
const elRef = ref();
|
||||
const state = reactive<State>({
|
||||
isInit: false,
|
||||
loading: false,
|
||||
intersectionObserverInstance: null,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
immediateInit();
|
||||
initIntersectionObserver();
|
||||
});
|
||||
|
||||
// If there is a set delay time, it will be executed immediately
|
||||
function immediateInit() {
|
||||
const { timeout } = props;
|
||||
timeout &&
|
||||
useTimeoutFn(() => {
|
||||
init();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
function init() {
|
||||
state.loading = true;
|
||||
|
||||
useTimeoutFn(() => {
|
||||
if (state.isInit) return;
|
||||
state.isInit = true;
|
||||
emit('init');
|
||||
}, props.maxWaitingTime || 80);
|
||||
}
|
||||
|
||||
function initIntersectionObserver() {
|
||||
const { timeout, direction, threshold } = props;
|
||||
if (timeout) return;
|
||||
// According to the scrolling direction to construct the viewport margin, used to load in advance
|
||||
let rootMargin = '0px';
|
||||
switch (direction) {
|
||||
case 'vertical':
|
||||
rootMargin = `${threshold} 0px`;
|
||||
break;
|
||||
case 'horizontal':
|
||||
rootMargin = `0px ${threshold}`;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stop, observer } = useIntersectionObserver({
|
||||
rootMargin,
|
||||
target: toRef(elRef.value, '$el'),
|
||||
onIntersect: (entries: any[]) => {
|
||||
const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio;
|
||||
if (isIntersecting) {
|
||||
init();
|
||||
if (observer) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
root: toRef(props, 'viewport'),
|
||||
});
|
||||
} catch (e) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
return {
|
||||
elRef,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -7,7 +7,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, unref, nextTick } from 'vue';
|
||||
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar';
|
||||
import { useScrollTo } from '/@/hooks/event/useScrollTo';
|
||||
import { useScrollTo } from '@vben/hooks';
|
||||
import { type Nullable } from '@vben/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ScrollContainer',
|
||||
|
@@ -2,10 +2,10 @@
|
||||
import { ref, unref, defineComponent, type PropType, type ExtractPropTypes } from 'vue';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { useTimeoutFn } from '@vben/hooks';
|
||||
import { CollapseTransition } from '/@/components/Transition';
|
||||
import CollapseHeader from './CollapseHeader.vue';
|
||||
import { triggerWindowResize } from '/@/utils/event';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
const collapseContainerProps = {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
|
||||
import type { FunctionalComponent, CSSProperties, PropType } from 'vue';
|
||||
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
|
||||
import Icon from '/@/components/Icon';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
import { Menu, Divider } from 'ant-design-vue';
|
||||
|
||||
const prefixCls = 'context-menu';
|
||||
@@ -59,12 +59,12 @@
|
||||
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
|
||||
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
|
||||
return {
|
||||
...styles,
|
||||
position: 'absolute',
|
||||
width: `${width}px`,
|
||||
left: `${left + 1}px`,
|
||||
top: `${top + 1}px`,
|
||||
zIndex: 9999,
|
||||
...styles, // Not the first, fix options.styles.width not working
|
||||
};
|
||||
});
|
||||
|
||||
@@ -127,11 +127,15 @@
|
||||
}
|
||||
const { items } = props;
|
||||
return (
|
||||
<div class={prefixCls}>
|
||||
<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
|
||||
{renderMenuItem(items)}
|
||||
</Menu>
|
||||
</div>
|
||||
<Menu
|
||||
inlineIndent={12}
|
||||
mode="vertical"
|
||||
class={prefixCls}
|
||||
ref={wrapRef}
|
||||
style={unref(getStyle)}
|
||||
>
|
||||
{renderMenuItem(items)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
},
|
||||
@@ -147,7 +151,7 @@
|
||||
.item-style() {
|
||||
li {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
width: 100% !important;
|
||||
height: @default-height;
|
||||
margin: 0 !important;
|
||||
line-height: @default-height;
|
||||
@@ -157,6 +161,8 @@
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
@@ -176,10 +182,12 @@
|
||||
width: 156px;
|
||||
margin: 0;
|
||||
border: 1px solid rgb(0 0 0 / 8%);
|
||||
border-radius: 0.25rem;
|
||||
border-radius: 8px;
|
||||
background-clip: padding-box;
|
||||
background-color: @component-background;
|
||||
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 10%),
|
||||
box-shadow:
|
||||
0 2px 2px 0 rgb(0 0 0 / 14%),
|
||||
0 3px 1px -2px rgb(0 0 0 / 10%),
|
||||
0 1px 5px 0 rgb(0 0 0 / 6%);
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
|
@@ -20,11 +20,12 @@
|
||||
{{ btnText ? btnText : t('component.cropper.selectImage') }}
|
||||
</a-button>
|
||||
|
||||
<CopperModal
|
||||
<CropperModal
|
||||
@register="register"
|
||||
@upload-success="handleUploadSuccess"
|
||||
:uploadApi="uploadApi"
|
||||
:src="sourceValue"
|
||||
:size="size"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,13 +40,13 @@
|
||||
watch,
|
||||
PropType,
|
||||
} from 'vue';
|
||||
import CopperModal from './CopperModal.vue';
|
||||
import CropperModal from './CropperModal.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import type { ButtonProps } from '/@/components/Button';
|
||||
import Icon from '/@/components/Icon';
|
||||
import Icon from '@/components/Icon/Icon.vue';
|
||||
|
||||
const props = {
|
||||
width: { type: [String, Number], default: '200px' },
|
||||
@@ -54,11 +55,12 @@
|
||||
btnProps: { type: Object as PropType<ButtonProps> },
|
||||
btnText: { type: String, default: '' },
|
||||
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
|
||||
size: { type: Number, default: 5 },
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CropperAvatar',
|
||||
components: { CopperModal, Icon },
|
||||
components: { CropperModal, Icon },
|
||||
props,
|
||||
emits: ['update:value', 'change'],
|
||||
setup(props, { emit, expose }) {
|
||||
|
@@ -1,284 +1,289 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:title="t('component.cropper.modalTitle')"
|
||||
width="800px"
|
||||
:canFullscreen="false"
|
||||
@ok="handleOk"
|
||||
:okText="t('component.cropper.okText')"
|
||||
>
|
||||
<div :class="prefixCls">
|
||||
<div :class="`${prefixCls}-left`">
|
||||
<div :class="`${prefixCls}-cropper`">
|
||||
<CropperImage
|
||||
v-if="src"
|
||||
:src="src"
|
||||
height="300px"
|
||||
:circled="circled"
|
||||
@cropend="handleCropend"
|
||||
@ready="handleReady"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="`${prefixCls}-toolbar`">
|
||||
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
|
||||
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
|
||||
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
<Space>
|
||||
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:reload-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('reset')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:rotate-left-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('rotate', -45)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:rotate-right-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('rotate', 45)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="vaadin:arrows-long-h"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('scaleX')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="vaadin:arrows-long-v"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('scaleY')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:zoom-in-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('zoom', 0.1)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:zoom-out-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('zoom', -0.1)"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`${prefixCls}-right`">
|
||||
<div :class="`${prefixCls}-preview`">
|
||||
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
|
||||
</div>
|
||||
<template v-if="previewSource">
|
||||
<div :class="`${prefixCls}-group`">
|
||||
<Avatar :src="previewSource" size="large" />
|
||||
<Avatar :src="previewSource" :size="48" />
|
||||
<Avatar :src="previewSource" :size="64" />
|
||||
<Avatar :src="previewSource" :size="80" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CropendResult, Cropper } from './typing';
|
||||
|
||||
import { defineComponent, ref, PropType } from 'vue';
|
||||
import CropperImage from './Cropper.vue';
|
||||
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
type apiFunParams = { file: Blob; name: string; filename: string };
|
||||
|
||||
const props = {
|
||||
circled: { type: Boolean, default: true },
|
||||
uploadApi: {
|
||||
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
|
||||
},
|
||||
src: { type: String },
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CropperModal',
|
||||
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
|
||||
props,
|
||||
emits: ['uploadSuccess', 'register'],
|
||||
setup(props, { emit }) {
|
||||
let filename = '';
|
||||
const src = ref(props.src || '');
|
||||
const previewSource = ref('');
|
||||
const cropper = ref<Cropper>();
|
||||
let scaleX = 1;
|
||||
let scaleY = 1;
|
||||
|
||||
const { prefixCls } = useDesign('cropper-am');
|
||||
const [register, { closeModal, setModalProps }] = useModalInner();
|
||||
const { t } = useI18n();
|
||||
|
||||
// Block upload
|
||||
function handleBeforeUpload(file: File) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
src.value = '';
|
||||
previewSource.value = '';
|
||||
reader.onload = function (e) {
|
||||
src.value = (e.target?.result as string) ?? '';
|
||||
filename = file.name;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleCropend({ imgBase64 }: CropendResult) {
|
||||
previewSource.value = imgBase64;
|
||||
}
|
||||
|
||||
function handleReady(cropperInstance: Cropper) {
|
||||
cropper.value = cropperInstance;
|
||||
}
|
||||
|
||||
function handlerToolbar(event: string, arg?: number) {
|
||||
if (event === 'scaleX') {
|
||||
scaleX = arg = scaleX === -1 ? 1 : -1;
|
||||
}
|
||||
if (event === 'scaleY') {
|
||||
scaleY = arg = scaleY === -1 ? 1 : -1;
|
||||
}
|
||||
cropper?.value?.[event]?.(arg);
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
const uploadApi = props.uploadApi;
|
||||
if (uploadApi && isFunction(uploadApi)) {
|
||||
const blob = dataURLtoBlob(previewSource.value);
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
const result = await uploadApi({ name: 'file', file: blob, filename });
|
||||
emit('uploadSuccess', { source: previewSource.value, data: result.url });
|
||||
closeModal();
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
prefixCls,
|
||||
src,
|
||||
register,
|
||||
previewSource,
|
||||
handleBeforeUpload,
|
||||
handleCropend,
|
||||
handleReady,
|
||||
handlerToolbar,
|
||||
handleOk,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-cropper-am';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
height: 340px;
|
||||
}
|
||||
|
||||
&-left {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
&-right {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
&-cropper {
|
||||
height: 300px;
|
||||
background: #eee;
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
rgb(0 0 0 / 25%) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgb(0 0 0 / 25%) 0
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
rgb(0 0 0 / 25%) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgb(0 0 0 / 25%) 0
|
||||
);
|
||||
background-position: 0 0, 12px 12px;
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&-preview {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
border: 1px solid @border-color-base;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid @border-color-base;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:title="t('component.cropper.modalTitle')"
|
||||
width="800px"
|
||||
:canFullscreen="false"
|
||||
@ok="handleOk"
|
||||
:okText="t('component.cropper.okText')"
|
||||
>
|
||||
<div :class="prefixCls">
|
||||
<div :class="`${prefixCls}-left`">
|
||||
<div :class="`${prefixCls}-cropper`">
|
||||
<CropperImage
|
||||
v-if="src"
|
||||
:src="src"
|
||||
height="300px"
|
||||
:circled="circled"
|
||||
@cropend="handleCropend"
|
||||
@ready="handleReady"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="`${prefixCls}-toolbar`">
|
||||
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
|
||||
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
|
||||
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
<Space>
|
||||
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:reload-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('reset')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:rotate-left-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('rotate', -45)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:rotate-right-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('rotate', 45)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="vaadin:arrows-long-h"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('scaleX')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="vaadin:arrows-long-v"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('scaleY')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:zoom-in-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('zoom', 0.1)"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
|
||||
<a-button
|
||||
type="primary"
|
||||
preIcon="ant-design:zoom-out-outlined"
|
||||
size="small"
|
||||
:disabled="!src"
|
||||
@click="handlerToolbar('zoom', -0.1)"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`${prefixCls}-right`">
|
||||
<div :class="`${prefixCls}-preview`">
|
||||
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
|
||||
</div>
|
||||
<template v-if="previewSource">
|
||||
<div :class="`${prefixCls}-group`">
|
||||
<Avatar :src="previewSource" size="large" />
|
||||
<Avatar :src="previewSource" :size="48" />
|
||||
<Avatar :src="previewSource" :size="64" />
|
||||
<Avatar :src="previewSource" :size="80" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CropendResult, Cropper } from './typing';
|
||||
|
||||
import { defineComponent, ref, PropType } from 'vue';
|
||||
import CropperImage from './Cropper.vue';
|
||||
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
type apiFunParams = { file: Blob; name: string; filename: string };
|
||||
|
||||
const props = {
|
||||
circled: { type: Boolean, default: true },
|
||||
uploadApi: {
|
||||
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
|
||||
},
|
||||
src: { type: String },
|
||||
size: { type: Number },
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CropperModal',
|
||||
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
|
||||
props,
|
||||
emits: ['uploadSuccess', 'uploadError', 'register'],
|
||||
setup(props, { emit }) {
|
||||
let filename = '';
|
||||
const src = ref(props.src || '');
|
||||
const previewSource = ref('');
|
||||
const cropper = ref<Cropper>();
|
||||
let scaleX = 1;
|
||||
let scaleY = 1;
|
||||
|
||||
const { prefixCls } = useDesign('cropper-am');
|
||||
const [register, { closeModal, setModalProps }] = useModalInner();
|
||||
const { t } = useI18n();
|
||||
|
||||
// Block upload
|
||||
function handleBeforeUpload(file: File) {
|
||||
if (props.size && file.size > 1024 * 1024 * props.size) {
|
||||
emit('uploadError', { msg: t('component.cropper.imageTooBig') });
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
src.value = '';
|
||||
previewSource.value = '';
|
||||
reader.onload = function (e) {
|
||||
src.value = (e.target?.result as string) ?? '';
|
||||
filename = file.name;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleCropend({ imgBase64 }: CropendResult) {
|
||||
previewSource.value = imgBase64;
|
||||
}
|
||||
|
||||
function handleReady(cropperInstance: Cropper) {
|
||||
cropper.value = cropperInstance;
|
||||
}
|
||||
|
||||
function handlerToolbar(event: string, arg?: number) {
|
||||
if (event === 'scaleX') {
|
||||
scaleX = arg = scaleX === -1 ? 1 : -1;
|
||||
}
|
||||
if (event === 'scaleY') {
|
||||
scaleY = arg = scaleY === -1 ? 1 : -1;
|
||||
}
|
||||
cropper?.value?.[event]?.(arg);
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
const uploadApi = props.uploadApi;
|
||||
if (uploadApi && isFunction(uploadApi)) {
|
||||
const blob = dataURLtoBlob(previewSource.value);
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
const result = await uploadApi({ name: 'file', file: blob, filename });
|
||||
emit('uploadSuccess', { source: previewSource.value, data: result.url });
|
||||
closeModal();
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
prefixCls,
|
||||
src,
|
||||
register,
|
||||
previewSource,
|
||||
handleBeforeUpload,
|
||||
handleCropend,
|
||||
handleReady,
|
||||
handlerToolbar,
|
||||
handleOk,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-cropper-am';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
height: 340px;
|
||||
}
|
||||
|
||||
&-left {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
&-right {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
&-cropper {
|
||||
height: 300px;
|
||||
background: #eee;
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
rgb(0 0 0 / 25%) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgb(0 0 0 / 25%) 0
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
rgb(0 0 0 / 25%) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgb(0 0 0 / 25%) 0
|
||||
);
|
||||
background-position: 0 0, 12px 12px;
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&-preview {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
border: 1px solid @border-color-base;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid @border-color-base;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,16 +1,23 @@
|
||||
<script lang="tsx">
|
||||
import type { DescriptionProps, DescInstance, DescItem } from './typing';
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { CollapseContainerOptions } from '/@/components/Container/index';
|
||||
import { defineComponent, computed, ref, unref, toRefs } from 'vue';
|
||||
import {
|
||||
type CSSProperties,
|
||||
type PropType,
|
||||
defineComponent,
|
||||
computed,
|
||||
ref,
|
||||
unref,
|
||||
toRefs,
|
||||
} from 'vue';
|
||||
import { get } from 'lodash-es';
|
||||
import { Descriptions } from 'ant-design-vue';
|
||||
import { CollapseContainer } from '/@/components/Container/index';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useAttrs } from '@vben/hooks';
|
||||
|
||||
const props = {
|
||||
useCollapse: { type: Boolean, default: true },
|
||||
@@ -22,7 +29,7 @@
|
||||
},
|
||||
bordered: { type: Boolean, default: true },
|
||||
column: {
|
||||
type: [Number, Object] as PropType<number | Recordable>,
|
||||
type: [Number, Object],
|
||||
default: () => {
|
||||
return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
|
||||
},
|
||||
@@ -52,7 +59,7 @@
|
||||
const getMergeProps = computed(() => {
|
||||
return {
|
||||
...props,
|
||||
...(unref(propsRef) as Recordable),
|
||||
...(unref(propsRef) as any),
|
||||
} as DescriptionProps;
|
||||
});
|
||||
|
||||
@@ -89,7 +96,10 @@
|
||||
*/
|
||||
function setDescProps(descProps: Partial<DescriptionProps>): void {
|
||||
// Keep the last setDrawerProps
|
||||
propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable;
|
||||
propsRef.value = {
|
||||
...(unref(propsRef) as Record<string, any>),
|
||||
...descProps,
|
||||
} as Record<string, any>;
|
||||
}
|
||||
|
||||
// Prevent line breaks
|
||||
@@ -121,6 +131,7 @@
|
||||
return null;
|
||||
}
|
||||
const getField = get(_data, field);
|
||||
// eslint-disable-next-line
|
||||
if (getField && !toRefs(_data).hasOwnProperty(field)) {
|
||||
return isFunction(render) ? render('', _data) : '';
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
|
||||
<Drawer @close="onClose" v-bind="getBindValues">
|
||||
<template #title v-if="!$slots.title">
|
||||
<DrawerHeader
|
||||
:title="getMergeProps.title"
|
||||
@@ -52,15 +52,15 @@
|
||||
import { ScrollContainer } from '/@/components/Container';
|
||||
import { basicProps } from './props';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useAttrs } from '@vben/hooks';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
|
||||
inheritAttrs: false,
|
||||
props: basicProps,
|
||||
emits: ['visible-change', 'ok', 'close', 'register'],
|
||||
emits: ['open-change', 'ok', 'close', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const visibleRef = ref(false);
|
||||
const openRef = ref(false);
|
||||
const attrs = useAttrs();
|
||||
const propsRef = ref<Partial<DrawerProps | null>>(null);
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
const { prefixVar, prefixCls } = useDesign('basic-drawer');
|
||||
|
||||
const drawerInstance: DrawerInstance = {
|
||||
setDrawerProps: setDrawerProps,
|
||||
emitVisible: undefined,
|
||||
setDrawerProps: setDrawerProps as any,
|
||||
emitOpen: undefined,
|
||||
};
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
@@ -85,7 +85,7 @@
|
||||
placement: 'right',
|
||||
...unref(attrs),
|
||||
...unref(getMergeProps),
|
||||
visible: unref(visibleRef),
|
||||
open: unref(openRef),
|
||||
};
|
||||
opt.title = undefined;
|
||||
const { isDetail, width, wrapClassName, getContainer } = opt;
|
||||
@@ -94,7 +94,7 @@
|
||||
opt.width = '100%';
|
||||
}
|
||||
const detailCls = `${prefixCls}__detail`;
|
||||
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
||||
opt.rootClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
||||
|
||||
if (!getContainer) {
|
||||
// TODO type error?
|
||||
@@ -135,19 +135,19 @@
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
() => props.open,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) visibleRef.value = newVal;
|
||||
if (newVal !== oldVal) openRef.value = newVal;
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => visibleRef.value,
|
||||
(visible) => {
|
||||
() => openRef.value,
|
||||
(open) => {
|
||||
nextTick(() => {
|
||||
emit('visible-change', visible);
|
||||
instance && drawerInstance.emitVisible?.(visible, instance.uid);
|
||||
emit('open-change', open);
|
||||
instance && drawerInstance.emitOpen?.(open, instance.uid);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -158,18 +158,18 @@
|
||||
emit('close', e);
|
||||
if (closeFunc && isFunction(closeFunc)) {
|
||||
const res = await closeFunc();
|
||||
visibleRef.value = !res;
|
||||
openRef.value = !res;
|
||||
return;
|
||||
}
|
||||
visibleRef.value = false;
|
||||
openRef.value = false;
|
||||
}
|
||||
|
||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||
// Keep the last setDrawerProps
|
||||
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
||||
|
||||
if (Reflect.has(props, 'visible')) {
|
||||
visibleRef.value = !!props.visible;
|
||||
if (Reflect.has(props, 'open')) {
|
||||
openRef.value = !!props.open;
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user