new-map
ihzero 2022-10-21 11:51:11 +08:00
parent 1f265d6b85
commit dbdc655406
261 changed files with 17386 additions and 8347 deletions

View File

@ -1,6 +1,4 @@
// @ts-check module.exports = {
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true, root: true,
env: { env: {
browser: true, browser: true,
@ -20,9 +18,7 @@ module.exports = defineConfig({
extends: [ extends: [
'plugin:vue/vue3-recommended', 'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'plugin:jest/recommended',
], ],
rules: { rules: {
'vue/script-setup-uses-vars': 'error', 'vue/script-setup-uses-vars': 'error',
@ -55,8 +51,6 @@ module.exports = defineConfig({
'space-before-function-paren': 'off', 'space-before-function-paren': 'off',
'vue/attributes-order': 'off', 'vue/attributes-order': 'off',
'vue/v-on-event-hyphenation': 'off',
'vue/multi-word-component-names': 'off',
'vue/one-component-per-file': 'off', 'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off', 'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off', 'vue/max-attributes-per-line': 'off',
@ -64,6 +58,7 @@ module.exports = defineConfig({
'vue/singleline-html-element-content-newline': 'off', 'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off', 'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off', 'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [ 'vue/html-self-closing': [
'error', 'error',
{ {
@ -76,5 +71,6 @@ module.exports = defineConfig({
math: 'always', math: 'always',
}, },
], ],
'vue/multi-word-component-names': 'off',
}, },
}); };

4
.gitignore vendored
View File

@ -27,3 +27,7 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
package-lock.json
pnpm-lock.yaml

View File

@ -2,5 +2,5 @@ ports:
- port: 3344 - port: 3344
onOpen: open-preview onOpen: open-preview
tasks: tasks:
- init: yarn - init: pnpm install
command: yarn dev command: pnpm run dev

View File

@ -0,0 +1,6 @@
#!/bin/sh
# shellcheck source=./_/husky.sh
. "$(dirname "$0")/_/husky.sh"
# npx --no-install commitlint --edit "$1"

9
.husky/common.sh 100644
View File

@ -0,0 +1,9 @@
#!/bin/sh
command_exists () {
command -v "$1" >/dev/null 2>&1
}
# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi

View File

@ -0,0 +1,8 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
[ -n "$CI" ] && exit 0
# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged

View File

@ -1,6 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"octref.vetur", "vue.volar",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint", "stylelint.vscode-stylelint",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",

23
.vscode/settings.json vendored
View File

@ -1,16 +1,10 @@
{ {
"typescript.tsdk": "./node_modules/typescript/lib", "typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"volar.tsPlugin": true, "volar.tsPlugin": true,
"volar.tsPluginStatus": false, "volar.tsPluginStatus": false,
//=========================================== "npm.packageManager": "pnpm",
//============= Editor ======================
//===========================================
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
//===========================================
//============= files =======================
//===========================================
"files.eol": "\n", "files.eol": "\n",
"search.exclude": { "search.exclude": {
"**/node_modules": true, "**/node_modules": true,
@ -61,7 +55,7 @@
"**/yarn.lock": true "**/yarn.lock": true
}, },
"stylelint.enable": true, "stylelint.enable": true,
"stylelint.packageManager": "yarn", "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": { "path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src" "/@/": "${workspaceRoot}/src"
}, },
@ -94,7 +88,8 @@
}, },
"[vue]": { "[vue]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": false "source.fixAll.eslint": true,
"source.fixAll.stylelint": true
} }
}, },
"i18n-ally.localesPaths": ["src/locales/lang"], "i18n-ally.localesPaths": ["src/locales/lang"],
@ -114,7 +109,6 @@
"esnext", "esnext",
"antv", "antv",
"tinymce", "tinymce",
"qrcode",
"sider", "sider",
"pinia", "pinia",
"sider", "sider",
@ -137,6 +131,11 @@
"lintstagedrc", "lintstagedrc",
"brotli", "brotli",
"tailwindcss", "tailwindcss",
"sider" "sider",
] "pnpm",
"antd"
],
"vetur.format.scriptInitialIndent": true,
"vetur.format.styleInitialIndent": true,
"vetur.validation.script": false
} }

View File

@ -1,48 +0,0 @@
# test directories
__tests__
test
tests
powered-test
# asset directories
docs
doc
website
images
assets
# examples
example
examples
# code coverage directories
coverage
.nyc_output
# build scripts
Makefile
Gulpfile.js
Gruntfile.js
# configs
appveyor.yml
circle.yml
codeship-services.yml
codeship-steps.yml
wercker.yml
.tern-project
.gitattributes
.editorconfig
.*ignore
.eslintrc
.jshintrc
.flowconfig
.documentup.json
.yarn-metadata.json
.travis.yml
# misc
*.md
!istanbul-reports/lib/html/assets
!istanbul-api/node_modules/istanbul-reports/lib/html/assets

View File

102
README.md
View File

@ -1 +1,101 @@
## 隆昌农业 <div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://anncwb.github.io/anncwb/images/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue vben admin</h1>
</div>
## 简介
精简 Vue Vben Admin。
## 特性
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**:可配置的主题
- **国际化**:内置完善的国际化方案
- **Mock 数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
## 预览
- [vue-vben-admin](https://vvbin.cn/next/) - 完整版中文站点
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - 完整版 github 站点
- [vben-admin-thin-next](https://vvbin.cn/thin/next/) - 简化版中文站点
- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) - 简化版 github 站点
## 准备
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Ant-Design-Vue](https://2x.antdv.com/docs/vue/introduce-cn/) - ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装使用
- 获取项目代码
```bash
git clone https://github.com/anncwb/vue-vben-admin.git
```
- 安装依赖
```bash
cd vue-vben-admin
pnpm install
```
- 运行
```bash
pnpm serve
```
- 打包
```bash
pnpm build
```
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
## 相关仓库
如果这些插件对你有帮助,可以给一个 star 支持下
- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock
- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩
- [vite-plugin-style-import](https://github.com/anncwb/vite-plugin-style-import) - 用于组件库样式按需引入
- [vite-plugin-theme](https://github.com/anncwb/vite-plugin-theme) - 用于在线切换主题色等颜色相关配置
- [vite-plugin-imagemin](https://github.com/anncwb/vite-plugin-imagemin) - 用于打包压缩图片资源
- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件
- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图
## License
[MIT © Vben-2020](./LICENSE)

View File

@ -1,6 +1,6 @@
import { generate } from '@ant-design/colors'; import { generate } from '@ant-design/colors';
export const primaryColor = '#4ba06c'; export const primaryColor = '#0960bd';
export const darkMode = 'light'; export const darkMode = 'light';

View File

@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import chalk from 'chalk'; import colors from 'picocolors';
import pkg from '../../../package.json'; import pkg from '../../../package.json';
async function generateIcon() { async function generateIcon() {
@ -64,7 +64,7 @@ async function generateIcon() {
} }
fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite')); fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
console.log( console.log(
`${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`, `${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`,
); );
}); });
} }

View File

@ -3,7 +3,7 @@
*/ */
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'; import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra'; import fs, { writeFileSync } from 'fs-extra';
import chalk from 'chalk'; import colors from 'picocolors';
import { getEnvConfig, getRootPath } from '../utils'; import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName'; import { getConfigFileName } from '../getConfigFileName';
@ -21,20 +21,22 @@ function createConfig(params: CreateConfigParams) {
try { try {
const windowConf = `window.${configName}`; const windowConf = `window.${configName}`;
// Ensure that the variable will not be modified // Ensure that the variable will not be modified
const configStr = `${windowConf}=${JSON.stringify(config)}; let configStr = `${windowConf}=${JSON.stringify(config)};`;
configStr += `
Object.freeze(${windowConf}); Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", { Object.defineProperty(window, "${configName}", {
configurable: false, configurable: false,
writable: false, writable: false,
}); });
`.replace(/\s/g, ''); `.replace(/\s/g, '');
fs.mkdirp(getRootPath(OUTPUT_DIR)); fs.mkdirp(getRootPath(OUTPUT_DIR));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr); writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`); console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n'); console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
} catch (error) { } catch (error) {
console.log(chalk.red('configuration file configuration file failed to package:\n' + error)); console.log(colors.red('configuration file configuration file failed to package:\n' + error));
} }
} }

View File

@ -1,7 +1,7 @@
// #!/usr/bin/env node // #!/usr/bin/env node
import { runBuildConfig } from './buildConf'; import { runBuildConfig } from './buildConf';
import chalk from 'chalk'; import colors from 'picocolors';
import pkg from '../../package.json'; import pkg from '../../package.json';
@ -14,9 +14,9 @@ export const runBuild = async () => {
runBuildConfig(); runBuildConfig();
} }
console.log(`${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) { } catch (error) {
console.log(chalk.red('vite build error:\n' + error)); console.log(colors.red('vite build error:\n' + error));
process.exit(1); process.exit(1);
} }
}; };

View File

@ -1,21 +0,0 @@
// TODO
import type { GetManualChunk } from 'rollup';
//
const vendorLibs: { match: string[]; output: string }[] = [
// {
// match: ['xlsx'],
// output: 'xlsx',
// },
];
// @ts-ignore
export const configManualChunk: GetManualChunk = (id: string) => {
if (/[\\/]node_modules[\\/]/.test(id)) {
const matchItem = vendorLibs.find((item) => {
const reg = new RegExp(`[\\/]node_modules[\\/]_?(${item.match.join('|')})(.*)`, 'ig');
return reg.test(id);
});
return matchItem ? matchItem.output : null;
}
};

View File

