提交 e2b05b2f 作者: 方治民

合并分支 'main' 到 'electron'

Main

查看合并请求 !10
......@@ -14,7 +14,7 @@ VITE_DROP_CONSOLE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# VITE_GLOB_API_URL=http://localhost:8181
# VITE_GLOB_API_URL=http://192.168.0.156:8081
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
......
# Whether to open mock
VITE_USE_MOCK = false
# public path
VITE_PUBLIC_PATH = /
# 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 = 'gzip'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
# VITE_GLOB_API_URL=/basic-api
VITE_GLOB_API_URL=http://192.168.0.156:17181
# Interface prefix
# VITE_GLOB_API_URL_PREFIX=
VITE_GLOB_API_URL_PREFIX=/api
# File upload address, optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/upload
# Whether to enable image compression
VITE_USE_IMAGEMIN= true
# use pwa
VITE_USE_PWA = false
# Is it compatible with older browsers
VITE_LEGACY = false
# Whether to open mock
VITE_USE_MOCK = true
VITE_USE_MOCK = false
# public path
VITE_PUBLIC_PATH = /
......@@ -10,16 +10,18 @@ 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_BUILD_COMPRESS = 'gzip'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# VITE_GLOB_API_URL=/basic-api
VITE_GLOB_API_URL=http://192.168.0.156:8081
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# VITE_GLOB_API_URL_PREFIX=
VITE_GLOB_API_URL_PREFIX=/api
# File upload address, optional
# It can be forwarded by nginx or write the actual address directly
......
......@@ -14,3 +14,4 @@ dist
Dockerfile
types/auto-imports.d.ts
src/api/services/api.d.ts
......@@ -68,5 +68,6 @@ module.exports = {
},
],
'vue/multi-word-component-names': 'off',
'vue/no-reserved-component-names': 'off',
},
}
......@@ -29,4 +29,5 @@ pnpm-debug.log*
*.sln
*.sw?
.mocks/
.mocks
.history
# 变量
variables:
# 本地镜像地址,用于拉取镜像以及发布
REGISTRY_REMOTE: localhost:18500
# 容器名称
CONTAINER_NAME: basic-vue-admin
# 对外访问端口(beta)
EXPOSE_PORT_BETA: 18000
# 对外访问端口(preview)
EXPOSE_PORT_PREVIEW: 17000
# Pipelines 步骤
stages:
- build
- test
- deploy
# 编译项目
build-job:
stage: build
# 缓存配置
cache:
paths:
- node_modules/
# 使用 node lts 精简版容器
image: $REGISTRY_REMOTE/node
only:
- beta
- preview
- tags
# 使用 CI Runner,在 GitLab-Runner 中注册好的 Runner
tags:
- YR-CI
script:
- pnpm i
- NPM_SCRIPT="build"
- true && [ "$CI_BUILD_REF_NAME" = "beta" ] && NPM_SCRIPT="build:test" || true
- true && [ "$CI_BUILD_REF_NAME" = "preview" ] && NPM_SCRIPT="build:preview" || true
- echo "Branch:" $CI_BUILD_REF_NAME "CMD:" $NPM_SCRIPT
- pnpm $NPM_SCRIPT
artifacts:
# 配置构建结果过期时间
expire_in: 1 day
# 保留目录
paths:
- dist
# 发布,在本地构建镜像并推送到发布环境的镜像库
deploy-job:
stage: deploy
image: $REGISTRY_REMOTE/docker
# 部署依赖编译
dependencies:
- build-job
only:
- beta
- preview
# 使用 CD Runner,在 GitLab-Runner 中注册好的 Runner(此处配置成使用宿主环境构建)
tags:
- YR-CD
script:
# 基于 Dockerfile 构建镜像
- docker build -t $TAG .
# 尝试删除上一个容器
- id=$(docker ps -aqf name=$CONTAINER_NAME) && [ "$id" ] && docker rm -f $id || true
# 在本地运行构建好的镜像
- EXPOSE_PORT=80
- true && [ "$CI_BUILD_REF_NAME" = "beta" ] && EXPOSE_PORT=$EXPOSE_PORT_BETA || true
- true && [ "$CI_BUILD_REF_NAME" = "preview" ] && EXPOSE_PORT=$EXPOSE_PORT_PREVIEW || true
- echo "Branch:" $CI_BUILD_REF_NAME "PORT:" $EXPOSE_PORT
- docker run -d --name $CONTAINER_NAME -p $EXPOSE_PORT:80 -e TZ="Asia/Shanghai" $TAG
variables:
# 设置镜像 tag,使用 git tag 标识作为镜像 tag
TAG: $REGISTRY_REMOTE/basic/$CONTAINER_NAME:$CI_BUILD_REF_NAME
......@@ -3,4 +3,6 @@
# shellcheck source=./_/husky.sh
. "$(dirname "$0")/_/husky.sh"
PATH="/usr/local/bin:$PATH"
npx --no-install commitlint --edit "$1"
......@@ -4,5 +4,7 @@
[ -n "$CI" ] && exit 0
PATH="/usr/local/bin:$PATH"
# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged
{
"recommendations": [
"johnsoncodehk.volar",
"vue.volar",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"esbenp.prettier-vscode",
......
......@@ -33,6 +33,7 @@
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
......@@ -135,5 +136,7 @@
"pnpm",
"antd"
],
"vue-i18n.i18nPaths": "src\\locales,src\\locales\\lang,public\\resource\\tinymce\\langs"
"vetur.format.scriptInitialIndent": true,
"vetur.format.styleInitialIndent": true,
"vetur.validation.script": false
}
FROM localhost:18500/nginx:latest
# 拷贝资源文件
COPY dist/ /usr/share/nginx/html/
# 添加配置文件
ADD default.conf /etc/nginx/conf.d/
# 设置工作目录
WORKDIR /usr/share/nginx/html
# 授权
RUN chmod -R a+rx *
# 端口
EXPOSE 80
# 关闭 nginx 后台运行
# https://www.cnblogs.com/weifeng1463/p/10277178.html
CMD ["nginx","-g","daemon off;"]
# 中后台基础工程 (Template)
> 项目 Fork 自 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin)
> 通用组件及相关配置, 参考文档: [Vben Doc](https://vvbin.cn/doc-next/)
> 通用组件及相关配置, 参考文档: [Vben Doc](https://vvbin.cn/doc-next/), [Ant Design Vue](https://www.antdv.com/components/overview-cn/)
## 开发环境
......@@ -14,6 +14,7 @@
<!-- prettier-ignore -->
- [开发规范说明](./doc/workflow.md)
- [技术栈说明](./doc/technique.md)
- 💡 [实用技巧](./doc/skill.md)
---
......@@ -26,18 +27,30 @@
- [x] 完善权限控制
- [ ] 适配上传/下载接口的自动化生成模板
- [ ] 新增 Demo 模块,集成基础 Table 和 Form 组件示例,上传下载等
- [ ] 跟进 Vben 更新,修复 Bug 和新增功能(持续...)
## 前后端联调(可选,默认以 Mock 方式运行)
---
### API 联调
```diff
# 修改 .env.development 变量, 将 API 接口从 Mock 切换到开发环境
- VITE_GLOB_API_URL=/basic-api
- VITE_GLOB_API_URL_PREFIX=
+ VITE_GLOB_API_URL=http://localhost:8181
# IP 地址和 API 前缀根据实际情况修改
+ VITE_GLOB_API_URL=http://192.168.0.156:8081
+ VITE_GLOB_API_URL_PREFIX=/api
```
### Route 模式切换
```diff
# 修改 projectSetting.ts 项目全局配置
# 例如: 修改权限模式,将前端菜单权限改为根据后台接口获取的权限
- permissionMode: PermissionModeEnum.ROUTE_MAPPING,
+ permissionMode: PermissionModeEnum.BACK,
```
......@@ -21,24 +21,22 @@ function createConfig(params: CreateConfigParams) {
try {
const windowConf = `window.${configName}`
// Ensure that the variable will not be modified
const configStr = `${windowConf}=${JSON.stringify(config)};
Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '')
let configStr = `${windowConf}=${JSON.stringify(config)};`
configStr += `
Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '')
fs.mkdirp(getRootPath(OUTPUT_DIR))
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
console.log(
colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`,
)
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`)
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n')
} catch (error) {
console.log(
colors.red('configuration file configuration file failed to package:\n' + error),
)
console.log(colors.red('configuration file configuration file failed to package:\n' + error))
}
}
......
......@@ -14,9 +14,10 @@ export function configAutoImportPlugin(): Plugin {
'vue',
'vue-router',
{
'/@/config/app': ['$app'],
'/@/api/services': ['defs'],
'/@/api/services/mods': ['API'],
'@/config/app': ['$app'],
'@/api/services': ['defs'],
'@/api/services/mods': ['API'],
'@/utils/stomp': [['default', 'Stomp']],
},
],
})
......
......@@ -14,7 +14,6 @@ import { configStyleImportPlugin } from './styleImport'
import { configVisualizerConfig } from './visualizer'
import { configThemePlugin } from './theme'
import { configSvgIconsPlugin } from './svgSprite'
import { configOptimizePlugin } from './optimize'
import { configAutoImportPlugin } from './autoImport'
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
......@@ -59,9 +58,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
// vite-plugin-theme
vitePlugins.push(configThemePlugin(isBuild))
// vite-plugin-optimize-persist
vitePlugins.push(configOptimizePlugin())
// vite-plugin-auto-import
vitePlugins.push(configAutoImportPlugin())
......
/**
* https://github.com/antfu/vite-plugin-optimize-persist
*/
import OptimizationPersist from 'vite-plugin-optimize-persist'
import PkgConfig from 'vite-plugin-package-config'
export function configOptimizePlugin() {
return [
PkgConfig({
packageJsonPath: './vite.optimize.json',
}),
OptimizationPersist(),
]
}
......@@ -5,9 +5,9 @@
import { createStyleImportPlugin } from 'vite-plugin-style-import'
export function configStyleImportPlugin(_isBuild: boolean) {
// if (!isBuild) {
// return [];
// }
if (!_isBuild) {
return []
}
const styleImportPlugin = createStyleImportPlugin({
libs: [
{
......@@ -64,6 +64,8 @@ export function configStyleImportPlugin(_isBuild: boolean) {
'layout-footer': 'layout',
'layout-header': 'layout',
'month-picker': 'date-picker',
'range-picker': 'date-picker',
'image-preview-group': 'image',
}
return ignoreList.includes(name)
......
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'],
......@@ -30,4 +50,58 @@ module.exports = {
],
],
},
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: 自定义',
},
}
server {
listen 80;
access_log /var/log/nginx/host.access.log;
location / {
gzip on;
gzip_min_length 10k;
gzip_http_version 1.1;
gzip_comp_level 4;
gzip_types text/plain
application/javascript
application/x-javascript
text/css
application/xml
text/javascript
application/x-httpd-php
image/gif;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
# history mode
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
# 配置页面不缓存
if ($request_filename ~* .*\.(?:htm|html)$) {
add_header Cache-Control
"private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
## 实用技巧
### IDE
<!-- prettier-ignore -->
1. 配置 `**触发建议**` 快捷键提升编码效率(例如: Ctrl + /)
### Code
<!-- prettier-ignore -->
1. 在路由 `meta` 属性中配置 `hideLayout` 可隐藏当前路由对应的布局,凸显页面内容
2. 在路由参数中增加 `__full__` 可设置当前路由为全屏模式,全屏模式下页面布局不显示
version: '3'
services:
basic-vue-admin:
build:
context: .
dockerfile: Dockerfile
# 镜像名称
image: localhost:18500/basic/basic-vue-admin:0.0.1
# 容器名称
container_name: 'basic-vue-admin'
# 当 docker 重启时,容器自动启动
restart: always
# 端口绑定
ports:
- 18000:80
environment:
# 设置时区
- TZ=Asia/Shanghai
# 指定网络
networks:
- basic
networks:
# 基础网络
basic:
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
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)
})
/**
......
{
"name": "basic-vue-admin",
"version": "0.0.1",
"version": "3.0.1",
"description": "Basic Vue Admin (Template)",
"keywords": [
"vue",
......@@ -21,11 +21,13 @@
"url": "https://yiring.com"
},
"scripts": {
"commit": "czg",
"bootstrap": "pnpm install",
"serve": "npm run dev",
"dev": "vite",
"build": "cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts",
"build:test": "cross-env vite build --mode test && esno ./build/script/postBuild.ts",
"build:preview": "cross-env vite build --mode preview && esno ./build/script/postBuild.ts",
"build:no-cache": "pnpm clean:cache && npm run build",
"report": "cross-env REPORT=true npm run build",
"type:check": "vue-tsc --noEmit --skipLibCheck",
......@@ -34,6 +36,7 @@
"log": "conventional-changelog -p angular -i CHANGELOG.md -s",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"clean:lib": "rimraf node_modules",
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:stylelint",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
......@@ -47,127 +50,134 @@
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
"path": "node_modules/cz-git"
}
},
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.1.0",
"@iconify/iconify": "^2.2.1",
"@logicflow/core": "^1.1.14",
"@logicflow/extension": "^1.1.14",
"@vue/runtime-core": "^3.2.33",
"@vue/shared": "^3.2.33",
"@vueuse/core": "^8.3.1",
"@vueuse/shared": "^8.3.1",
"@zxcvbn-ts/core": "^2.0.1",
"ant-design-vue": "3.2.0",
"@logicflow/core": "^1.1.30",
"@logicflow/extension": "^1.1.30",
"@stomp/stompjs": "^6.1.2",
"@vue/runtime-core": "^3.2.44",
"@vue/shared": "^3.2.44",
"@vueuse/core": "^9.5.0",
"@vueuse/shared": "^9.5.0",
"@zxcvbn-ts/core": "^2.1.0",
"ant-design-vue": "^3.2.15",
"axios": "^0.26.1",
"codemirror": "^5.65.3",
"codemirror": "^6.0.1",
"cropperjs": "^1.5.12",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.1",
"echarts": "^5.3.2",
"dayjs": "^1.11.6",
"echarts": "^5.4.0",
"intro.js": "^5.1.0",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"nanoid": "^4.0.0",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0",
"pinia": "2.0.12",
"path-to-regexp": "^6.2.1",
"pinia": "^2.0.23",
"print-js": "^1.6.0",
"qrcode": "^1.5.0",
"qs": "^6.10.3",
"qrcode": "^1.5.1",
"qs": "^6.11.0",
"resize-observer-polyfill": "^1.5.1",
"showdown": "^2.1.0",
"sockjs-client": "^1.6.1",
"sortablejs": "^1.15.0",
"tinymce": "^5.10.3",
"vditor": "^3.8.13",
"vue": "^3.2.33",
"vue-i18n": "^9.1.9",
"vue-json-pretty": "^2.0.6",
"vue-router": "^4.0.14",
"vue-types": "^4.1.1",
"stompjs": "^2.3.3",
"tinymce": "^5.10.6",
"vditor": "^3.8.18",
"vue": "^3.2.44",
"vue-i18n": "^9.2.2",
"vue-json-pretty": "^2.2.3",
"vue-router": "^4.1.6",
"vue-types": "^4.2.1",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@iconify/json": "^2.1.31",
"@commitlint/cli": "^17.2.0",
"@commitlint/config-conventional": "^17.2.0",
"@iconify/json": "^2.1.135",
"@purge-icons/generated": "^0.8.1",
"@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.1.1",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.2.1",
"@types/inquirer": "^9.0.3",
"@types/intro.js": "^3.0.2",
"@types/lodash-es": "^4.17.6",
"@types/mockjs": "^1.0.6",
"@types/node": "^17.0.25",
"@types/mockjs": "^1.0.7",
"@types/node": "^18.11.9",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2",
"@types/qrcode": "^1.5.0",
"@types/qs": "^6.9.7",
"@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"@vitejs/plugin-legacy": "^1.8.1",
"@vitejs/plugin-vue": "^2.3.1",
"@types/sockjs-client": "^1.5.1",
"@types/sortablejs": "^1.15.0",
"@types/stompjs": "^2.3.5",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@vitejs/plugin-legacy": "^2.3.1",
"@vitejs/plugin-vue": "^3.2.0",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/compiler-sfc": "3.2.31",
"@vue/test-utils": "^2.0.0-rc.21",
"autoprefixer": "^10.4.4",
"commitizen": "^4.2.4",
"@vue/test-utils": "^2.2.1",
"autoprefixer": "^10.4.13",
"commitizen": "^4.2.5",
"conventional-changelog-cli": "^2.2.2",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"dotenv": "^16.0.0",
"eslint": "^8.14.0",
"cz-git": "^1.3.12",
"czg": "^1.3.12",
"dotenv": "^16.0.3",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.7.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.7.0",
"esno": "^0.14.1",
"fs-extra": "^10.1.0",
"husky": "^7.0.4",
"inquirer": "^8.2.2",
"less": "^4.1.2",
"inquirer": "^9.1.4",
"less": "^4.1.3",
"lint-staged": "12.3.7",
"npm-run-all": "^4.1.5",
"picocolors": "^1.0.0",
"pont-engine": "^1.3.1",
"postcss": "^8.4.12",
"postcss-html": "^1.4.1",
"pont-engine": "^1.5.2",
"postcss": "^8.4.19",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
"prettier": "^2.6.2",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"rollup": "^2.70.2",
"rollup-plugin-visualizer": "^5.6.0",
"sort-package-json": "^1.55.0",
"stylelint": "^14.7.1",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^7.0.0",
"rollup": "^2.79.1",
"rollup-plugin-visualizer": "^5.8.3",
"sort-package-json": "^1.57.0",
"stylelint": "^14.14.1",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-recommended": "^9.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-config-standard": "^29.0.0",
"stylelint-order": "^5.0.0",
"ts-node": "^10.7.0",
"typescript": "^4.6.3",
"unplugin-auto-import": "^0.7.1",
"vite": "^2.9.5",
"terser": "^5.15.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"unplugin-auto-import": "^0.11.4",
"vite": "^3.2.3",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mkcert": "^1.6.0",
"vite-plugin-mkcert": "^1.10.1",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-optimize-persist": "^0.1.2",
"vite-plugin-package-config": "^0.1.1",
"vite-plugin-purge-icons": "^0.8.1",
"vite-plugin-pwa": "^0.12.0",
"vite-plugin-purge-icons": "^0.9.1",
"vite-plugin-pwa": "^0.13.3",
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-theme": "^0.8.6",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vite-plugin-windicss": "^1.8.4",
"vue-eslint-parser": "^8.3.0",
"vue-tsc": "^0.33.9"
"vite-plugin-windicss": "^1.8.8",
"vue-eslint-parser": "^9.1.0",
"vue-tsc": "^1.0.9"
},
"engines": {
"node": "^12 || >=14"
"node": ">=14"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"originUrl": "http://localhost:8181/api/v2/api-docs",
"templatePath": "./pont.template",
"originType": "SwaggerV2",
"originUrl": "http://127.0.0.1:8081/api/v2/api-docs",
"templateType": "fetch",
"templatePath": "./pont-template",
"transformPath": "./pont-transform",
"outDir": "./src/api/services",
"surrounding": "typeScript",
"mocks": {
......@@ -9,7 +12,6 @@
"port": 3101,
"wrapper": "{\"status\": 200, \"body\": {response}, \"message\": \"OK\"}"
},
"templateType": "fetch",
"prettierConfig": {
"printWidth": 120,
"semi": false,
......
import * as Pont from 'pont-engine'
import { Interface, BaseClass, Property, CodeGenerator } from 'pont-engine'
// 接口 API 前缀
// 通常与项目的 env 配置中的 VITE_GLOB_API_URL_PREFIX 相同
const API_URL_PREFIX = '/api'
export class FileStructures extends Pont.FileStructures {
getModsDeclaration(originCode: string): string {
return originCode
}
}
export default class BasicGenerator extends CodeGenerator {
checkJsonParam(inter: Interface, paramsCode: string): boolean {
const prettier = require('prettier')
const requestParams = inter.getRequestParams(this.surrounding)
const code = prettier.format(paramsCode, { parser: 'typescript' })
return code.includes('{}') && requestParams.includes('body')
}
// 认为没有返回值的接口表示下载(通常是)
isDownload(inter: Interface): boolean {
return inter.responseType === 'any'
}
// 认为包含 form 参数的接口表示带有上传功能
isUpload(inter: Interface): boolean {
return inter.getRequestParams(this.surrounding).includes('form')
}
getParams(inter: Interface, paramsCode: string) {
let requestParams = inter.getRequestParams(this.surrounding)
let bodyTypeDef = ''
const prettier = require('prettier')
if (prettier.format(paramsCode, { parser: 'typescript' }).includes('{}')) {
requestParams = requestParams.replace('params', 'params?')
if (!requestParams.includes('form')) {
requestParams = requestParams.replace('params', 'params?')
}
if (requestParams.includes('body')) {
requestParams = requestParams.replace('params?: Params, ', '')
bodyTypeDef = requestParams.replace(/^.*body: (.*),.*$/gi, '$1')
}
}
return `${requestParams.replace(
'options?: any',
`config?: http.RequestConfig<Params${requestParams.includes('form') ? ' | FormData' : ''}>`,
`config?: http.RequestConfig<${
bodyTypeDef ? bodyTypeDef : `Params${requestParams.includes('form') ? ' | FormData' : ''}`
}>`,
)}, options?: http.RequestOptions`
}
......@@ -54,28 +88,37 @@ export default class BasicGenerator extends CodeGenerator {
const method = inter.method.toUpperCase()
const paramsCode = inter.getParamsCode('Params', this.surrounding)
const requestParams = this.getParams(inter, paramsCode)
const isBody = this.checkJsonParam(inter, paramsCode)
const responseType = `Promise<${inter.responseType.replace(/defs.Result\<(.*)\>/, '$1') || 'any'}>`
// TODO
// 扩展 hooks mode
// 采用 vue-request 或 @tanstack/vue-query
// export function useRequest()
return `
/**
* @desc ${inter.description}
*/
import * as defs from '../../baseClass';
import { defHttp } from '/@/utils/http/axios'
export ${paramsCode}
export const init = ${inter.response.getInitialValue()};
${isBody ? '' : `export ${paramsCode}`}
export function request(${requestParams}) {
export function request(${requestParams}): ${responseType} {
return defHttp.request({
url: "${inter.path.replace(API_URL_PREFIX, '')}",
method: '${method}',
${
method === 'GET'
? 'params'
: `data: ${requestParams.includes('form') ? 'form || params' : 'params'}`
: `data: ${requestParams.includes('form') ? 'form || params' : isBody ? 'body' : 'params'}`
},
${
requestParams.includes('form')
? `headers: { 'Content-Type': 'multipart/form-data;charset=UTF-8' },`
: isBody
? `headers: { 'Content-Type': 'application/json;charset=UTF-8' },`
: ''
}
...config,
}, options);
......
import { StandardDataSource } from 'pont-engine'
export default function (dataSource: StandardDataSource): StandardDataSource {
return dataSource
}
......@@ -8,4 +8,12 @@ module.exports = {
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'lf',
overrides: [
{
files: ['*.yml', '*.yaml'],
options: {
tabWidth: 2,
},
},
],
}
......@@ -4,14 +4,4 @@
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;}
body{-webkit-text-size-adjust: none;}
body img{max-width: 96vw;}
body table img{max-width: 95%;}
body{font-family: sans-serif;}
table{border-collapse: collapse;}
.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}
......@@ -4,14 +4,4 @@
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;}
body{-webkit-text-size-adjust: none;}
body img{max-width: 96vw;}
body table img{max-width: 95%;}
body{font-family: sans-serif;}
table{border-collapse: collapse;}
.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/
body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}
import { defHttp } from '/@/utils/http/axios'
enum Api {
// The address does not exist
Error = '/error',
}
/**
* @description: Trigger ajax error
*/
export const fireErrorApi = () => defHttp.get({ url: Api.Error })
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -2,8 +2,8 @@ export class LoginVo {
/** token */
token = ''
/** 主键 */
userId = undefined
/** 用户 ID */
userId = ''
}
export class MenuVo {
......@@ -26,6 +26,20 @@ export class MenuVo {
redirect = ''
}
export class PageParam {
/** 当前页数 */
pageNo = undefined
/** 分页条数 */
pageSize = undefined
/** 排序字段 */
sortField = ''
/** 排序方向(ASC|DESC) */
sortOrder = 'ASC'
}
export class PageVo {
/** 数据 */
data = []
......@@ -54,7 +68,7 @@ export class PermissionVo {
icon = ''
/** 主键 */
id = undefined
id = ''
/** 元数据 */
meta = undefined
......@@ -66,7 +80,7 @@ export class PermissionVo {
path = ''
/** 父级ID */
pid = undefined
pid = ''
/** 序号 */
serial = undefined
......@@ -106,7 +120,7 @@ export class Result {
export class RoleVo {
/** 主键 */
id = undefined
id = ''
/** 名称 */
name = ''
......@@ -118,6 +132,14 @@ export class RoleVo {
uid = ''
}
export class UserExtensionVo {
/** 年龄 */
age = undefined
/** 性别 */
gender = undefined
}
export class UserInfo {
/** 头像 */
avatar = ''
......@@ -128,6 +150,9 @@ export class UserInfo {
/** 用户主页 */
homePath = ''
/** 手机号 */
mobile = ''
/** 真实姓名 */
realName = ''
......@@ -135,7 +160,7 @@ export class UserInfo {
roles = []
/** 主键 */
userId = undefined
userId = ''
/** 用户名 */
username = ''
......@@ -158,7 +183,7 @@ export class UserVo {
enabled = false
/** 主键 */
id = undefined
id = ''
/** 最后登录IP地址 */
lastLoginIp = ''
......
/**
* @description
* @description 身份认证
*/
import * as login from './login'
import * as logout from './logout'
import * as register from './register'
import * as safe from './safe'
import * as valid from './valid'
export { login, logout, register }
export { login, logout, register, safe, valid }
/**
* @desc 登录
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -12,9 +10,11 @@ export class Params {
password: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.LoginVo> {
return defHttp.request(
{
url: '/auth/login',
......
/**
* @desc 登出
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/auth/logout',
......
/**
* @desc 注册
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -24,9 +22,11 @@ export class Params {
username: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/auth/register',
......
/**
* @desc 安全校验
*/
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** 密码 */
password: string
}
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/auth/safe',
method: 'GET',
params,
...config,
},
options,
)
}
/**
* @desc fail
* @desc 检查登录
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<boolean> {
return defHttp.request(
{
url: '/hello/fail',
url: '/auth/valid',
method: 'GET',
params,
......
/**
* @desc hello
* @desc 文件下载
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<any> {
return defHttp.request(
{
url: '/hello/',
url: '/example/download',
method: 'GET',
params,
......
/**
* @desc 测试 MyBatis Plus 查询
* @desc 测试失败
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/hello/test',
url: '/example/fail',
method: 'GET',
params,
......
/**
* @desc 查询用户属性
*/
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.UserExtensionVo> {
return defHttp.request(
{
url: '/example/findUserExtensionInfo',
method: 'GET',
params,
...config,
},
options,
)
}
/**
* @desc Hello World
*/
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/example/',
method: 'GET',
params,
...config,
},
options,
)
}
/**
* @description
* @description 示例
*/
import * as hello from './hello'
import * as download from './download'
import * as fail from './fail'
import * as findUserExtensionInfo from './findUserExtensionInfo'
import * as json from './json'
import * as page from './page'
import * as test from './test'
export { hello, fail, page, test }
export { hello, download, fail, findUserExtensionInfo, json, page }
/**
* @desc JSON 传参
*/
import { defHttp } from '/@/utils/http/axios'
export function request(
body: defs.PageParam,
config?: http.RequestConfig<defs.PageParam>,
options?: http.RequestOptions,
): Promise<defs.PageVo<string>> {
return defHttp.request(
{
url: '/example/json',
method: 'POST',
data: body,
headers: { 'Content-Type': 'application/json' },
...config,
},
options,
)
}
/**
* @desc page
* @desc 分页条件查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -16,12 +14,14 @@ export class Params {
sortOrder?: 'ASC' | 'DESC'
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.PageVo<string>> {
return defHttp.request(
{
url: '/hello/page',
url: '/example/page',
method: 'GET',
params,
......
/**
* @desc 文件下载
*/
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** bucket */
bucket: string
/** object */
object: string
}
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<any> {
return defHttp.request(
{
url: '/common/file/download',
method: 'GET',
params,
...config,
},
options,
)
}
/**
* @description
* @description 文件管理
*/
import * as download from './download'
import * as upload from './upload'
import * as uploadBase64Image from './uploadBase64Image'
export { upload }
export { download, upload, uploadBase64Image }
/**
* @desc 文件上传
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(
params?: Params,
params: Params,
form: FormData,
config?: http.RequestConfig<Params | FormData>,
options?: http.RequestOptions,
) {
): Promise<string> {
return defHttp.request(
{
url: '/common/minio/upload',
url: '/common/file/upload',
method: 'POST',
data: form || params,
headers: { 'Content-Type': 'multipart/form-data;charset=UTF-8' },
...config,
},
......
/**
* @desc Base64 图片上传
*/
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** Base64 图片信息 */
base64Image: string
}
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/common/file/uploadBase64Image',
method: 'POST',
data: params,
...config,
},
options,
)
}
import * as auth from './auth'
import * as hello from './hello'
import * as minio from './minio'
import * as example from './example'
import * as file from './file'
import * as permission from './permission'
import * as role from './role'
import * as user from './user'
import * as userView from './userView'
export const API = {
auth,
hello,
minio,
example,
file,
permission,
role,
user,
userView,
}
/**
* @desc 新增
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -21,7 +19,7 @@ export class Params {
/** 路径 */
path?: string
/** 父级ID */
pid?: number
pid?: string
/** 重定向 */
redirect?: string
/** 序号 */
......@@ -32,12 +30,14 @@ export class Params {
uid: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/permission/add',
url: '/sys/permission/add',
method: 'POST',
data: params,
......
/**
* @desc 删除
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
id: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/permission/deleted',
url: '/sys/permission/deleted',
method: 'POST',
data: params,
......
/**
* @desc 查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
id: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.PermissionVo> {
return defHttp.request(
{
url: '/manage/permission/find',
url: '/sys/permission/find',
method: 'GET',
params,
......
/**
* @description
* @description 权限管理
*/
import * as add from './add'
import * as deleted from './deleted'
......
/**
* @desc 修改
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -15,7 +13,7 @@ export class Params {
/** 图标 */
icon?: string
/** id */
id: number
id: string
/** 元数据 */
meta?: string
/** 名称 */
......@@ -23,7 +21,7 @@ export class Params {
/** 路径 */
path?: string
/** 父级ID */
pid?: number
pid?: string
/** 重定向 */
redirect?: string
/** 序号 */
......@@ -34,12 +32,14 @@ export class Params {
uid: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/permission/modify',
url: '/sys/permission/modify',
method: 'POST',
data: params,
......
/**
* @desc 分页查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -16,12 +14,14 @@ export class Params {
sortOrder?: 'ASC' | 'DESC'
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.PageVo<defs.PermissionVo>> {
return defHttp.request(
{
url: '/manage/permission/page',
url: '/sys/permission/page',
method: 'GET',
params,
......
/**
* @desc 树结构查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** 父级 id */
pid?: number
/** pid */
pid?: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<Array<defs.PermissionVo>> {
return defHttp.request(
{
url: '/manage/permission/tree',
url: '/sys/permission/tree',
method: 'GET',
params,
......
/**
* @desc 新增
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -12,12 +10,14 @@ export class Params {
uid: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/role/add',
url: '/sys/role/add',
method: 'POST',
data: params,
......
/**
* @desc 分配权限
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
id: string
/** ids 多个以逗号分割 */
ids: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/role/assign',
url: '/sys/role/assign',
method: 'POST',
data: params,
......
/**
* @desc 删除
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
/** ids 多个以逗号分割 */
ids: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/role/deleted',
url: '/sys/role/deleted',
method: 'POST',
data: params,
......
/**
* @desc 查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
id: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.RoleVo> {
return defHttp.request(
{
url: '/manage/role/find',
url: '/sys/role/find',
method: 'GET',
params,
......
/**
* @description
* @description 角色管理
*/
import * as add from './add'
import * as assign from './assign'
......
/**
* @desc 修改
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
id: string
/** 名称 */
name: string
/** 标识 */
uid: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/manage/role/modify',
url: '/sys/role/modify',
method: 'POST',
data: params,
......
/**
* @desc 分页查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -16,12 +14,14 @@ export class Params {
sortOrder?: 'ASC' | 'DESC'
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.PageVo<defs.RoleVo>> {
return defHttp.request(
{
url: '/manage/role/page',
url: '/sys/role/page',
method: 'GET',
params,
......
/**
* @desc 选项查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<Array<defs.RoleVo>> {
return defHttp.request(
{
url: '/manage/role/selector',
url: '/sys/role/selector',
method: 'GET',
params,
......
/**
* @desc 分配角色
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
/** id */
id: number
id: string
/** ids 多个以逗号分割 */
ids: string
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<string> {
return defHttp.request(
{
url: '/user/manage/assign',
url: '/sys/user/assign',
method: 'POST',
data: params,
......
/**
* @description
* @description 用户管理
*/
import * as getMenuList from './getMenuList'
import * as getPermCode from './getPermCode'
import * as getUserInfo from './getUserInfo'
import * as assign from './assign'
import * as page from './page'
export { getMenuList, getPermCode, getUserInfo, assign, page }
export { assign, page }
/**
* @desc 分页查询
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {
......@@ -16,12 +14,14 @@ export class Params {
sortOrder?: 'ASC' | 'DESC'
}
export const init = new defs.Result()
export function request(params: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.PageVo<defs.UserVo>> {
return defHttp.request(
{
url: '/user/manage/page',
url: '/sys/user/page',
method: 'GET',
params,
......
/**
* @desc 获取用户菜单
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<Array<defs.MenuVo>> {
return defHttp.request(
{
url: '/user/getMenuList',
......
/**
* @desc 获取用户权限
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<Array<string>> {
return defHttp.request(
{
url: '/user/getPermCode',
......
/**
* @desc 获取登录用户信息
*/
import * as defs from '../../baseClass'
import { defHttp } from '/@/utils/http/axios'
export class Params {}
export const init = new defs.Result()
export function request(params?: Params, config?: http.RequestConfig<Params>, options?: http.RequestOptions) {
export function request(
params?: Params,
config?: http.RequestConfig<Params>,
options?: http.RequestOptions,
): Promise<defs.UserInfo> {
return defHttp.request(
{
url: '/user/getUserInfo',
......
/**
* @description 用户信息
*/
import * as getMenuList from './getMenuList'
import * as getPermCode from './getPermCode'
import * as getUserInfo from './getUserInfo'
export { getMenuList, getPermCode, getUserInfo }
......@@ -3,7 +3,7 @@
* @Description: logo component
-->
<template>
<div class="anticon" :class="getAppLogoClass" @click="goHome">
<div class="anticon" :class="getAppLogoClass" @click="goHome" :title="version">
<img src="../../../assets/images/logo.png" />
<div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
{{ title }}
......@@ -18,6 +18,7 @@
import { useDesign } from '/@/hooks/web/useDesign'
import { PageEnum } from '/@/enums/pageEnum'
import { useUserStore } from '/@/store/modules/user'
import { getEnvText } from '/@/utils/env'
const props = defineProps({
/**
......@@ -53,6 +54,8 @@
},
])
const version = `${getEnvText()}@${$app.version} -> ${$app.lastBuildTime}`
function goHome() {
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
}
......
......@@ -10,18 +10,18 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { Button } from 'ant-design-vue'
export default defineComponent({
name: 'AButton',
extends: Button,
inheritAttrs: false,
})
</script>
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { Button } from 'ant-design-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
const attrs = useAttrs({ excludeDefaultKeys: false })
......@@ -34,7 +34,6 @@
},
]
})
// get inherit binding value
const getBindValue = computed(() => ({ ...unref(attrs), ...props }))
</script>
const validColors = ['error', 'warning', 'success', ''] as const
type ButtonColorType = typeof validColors[number]
export const buttonProps = {
color: { type: String, validator: (v) => ['error', 'warning', 'success', ''].includes(v) },
color: {
type: String as PropType<ButtonColorType>,
validator: (v) => validColors.includes(v),
default: '',
},
loading: { type: Boolean },
disabled: { type: Boolean },
/**
......
......@@ -25,6 +25,7 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { ref } from 'vue'
import { isNil } from 'lodash-es'
// component
import { Skeleton } from 'ant-design-vue'
import { CollapseTransition } from '/@/components/Transition'
......@@ -33,7 +34,6 @@
// hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { useDesign } from '/@/hooks/web/useDesign'
const props = defineProps({
title: { type: String, default: '' },
loading: { type: Boolean },
......@@ -58,25 +58,24 @@
*/
lazyTime: { type: Number, default: 0 },
})
const show = ref(true)
const { prefixCls } = useDesign('collapse-container')
/**
* @description: Handling development events
*/
function handleExpand() {
show.value = !show.value
function handleExpand(val: boolean) {
show.value = isNil(val) ? !show.value : val
if (props.triggerWindowResize) {
// 200 milliseconds here is because the expansion has animation,
useTimeoutFn(triggerWindowResize, 200)
}
}
defineExpose({
handleExpand,
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container';
.@{prefix-cls} {
background-color: @component-background;
border-radius: 2px;
......
<script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing'
import type { FunctionalComponent, CSSProperties } from 'vue'
import type { FunctionalComponent, CSSProperties, PropType } from 'vue'
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'
import Icon from '/@/components/Icon'
import { Menu, Divider } from 'ant-design-vue'
const prefixCls = 'context-menu'
const props = {
width: { type: Number, default: 156 },
customEvent: { type: Object as PropType<Event>, default: null },
......@@ -27,7 +25,6 @@
},
},
}
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props
return (
......@@ -37,21 +34,18 @@
</span>
)
}
export default defineComponent({
name: 'ContextMenu',
props,
setup(props) {
const wrapRef = ref(null)
const showRef = ref(false)
const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props
const { x, y } = axis || { x: 0, y: 0 }
const menuHeight = (items || []).length * 40
const menuWidth = width
const body = document.body
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y
return {
......@@ -63,16 +57,13 @@
zIndex: 9999,
}
})
onMounted(() => {
nextTick(() => (showRef.value = true))
})
onUnmounted(() => {
const el = unref(wrapRef)
el && document.body.removeChild(el)
})
function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item
if (disabled) {
......@@ -83,17 +74,15 @@
e?.preventDefault()
handler?.()
}
function renderMenuItem(items: ContextMenuItem[]) {
return items.map((item) => {
const visibleItems = items.filter((item) => !item.hidden)
return visibleItems.map((item) => {
const { disabled, label, children, divider = false } = item
const contentProps = {
item,
handler: handleAction,
showIcon: props.showIcon,
}
if (!children || children.length === 0) {
return (
<>
......@@ -105,7 +94,6 @@
)
}
if (!unref(showRef)) return null
return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
{{
......@@ -134,11 +122,8 @@
</script>
<style lang="less">
@default-height: 42px !important;
@small-height: 36px !important;
@large-height: 36px !important;
.item-style() {
li {
display: inline-block;
......@@ -191,7 +176,6 @@
.ant-divider {
margin: 0;
}
.item-style();
}
......
......@@ -6,6 +6,7 @@ export interface Axis {
export interface ContextMenuItem {
label: string
icon?: string
hidden?: boolean
disabled?: boolean
handler?: Fn
divider?: boolean
......
......@@ -32,7 +32,6 @@
import { useI18n } from '/@/hooks/web/useI18n'
import type { ButtonProps } from '/@/components/Button'
import Icon from '/@/components/Icon'
const props = {
width: { type: [String, Number], default: '200px' },
value: { type: String },
......@@ -41,7 +40,6 @@
btnText: { type: String, default: '' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
}
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal, Icon },
......@@ -53,38 +51,28 @@
const [register, { openModal, closeModal }] = useModal()
const { createMessage } = useMessage()
const { t } = useI18n()
const getClass = computed(() => [prefixCls])
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px')
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px')
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }))
const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
)
watchEffect(() => {
sourceValue.value = props.value || ''
})
watch(
() => sourceValue.value,
(v: string) => {
emit('update:value', v)
},
)
function handleUploadSuccess({ source }) {
function handleUploadSuccess({ source, data }) {
sourceValue.value = source
emit('change', source)
emit('change', { source, data })
createMessage.success(t('component.cropper.uploadSuccess'))
}
expose({ openModal: openModal.bind(null, true), closeModal })
return {
t,
prefixCls,
......@@ -103,7 +91,6 @@
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-cropper-avatar';
.@{prefix-cls} {
display: inline-block;
text-align: center;
......
......@@ -3,7 +3,7 @@
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 } from 'vue'
import { defineComponent, computed, ref, unref, toRefs } from 'vue'
import { get } from 'lodash-es'
import { Descriptions } from 'ant-design-vue'
import { CollapseContainer } from '/@/components/Container/index'
......@@ -11,7 +11,6 @@
import { isFunction } from '/@/utils/is'
import { getSlot } from '/@/utils/helper/tsxHelper'
import { useAttrs } from '/@/hooks/core/useAttrs'
const props = {
useCollapse: { type: Boolean, default: true },
title: { type: String, default: '' },
......@@ -37,17 +36,14 @@
},
data: { type: Object },
}
export default defineComponent({
name: 'Description',
props,
emits: ['register'],
setup(props, { slots, emit }) {
const propsRef = ref<Partial<DescriptionProps> | null>(null)
const { prefixCls } = useDesign('description')
const attrs = useAttrs()
// Custom title component: get title
const getMergeProps = computed(() => {
return {
......@@ -55,7 +51,6 @@
...(unref(propsRef) as Recordable),
} as DescriptionProps
})
const getProps = computed(() => {
const opt = {
...unref(getMergeProps),
......@@ -63,12 +58,10 @@
}
return opt as DescriptionProps
})
/**
* @description: Whether to setting title
*/
const useWrapper = computed(() => !!unref(getMergeProps).title)
/**
* @description: Get configuration Collapse
*/
......@@ -79,11 +72,9 @@
...unref(getProps).collapseOptions,
}
})
const getDescriptionsProps = computed(() => {
return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps
})
/**
* @description:设置desc
*/
......@@ -91,39 +82,36 @@
// Keep the last setDrawerProps
propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable
}
// Prevent line breaks
function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
if (!labelStyle && !labelMinWidth) {
return label
}
const labelStyles: CSSProperties = {
...labelStyle,
minWidth: `${labelMinWidth}px `,
}
return <div style={labelStyles}>{label}</div>
}
function renderItem() {
const { schema, data } = unref(getProps)
return unref(schema)
.map((item) => {
const { render, field, span, show, contentMinWidth } = item
if (show && isFunction(show) && !show(data)) {
return null
}
const getContent = () => {
const _data = unref(getProps)?.data
if (!_data) {
return null
}
const getField = get(_data, field)
if (getField && !toRefs(_data).hasOwnProperty(field)) {
return isFunction(render) ? render('', _data) : ''
}
return isFunction(render) ? render(getField, _data) : getField ?? ''
}
const width = contentMinWidth
return (
<Descriptions.Item label={renderLabel(item)} key={field} span={span}>
......@@ -141,7 +129,6 @@
})
.filter((item) => !!item)
}
const renderDesc = () => {
return (
<Descriptions class={`${prefixCls}`} {...(unref(getDescriptionsProps) as any)}>
......@@ -149,17 +136,14 @@
</Descriptions>
)
}
const renderContainer = () => {
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>
// Reduce the dom level
if (!props.useCollapse) {
return content
}
const { canExpand, helpMessage } = unref(getCollapseOptions)
const { title } = unref(getMergeProps)
return (
<CollapseContainer title={title} canExpan={canExpand} helpMessage={helpMessage}>
{{
......@@ -169,11 +153,9 @@
</CollapseContainer>
)
}
const methods: DescInstance = {
setDescProps,
}
emit('register', methods)
return () => (unref(useWrapper) ? renderContainer() : renderDesc())
},
......
......@@ -47,7 +47,7 @@
const heightStr = `${props.height}`
return {
height: heightStr,
lineHeight: heightStr,
lineHeight: `calc(${heightStr} - 1px)`,
}
})
......
......@@ -6,6 +6,28 @@ const { utils, writeFile } = xlsx
const DEF_FILE_NAME = 'excel-list.xlsx'
/**
* @param data source data
* @param worksheet worksheet object
* @param min min width
*/
function setColumnWidth(data, worksheet, min = 3) {
const obj = {}
worksheet['!cols'] = []
data.forEach((item) => {
Object.keys(item).forEach((key) => {
const cur = item[key]
const length = cur.length
obj[key] = Math.max(min, length)
})
})
Object.keys(obj).forEach((key) => {
worksheet['!cols'].push({
wch: obj[key],
})
})
}
export function jsonToSheetXlsx<T = any>({
data,
header,
......@@ -20,7 +42,7 @@ export function jsonToSheetXlsx<T = any>({
}
const worksheet = utils.json_to_sheet(arrData, json2sheetOpts)
setColumnWidth(arrData, worksheet)
/* add worksheet to workbook */
const workbook: WorkBook = {
SheetNames: [filename],
......
......@@ -10,7 +10,6 @@
import { defineComponent, ref, unref } from 'vue'
import * as XLSX from 'xlsx'
import { dateUtil } from '/@/utils/dateUtil'
import type { ExcelData } from './typing'
export default defineComponent({
name: 'ImportExcel',
......@@ -25,12 +24,16 @@
type: Number,
default: 8,
},
// 是否直接返回选中文件
isReturnFile: {
type: Boolean,
default: false,
},
},
emits: ['success', 'error'],
setup(props, { emit }) {
const inputRef = ref<HTMLInputElement | null>(null)
const loadingRef = ref<Boolean>(false)
/**
* @description: 第一行作为头部
*/
......@@ -39,7 +42,6 @@
const headers: string[] = []
// A3:B7=>{s:{c:0, r:2}, e:{c:1, r:6}}
const range = XLSX.utils.decode_range(sheet['!ref'])
const R = range.s.r
/* start in the first row */
for (let C = range.s.c; C <= range.e.c; ++C) {
......@@ -52,7 +54,6 @@
}
return headers
}
/**
* @description: 获得excel数据
*/
......@@ -79,7 +80,6 @@
}
return row
})
excelData.push({
header,
results,
......@@ -90,7 +90,6 @@
}
return excelData
}
/**
* @description: 读取excel数据
*/
......@@ -117,7 +116,6 @@
reader.readAsArrayBuffer(rawFile)
})
}
async function upload(rawFile: File) {
const inputRefDom = unref(inputRef)
if (inputRefDom) {
......@@ -126,7 +124,6 @@
}
await readerData(rawFile)
}
/**
* @description: 触发选择文件管理器
*/
......@@ -134,9 +131,12 @@
const files = e && (e.target as HTMLInputElement).files
const rawFile = files && files[0] // only setting files[0]
if (!rawFile) return
if (props.isReturnFile) {
emit('success', rawFile)
return
}
upload(rawFile)
}
/**
* @description: 点击上传按钮
*/
......@@ -144,7 +144,6 @@
const inputRefDom = unref(inputRef)
inputRefDom && inputRefDom.click()
}
return { handleUpload, handleInputClick, inputRef }
},
})
......
......@@ -12,5 +12,6 @@ export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'
export { default as ApiTree } from './src/components/ApiTree.vue'
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'
export { default as ApiCascader } from './src/components/ApiCascader.vue'
export { default as ApiTransfer } from './src/components/ApiTransfer.vue'
export { BasicForm }
......@@ -10,6 +10,7 @@
<slot name="formHeader"></slot>
<template v-for="schema in getSchema" :key="schema.field">
<FormItem
:isAdvanced="fieldsIsAdvancedMap[schema.field]"
:tableAction="tableAction"
:formActionType="formActionType"
:schema="schema"
......@@ -40,18 +41,14 @@
import type { FormActionType, FormProps, FormSchema } from './types/form'
import type { AdvanceState } from './types/hooks'
import type { Ref } from 'vue'
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue'
import { Form, Row } from 'ant-design-vue'
import FormItem from './components/FormItem.vue'
import FormAction from './components/FormAction.vue'
import { dateItemType } from './helper'
import { dateUtil } from '/@/utils/dateUtil'
// import { cloneDeep } from 'lodash-es';
import { deepMerge } from '/@/utils'
import { useFormValues } from './hooks/useFormValues'
import useAdvanced from './hooks/useAdvanced'
import { useFormEvents } from './hooks/useFormEvents'
......@@ -59,39 +56,33 @@
import { useAutoFocus } from './hooks/useAutoFocus'
import { useModalContext } from '/@/components/Modal'
import { useDebounceFn } from '@vueuse/core'
import { basicProps } from './props'
import { useDesign } from '/@/hooks/web/useDesign'
import { cloneDeep } from 'lodash-es'
export default defineComponent({
name: 'BasicForm',
components: { FormItem, Form, Row, FormAction },
props: basicProps,
emits: ['advanced-change', 'reset', 'submit', 'register'],
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({})
const modalFn = useModalContext()
const advanceState = reactive<AdvanceState>({
isAdvanced: true,
hideAdvanceBtn: false,
isLoad: false,
actionSpan: 6,
})
const defaultValueRef = ref<Recordable>({})
const isInitedDefaultRef = ref(false)
const propsRef = ref<Partial<FormProps>>({})
const schemaRef = ref<Nullable<FormSchema[]>>(null)
const formElRef = ref<Nullable<FormActionType>>(null)
const { prefixCls } = useDesign('basic-form')
// Get the basic configuration of the form
const getProps = computed((): FormProps => {
return { ...props, ...unref(propsRef) } as FormProps
})
const getFormClass = computed(() => {
return [
prefixCls,
......@@ -100,7 +91,6 @@
},
]
})
// Get uniform row style and Row configuration for the entire form
const getRow = computed((): Recordable => {
const { baseRowStyle = {}, rowProps } = unref(getProps)
......@@ -109,15 +99,13 @@
...rowProps,
}
})
const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable))
const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any)
for (const schema of schemas) {
const { defaultValue, component } = schema
const { defaultValue, component, isHandleDateDefaultValue = true } = schema
// handle date type
if (defaultValue && dateItemType.includes(component)) {
if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue)
} else {
......@@ -130,13 +118,12 @@
}
}
if (unref(getProps).showAdvancedButton) {
return schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[]
return cloneDeep(schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[])
} else {
return schemas as FormSchema[]
return cloneDeep(schemas as FormSchema[])
}
})
const { handleToggleAdvanced } = useAdvanced({
const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
advanceState,
emit,
getProps,
......@@ -144,21 +131,18 @@
formModel,
defaultValueRef,
})
const { handleFormValues, initDefault } = useFormValues({
getProps,
defaultValueRef,
getSchema,
formModel,
})
useAutoFocus({
getSchema,
getProps,
isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>,
})
const {
handleSubmit,
setFieldsValue,
......@@ -169,7 +153,7 @@
updateSchema,
resetSchema,
appendSchemaByField,
removeSchemaByFiled,
removeSchemaByField,
resetFields,
scrollToField,
} = useFormEvents({
......@@ -182,12 +166,10 @@
schemaRef: schemaRef as Ref<FormSchema[]>,
handleFormValues,
})
createFormContext({
resetAction: resetFields,
submitAction: handleSubmit,
})
watch(
() => unref(getProps).model,
() => {
......@@ -199,14 +181,12 @@
immediate: true,
},
)
watch(
() => unref(getProps).schemas,
(schemas) => {
resetSchema(schemas ?? [])
},
)
watch(
() => getSchema.value,
(schema) => {
......@@ -223,7 +203,6 @@
}
},
)
watch(
() => formModel,
useDebounceFn(() => {
......@@ -231,19 +210,17 @@
}, 300),
{ deep: true },
)
async function setProps(formProps: Partial<FormProps>): Promise<void> {
propsRef.value = deepMerge(unref(propsRef) || {}, formProps)
}
function setFormModel(key: string, value: any) {
formModel[key] = value
const { validateTrigger } = unref(getBindValue)
if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {})
}
emit('field-value-change', key, value)
}
function handleEnterPress(e: KeyboardEvent) {
const { autoSubmitOnEnter } = unref(getProps)
if (!autoSubmitOnEnter) return
......@@ -254,7 +231,6 @@
}
}
}
const formActionType: Partial<FormActionType> = {
getFieldsValue,
setFieldsValue,
......@@ -262,7 +238,7 @@
updateSchema,
resetSchema,
setProps,
removeSchemaByFiled,
removeSchemaByField,
appendSchemaByField,
clearValidate,
validateFields,
......@@ -270,12 +246,10 @@
submit: handleSubmit,
scrollToField: scrollToField,
}
onMounted(() => {
initDefault()
emit('register', formActionType)
})
return {
getBindValue,
handleToggleAdvanced,
......@@ -291,6 +265,7 @@
setFormModel,
getFormClass,
getFormActionBindProps: computed((): Recordable => ({ ...getProps.value, ...advanceState })),
fieldsIsAdvancedMap,
...formActionType,
}
},
......@@ -298,7 +273,6 @@
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-form';
.@{prefix-cls} {
.ant-form-item {
&-label label::after {
......
......@@ -27,6 +27,7 @@ import ApiSelect from './components/ApiSelect.vue'
import ApiTree from './components/ApiTree.vue'
import ApiTreeSelect from './components/ApiTreeSelect.vue'
import ApiCascader from './components/ApiCascader.vue'
import ApiTransfer from './components/ApiTransfer.vue'
import { BasicUpload } from '/@/components/Upload'
import { StrengthMeter } from '/@/components/StrengthMeter'
import { IconPicker } from '/@/components/Icon'
......@@ -57,6 +58,7 @@ componentMap.set('ApiCascader', ApiCascader)
componentMap.set('Cascader', Cascader)
componentMap.set('Slider', Slider)
componentMap.set('Rate', Rate)
componentMap.set('ApiTransfer', ApiTransfer)
componentMap.set('DatePicker', DatePicker)
componentMap.set('MonthPicker', DatePicker.MonthPicker)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论