@ -2,16 +2,16 @@
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
* https://github.com/anncwb/vite-plugin-compression * https://github.com/anncwb/vite-plugin-compression
*/ */
import type { Plugin } from 'vite'; import type { PluginOption } from 'vite';
import compressPlugin from 'vite-plugin-compression'; import compressPlugin from 'vite-plugin-compression';
export function configCompressPlugin( export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none', compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false, deleteOriginFile = false,
): Plugin | Plugin[] { ): PluginOption | PluginOption[] {
const compressList = compress.split(','); const compressList = compress.split(',');
const plugins: Plugin[] = []; const plugins: PluginOption[] = [];
if (compressList.includes('gzip')) { if (compressList.includes('gzip')) {
plugins.push( plugins.push(

View File

@ -1,25 +0,0 @@
import type { Plugin } from 'vite';
/**
* TODO
* Temporarily solve the Vite circular dependency problem, and wait for a better solution to fix it later. I don't know what problems this writing will bring.
* @returns
*/
export function configHmrPlugin(): Plugin {
return {
name: 'singleHMR',
handleHotUpdate({ modules, file }) {
if (file.match(/xml$/)) return [];
modules.forEach((m) => {
if (!m.url.match(/\.(css|less)/)) {
m.importedModules = new Set();
m.importers = new Set();
}
});
return modules;
},
};
}

View File

@ -2,8 +2,8 @@
* Plugin to minimize and use ejs template syntax in index.html. * Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html * https://github.com/anncwb/vite-plugin-html
*/ */
import type { Plugin } from 'vite'; import type { PluginOption } from 'vite';
import html from 'vite-plugin-html'; import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json'; import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant'; import { GLOB_CONFIG_FILE_NAME } from '../../constant';
@ -16,7 +16,7 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
}; };
const htmlPlugin: Plugin[] = html({ const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild, minify: isBuild,
inject: { inject: {
// Inject data into ejs template // Inject data into ejs template

View File

@ -1,20 +1,20 @@
import type { Plugin } from 'vite'; import { PluginOption } from 'vite'
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'; import vueJsx from '@vitejs/plugin-vue-jsx'
import legacy from '@vitejs/plugin-legacy'; import legacy from '@vitejs/plugin-legacy'
import purgeIcons from 'vite-plugin-purge-icons'; import purgeIcons from 'vite-plugin-purge-icons'
import windiCSS from 'vite-plugin-windicss'; import windiCSS from 'vite-plugin-windicss'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'; import VitePluginCertificate from 'vite-plugin-mkcert'
import { configHtmlPlugin } from './html'; //import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { configPwaConfig } from './pwa'; import { configHtmlPlugin } from './html'
import { configMockPlugin } from './mock'; import { configPwaConfig } from './pwa'
import { configCompressPlugin } from './compress'; import { configMockPlugin } from './mock'
import { configStyleImportPlugin } from './styleImport'; import { configCompressPlugin } from './compress'
import { configVisualizerConfig } from './visualizer'; import { configStyleImportPlugin } from './styleImport'
import { configThemePlugin } from './theme'; import { configVisualizerConfig } from './visualizer'
import { configImageminPlugin } from './imagemin'; import { configThemePlugin } from './theme'
import { configSvgIconsPlugin } from './svgSprite'; import { configImageminPlugin } from './imagemin'
import { configHmrPlugin } from './hmr'; import { configSvgIconsPlugin } from './svgSprite'
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
const { const {
@ -23,60 +23,60 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
VITE_LEGACY, VITE_LEGACY,
VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS,
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
} = viteEnv; } = viteEnv
const vitePlugins: (Plugin | Plugin[])[] = [ const vitePlugins: (PluginOption | PluginOption[])[] = [
// have to // have to
vue(), vue(),
// have to // have to
vueJsx(), vueJsx(),
// support name // support name
vueSetupExtend(), //vueSetupExtend(),
]; VitePluginCertificate({
source: 'coding',
}),
]
// vite-plugin-windicss // vite-plugin-windicss
vitePlugins.push(windiCSS()); vitePlugins.push(windiCSS())
// TODO
!isBuild && vitePlugins.push(configHmrPlugin());
// @vitejs/plugin-legacy // @vitejs/plugin-legacy
VITE_LEGACY && isBuild && vitePlugins.push(legacy()); VITE_LEGACY && isBuild && vitePlugins.push(legacy())
// vite-plugin-html // vite-plugin-html
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
// vite-plugin-svg-icons // vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild)); vitePlugins.push(configSvgIconsPlugin(isBuild))
// vite-plugin-mock // vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild)); VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild))
// vite-plugin-purge-icons // vite-plugin-purge-icons
vitePlugins.push(purgeIcons()); vitePlugins.push(purgeIcons())
// vite-plugin-style-import // vite-plugin-style-import
vitePlugins.push(configStyleImportPlugin(isBuild)); vitePlugins.push(configStyleImportPlugin(isBuild))
// rollup-plugin-visualizer // rollup-plugin-visualizer
vitePlugins.push(configVisualizerConfig()); vitePlugins.push(configVisualizerConfig())
//vite-plugin-theme // vite-plugin-theme
vitePlugins.push(configThemePlugin(isBuild)); vitePlugins.push(configThemePlugin(isBuild))
// The following plugins only work in the production environment // The following plugins only work in the production environment
if (isBuild) { if (isBuild) {
//vite-plugin-imagemin // vite-plugin-imagemin
VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin()); VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin())
// rollup-plugin-gzip // rollup-plugin-gzip
vitePlugins.push( vitePlugins.push(
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE), configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
); )
// vite-plugin-pwa // vite-plugin-pwa
vitePlugins.push(configPwaConfig(viteEnv)); vitePlugins.push(configPwaConfig(viteEnv))
} }
return vitePlugins; return vitePlugins
} }

View File

@ -2,13 +2,13 @@
* Introduces component library styles on demand. * Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import * https://github.com/anncwb/vite-plugin-style-import
*/ */
import styleImport from 'vite-plugin-style-import'; import { createStyleImportPlugin } from 'vite-plugin-style-import'
export function configStyleImportPlugin(isBuild: boolean) { export function configStyleImportPlugin(_isBuild: boolean) {
if (!isBuild) { if (!_isBuild) {
return []; return []
} }
const styleImportPlugin = styleImport({ const styleImportPlugin = createStyleImportPlugin({
libs: [ libs: [
{ {
libraryName: 'ant-design-vue', libraryName: 'ant-design-vue',
@ -19,6 +19,7 @@ export function configStyleImportPlugin(isBuild: boolean) {
'anchor-link', 'anchor-link',
'sub-menu', 'sub-menu',
'menu-item', 'menu-item',
'menu-divider',
'menu-item-group', 'menu-item-group',
'breadcrumb-item', 'breadcrumb-item',
'breadcrumb-separator', 'breadcrumb-separator',
@ -44,7 +45,7 @@ export function configStyleImportPlugin(isBuild: boolean) {
'skeleton-paragraph', 'skeleton-paragraph',
'skeleton-image', 'skeleton-image',
'skeleton-button', 'skeleton-button',
]; ]
// 这里是需要额外引入样式的子组件列表 // 这里是需要额外引入样式的子组件列表
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失 // 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
const replaceList = { const replaceList = {
@ -63,16 +64,18 @@ export function configStyleImportPlugin(isBuild: boolean) {
'layout-footer': 'layout', 'layout-footer': 'layout',
'layout-header': 'layout', 'layout-header': 'layout',
'month-picker': 'date-picker', 'month-picker': 'date-picker',
}; 'range-picker': 'date-picker',
'image-preview-group': 'image',
}
return ignoreList.includes(name) return ignoreList.includes(name)
? '' ? ''
: replaceList.hasOwnProperty(name) : replaceList.hasOwnProperty(name)
? `ant-design-vue/es/${replaceList[name]}/style/index` ? `ant-design-vue/es/${replaceList[name]}/style/index`
: `ant-design-vue/es/${name}/style/index`; : `ant-design-vue/es/${name}/style/index`
}, },
}, },
], ],
}); })
return styleImportPlugin; return styleImportPlugin
} }

View File

@ -3,11 +3,11 @@
* https://github.com/anncwb/vite-plugin-svg-icons * https://github.com/anncwb/vite-plugin-svg-icons
*/ */
import SvgIconsPlugin from 'vite-plugin-svg-icons'; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path'; import path from 'path';
export function configSvgIconsPlugin(isBuild: boolean) { export function configSvgIconsPlugin(isBuild: boolean) {
const svgIconsPlugin = SvgIconsPlugin({ const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild, svgoOptions: isBuild,
// default // default

View File

@ -2,7 +2,7 @@
* Vite plugin for website theme color switching * Vite plugin for website theme color switching
* https://github.com/anncwb/vite-plugin-theme * https://github.com/anncwb/vite-plugin-theme
*/ */
import type { Plugin } from 'vite'; import type { PluginOption } from 'vite';
import path from 'path'; import path from 'path';
import { import {
viteThemePlugin, viteThemePlugin,
@ -14,7 +14,7 @@ import {
import { getThemeColors, generateColors } from '../../config/themeConfig'; import { getThemeColors, generateColors } from '../../config/themeConfig';
import { generateModifyVars } from '../../generate/generateModifyVars'; import { generateModifyVars } from '../../generate/generateModifyVars';
export function configThemePlugin(isBuild: boolean): Plugin[] { export function configThemePlugin(isBuild: boolean): PluginOption[] {
const colors = generateColors({ const colors = generateColors({
mixDarken, mixDarken,
mixLighten, mixLighten,
@ -85,5 +85,5 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
}), }),
]; ];
return plugin as unknown as Plugin[]; return plugin as unknown as PluginOption[];
} }

View File

@ -1,3 +1,23 @@
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 = { module.exports = {
ignores: [(commit) => commit.includes('init')], ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'], 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: 自定义',
},
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" id="htmlRoot"> <html lang="zh-cn" id="htmlRoot">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
@ -8,20 +8,19 @@
name="viewport" name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/> />
<title><%= title %></title> <title><%= title %></title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</head> </head>
<body> <body>
<script> <script>
(() => { ;(() => {
var htmlRoot = document.getElementById('htmlRoot'); var htmlRoot = document.getElementById('htmlRoot')
var theme = window.localStorage.getItem('__APP__DARK__MODE__'); var theme = window.localStorage.getItem('__APP__DARK__MODE__')
if (htmlRoot && theme) { if (htmlRoot && theme) {
htmlRoot.setAttribute('data-theme', theme); htmlRoot.setAttribute('data-theme', theme)
theme = htmlRoot = null; theme = htmlRoot = null
} }
})(); })()
</script> </script>
<div id="app"> <div id="app">
<style> <style>
@ -30,7 +29,7 @@
} }
html[data-theme='dark'] .app-loading .app-loading-title { html[data-theme='dark'] .app-loading .app-loading-title {
color: rgba(255, 255, 255, 0.85); color: rgb(255 255 255 / 85%);
} }
.app-loading { .app-loading {
@ -48,7 +47,6 @@
top: 50%; top: 50%;
left: 50%; left: 50%;
display: flex; display: flex;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0);
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -66,7 +64,7 @@
display: flex; display: flex;
margin-top: 30px; margin-top: 30px;
font-size: 30px; font-size: 30px;
color: rgba(0, 0, 0, 0.85); color: rgb(0 0 0 / 85%);
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
@ -97,7 +95,7 @@
height: 20px; height: 20px;
background-color: #0065cc; background-color: #0065cc;
border-radius: 100%; border-radius: 100%;
opacity: 0.3; opacity: 30%;
transform: scale(0.75); transform: scale(0.75);
animation: antSpinMove 1s infinite linear alternate; animation: antSpinMove 1s infinite linear alternate;
transform-origin: 50% 50%; transform-origin: 50% 50%;
@ -111,43 +109,38 @@
.dot i:nth-child(2) { .dot i:nth-child(2) {
top: 0; top: 0;
right: 0; right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.dot i:nth-child(3) { .dot i:nth-child(3) {
right: 0; right: 0;
bottom: 0; bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s; animation-delay: 0.8s;
} }
.dot i:nth-child(4) { .dot i:nth-child(4) {
bottom: 0; bottom: 0;
left: 0; left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s; animation-delay: 1.2s;
} }
@keyframes antRotate { @keyframes antRotate {
to { to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg); transform: rotate(405deg);
} }
} }
@-webkit-keyframes antRotate { @keyframes antRotate {
to { to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg); transform: rotate(405deg);
} }
} }
@keyframes antSpinMove { @keyframes antSpinMove {
to { to {
opacity: 1; opacity: 100%;
} }
} }
@-webkit-keyframes antSpinMove { @keyframes antSpinMove {
to { to {
opacity: 1; opacity: 100%;
} }
} }
</style> </style>

View File

@ -1,36 +0,0 @@
export default {
preset: 'ts-jest',
roots: ['<rootDir>/tests/'],
clearMocks: true,
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'],
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
testMatch: [
'**/tests/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[tj]s?(x)',
'(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
],
testPathIgnorePatterns: [
'<rootDir>/tests/server/',
'<rootDir>/tests/__mocks__/',
'/node_modules/',
],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
transformIgnorePatterns: ['<rootDir>/tests/__mocks__/', '/node_modules/'],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/tests/__mocks__/fileMock.ts',
'\\.(sass|s?css|less)$': '<rootDir>/tests/__mocks__/styleMock.ts',
'\\?worker$': '<rootDir>/tests/__mocks__/workerMock.ts',
'^/@/(.*)$': '<rootDir>/src/$1',
},
testEnvironment: 'jsdom',
verbose: true,
collectCoverage: false,
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{js,ts,vue}'],
coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'],
};

View File

@ -7,12 +7,13 @@
"url": "https://github.com/anncwb" "url": "https://github.com/anncwb"
}, },
"scripts": { "scripts": {
"bootstrap": "yarn install", "commit": "czg",
"bootstrap": "pnpm install",
"serve": "npm run dev", "serve": "npm run dev",
"dev": "vite", "dev": "vite",
"build": "cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts", "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:test": "cross-env vite build --mode test && esno ./build/script/postBuild.ts",
"build:no-cache": "yarn clean:cache && npm run build", "build:no-cache": "pnpm clean:cache && npm run build",
"report": "cross-env REPORT=true npm run build", "report": "cross-env REPORT=true npm run build",
"type:check": "vue-tsc --noEmit --skipLibCheck", "type:check": "vue-tsc --noEmit --skipLibCheck",
"preview": "npm run build && vite preview", "preview": "npm run build && vite preview",
@ -23,119 +24,131 @@
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", "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: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/", "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", "lint:lint-staged": "lint-staged",
"test:unit": "jest", "test:unit": "jest",
"test:unit-coverage": "jest --coverage",
"test:gzip": "npx http-server dist --cors --gzip -c-1", "test:gzip": "npx http-server dist --cors --gzip -c-1",
"test:br": "npx http-server dist --cors --brotli -c-1", "test:br": "npx http-server dist --cors --brotli -c-1",
"reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap", "reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
"prepare": "husky install", "prepare": "husky install",
"gen:icon": "esno ./build/generate/icon/index.ts" "gen:icon": "esno ./build/generate/icon/index.ts"
}, },
"dependencies": { "dependencies": {
"@ant-design/colors": "^6.0.0", "@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.0.1", "@ant-design/icons-vue": "^6.1.0",
"@iconify/iconify": "^2.0.4", "@iconify/iconify": "^2.2.1",
"@vueuse/core": "^6.7.4", "@logicflow/core": "^1.1.13",
"@vueuse/shared": "^6.7.4", "@logicflow/extension": "^1.1.13",
"@zxcvbn-ts/core": "^1.0.0-beta.0", "@vue/runtime-core": "^3.2.33",
"ant-design-vue": "2.2.8", "@vue/shared": "^3.2.33",
"axios": "^0.24.0", "@vueuse/core": "^8.3.0",
"@vueuse/shared": "^8.3.0",
"@zxcvbn-ts/core": "^2.0.1",
"ant-design-vue": "^3.2.0",
"axios": "^0.26.1",
"codemirror": "^5.65.3",
"cropperjs": "^1.5.12",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"echarts": "^5.2.2", "dayjs": "^1.11.1",
"echarts": "^5.3.2",
"intro.js": "^5.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"moment": "^2.29.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "2.0.0", "pinia": "2.0.12",
"qrcode": "^1.4.4", "print-js": "^1.6.0",
"qs": "^6.10.1", "qrcode": "^1.5.0",
"qs": "^6.10.3",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sortablejs": "^1.14.0", "showdown": "^2.1.0",
"vue": "^3.2.21", "sortablejs": "^1.15.0",
"tinymce": "^5.10.3",
"vditor": "^3.8.13",
"vue": "^3.2.33",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"vue-router": "^4.0.12", "vue-json-pretty": "^2.0.6",
"vue-types": "^4.1.1" "vue-router": "^4.0.14",
"vue-types": "^4.1.1",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^14.1.0", "@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^14.1.0", "@commitlint/config-conventional": "^16.2.1",
"@iconify/json": "^1.1.422", "@iconify/json": "^2.1.30",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.8.1",
"@types/codemirror": "^5.60.5", "@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.1.1",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.1.3", "@types/inquirer": "^8.2.1",
"@types/intro.js": "^3.0.2", "@types/intro.js": "^3.0.2",
"@types/jest": "^27.0.2", "@types/lodash-es": "^4.17.6",
"@types/lodash-es": "^4.17.5", "@types/mockjs": "^1.0.6",
"@types/mockjs": "^1.0.4", "@types/node": "^17.0.25",
"@types/node": "^16.11.6",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.1", "@types/qrcode": "^1.4.2",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/showdown": "^1.9.4", "@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^5.3.0", "@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.3.0", "@typescript-eslint/parser": "^5.20.0",
"@vitejs/plugin-legacy": "^1.6.2", "@vitejs/plugin-legacy": "^1.8.1",
"@vitejs/plugin-vue": "^1.9.4", "@vitejs/plugin-vue": "^2.3.1",
"@vitejs/plugin-vue-jsx": "^1.2.0", "@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/compiler-sfc": "3.2.21", "@vue/compiler-sfc": "^3.2.33",
"@vue/test-utils": "^2.0.0-rc.16", "@vue/test-utils": "^2.0.0-rc.21",
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.4",
"commitizen": "^4.2.4", "conventional-changelog-cli": "^2.2.2",
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^10.0.0", "cz-git": "^1.3.9",
"eslint": "^8.1.0", "czg": "^1.3.9",
"eslint-config-prettier": "^8.3.0", "dotenv": "^16.0.0",
"eslint-define-config": "^1.1.2", "eslint": "^8.13.0",
"eslint-plugin-jest": "^25.2.2", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.6.0",
"esno": "^0.10.1", "esno": "^0.14.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.1.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"inquirer": "^8.2.0", "inquirer": "^8.2.2",
"jest": "^27.3.1",
"less": "^4.1.2", "less": "^4.1.2",
"lint-staged": "11.2.6", "lint-staged": "12.3.7",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.3.11", "picocolors": "^1.0.0",
"postcss-html": "^1.2.0", "postcss": "^8.4.12",
"postcss-less": "^5.0.0", "postcss-html": "^1.4.1",
"prettier": "^2.4.1", "postcss-less": "^6.0.0",
"prettier": "^2.6.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-visualizer": "^5.5.2", "rollup": "^2.70.2",
"stylelint": "^14.0.1", "rollup-plugin-visualizer": "^5.6.0",
"stylelint-config-html": "^1.0.0", "stylelint": "^14.7.1",
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^23.0.0", "stylelint-config-recommended": "^7.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"ts-jest": "^27.0.7", "ts-node": "^10.7.0",
"ts-node": "^10.4.0", "typescript": "^4.6.3",
"typescript": "^4.4.4", "vite": "^2.9.5",
"vite": "^2.6.13", "vite-plugin-compression": "^0.5.1",
"vite-plugin-compression": "^0.3.5", "vite-plugin-html": "^3.2.0",
"vite-plugin-html": "^2.1.1", "vite-plugin-imagemin": "^0.6.1",
"vite-plugin-imagemin": "^0.4.6", "vite-plugin-mkcert": "^1.6.0",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.7.0", "vite-plugin-purge-icons": "^0.8.1",
"vite-plugin-pwa": "^0.11.3", "vite-plugin-pwa": "^0.11.13",
"vite-plugin-style-import": "^1.3.0", "vite-plugin-style-import": "^2.0.0",
"vite-plugin-svg-icons": "^1.0.5", "vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-theme": "^0.8.1", "vite-plugin-theme": "^0.8.6",
"vite-plugin-vue-setup-extend": "^0.1.0", "vite-plugin-vue-setup-extend": "^0.4.0",
"vite-plugin-windicss": "^1.4.12", "vite-plugin-windicss": "^1.8.4",
"vue-eslint-parser": "^8.0.1", "vue-eslint-parser": "^8.3.0",
"vue-tsc": "^0.28.10" "vue-tsc": "^0.33.9"
}, },
"resolutions": { "resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
"bin-wrapper": "npm:bin-wrapper-china", "bin-wrapper": "npm:bin-wrapper-china",
"rollup": "^2.56.3" "rollup": "^2.56.3",
"gifsicle": "5.2.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -148,5 +161,34 @@
"homepage": "https://github.com/anncwb/vue-vben-admin", "homepage": "https://github.com/anncwb/vue-vben-admin",
"engines": { "engines": {
"node": "^12 || >=14" "node": "^12 || >=14"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"package.json": [
"prettier --write"
],
"*.vue": [
"eslint --fix",
"prettier --write",
"stylelint --fix"
],
"*.{scss,less,styl,html}": [
"stylelint --fix",
"prettier --write"
],
"*.md": [
"prettier --write"
]
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
module.exports = { module.exports = {
printWidth: 100, printWidth: 100,
semi: true, semi: false,
vueIndentScriptAndStyle: true, vueIndentScriptAndStyle: true,
singleQuote: true, singleQuote: true,
trailingComma: 'all', trailingComma: 'all',
proseWrap: 'never', proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict', htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto', endOfLine: 'auto',
}; }

View File

@ -4,11 +4,11 @@
--> -->
<template> <template>
<Dropdown <Dropdown
placement="bottomCenter" placement="bottom"
:trigger="['click']" :trigger="['click']"
:dropMenuList="localeList" :dropMenuList="localeList"
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
@menuEvent="handleMenuEvent" @menu-event="handleMenuEvent"
overlayClassName="app-locale-picker-overlay" overlayClassName="app-locale-picker-overlay"
> >
<span class="cursor-pointer flex items-center"> <span class="cursor-pointer flex items-center">

View File

@ -1,6 +1,6 @@
<template> <template>
<div :class="prefixCls"> <div :class="prefixCls">
<CollapseHeader v-bind="$props" :prefixCls="prefixCls" :show="show" @expand="handleExpand"> <CollapseHeader v-bind="props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
<template #title> <template #title>
<slot name="title"></slot> <slot name="title"></slot>
</template> </template>
@ -25,6 +25,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { isNil } from 'lodash-es';
// component // component
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue';
import { CollapseTransition } from '/@/components/Transition'; import { CollapseTransition } from '/@/components/Transition';
@ -66,13 +67,17 @@
/** /**
* @description: Handling development events * @description: Handling development events
*/ */
function handleExpand() { function handleExpand(val: boolean) {
show.value = !show.value; show.value = isNil(val) ? !show.value : val;
if (props.triggerWindowResize) { if (props.triggerWindowResize) {
// 200 milliseconds here is because the expansion has animation, // 200 milliseconds here is because the expansion has animation,
useTimeoutFn(triggerWindowResize, 200); useTimeoutFn(triggerWindowResize, 200);
} }
} }
defineExpose({
handleExpand,
});
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container'; @prefix-cls: ~'@{namespace}-collapse-container';

View File

@ -1,6 +1,6 @@
<script lang="tsx"> <script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing'; 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 { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue'; import { Menu, Divider } from 'ant-design-vue';
@ -60,9 +60,11 @@
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y; const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
return { return {
...styles, ...styles,
position: 'absolute',
width: `${width}px`, width: `${width}px`,
left: `${left + 1}px`, left: `${left + 1}px`,
top: `${top + 1}px`, top: `${top + 1}px`,
zIndex: 9999,
}; };
}); });
@ -87,7 +89,8 @@
} }
function renderMenuItem(items: ContextMenuItem[]) { 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 { disabled, label, children, divider = false } = item;
const contentProps = { const contentProps = {
@ -124,15 +127,11 @@
} }
const { items } = props; const { items } = props;
return ( return (
<Menu <div class={prefixCls}>
inlineIndent={12} <Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
mode="vertical" {renderMenuItem(items)}
class={prefixCls} </Menu>
ref={wrapRef} </div>
style={unref(getStyle)}
>
{renderMenuItem(items)}
</Menu>
); );
}; };
}, },
@ -185,6 +184,9 @@
background-clip: padding-box; background-clip: padding-box;
user-select: none; user-select: none;
&__item {
margin: 0 !important;
}
.item-style(); .item-style();
.ant-divider { .ant-divider {

View File

@ -6,6 +6,7 @@ export interface Axis {
export interface ContextMenuItem { export interface ContextMenuItem {
label: string; label: string;
icon?: string; icon?: string;
hidden?: boolean;
disabled?: boolean; disabled?: boolean;
handler?: Fn; handler?: Fn;
divider?: boolean; divider?: boolean;

View File

@ -94,7 +94,7 @@
opt.width = '100%'; opt.width = '100%';
} }
const detailCls = `${prefixCls}__detail`; const detailCls = `${prefixCls}__detail`;
opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls; opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
if (!getContainer) { if (!getContainer) {
// TODO type error? // TODO type error?
@ -201,10 +201,6 @@
.@{prefix-cls} { .@{prefix-cls} {
.ant-drawer-wrapper-body { .ant-drawer-wrapper-body {
overflow: hidden; overflow: hidden;
display: flex;
flex-flow: column nowrap;
width: 100%;
height: 100%;
} }
.ant-drawer-close { .ant-drawer-close {
@ -213,19 +209,10 @@
} }
} }
.ant-drawer-content {
overflow: hidden;
}
.ant-drawer-body { .ant-drawer-body {
height: calc(100% - @header-height); height: calc(100% - @header-height);
padding: 0; padding: 0;
background-color: @component-background; background-color: @component-background;
flex-grow: 1;
overflow: auto;
font-size: 14px;
line-height: 1.5715;
word-wrap: break-word;
.scrollbar__wrap { .scrollbar__wrap {
padding: 16px !important; padding: 16px !important;

View File

@ -47,7 +47,7 @@
const heightStr = `${props.height}`; const heightStr = `${props.height}`;
return { return {
height: heightStr, height: heightStr,
lineHeight: heightStr, lineHeight: `calc(${heightStr} - 1px)`,
}; };
}); });

View File

@ -128,13 +128,12 @@ export interface DrawerProps extends DrawerFooterProps {
* @type any (string | slot) * @type any (string | slot)
*/ */
title?: VNodeChild | JSX.Element; title?: VNodeChild | JSX.Element;
/** /**
* The class name of the container of the Drawer dialog. * The class name of the container of the Drawer dialog.
* @type string * @type string
*/ */
wrapClassName?: string; wrapClassName?: string;
class?: string;
/** /**
* Style of wrapper element which **contains mask** compare to `drawerStyle` * Style of wrapper element which **contains mask** compare to `drawerStyle`
* @type object * @type object

View File

@ -9,7 +9,9 @@ export { useForm } from './src/hooks/useForm';
export { default as ApiSelect } from './src/components/ApiSelect.vue'; export { default as ApiSelect } from './src/components/ApiSelect.vue';
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; 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 ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
export { default as ApiCascader } from './src/components/ApiCascader.vue'; export { default as ApiCascader } from './src/components/ApiCascader.vue';
export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
export { BasicForm }; export { BasicForm };

View File

@ -58,15 +58,17 @@
import { createFormContext } from './hooks/useFormContext'; import { createFormContext } from './hooks/useFormContext';
import { useAutoFocus } from './hooks/useAutoFocus'; import { useAutoFocus } from './hooks/useAutoFocus';
import { useModalContext } from '/@/components/Modal'; import { useModalContext } from '/@/components/Modal';
import { useDebounceFn } from '@vueuse/core';
import { basicProps } from './props'; import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { cloneDeep } from 'lodash-es';
export default defineComponent({ export default defineComponent({
name: 'BasicForm', name: 'BasicForm',
components: { FormItem, Form, Row, FormAction }, components: { FormItem, Form, Row, FormAction },
props: basicProps, props: basicProps,
emits: ['advanced-change', 'reset', 'submit', 'register'], emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({}); const formModel = reactive<Recordable>({});
const modalFn = useModalContext(); const modalFn = useModalContext();
@ -122,7 +124,7 @@
if (!Array.isArray(defaultValue)) { if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue); schema.defaultValue = dateUtil(defaultValue);
} else { } else {
const def: moment.Moment[] = []; const def: any[] = [];
defaultValue.forEach((item) => { defaultValue.forEach((item) => {
def.push(dateUtil(item)); def.push(dateUtil(item));
}); });
@ -131,9 +133,11 @@
} }
} }
if (unref(getProps).showAdvancedButton) { if (unref(getProps).showAdvancedButton) {
return schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[]; return cloneDeep(
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
);
} else { } else {
return schemas as FormSchema[]; return cloneDeep(schemas as FormSchema[]);
} }
}); });
@ -170,7 +174,7 @@
updateSchema, updateSchema,
resetSchema, resetSchema,
appendSchemaByField, appendSchemaByField,
removeSchemaByFiled, removeSchemaByField,
resetFields, resetFields,
scrollToField, scrollToField,
} = useFormEvents({ } = useFormEvents({
@ -225,6 +229,14 @@
}, },
); );
watch(
() => formModel,
useDebounceFn(() => {
unref(getProps).submitOnChange && handleSubmit();
}, 300),
{ deep: true },
);
async function setProps(formProps: Partial<FormProps>): Promise<void> { async function setProps(formProps: Partial<FormProps>): Promise<void> {
propsRef.value = deepMerge(unref(propsRef) || {}, formProps); propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
} }
@ -235,6 +247,7 @@
if (!validateTrigger || validateTrigger === 'change') { if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {}); validateFields([key]).catch((_) => {});
} }
emit('field-value-change', key, value);
} }
function handleEnterPress(e: KeyboardEvent) { function handleEnterPress(e: KeyboardEvent) {
@ -255,7 +268,7 @@
updateSchema, updateSchema,
resetSchema, resetSchema,
setProps, setProps,
removeSchemaByFiled, removeSchemaByField,
appendSchemaByField, appendSchemaByField,
clearValidate, clearValidate,
validateFields, validateFields,

View File

@ -24,8 +24,10 @@ import {
import ApiRadioGroup from './components/ApiRadioGroup.vue'; import ApiRadioGroup from './components/ApiRadioGroup.vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue'; import RadioButtonGroup from './components/RadioButtonGroup.vue';
import ApiSelect from './components/ApiSelect.vue'; import ApiSelect from './components/ApiSelect.vue';
import ApiTree from './components/ApiTree.vue';
import ApiTreeSelect from './components/ApiTreeSelect.vue'; import ApiTreeSelect from './components/ApiTreeSelect.vue';
import ApiCascader from './components/ApiCascader.vue'; import ApiCascader from './components/ApiCascader.vue';
import ApiTransfer from './components/ApiTransfer.vue';
import { BasicUpload } from '/@/components/Upload'; import { BasicUpload } from '/@/components/Upload';
import { StrengthMeter } from '/@/components/StrengthMeter'; import { StrengthMeter } from '/@/components/StrengthMeter';
import { IconPicker } from '/@/components/Icon'; import { IconPicker } from '/@/components/Icon';
@ -43,6 +45,7 @@ componentMap.set('AutoComplete', AutoComplete);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect);
componentMap.set('ApiTree', ApiTree);
componentMap.set('TreeSelect', TreeSelect); componentMap.set('TreeSelect', TreeSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('ApiRadioGroup', ApiRadioGroup); componentMap.set('ApiRadioGroup', ApiRadioGroup);
@ -55,6 +58,7 @@ componentMap.set('ApiCascader', ApiCascader);
componentMap.set('Cascader', Cascader); componentMap.set('Cascader', Cascader);
componentMap.set('Slider', Slider); componentMap.set('Slider', Slider);
componentMap.set('Rate', Rate); componentMap.set('Rate', Rate);
componentMap.set('ApiTransfer', ApiTransfer);
componentMap.set('DatePicker', DatePicker); componentMap.set('DatePicker', DatePicker);
componentMap.set('MonthPicker', DatePicker.MonthPicker); componentMap.set('MonthPicker', DatePicker.MonthPicker);

View File

@ -26,7 +26,7 @@
import { get, omit } from 'lodash-es'; import { get, omit } from 'lodash-es';
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { LoadingOutlined } from '@ant-design/icons-vue'; import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
interface Option { interface Option {
value: string; value: string;
label: string; label: string;
@ -76,7 +76,7 @@
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const emitData = ref<any[]>([]); const emitData = ref<any[]>([]);
const isFirstLoad = ref(true); const isFirstLoad = ref(true);
const { t } = useI18n();
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData); const [state] = useRuleFormItem(props, 'value', 'change', emitData);
@ -188,6 +188,7 @@
state, state,
options, options,
loading, loading,
t,
handleChange, handleChange,
loadData, loadData,
handleRenderDisplay, handleRenderDisplay,

View File

@ -1,6 +1,6 @@
<template> <template>
<Select <Select
@dropdownVisibleChange="handleFetch" @dropdown-visible-change="handleFetch"
v-bind="$attrs" v-bind="$attrs"
@change="handleChange" @change="handleChange"
:options="getOptions" :options="getOptions"
@ -57,6 +57,7 @@
labelField: propTypes.string.def('label'), labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'), valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true), immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(false),
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change'],
setup(props, { emit }) { setup(props, { emit }) {
@ -87,7 +88,7 @@
}); });
watchEffect(() => { watchEffect(() => {
props.immediate && fetch(); props.immediate && !props.alwaysLoad && fetch();
}); });
watch( watch(
@ -121,10 +122,14 @@
} }
} }
async function handleFetch() { async function handleFetch(visible) {
if (!props.immediate && unref(isFirstLoad)) { if (visible) {
await fetch(); if (props.alwaysLoad) {
isFirstLoad.value = false; await fetch();
} else if (!props.immediate && unref(isFirstLoad)) {
await fetch();
isFirstLoad.value = false;
}
} }
} }

View File

@ -0,0 +1,134 @@
<template>
<Transfer
:data-source="getdataSource"
:filter-option="filterOption"
:render="(item) => item.title"
:showSelectAll="showSelectAll"
:selectedKeys="selectedKeys"
:targetKeys="getTargetKeys"
:showSearch="showSearch"
@change="handleChange"
/>
</template>
<script lang="ts">
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue';
import { Transfer } from 'ant-design-vue';
import { isFunction } from '/@/utils/is';
import { get, omit } from 'lodash-es';
import { propTypes } from '/@/utils/propTypes';
import { useI18n } from '/@/hooks/web/useI18n';
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer';
export default defineComponent({
name: 'ApiTransfer',
components: { Transfer },
props: {
value: { type: Array as PropType<Array<string>> },
api: {
type: Function as PropType<(arg?: Recordable) => Promise<TransferItem[]>>,
default: null,
},
params: { type: Object },
dataSource: { type: Array as PropType<Array<TransferItem>> },
immediate: propTypes.bool.def(true),
alwaysLoad: propTypes.bool.def(false),
afterFetch: { type: Function as PropType<Fn> },
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('title'),
valueField: propTypes.string.def('key'),
showSearch: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
filterOption: {
type: Function as PropType<(inputValue: string, item: TransferItem) => boolean>,
},
selectedKeys: { type: Array as PropType<Array<string>> },
showSelectAll: { type: Boolean, default: false },
targetKeys: { type: Array as PropType<Array<string>> },
},
emits: ['options-change', 'change'],
setup(props, { attrs, emit }) {
const _dataSource = ref<TransferItem[]>([]);
const _targetKeys = ref<string[]>([]);
const { t } = useI18n();
const getAttrs = computed(() => {
return {
...(!props.api ? { dataSource: unref(_dataSource) } : {}),
...attrs,
};
});
const getdataSource = computed(() => {
const { labelField, valueField } = props;
return unref(_dataSource).reduce((prev, next: Recordable) => {
if (next) {
prev.push({
...omit(next, [labelField, valueField]),
title: next[labelField],
key: next[valueField],
});
}
return prev;
}, [] as TransferItem[]);
});
const getTargetKeys = computed<string[]>(() => {
if (unref(_targetKeys).length > 0) {
return unref(_targetKeys);
}
if (Array.isArray(props.value)) {
return props.value;
}
return [];
});
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
_targetKeys.value = keys;
console.log(direction);
console.log(moveKeys);
emit('change', keys);
}
watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch();
});
watch(
() => props.params,
() => {
fetch();
},
{ deep: true },
);
async function fetch() {
const api = props.api;
if (!api || !isFunction(api)) {
if (Array.isArray(props.dataSource)) {
_dataSource.value = props.dataSource;
}
return;
}
_dataSource.value = [];
try {
const res = await api(props.params);
if (Array.isArray(res)) {
_dataSource.value = res;
emitChange();
return;
}
if (props.resultField) {
_dataSource.value = get(res, props.resultField) || [];
}
emitChange();
} catch (error) {
console.warn(error);
} finally {
}
}
function emitChange() {
emit('options-change', unref(getdataSource));
}
return { getTargetKeys, getdataSource, t, getAttrs, handleChange };
},
});
</script>

View File

@ -0,0 +1,90 @@
<template>
<a-tree v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</a-tree>
</template>
<script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import { Tree } from 'ant-design-vue';
import { isArray, isFunction } from '/@/utils/is';
import { get } from 'lodash-es';
import { propTypes } from '/@/utils/propTypes';
import { LoadingOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'ApiTree',
components: { ATree: Tree, LoadingOutlined },
props: {
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
params: { type: Object },
immediate: { type: Boolean, default: true },
resultField: propTypes.string.def(''),
afterFetch: { type: Function as PropType<Fn> },
},
emits: ['options-change', 'change'],
setup(props, { attrs, emit }) {
const treeData = ref<Recordable[]>([]);
const isFirstLoaded = ref<Boolean>(false);
const loading = ref(false);
const getAttrs = computed(() => {
return {
...(props.api ? { treeData: unref(treeData) } : {}),
...attrs,
};
});
function handleChange(...args) {
emit('change', ...args);
}
watch(
() => props.params,
() => {
!unref(isFirstLoaded) && fetch();
},
{ deep: true },
);
watch(
() => props.immediate,
(v) => {
v && !isFirstLoaded.value && fetch();
},
);
onMounted(() => {
props.immediate && fetch();
});
async function fetch() {
const { api, afterFetch } = props;
if (!api || !isFunction(api)) return;
loading.value = true;
treeData.value = [];
let result;
try {
result = await api(props.params);
} catch (e) {
console.error(e);
}
if (afterFetch && isFunction(afterFetch)) {
result = afterFetch(result);
}
loading.value = false;
if (!result) return;
if (!isArray(result)) {
result = get(result, props.resultField);
}
treeData.value = (result as Recordable[]) || [];
isFirstLoaded.value = true;
emit('options-change', treeData.value);
}
return { getAttrs, loading, handleChange };
},
});
</script>

View File

@ -1,17 +1,16 @@
<script lang="tsx"> <script lang="tsx">
import type { PropType, Ref } from 'vue'; import type { PropType, Ref } from 'vue';
import type { FormActionType, FormProps } from '../types/form'; import { computed, defineComponent, toRefs, unref } from 'vue';
import type { FormSchema } from '../types/form'; import type { FormActionType, FormProps, FormSchema } from '../types/form';
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { TableActionType } from '/@/components/Table'; import type { TableActionType } from '/@/components/Table';
import { defineComponent, computed, unref, toRefs } from 'vue'; import { Col, Divider, Form } from 'ant-design-vue';
import { Form, Col, Divider } from 'ant-design-vue';
import { componentMap } from '../componentMap'; import { componentMap } from '../componentMap';
import { BasicHelp } from '/@/components/Basic'; import { BasicHelp } from '/@/components/Basic';
import { isBoolean, isFunction, isNull } from '/@/utils/is'; import { isBoolean, isFunction, isNull } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { createPlaceholderMessage, setComponentRuleType } from '../helper'; import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { upperFirst, cloneDeep } from 'lodash-es'; import { cloneDeep, upperFirst } from 'lodash-es';
import { useItemLabelWidth } from '../hooks/useLabelWidth'; import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
@ -178,8 +177,21 @@
const getRequired = isFunction(required) ? required(unref(getValues)) : required; const getRequired = isFunction(required) ? required(unref(getValues)) : required;
if ((!rules || rules.length === 0) && getRequired) { /*
rules = [{ required: getRequired, validator }]; * 1若设置了required属性又没有其他的rules就创建一个验证规则
* 2若设置了required属性又存在其他的rules则只rules中不存在required属性时才添加验证required的规则
* 也就是说rules中的required优先级大于required
*/
if (getRequired) {
if (!rules || rules.length === 0) {
rules = [{ required: getRequired, validator }];
} else {
const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required'));
if (requiredIndex === -1) {
rules.push({ required: getRequired, validator });
}
}
} }
const requiredRuleIndex: number = rules.findIndex( const requiredRuleIndex: number = rules.findIndex(

View File

@ -70,3 +70,5 @@ export function handleInputNumberValue(component?: ComponentType, val?: any) {
* *
*/ */
export const dateItemType = genType(); export const dateItemType = genType();
export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'InputTextArea'];

View File

@ -1,6 +1,6 @@
import type { ColEx } from '../types'; import type { ColEx } from '../types';
import type { AdvanceState } from '../types/hooks'; import type { AdvanceState } from '../types/hooks';
import type { ComputedRef, Ref } from 'vue'; import { ComputedRef, getCurrentInstance, Ref } from 'vue';
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form';
import { computed, unref, watch } from 'vue'; import { computed, unref, watch } from 'vue';
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'; import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
@ -26,6 +26,8 @@ export default function ({
formModel, formModel,
defaultValueRef, defaultValueRef,
}: UseAdvancedContext) { }: UseAdvancedContext) {
const vm = getCurrentInstance();
const { realWidthRef, screenEnum, screenRef } = useBreakpoint(); const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
const getEmptySpan = computed((): number => { const getEmptySpan = computed((): number => {
@ -150,6 +152,9 @@ export default function ({
} }
} }
// 确保页面发送更新
vm?.proxy?.$forceUpdate();
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan); advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true); getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);

View File

@ -79,8 +79,8 @@ export function useForm(props?: Props): UseFormReturnType {
}); });
}, },
removeSchemaByFiled: async (field: string | string[]) => { removeSchemaByField: async (field: string | string[]) => {
unref(formRef)?.removeSchemaByFiled(field); unref(formRef)?.removeSchemaByField(field);
}, },
// TODO promisify // TODO promisify

View File

@ -1,10 +1,10 @@
import type { ComputedRef, Ref } from 'vue'; import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form'; import type { FormProps, FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface'; import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw } from 'vue'; import { unref, toRaw, nextTick } from 'vue';
import { isArray, isFunction, isObject, isString } from '/@/utils/is'; import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue } from '../helper'; import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil';
import { cloneDeep, uniqBy } from 'lodash-es'; import { cloneDeep, uniqBy } from 'lodash-es';
import { error } from '/@/utils/log'; import { error } from '/@/utils/log';
@ -37,9 +37,13 @@ export function useFormEvents({
if (!formEl) return; if (!formEl) return;
Object.keys(formModel).forEach((key) => { Object.keys(formModel).forEach((key) => {
formModel[key] = defaultValueRef.value[key]; const schema = unref(getSchema).find((item) => item.field === key);
const isInput = schema?.component && defaultValueComponents.includes(schema.component);
const defaultValue = cloneDeep(defaultValueRef.value[key]);
formModel[key] = isInput ? defaultValue || '' : defaultValue;
}); });
clearValidate(); nextTick(() => clearValidate());
emit('reset', toRaw(formModel)); emit('reset', toRaw(formModel));
submitOnReset && handleSubmit(); submitOnReset && handleSubmit();
} }
@ -52,6 +56,10 @@ export function useFormEvents({
.map((item) => item.field) .map((item) => item.field)
.filter(Boolean); .filter(Boolean);
// key 支持 a.b.c 的嵌套写法
const delimiter = '.';
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0);
const validKeys: string[] = []; const validKeys: string[] = [];
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key); const schema = unref(getSchema).find((item) => item.field === key);
@ -82,6 +90,21 @@ export function useFormEvents({
formModel[key] = value; formModel[key] = value;
} }
validKeys.push(key); validKeys.push(key);
} else {
nestKeyArray.forEach((nestKey: string) => {
try {
const value = eval('values' + delimiter + nestKey);
if (isDef(value)) {
formModel[nestKey] = value;
validKeys.push(nestKey);
}
} catch (e) {
// key not exist
if (isDef(defaultValueRef.value[nestKey])) {
formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey]);
}
}
});
} }
}); });
validateFields(validKeys).catch((_) => {}); validateFields(validKeys).catch((_) => {});
@ -89,7 +112,7 @@ export function useFormEvents({
/** /**
* @description: Delete based on field name * @description: Delete based on field name
*/ */
async function removeSchemaByFiled(fields: string | string[]): Promise<void> { async function removeSchemaByField(fields: string | string[]): Promise<void> {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema)); const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
if (!fields) { if (!fields) {
return; return;
@ -100,7 +123,7 @@ export function useFormEvents({
fieldList = [fields]; fieldList = [fields];
} }
for (const field of fieldList) { for (const field of fieldList) {
_removeSchemaByFiled(field, schemaList); _removeSchemaByFeild(field, schemaList);
} }
schemaRef.value = schemaList; schemaRef.value = schemaList;
} }
@ -108,7 +131,7 @@ export function useFormEvents({
/** /**
* @description: Delete based on field name * @description: Delete based on field name
*/ */
function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void { function _removeSchemaByFeild(field: string, schemaList: FormSchema[]): void {
if (isString(field)) { if (isString(field)) {
const index = schemaList.findIndex((schema) => schema.field === field); const index = schemaList.findIndex((schema) => schema.field === field);
if (index !== -1) { if (index !== -1) {
@ -125,18 +148,18 @@ export function useFormEvents({
const schemaList: FormSchema[] = cloneDeep(unref(getSchema)); const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
const index = schemaList.findIndex((schema) => schema.field === prefixField); const index = schemaList.findIndex((schema) => schema.field === prefixField);
const hasInList = schemaList.some((item) => item.field === prefixField || schema.field);
if (!hasInList) return;
if (!prefixField || index === -1 || first) { if (!prefixField || index === -1 || first) {
first ? schemaList.unshift(schema) : schemaList.push(schema); first ? schemaList.unshift(schema) : schemaList.push(schema);
schemaRef.value = schemaList; schemaRef.value = schemaList;
_setDefaultValue(schema);
return; return;
} }
if (index !== -1) { if (index !== -1) {
schemaList.splice(index + 1, 0, schema); schemaList.splice(index + 1, 0, schema);
} }
_setDefaultValue(schema);
schemaRef.value = schemaList; schemaRef.value = schemaList;
} }
@ -192,9 +215,36 @@ export function useFormEvents({
} }
}); });
}); });
_setDefaultValue(schema);
schemaRef.value = uniqBy(schema, 'field'); schemaRef.value = uniqBy(schema, 'field');
} }
function _setDefaultValue(data: FormSchema | FormSchema[]) {
let schemas: FormSchema[] = [];
if (isObject(data)) {
schemas.push(data as FormSchema);
}
if (isArray(data)) {
schemas = [...data];
}
const obj: Recordable = {};
const currentFieldsValue = getFieldsValue();
schemas.forEach((item) => {
if (
item.component != 'Divider' &&
Reflect.has(item, 'field') &&
item.field &&
!isNullOrUnDef(item.defaultValue) &&
!(item.field in currentFieldsValue)
) {
obj[item.field] = item.defaultValue;
}
});
setFieldsValue(obj);
}
function getFieldsValue(): Recordable { function getFieldsValue(): Recordable {
const formEl = unref(formElRef); const formEl = unref(formElRef);
if (!formEl) return {}; if (!formEl) return {};
@ -256,7 +306,7 @@ export function useFormEvents({
updateSchema, updateSchema,
resetSchema, resetSchema,
appendSchemaByField, appendSchemaByField,
removeSchemaByFiled, removeSchemaByField,
resetFields, resetFields,
setFieldsValue, setFieldsValue,
scrollToField, scrollToField,

View File

@ -3,7 +3,7 @@ import { dateUtil } from '/@/utils/dateUtil';
import { unref } from 'vue'; import { unref } from 'vue';
import type { Ref, ComputedRef } from 'vue'; import type { Ref, ComputedRef } from 'vue';
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form';
import { set } from 'lodash-es'; import { cloneDeep, set } from 'lodash-es';
interface UseFormValuesContext { interface UseFormValuesContext {
defaultValueRef: Ref<any>; defaultValueRef: Ref<any>;
@ -11,6 +11,43 @@ interface UseFormValuesContext {
getProps: ComputedRef<FormProps>; getProps: ComputedRef<FormProps>;
formModel: Recordable; formModel: Recordable;
} }
/**
* @desription deconstruct array-link key. This method will mutate the target.
*/
function tryDeconstructArray(key: string, value: any, target: Recordable) {
const pattern = /^\[(.+)\]$/;
if (pattern.test(key)) {
const match = key.match(pattern);
if (match && match[1]) {
const keys = match[1].split(',');
value = Array.isArray(value) ? value : [value];
keys.forEach((k, index) => {
set(target, k.trim(), value[index]);
});
return true;
}
}
}
/**
* @desription deconstruct object-link key. This method will mutate the target.
*/
function tryDeconstructObject(key: string, value: any, target: Recordable) {
const pattern = /^\{(.+)\}$/;
if (pattern.test(key)) {
const match = key.match(pattern);
if (match && match[1]) {
const keys = match[1].split(',');
value = isObject(value) ? value : {};
keys.forEach((k) => {
set(target, k.trim(), value[k.trim()]);
});
return true;
}
}
}
export function useFormValues({ export function useFormValues({
defaultValueRef, defaultValueRef,
getSchema, getSchema,
@ -33,14 +70,18 @@ export function useFormValues({
if (isObject(value)) { if (isObject(value)) {
value = transformDateFunc?.(value); value = transformDateFunc?.(value);
} }
if (isArray(value) && value[0]?._isAMomentObject && value[1]?._isAMomentObject) {
if (isArray(value) && value[0]?.format && value[1]?.format) {
value = value.map((item) => transformDateFunc?.(item)); value = value.map((item) => transformDateFunc?.(item));
} }
// Remove spaces // Remove spaces
if (isString(value)) { if (isString(value)) {
value = value.trim(); value = value.trim();
} }
set(res, key, value); if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
// 没有解构成功的,按原样赋值
set(res, key, value);
}
} }
return handleRangeTimeValue(res); return handleRangeTimeValue(res);
} }
@ -77,10 +118,13 @@ export function useFormValues({
const { defaultValue } = item; const { defaultValue } = item;
if (!isNullOrUnDef(defaultValue)) { if (!isNullOrUnDef(defaultValue)) {
obj[item.field] = defaultValue; obj[item.field] = defaultValue;
formModel[item.field] = defaultValue;
if (formModel[item.field] === undefined) {
formModel[item.field] = defaultValue;
}
} }
}); });
defaultValueRef.value = obj; defaultValueRef.value = cloneDeep(obj);
} }
return { handleFormValues, initDefault }; return { handleFormValues, initDefault };

View File

@ -1,7 +1,6 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import { computed, unref } from 'vue'; import { computed, unref } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import { isNumber } from '/@/utils/is'; import { isNumber } from '/@/utils/is';
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) { export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
@ -14,6 +13,7 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
labelWidth: globalLabelWidth, labelWidth: globalLabelWidth,
labelCol: globalLabelCol, labelCol: globalLabelCol,
wrapperCol: globWrapperCol, wrapperCol: globWrapperCol,
layout,
} = unref(propsRef); } = unref(propsRef);
// If labelWidth is set globally, all items setting // If labelWidth is set globally, all items setting
@ -33,7 +33,10 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
return { return {
labelCol: { style: { width }, ...col }, labelCol: { style: { width }, ...col },
wrapperCol: { style: { width: `calc(100% - ${width})` }, ...wrapCol }, wrapperCol: {
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
...wrapCol,
},
}; };
}); });
} }

View File

@ -40,6 +40,7 @@ export const basicProps = {
// 在INPUT组件上单击回车时是否自动提交 // 在INPUT组件上单击回车时是否自动提交
autoSubmitOnEnter: propTypes.bool.def(false), autoSubmitOnEnter: propTypes.bool.def(false),
submitOnReset: propTypes.bool, submitOnReset: propTypes.bool,
submitOnChange: propTypes.bool,
size: propTypes.oneOf(['default', 'small', 'large']).def('default'), size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
// 禁用表单 // 禁用表单
disabled: propTypes.bool, disabled: propTypes.bool,
@ -53,7 +54,7 @@ export const basicProps = {
transformDateFunc: { transformDateFunc: {
type: Function as PropType<Fn>, type: Function as PropType<Fn>,
default: (date: any) => { default: (date: any) => {
return date._isAMomentObject ? date?.format('YYYY-MM-DD HH:mm:ss') : date; return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date;
}, },
}, },
rulesMessageJoinLabel: propTypes.bool.def(true), rulesMessageJoinLabel: propTypes.bool.def(true),

View File

@ -33,7 +33,7 @@ export interface FormActionType {
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>; updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>; resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
setProps: (formProps: Partial<FormProps>) => Promise<void>; setProps: (formProps: Partial<FormProps>) => Promise<void>;
removeSchemaByFiled: (field: string | string[]) => Promise<void>; removeSchemaByField: (field: string | string[]) => Promise<void>;
appendSchemaByField: ( appendSchemaByField: (
schema: FormSchema, schema: FormSchema,
prefixField: string | undefined, prefixField: string | undefined,
@ -49,17 +49,20 @@ export type RegisterFn = (formInstance: FormActionType) => void;
export type UseFormReturnType = [RegisterFn, FormActionType]; export type UseFormReturnType = [RegisterFn, FormActionType];
export interface FormProps { export interface FormProps {
name?: string;
layout?: 'vertical' | 'inline' | 'horizontal'; layout?: 'vertical' | 'inline' | 'horizontal';
// Form value // Form value
model?: Recordable; model?: Recordable;
// The width of all items in the entire form // The width of all items in the entire form
labelWidth?: number | string; labelWidth?: number | string;
//alignment // alignment
labelAlign?: 'left' | 'right'; labelAlign?: 'left' | 'right';
//Row configuration for the entire form // Row configuration for the entire form
rowProps?: RowProps; rowProps?: RowProps;
// Submit form on reset // Submit form on reset
submitOnReset?: boolean; submitOnReset?: boolean;
// Submit form on form changing
submitOnChange?: boolean;
// Col configuration for the entire form // Col configuration for the entire form
labelCol?: Partial<ColEx>; labelCol?: Partial<ColEx>;
// Col configuration for the entire form // Col configuration for the entire form

View File

@ -91,6 +91,7 @@ export type ComponentType =
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'TreeSelect' | 'TreeSelect'
| 'ApiTree'
| 'ApiTreeSelect' | 'ApiTreeSelect'
| 'ApiRadioGroup' | 'ApiRadioGroup'
| 'RadioButtonGroup' | 'RadioButtonGroup'
@ -112,4 +113,5 @@ export type ComponentType =
| 'Render' | 'Render'
| 'Slider' | 'Slider'
| 'Rate' | 'Rate'
| 'Divider'; | 'Divider'
| 'ApiTransfer';

View File

@ -6,7 +6,7 @@
:openKeys="getOpenKeys" :openKeys="getOpenKeys"
:inlineIndent="inlineIndent" :inlineIndent="inlineIndent"
:theme="theme" :theme="theme"
@openChange="handleOpenChange" @open-change="handleOpenChange"
:class="getMenuClass" :class="getMenuClass"
@click="handleMenuClick" @click="handleMenuClick"
:subMenuOpenDelay="0.2" :subMenuOpenDelay="0.2"

View File

@ -10,7 +10,7 @@ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
emits: ['cancel'], emits: ['cancel'],
setup(props, { slots }) { setup(props, { slots, emit }) {
const { visible, draggable, destroyOnClose } = toRefs(props); const { visible, draggable, destroyOnClose } = toRefs(props);
const attrs = useAttrs(); const attrs = useAttrs();
useModalDragMove({ useModalDragMove({
@ -19,8 +19,12 @@ export default defineComponent({
draggable, draggable,
}); });
const onCancel = (e: Event) => {
emit('cancel', e);
};
return () => { return () => {
const propsData = { ...unref(attrs), ...props } as Recordable; const propsData = { ...unref(attrs), ...props, onCancel } as Recordable;
return <Modal {...propsData}>{extendSlots(slots)}</Modal>; return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
}; };
}, },

View File

@ -26,7 +26,6 @@
&-title { &-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: 16px;
.base-title { .base-title {
cursor: move !important; cursor: move !important;

View File

@ -5,7 +5,7 @@
:title="title" :title="title"
v-bind="omit($attrs, 'class')" v-bind="omit($attrs, 'class')"
ref="headerRef" ref="headerRef"
v-if="content || $slots.headerContent || title || getHeaderSlots.length" v-if="getShowHeader"
> >
<template #default> <template #default>
<template v-if="content"> <template v-if="content">
@ -99,6 +99,10 @@
]; ];
}); });
const getShowHeader = computed(
() => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
);
const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter); const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
const getHeaderSlots = computed(() => { const getHeaderSlots = computed(() => {
@ -150,6 +154,7 @@
getClass, getClass,
getHeaderSlots, getHeaderSlots,
prefixCls, prefixCls,
getShowHeader,
getShowFooter, getShowFooter,
omit, omit,
getContentClass, getContentClass,

View File

@ -1,7 +1,7 @@
import { toCanvas } from 'qrcode'; import { toCanvas } from 'qrcode'
import type { QRCodeRenderersOptions } from 'qrcode'; import type { QRCodeRenderersOptions } from 'qrcode'
import { RenderQrCodeParams, ContentType } from './typing'; import { RenderQrCodeParams, ContentType } from './typing'
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es'
export const renderQrCode = ({ export const renderQrCode = ({
canvas, canvas,
@ -9,29 +9,29 @@ export const renderQrCode = ({
width = 0, width = 0,
options: params = {}, options: params = {},
}: RenderQrCodeParams) => { }: RenderQrCodeParams) => {
const options = cloneDeep(params); const options = cloneDeep(params)
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率 // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content); options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content)
return getOriginWidth(content, options).then((_width: number) => { return getOriginWidth(content, options).then((_width: number) => {
options.scale = width === 0 ? undefined : (width / _width) * 4; options.scale = width === 0 ? undefined : (width / _width) * 4
return toCanvas(canvas, content, options); return toCanvas(canvas, content, options)
}); })
}; }
// 得到原QrCode的大小以便缩放得到正确的QrCode大小 // 得到原QrCode的大小以便缩放得到正确的QrCode大小
function getOriginWidth(content: ContentType, options: QRCodeRenderersOptions) { function getOriginWidth(content: ContentType, options: QRCodeRenderersOptions) {
const _canvas = document.createElement('canvas'); const _canvas = document.createElement('canvas')
return toCanvas(_canvas, content, options).then(() => _canvas.width); return toCanvas(_canvas, content, options).then(() => _canvas.width)
} }
// 对于内容少的QrCode增大容错率 // 对于内容少的QrCode增大容错率
function getErrorCorrectionLevel(content: ContentType) { function getErrorCorrectionLevel(content: ContentType) {
if (content.length > 36) { if (content.length > 36) {
return 'M'; return 'M'
} else if (content.length > 16) { } else if (content.length > 16) {
return 'Q'; return 'Q'
} else { } else {
return 'H'; return 'H'
} }
} }

View File

@ -34,7 +34,10 @@
</span> </span>
<SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" /> <SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" />
</template> </template>
<template v-for="childrenItem in item.children || []" :key="childrenItem.path"> <template
v-for="childrenItem in item.children || []"
:key="childrenItem.paramPath || childrenItem.path"
>
<SimpleSubMenu v-bind="$props" :item="childrenItem" :parent="false" /> <SimpleSubMenu v-bind="$props" :item="childrenItem" :parent="false" />
</template> </template>
</SubMenu> </SubMenu>

View File

@ -21,7 +21,7 @@
:overlayClassName="`${prefixCls}-menu-popover`" :overlayClassName="`${prefixCls}-menu-popover`"
v-else v-else
:visible="getIsOpend" :visible="getIsOpend"
@visibleChange="handleVisibleChange" @visible-change="handleVisibleChange"
:overlayStyle="getOverlayStyle" :overlayStyle="getOverlayStyle"
:align="{ offset: [0, 0] }" :align="{ offset: [0, 0] }"
> >

View File

@ -13,8 +13,8 @@
bottom: 0; bottom: 0;
display: block; display: block;
width: 2px; width: 2px;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
} }
@ -45,8 +45,8 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 18px; right: 18px;
transform: translateY(-50%) rotate(-90deg);
transition: transform @transition-time @ease-in-out; transition: transform @transition-time @ease-in-out;
transform: translateY(-50%) rotate(-90deg);
} }
} }
@ -128,12 +128,12 @@
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex; display: flex;
align-items: center;
font-size: @font-size-base; font-size: @font-size-base;
color: inherit; color: inherit;
list-style: none; list-style: none;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
align-items: center;
&:hover, &:hover,
&:active { &:active {
@ -178,8 +178,8 @@
&-vertical &-submenu-collapse { &-vertical &-submenu-collapse {
.@{submenu-popup-prefix-cls} { .@{submenu-popup-prefix-cls} {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
justify-content: center;
} }
.@{menu-prefix-cls}-submenu-collapsed-show-tit { .@{menu-prefix-cls}-submenu-collapsed-show-tit {
flex-direction: column; flex-direction: column;
@ -244,8 +244,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
} }
} }
@ -276,8 +276,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
.@{menu-prefix-cls}-submenu-collapse { .@{menu-prefix-cls}-submenu-collapse {

View File

@ -2,6 +2,7 @@ export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction.vue'; export { default as TableAction } from './src/components/TableAction.vue';
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
export { default as TableImg } from './src/components/TableImg.vue'; export { default as TableImg } from './src/components/TableImg.vue';
export * from './src/types/table'; export * from './src/types/table';
export * from './src/types/pagination'; export * from './src/types/pagination';
export * from './src/types/tableAction'; export * from './src/types/tableAction';

View File

@ -1,6 +1,7 @@
<template> <template>
<div ref="wrapRef" :class="getWrapperClass"> <div ref="wrapRef" :class="getWrapperClass">
<BasicForm <BasicForm
ref="formRef"
submitOnReset submitOnReset
v-bind="getFormProps" v-bind="getFormProps"
v-if="getBindValues.useSearchForm" v-if="getBindValues.useSearchForm"
@ -24,10 +25,16 @@
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item"> <template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot> <slot :name="item" v-bind="data || {}"></slot>
</template> </template>
<template #headerCell="{ column }">
<template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
<HeaderCell :column="column" /> <HeaderCell :column="column" />
</template> </template>
<!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data">
<slot name="bodyCell" v-bind="data || {}"></slot>
</template>
<!-- <template #[`header-${column.dataIndex}`] v-for="(column, index) in columns" :key="index">-->
<!-- <HeaderCell :column="column" />-->
<!-- </template>-->
</Table> </Table>
</div> </div>
</template> </template>
@ -43,7 +50,6 @@
import { Table } from 'ant-design-vue'; import { Table } from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index';
import { PageWrapperFixedHeightKey } from '/@/components/Page'; import { PageWrapperFixedHeightKey } from '/@/components/Page';
import expandIcon from './components/ExpandIcon';
import HeaderCell from './components/HeaderCell.vue'; import HeaderCell from './components/HeaderCell.vue';
import { InnerHandlers } from './types/table'; import { InnerHandlers } from './types/table';
@ -53,6 +59,7 @@
import { useLoading } from './hooks/useLoading'; import { useLoading } from './hooks/useLoading';
import { useRowSelection } from './hooks/useRowSelection'; import { useRowSelection } from './hooks/useRowSelection';
import { useTableScroll } from './hooks/useTableScroll'; import { useTableScroll } from './hooks/useTableScroll';
import { useTableScrollTo } from './hooks/useScrollTo';
import { useCustomRow } from './hooks/useCustomRow'; import { useCustomRow } from './hooks/useCustomRow';
import { useTableStyle } from './hooks/useTableStyle'; import { useTableStyle } from './hooks/useTableStyle';
import { useTableHeader } from './hooks/useTableHeader'; import { useTableHeader } from './hooks/useTableHeader';
@ -97,6 +104,7 @@
const tableData = ref<Recordable[]>([]); const tableData = ref<Recordable[]>([]);
const wrapRef = ref(null); const wrapRef = ref(null);
const formRef = ref(null);
const innerPropsRef = ref<Partial<BasicTableProps>>(); const innerPropsRef = ref<Partial<BasicTableProps>>();
const { prefixCls } = useDesign('basic-table'); const { prefixCls } = useDesign('basic-table');
@ -185,8 +193,12 @@
getColumnsRef, getColumnsRef,
getRowSelectionRef, getRowSelectionRef,
getDataSourceRef, getDataSourceRef,
wrapRef,
formRef,
); );
const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef);
const { customRow } = useCustomRow(getProps, { const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys, setSelectedRowKeys,
getSelectRowKeys, getSelectRowKeys,
@ -197,7 +209,11 @@
const { getRowClassName } = useTableStyle(getProps, prefixCls); const { getRowClassName } = useTableStyle(getProps, prefixCls);
const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit); const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(
getProps,
tableData,
emit,
);
const handlers: InnerHandlers = { const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => { onColumnsChange: (data: ColumnChangeParam[]) => {
@ -222,10 +238,8 @@
const getBindValues = computed(() => { const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef); const dataSource = unref(getDataSourceRef);
let propsData: Recordable = { let propsData: Recordable = {
// ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
...attrs, ...attrs,
customRow, customRow,
expandIcon: slots.expandIcon ? null : expandIcon(),
...unref(getProps), ...unref(getProps),
...unref(getHeaderProps), ...unref(getHeaderProps),
scroll: unref(getScrollRef), scroll: unref(getScrollRef),
@ -239,9 +253,9 @@
footer: unref(getFooterProps), footer: unref(getFooterProps),
...unref(getExpandOption), ...unref(getExpandOption),
}; };
if (slots.expandedRowRender) { // if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll'); // propsData = omit(propsData, 'scroll');
} // }
propsData = omit(propsData, ['class', 'onChange']); propsData = omit(propsData, ['class', 'onChange']);
return propsData; return propsData;
@ -300,7 +314,9 @@
getShowPagination, getShowPagination,
setCacheColumnsByField, setCacheColumnsByField,
expandAll, expandAll,
expandRows,
collapseAll, collapseAll,
scrollTo,
getSize: () => { getSize: () => {
return unref(getBindValues).size as SizeType; return unref(getBindValues).size as SizeType;
}, },
@ -312,6 +328,7 @@
emit('register', tableAction, formActions); emit('register', tableAction, formActions);
return { return {
formRef,
tableElRef, tableElRef,
getBindValues, getBindValues,
getLoading, getLoading,
@ -346,6 +363,7 @@
.@{prefix-cls} { .@{prefix-cls} {
max-width: 100%; max-width: 100%;
height: 100%;
&-row__striped { &-row__striped {
td { td {
@ -357,6 +375,7 @@
padding: 16px; padding: 16px;
.ant-form { .ant-form {
width: 100%;
padding: 12px 10px 6px; padding: 12px 10px 6px;
margin-bottom: 16px; margin-bottom: 16px;
background-color: @component-background; background-color: @component-background;

View File

@ -7,9 +7,11 @@ import {
Switch, Switch,
DatePicker, DatePicker,
TimePicker, TimePicker,
AutoComplete,
Radio,
} from 'ant-design-vue'; } from 'ant-design-vue';
import type { ComponentType } from './types/componentType'; import type { ComponentType } from './types/componentType';
import { ApiSelect, ApiTreeSelect } from '/@/components/Form'; import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form';
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>();
@ -17,11 +19,15 @@ componentMap.set('Input', Input);
componentMap.set('InputNumber', InputNumber); componentMap.set('InputNumber', InputNumber);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect);
componentMap.set('AutoComplete', AutoComplete);
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch);
componentMap.set('Checkbox', Checkbox); componentMap.set('Checkbox', Checkbox);
componentMap.set('DatePicker', DatePicker); componentMap.set('DatePicker', DatePicker);
componentMap.set('TimePicker', TimePicker); componentMap.set('TimePicker', TimePicker);
componentMap.set('RadioGroup', Radio.Group);
componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('ApiRadioGroup', ApiRadioGroup);
export function add(compName: ComponentType, component: Component) { export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component); componentMap.set(compName, component);

View File

@ -1,23 +0,0 @@
import { BasicArrow } from '/@/components/Basic';
export default () => {
return (props: Recordable) => {
if (!props.expandable) {
if (props.needIndentSpaced) {
return <span class="ant-table-row-expand-icon ant-table-row-spaced" />;
} else {
return <span />;
}
}
return (
<BasicArrow
style="margin-right: 8px"
iconStyle="margin-top: -2px;"
onClick={(e: Event) => {
props.onExpand(props.record, e);
}}
expand={props.expanded}
/>
);
};
};

View File

@ -29,7 +29,7 @@
const { prefixCls } = useDesign('basic-table-header-cell'); const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => !!props.column?.edit); const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => props.column?.customTitle); const getTitle = computed(() => props.column?.customTitle || props.column?.title);
const getHelpMessage = computed(() => props.column?.helpMessage); const getHelpMessage = computed(() => props.column?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage }; return { prefixCls, getIsEdit, getTitle, getHelpMessage };

View File

@ -104,21 +104,20 @@
}); });
const getDropdownList = computed((): any[] => { const getDropdownList = computed((): any[] => {
return (toRaw(props.dropDownActions) || []) const list = (toRaw(props.dropDownActions) || []).filter((action) => {
.filter((action) => { return hasPermission(action.auth) && isIfShow(action);
return hasPermission(action.auth) && isIfShow(action); });
}) return list.map((action, index) => {
.map((action, index) => { const { label, popConfirm } = action;
const { label, popConfirm } = action; return {
return { ...action,
...action, ...popConfirm,
...popConfirm, onConfirm: popConfirm?.confirm,
onConfirm: popConfirm?.confirm, onCancel: popConfirm?.cancel,
onCancel: popConfirm?.cancel, text: label,
text: label, divider: index < list.length - 1 ? props.divider : false,
divider: index < props.dropDownActions.length - 1 ? props.divider : false, };
}; });
});
}); });
const getAlign = computed(() => { const getAlign = computed(() => {

View File

@ -1,40 +1,4 @@
<template> <script lang="tsx">
<div :class="prefixCls">
<div
v-show="!isEdit"
:class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }"
@click="handleEdit"
>
<div class="cell-content" :title="column.ellipsis ? getValues ?? '' : ''">
{{ getValues ? getValues : '&nbsp;' }}
</div>
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
</div>
<a-spin v-if="isEdit" :spinning="spinning">
<div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
<CellComponent
v-bind="getComponentProps"
:component="getComponent"
:style="getWrapperStyle"
:popoverVisible="getRuleVisible"
:rule="getRule"
:ruleMessage="ruleMessage"
:class="getWrapperClass"
ref="elRef"
@change="handleChange"
@options-change="handleOptionsChange"
@pressEnter="handleEnter"
/>
<div :class="`${prefixCls}__action`" v-if="!getRowEditable">
<CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />
<CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
</div>
</div>
</a-spin>
</div>
</template>
<script lang="ts">
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue';
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'; import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue';
import type { BasicColumn } from '../../types/table'; import type { BasicColumn } from '../../types/table';
@ -50,13 +14,13 @@
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'; import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is';
import { createPlaceholderMessage } from './helper'; import { createPlaceholderMessage } from './helper';
import { omit, pick, set } from 'lodash-es'; import { pick, set } from 'lodash-es';
import { treeToList } from '/@/utils/helper/treeHelper'; import { treeToList } from '/@/utils/helper/treeHelper';
import { Spin } from 'ant-design-vue'; import { Spin } from 'ant-design-vue';
export default defineComponent({ export default defineComponent({
name: 'EditableCell', name: 'EditableCell',
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin }, components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, Spin },
directives: { directives: {
clickOutside, clickOutside,
}, },
@ -100,13 +64,6 @@
}); });
const getComponentProps = computed(() => { const getComponentProps = computed(() => {
const compProps = props.column?.editComponentProps ?? {};
const component = unref(getComponent);
const apiSelectProps: Recordable = {};
if (component === 'ApiSelect') {
apiSelectProps.cache = true;
}
const isCheckValue = unref(getIsCheckComp); const isCheckValue = unref(getIsCheckComp);
const valueField = isCheckValue ? 'checked' : 'value'; const valueField = isCheckValue ? 'checked' : 'value';
@ -114,19 +71,49 @@
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val; const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
let compProps = props.column?.editComponentProps ?? {};
const { record, column, index } = props;
if (isFunction(compProps)) {
compProps = compProps({ text: val, record, column, index }) ?? {};
}
const component = unref(getComponent);
const apiSelectProps: Recordable = {};
if (component === 'ApiSelect') {
apiSelectProps.cache = true;
}
upEditDynamicDisabled(record, column, value);
return { return {
size: 'small', size: 'small',
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body, getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
getCalendarContainer: () => unref(table?.wrapRef.value) ?? document.body,
placeholder: createPlaceholderMessage(unref(getComponent)), placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps, ...apiSelectProps,
...omit(compProps, 'onChange'), ...compProps,
[valueField]: value, [valueField]: value,
}; disabled: unref(getDisable),
} as any;
});
function upEditDynamicDisabled(record, column, value) {
if (!record) return false;
const { key, dataIndex } = column;
if (!key && !dataIndex) return;
const dataKey = (dataIndex || key) as string;
set(record, dataKey, value);
}
const getDisable = computed(() => {
const { editDynamicDisabled } = props.column;
let disabled = false;
if (isBoolean(editDynamicDisabled)) {
disabled = editDynamicDisabled;
}
if (isFunction(editDynamicDisabled)) {
const { record } = props;
disabled = editDynamicDisabled({ record });
}
return disabled;
}); });
const getValues = computed(() => { const getValues = computed(() => {
const { editComponentProps, editValueMap } = props.column; const { editValueMap } = props.column;
const value = unref(currentValueRef); const value = unref(currentValueRef);
@ -135,11 +122,12 @@
} }
const component = unref(getComponent); const component = unref(getComponent);
if (!component.includes('Select')) { if (!component.includes('Select') && !component.includes('Radio')) {
return value; return value;
} }
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []); const options: LabelValueOptions =
unref(getComponentProps)?.options ?? (unref(optionsRef) || []);
const option = options.find((item) => `${item.value}` === `${value}`); const option = options.find((item) => `${item.value}` === `${value}`);
return option?.label ?? value; return option?.label ?? value;
@ -190,14 +178,16 @@
const component = unref(getComponent); const component = unref(getComponent);
if (!e) { if (!e) {
currentValueRef.value = e; currentValueRef.value = e;
} else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
} else if (component === 'Checkbox') { } else if (component === 'Checkbox') {
currentValueRef.value = (e as ChangeEvent).target.checked; currentValueRef.value = (e as ChangeEvent).target.checked;
} else if (isString(e) || isBoolean(e) || isNumber(e)) { } else if (component === 'Switch') {
currentValueRef.value = e;
} else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
currentValueRef.value = e; currentValueRef.value = e;
} }
const onChange = props.column?.editComponentProps?.onChange; const onChange = unref(getComponentProps)?.onChange;
if (onChange && isFunction(onChange)) onChange(...arguments); if (onChange && isFunction(onChange)) onChange(...arguments);
table.emit?.('edit-change', { table.emit?.('edit-change', {
@ -265,7 +255,7 @@
result = await beforeEditSubmit({ result = await beforeEditSubmit({
record: pick(record, keys), record: pick(record, keys),
index, index,
key: key as string, key: dataKey as string,
value, value,
}); });
} catch (e) { } catch (e) {
@ -281,7 +271,7 @@
set(record, dataKey, value); set(record, dataKey, value);
//const record = await table.updateTableData(index, dataKey, value); //const record = await table.updateTableData(index, dataKey, value);
needEmit && table.emit?.('edit-end', { record, index, key, value }); needEmit && table.emit?.('edit-end', { record, index, key: dataKey, value });
isEdit.value = false; isEdit.value = false;
} }
@ -322,7 +312,7 @@
// only ApiSelect or TreeSelect // only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) { function handleOptionsChange(options: LabelValueOptions) {
const { replaceFields } = props.column?.editComponentProps ?? {}; const { replaceFields } = unref(getComponentProps);
const component = unref(getComponent); const component = unref(getComponent);
if (component === 'ApiTreeSelect') { if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {}; const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
@ -355,7 +345,7 @@
if (props.column.dataIndex) { if (props.column.dataIndex) {
if (!props.record.editValueRefs) props.record.editValueRefs = {}; if (!props.record.editValueRefs) props.record.editValueRefs = {};
props.record.editValueRefs[props.column.dataIndex] = currentValueRef; props.record.editValueRefs[props.column.dataIndex as any] = currentValueRef;
} }
/* eslint-disable */ /* eslint-disable */
props.record.onCancelEdit = () => { props.record.onCancelEdit = () => {
@ -398,6 +388,59 @@
spinning, spinning,
}; };
}, },
render() {
return (
<div class={this.prefixCls}>
<div
v-show={!this.isEdit}
class={{ [`${this.prefixCls}__normal`]: true, 'ellipsis-cell': this.column.ellipsis }}
onClick={this.handleEdit}
>
<div class="cell-content" title={this.column.ellipsis ? this.getValues ?? '' : ''}>
{this.column.editRender
? this.column.editRender({
text: this.value,
record: this.record as Recordable,
column: this.column,
index: this.index,
})
: this.getValues
? this.getValues
: '\u00A0'}
</div>
{!this.column.editRow && <FormOutlined class={`${this.prefixCls}__normal-icon`} />}
</div>
{this.isEdit && (
<Spin spinning={this.spinning}>
<div class={`${this.prefixCls}__wrapper`} v-click-outside={this.onClickOutside}>
<CellComponent
{...this.getComponentProps}
component={this.getComponent}
style={this.getWrapperStyle}
popoverVisible={this.getRuleVisible}
rule={this.getRule}
ruleMessage={this.ruleMessage}
class={this.getWrapperClass}
ref="elRef"
onChange={this.handleChange}
onOptionsChange={this.handleOptionsChange}
onPressEnter={this.handleEnter}
/>
{!this.getRowEditable && (
<div class={`${this.prefixCls}__action`}>
<CheckOutlined
class={[`${this.prefixCls}__icon`, 'mx-2']}
onClick={this.handleSubmitClick}
/>
<CloseOutlined class={`${this.prefixCls}__icon `} onClick={this.handleCancel} />
</div>
)}
</div>
</Spin>
)}
</div>
);
},
}); });
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -7,7 +7,7 @@ const { t } = useI18n();
* @description: placeholder * @description: placeholder
*/ */
export function createPlaceholderMessage(component: ComponentType) { export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input')) { if (component.includes('Input') || component.includes('AutoComplete')) {
return t('common.inputText'); return t('common.inputText');
} }
if (component.includes('Picker')) { if (component.includes('Picker')) {

View File

@ -6,7 +6,7 @@
<Popover <Popover
placement="bottomLeft" placement="bottomLeft"
trigger="click" trigger="click"
@visibleChange="handleVisibleChange" @visible-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`" :overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer" :getPopupContainer="getPopupContainer"
> >
@ -43,7 +43,7 @@
<CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef"> <CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef">
<template v-for="item in plainOptions" :key="item.value"> <template v-for="item in plainOptions" :key="item.value">
<div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)"> <div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)">
<DragOutlined class="table-coulmn-drag-icon" /> <DragOutlined class="table-column-drag-icon" />
<Checkbox :value="item.value"> <Checkbox :value="item.value">
{{ item.label }} {{ item.label }}
</Checkbox> </Checkbox>
@ -111,19 +111,23 @@
computed, computed,
} from 'vue'; } from 'vue';
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue'; import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'; import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext'; import { useTableContext } from '../../hooks/useTableContext';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useSortable } from '/@/hooks/web/useSortable'; // import { useSortable } from '/@/hooks/web/useSortable';
import { isFunction, isNullAndUnDef } from '/@/utils/is'; import { isFunction, isNullAndUnDef } from '/@/utils/is';
import { getPopupContainer as getParentContainer } from '/@/utils'; import { getPopupContainer as getParentContainer } from '/@/utils';
import { omit } from 'lodash-es'; import { cloneDeep, omit } from 'lodash-es';
import Sortablejs from 'sortablejs';
import type Sortable from 'sortablejs';
interface State { interface State {
checkAll: boolean; checkAll: boolean;
isInit?: boolean;
checkedList: string[]; checkedList: string[];
defaultCheckList: string[]; defaultCheckList: string[];
} }
@ -157,7 +161,7 @@
let inited = false; let inited = false;
const cachePlainOptions = ref<Options[]>([]); const cachePlainOptions = ref<Options[]>([]);
const plainOptions = ref<Options[]>([]); const plainOptions = ref<Options[] | any>([]);
const plainSortOptions = ref<Options[]>([]); const plainSortOptions = ref<Options[]>([]);
@ -179,10 +183,12 @@
}); });
watchEffect(() => { watchEffect(() => {
const columns = table.getColumns(); setTimeout(() => {
if (columns.length) { const columns = table.getColumns();
init(); if (columns.length && !state.isInit) {
} init();
}
}, 0);
}); });
watchEffect(() => { watchEffect(() => {
@ -207,7 +213,7 @@
const columns = getColumns(); const columns = getColumns();
const checkList = table const checkList = table
.getColumns({ ignoreAction: true }) .getColumns({ ignoreAction: true, ignoreIndex: true })
.map((item) => { .map((item) => {
if (item.defaultHidden) { if (item.defaultHidden) {
return ''; return '';
@ -233,11 +239,12 @@
} }
}); });
} }
state.isInit = true;
state.checkedList = checkList; state.checkedList = checkList;
} }
// checkAll change // checkAll change
function onCheckAllChange(e: ChangeEvent) { function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainOptions.value.map((item) => item.value); const checkList = plainOptions.value.map((item) => item.value);
if (e.target.checked) { if (e.target.checked) {
state.checkedList = checkList; state.checkedList = checkList;
@ -250,16 +257,15 @@
const indeterminate = computed(() => { const indeterminate = computed(() => {
const len = plainOptions.value.length; const len = plainOptions.value.length;
let checkdedLen = state.checkedList.length; let checkedLen = state.checkedList.length;
unref(checkIndex) && checkdedLen--; // unref(checkIndex) && checkedLen--;
return checkdedLen > 0 && checkdedLen < len; return checkedLen > 0 && checkedLen < len;
}); });
// Trigger when check/uncheck a column // Trigger when check/uncheck a column
function onChange(checkedList: string[]) { function onChange(checkedList: string[]) {
const len = plainOptions.value.length; const len = plainSortOptions.value.length;
state.checkAll = checkedList.length === len; state.checkAll = checkedList.length === len;
const sortList = unref(plainSortOptions).map((item) => item.value); const sortList = unref(plainSortOptions).map((item) => item.value);
checkedList.sort((prev, next) => { checkedList.sort((prev, next) => {
return sortList.indexOf(prev) - sortList.indexOf(next); return sortList.indexOf(prev) - sortList.indexOf(next);
@ -267,6 +273,8 @@
setColumns(checkedList); setColumns(checkedList);
} }
let sortable: Sortable;
let sortableOrder: string[] = [];
// reset columns // reset columns
function reset() { function reset() {
state.checkedList = [...state.defaultCheckList]; state.checkedList = [...state.defaultCheckList];
@ -274,6 +282,7 @@
plainOptions.value = unref(cachePlainOptions); plainOptions.value = unref(cachePlainOptions);
plainSortOptions.value = unref(cachePlainOptions); plainSortOptions.value = unref(cachePlainOptions);
setColumns(table.getCacheColumns()); setColumns(table.getCacheColumns());
sortable.sort(sortableOrder);
} }
// Open the pop-up window for drag and drop initialization // Open the pop-up window for drag and drop initialization
@ -285,15 +294,18 @@
const el = columnListEl.$el as any; const el = columnListEl.$el as any;
if (!el) return; if (!el) return;
// Drag and drop sort // Drag and drop sort
const { initSortable } = useSortable(el, { sortable = Sortablejs.create(unref(el), {
handle: '.table-coulmn-drag-icon ', animation: 500,
delay: 400,
delayOnTouchOnly: true,
handle: '.table-column-drag-icon ',
onEnd: (evt) => { onEnd: (evt) => {
const { oldIndex, newIndex } = evt; const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return; return;
} }
// Sort column // Sort column
const columns = getColumns(); const columns = cloneDeep(plainSortOptions.value);
if (oldIndex > newIndex) { if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]); columns.splice(newIndex, 0, columns[oldIndex]);
@ -304,24 +316,29 @@
} }
plainSortOptions.value = columns; plainSortOptions.value = columns;
plainOptions.value = columns;
setColumns(columns); setColumns(
columns
.map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)),
);
}, },
}); });
initSortable(); // order
sortableOrder = sortable.toArray();
inited = true; inited = true;
}); });
} }
// Control whether the serial number column is displayed // Control whether the serial number column is displayed
function handleIndexCheckChange(e: ChangeEvent) { function handleIndexCheckChange(e: CheckboxChangeEvent) {
table.setProps({ table.setProps({
showIndexColumn: e.target.checked, showIndexColumn: e.target.checked,
}); });
} }
// Control whether the check box is displayed // Control whether the check box is displayed
function handleSelectCheckChange(e: ChangeEvent) { function handleSelectCheckChange(e: CheckboxChangeEvent) {
table.setProps({ table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined, rowSelection: e.target.checked ? defaultRowSelection : undefined,
}); });
@ -341,13 +358,13 @@
if (isFixed && !item.width) { if (isFixed && !item.width) {
item.width = 100; item.width = 100;
} }
table.setCacheColumnsByField?.(item.dataIndex, { fixed: isFixed }); table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
setColumns(columns); setColumns(columns);
} }
function setColumns(columns: BasicColumn[] | string[]) { function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns); table.setColumns(columns);
const data: ColumnChangeParam[] = unref(plainOptions).map((col) => { const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const visible = const visible =
columns.findIndex( columns.findIndex(
(c: BasicColumn | string) => (c: BasicColumn | string) =>
@ -390,7 +407,7 @@
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-column-setting'; @prefix-cls: ~'@{namespace}-basic-column-setting';
.table-coulmn-drag-icon { .table-column-drag-icon {
margin: 0 5px; margin: 0 5px;
cursor: move; cursor: move;
} }

View File

@ -4,7 +4,7 @@
<span>{{ t('component.table.settingDens') }}</span> <span>{{ t('component.table.settingDens') }}</span>
</template> </template>
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer"> <Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
<ColumnHeightOutlined /> <ColumnHeightOutlined />
<template #overlay> <template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef"> <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">

View File

@ -1,7 +1,7 @@
import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table'; import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
import type { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination';
import type { ComputedRef } from 'vue'; import type { ComputedRef } from 'vue';
import { computed, Ref, ref, toRaw, unref, watch } from 'vue'; import { computed, Ref, ref, reactive, toRaw, unref, watch } from 'vue';
import { renderEditCell } from '../components/editable'; import { renderEditCell } from '../components/editable';
import { usePermission } from '/@/hooks/web/usePermission'; import { usePermission } from '/@/hooks/web/usePermission';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
@ -152,10 +152,10 @@ export function useColumns(
return hasPermission(column.auth) && isIfShow(column); return hasPermission(column.auth) && isIfShow(column);
}) })
.map((column) => { .map((column) => {
const { slots, dataIndex, customRender, format, edit, editRow, flag } = column; const { slots, customRender, format, edit, editRow, flag } = column;
if (!slots || !slots?.title) { if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) }; // column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title; column.customTitle = column.title;
Reflect.deleteProperty(column, 'title'); Reflect.deleteProperty(column, 'title');
} }
@ -170,7 +170,7 @@ export function useColumns(
if ((edit || editRow) && !isDefaultAction) { if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column); column.customRender = renderEditCell(column);
} }
return column; return reactive(column);
}); });
}); });
@ -197,7 +197,7 @@ export function useColumns(
* set columns * set columns
* @param columnList keycolumn * @param columnList keycolumn
*/ */
function setColumns(columnList: Partial<BasicColumn>[] | string[]) { function setColumns(columnList: Partial<BasicColumn>[] | (string | string[])[]) {
const columns = cloneDeep(columnList); const columns = cloneDeep(columnList);
if (!isArray(columns)) return; if (!isArray(columns)) return;
@ -210,31 +210,23 @@ export function useColumns(
const cacheKeys = cacheColumns.map((item) => item.dataIndex); const cacheKeys = cacheColumns.map((item) => item.dataIndex);
if (!isString(firstColumn)) { if (!isString(firstColumn) && !isArray(firstColumn)) {
columnsRef.value = columns as BasicColumn[]; columnsRef.value = columns as BasicColumn[];
} else { } else {
const columnKeys = columns as string[]; const columnKeys = (columns as (string | string[])[]).map((m) => m.toString());
const newColumns: BasicColumn[] = []; const newColumns: BasicColumn[] = [];
cacheColumns.forEach((item) => { cacheColumns.forEach((item) => {
if (columnKeys.includes(item.dataIndex! || (item.key as string))) { newColumns.push({
newColumns.push({ ...item,
...item, defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)),
defaultHidden: false, });
});
} else {
newColumns.push({
...item,
defaultHidden: true,
});
}
}); });
// Sort according to another array // Sort according to another array
if (!isEqual(cacheKeys, columns)) { if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => { newColumns.sort((prev, next) => {
return ( return (
cacheKeys.indexOf(prev.dataIndex as string) - columnKeys.indexOf(prev.dataIndex?.toString() as string) -
cacheKeys.indexOf(next.dataIndex as string) columnKeys.indexOf(next.dataIndex?.toString() as string)
); );
}); });
} }
@ -306,7 +298,7 @@ export function formatCell(text: string, format: CellFormat, record: Recordable,
try { try {
// date type // date type
const DATE_FORMAT_PREFIX = 'date|'; const DATE_FORMAT_PREFIX = 'date|';
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) { if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX) && text) {
const dateFormat = format.replace(DATE_FORMAT_PREFIX, ''); const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
if (!dateFormat) { if (!dateFormat) {

View File

@ -14,7 +14,7 @@ import {
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { buildUUID } from '/@/utils/uuid'; import { buildUUID } from '/@/utils/uuid';
import { isFunction, isBoolean } from '/@/utils/is'; import { isFunction, isBoolean } from '/@/utils/is';
import { get, cloneDeep } from 'lodash-es'; import { get, cloneDeep, merge } from 'lodash-es';
import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const'; import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const';
interface ActionType { interface ActionType {
@ -196,11 +196,10 @@ export function useDataSource(
} }
function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined { function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length; index = index ?? dataSourceRef.value?.length;
unref(dataSourceRef).splice(index, 0, record); unref(dataSourceRef).splice(index, 0, record);
unref(propsRef).dataSource?.splice(index, 0, record); return unref(dataSourceRef);
return unref(propsRef).dataSource;
} }
function findTableDataRecord(rowKey: string | number) { function findTableDataRecord(rowKey: string | number) {
@ -272,17 +271,17 @@ export function useDataSource(
const { sortInfo = {}, filterInfo } = searchState; const { sortInfo = {}, filterInfo } = searchState;
let params: Recordable = { let params: Recordable = merge(
...pageParams, pageParams,
...(useSearchForm ? getFieldsValue() : {}), useSearchForm ? getFieldsValue() : {},
...searchInfo, searchInfo,
...(opt?.searchInfo ?? {}), opt?.searchInfo ?? {},
...defSort, defSort,
...sortInfo, sortInfo,
...filterInfo, filterInfo,
...(opt?.sortInfo ?? {}), opt?.sortInfo ?? {},
...(opt?.filterInfo ?? {}), opt?.filterInfo ?? {},
}; );
if (beforeFetch && isFunction(beforeFetch)) { if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params; params = (await beforeFetch(params)) || params;
} }
@ -293,7 +292,7 @@ export function useDataSource(
const isArrayResult = Array.isArray(res); const isArrayResult = Array.isArray(res);
let resultItems: Recordable[] = isArrayResult ? res : get(res, listField); let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
const resultTotal: number = isArrayResult ? 0 : get(res, totalField); const resultTotal: number = isArrayResult ? res.length : get(res, totalField);
// 假如数据变少导致总页数变少并小于当前选中页码通过getPaginationRef获取到的页码是不正确的需获取正确的页码再次执行 // 假如数据变少导致总页数变少并小于当前选中页码通过getPaginationRef获取到的页码是不正确的需获取正确的页码再次执行
if (resultTotal) { if (resultTotal) {

View File

@ -21,11 +21,8 @@ export function useRowSelection(
return { return {
selectedRowKeys: unref(selectedRowKeysRef), selectedRowKeys: unref(selectedRowKeysRef),
hideDefaultSelections: false,
onChange: (selectedRowKeys: string[]) => { onChange: (selectedRowKeys: string[]) => {
setSelectedRowKeys(selectedRowKeys); setSelectedRowKeys(selectedRowKeys);
// selectedRowKeysRef.value = selectedRowKeys;
// selectedRowRef.value = selectedRows;
}, },
...omit(rowSelection, ['onChange']), ...omit(rowSelection, ['onChange']),
}; };

View File

@ -0,0 +1,55 @@
import type { ComputedRef, Ref } from 'vue';
import { nextTick, unref } from 'vue';
import { warn } from '/@/utils/log';
export function useTableScrollTo(
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable[]>,
) {
let bodyEl: HTMLElement | null;
async function findTargetRowToScroll(targetRowData: Recordable) {
const { id } = targetRowData;
const targetRowEl: HTMLElement | null | undefined = bodyEl?.querySelector(
`[data-row-key="${id}"]`,
);
//Add a delay to get new dataSource
await nextTick();
bodyEl?.scrollTo({
top: targetRowEl?.offsetTop ?? 0,
behavior: 'smooth',
});
}
function scrollTo(pos: string): void {
const table = unref(tableElRef);
if (!table) return;
const tableEl: Element = table.$el;
if (!tableEl) return;
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return;
}
const dataSource = unref(getDataSourceRef);
if (!dataSource) return;
// judge pos type
if (pos === 'top') {
findTargetRowToScroll(dataSource[0]);
} else if (pos === 'bottom') {
findTargetRowToScroll(dataSource[dataSource.length - 1]);
} else {
const targetRowData = dataSource.find((data) => data.id === pos);
if (targetRowData) {
findTargetRowToScroll(targetRowData);
} else {
warn(`id: ${pos} doesn't exist`);
}
}
}
return { scrollTo };
}

View File

@ -152,9 +152,15 @@ export function useTable(tableProps?: Props): [
expandAll: () => { expandAll: () => {
getTableInstance().expandAll(); getTableInstance().expandAll();
}, },
expandRows: (keys: string[]) => {
getTableInstance().expandRows(keys);
},
collapseAll: () => { collapseAll: () => {
getTableInstance().collapseAll(); getTableInstance().collapseAll();
}, },
scrollTo: (pos: string) => {
getTableInstance().scrollTo(pos);
},
}; };
return [register, methods]; return [register, methods];

View File

@ -37,6 +37,13 @@ export function useTableExpand(
expandedRowKeys.value = keys; expandedRowKeys.value = keys;
} }
function expandRows(keys: string[]) {
// use row ID expands the specified table row
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return;
expandedRowKeys.value = [...expandedRowKeys.value, ...keys];
}
function getAllKeys(data?: Recordable[]) { function getAllKeys(data?: Recordable[]) {
const keys: string[] = []; const keys: string[] = [];
const { childrenColumnName } = unref(propsRef); const { childrenColumnName } = unref(propsRef);
@ -54,5 +61,5 @@ export function useTableExpand(
expandedRowKeys.value = []; expandedRowKeys.value = [];
} }
return { getExpandOption, expandAll, collapseAll }; return { getExpandOption, expandAll, expandRows, collapseAll };
} }

View File

@ -8,7 +8,7 @@ export function useTableFooter(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
scrollRef: ComputedRef<{ scrollRef: ComputedRef<{
x: string | number | true; x: string | number | true;
y: Nullable<number>; y: string | number | null;
scrollToFirstRowOnChange: boolean; scrollToFirstRowOnChange: boolean;
}>, }>,
tableElRef: Ref<ComponentRef>, tableElRef: Ref<ComponentRef>,
@ -36,14 +36,13 @@ export function useTableFooter(
nextTick(() => { nextTick(() => {
const tableEl = unref(tableElRef); const tableEl = unref(tableElRef);
if (!tableEl) return; if (!tableEl) return;
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body'); const bodyDom = tableEl.$el.querySelector('.ant-table-content');
const bodyDom = bodyDomList[0];
useEventListener({ useEventListener({
el: bodyDom, el: bodyDom,
name: 'scroll', name: 'scroll',
listener: () => { listener: () => {
const footerBodyDom = tableEl.$el.querySelector( const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-body', '.ant-table-footer .ant-table-content',
) as HTMLDivElement; ) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return; if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft; footerBodyDom.scrollLeft = bodyDom.scrollLeft;

View File

@ -1,6 +1,6 @@
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table'; import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
import type { Ref, ComputedRef } from 'vue'; import { Ref, ComputedRef, ref } from 'vue';
import { computed, unref, ref, nextTick, watch } from 'vue'; import { computed, unref, nextTick, watch } from 'vue';
import { getViewportOffset } from '/@/utils/domUtils'; import { getViewportOffset } from '/@/utils/domUtils';
import { isBoolean } from '/@/utils/is'; import { isBoolean } from '/@/utils/is';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
@ -12,11 +12,12 @@ export function useTableScroll(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
tableElRef: Ref<ComponentRef>, tableElRef: Ref<ComponentRef>,
columnsRef: ComputedRef<BasicColumn[]>, columnsRef: ComputedRef<BasicColumn[]>,
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>, rowSelectionRef: ComputedRef<TableRowSelection | null>,
getDataSourceRef: ComputedRef<Recordable[]>, getDataSourceRef: ComputedRef<Recordable[]>,
wrapRef: Ref<HTMLElement | null>,
formRef: Ref<ComponentRef>,
) { ) {
const tableHeightRef: Ref<Nullable<number>> = ref(null); const tableHeightRef: Ref<Nullable<number | string>> = ref(167);
const modalFn = useModalContext(); const modalFn = useModalContext();
// Greater than animation time 280 // Greater than animation time 280
@ -43,8 +44,8 @@ export function useTableScroll(
}); });
} }
function setHeight(heigh: number) { function setHeight(height: number) {
tableHeightRef.value = heigh; tableHeightRef.value = height;
// Solve the problem of modal adaptive height calculation when the form is placed in the modal // Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.(); modalFn?.redoModalHeight?.();
} }
@ -55,7 +56,8 @@ export function useTableScroll(
let bodyEl: HTMLElement | null; let bodyEl: HTMLElement | null;
async function calcTableHeight() { async function calcTableHeight() {
const { resizeHeightOffset, pagination, maxHeight } = unref(propsRef); const { resizeHeightOffset, pagination, maxHeight, isCanResizeParent, useSearchForm } =
unref(propsRef);
const tableData = unref(getDataSourceRef); const tableData = unref(getDataSourceRef);
const table = unref(tableElRef); const table = unref(tableElRef);
@ -88,20 +90,17 @@ export function useTableScroll(
bodyEl!.style.height = 'unset'; bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || tableData.length === 0) return; if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return;
await nextTick(); await nextTick();
//Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight // Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
const headEl = tableEl.querySelector('.ant-table-thead '); const headEl = tableEl.querySelector('.ant-table-thead ');
if (!headEl) return; if (!headEl) return;
// Table height from bottom
const { bottomIncludeBody } = getViewportOffset(headEl);
// Table height from bottom height-custom offset // Table height from bottom height-custom offset
let paddingHeight = 32;
const paddingHeight = 32;
// Pager height // Pager height
let paginationHeight = 2; let paginationHeight = 2;
if (!isBoolean(pagination)) { if (!isBoolean(pagination)) {
@ -132,6 +131,35 @@ export function useTableScroll(
headerHeight = (headEl as HTMLElement).offsetHeight; headerHeight = (headEl as HTMLElement).offsetHeight;
} }
let bottomIncludeBody = 0;
if (unref(wrapRef) && isCanResizeParent) {
const tablePadding = 12;
const formMargin = 16;
let paginationMargin = 10;
const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0;
let formHeight = unref(formRef)?.$el.offsetHeight ?? 0;
if (formHeight) {
formHeight += formMargin;
}
if (isBoolean(pagination) && !pagination) {
paginationMargin = 0;
}
if (isBoolean(useSearchForm) && !useSearchForm) {
paddingHeight = 0;
}
const headerCellHeight =
(tableEl.querySelector('.ant-table-title') as HTMLElement)?.offsetHeight ?? 0;
console.log(wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin);
bottomIncludeBody =
wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin;
} else {
// Table height from bottom
bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody;
}
let height = let height =
bottomIncludeBody - bottomIncludeBody -
(resizeHeightOffset || 0) - (resizeHeightOffset || 0) -
@ -139,7 +167,6 @@ export function useTableScroll(
paginationHeight - paginationHeight -
footerHeight - footerHeight -
headerHeight; headerHeight;
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height; height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
setHeight(height); setHeight(height);
@ -164,7 +191,7 @@ export function useTableScroll(
const columns = unref(columnsRef).filter((item) => !item.defaultHidden); const columns = unref(columnsRef).filter((item) => !item.defaultHidden);
columns.forEach((item) => { columns.forEach((item) => {
width += Number.parseInt(item.width as string) || 0; width += Number.parseFloat(item.width as string) || 0;
}); });
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width')); const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));

View File

@ -10,14 +10,15 @@ import type {
SizeType, SizeType,
} from './types/table'; } from './types/table';
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '/@/components/Form';
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'; import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
export const basicProps = { export const basicProps = {
clickToRowSelect: propTypes.bool.def(true), clickToRowSelect: { type: Boolean, default: true },
isTreeTable: propTypes.bool.def(false), isTreeTable: Boolean,
tableSetting: propTypes.shape<TableSetting>({}), tableSetting: propTypes.shape<TableSetting>({}),
inset: propTypes.bool, inset: Boolean,
sortFn: { sortFn: {
type: Function as PropType<(sortInfo: SorterResult) => any>, type: Function as PropType<(sortInfo: SorterResult) => any>,
default: DEFAULT_SORT_FN, default: DEFAULT_SORT_FN,
@ -26,10 +27,10 @@ export const basicProps = {
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>, type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
default: DEFAULT_FILTER_FN, default: DEFAULT_FILTER_FN,
}, },
showTableSetting: propTypes.bool, showTableSetting: Boolean,
autoCreateKey: propTypes.bool.def(true), autoCreateKey: { type: Boolean, default: true },
striped: propTypes.bool.def(true), striped: { type: Boolean, default: true },
showSummary: propTypes.bool, showSummary: Boolean,
summaryFunc: { summaryFunc: {
type: [Function, Array] as PropType<(...arg: any[]) => any[]>, type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
default: null, default: null,
@ -39,7 +40,7 @@ export const basicProps = {
default: null, default: null,
}, },
indentSize: propTypes.number.def(24), indentSize: propTypes.number.def(24),
canColDrag: propTypes.bool.def(true), canColDrag: { type: Boolean, default: true },
api: { api: {
type: Function as PropType<(...arg: any[]) => Promise<any>>, type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null, default: null,
@ -63,8 +64,8 @@ export const basicProps = {
}, },
}, },
// 立即请求接口 // 立即请求接口
immediate: propTypes.bool.def(true), immediate: { type: Boolean, default: true },
emptyDataIsShowTable: propTypes.bool.def(true), emptyDataIsShowTable: { type: Boolean, default: true },
// 额外的请求参数 // 额外的请求参数
searchInfo: { searchInfo: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable>,
@ -86,7 +87,7 @@ export const basicProps = {
type: [Array] as PropType<BasicColumn[]>, type: [Array] as PropType<BasicColumn[]>,
default: () => [], default: () => [],
}, },
showIndexColumn: propTypes.bool.def(true), showIndexColumn: { type: Boolean, default: true },
indexColumnProps: { indexColumnProps: {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
@ -95,8 +96,9 @@ export const basicProps = {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
}, },
ellipsis: propTypes.bool.def(true), ellipsis: { type: Boolean, default: true },
canResize: propTypes.bool.def(true), isCanResizeParent: { type: Boolean, default: false },
canResize: { type: Boolean, default: true },
clearSelectOnPageChange: propTypes.bool, clearSelectOnPageChange: propTypes.bool,
resizeHeightOffset: propTypes.number.def(0), resizeHeightOffset: propTypes.number.def(0),
rowSelection: { rowSelection: {

View File

@ -3,8 +3,12 @@ export type ComponentType =
| 'InputNumber' | 'InputNumber'
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'AutoComplete'
| 'ApiTreeSelect' | 'ApiTreeSelect'
| 'Checkbox' | 'Checkbox'
| 'Switch' | 'Switch'
| 'DatePicker' | 'DatePicker'
| 'TimePicker'; | 'TimePicker'
| 'RadioGroup'
| 'RadioButtonGroup'
| 'ApiRadioGroup';

View File

@ -7,9 +7,18 @@ interface PaginationRenderProps {
originalElement: any; originalElement: any;
} }
type PaginationPositon =
| 'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomLeft'
| 'bottomCenter'
| 'bottomRight';
export declare class PaginationConfig extends Pagination { export declare class PaginationConfig extends Pagination {
position?: 'top' | 'bottom' | 'both'; position?: PaginationPositon[];
} }
export interface PaginationProps { export interface PaginationProps {
/** /**
* total number of data items * total number of data items
@ -96,4 +105,11 @@ export interface PaginationProps {
* @type Function * @type Function
*/ */
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element; itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
/**
* specify the position of Pagination
* @default ['bottomRight']
* @type string[]
*/
position?: PaginationPositon[];
} }

View File

@ -1,10 +1,8 @@
import type { VNodeChild } from 'vue'; import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination'; import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '/@/components/Form';
import type { import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
ColumnProps, import type { ColumnProps } from 'ant-design-vue/lib/table';
TableRowSelection as ITableRowSelection,
} from 'ant-design-vue/lib/table/interface';
import { ComponentType } from './componentType'; import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes'; import { VueNode } from '/@/utils/propTypes';
@ -89,7 +87,9 @@ export interface TableActionType {
getSelectRows: <T = Recordable>() => T[]; getSelectRows: <T = Recordable>() => T[];
clearSelectedRowKeys: () => void; clearSelectedRowKeys: () => void;
expandAll: () => void; expandAll: () => void;
expandRows: (keys: string[] | number[]) => void;
collapseAll: () => void; collapseAll: () => void;
scrollTo: (pos: string) => void; // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[]; getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => void; deleteSelectRowByKey: (key: string) => void;
setPagination: (info: Partial<PaginationProps>) => void; setPagination: (info: Partial<PaginationProps>) => void;
@ -191,6 +191,8 @@ export interface BasicTableProps<T = any> {
actionColumn?: BasicColumn; actionColumn?: BasicColumn;
// 文本超过宽度是否显示。。。 // 文本超过宽度是否显示。。。
ellipsis?: boolean; ellipsis?: boolean;
// 是否继承父级高度(父级高度-表单高度-padding高度
isCanResizeParent?: boolean;
// 是否可以自适应高度 // 是否可以自适应高度
canResize?: boolean; canResize?: boolean;
// 自适应高度偏移, 计算结果-偏移量 // 自适应高度偏移, 计算结果-偏移量
@ -410,7 +412,7 @@ export type CellFormat =
| Map<string | number, any>; | Map<string | number, any>;
// @ts-ignore // @ts-ignore
export interface BasicColumn extends ColumnProps { export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[]; children?: BasicColumn[];
filters?: { filters?: {
text: string; text: string;
@ -439,7 +441,14 @@ export interface BasicColumn extends ColumnProps {
editRow?: boolean; editRow?: boolean;
editable?: boolean; editable?: boolean;
editComponent?: ComponentType; editComponent?: ComponentType;
editComponentProps?: Recordable; editComponentProps?:
| ((opt: {
text: string | number | boolean | Recordable;
record: Recordable;
column: BasicColumn;
index: number;
}) => Recordable)
| Recordable;
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>); editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
editValueMap?: (value: any) => string; editValueMap?: (value: any) => string;
onEditRow?: () => void; onEditRow?: () => void;
@ -447,6 +456,15 @@ export interface BasicColumn extends ColumnProps {
auth?: RoleEnum | RoleEnum[] | string | string[]; auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示 // 业务控制是否显示
ifShow?: boolean | ((column: BasicColumn) => boolean); ifShow?: boolean | ((column: BasicColumn) => boolean);
// 自定义修改后显示的内容
editRender?: (opt: {
text: string | number | boolean | Recordable;
record: Recordable;
column: BasicColumn;
index: number;
}) => VNodeChild | JSX.Element;
// 动态 Disabled
editDynamicDisabled?: boolean | ((record: Recordable) => boolean);
} }
export type ColumnChangeParam = { export type ColumnChangeParam = {

View File

@ -23,4 +23,17 @@ export interface PopConfirm {
confirm: Fn; confirm: Fn;
cancel?: Fn; cancel?: Fn;
icon?: string; icon?: string;
placement?:
| 'top'
| 'left'
| 'right'
| 'bottom'
| 'topLeft'
| 'topRight'
| 'leftTop'
| 'leftBottom'
| 'rightTop'
| 'rightBottom'
| 'bottomLeft'
| 'bottomRight';
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<a-button-group> <Space>
<a-button type="primary" @click="openUploadModal" preIcon="carbon:cloud-upload"> <a-button type="primary" @click="openUploadModal" preIcon="carbon:cloud-upload">
{{ t('component.upload.upload') }} {{ t('component.upload.upload') }}
</a-button> </a-button>
@ -18,8 +18,7 @@
</template> </template>
</a-button> </a-button>
</Tooltip> </Tooltip>
</a-button-group> </Space>
<UploadModal <UploadModal
v-bind="bindValue" v-bind="bindValue"
:previewFileList="fileList" :previewFileList="fileList"
@ -38,19 +37,19 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watch, unref, computed } from 'vue'; import { defineComponent, ref, watch, unref, computed } from 'vue';
import UploadModal from './UploadModal.vue';
import UploadPreviewModal from './UploadPreviewModal.vue';
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
import { Tooltip } from 'ant-design-vue'; import { Tooltip, Space } from 'ant-design-vue';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import { uploadContainerProps } from './props'; import { uploadContainerProps } from './props';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { isArray } from '/@/utils/is'; import { isArray } from '/@/utils/is';
import UploadModal from './UploadModal.vue';
import UploadPreviewModal from './UploadPreviewModal.vue';
export default defineComponent({ export default defineComponent({
name: 'BasicUpload', name: 'BasicUpload',
components: { UploadModal, UploadPreviewModal, Icon, Tooltip }, components: { UploadModal, Space, UploadPreviewModal, Icon, Tooltip },
props: uploadContainerProps, props: uploadContainerProps,
emits: ['change', 'delete', 'preview-delete', 'update:value'], emits: ['change', 'delete', 'preview-delete', 'update:value'],

View File

@ -9,7 +9,7 @@
:closeFunc="handleCloseFunc" :closeFunc="handleCloseFunc"
:maskClosable="false" :maskClosable="false"
:keyboard="false" :keyboard="false"
wrapClassName="upload-modal" class="upload-modal"
:okButtonProps="getOkButtonProps" :okButtonProps="getOkButtonProps"
:cancelButtonProps="{ disabled: isUploadingRef }" :cancelButtonProps="{ disabled: isUploadingRef }"
> >
@ -31,6 +31,7 @@
:accept="getStringAccept" :accept="getStringAccept"
:multiple="multiple" :multiple="multiple"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:show-upload-list="false"
class="upload-modal-toolbar__btn" class="upload-modal-toolbar__btn"
> >
<a-button type="primary"> <a-button type="primary">
@ -54,7 +55,7 @@
import { basicProps } from './props'; import { basicProps } from './props';
import { createTableColumns, createActionColumn } from './data'; import { createTableColumns, createActionColumn } from './data';
// utils // utils
import { checkFileType, checkImgType, getBase64WithFile } from './helper'; import { checkImgType, getBase64WithFile } from './helper';
import { buildUUID } from '/@/utils/uuid'; import { buildUUID } from '/@/utils/uuid';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { warn } from '/@/utils/log'; import { warn } from '/@/utils/log';
@ -84,7 +85,7 @@
const { t } = useI18n(); const { t } = useI18n();
const [register, { closeModal }] = useModalInner(); const [register, { closeModal }] = useModalInner();
const { getAccept, getStringAccept, getHelpText } = useUploadType({ const { getStringAccept, getHelpText } = useUploadType({
acceptRef: accept, acceptRef: accept,
helpTextRef: helpText, helpTextRef: helpText,
maxNumberRef: maxNumber, maxNumberRef: maxNumber,
@ -124,18 +125,12 @@
function beforeUpload(file: File) { function beforeUpload(file: File) {
const { size, name } = file; const { size, name } = file;
const { maxSize } = props; const { maxSize } = props;
const accept = unref(getAccept);
// //
if (maxSize && file.size / 1024 / 1024 >= maxSize) { if (maxSize && file.size / 1024 / 1024 >= maxSize) {
createMessage.error(t('component.upload.maxSizeMultiple', [maxSize])); createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
return false; return false;
} }
// ,
if (accept.length > 0 && !checkFileType(file, accept)) {
createMessage.error!(t('component.upload.acceptUpload', [accept.join(',')]));
return false;
}
const commonItem = { const commonItem = {
uuid: buildUUID(), uuid: buildUUID(),
file, file,

View File

@ -2,7 +2,7 @@
<BasicModal <BasicModal
width="800px" width="800px"
:title="t('component.upload.preview')" :title="t('component.upload.preview')"
wrapClassName="upload-preview-modal" class="upload-preview-modal"
v-bind="$attrs" v-bind="$attrs"
@register="register" @register="register"
:showOkBtn="false" :showOkBtn="false"

View File

@ -1,18 +1,7 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { Button } from './Button'; import { Button } from './Button';
import { import { Input, Layout } from 'ant-design-vue';
// Need
Button as AntButton,
Input,
Layout,
} from 'ant-design-vue';
const compList = [AntButton.Group];
export function registerGlobComp(app: App) { export function registerGlobComp(app: App) {
compList.forEach((comp) => {
app.component(comp.name || comp.displayName, comp);
});
app.use(Input).use(Button).use(Layout); app.use(Input).use(Button).use(Layout);
} }

View File

@ -1,19 +1,5 @@
// button reset // button reset
.ant-btn { .ant-btn {
// display: inline-flex;
// justify-content: center;
// align-items: center;
// &.ant-btn-success:not(.ant-btn-link),
// &.ant-btn-error:not(.ant-btn-link),
// &.ant-btn-warning:not(.ant-btn-link),
// &.ant-btn-primary:not(.ant-btn-link) {
// box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08) !important;
// }
// &-group {
// .ant-btn:not(:first-child) {
// bottom: 1px;
// }
// }
&-link:hover, &-link:hover,
&-link:focus, &-link:focus,
&-link:active { &-link:active {
@ -29,23 +15,12 @@
color: @white; color: @white;
background-color: @button-primary-hover-color; background-color: @button-primary-hover-color;
} }
//
//&[disabled],
//&[disabled]:hover {
// color: fade(@button-cancel-color, 40%) !important;
// background-color: fade(@button-cancel-bg-color, 40%) !important;
// border-color: fade(@button-cancel-border-color, 40%) !important;
//}
} }
&-primary:not(&-background-ghost):not([disabled]) { &-primary:not(&-background-ghost):not([disabled]) {
color: @white; color: @white;
} }
//&-primary:not(&-background-ghost) {
// border-width: 0;
//}
&-default { &-default {
color: @button-cancel-color; color: @button-cancel-color;
background-color: @button-cancel-bg-color; background-color: @button-cancel-bg-color;
@ -127,13 +102,6 @@
background-color: @button-success-active-color; background-color: @button-success-active-color;
border-color: @button-success-active-color; border-color: @button-success-active-color;
} }
//&[disabled],
//&[disabled]:hover {
// color: @white;
// background-color: fade(@button-success-color, 40%);
// border-color: fade(@button-success-color, 40%);
//}
} }
&-warning.ant-btn-link:not([disabled='disabled']) { &-warning.ant-btn-link:not([disabled='disabled']) {

View File

@ -1,12 +1,6 @@
@import './pagination.less'; @import './pagination.less';
@import './input.less'; @import './input.less';
@import './btn.less'; @import './btn.less';
// @import './table.less';
// TODO beta.11 fix
.ant-col {
width: 100%;
}
.ant-image-preview-root { .ant-image-preview-root {
img { img {
@ -63,3 +57,11 @@ span.anticon:not(.app-iconify) {
border-top: 0 !important; border-top: 0 !important;
border-left: 0 !important; border-left: 0 !important;
} }
.ant-form-item-control-input-content {
> div {
> div {
max-width: 100%;
}
}
}

View File

@ -22,3 +22,10 @@
padding: 4px; padding: 4px;
} }
} }
.ant-input-number {
width: 100% !important;
max-width: 100%;
}

View File

@ -21,8 +21,8 @@ html,
body { body {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: visible !important; overflow: visible;
overflow-x: hidden !important; overflow-x: hidden;
&.color-weak { &.color-weak {
filter: invert(80%); filter: invert(80%);
@ -40,5 +40,5 @@ button,
div, div,
svg, svg,
span { span {
outline: none !important; outline: none;
} }

View File

@ -46,6 +46,6 @@
width: 100%; width: 100%;
height: 2px; height: 2px;
background-color: @primary-color; background-color: @primary-color;
opacity: 75%; opacity: 0.75;
} }
} }

View File

@ -1,3 +1,15 @@
.fade-transition {
&-enter-active,
&-leave-active {
transition: opacity 0.2s ease-in-out;
}
&-enter-from,
&-leave-to {
opacity: 0;
}
}
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.2s ease-in-out; transition: opacity 0.2s ease-in-out;
@ -5,7 +17,7 @@
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {
opacity: 0%; opacity: 0;
} }
/* fade-slide */ /* fade-slide */
@ -15,12 +27,12 @@
} }
.fade-slide-enter-from { .fade-slide-enter-from {
opacity: 0%; opacity: 0;
transform: translateX(-30px); transform: translateX(-30px);
} }
.fade-slide-leave-to { .fade-slide-leave-to {
opacity: 0%; opacity: 0;
transform: translateX(30px); transform: translateX(30px);
} }
@ -35,12 +47,12 @@
} }
.fade-bottom-enter-from { .fade-bottom-enter-from {
opacity: 0%; opacity: 0;
transform: translateY(-10%); transform: translateY(-10%);
} }
.fade-bottom-leave-to { .fade-bottom-leave-to {
opacity: 0%; opacity: 0;
transform: translateY(10%); transform: translateY(10%);
} }
@ -51,12 +63,12 @@
} }
.fade-scale-enter-from { .fade-scale-enter-from {
opacity: 0%; opacity: 0;
transform: scale(1.2); transform: scale(1.2);
} }
.fade-scale-leave-to { .fade-scale-leave-to {
opacity: 0%; opacity: 0;
transform: scale(0.8); transform: scale(0.8);
} }
@ -71,11 +83,11 @@
} }
.fade-top-enter-from { .fade-top-enter-from {
opacity: 0%; opacity: 0;
transform: translateY(8%); transform: translateY(8%);
} }
.fade-top-leave-to { .fade-top-leave-to {
opacity: 0%; opacity: 0;
transform: translateY(-8%); transform: translateY(-8%);
} }

View File

@ -4,7 +4,7 @@
&-enter-from, &-enter-from,
&-leave, &-leave,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
transform: scale(0); transform: scale(0);
} }
} }
@ -15,7 +15,7 @@
&-enter-from, &-enter-from,
&-leave, &-leave,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
transform: scale(0) rotate(-45deg); transform: scale(0) rotate(-45deg);
} }
} }

View File

@ -3,7 +3,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
} }
&-enter-from { &-enter-from {
@ -20,7 +20,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
} }
&-enter-from { &-enter-from {
@ -37,7 +37,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
} }
&-enter-from { &-enter-from {
@ -54,7 +54,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
} }
&-enter-from { &-enter-from {

View File

@ -3,7 +3,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
transform: translateY(-15px); transform: translateY(-15px);
} }
} }
@ -13,7 +13,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
transform: translateY(15px); transform: translateY(15px);
} }
} }
@ -23,7 +23,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
transform: translateX(-15px); transform: translateX(-15px);
} }
} }
@ -33,7 +33,7 @@
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0%; opacity: 0;
transform: translateX(15px); transform: translateX(15px);
} }
} }

View File

@ -6,7 +6,7 @@
.zoom-out-enter-from, .zoom-out-enter-from,
.zoom-out-leave-to { .zoom-out-leave-to {
opacity: 0%; opacity: 0;
transform: scale(0); transform: scale(0);
} }
@ -17,11 +17,11 @@
} }
.zoom-fade-enter-from { .zoom-fade-enter-from {
opacity: 0%; opacity: 0;
transform: scale(0.92); transform: scale(0.92);
} }
.zoom-fade-leave-to { .zoom-fade-leave-to {
opacity: 0%; opacity: 0;
transform: scale(1.06); transform: scale(1.06);
} }

View File

@ -1,56 +1,56 @@
import { on } from '/@/utils/domUtils'; import { on } from '/@/utils/domUtils'
import { isServer } from '/@/utils/is'; import { isServer } from '/@/utils/is'
import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue'; import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue'
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void; type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void
type FlushList = Map< type FlushList = Map<
HTMLElement, HTMLElement,
{ {
documentHandler: DocumentHandler; documentHandler: DocumentHandler
bindingFn: (...args: unknown[]) => unknown; bindingFn: (...args: unknown[]) => unknown
} }
>; >
const nodeList: FlushList = new Map(); const nodeList: FlushList = new Map()
let startClick: MouseEvent; let startClick: MouseEvent
if (!isServer) { if (!isServer) {
on(document, 'mousedown', (e: MouseEvent) => (startClick = e)); on(document, 'mousedown', (e: MouseEvent) => (startClick = e))
on(document, 'mouseup', (e: MouseEvent) => { on(document, 'mouseup', (e: MouseEvent) => {
for (const { documentHandler } of nodeList.values()) { for (const { documentHandler } of nodeList.values()) {
documentHandler(e, startClick); documentHandler(e, startClick)
} }
}); })
} }
function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler { function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
let excludes: HTMLElement[] = []; let excludes: HTMLElement[] = []
if (Array.isArray(binding.arg)) { if (Array.isArray(binding.arg)) {
excludes = binding.arg; excludes = binding.arg
} else { } else {
// due to current implementation on binding type is wrong the type casting is necessary here // due to current implementation on binding type is wrong the type casting is necessary here
excludes.push(binding.arg as unknown as HTMLElement); excludes.push(binding.arg as unknown as HTMLElement)
} }
return function (mouseup, mousedown) { return function (mouseup, mousedown) {
const popperRef = ( const popperRef = (
binding.instance as ComponentPublicInstance<{ binding.instance as ComponentPublicInstance<{
popperRef: Nullable<HTMLElement>; popperRef: Nullable<HTMLElement>
}> }>
).popperRef; ).popperRef
const mouseUpTarget = mouseup.target as Node; const mouseUpTarget = mouseup.target as Node
const mouseDownTarget = mousedown.target as Node; const mouseDownTarget = mousedown.target as Node
const isBound = !binding || !binding.instance; const isBound = !binding || !binding.instance
const isTargetExists = !mouseUpTarget || !mouseDownTarget; const isTargetExists = !mouseUpTarget || !mouseDownTarget
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget); const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget)
const isSelf = el === mouseUpTarget; const isSelf = el === mouseUpTarget
const isTargetExcluded = const isTargetExcluded =
(excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) || (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement)); (excludes.length && excludes.includes(mouseDownTarget as HTMLElement))
const isContainedByPopper = const isContainedByPopper =
popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget)); popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget))
if ( if (
isBound || isBound ||
isTargetExists || isTargetExists ||
@ -59,10 +59,10 @@ function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): Docu
isTargetExcluded || isTargetExcluded ||
isContainedByPopper isContainedByPopper
) { ) {
return; return
} }
binding.value(); binding.value()
}; }
} }
const ClickOutside: ObjectDirective = { const ClickOutside: ObjectDirective = {
@ -70,17 +70,17 @@ const ClickOutside: ObjectDirective = {
nodeList.set(el, { nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding), documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value, bindingFn: binding.value,
}); })
}, },
updated(el, binding) { updated(el, binding) {
nodeList.set(el, { nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding), documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value, bindingFn: binding.value,
}); })
}, },
unmounted(el) { unmounted(el) {
nodeList.delete(el); nodeList.delete(el)
}, },
}; }
export default ClickOutside; export default ClickOutside

View File

@ -1,11 +1,11 @@
/** /**
* Configure and register global directives * Configure and register global directives
*/ */
import type { App } from 'vue'; import type { App } from 'vue'
import { setupPermissionDirective } from './permission'; import { setupPermissionDirective } from './permission'
import { setupLoadingDirective } from './loading'; import { setupLoadingDirective } from './loading'
export function setupGlobDirectives(app: App) { export function setupGlobDirectives(app: App) {
setupPermissionDirective(app); setupPermissionDirective(app)
setupLoadingDirective(app); setupLoadingDirective(app)
} }

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