Compare commits

..

No commits in common. "8aaaa449d655f4ec203fd5f67aeb00b5b571fc6b" and "98f7ffd9b07c79c3c90249a08e341bad311ae15e" have entirely different histories.

268 changed files with 16271 additions and 14316 deletions

View File

@ -1,4 +1,6 @@
module.exports = { // @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true, root: true,
env: { env: {
browser: true, browser: true,
@ -18,7 +20,9 @@ module.exports = {
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',
@ -51,6 +55,8 @@ module.exports = {
'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',
@ -58,7 +64,6 @@ module.exports = {
'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',
{ {
@ -71,6 +76,5 @@ module.exports = {
math: 'always', math: 'always',
}, },
], ],
'vue/multi-word-component-names': 'off',
}, },
} });

4
.gitignore vendored
View File

@ -27,7 +27,3 @@ 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: pnpm install - init: yarn
command: pnpm run dev command: yarn dev

View File

@ -1,6 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"vue.volar", "octref.vetur",
"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,10 +1,16 @@
{ {
"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,
@ -55,7 +61,7 @@
"**/yarn.lock": true "**/yarn.lock": true
}, },
"stylelint.enable": true, "stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"], "stylelint.packageManager": "yarn",
"path-intellisense.mappings": { "path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src" "/@/": "${workspaceRoot}/src"
}, },
@ -88,8 +94,7 @@
}, },
"[vue]": { "[vue]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": false
"source.fixAll.stylelint": true
} }
}, },
"i18n-ally.localesPaths": ["src/locales/lang"], "i18n-ally.localesPaths": ["src/locales/lang"],
@ -109,6 +114,7 @@
"esnext", "esnext",
"antv", "antv",
"tinymce", "tinymce",
"qrcode",
"sider", "sider",
"pinia", "pinia",
"sider", "sider",
@ -131,11 +137,6 @@
"lintstagedrc", "lintstagedrc",
"brotli", "brotli",
"tailwindcss", "tailwindcss",
"sider", "sider"
"pnpm", ]
"antd"
],
"vetur.format.scriptInitialIndent": true,
"vetur.format.styleInitialIndent": true,
"vetur.validation.script": false
} }

48
.yarnclean 100644
View File

@ -0,0 +1,48 @@
# 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,101 +1 @@
<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,33 +1,33 @@
import { generate } from '@ant-design/colors' import { generate } from '@ant-design/colors';
export const primaryColor = '#0960bd' export const primaryColor = '#4ba06c';
export const darkMode = 'light' export const darkMode = 'light';
type Fn = (...arg: any) => any type Fn = (...arg: any) => any;
type GenerateTheme = 'default' | 'dark' type GenerateTheme = 'default' | 'dark';
export interface GenerateColorsParams { export interface GenerateColorsParams {
mixLighten: Fn mixLighten: Fn;
mixDarken: Fn mixDarken: Fn;
tinycolor: any tinycolor: any;
color?: string color?: string;
} }
export function generateAntColors(color: string, theme: GenerateTheme = 'default') { export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
return generate(color, { return generate(color, {
theme, theme,
}) });
} }
export function getThemeColors(color?: string) { export function getThemeColors(color?: string) {
const tc = color || primaryColor const tc = color || primaryColor;
const lightColors = generateAntColors(tc) const lightColors = generateAntColors(tc);
const primary = lightColors[5] const primary = lightColors[5];
const modeColors = generateAntColors(primary, 'dark') const modeColors = generateAntColors(primary, 'dark');
return [...lightColors, ...modeColors] return [...lightColors, ...modeColors];
} }
export function generateColors({ export function generateColors({
@ -36,38 +36,38 @@ export function generateColors({
mixDarken, mixDarken,
tinycolor, tinycolor,
}: GenerateColorsParams) { }: GenerateColorsParams) {
const arr = new Array(19).fill(0) const arr = new Array(19).fill(0);
const lightens = arr.map((_t, i) => { const lightens = arr.map((_t, i) => {
return mixLighten(color, i / 5) return mixLighten(color, i / 5);
}) });
const darkens = arr.map((_t, i) => { const darkens = arr.map((_t, i) => {
return mixDarken(color, i / 5) return mixDarken(color, i / 5);
}) });
const alphaColors = arr.map((_t, i) => { const alphaColors = arr.map((_t, i) => {
return tinycolor(color) return tinycolor(color)
.setAlpha(i / 20) .setAlpha(i / 20)
.toRgbString() .toRgbString();
}) });
const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.')) const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.'));
const tinycolorLightens = arr const tinycolorLightens = arr
.map((_t, i) => { .map((_t, i) => {
return tinycolor(color) return tinycolor(color)
.lighten(i * 5) .lighten(i * 5)
.toHexString() .toHexString();
}) })
.filter((item) => item !== '#ffffff') .filter((item) => item !== '#ffffff');
const tinycolorDarkens = arr const tinycolorDarkens = arr
.map((_t, i) => { .map((_t, i) => {
return tinycolor(color) return tinycolor(color)
.darken(i * 5) .darken(i * 5)
.toHexString() .toHexString();
}) })
.filter((item) => item !== '#000000') .filter((item) => item !== '#000000');
return [ return [
...lightens, ...lightens,
...darkens, ...darkens,
@ -75,5 +75,5 @@ export function generateColors({
...shortAlphaColors, ...shortAlphaColors,
...tinycolorDarkens, ...tinycolorDarkens,
...tinycolorLightens, ...tinycolorLightens,
].filter((item) => !item.includes('-')) ].filter((item) => !item.includes('-'));
} }

View File

@ -1,20 +1,20 @@
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 colors from 'picocolors' import chalk from 'chalk';
import pkg from '../../../package.json' import pkg from '../../../package.json';
async function generateIcon() { async function generateIcon() {
const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json') const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json');
const raw = await fs.readJSON(path.join(dir, 'collections.json')) const raw = await fs.readJSON(path.join(dir, 'collections.json'));
const collections = Object.entries(raw).map(([id, v]) => ({ const collections = Object.entries(raw).map(([id, v]) => ({
...(v as any), ...(v as any),
id, id,
})) }));
const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name })) const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }));
inquirer inquirer
.prompt([ .prompt([
@ -41,32 +41,32 @@ async function generateIcon() {
}, },
]) ])
.then(async (answers) => { .then(async (answers) => {
const { iconSet, output, useType } = answers const { iconSet, output, useType } = answers;
const outputDir = path.resolve(process.cwd(), output) const outputDir = path.resolve(process.cwd(), output);
fs.ensureDir(outputDir) fs.ensureDir(outputDir);
const genCollections = collections.filter((item) => [iconSet].includes(item.id)) const genCollections = collections.filter((item) => [iconSet].includes(item.id));
const prefixSet: string[] = [] const prefixSet: string[] = [];
for (const info of genCollections) { for (const info of genCollections) {
const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`)) const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`));
if (data) { if (data) {
const { prefix } = data const { prefix } = data;
const isLocal = useType === 'local' const isLocal = useType === 'local';
const icons = Object.keys(data.icons).map( const icons = Object.keys(data.icons).map(
(item) => `${isLocal ? prefix + ':' : ''}${item}`, (item) => `${isLocal ? prefix + ':' : ''}${item}`,
) );
await fs.writeFileSync( await fs.writeFileSync(
path.join(output, `icons.data.ts`), path.join(output, `icons.data.ts`),
`export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`, `export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`,
) );
prefixSet.push(prefix) prefixSet.push(prefix);
} }
} }
fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite')) fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
console.log( console.log(
`${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`, `${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`,
) );
}) });
} }
generateIcon() generateIcon();

View File

@ -1,47 +1,45 @@
/** /**
* Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging * Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging
*/ */
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 colors from 'picocolors' import chalk from 'chalk';
import { getEnvConfig, getRootPath } from '../utils' import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName' import { getConfigFileName } from '../getConfigFileName';
import pkg from '../../package.json' import pkg from '../../package.json';
interface CreateConfigParams { interface CreateConfigParams {
configName: string configName: string;
config: any config: any;
configFileName?: string configFileName?: string;
} }
function createConfig(params: CreateConfigParams) { function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params const { configName, config, configFileName } = params;
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
let configStr = `${windowConf}=${JSON.stringify(config)};` const 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));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
fs.mkdirp(getRootPath(OUTPUT_DIR)) console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr) console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n');
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`)
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n')
} catch (error) { } catch (error) {
console.log(colors.red('configuration file configuration file failed to package:\n' + error)) console.log(chalk.red('configuration file configuration file failed to package:\n' + error));
} }
} }
export function runBuildConfig() { export function runBuildConfig() {
const config = getEnvConfig() const config = getEnvConfig();
const configFileName = getConfigFileName(config) const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME }) createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
} }

View File

@ -1,23 +1,23 @@
// #!/usr/bin/env node // #!/usr/bin/env node
import { runBuildConfig } from './buildConf' import { runBuildConfig } from './buildConf';
import colors from 'picocolors' import chalk from 'chalk';
import pkg from '../../package.json' import pkg from '../../package.json';
export const runBuild = async () => { export const runBuild = async () => {
try { try {
const argvList = process.argv.splice(2) const argvList = process.argv.splice(2);
// Generate configuration file // Generate configuration file
if (!argvList.includes('disabled-config')) { if (!argvList.includes('disabled-config')) {
runBuildConfig() runBuildConfig();
} }
console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!') console.log(`${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) { } catch (error) {
console.log(colors.red('vite build error:\n' + error)) console.log(chalk.red('vite build error:\n' + error));
process.exit(1) process.exit(1);
} }
} };
runBuild() runBuild();

View File

@ -0,0 +1,21 @@
// 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 { PluginOption } from 'vite' import type { Plugin } 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,
): PluginOption | PluginOption[] { ): Plugin | Plugin[] {
const compressList = compress.split(',') const compressList = compress.split(',');
const plugins: PluginOption[] = [] const plugins: Plugin[] = [];
if (compressList.includes('gzip')) { if (compressList.includes('gzip')) {
plugins.push( plugins.push(
@ -19,7 +19,7 @@ export function configCompressPlugin(
ext: '.gz', ext: '.gz',
deleteOriginFile, deleteOriginFile,
}), }),
) );
} }
if (compressList.includes('brotli')) { if (compressList.includes('brotli')) {
@ -29,7 +29,7 @@ export function configCompressPlugin(
algorithm: 'brotliCompress', algorithm: 'brotliCompress',
deleteOriginFile, deleteOriginFile,
}), }),
) );
} }
return plugins return plugins;
} }

View File

@ -0,0 +1,25 @@
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,21 +2,21 @@
* 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 { PluginOption } from 'vite' import type { Plugin } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html' import html 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';
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/` const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
const getAppConfigSrc = () => { const getAppConfigSrc = () => {
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: PluginOption[] = createHtmlPlugin({ const htmlPlugin: Plugin[] = html({
minify: isBuild, minify: isBuild,
inject: { inject: {
// Inject data into ejs template // Inject data into ejs template
@ -35,6 +35,6 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
] ]
: [], : [],
}, },
}) });
return htmlPlugin return htmlPlugin;
} }

View File

@ -1,20 +1,20 @@
import { PluginOption } from 'vite' import type { Plugin } 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 VitePluginCertificate from 'vite-plugin-mkcert' import vueSetupExtend from 'vite-plugin-vue-setup-extend';
//import vueSetupExtend from 'vite-plugin-vue-setup-extend'; import { configHtmlPlugin } from './html';
import { configHtmlPlugin } from './html' import { configPwaConfig } from './pwa';
import { configPwaConfig } from './pwa' import { configMockPlugin } from './mock';
import { configMockPlugin } from './mock' import { configCompressPlugin } from './compress';
import { configCompressPlugin } from './compress' import { configStyleImportPlugin } from './styleImport';
import { configStyleImportPlugin } from './styleImport' import { configVisualizerConfig } from './visualizer';
import { configVisualizerConfig } from './visualizer' import { configThemePlugin } from './theme';
import { configThemePlugin } from './theme' import { configImageminPlugin } from './imagemin';
import { configImageminPlugin } from './imagemin' import { configSvgIconsPlugin } from './svgSprite';
import { configSvgIconsPlugin } from './svgSprite' import { configHmrPlugin } from './hmr';
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: (PluginOption | PluginOption[])[] = [ const vitePlugins: (Plugin | Plugin[])[] = [
// 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 { createStyleImportPlugin } from 'vite-plugin-style-import' import styleImport from 'vite-plugin-style-import';
export function configStyleImportPlugin(_isBuild: boolean) { export function configStyleImportPlugin(isBuild: boolean) {
if (!_isBuild) { if (!isBuild) {
return [] return [];
} }
const styleImportPlugin = createStyleImportPlugin({ const styleImportPlugin = styleImport({
libs: [ libs: [
{ {
libraryName: 'ant-design-vue', libraryName: 'ant-design-vue',
@ -19,7 +19,6 @@ 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',
@ -45,7 +44,7 @@ export function configStyleImportPlugin(_isBuild: boolean) {
'skeleton-paragraph', 'skeleton-paragraph',
'skeleton-image', 'skeleton-image',
'skeleton-button', 'skeleton-button',
] ];
// 这里是需要额外引入样式的子组件列表 // 这里是需要额外引入样式的子组件列表
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失 // 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
const replaceList = { const replaceList = {
@ -64,18 +63,16 @@ 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,15 +3,15 @@
* https://github.com/anncwb/vite-plugin-svg-icons * https://github.com/anncwb/vite-plugin-svg-icons
*/ */
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import SvgIconsPlugin 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 = createSvgIconsPlugin({ const svgIconsPlugin = SvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild, svgoOptions: isBuild,
// default // default
symbolId: 'icon-[dir]-[name]', symbolId: 'icon-[dir]-[name]',
}) });
return svgIconsPlugin return svgIconsPlugin;
} }

View File

@ -2,46 +2,46 @@
* 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 { PluginOption } from 'vite' import type { Plugin } from 'vite';
import path from 'path' import path from 'path';
import { import {
viteThemePlugin, viteThemePlugin,
antdDarkThemePlugin, antdDarkThemePlugin,
mixLighten, mixLighten,
mixDarken, mixDarken,
tinycolor, tinycolor,
} from 'vite-plugin-theme' } from 'vite-plugin-theme';
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): PluginOption[] { export function configThemePlugin(isBuild: boolean): Plugin[] {
const colors = generateColors({ const colors = generateColors({
mixDarken, mixDarken,
mixLighten, mixLighten,
tinycolor, tinycolor,
}) });
const plugin = [ const plugin = [
viteThemePlugin({ viteThemePlugin({
resolveSelector: (s) => { resolveSelector: (s) => {
s = s.trim() s = s.trim();
switch (s) { switch (s) {
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon': case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return '.ant-steps-item-icon > .ant-steps-icon' return '.ant-steps-item-icon > .ant-steps-icon';
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)': case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover': case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active': case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
return s return s;
case '.ant-steps-item-icon > .ant-steps-icon': case '.ant-steps-item-icon > .ant-steps-icon':
return s return s;
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)': case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s return s;
default: default:
if (s.indexOf('.ant-btn') >= -1) { if (s.indexOf('.ant-btn') >= -1) {
// 按钮被重新定制过需要过滤掉class防止覆盖 // 按钮被重新定制过需要过滤掉class防止覆盖
return s return s;
} }
} }
return s.startsWith('[data-theme') ? s : `[data-theme] ${s}` return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
}, },
colorVariables: [...getThemeColors(), ...colors], colorVariables: [...getThemeColors(), ...colors],
}), }),
@ -83,7 +83,7 @@ export function configThemePlugin(isBuild: boolean): PluginOption[] {
'alert-error-icon-color': '#a61d24', 'alert-error-icon-color': '#a61d24',
}, },
}), }),
] ];
return plugin as unknown as PluginOption[] return plugin as unknown as Plugin[];
} }

View File

@ -1,23 +1,3 @@
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'],
@ -50,58 +30,4 @@ 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="zh-cn" id="htmlRoot"> <html lang="en" 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,19 +8,20 @@
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>
@ -29,7 +30,7 @@
} }
html[data-theme='dark'] .app-loading .app-loading-title { html[data-theme='dark'] .app-loading .app-loading-title {
color: rgb(255 255 255 / 85%); color: rgba(255, 255, 255, 0.85);
} }
.app-loading { .app-loading {
@ -47,6 +48,7 @@
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;
@ -64,7 +66,7 @@
display: flex; display: flex;
margin-top: 30px; margin-top: 30px;
font-size: 30px; font-size: 30px;
color: rgb(0 0 0 / 85%); color: rgba(0, 0, 0, 0.85);
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
@ -95,7 +97,7 @@
height: 20px; height: 20px;
background-color: #0065cc; background-color: #0065cc;
border-radius: 100%; border-radius: 100%;
opacity: 30%; opacity: 0.3;
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%;
@ -109,38 +111,43 @@
.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);
} }
} }
@keyframes antRotate { @-webkit-keyframes antRotate {
to { to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg); transform: rotate(405deg);
} }
} }
@keyframes antSpinMove { @keyframes antSpinMove {
to { to {
opacity: 100%; opacity: 1;
} }
} }
@keyframes antSpinMove { @-webkit-keyframes antSpinMove {
to { to {
opacity: 100%; opacity: 1;
} }
} }
</style> </style>

36
jest.config.mjs 100644
View File

@ -0,0 +1,36 @@
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,13 +7,12 @@
"url": "https://github.com/anncwb" "url": "https://github.com/anncwb"
}, },
"scripts": { "scripts": {
"commit": "czg", "bootstrap": "yarn install",
"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": "pnpm clean:cache && npm run build", "build:no-cache": "yarn 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",
@ -24,131 +23,119 @@
"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", "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"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 pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap", "reinstall": "rimraf yarn.lock && 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.1.0", "@ant-design/icons-vue": "^6.0.1",
"@iconify/iconify": "^2.2.1", "@iconify/iconify": "^2.0.4",
"@logicflow/core": "^1.1.13", "@vueuse/core": "^6.7.4",
"@logicflow/extension": "^1.1.13", "@vueuse/shared": "^6.7.4",
"@vue/runtime-core": "^3.2.33", "@zxcvbn-ts/core": "^1.0.0-beta.0",
"@vue/shared": "^3.2.33", "ant-design-vue": "2.2.8",
"@vueuse/core": "^8.3.0", "axios": "^0.24.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",
"dayjs": "^1.11.1", "echarts": "^5.2.2",
"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.12", "pinia": "2.0.0",
"print-js": "^1.6.0", "qrcode": "^1.4.4",
"qrcode": "^1.5.0", "qs": "^6.10.1",
"qs": "^6.10.3",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"showdown": "^2.1.0", "sortablejs": "^1.14.0",
"sortablejs": "^1.15.0", "vue": "^3.2.21",
"tinymce": "^5.10.3",
"vditor": "^3.8.13",
"vue": "^3.2.33",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"vue-json-pretty": "^2.0.6", "vue-router": "^4.0.12",
"vue-router": "^4.0.14", "vue-types": "^4.1.1"
"vue-types": "^4.1.1",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "^14.1.0",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "^14.1.0",
"@iconify/json": "^2.1.30", "@iconify/json": "^1.1.422",
"@purge-icons/generated": "^0.8.1", "@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.5", "@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.0.2",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.2.1", "@types/inquirer": "^8.1.3",
"@types/intro.js": "^3.0.2", "@types/intro.js": "^3.0.2",
"@types/lodash-es": "^4.17.6", "@types/jest": "^27.0.2",
"@types/mockjs": "^1.0.6", "@types/lodash-es": "^4.17.5",
"@types/node": "^17.0.25", "@types/mockjs": "^1.0.4",
"@types/node": "^16.11.6",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2", "@types/qrcode": "^1.4.1",
"@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.20.0", "@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.20.0", "@typescript-eslint/parser": "^5.3.0",
"@vitejs/plugin-legacy": "^1.8.1", "@vitejs/plugin-legacy": "^1.6.2",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^1.9.4",
"@vitejs/plugin-vue-jsx": "^1.3.10", "@vitejs/plugin-vue-jsx": "^1.2.0",
"@vue/compiler-sfc": "^3.2.33", "@vue/compiler-sfc": "3.2.21",
"@vue/test-utils": "^2.0.0-rc.21", "@vue/test-utils": "^2.0.0-rc.16",
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.0",
"conventional-changelog-cli": "^2.2.2", "commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cz-git": "^1.3.9", "dotenv": "^10.0.0",
"czg": "^1.3.9", "eslint": "^8.1.0",
"dotenv": "^16.0.0", "eslint-config-prettier": "^8.3.0",
"eslint": "^8.13.0", "eslint-define-config": "^1.1.2",
"eslint-config-prettier": "^8.5.0", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.6.0", "eslint-plugin-vue": "^8.0.3",
"esno": "^0.14.1", "esno": "^0.10.1",
"fs-extra": "^10.1.0", "fs-extra": "^10.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"inquirer": "^8.2.2", "inquirer": "^8.2.0",
"jest": "^27.3.1",
"less": "^4.1.2", "less": "^4.1.2",
"lint-staged": "12.3.7", "lint-staged": "11.2.6",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"picocolors": "^1.0.0", "postcss": "^8.3.11",
"postcss": "^8.4.12", "postcss-html": "^1.2.0",
"postcss-html": "^1.4.1", "postcss-less": "^5.0.0",
"postcss-less": "^6.0.0", "prettier": "^2.4.1",
"prettier": "^2.6.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.70.2", "rollup-plugin-visualizer": "^5.5.2",
"rollup-plugin-visualizer": "^5.6.0", "stylelint": "^14.0.1",
"stylelint": "^14.7.1", "stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^7.0.0", "stylelint-config-standard": "^23.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-node": "^10.7.0", "ts-jest": "^27.0.7",
"typescript": "^4.6.3", "ts-node": "^10.4.0",
"vite": "^2.9.5", "typescript": "^4.4.4",
"vite-plugin-compression": "^0.5.1", "vite": "^2.6.13",
"vite-plugin-html": "^3.2.0", "vite-plugin-compression": "^0.3.5",
"vite-plugin-imagemin": "^0.6.1", "vite-plugin-html": "^2.1.1",
"vite-plugin-mkcert": "^1.6.0", "vite-plugin-imagemin": "^0.4.6",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.8.1", "vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.11.13", "vite-plugin-pwa": "^0.11.3",
"vite-plugin-style-import": "^2.0.0", "vite-plugin-style-import": "^1.3.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^1.0.5",
"vite-plugin-theme": "^0.8.6", "vite-plugin-theme": "^0.8.1",
"vite-plugin-vue-setup-extend": "^0.4.0", "vite-plugin-vue-setup-extend": "^0.1.0",
"vite-plugin-windicss": "^1.8.4", "vite-plugin-windicss": "^1.4.12",
"vue-eslint-parser": "^8.3.0", "vue-eslint-parser": "^8.0.1",
"vue-tsc": "^0.33.9" "vue-tsc": "^0.28.10"
}, },
"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",
@ -161,34 +148,5 @@
"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: false, semi: true,
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

@ -0,0 +1,12 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
// The address does not exist
Error = '/error',
}
/**
* @description: Trigger ajax error
*/
export const fireErrorApi = () => defHttp.get({ url: Api.Error });

View File

@ -0,0 +1,9 @@
export interface BasicPageParams {
page: number;
pageSize: number;
}
export interface BasicFetchResult<T> {
items: T[];
total: number;
}

View File

@ -0,0 +1,5 @@
export interface UploadApiResult {
message: string;
code: number;
url: string;
}

View File

@ -1,89 +0,0 @@
import { defHttp } from '/@/utils/http/axios';
import { ErrorMessageMode } from '/#/axios';
/**
* @description:
*/
export function getDevices(params, mode: ErrorMessageMode = 'modal') {
return defHttp.get(
{
url: '/api/devices',
params,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description:
*/
export function getDeviceTypes(mode: ErrorMessageMode = 'modal') {
return defHttp.get(
{
url: '/api/device-types',
},
{
errorMessageMode: mode,
},
);
}
/**
* @description:
*/
export function createDevice(data, mode: ErrorMessageMode = 'modal') {
return defHttp.post(
{
url: '/api/devices',
data,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description:
*/
export function updateDevice(device, data, mode: ErrorMessageMode = 'modal') {
return defHttp.put(
{
url: `/api/devices/${device}`,
data,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description:
*/
export function deleteDevice(device, mode: ErrorMessageMode = 'modal') {
return defHttp.delete(
{
url: `/api/devices/${device}`,
},
{
errorMessageMode: mode,
},
);
}
/**
* @description:
*/
export function getAgriculturalBasic(
params = { per_page: 999999, page: 1 },
mode: ErrorMessageMode = 'modal',
) {
return defHttp.get(
{
url: '/api/agricultural-basic',
params,
},
{
errorMessageMode: mode,
},
);
}

View File

@ -0,0 +1,22 @@
import { UploadApiResult } from './model/uploadModel';
import { defHttp } from '/@/utils/http/axios';
import { UploadFileParams } from '/#/axios';
import { useGlobSetting } from '/@/hooks/setting';
const { uploadUrl = '' } = useGlobSetting();
/**
* @description: Upload interface
*/
export function uploadApi(
params: UploadFileParams,
onUploadProgress: (progressEvent: ProgressEvent) => void,
) {
return defHttp.uploadFile<UploadApiResult>(
{
url: uploadUrl,
onUploadProgress,
},
params,
);
}

View File

@ -5,7 +5,7 @@ import { ErrorMessageMode } from '/#/axios';
enum Api { enum Api {
Login = '/api/auth/login', Login = '/api/auth/login',
Logout = '/api/users/logout', Logout = '/logout',
GetUserInfo = '/api/users/info', GetUserInfo = '/api/users/info',
GetPermCode = '/getPermCode', GetPermCode = '/getPermCode',
} }
@ -37,5 +37,5 @@ export function getPermCode() {
} }
export function doLogout() { export function doLogout() {
return defHttp.delete({ url: Api.Logout }); return defHttp.get({ url: Api.Logout });
} }

View File

@ -4,11 +4,11 @@
--> -->
<template> <template>
<Dropdown <Dropdown
placement="bottom" placement="bottomCenter"
:trigger="['click']" :trigger="['click']"
:dropMenuList="localeList" :dropMenuList="localeList"
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
@menu-event="handleMenuEvent" @menuEvent="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">
@ -18,13 +18,13 @@
</Dropdown> </Dropdown>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { LocaleType } from '/#/config' import type { LocaleType } from '/#/config';
import type { DropMenu } from '/@/components/Dropdown' import type { DropMenu } from '/@/components/Dropdown';
import { ref, watchEffect, unref, computed } from 'vue' import { ref, watchEffect, unref, computed } from 'vue';
import { Dropdown } from '/@/components/Dropdown' import { Dropdown } from '/@/components/Dropdown';
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon';
import { useLocale } from '/@/locales/useLocale' import { useLocale } from '/@/locales/useLocale';
import { localeList } from '/@/settings/localeSetting' import { localeList } from '/@/settings/localeSetting';
const props = defineProps({ const props = defineProps({
/** /**
@ -35,35 +35,35 @@
* Whether to refresh the interface when changing * Whether to refresh the interface when changing
*/ */
reload: { type: Boolean }, reload: { type: Boolean },
}) });
const selectedKeys = ref<string[]>([]) const selectedKeys = ref<string[]>([]);
const { changeLocale, getLocale } = useLocale() const { changeLocale, getLocale } = useLocale();
const getLocaleText = computed(() => { const getLocaleText = computed(() => {
const key = selectedKeys.value[0] const key = selectedKeys.value[0];
if (!key) { if (!key) {
return '' return '';
} }
return localeList.find((item) => item.event === key)?.text return localeList.find((item) => item.event === key)?.text;
}) });
watchEffect(() => { watchEffect(() => {
selectedKeys.value = [unref(getLocale)] selectedKeys.value = [unref(getLocale)];
}) });
async function toggleLocale(lang: LocaleType | string) { async function toggleLocale(lang: LocaleType | string) {
await changeLocale(lang as LocaleType) await changeLocale(lang as LocaleType);
selectedKeys.value = [lang as string] selectedKeys.value = [lang as string];
props.reload && location.reload() props.reload && location.reload();
} }
function handleMenuEvent(menu: DropMenu) { function handleMenuEvent(menu: DropMenu) {
if (unref(getLocale) === menu.event) { if (unref(getLocale) === menu.event) {
return return;
} }
toggleLocale(menu.event as string) toggleLocale(menu.event as string);
} }
</script> </script>

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>
@ -23,17 +23,16 @@
</div> </div>
</template> </template>
<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';
import CollapseHeader from './CollapseHeader.vue' import CollapseHeader from './CollapseHeader.vue';
import { triggerWindowResize } from '/@/utils/event' import { triggerWindowResize } from '/@/utils/event';
// hook // hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout' import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
const props = defineProps({ const props = defineProps({
title: { type: String, default: '' }, title: { type: String, default: '' },
@ -58,26 +57,22 @@
* Delayed loading time * Delayed loading time
*/ */
lazyTime: { type: Number, default: 0 }, lazyTime: { type: Number, default: 0 },
}) });
const show = ref(true) const show = ref(true);
const { prefixCls } = useDesign('collapse-container') const { prefixCls } = useDesign('collapse-container');
/** /**
* @description: Handling development events * @description: Handling development events
*/ */
function handleExpand(val: boolean) { function handleExpand() {
show.value = isNil(val) ? !show.value : val show.value = !show.value;
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,11 +1,11 @@
<script lang="tsx"> <script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing' import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
import type { FunctionalComponent, CSSProperties, PropType } from 'vue' import type { FunctionalComponent, CSSProperties } 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';
const prefixCls = 'context-menu' const prefixCls = 'context-menu';
const props = { const props = {
width: { type: Number, default: 156 }, width: { type: Number, default: 156 },
@ -16,20 +16,20 @@
// The position of the right mouse button click // The position of the right mouse button click
type: Object as PropType<Axis>, type: Object as PropType<Axis>,
default() { default() {
return { x: 0, y: 0 } return { x: 0, y: 0 };
}, },
}, },
items: { items: {
// The most important list, if not, will not be displayed // The most important list, if not, will not be displayed
type: Array as PropType<ContextMenuItem[]>, type: Array as PropType<ContextMenuItem[]>,
default() { default() {
return [] return [];
}, },
}, },
} };
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => { const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props const { item } = props;
return ( return (
<span <span
style="display: inline-block; width: 100%; " style="display: inline-block; width: 100%; "
@ -39,65 +39,62 @@
{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />} {props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
<span>{item.label}</span> <span>{item.label}</span>
</span> </span>
) );
} };
export default defineComponent({ export default defineComponent({
name: 'ContextMenu', name: 'ContextMenu',
props, props,
setup(props) { setup(props) {
const wrapRef = ref(null) const wrapRef = ref(null);
const showRef = ref(false) const showRef = ref(false);
const getStyle = computed((): CSSProperties => { const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props const { axis, items, styles, width } = props;
const { x, y } = axis || { x: 0, y: 0 } const { x, y } = axis || { x: 0, y: 0 };
const menuHeight = (items || []).length * 40 const menuHeight = (items || []).length * 40;
const menuWidth = width const menuWidth = width;
const body = document.body const body = document.body;
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
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, };
} });
})
onMounted(() => { onMounted(() => {
nextTick(() => (showRef.value = true)) nextTick(() => (showRef.value = true));
}) });
onUnmounted(() => { onUnmounted(() => {
const el = unref(wrapRef) const el = unref(wrapRef);
el && document.body.removeChild(el) el && document.body.removeChild(el);
}) });
function handleAction(item: ContextMenuItem, e: MouseEvent) { function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item const { handler, disabled } = item;
if (disabled) { if (disabled) {
return return;
} }
showRef.value = false showRef.value = false;
e?.stopPropagation() e?.stopPropagation();
e?.preventDefault() e?.preventDefault();
handler?.() handler?.();
} }
function renderMenuItem(items: ContextMenuItem[]) { function renderMenuItem(items: ContextMenuItem[]) {
const visibleItems = items.filter((item) => !item.hidden) return items.map((item) => {
return visibleItems.map((item) => { const { disabled, label, children, divider = false } = item;
const { disabled, label, children, divider = false } = item
const contentProps = { const contentProps = {
item, item,
handler: handleAction, handler: handleAction,
showIcon: props.showIcon, showIcon: props.showIcon,
} };
if (!children || children.length === 0) { if (!children || children.length === 0) {
return ( return (
@ -107,9 +104,9 @@
</Menu.Item> </Menu.Item>
{divider ? <Divider key={`d-${label}`} /> : null} {divider ? <Divider key={`d-${label}`} /> : null}
</> </>
) );
} }
if (!unref(showRef)) return null if (!unref(showRef)) return null;
return ( return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}> <Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
@ -118,24 +115,28 @@
default: () => renderMenuItem(children), default: () => renderMenuItem(children),
}} }}
</Menu.SubMenu> </Menu.SubMenu>
) );
}) });
} }
return () => { return () => {
if (!unref(showRef)) { if (!unref(showRef)) {
return null return null;
} }
const { items } = props const { items } = props;
return ( return (
<div class={prefixCls}> <Menu
<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}> inlineIndent={12}
{renderMenuItem(items)} mode="vertical"
</Menu> class={prefixCls}
</div> ref={wrapRef}
) style={unref(getStyle)}
} >
{renderMenuItem(items)}
</Menu>
);
};
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@default-height: 42px !important; @default-height: 42px !important;
@ -184,9 +185,6 @@
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

@ -1,36 +1,35 @@
export interface Axis { export interface Axis {
x: number x: number;
y: number y: number;
} }
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 children?: ContextMenuItem[];
children?: ContextMenuItem[]
} }
export interface CreateContextOptions { export interface CreateContextOptions {
event: MouseEvent event: MouseEvent;
icon?: string icon?: string;
styles?: any styles?: any;
items?: ContextMenuItem[] items?: ContextMenuItem[];
} }
export interface ContextMenuProps { export interface ContextMenuProps {
event?: MouseEvent event?: MouseEvent;
styles?: any styles?: any;
items: ContextMenuItem[] items: ContextMenuItem[];
customEvent?: MouseEvent customEvent?: MouseEvent;
axis?: Axis axis?: Axis;
width?: number width?: number;
showIcon?: boolean showIcon?: boolean;
} }
export interface ItemContentProps { export interface ItemContentProps {
showIcon: boolean | undefined showIcon: boolean | undefined;
item: ContextMenuItem item: ContextMenuItem;
handler: Fn handler: Fn;
} }

View File

@ -31,8 +31,8 @@
</Drawer> </Drawer>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { DrawerInstance, DrawerProps } from './typing' import type { DrawerInstance, DrawerProps } from './typing';
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue';
import { import {
defineComponent, defineComponent,
ref, ref,
@ -42,17 +42,17 @@
nextTick, nextTick,
toRaw, toRaw,
getCurrentInstance, getCurrentInstance,
} from 'vue' } from 'vue';
import { Drawer } from 'ant-design-vue' import { Drawer } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { isFunction, isNumber } from '/@/utils/is' import { isFunction, isNumber } from '/@/utils/is';
import { deepMerge } from '/@/utils' import { deepMerge } from '/@/utils';
import DrawerFooter from './components/DrawerFooter.vue' import DrawerFooter from './components/DrawerFooter.vue';
import DrawerHeader from './components/DrawerHeader.vue' import DrawerHeader from './components/DrawerHeader.vue';
import { ScrollContainer } from '/@/components/Container' import { ScrollContainer } from '/@/components/Container';
import { basicProps } from './props' import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import { useAttrs } from '/@/hooks/core/useAttrs' import { useAttrs } from '/@/hooks/core/useAttrs';
export default defineComponent({ export default defineComponent({
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader }, components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
@ -60,25 +60,25 @@
props: basicProps, props: basicProps,
emits: ['visible-change', 'ok', 'close', 'register'], emits: ['visible-change', 'ok', 'close', 'register'],
setup(props, { emit }) { setup(props, { emit }) {
const visibleRef = ref(false) const visibleRef = ref(false);
const attrs = useAttrs() const attrs = useAttrs();
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null) const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
const { t } = useI18n() const { t } = useI18n();
const { prefixVar, prefixCls } = useDesign('basic-drawer') const { prefixVar, prefixCls } = useDesign('basic-drawer');
const drawerInstance: DrawerInstance = { const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps, setDrawerProps: setDrawerProps,
emitVisible: undefined, emitVisible: undefined,
} };
const instance = getCurrentInstance() const instance = getCurrentInstance();
instance && emit('register', drawerInstance, instance.uid) instance && emit('register', drawerInstance, instance.uid);
const getMergeProps = computed((): DrawerProps => { const getMergeProps = computed((): DrawerProps => {
return deepMerge(toRaw(props), unref(propsRef)) return deepMerge(toRaw(props), unref(propsRef));
}) });
const getProps = computed((): DrawerProps => { const getProps = computed((): DrawerProps => {
const opt = { const opt = {
@ -86,95 +86,95 @@
...unref(attrs), ...unref(attrs),
...unref(getMergeProps), ...unref(getMergeProps),
visible: unref(visibleRef), visible: unref(visibleRef),
} };
opt.title = undefined opt.title = undefined;
const { isDetail, width, wrapClassName, getContainer } = opt const { isDetail, width, wrapClassName, getContainer } = opt;
if (isDetail) { if (isDetail) {
if (!width) { if (!width) {
opt.width = '100%' opt.width = '100%';
} }
const detailCls = `${prefixCls}__detail` const detailCls = `${prefixCls}__detail`;
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls opt.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
if (!getContainer) { if (!getContainer) {
// TODO type error? // TODO type error?
opt.getContainer = `.${prefixVar}-layout-content` as any opt.getContainer = `.${prefixVar}-layout-content` as any;
} }
} }
return opt as DrawerProps return opt as DrawerProps;
}) });
const getBindValues = computed((): DrawerProps => { const getBindValues = computed((): DrawerProps => {
return { return {
...attrs, ...attrs,
...unref(getProps), ...unref(getProps),
} };
}) });
// Custom implementation of the bottom button, // Custom implementation of the bottom button,
const getFooterHeight = computed(() => { const getFooterHeight = computed(() => {
const { footerHeight, showFooter } = unref(getProps) const { footerHeight, showFooter } = unref(getProps);
if (showFooter && footerHeight) { if (showFooter && footerHeight) {
return isNumber(footerHeight) return isNumber(footerHeight)
? `${footerHeight}px` ? `${footerHeight}px`
: `${footerHeight.replace('px', '')}px` : `${footerHeight.replace('px', '')}px`;
} }
return `0px` return `0px`;
}) });
const getScrollContentStyle = computed((): CSSProperties => { const getScrollContentStyle = computed((): CSSProperties => {
const footerHeight = unref(getFooterHeight) const footerHeight = unref(getFooterHeight);
return { return {
position: 'relative', position: 'relative',
height: `calc(100% - ${footerHeight})`, height: `calc(100% - ${footerHeight})`,
} };
}) });
const getLoading = computed(() => { const getLoading = computed(() => {
return !!unref(getProps)?.loading return !!unref(getProps)?.loading;
}) });
watch( watch(
() => props.visible, () => props.visible,
(newVal, oldVal) => { (newVal, oldVal) => {
if (newVal !== oldVal) visibleRef.value = newVal if (newVal !== oldVal) visibleRef.value = newVal;
}, },
{ deep: true }, { deep: true },
) );
watch( watch(
() => visibleRef.value, () => visibleRef.value,
(visible) => { (visible) => {
nextTick(() => { nextTick(() => {
emit('visible-change', visible) emit('visible-change', visible);
instance && drawerInstance.emitVisible?.(visible, instance.uid) instance && drawerInstance.emitVisible?.(visible, instance.uid);
}) });
}, },
) );
// Cancel event // Cancel event
async function onClose(e: Recordable) { async function onClose(e: Recordable) {
const { closeFunc } = unref(getProps) const { closeFunc } = unref(getProps);
emit('close', e) emit('close', e);
if (closeFunc && isFunction(closeFunc)) { if (closeFunc && isFunction(closeFunc)) {
const res = await closeFunc() const res = await closeFunc();
visibleRef.value = !res visibleRef.value = !res;
return return;
} }
visibleRef.value = false visibleRef.value = false;
} }
function setDrawerProps(props: Partial<DrawerProps>): void { function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps // Keep the last setDrawerProps
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props) propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (Reflect.has(props, 'visible')) { if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible visibleRef.value = !!props.visible;
} }
} }
function handleOk() { function handleOk() {
emit('ok') emit('ok');
} }
return { return {
@ -188,9 +188,9 @@
getBindValues, getBindValues,
getFooterHeight, getFooterHeight,
handleOk, handleOk,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@header-height: 60px; @header-height: 60px;

View File

@ -25,11 +25,11 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue';
import { defineComponent, computed } from 'vue' import { defineComponent, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import { footerProps } from '../props' import { footerProps } from '../props';
export default defineComponent({ export default defineComponent({
name: 'BasicDrawerFooter', name: 'BasicDrawerFooter',
props: { props: {
@ -41,26 +41,26 @@
}, },
emits: ['ok', 'close'], emits: ['ok', 'close'],
setup(props, { emit }) { setup(props, { emit }) {
const { prefixCls } = useDesign('basic-drawer-footer') const { prefixCls } = useDesign('basic-drawer-footer');
const getStyle = computed((): CSSProperties => { const getStyle = computed((): CSSProperties => {
const heightStr = `${props.height}` const heightStr = `${props.height}`;
return { return {
height: heightStr, height: heightStr,
lineHeight: `calc(${heightStr} - 1px)`, lineHeight: heightStr,
} };
}) });
function handleOk() { function handleOk() {
emit('ok') emit('ok');
} }
function handleClose() { function handleClose() {
emit('close') emit('close');
} }
return { handleOk, prefixCls, handleClose, getStyle } return { handleOk, prefixCls, handleClose, getStyle };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -1,193 +1,194 @@
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes' import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue' import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
import type { ScrollContainerOptions } from '/@/components/Container/index' import type { ScrollContainerOptions } from '/@/components/Container/index';
export interface DrawerInstance { export interface DrawerInstance {
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
emitVisible?: (visible: boolean, uid: number) => void emitVisible?: (visible: boolean, uid: number) => void;
} }
export interface ReturnMethods extends DrawerInstance { export interface ReturnMethods extends DrawerInstance {
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
closeDrawer: () => void closeDrawer: () => void;
getVisible?: ComputedRef<boolean> getVisible?: ComputedRef<boolean>;
} }
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
export interface ReturnInnerMethods extends DrawerInstance { export interface ReturnInnerMethods extends DrawerInstance {
closeDrawer: () => void closeDrawer: () => void;
changeLoading: (loading: boolean) => void changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void changeOkLoading: (loading: boolean) => void;
getVisible?: ComputedRef<boolean> getVisible?: ComputedRef<boolean>;
} }
export type UseDrawerReturnType = [RegisterFn, ReturnMethods] export type UseDrawerReturnType = [RegisterFn, ReturnMethods];
export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods] export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods];
export interface DrawerFooterProps { export interface DrawerFooterProps {
showOkBtn: boolean showOkBtn: boolean;
showCancelBtn: boolean showCancelBtn: boolean;
/** /**
* Text of the Cancel button * Text of the Cancel button
* @default 'cancel' * @default 'cancel'
* @type string * @type string
*/ */
cancelText: string cancelText: string;
/** /**
* Text of the OK button * Text of the OK button
* @default 'OK' * @default 'OK'
* @type string * @type string
*/ */
okText: string okText: string;
/** /**
* Button type of the OK button * Button type of the OK button
* @default 'primary' * @default 'primary'
* @type string * @type string
*/ */
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default' okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default';
/** /**
* The ok button props, follow jsx rules * The ok button props, follow jsx rules
* @type object * @type object
*/ */
okButtonProps: { props: ButtonProps; on: {} } okButtonProps: { props: ButtonProps; on: {} };
/** /**
* The cancel button props, follow jsx rules * The cancel button props, follow jsx rules
* @type object * @type object
*/ */
cancelButtonProps: { props: ButtonProps; on: {} } cancelButtonProps: { props: ButtonProps; on: {} };
/** /**
* Whether to apply loading visual effect for OK button or not * Whether to apply loading visual effect for OK button or not
* @default false * @default false
* @type boolean * @type boolean
*/ */
confirmLoading: boolean confirmLoading: boolean;
showFooter: boolean showFooter: boolean;
footerHeight: string | number footerHeight: string | number;
} }
export interface DrawerProps extends DrawerFooterProps { export interface DrawerProps extends DrawerFooterProps {
isDetail?: boolean isDetail?: boolean;
loading?: boolean loading?: boolean;
showDetailBack?: boolean showDetailBack?: boolean;
visible?: boolean visible?: boolean;
/** /**
* Built-in ScrollContainer component configuration * Built-in ScrollContainer component configuration
* @type ScrollContainerOptions * @type ScrollContainerOptions
*/ */
scrollOptions?: ScrollContainerOptions scrollOptions?: ScrollContainerOptions;
closeFunc?: () => Promise<any> closeFunc?: () => Promise<any>;
triggerWindowResize?: boolean triggerWindowResize?: boolean;
/** /**
* Whether a close (x) button is visible on top right of the Drawer dialog or not. * Whether a close (x) button is visible on top right of the Drawer dialog or not.
* @default true * @default true
* @type boolean * @type boolean
*/ */
closable?: boolean closable?: boolean;
/** /**
* Whether to unmount child components on closing drawer or not. * Whether to unmount child components on closing drawer or not.
* @default false * @default false
* @type boolean * @type boolean
*/ */
destroyOnClose?: boolean destroyOnClose?: boolean;
/** /**
* Return the mounted node for Drawer. * Return the mounted node for Drawer.
* @default 'body' * @default 'body'
* @type any ( HTMLElement| () => HTMLElement | string) * @type any ( HTMLElement| () => HTMLElement | string)
*/ */
getContainer?: () => HTMLElement | string getContainer?: () => HTMLElement | string;
/** /**
* Whether to show mask or not. * Whether to show mask or not.
* @default true * @default true
* @type boolean * @type boolean
*/ */
mask?: boolean mask?: boolean;
/** /**
* Clicking on the mask (area outside the Drawer) to close the Drawer or not. * Clicking on the mask (area outside the Drawer) to close the Drawer or not.
* @default true * @default true
* @type boolean * @type boolean
*/ */
maskClosable?: boolean maskClosable?: boolean;
/** /**
* Style for Drawer's mask element. * Style for Drawer's mask element.
* @default {} * @default {}
* @type object * @type object
*/ */
maskStyle?: CSSProperties maskStyle?: CSSProperties;
/** /**
* The title for Drawer. * The title for Drawer.
* @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
*/ */
wrapStyle?: CSSProperties wrapStyle?: CSSProperties;
/** /**
* Style of the popup layer element * Style of the popup layer element
* @type object * @type object
*/ */
drawerStyle?: CSSProperties drawerStyle?: CSSProperties;
/** /**
* Style of floating layer, typically used for adjusting its position. * Style of floating layer, typically used for adjusting its position.
* @type object * @type object
*/ */
bodyStyle?: CSSProperties bodyStyle?: CSSProperties;
headerStyle?: CSSProperties headerStyle?: CSSProperties;
/** /**
* Width of the Drawer dialog. * Width of the Drawer dialog.
* @default 256 * @default 256
* @type string | number * @type string | number
*/ */
width?: string | number width?: string | number;
/** /**
* placement is top or bottom, height of the Drawer dialog. * placement is top or bottom, height of the Drawer dialog.
* @type string | number * @type string | number
*/ */
height?: string | number height?: string | number;
/** /**
* The z-index of the Drawer. * The z-index of the Drawer.
* @default 1000 * @default 1000
* @type number * @type number
*/ */
zIndex?: number zIndex?: number;
/** /**
* The placement of the Drawer. * The placement of the Drawer.
* @default 'right' * @default 'right'
* @type string * @type string
*/ */
placement?: 'top' | 'right' | 'bottom' | 'left' placement?: 'top' | 'right' | 'bottom' | 'left';
afterVisibleChange?: (visible?: boolean) => void afterVisibleChange?: (visible?: boolean) => void;
keyboard?: boolean keyboard?: boolean;
/** /**
* Specify a callback that will be called when a user clicks mask, close button or Cancel button. * Specify a callback that will be called when a user clicks mask, close button or Cancel button.
*/ */
onClose?: (e?: Event) => void onClose?: (e?: Event) => void;
} }
export interface DrawerActionType { export interface DrawerActionType {
scrollBottom: () => void scrollBottom: () => void;
scrollTo: (to: number) => void scrollTo: (to: number) => void;
getScrollWrap: () => Element | null getScrollWrap: () => Element | null;
} }

View File

@ -1,17 +1,15 @@
import BasicForm from './src/BasicForm.vue' import BasicForm from './src/BasicForm.vue';
export * from './src/types/form' export * from './src/types/form';
export * from './src/types/formItem' export * from './src/types/formItem';
export { useComponentRegister } from './src/hooks/useComponentRegister' export { useComponentRegister } from './src/hooks/useComponentRegister';
export { useForm } from './src/hooks/useForm' 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

@ -37,61 +37,59 @@
</Form> </Form>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form' import type { FormActionType, FormProps, FormSchema } from './types/form';
import type { AdvanceState } from './types/hooks' import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue' import type { Ref } from 'vue';
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue' import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
import { Form, Row } from 'ant-design-vue' import { Form, Row } from 'ant-design-vue';
import FormItem from './components/FormItem.vue' import FormItem from './components/FormItem.vue';
import FormAction from './components/FormAction.vue' import FormAction from './components/FormAction.vue';
import { dateItemType } from './helper' import { dateItemType } from './helper';
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil';
// import { cloneDeep } from 'lodash-es'; // import { cloneDeep } from 'lodash-es';
import { deepMerge } from '/@/utils' import { deepMerge } from '/@/utils';
import { useFormValues } from './hooks/useFormValues' import { useFormValues } from './hooks/useFormValues';
import useAdvanced from './hooks/useAdvanced' import useAdvanced from './hooks/useAdvanced';
import { useFormEvents } from './hooks/useFormEvents' import { useFormEvents } from './hooks/useFormEvents';
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', 'field-value-change'], emits: ['advanced-change', 'reset', 'submit', 'register'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({}) const formModel = reactive<Recordable>({});
const modalFn = useModalContext() const modalFn = useModalContext();
const advanceState = reactive<AdvanceState>({ const advanceState = reactive<AdvanceState>({
isAdvanced: true, isAdvanced: true,
hideAdvanceBtn: false, hideAdvanceBtn: false,
isLoad: false, isLoad: false,
actionSpan: 6, actionSpan: 6,
}) });
const defaultValueRef = ref<Recordable>({}) const defaultValueRef = ref<Recordable>({});
const isInitedDefaultRef = ref(false) const isInitedDefaultRef = ref(false);
const propsRef = ref<Partial<FormProps>>({}) const propsRef = ref<Partial<FormProps>>({});
const schemaRef = ref<Nullable<FormSchema[]>>(null) const schemaRef = ref<Nullable<FormSchema[]>>(null);
const formElRef = ref<Nullable<FormActionType>>(null) const formElRef = ref<Nullable<FormActionType>>(null);
const { prefixCls } = useDesign('basic-form') const { prefixCls } = useDesign('basic-form');
// Get the basic configuration of the form // Get the basic configuration of the form
const getProps = computed((): FormProps => { const getProps = computed((): FormProps => {
return { ...props, ...unref(propsRef) } as FormProps return { ...props, ...unref(propsRef) } as FormProps;
}) });
const getFormClass = computed(() => { const getFormClass = computed(() => {
return [ return [
@ -99,47 +97,45 @@
{ {
[`${prefixCls}--compact`]: unref(getProps).compact, [`${prefixCls}--compact`]: unref(getProps).compact,
}, },
] ];
}) });
// Get uniform row style and Row configuration for the entire form // Get uniform row style and Row configuration for the entire form
const getRow = computed((): Recordable => { const getRow = computed((): Recordable => {
const { baseRowStyle = {}, rowProps } = unref(getProps) const { baseRowStyle = {}, rowProps } = unref(getProps);
return { return {
style: baseRowStyle, style: baseRowStyle,
...rowProps, ...rowProps,
} };
}) });
const getBindValue = computed( const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable), () => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
) );
const getSchema = computed((): FormSchema[] => { const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any) const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
for (const schema of schemas) { for (const schema of schemas) {
const { defaultValue, component } = schema const { defaultValue, component } = schema;
// handle date type // handle date type
if (defaultValue && dateItemType.includes(component)) { if (defaultValue && dateItemType.includes(component)) {
if (!Array.isArray(defaultValue)) { if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue) schema.defaultValue = dateUtil(defaultValue);
} else { } else {
const def: any[] = [] const def: moment.Moment[] = [];
defaultValue.forEach((item) => { defaultValue.forEach((item) => {
def.push(dateUtil(item)) def.push(dateUtil(item));
}) });
schema.defaultValue = def schema.defaultValue = def;
} }
} }
} }
if (unref(getProps).showAdvancedButton) { if (unref(getProps).showAdvancedButton) {
return cloneDeep( return schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[];
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
)
} else { } else {
return cloneDeep(schemas as FormSchema[]) return schemas as FormSchema[];
} }
}) });
const { handleToggleAdvanced } = useAdvanced({ const { handleToggleAdvanced } = useAdvanced({
advanceState, advanceState,
@ -148,21 +144,21 @@
getSchema, getSchema,
formModel, formModel,
defaultValueRef, defaultValueRef,
}) });
const { handleFormValues, initDefault } = useFormValues({ const { handleFormValues, initDefault } = useFormValues({
getProps, getProps,
defaultValueRef, defaultValueRef,
getSchema, getSchema,
formModel, formModel,
}) });
useAutoFocus({ useAutoFocus({
getSchema, getSchema,
getProps, getProps,
isInitedDefault: isInitedDefaultRef, isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>, formElRef: formElRef as Ref<FormActionType>,
}) });
const { const {
handleSubmit, handleSubmit,
@ -174,7 +170,7 @@
updateSchema, updateSchema,
resetSchema, resetSchema,
appendSchemaByField, appendSchemaByField,
removeSchemaByField, removeSchemaByFiled,
resetFields, resetFields,
scrollToField, scrollToField,
} = useFormEvents({ } = useFormEvents({
@ -186,77 +182,68 @@
formElRef: formElRef as Ref<FormActionType>, formElRef: formElRef as Ref<FormActionType>,
schemaRef: schemaRef as Ref<FormSchema[]>, schemaRef: schemaRef as Ref<FormSchema[]>,
handleFormValues, handleFormValues,
}) });
createFormContext({ createFormContext({
resetAction: resetFields, resetAction: resetFields,
submitAction: handleSubmit, submitAction: handleSubmit,
}) });
watch( watch(
() => unref(getProps).model, () => unref(getProps).model,
() => { () => {
const { model } = unref(getProps) const { model } = unref(getProps);
if (!model) return if (!model) return;
setFieldsValue(model) setFieldsValue(model);
}, },
{ {
immediate: true, immediate: true,
}, },
) );
watch( watch(
() => unref(getProps).schemas, () => unref(getProps).schemas,
(schemas) => { (schemas) => {
resetSchema(schemas ?? []) resetSchema(schemas ?? []);
}, },
) );
watch( watch(
() => getSchema.value, () => getSchema.value,
(schema) => { (schema) => {
nextTick(() => { nextTick(() => {
// 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?.();
}) });
if (unref(isInitedDefaultRef)) { if (unref(isInitedDefaultRef)) {
return return;
} }
if (schema?.length) { if (schema?.length) {
initDefault() initDefault();
isInitedDefaultRef.value = true isInitedDefaultRef.value = true;
} }
}, },
) );
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);
} }
function setFormModel(key: string, value: any) { function setFormModel(key: string, value: any) {
formModel[key] = value formModel[key] = value;
const { validateTrigger } = unref(getBindValue) const { validateTrigger } = unref(getBindValue);
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) {
const { autoSubmitOnEnter } = unref(getProps) const { autoSubmitOnEnter } = unref(getProps);
if (!autoSubmitOnEnter) return if (!autoSubmitOnEnter) return;
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) { if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
const target: HTMLElement = e.target as HTMLElement const target: HTMLElement = e.target as HTMLElement;
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') { if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
handleSubmit() handleSubmit();
} }
} }
} }
@ -268,19 +255,19 @@
updateSchema, updateSchema,
resetSchema, resetSchema,
setProps, setProps,
removeSchemaByField, removeSchemaByFiled,
appendSchemaByField, appendSchemaByField,
clearValidate, clearValidate,
validateFields, validateFields,
validate, validate,
submit: handleSubmit, submit: handleSubmit,
scrollToField: scrollToField, scrollToField: scrollToField,
} };
onMounted(() => { onMounted(() => {
initDefault() initDefault();
emit('register', formActionType) emit('register', formActionType);
}) });
return { return {
getBindValue, getBindValue,
@ -300,9 +287,9 @@
(): Recordable => ({ ...getProps.value, ...advanceState }), (): Recordable => ({ ...getProps.value, ...advanceState }),
), ),
...formActionType, ...formActionType,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-form'; @prefix-cls: ~'@{namespace}-basic-form';

View File

@ -1,5 +1,5 @@
import type { Component } from 'vue' import type { Component } from 'vue';
import type { ComponentType } from './types/index' import type { ComponentType } from './types/index';
/** /**
* Component list, register here to setting it in the form * Component list, register here to setting it in the form
@ -19,65 +19,61 @@ import {
Slider, Slider,
Rate, Rate,
Divider, Divider,
} from 'ant-design-vue' } from 'ant-design-vue';
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 { BasicUpload } from '/@/components/Upload';
import ApiTransfer from './components/ApiTransfer.vue' import { StrengthMeter } from '/@/components/StrengthMeter';
import { BasicUpload } from '/@/components/Upload' import { IconPicker } from '/@/components/Icon';
import { StrengthMeter } from '/@/components/StrengthMeter' import { CountdownInput } from '/@/components/CountDown';
import { IconPicker } from '/@/components/Icon'
import { CountdownInput } from '/@/components/CountDown'
const componentMap = new Map<ComponentType, Component>() const componentMap = new Map<ComponentType, Component>();
componentMap.set('Input', Input) componentMap.set('Input', Input);
componentMap.set('InputGroup', Input.Group) componentMap.set('InputGroup', Input.Group);
componentMap.set('InputPassword', Input.Password) componentMap.set('InputPassword', Input.Password);
componentMap.set('InputSearch', Input.Search) componentMap.set('InputSearch', Input.Search);
componentMap.set('InputTextArea', Input.TextArea) componentMap.set('InputTextArea', Input.TextArea);
componentMap.set('InputNumber', InputNumber) componentMap.set('InputNumber', InputNumber);
componentMap.set('AutoComplete', AutoComplete) 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) componentMap.set('Switch', Switch);
componentMap.set('Switch', Switch) componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('RadioButtonGroup', RadioButtonGroup) componentMap.set('RadioGroup', Radio.Group);
componentMap.set('RadioGroup', Radio.Group) componentMap.set('Checkbox', Checkbox);
componentMap.set('Checkbox', Checkbox) componentMap.set('CheckboxGroup', Checkbox.Group);
componentMap.set('CheckboxGroup', Checkbox.Group) componentMap.set('ApiCascader', ApiCascader);
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);
componentMap.set('RangePicker', DatePicker.RangePicker) componentMap.set('RangePicker', DatePicker.RangePicker);
componentMap.set('WeekPicker', DatePicker.WeekPicker) componentMap.set('WeekPicker', DatePicker.WeekPicker);
componentMap.set('TimePicker', TimePicker) componentMap.set('TimePicker', TimePicker);
componentMap.set('StrengthMeter', StrengthMeter) componentMap.set('StrengthMeter', StrengthMeter);
componentMap.set('IconPicker', IconPicker) componentMap.set('IconPicker', IconPicker);
componentMap.set('InputCountDown', CountdownInput) componentMap.set('InputCountDown', CountdownInput);
componentMap.set('Upload', BasicUpload) componentMap.set('Upload', BasicUpload);
componentMap.set('Divider', Divider) componentMap.set('Divider', Divider);
export function add(compName: ComponentType, component: Component) { export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component) componentMap.set(compName, component);
} }
export function del(compName: ComponentType) { export function del(compName: ComponentType) {
componentMap.delete(compName) componentMap.delete(compName);
} }
export { componentMap } export { componentMap };

View File

@ -19,20 +19,20 @@
</a-cascader> </a-cascader>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue' import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue';
import { Cascader } from 'ant-design-vue' import { Cascader } from 'ant-design-vue';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
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;
loading?: boolean loading?: boolean;
isLeaf?: boolean isLeaf?: boolean;
children?: Option[] children?: Option[];
} }
export default defineComponent({ export default defineComponent({
name: 'ApiCascader', name: 'ApiCascader',
@ -71,128 +71,127 @@
}, },
emits: ['change', 'defaultChange'], emits: ['change', 'defaultChange'],
setup(props, { emit }) { setup(props, { emit }) {
const apiData = ref<any[]>([]) const apiData = ref<any[]>([]);
const options = ref<Option[]>([]) const options = ref<Option[]>([]);
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);
watch( watch(
apiData, apiData,
(data) => { (data) => {
const opts = generatorOptions(data) const opts = generatorOptions(data);
options.value = opts options.value = opts;
}, },
{ deep: true }, { deep: true },
) );
function generatorOptions(options: any[]): Option[] { function generatorOptions(options: any[]): Option[] {
const { labelField, valueField, numberToString, childrenField, isLeaf } = props const { labelField, valueField, numberToString, childrenField, isLeaf } = props;
return options.reduce((prev, next: Recordable) => { return options.reduce((prev, next: Recordable) => {
if (next) { if (next) {
const value = next[valueField] const value = next[valueField];
const item = { const item = {
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
label: next[labelField], label: next[labelField],
value: numberToString ? `${value}` : value, value: numberToString ? `${value}` : value,
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false, isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
} };
const children = Reflect.get(next, childrenField) const children = Reflect.get(next, childrenField);
if (children) { if (children) {
Reflect.set(item, childrenField, generatorOptions(children)) Reflect.set(item, childrenField, generatorOptions(children));
} }
prev.push(item) prev.push(item);
} }
return prev return prev;
}, [] as Option[]) }, [] as Option[]);
} }
async function initialFetch() { async function initialFetch() {
const api = props.api const api = props.api;
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
apiData.value = [] apiData.value = [];
loading.value = true loading.value = true;
try { try {
const res = await api(props.initFetchParams) const res = await api(props.initFetchParams);
if (Array.isArray(res)) { if (Array.isArray(res)) {
apiData.value = res apiData.value = res;
return return;
} }
if (props.resultField) { if (props.resultField) {
apiData.value = get(res, props.resultField) || [] apiData.value = get(res, props.resultField) || [];
} }
} catch (error) { } catch (error) {
console.warn(error) console.warn(error);
} finally { } finally {
loading.value = false loading.value = false;
} }
} }
async function loadData(selectedOptions: Option[]) { async function loadData(selectedOptions: Option[]) {
const targetOption = selectedOptions[selectedOptions.length - 1] const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true targetOption.loading = true;
const api = props.api const api = props.api;
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
try { try {
const res = await api({ const res = await api({
[props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'), [props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
}) });
if (Array.isArray(res)) { if (Array.isArray(res)) {
const children = generatorOptions(res) const children = generatorOptions(res);
targetOption.children = children targetOption.children = children;
return return;
} }
if (props.resultField) { if (props.resultField) {
const children = generatorOptions(get(res, props.resultField) || []) const children = generatorOptions(get(res, props.resultField) || []);
targetOption.children = children targetOption.children = children;
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e);
} finally { } finally {
targetOption.loading = false targetOption.loading = false;
} }
} }
watchEffect(() => { watchEffect(() => {
props.immediate && initialFetch() props.immediate && initialFetch();
}) });
watch( watch(
() => props.initFetchParams, () => props.initFetchParams,
() => { () => {
!unref(isFirstLoad) && initialFetch() !unref(isFirstLoad) && initialFetch();
}, },
{ deep: true }, { deep: true },
) );
function handleChange(keys, args) { function handleChange(keys, args) {
emitData.value = keys emitData.value = keys;
emit('defaultChange', keys, args) emit('defaultChange', keys, args);
} }
function handleRenderDisplay({ labels, selectedOptions }) { function handleRenderDisplay({ labels, selectedOptions }) {
if (unref(emitData).length === selectedOptions.length) { if (unref(emitData).length === selectedOptions.length) {
return labels.join(' / ') return labels.join(' / ');
} }
if (props.displayRenderArray) { if (props.displayRenderArray) {
return props.displayRenderArray.join(' / ') return props.displayRenderArray.join(' / ');
} }
return '' return '';
} }
return { return {
state, state,
options, options,
loading, loading,
t,
handleChange, handleChange,
loadData, loadData,
handleRenderDisplay, handleRenderDisplay,
} };
}, },
}) });
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<Select <Select
@dropdown-visible-change="handleFetch" @dropdownVisibleChange="handleFetch"
v-bind="$attrs" v-bind="$attrs"
@change="handleChange" @change="handleChange"
:options="getOptions" :options="getOptions"
@ -21,17 +21,17 @@
</Select> </Select>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue' import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue';
import { Select } from 'ant-design-vue' import { Select } from 'ant-design-vue';
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
import { useRuleFormItem } from '/@/hooks/component/useFormItem' import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs' import { useAttrs } from '/@/hooks/core/useAttrs';
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es';
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
type OptionsItem = { label: string; value: string; disabled?: boolean } type OptionsItem = { label: string; value: string; disabled?: boolean };
export default defineComponent({ export default defineComponent({
name: 'ApiSelect', name: 'ApiSelect',
@ -57,91 +57,86 @@
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 }) {
const options = ref<OptionsItem[]>([]) const options = ref<OptionsItem[]>([]);
const loading = ref(false) const loading = ref(false);
const isFirstLoad = ref(true) const isFirstLoad = ref(true);
const emitData = ref<any[]>([]) const emitData = ref<any[]>([]);
const attrs = useAttrs() const attrs = useAttrs();
const { t } = useI18n() 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);
const getOptions = computed(() => { const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props const { labelField, valueField, numberToString } = props;
return unref(options).reduce((prev, next: Recordable) => { return unref(options).reduce((prev, next: Recordable) => {
if (next) { if (next) {
const value = next[valueField] const value = next[valueField];
prev.push({ prev.push({
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
label: next[labelField], label: next[labelField],
value: numberToString ? `${value}` : value, value: numberToString ? `${value}` : value,
}) });
} }
return prev return prev;
}, [] as OptionsItem[]) }, [] as OptionsItem[]);
}) });
watchEffect(() => { watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch() props.immediate && fetch();
}) });
watch( watch(
() => props.params, () => props.params,
() => { () => {
!unref(isFirstLoad) && fetch() !unref(isFirstLoad) && fetch();
}, },
{ deep: true }, { deep: true },
) );
async function fetch() { async function fetch() {
const api = props.api const api = props.api;
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
options.value = [] options.value = [];
try { try {
loading.value = true loading.value = true;
const res = await api(props.params) const res = await api(props.params);
if (Array.isArray(res)) { if (Array.isArray(res)) {
options.value = res options.value = res;
emitChange() emitChange();
return return;
} }
if (props.resultField) { if (props.resultField) {
options.value = get(res, props.resultField) || [] options.value = get(res, props.resultField) || [];
} }
emitChange() emitChange();
} catch (error) { } catch (error) {
console.warn(error) console.warn(error);
} finally { } finally {
loading.value = false loading.value = false;
} }
} }
async function handleFetch(visible) { async function handleFetch() {
if (visible) { if (!props.immediate && unref(isFirstLoad)) {
if (props.alwaysLoad) { await fetch();
await fetch() isFirstLoad.value = false;
} else if (!props.immediate && unref(isFirstLoad)) {
await fetch()
isFirstLoad.value = false
}
} }
} }
function emitChange() { function emitChange() {
emit('options-change', unref(getOptions)) emit('options-change', unref(getOptions));
} }
function handleChange(_, ...args) { function handleChange(_, ...args) {
emitData.value = args emitData.value = args;
} }
return { state, attrs, getOptions, loading, t, handleFetch, handleChange } return { state, attrs, getOptions, loading, t, handleFetch, handleChange };
}, },
}) });
</script> </script>

View File

@ -1,134 +0,0 @@
<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

@ -1,90 +0,0 @@
<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,18 +1,19 @@
<script lang="tsx"> <script lang="tsx">
import type { PropType, Ref } from 'vue' import type { PropType, Ref } from 'vue';
import { computed, defineComponent, toRefs, unref } from 'vue' import type { FormActionType, FormProps } from '../types/form';
import type { FormActionType, FormProps, FormSchema } from '../types/form' import type { 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 { Col, Divider, Form } from 'ant-design-vue' import { defineComponent, computed, unref, toRefs } from 'vue';
import { componentMap } from '../componentMap' import { Form, Col, Divider } from 'ant-design-vue';
import { BasicHelp } from '/@/components/Basic' import { componentMap } from '../componentMap';
import { isBoolean, isFunction, isNull } from '/@/utils/is' import { BasicHelp } from '/@/components/Basic';
import { getSlot } from '/@/utils/helper/tsxHelper' import { isBoolean, isFunction, isNull } from '/@/utils/is';
import { createPlaceholderMessage, setComponentRuleType } from '../helper' import { getSlot } from '/@/utils/helper/tsxHelper';
import { cloneDeep, upperFirst } from 'lodash-es' import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { useItemLabelWidth } from '../hooks/useLabelWidth' import { upperFirst, cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n' import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
name: 'BasicFormItem', name: 'BasicFormItem',
@ -46,18 +47,18 @@
}, },
}, },
setup(props, { slots }) { setup(props, { slots }) {
const { t } = useI18n() const { t } = useI18n();
const { schema, formProps } = toRefs(props) as { const { schema, formProps } = toRefs(props) as {
schema: Ref<FormSchema> schema: Ref<FormSchema>;
formProps: Ref<FormProps> formProps: Ref<FormProps>;
} };
const itemLabelWidthProp = useItemLabelWidth(schema, formProps) const itemLabelWidthProp = useItemLabelWidth(schema, formProps);
const getValues = computed(() => { const getValues = computed(() => {
const { allDefaultValues, formModel, schema } = props const { allDefaultValues, formModel, schema } = props;
const { mergeDynamicData } = props.formProps const { mergeDynamicData } = props.formProps;
return { return {
field: schema.field, field: schema.field,
model: formModel, model: formModel,
@ -67,64 +68,64 @@
...formModel, ...formModel,
} as Recordable, } as Recordable,
schema: schema, schema: schema,
} };
}) });
const getComponentsProps = computed(() => { const getComponentsProps = computed(() => {
const { schema, tableAction, formModel, formActionType } = props const { schema, tableAction, formModel, formActionType } = props;
let { componentProps = {} } = schema let { componentProps = {} } = schema;
if (isFunction(componentProps)) { if (isFunction(componentProps)) {
componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {} componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
} }
if (schema.component === 'Divider') { if (schema.component === 'Divider') {
componentProps = Object.assign({ type: 'horizontal' }, componentProps, { componentProps = Object.assign({ type: 'horizontal' }, componentProps, {
orientation: 'left', orientation: 'left',
plain: true, plain: true,
}) });
} }
return componentProps as Recordable return componentProps as Recordable;
}) });
const getDisable = computed(() => { const getDisable = computed(() => {
const { disabled: globDisabled } = props.formProps const { disabled: globDisabled } = props.formProps;
const { dynamicDisabled } = props.schema const { dynamicDisabled } = props.schema;
const { disabled: itemDisabled = false } = unref(getComponentsProps) const { disabled: itemDisabled = false } = unref(getComponentsProps);
let disabled = !!globDisabled || itemDisabled let disabled = !!globDisabled || itemDisabled;
if (isBoolean(dynamicDisabled)) { if (isBoolean(dynamicDisabled)) {
disabled = dynamicDisabled disabled = dynamicDisabled;
} }
if (isFunction(dynamicDisabled)) { if (isFunction(dynamicDisabled)) {
disabled = dynamicDisabled(unref(getValues)) disabled = dynamicDisabled(unref(getValues));
} }
return disabled return disabled;
}) });
function getShow(): { isShow: boolean; isIfShow: boolean } { function getShow(): { isShow: boolean; isIfShow: boolean } {
const { show, ifShow } = props.schema const { show, ifShow } = props.schema;
const { showAdvancedButton } = props.formProps const { showAdvancedButton } = props.formProps;
const itemIsAdvanced = showAdvancedButton const itemIsAdvanced = showAdvancedButton
? isBoolean(props.schema.isAdvanced) ? isBoolean(props.schema.isAdvanced)
? props.schema.isAdvanced ? props.schema.isAdvanced
: true : true
: true : true;
let isShow = true let isShow = true;
let isIfShow = true let isIfShow = true;
if (isBoolean(show)) { if (isBoolean(show)) {
isShow = show isShow = show;
} }
if (isBoolean(ifShow)) { if (isBoolean(ifShow)) {
isIfShow = ifShow isIfShow = ifShow;
} }
if (isFunction(show)) { if (isFunction(show)) {
isShow = show(unref(getValues)) isShow = show(unref(getValues));
} }
if (isFunction(ifShow)) { if (isFunction(ifShow)) {
isIfShow = ifShow(unref(getValues)) isIfShow = ifShow(unref(getValues));
} }
isShow = isShow && itemIsAdvanced isShow = isShow && itemIsAdvanced;
return { isShow, isIfShow } return { isShow, isIfShow };
} }
function handleRules(): ValidationRule[] { function handleRules(): ValidationRule[] {
@ -135,31 +136,31 @@
label, label,
dynamicRules, dynamicRules,
required, required,
} = props.schema } = props.schema;
if (isFunction(dynamicRules)) { if (isFunction(dynamicRules)) {
return dynamicRules(unref(getValues)) as ValidationRule[] return dynamicRules(unref(getValues)) as ValidationRule[];
} }
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[] let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
? rulesMessageJoinLabel ? rulesMessageJoinLabel
: globalRulesMessageJoinLabel : globalRulesMessageJoinLabel;
const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}` const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
function validator(rule: any, value: any) { function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg const msg = rule.message || defaultMsg;
if (value === undefined || isNull(value)) { if (value === undefined || isNull(value)) {
// //
return Promise.reject(msg) return Promise.reject(msg);
} else if (Array.isArray(value) && value.length === 0) { } else if (Array.isArray(value) && value.length === 0) {
// //
return Promise.reject(msg) return Promise.reject(msg);
} else if (typeof value === 'string' && value.trim() === '') { } else if (typeof value === 'string' && value.trim() === '') {
// //
return Promise.reject(msg) return Promise.reject(msg);
} else if ( } else if (
typeof value === 'object' && typeof value === 'object' &&
Reflect.has(value, 'checked') && Reflect.has(value, 'checked') &&
@ -170,63 +171,50 @@
value.halfChecked.length === 0 value.halfChecked.length === 0
) { ) {
// tree // tree
return Promise.reject(msg) return Promise.reject(msg);
} }
return Promise.resolve() return Promise.resolve();
} }
const getRequired = isFunction(required) ? required(unref(getValues)) : required const getRequired = isFunction(required) ? required(unref(getValues)) : required;
/* if ((!rules || rules.length === 0) && getRequired) {
* 1若设置了required属性又没有其他的rules就创建一个验证规则 rules = [{ required: getRequired, validator }];
* 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(
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'), (rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
) );
if (requiredRuleIndex !== -1) { if (requiredRuleIndex !== -1) {
const rule = rules[requiredRuleIndex] const rule = rules[requiredRuleIndex];
const { isShow } = getShow() const { isShow } = getShow();
if (!isShow) { if (!isShow) {
rule.required = false rule.required = false;
} }
if (component) { if (component) {
if (!Reflect.has(rule, 'type')) { if (!Reflect.has(rule, 'type')) {
rule.type = component === 'InputNumber' ? 'number' : 'string' rule.type = component === 'InputNumber' ? 'number' : 'string';
} }
rule.message = rule.message || defaultMsg rule.message = rule.message || defaultMsg;
if (component.includes('Input') || component.includes('Textarea')) { if (component.includes('Input') || component.includes('Textarea')) {
rule.whitespace = true rule.whitespace = true;
} }
const valueFormat = unref(getComponentsProps)?.valueFormat const valueFormat = unref(getComponentsProps)?.valueFormat;
setComponentRuleType(rule, component, valueFormat) setComponentRuleType(rule, component, valueFormat);
} }
} }
// Maximum input length rule check // Maximum input length rule check
const characterInx = rules.findIndex((val) => val.max) const characterInx = rules.findIndex((val) => val.max);
if (characterInx !== -1 && !rules[characterInx].validator) { if (characterInx !== -1 && !rules[characterInx].validator) {
rules[characterInx].message = rules[characterInx].message =
rules[characterInx].message || rules[characterInx].message ||
t('component.form.maxTip', [rules[characterInx].max] as Recordable) t('component.form.maxTip', [rules[characterInx].max] as Recordable);
} }
return rules return rules;
} }
function renderComponent() { function renderComponent() {
@ -236,107 +224,109 @@
field, field,
changeEvent = 'change', changeEvent = 'change',
valueField, valueField,
} = props.schema } = props.schema;
const isCheck = component && ['Switch', 'Checkbox'].includes(component) const isCheck = component && ['Switch', 'Checkbox'].includes(component);
const eventKey = `on${upperFirst(changeEvent)}` const eventKey = `on${upperFirst(changeEvent)}`;
const on = { const on = {
[eventKey]: (...args: Nullable<Recordable>[]) => { [eventKey]: (...args: Nullable<Recordable>[]) => {
const [e] = args const [e] = args;
if (propsData[eventKey]) { if (propsData[eventKey]) {
propsData[eventKey](...args) propsData[eventKey](...args);
} }
const target = e ? e.target : null const target = e ? e.target : null;
const value = target ? (isCheck ? target.checked : target.value) : e const value = target ? (isCheck ? target.checked : target.value) : e;
props.setFormModel(field, value) props.setFormModel(field, value);
}, },
} };
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent> const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;
const { autoSetPlaceHolder, size } = props.formProps const { autoSetPlaceHolder, size } = props.formProps;
const propsData: Recordable = { const propsData: Recordable = {
allowClear: true, allowClear: true,
getPopupContainer: (trigger: Element) => trigger.parentNode, getPopupContainer: (trigger: Element) => trigger.parentNode,
size, size,
...unref(getComponentsProps), ...unref(getComponentsProps),
disabled: unref(getDisable), disabled: unref(getDisable),
} };
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
// RangePicker place is an array // RangePicker place is an array
if (isCreatePlaceholder && component !== 'RangePicker' && component) { if (isCreatePlaceholder && component !== 'RangePicker' && component) {
propsData.placeholder = propsData.placeholder =
unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component) unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
} }
propsData.codeField = field propsData.codeField = field;
propsData.formValues = unref(getValues) propsData.formValues = unref(getValues);
const bindValue: Recordable = { const bindValue: Recordable = {
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field], [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
} };
const compAttr: Recordable = { const compAttr: Recordable = {
...propsData, ...propsData,
...on, ...on,
...bindValue, ...bindValue,
} };
if (!renderComponentContent) { if (!renderComponentContent) {
return <Comp {...compAttr} /> return <Comp {...compAttr} />;
} }
const compSlot = isFunction(renderComponentContent) const compSlot = isFunction(renderComponentContent)
? { ...renderComponentContent(unref(getValues)) } ? { ...renderComponentContent(unref(getValues)) }
: { : {
default: () => renderComponentContent, default: () => renderComponentContent,
} };
return <Comp {...compAttr}>{compSlot}</Comp> return <Comp {...compAttr}>{compSlot}</Comp>;
} }
function renderLabelHelpMessage() { function renderLabelHelpMessage() {
const { label, helpMessage, helpComponentProps, subLabel } = props.schema const { label, helpMessage, helpComponentProps, subLabel } = props.schema;
const renderLabel = subLabel ? ( const renderLabel = subLabel ? (
<span> <span>
{label} <span class="text-secondary">{subLabel}</span> {label} <span class="text-secondary">{subLabel}</span>
</span> </span>
) : ( ) : (
label label
) );
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage const getHelpMessage = isFunction(helpMessage)
? helpMessage(unref(getValues))
: helpMessage;
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) { if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
return renderLabel return renderLabel;
} }
return ( return (
<span> <span>
{renderLabel} {renderLabel}
<BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} /> <BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} />
</span> </span>
) );
} }
function renderItem() { function renderItem() {
const { itemProps, slot, render, field, suffix, component } = props.schema const { itemProps, slot, render, field, suffix, component } = props.schema;
const { labelCol, wrapperCol } = unref(itemLabelWidthProp) const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
const { colon } = props.formProps const { colon } = props.formProps;
if (component === 'Divider') { if (component === 'Divider') {
return ( return (
<Col span={24}> <Col span={24}>
<Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider> <Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider>
</Col> </Col>
) );
} else { } else {
const getContent = () => { const getContent = () => {
return slot return slot
? getSlot(slots, slot, unref(getValues)) ? getSlot(slots, slot, unref(getValues))
: render : render
? render(unref(getValues)) ? render(unref(getValues))
: renderComponent() : renderComponent();
} };
const showSuffix = !!suffix const showSuffix = !!suffix;
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
return ( return (
<Form.Item <Form.Item
@ -354,28 +344,28 @@
{showSuffix && <span class="suffix">{getSuffix}</span>} {showSuffix && <span class="suffix">{getSuffix}</span>}
</div> </div>
</Form.Item> </Form.Item>
) );
} }
} }
return () => { return () => {
const { colProps = {}, colSlot, renderColContent, component } = props.schema const { colProps = {}, colSlot, renderColContent, component } = props.schema;
if (!componentMap.has(component)) { if (!componentMap.has(component)) {
return null return null;
} }
const { baseColProps = {} } = props.formProps const { baseColProps = {} } = props.formProps;
const realColProps = { ...baseColProps, ...colProps } const realColProps = { ...baseColProps, ...colProps };
const { isIfShow, isShow } = getShow() const { isIfShow, isShow } = getShow();
const values = unref(getValues) const values = unref(getValues);
const getContent = () => { const getContent = () => {
return colSlot return colSlot
? getSlot(slots, colSlot, values) ? getSlot(slots, colSlot, values)
: renderColContent : renderColContent
? renderColContent(values) ? renderColContent(values)
: renderItem() : renderItem();
} };
return ( return (
isIfShow && ( isIfShow && (
@ -383,8 +373,8 @@
{getContent()} {getContent()}
</Col> </Col>
) )
) );
} };
}, },
}) });
</script> </script>

View File

@ -1,20 +1,20 @@
import type { ValidationRule } from 'ant-design-vue/lib/form/Form' import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { ComponentType } from './types/index' import type { ComponentType } from './types/index';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil';
import { isNumber, isObject } from '/@/utils/is' import { isNumber, isObject } from '/@/utils/is';
const { t } = useI18n() const { t } = useI18n();
/** /**
* @description: placeholder * @description: placeholder
*/ */
export function createPlaceholderMessage(component: ComponentType) { export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input') || component.includes('Complete')) { if (component.includes('Input') || component.includes('Complete')) {
return t('common.inputText') return t('common.inputText');
} }
if (component.includes('Picker')) { if (component.includes('Picker')) {
return t('common.chooseText') return t('common.chooseText');
} }
if ( if (
component.includes('Select') || component.includes('Select') ||
@ -24,15 +24,15 @@ export function createPlaceholderMessage(component: ComponentType) {
component.includes('Switch') component.includes('Switch')
) { ) {
// return `请选择${label}`; // return `请选择${label}`;
return t('common.chooseText') return t('common.chooseText');
} }
return '' return '';
} }
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'] const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'];
function genType() { function genType() {
return [...DATE_TYPE, 'RangePicker'] return [...DATE_TYPE, 'RangePicker'];
} }
export function setComponentRuleType( export function setComponentRuleType(
@ -41,34 +41,32 @@ export function setComponentRuleType(
valueFormat: string, valueFormat: string,
) { ) {
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) { if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) {
rule.type = valueFormat ? 'string' : 'object' rule.type = valueFormat ? 'string' : 'object';
} else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) { } else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) {
rule.type = 'array' rule.type = 'array';
} else if (['InputNumber'].includes(component)) { } else if (['InputNumber'].includes(component)) {
rule.type = 'number' rule.type = 'number';
} }
} }
export function processDateValue(attr: Recordable, component: string) { export function processDateValue(attr: Recordable, component: string) {
const { valueFormat, value } = attr const { valueFormat, value } = attr;
if (valueFormat) { if (valueFormat) {
attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value;
} else if (DATE_TYPE.includes(component) && value) { } else if (DATE_TYPE.includes(component) && value) {
attr.value = dateUtil(attr.value) attr.value = dateUtil(attr.value);
} }
} }
export function handleInputNumberValue(component?: ComponentType, val?: any) { export function handleInputNumberValue(component?: ComponentType, val?: any) {
if (!component) return val if (!component) return val;
if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) { if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
return val && isNumber(val) ? `${val}` : val return val && isNumber(val) ? `${val}` : val;
} }
return val return val;
} }
/** /**
* *
*/ */
export const dateItemType = genType() export const dateItemType = genType();
export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'InputTextArea']

View File

@ -1,21 +1,21 @@
import type { ColEx } from '../types' import type { ColEx } from '../types';
import type { AdvanceState } from '../types/hooks' import type { AdvanceState } from '../types/hooks';
import { ComputedRef, getCurrentInstance, Ref } from 'vue' import type { ComputedRef, 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';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint' import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core';
const BASIC_COL_LEN = 24 const BASIC_COL_LEN = 24;
interface UseAdvancedContext { interface UseAdvancedContext {
advanceState: AdvanceState advanceState: AdvanceState;
emit: EmitType emit: EmitType;
getProps: ComputedRef<FormProps> getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>;
formModel: Recordable formModel: Recordable;
defaultValueRef: Ref<Recordable> defaultValueRef: Ref<Recordable>;
} }
export default function ({ export default function ({
@ -26,104 +26,102 @@ 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 => {
if (!advanceState.isAdvanced) { if (!advanceState.isAdvanced) {
return 0 return 0;
} }
// For some special cases, you need to manually specify additional blank lines // For some special cases, you need to manually specify additional blank lines
const emptySpan = unref(getProps).emptySpan || 0 const emptySpan = unref(getProps).emptySpan || 0;
if (isNumber(emptySpan)) { if (isNumber(emptySpan)) {
return emptySpan return emptySpan;
} }
if (isObject(emptySpan)) { if (isObject(emptySpan)) {
const { span = 0 } = emptySpan const { span = 0 } = emptySpan;
const screen = unref(screenRef) as string const screen = unref(screenRef) as string;
const screenSpan = (emptySpan as any)[screen.toLowerCase()] const screenSpan = (emptySpan as any)[screen.toLowerCase()];
return screenSpan || span || 0 return screenSpan || span || 0;
} }
return 0 return 0;
}) });
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30) const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30);
watch( watch(
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)], [() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
() => { () => {
const { showAdvancedButton } = unref(getProps) const { showAdvancedButton } = unref(getProps);
if (showAdvancedButton) { if (showAdvancedButton) {
debounceUpdateAdvanced() debounceUpdateAdvanced();
} }
}, },
{ immediate: true }, { immediate: true },
) );
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) { function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
const width = unref(realWidthRef) const width = unref(realWidthRef);
const mdWidth = const mdWidth =
parseInt(itemCol.md as string) || parseInt(itemCol.md as string) ||
parseInt(itemCol.xs as string) || parseInt(itemCol.xs as string) ||
parseInt(itemCol.sm as string) || parseInt(itemCol.sm as string) ||
(itemCol.span as number) || (itemCol.span as number) ||
BASIC_COL_LEN BASIC_COL_LEN;
const lgWidth = parseInt(itemCol.lg as string) || mdWidth const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
const xlWidth = parseInt(itemCol.xl as string) || lgWidth const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
if (width <= screenEnum.LG) { if (width <= screenEnum.LG) {
itemColSum += mdWidth itemColSum += mdWidth;
} else if (width < screenEnum.XL) { } else if (width < screenEnum.XL) {
itemColSum += lgWidth itemColSum += lgWidth;
} else if (width < screenEnum.XXL) { } else if (width < screenEnum.XXL) {
itemColSum += xlWidth itemColSum += xlWidth;
} else { } else {
itemColSum += xxlWidth itemColSum += xxlWidth;
} }
if (isLastAction) { if (isLastAction) {
advanceState.hideAdvanceBtn = false advanceState.hideAdvanceBtn = false;
if (itemColSum <= BASIC_COL_LEN * 2) { if (itemColSum <= BASIC_COL_LEN * 2) {
// When less than or equal to 2 lines, the collapse and expand buttons are not displayed // When less than or equal to 2 lines, the collapse and expand buttons are not displayed
advanceState.hideAdvanceBtn = true advanceState.hideAdvanceBtn = true;
advanceState.isAdvanced = true advanceState.isAdvanced = true;
} else if ( } else if (
itemColSum > BASIC_COL_LEN * 2 && itemColSum > BASIC_COL_LEN * 2 &&
itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3) itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
) { ) {
advanceState.hideAdvanceBtn = false advanceState.hideAdvanceBtn = false;
// More than 3 lines collapsed by default // More than 3 lines collapsed by default
} else if (!advanceState.isLoad) { } else if (!advanceState.isLoad) {
advanceState.isLoad = true advanceState.isLoad = true;
advanceState.isAdvanced = !advanceState.isAdvanced advanceState.isAdvanced = !advanceState.isAdvanced;
} }
return { isAdvanced: advanceState.isAdvanced, itemColSum } return { isAdvanced: advanceState.isAdvanced, itemColSum };
} }
if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) { if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) {
return { isAdvanced: advanceState.isAdvanced, itemColSum } return { isAdvanced: advanceState.isAdvanced, itemColSum };
} else { } else {
// The first line is always displayed // The first line is always displayed
return { isAdvanced: true, itemColSum } return { isAdvanced: true, itemColSum };
} }
} }
function updateAdvanced() { function updateAdvanced() {
let itemColSum = 0 let itemColSum = 0;
let realItemColSum = 0 let realItemColSum = 0;
const { baseColProps = {} } = unref(getProps) const { baseColProps = {} } = unref(getProps);
for (const schema of unref(getSchema)) { for (const schema of unref(getSchema)) {
const { show, colProps } = schema const { show, colProps } = schema;
let isShow = true let isShow = true;
if (isBoolean(show)) { if (isBoolean(show)) {
isShow = show isShow = show;
} }
if (isFunction(show)) { if (isFunction(show)) {
@ -135,36 +133,33 @@ export default function ({
...unref(defaultValueRef), ...unref(defaultValueRef),
...formModel, ...formModel,
}, },
}) });
} }
if (isShow && (colProps || baseColProps)) { if (isShow && (colProps || baseColProps)) {
const { itemColSum: sum, isAdvanced } = getAdvanced( const { itemColSum: sum, isAdvanced } = getAdvanced(
{ ...baseColProps, ...colProps }, { ...baseColProps, ...colProps },
itemColSum, itemColSum,
) );
itemColSum = sum || 0 itemColSum = sum || 0;
if (isAdvanced) { if (isAdvanced) {
realItemColSum = itemColSum realItemColSum = itemColSum;
} }
schema.isAdvanced = isAdvanced schema.isAdvanced = isAdvanced;
} }
} }
// 确保页面发送更新 advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
vm?.proxy?.$forceUpdate()
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) emit('advanced-change');
emit('advanced-change')
} }
function handleToggleAdvanced() { function handleToggleAdvanced() {
advanceState.isAdvanced = !advanceState.isAdvanced advanceState.isAdvanced = !advanceState.isAdvanced;
} }
return { handleToggleAdvanced } return { handleToggleAdvanced };
} }

View File

@ -1,96 +1,96 @@
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form' import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface' import type { NamePath } from 'ant-design-vue/lib/form/interface';
import type { DynamicProps } from '/#/utils' import type { DynamicProps } from '/#/utils';
import { ref, onUnmounted, unref, nextTick, watch } from 'vue' import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
import { isProdMode } from '/@/utils/env' import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log' import { error } from '/@/utils/log';
import { getDynamicProps } from '/@/utils' import { getDynamicProps } from '/@/utils';
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable> export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
type Props = Partial<DynamicProps<FormProps>> type Props = Partial<DynamicProps<FormProps>>;
export function useForm(props?: Props): UseFormReturnType { export function useForm(props?: Props): UseFormReturnType {
const formRef = ref<Nullable<FormActionType>>(null) const formRef = ref<Nullable<FormActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false) const loadedRef = ref<Nullable<boolean>>(false);
async function getForm() { async function getForm() {
const form = unref(formRef) const form = unref(formRef);
if (!form) { if (!form) {
error( error(
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!', 'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
) );
} }
await nextTick() await nextTick();
return form as FormActionType return form as FormActionType;
} }
function register(instance: FormActionType) { function register(instance: FormActionType) {
isProdMode() && isProdMode() &&
onUnmounted(() => { onUnmounted(() => {
formRef.value = null formRef.value = null;
loadedRef.value = null loadedRef.value = null;
}) });
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
formRef.value = instance formRef.value = instance;
loadedRef.value = true loadedRef.value = true;
watch( watch(
() => props, () => props,
() => { () => {
props && instance.setProps(getDynamicProps(props)) props && instance.setProps(getDynamicProps(props));
}, },
{ {
immediate: true, immediate: true,
deep: true, deep: true,
}, },
) );
} }
const methods: FormActionType = { const methods: FormActionType = {
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => { scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
const form = await getForm() const form = await getForm();
form.scrollToField(name, options) form.scrollToField(name, options);
}, },
setProps: async (formProps: Partial<FormProps>) => { setProps: async (formProps: Partial<FormProps>) => {
const form = await getForm() const form = await getForm();
form.setProps(formProps) form.setProps(formProps);
}, },
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => { updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm() const form = await getForm();
form.updateSchema(data) form.updateSchema(data);
}, },
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => { resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm() const form = await getForm();
form.resetSchema(data) form.resetSchema(data);
}, },
clearValidate: async (name?: string | string[]) => { clearValidate: async (name?: string | string[]) => {
const form = await getForm() const form = await getForm();
form.clearValidate(name) form.clearValidate(name);
}, },
resetFields: async () => { resetFields: async () => {
getForm().then(async (form) => { getForm().then(async (form) => {
await form.resetFields() await form.resetFields();
}) });
}, },
removeSchemaByField: async (field: string | string[]) => { removeSchemaByFiled: async (field: string | string[]) => {
unref(formRef)?.removeSchemaByField(field) unref(formRef)?.removeSchemaByFiled(field);
}, },
// TODO promisify // TODO promisify
getFieldsValue: <T>() => { getFieldsValue: <T>() => {
return unref(formRef)?.getFieldsValue() as T return unref(formRef)?.getFieldsValue() as T;
}, },
setFieldsValue: async <T>(values: T) => { setFieldsValue: async <T>(values: T) => {
const form = await getForm() const form = await getForm();
form.setFieldsValue<T>(values) form.setFieldsValue<T>(values);
}, },
appendSchemaByField: async ( appendSchemaByField: async (
@ -98,25 +98,25 @@ export function useForm(props?: Props): UseFormReturnType {
prefixField: string | undefined, prefixField: string | undefined,
first: boolean, first: boolean,
) => { ) => {
const form = await getForm() const form = await getForm();
form.appendSchemaByField(schema, prefixField, first) form.appendSchemaByField(schema, prefixField, first);
}, },
submit: async (): Promise<any> => { submit: async (): Promise<any> => {
const form = await getForm() const form = await getForm();
return form.submit() return form.submit();
}, },
validate: async (nameList?: NamePath[]): Promise<Recordable> => { validate: async (nameList?: NamePath[]): Promise<Recordable> => {
const form = await getForm() const form = await getForm();
return form.validate(nameList) return form.validate(nameList);
}, },
validateFields: async (nameList?: NamePath[]): Promise<Recordable> => { validateFields: async (nameList?: NamePath[]): Promise<Recordable> => {
const form = await getForm() const form = await getForm();
return form.validateFields(nameList) return form.validateFields(nameList);
}, },
} };
return [register, methods] return [register, methods];
} }

View File

@ -1,23 +1,23 @@
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, nextTick } from 'vue' import { unref, toRaw } from 'vue';
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is' import { isArray, isFunction, isObject, isString } from '/@/utils/is';
import { deepMerge } from '/@/utils' import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper' import { dateItemType, handleInputNumberValue } 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';
interface UseFormActionContext { interface UseFormActionContext {
emit: EmitType emit: EmitType;
getProps: ComputedRef<FormProps> getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>;
formModel: Recordable formModel: Recordable;
defaultValueRef: Ref<Recordable> defaultValueRef: Ref<Recordable>;
formElRef: Ref<FormActionType> formElRef: Ref<FormActionType>;
schemaRef: Ref<FormSchema[]> schemaRef: Ref<FormSchema[]>;
handleFormValues: Fn handleFormValues: Fn;
} }
export function useFormEvents({ export function useFormEvents({
emit, emit,
@ -30,22 +30,18 @@ export function useFormEvents({
handleFormValues, handleFormValues,
}: UseFormActionContext) { }: UseFormActionContext) {
async function resetFields(): Promise<void> { async function resetFields(): Promise<void> {
const { resetFunc, submitOnReset } = unref(getProps) const { resetFunc, submitOnReset } = unref(getProps);
resetFunc && isFunction(resetFunc) && (await resetFunc()) resetFunc && isFunction(resetFunc) && (await resetFunc());
const formEl = unref(formElRef) const formEl = unref(formElRef);
if (!formEl) return if (!formEl) return;
Object.keys(formModel).forEach((key) => { Object.keys(formModel).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key) formModel[key] = defaultValueRef.value[key];
const isInput = schema?.component && defaultValueComponents.includes(schema.component) });
const defaultValue = cloneDeep(defaultValueRef.value[key]) clearValidate();
formModel[key] = isInput ? defaultValue || '' : defaultValue emit('reset', toRaw(formModel));
}) submitOnReset && handleSubmit();
nextTick(() => clearValidate())
emit('reset', toRaw(formModel))
submitOnReset && handleSubmit()
} }
/** /**
@ -54,89 +50,70 @@ export function useFormEvents({
async function setFieldsValue(values: Recordable): Promise<void> { async function setFieldsValue(values: Recordable): Promise<void> {
const fields = unref(getSchema) const fields = unref(getSchema)
.map((item) => item.field) .map((item) => item.field)
.filter(Boolean) .filter(Boolean);
// key 支持 a.b.c 的嵌套写法 const validKeys: string[] = [];
const delimiter = '.'
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0)
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);
let value = values[key] let value = values[key];
const hasKey = Reflect.has(values, key) const hasKey = Reflect.has(values, key);
value = handleInputNumberValue(schema?.component, value) value = handleInputNumberValue(schema?.component, value);
// 0| '' is allow // 0| '' is allow
if (hasKey && fields.includes(key)) { if (hasKey && fields.includes(key)) {
// time type // time type
if (itemIsDateType(key)) { if (itemIsDateType(key)) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
const arr: any[] = [] const arr: any[] = [];
for (const ele of value) { for (const ele of value) {
arr.push(ele ? dateUtil(ele) : null) arr.push(ele ? dateUtil(ele) : null);
} }
formModel[key] = arr formModel[key] = arr;
} else { } else {
const { componentProps } = schema || {} const { componentProps } = schema || {};
let _props = componentProps as any let _props = componentProps as any;
if (typeof componentProps === 'function') { if (typeof componentProps === 'function') {
_props = _props({ formModel }) _props = _props({ formModel });
} }
formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null;
} }
} else { } else {
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((_) => {});
} }
/** /**
* @description: Delete based on field name * @description: Delete based on field name
*/ */
async function removeSchemaByField(fields: string | string[]): Promise<void> { async function removeSchemaByFiled(fields: string | string[]): Promise<void> {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema)) const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
if (!fields) { if (!fields) {
return return;
} }
let fieldList: string[] = isString(fields) ? [fields] : fields let fieldList: string[] = isString(fields) ? [fields] : fields;
if (isString(fields)) { if (isString(fields)) {
fieldList = [fields] fieldList = [fields];
} }
for (const field of fieldList) { for (const field of fieldList) {
_removeSchemaByFeild(field, schemaList) _removeSchemaByFiled(field, schemaList);
} }
schemaRef.value = schemaList schemaRef.value = schemaList;
} }
/** /**
* @description: Delete based on field name * @description: Delete based on field name
*/ */
function _removeSchemaByFeild(field: string, schemaList: FormSchema[]): void { function _removeSchemaByFiled(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) {
delete formModel[field] delete formModel[field];
schemaList.splice(index, 1) schemaList.splice(index, 1);
} }
} }
} }
@ -145,110 +122,83 @@ export function useFormEvents({
* @description: Insert after a certain field, if not insert the last * @description: Insert after a certain field, if not insert the last
*/ */
async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) { async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) {
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
} }
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) { async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = [] let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) { if (isObject(data)) {
updateData.push(data as FormSchema) updateData.push(data as FormSchema);
} }
if (isArray(data)) { if (isArray(data)) {
updateData = [...data] updateData = [...data];
} }
const hasField = updateData.every( const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field), (item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
) );
if (!hasField) { if (!hasField) {
error( error(
'All children of the form Schema array that need to be updated must contain the `field` field', 'All children of the form Schema array that need to be updated must contain the `field` field',
) );
return return;
} }
schemaRef.value = updateData as FormSchema[] schemaRef.value = updateData as FormSchema[];
} }
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) { async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = [] let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) { if (isObject(data)) {
updateData.push(data as FormSchema) updateData.push(data as FormSchema);
} }
if (isArray(data)) { if (isArray(data)) {
updateData = [...data] updateData = [...data];
} }
const hasField = updateData.every( const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field), (item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
) );
if (!hasField) { if (!hasField) {
error( error(
'All children of the form Schema array that need to be updated must contain the `field` field', 'All children of the form Schema array that need to be updated must contain the `field` field',
) );
return return;
} }
const schema: FormSchema[] = [] const schema: FormSchema[] = [];
updateData.forEach((item) => { updateData.forEach((item) => {
unref(getSchema).forEach((val) => { unref(getSchema).forEach((val) => {
if (val.field === item.field) { if (val.field === item.field) {
const newSchema = deepMerge(val, item) const newSchema = deepMerge(val, item);
schema.push(newSchema as FormSchema) schema.push(newSchema as FormSchema);
} else { } else {
schema.push(val) schema.push(val);
} }
}) });
}) });
_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 {};
return handleFormValues(toRaw(unref(formModel))) return handleFormValues(toRaw(unref(formModel)));
} }
/** /**
@ -256,44 +206,44 @@ export function useFormEvents({
*/ */
function itemIsDateType(key: string) { function itemIsDateType(key: string) {
return unref(getSchema).some((item) => { return unref(getSchema).some((item) => {
return item.field === key ? dateItemType.includes(item.component) : false return item.field === key ? dateItemType.includes(item.component) : false;
}) });
} }
async function validateFields(nameList?: NamePath[] | undefined) { async function validateFields(nameList?: NamePath[] | undefined) {
return unref(formElRef)?.validateFields(nameList) return unref(formElRef)?.validateFields(nameList);
} }
async function validate(nameList?: NamePath[] | undefined) { async function validate(nameList?: NamePath[] | undefined) {
return await unref(formElRef)?.validate(nameList) return await unref(formElRef)?.validate(nameList);
} }
async function clearValidate(name?: string | string[]) { async function clearValidate(name?: string | string[]) {
await unref(formElRef)?.clearValidate(name) await unref(formElRef)?.clearValidate(name);
} }
async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) { async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
await unref(formElRef)?.scrollToField(name, options) await unref(formElRef)?.scrollToField(name, options);
} }
/** /**
* @description: Form submission * @description: Form submission
*/ */
async function handleSubmit(e?: Event): Promise<void> { async function handleSubmit(e?: Event): Promise<void> {
e && e.preventDefault() e && e.preventDefault();
const { submitFunc } = unref(getProps) const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) { if (submitFunc && isFunction(submitFunc)) {
await submitFunc() await submitFunc();
return return;
} }
const formEl = unref(formElRef) const formEl = unref(formElRef);
if (!formEl) return if (!formEl) return;
try { try {
const values = await validate() const values = await validate();
const res = handleFormValues(values) const res = handleFormValues(values);
emit('submit', res) emit('submit', res);
} catch (error: any) { } catch (error: any) {
throw new Error(error) throw new Error(error);
} }
} }
@ -306,9 +256,9 @@ export function useFormEvents({
updateSchema, updateSchema,
resetSchema, resetSchema,
appendSchemaByField, appendSchemaByField,
removeSchemaByField, removeSchemaByFiled,
resetFields, resetFields,
setFieldsValue, setFieldsValue,
scrollToField, scrollToField,
} };
} }

View File

@ -1,53 +1,16 @@
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is' import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
import { dateUtil } from '/@/utils/dateUtil' 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 { cloneDeep, set } from 'lodash-es' import { set } from 'lodash-es';
interface UseFormValuesContext { interface UseFormValuesContext {
defaultValueRef: Ref<any> defaultValueRef: Ref<any>;
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>;
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,
@ -57,75 +20,68 @@ export function useFormValues({
// Processing form values // Processing form values
function handleFormValues(values: Recordable) { function handleFormValues(values: Recordable) {
if (!isObject(values)) { if (!isObject(values)) {
return {} return {};
} }
const res: Recordable = {} const res: Recordable = {};
for (const item of Object.entries(values)) { for (const item of Object.entries(values)) {
let [, value] = item let [, value] = item;
const [key] = item const [key] = item;
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) { if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
continue continue;
} }
const transformDateFunc = unref(getProps).transformDateFunc const transformDateFunc = unref(getProps).transformDateFunc;
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();
}
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
// 没有解构成功的,按原样赋值
set(res, key, value)
} }
set(res, key, value);
} }
return handleRangeTimeValue(res) return handleRangeTimeValue(res);
} }
/** /**
* @description: Processing time interval parameters * @description: Processing time interval parameters
*/ */
function handleRangeTimeValue(values: Recordable) { function handleRangeTimeValue(values: Recordable) {
const fieldMapToTime = unref(getProps).fieldMapToTime const fieldMapToTime = unref(getProps).fieldMapToTime;
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) { if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
return values return values;
} }
for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) { for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) {
if (!field || !startTimeKey || !endTimeKey || !values[field]) { if (!field || !startTimeKey || !endTimeKey || !values[field]) {
continue continue;
} }
const [startTime, endTime]: string[] = values[field] const [startTime, endTime]: string[] = values[field];
values[startTimeKey] = dateUtil(startTime).format(format) values[startTimeKey] = dateUtil(startTime).format(format);
values[endTimeKey] = dateUtil(endTime).format(format) values[endTimeKey] = dateUtil(endTime).format(format);
Reflect.deleteProperty(values, field) Reflect.deleteProperty(values, field);
} }
return values return values;
} }
function initDefault() { function initDefault() {
const schemas = unref(getSchema) const schemas = unref(getSchema);
const obj: Recordable = {} const obj: Recordable = {};
schemas.forEach((item) => { schemas.forEach((item) => {
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 = cloneDeep(obj) defaultValueRef.value = obj;
} }
return { handleFormValues, initDefault } return { handleFormValues, initDefault };
} }

View File

@ -1,42 +1,39 @@
import type { Ref } from 'vue' import type { Ref } from 'vue';
import { computed, unref } from 'vue' import type { FormProps, FormSchema } from '../types/form';
import type { FormProps, FormSchema } from '../types/form'
import { isNumber } from '/@/utils/is' import { computed, unref } from 'vue';
import { isNumber } from '/@/utils/is';
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) { export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
return computed(() => { return computed(() => {
const schemaItem = unref(schemaItemRef) const schemaItem = unref(schemaItemRef);
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {} const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
const { labelWidth, disabledLabelWidth } = schemaItem const { labelWidth, disabledLabelWidth } = schemaItem;
const { const {
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
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) { if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
labelCol.style = { labelCol.style = {
textAlign: 'left', textAlign: 'left',
} };
return { labelCol, wrapperCol } return { labelCol, wrapperCol };
} }
let width = labelWidth || globalLabelWidth let width = labelWidth || globalLabelWidth;
const col = { ...globalLabelCol, ...labelCol } const col = { ...globalLabelCol, ...labelCol };
const wrapCol = { ...globWrapperCol, ...wrapperCol } const wrapCol = { ...globWrapperCol, ...wrapperCol };
if (width) { if (width) {
width = isNumber(width) ? `${width}px` : width width = isNumber(width) ? `${width}px` : width;
} }
return { return {
labelCol: { style: { width }, ...col }, labelCol: { style: { width }, ...col },
wrapperCol: { wrapperCol: { style: { width: `calc(100% - ${width})` }, ...wrapCol },
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` }, };
...wrapCol, });
},
}
})
} }

View File

@ -1,10 +1,10 @@
import type { FieldMapToTime, FormSchema } from './types/form' import type { FieldMapToTime, FormSchema } from './types/form';
import type { CSSProperties, PropType } from 'vue' import type { CSSProperties, PropType } from 'vue';
import type { ColEx } from './types' import type { ColEx } from './types';
import type { TableActionType } from '/@/components/Table' import type { TableActionType } from '/@/components/Table';
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes' import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import type { RowProps } from 'ant-design-vue/lib/grid/Row' import type { RowProps } from 'ant-design-vue/lib/grid/Row';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
export const basicProps = { export const basicProps = {
model: { model: {
@ -40,7 +40,6 @@ 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,
@ -54,7 +53,7 @@ export const basicProps = {
transformDateFunc: { transformDateFunc: {
type: Function as PropType<Fn>, type: Function as PropType<Fn>,
default: (date: any) => { default: (date: any) => {
return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date return date._isAMomentObject ? date?.format('YYYY-MM-DD HH:mm:ss') : date;
}, },
}, },
rulesMessageJoinLabel: propTypes.bool.def(true), rulesMessageJoinLabel: propTypes.bool.def(true),
@ -100,4 +99,4 @@ export const basicProps = {
labelAlign: propTypes.string, labelAlign: propTypes.string,
rowProps: Object as PropType<RowProps>, rowProps: Object as PropType<RowProps>,
} };

View File

@ -1,223 +1,220 @@
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface' import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
import type { VNode } from 'vue' import type { VNode } from 'vue';
import type { ButtonProps as AntdButtonProps } from '/@/components/Button' import type { ButtonProps as AntdButtonProps } from '/@/components/Button';
import type { FormItem } from './formItem' import type { FormItem } from './formItem';
import type { ColEx, ComponentType } from './index' import type { ColEx, ComponentType } from './index';
import type { TableActionType } from '/@/components/Table/src/types/table' import type { TableActionType } from '/@/components/Table/src/types/table';
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue';
import type { RowProps } from 'ant-design-vue/lib/grid/Row' import type { RowProps } from 'ant-design-vue/lib/grid/Row';
export type FieldMapToTime = [string, [string, string], string?][] export type FieldMapToTime = [string, [string, string], string?][];
export type Rule = RuleObject & { export type Rule = RuleObject & {
trigger?: 'blur' | 'change' | ['change', 'blur'] trigger?: 'blur' | 'change' | ['change', 'blur'];
} };
export interface RenderCallbackParams { export interface RenderCallbackParams {
schema: FormSchema schema: FormSchema;
values: Recordable values: Recordable;
model: Recordable model: Recordable;
field: string field: string;
} }
export interface ButtonProps extends AntdButtonProps { export interface ButtonProps extends AntdButtonProps {
text?: string text?: string;
} }
export interface FormActionType { export interface FormActionType {
submit: () => Promise<void> submit: () => Promise<void>;
setFieldsValue: <T>(values: T) => Promise<void> setFieldsValue: <T>(values: T) => Promise<void>;
resetFields: () => Promise<void> resetFields: () => Promise<void>;
getFieldsValue: () => Recordable getFieldsValue: () => Recordable;
clearValidate: (name?: string | string[]) => Promise<void> clearValidate: (name?: string | string[]) => Promise<void>;
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>;
removeSchemaByField: (field: string | string[]) => Promise<void> removeSchemaByFiled: (field: string | string[]) => Promise<void>;
appendSchemaByField: ( appendSchemaByField: (
schema: FormSchema, schema: FormSchema,
prefixField: string | undefined, prefixField: string | undefined,
first?: boolean | undefined, first?: boolean | undefined,
) => Promise<void> ) => Promise<void>;
validateFields: (nameList?: NamePath[]) => Promise<any> validateFields: (nameList?: NamePath[]) => Promise<any>;
validate: (nameList?: NamePath[]) => Promise<any> validate: (nameList?: NamePath[]) => Promise<any>;
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void> scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
} }
export type RegisterFn = (formInstance: FormActionType) => void 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
wrapperCol?: Partial<ColEx> wrapperCol?: Partial<ColEx>;
// General row style // General row style
baseRowStyle?: CSSProperties baseRowStyle?: CSSProperties;
// General col configuration // General col configuration
baseColProps?: Partial<ColEx> baseColProps?: Partial<ColEx>;
// Form configuration rules // Form configuration rules
schemas?: FormSchema[] schemas?: FormSchema[];
// Function values used to merge into dynamic control form items // Function values used to merge into dynamic control form items
mergeDynamicData?: Recordable mergeDynamicData?: Recordable;
// Compact mode for search forms // Compact mode for search forms
compact?: boolean compact?: boolean;
// Blank line span // Blank line span
emptySpan?: number | Partial<ColEx> emptySpan?: number | Partial<ColEx>;
// Internal component size of the form // Internal component size of the form
size?: 'default' | 'small' | 'large' size?: 'default' | 'small' | 'large';
// Whether to disable // Whether to disable
disabled?: boolean disabled?: boolean;
// Time interval fields are mapped into multiple // Time interval fields are mapped into multiple
fieldMapToTime?: FieldMapToTime fieldMapToTime?: FieldMapToTime;
// Placeholder is set automatically // Placeholder is set automatically
autoSetPlaceHolder?: boolean autoSetPlaceHolder?: boolean;
// Auto submit on press enter on input // Auto submit on press enter on input
autoSubmitOnEnter?: boolean autoSubmitOnEnter?: boolean;
// Check whether the information is added to the label // Check whether the information is added to the label
rulesMessageJoinLabel?: boolean rulesMessageJoinLabel?: boolean;
// Whether to show collapse and expand buttons // Whether to show collapse and expand buttons
showAdvancedButton?: boolean showAdvancedButton?: boolean;
// Whether to focus on the first input box, only works when the first form item is input // Whether to focus on the first input box, only works when the first form item is input
autoFocusFirstItem?: boolean autoFocusFirstItem?: boolean;
// Automatically collapse over the specified number of rows // Automatically collapse over the specified number of rows
autoAdvancedLine?: number autoAdvancedLine?: number;
// Always show lines // Always show lines
alwaysShowLines?: number alwaysShowLines?: number;
// Whether to show the operation button // Whether to show the operation button
showActionButtonGroup?: boolean showActionButtonGroup?: boolean;
// Reset button configuration // Reset button configuration
resetButtonOptions?: Partial<ButtonProps> resetButtonOptions?: Partial<ButtonProps>;
// Confirm button configuration // Confirm button configuration
submitButtonOptions?: Partial<ButtonProps> submitButtonOptions?: Partial<ButtonProps>;
// Operation column configuration // Operation column configuration
actionColOptions?: Partial<ColEx> actionColOptions?: Partial<ColEx>;
// Show reset button // Show reset button
showResetButton?: boolean showResetButton?: boolean;
// Show confirmation button // Show confirmation button
showSubmitButton?: boolean showSubmitButton?: boolean;
resetFunc?: () => Promise<void> resetFunc?: () => Promise<void>;
submitFunc?: () => Promise<void> submitFunc?: () => Promise<void>;
transformDateFunc?: (date: any) => string transformDateFunc?: (date: any) => string;
colon?: boolean colon?: boolean;
} }
export interface FormSchema { export interface FormSchema {
// Field name // Field name
field: string field: string;
// Event name triggered by internal value change, default change // Event name triggered by internal value change, default change
changeEvent?: string changeEvent?: string;
// Variable name bound to v-model Default value // Variable name bound to v-model Default value
valueField?: string valueField?: string;
// Label name // Label name
label: string | VNode label: string | VNode;
// Auxiliary text // Auxiliary text
subLabel?: string subLabel?: string;
// Help text on the right side of the text // Help text on the right side of the text
helpMessage?: helpMessage?:
| string | string
| string[] | string[]
| ((renderCallbackParams: RenderCallbackParams) => string | string[]) | ((renderCallbackParams: RenderCallbackParams) => string | string[]);
// BaseHelp component props // BaseHelp component props
helpComponentProps?: Partial<HelpComponentProps> helpComponentProps?: Partial<HelpComponentProps>;
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid // Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
labelWidth?: string | number labelWidth?: string | number;
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself // Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
disabledLabelWidth?: boolean disabledLabelWidth?: boolean;
// render component // render component
component: ComponentType component: ComponentType;
// Component parameters // Component parameters
componentProps?: componentProps?:
| ((opt: { | ((opt: {
schema: FormSchema schema: FormSchema;
tableAction: TableActionType tableAction: TableActionType;
formActionType: FormActionType formActionType: FormActionType;
formModel: Recordable formModel: Recordable;
}) => Recordable) }) => Recordable)
| object | object;
// Required // Required
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean) required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
suffix?: string | number | ((values: RenderCallbackParams) => string | number) suffix?: string | number | ((values: RenderCallbackParams) => string | number);
// Validation rules // Validation rules
rules?: Rule[] rules?: Rule[];
// Check whether the information is added to the label // Check whether the information is added to the label
rulesMessageJoinLabel?: boolean rulesMessageJoinLabel?: boolean;
// Reference formModelItem // Reference formModelItem
itemProps?: Partial<FormItem> itemProps?: Partial<FormItem>;
// col configuration outside formModelItem // col configuration outside formModelItem
colProps?: Partial<ColEx> colProps?: Partial<ColEx>;
// 默认值 // 默认值
defaultValue?: any defaultValue?: any;
isAdvanced?: boolean isAdvanced?: boolean;
// Matching details components // Matching details components
span?: number span?: number;
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean) ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean) show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
// Render the content in the form-item tag // Render the content in the form-item tag
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
// Rendering col content requires outer wrapper form-item // Rendering col content requires outer wrapper form-item
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
renderComponentContent?: renderComponentContent?:
| ((renderCallbackParams: RenderCallbackParams) => any) | ((renderCallbackParams: RenderCallbackParams) => any)
| VNode | VNode
| VNode[] | VNode[]
| string | string;
// Custom slot, in from-item // Custom slot, in from-item
slot?: string slot?: string;
// Custom slot, similar to renderColContent // Custom slot, similar to renderColContent
colSlot?: string colSlot?: string;
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean) dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[] dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
} }
export interface HelpComponentProps { export interface HelpComponentProps {
maxWidth: string maxWidth: string;
// Whether to display the serial number // Whether to display the serial number
showIndex: boolean showIndex: boolean;
// Text list // Text list
text: any text: any;
// colour // colour
color: string color: string;
// font size // font size
fontSize: string fontSize: string;
icon: string icon: string;
absolute: boolean absolute: boolean;
// Positioning // Positioning
position: any position: any;
} }

View File

@ -1,83 +1,83 @@
type ColSpanType = number | string type ColSpanType = number | string;
export interface ColEx { export interface ColEx {
style?: any style?: any;
/** /**
* raster number of cells to occupy, 0 corresponds to display: none * raster number of cells to occupy, 0 corresponds to display: none
* @default none (0) * @default none (0)
* @type ColSpanType * @type ColSpanType
*/ */
span?: ColSpanType span?: ColSpanType;
/** /**
* raster order, used in flex layout mode * raster order, used in flex layout mode
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
order?: ColSpanType order?: ColSpanType;
/** /**
* the layout fill of flex * the layout fill of flex
* @default none * @default none
* @type ColSpanType * @type ColSpanType
*/ */
flex?: ColSpanType flex?: ColSpanType;
/** /**
* the number of cells to offset Col from the left * the number of cells to offset Col from the left
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
offset?: ColSpanType offset?: ColSpanType;
/** /**
* the number of cells that raster is moved to the right * the number of cells that raster is moved to the right
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
push?: ColSpanType push?: ColSpanType;
/** /**
* the number of cells that raster is moved to the left * the number of cells that raster is moved to the left
* @default 0 * @default 0
* @type ColSpanType * @type ColSpanType
*/ */
pull?: ColSpanType pull?: ColSpanType;
/** /**
* <576px and also default setting, could be a span value or an object containing above props * <576px and also default setting, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
/** /**
* 576px, could be a span value or an object containing above props * 576px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
/** /**
* 768px, could be a span value or an object containing above props * 768px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
/** /**
* 992px, could be a span value or an object containing above props * 992px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
/** /**
* 1200px, could be a span value or an object containing above props * 1200px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
/** /**
* 1600px, could be a span value or an object containing above props * 1600px, could be a span value or an object containing above props
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/ */
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
} }
export type ComponentType = export type ComponentType =
@ -91,7 +91,6 @@ export type ComponentType =
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'TreeSelect' | 'TreeSelect'
| 'ApiTree'
| 'ApiTreeSelect' | 'ApiTreeSelect'
| 'ApiRadioGroup' | 'ApiRadioGroup'
| 'RadioButtonGroup' | 'RadioButtonGroup'
@ -113,5 +112,4 @@ 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"
@open-change="handleOpenChange" @openChange="handleOpenChange"
:class="getMenuClass" :class="getMenuClass"
@click="handleMenuClick" @click="handleMenuClick"
:subMenuOpenDelay="0.2" :subMenuOpenDelay="0.2"
@ -18,21 +18,21 @@
</Menu> </Menu>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { MenuState } from './types' import type { MenuState } from './types';
import { computed, defineComponent, unref, reactive, watch, toRefs, ref } from 'vue' import { computed, defineComponent, unref, reactive, watch, toRefs, ref } from 'vue';
import { Menu } from 'ant-design-vue' import { Menu } from 'ant-design-vue';
import BasicSubMenuItem from './components/BasicSubMenuItem.vue' import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum' import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { useOpenKeys } from './useOpenKeys' import { useOpenKeys } from './useOpenKeys';
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router' import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router';
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
import { basicProps } from './props' import { basicProps } from './props';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting' import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { REDIRECT_NAME } from '/@/router/constant' import { REDIRECT_NAME } from '/@/router/constant';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import { getCurrentParentPath } from '/@/router/menus' import { getCurrentParentPath } from '/@/router/menus';
import { listenerRouteChange } from '/@/logics/mitt/routeChange' import { listenerRouteChange } from '/@/logics/mitt/routeChange';
import { getAllParentPath } from '/@/router/helper/menuHelper' import { getAllParentPath } from '/@/router/helper/menuHelper';
export default defineComponent({ export default defineComponent({
name: 'BasicMenu', name: 'BasicMenu',
@ -43,41 +43,41 @@
props: basicProps, props: basicProps,
emits: ['menuClick'], emits: ['menuClick'],
setup(props, { emit }) { setup(props, { emit }) {
const isClickGo = ref(false) const isClickGo = ref(false);
const currentActiveMenu = ref('') const currentActiveMenu = ref('');
const menuState = reactive<MenuState>({ const menuState = reactive<MenuState>({
defaultSelectedKeys: [], defaultSelectedKeys: [],
openKeys: [], openKeys: [],
selectedKeys: [], selectedKeys: [],
collapsedOpenKeys: [], collapsedOpenKeys: [],
}) });
const { prefixCls } = useDesign('basic-menu') const { prefixCls } = useDesign('basic-menu');
const { items, mode, accordion } = toRefs(props) const { items, mode, accordion } = toRefs(props);
const { getCollapsed, getTopMenuAlign, getSplit } = useMenuSetting() const { getCollapsed, getTopMenuAlign, getSplit } = useMenuSetting();
const { currentRoute } = useRouter() const { currentRoute } = useRouter();
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys( const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
menuState, menuState,
items, items,
mode as any, mode as any,
accordion, accordion,
) );
const getIsTopMenu = computed(() => { const getIsTopMenu = computed(() => {
const { type, mode } = props const { type, mode } = props;
return ( return (
(type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) || (type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) ||
(props.isHorizontal && unref(getSplit)) (props.isHorizontal && unref(getSplit))
) );
}) });
const getMenuClass = computed(() => { const getMenuClass = computed(() => {
const align = props.isHorizontal && unref(getSplit) ? 'start' : unref(getTopMenuAlign) const align = props.isHorizontal && unref(getSplit) ? 'start' : unref(getTopMenuAlign);
return [ return [
prefixCls, prefixCls,
`justify-${align}`, `justify-${align}`,
@ -85,66 +85,66 @@
[`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit), [`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
[`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu), [`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
}, },
] ];
}) });
const getInlineCollapseOptions = computed(() => { const getInlineCollapseOptions = computed(() => {
const isInline = props.mode === MenuModeEnum.INLINE const isInline = props.mode === MenuModeEnum.INLINE;
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {} const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
if (isInline) { if (isInline) {
inlineCollapseOptions.inlineCollapsed = props.mixSider ? false : unref(getCollapsed) inlineCollapseOptions.inlineCollapsed = props.mixSider ? false : unref(getCollapsed);
} }
return inlineCollapseOptions return inlineCollapseOptions;
}) });
listenerRouteChange((route) => { listenerRouteChange((route) => {
if (route.name === REDIRECT_NAME) return if (route.name === REDIRECT_NAME) return;
handleMenuChange(route) handleMenuChange(route);
currentActiveMenu.value = route.meta?.currentActiveMenu as string currentActiveMenu.value = route.meta?.currentActiveMenu as string;
if (unref(currentActiveMenu)) { if (unref(currentActiveMenu)) {
menuState.selectedKeys = [unref(currentActiveMenu)] menuState.selectedKeys = [unref(currentActiveMenu)];
setOpenKeys(unref(currentActiveMenu)) setOpenKeys(unref(currentActiveMenu));
} }
}) });
!props.mixSider && !props.mixSider &&
watch( watch(
() => props.items, () => props.items,
() => { () => {
handleMenuChange() handleMenuChange();
}, },
) );
async function handleMenuClick({ key }: { key: string; keyPath: string[] }) { async function handleMenuClick({ key }: { key: string; keyPath: string[] }) {
const { beforeClickFn } = props const { beforeClickFn } = props;
if (beforeClickFn && isFunction(beforeClickFn)) { if (beforeClickFn && isFunction(beforeClickFn)) {
const flag = await beforeClickFn(key) const flag = await beforeClickFn(key);
if (!flag) return if (!flag) return;
} }
emit('menuClick', key) emit('menuClick', key);
isClickGo.value = true isClickGo.value = true;
menuState.selectedKeys = [key] menuState.selectedKeys = [key];
} }
async function handleMenuChange(route?: RouteLocationNormalizedLoaded) { async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
if (unref(isClickGo)) { if (unref(isClickGo)) {
isClickGo.value = false isClickGo.value = false;
return return;
} }
const path = const path =
(route || unref(currentRoute)).meta?.currentActiveMenu || (route || unref(currentRoute)).meta?.currentActiveMenu ||
(route || unref(currentRoute)).path (route || unref(currentRoute)).path;
setOpenKeys(path) setOpenKeys(path);
if (unref(currentActiveMenu)) return if (unref(currentActiveMenu)) return;
if (props.isHorizontal && unref(getSplit)) { if (props.isHorizontal && unref(getSplit)) {
const parentPath = await getCurrentParentPath(path) const parentPath = await getCurrentParentPath(path);
menuState.selectedKeys = [parentPath] menuState.selectedKeys = [parentPath];
} else { } else {
const parentPaths = await getAllParentPath(props.items, path) const parentPaths = await getAllParentPath(props.items, path);
menuState.selectedKeys = parentPaths menuState.selectedKeys = parentPaths;
} }
} }
@ -155,9 +155,9 @@
handleOpenChange, handleOpenChange,
getOpenKeys, getOpenKeys,
...toRefs(menuState), ...toRefs(menuState),
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@import './index.less'; @import './index.less';

View File

@ -1,31 +1,27 @@
import { Modal } from 'ant-design-vue' import { Modal } from 'ant-design-vue';
import { defineComponent, toRefs, unref } from 'vue' import { defineComponent, toRefs, unref } from 'vue';
import { basicProps } from '../props' import { basicProps } from '../props';
import { useModalDragMove } from '../hooks/useModalDrag' import { useModalDragMove } from '../hooks/useModalDrag';
import { useAttrs } from '/@/hooks/core/useAttrs' import { useAttrs } from '/@/hooks/core/useAttrs';
import { extendSlots } from '/@/utils/helper/tsxHelper' import { extendSlots } from '/@/utils/helper/tsxHelper';
export default defineComponent({ export default defineComponent({
name: 'Modal', name: 'Modal',
inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
emits: ['cancel'], emits: ['cancel'],
setup(props, { slots, emit }) { setup(props, { slots }) {
const { visible, draggable, destroyOnClose } = toRefs(props) const { visible, draggable, destroyOnClose } = toRefs(props);
const attrs = useAttrs() const attrs = useAttrs();
useModalDragMove({ useModalDragMove({
visible, visible,
destroyOnClose, destroyOnClose,
draggable, draggable,
}) });
const onCancel = (e: Event) => {
emit('cancel', e)
}
return () => { return () => {
const propsData = { ...unref(attrs), ...props, onCancel } as Recordable const propsData = { ...unref(attrs), ...props } as Recordable;
return <Modal {...propsData}>{extendSlots(slots)}</Modal> return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
} };
}, },
}) });

View File

@ -26,6 +26,7 @@
&-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;
@ -110,19 +111,16 @@
.ant-modal-confirm .ant-modal-body { .ant-modal-confirm .ant-modal-body {
padding: 24px !important; padding: 24px !important;
} }
@media screen and (max-height: 600px) { @media screen and (max-height: 600px) {
.ant-modal { .ant-modal {
top: 60px; top: 60px;
} }
} }
@media screen and (max-height: 540px) { @media screen and (max-height: 540px) {
.ant-modal { .ant-modal {
top: 30px; top: 30px;
} }
} }
@media screen and (max-height: 480px) { @media screen and (max-height: 480px) {
.ant-modal { .ant-modal {
top: 10px; top: 10px;

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="getShowHeader" v-if="content || $slots.headerContent || title || getHeaderSlots.length"
> >
<template #default> <template #default>
<template v-if="content"> <template v-if="content">
@ -33,17 +33,17 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { CSSProperties, PropType, provide } from 'vue' import { CSSProperties, PropType, provide } from 'vue';
import { defineComponent, computed, watch, ref, unref } from 'vue' import { defineComponent, computed, watch, ref, unref } from 'vue';
import PageFooter from './PageFooter.vue' import PageFooter from './PageFooter.vue';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { omit } from 'lodash-es' import { omit } from 'lodash-es';
import { PageHeader } from 'ant-design-vue' import { PageHeader } from 'ant-design-vue';
import { useContentHeight } from '/@/hooks/web/useContentHeight' import { useContentHeight } from '/@/hooks/web/useContentHeight';
import { PageWrapperFixedHeightKey } from '..' import { PageWrapperFixedHeightKey } from '..';
export default defineComponent({ export default defineComponent({
name: 'PageWrapper', name: 'PageWrapper',
@ -64,30 +64,30 @@
upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0), upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
}, },
setup(props, { slots, attrs }) { setup(props, { slots, attrs }) {
const wrapperRef = ref(null) const wrapperRef = ref(null);
const headerRef = ref(null) const headerRef = ref(null);
const contentRef = ref(null) const contentRef = ref(null);
const footerRef = ref(null) const footerRef = ref(null);
const { prefixCls } = useDesign('page-wrapper') const { prefixCls } = useDesign('page-wrapper');
provide( provide(
PageWrapperFixedHeightKey, PageWrapperFixedHeightKey,
computed(() => props.fixedHeight), computed(() => props.fixedHeight),
) );
const getIsContentFullHeight = computed(() => { const getIsContentFullHeight = computed(() => {
return props.contentFullHeight return props.contentFullHeight;
}) });
const getUpwardSpace = computed(() => props.upwardSpace) const getUpwardSpace = computed(() => props.upwardSpace);
const { redoHeight, setCompensation, contentHeight } = useContentHeight( const { redoHeight, setCompensation, contentHeight } = useContentHeight(
getIsContentFullHeight, getIsContentFullHeight,
wrapperRef, wrapperRef,
[headerRef, footerRef], [headerRef, footerRef],
[contentRef], [contentRef],
getUpwardSpace, getUpwardSpace,
) );
setCompensation({ useLayoutFooter: true, elements: [footerRef] }) setCompensation({ useLayoutFooter: true, elements: [footerRef] });
const getClass = computed(() => { const getClass = computed(() => {
return [ return [
@ -96,54 +96,50 @@
[`${prefixCls}--dense`]: props.dense, [`${prefixCls}--dense`]: props.dense,
}, },
attrs.class ?? {}, attrs.class ?? {},
] ];
}) });
const getShowHeader = computed( const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
() => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
)
const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter)
const getHeaderSlots = computed(() => { const getHeaderSlots = computed(() => {
return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent')) return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
}) });
const getContentStyle = computed((): CSSProperties => { const getContentStyle = computed((): CSSProperties => {
const { contentFullHeight, contentStyle, fixedHeight } = props const { contentFullHeight, contentStyle, fixedHeight } = props;
if (!contentFullHeight) { if (!contentFullHeight) {
return { ...contentStyle } return { ...contentStyle };
} }
const height = `${unref(contentHeight)}px` const height = `${unref(contentHeight)}px`;
return { return {
...contentStyle, ...contentStyle,
minHeight: height, minHeight: height,
...(fixedHeight ? { height } : {}), ...(fixedHeight ? { height } : {}),
} };
}) });
const getContentClass = computed(() => { const getContentClass = computed(() => {
const { contentBackground, contentClass } = props const { contentBackground, contentClass } = props;
return [ return [
`${prefixCls}-content`, `${prefixCls}-content`,
contentClass, contentClass,
{ {
[`${prefixCls}-content-bg`]: contentBackground, [`${prefixCls}-content-bg`]: contentBackground,
}, },
] ];
}) });
watch( watch(
() => [getShowFooter.value], () => [getShowFooter.value],
() => { () => {
redoHeight() redoHeight();
}, },
{ {
flush: 'post', flush: 'post',
immediate: true, immediate: true,
}, },
) );
return { return {
getContentStyle, getContentStyle,
@ -154,13 +150,12 @@
getClass, getClass,
getHeaderSlots, getHeaderSlots,
prefixCls, prefixCls,
getShowHeader,
getShowFooter, getShowFooter,
omit, omit,
getContentClass, getContentClass,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-page-wrapper'; @prefix-cls: ~'@{namespace}-page-wrapper';

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,27 +34,24 @@
</span> </span>
<SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" /> <SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" />
</template> </template>
<template <template v-for="childrenItem in item.children || []" :key="childrenItem.path">
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>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue' import type { PropType } from 'vue';
import type { Menu } from '/@/router/types' import type { Menu } from '/@/router/types';
import { defineComponent, computed } from 'vue' import { defineComponent, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import Icon from '/@/components/Icon/index' import Icon from '/@/components/Icon/index';
import MenuItem from './components/MenuItem.vue' import MenuItem from './components/MenuItem.vue';
import SubMenu from './components/SubMenuItem.vue' import SubMenu from './components/SubMenuItem.vue';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent' import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export default defineComponent({ export default defineComponent({
name: 'SimpleSubMenu', name: 'SimpleSubMenu',
@ -75,22 +72,22 @@
theme: propTypes.oneOf(['dark', 'light']), theme: propTypes.oneOf(['dark', 'light']),
}, },
setup(props) { setup(props) {
const { t } = useI18n() const { t } = useI18n();
const { prefixCls } = useDesign('simple-menu') const { prefixCls } = useDesign('simple-menu');
const getShowMenu = computed(() => !props.item?.meta?.hideMenu) const getShowMenu = computed(() => !props.item?.meta?.hideMenu);
const getIcon = computed(() => props.item?.icon) const getIcon = computed(() => props.item?.icon);
const getI18nName = computed(() => t(props.item?.name)) const getI18nName = computed(() => t(props.item?.name));
const getShowSubTitle = computed(() => !props.collapse || !props.parent) const getShowSubTitle = computed(() => !props.collapse || !props.parent);
const getIsCollapseParent = computed(() => !!props.collapse && !!props.parent) const getIsCollapseParent = computed(() => !!props.collapse && !!props.parent);
const getLevelClass = computed(() => { const getLevelClass = computed(() => {
return [ return [
{ {
[`${prefixCls}__parent`]: props.parent, [`${prefixCls}__parent`]: props.parent,
[`${prefixCls}__children`]: !props.parent, [`${prefixCls}__children`]: !props.parent,
}, },
] ];
}) });
function menuHasChildren(menuTreeItem: Menu): boolean { function menuHasChildren(menuTreeItem: Menu): boolean {
return ( return (
@ -98,7 +95,7 @@
Reflect.has(menuTreeItem, 'children') && Reflect.has(menuTreeItem, 'children') &&
!!menuTreeItem.children && !!menuTreeItem.children &&
menuTreeItem.children.length > 0 menuTreeItem.children.length > 0
) );
} }
return { return {
@ -110,7 +107,7 @@
getShowSubTitle, getShowSubTitle,
getLevelClass, getLevelClass,
getIsCollapseParent, getIsCollapseParent,
} };
}, },
}) });
</script> </script>

View File

@ -21,7 +21,7 @@
:overlayClassName="`${prefixCls}-menu-popover`" :overlayClassName="`${prefixCls}-menu-popover`"
v-else v-else
:visible="getIsOpend" :visible="getIsOpend"
@visible-change="handleVisibleChange" @visibleChange="handleVisibleChange"
:overlayStyle="getOverlayStyle" :overlayStyle="getOverlayStyle"
:align="{ offset: [0, 0] }" :align="{ offset: [0, 0] }"
> >
@ -56,8 +56,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties, PropType } from 'vue' import type { CSSProperties, PropType } from 'vue';
import type { SubMenuProvider } from './types' import type { SubMenuProvider } from './types';
import { import {
defineComponent, defineComponent,
computed, computed,
@ -68,18 +68,18 @@
provide, provide,
onBeforeMount, onBeforeMount,
inject, inject,
} from 'vue' } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { useMenuItem } from './useMenu' import { useMenuItem } from './useMenu';
import { useSimpleRootMenuContext } from './useSimpleMenuContext' import { useSimpleRootMenuContext } from './useSimpleMenuContext';
import { CollapseTransition } from '/@/components/Transition' import { CollapseTransition } from '/@/components/Transition';
import Icon from '/@/components/Icon' import Icon from '/@/components/Icon';
import { Popover } from 'ant-design-vue' import { Popover } from 'ant-design-vue';
import { isBoolean, isObject } from '/@/utils/is' import { isBoolean, isObject } from '/@/utils/is';
import mitt from '/@/utils/mitt' import mitt from '/@/utils/mitt';
const DELAY = 200 const DELAY = 200;
export default defineComponent({ export default defineComponent({
name: 'SubMenu', name: 'SubMenu',
components: { components: {
@ -96,26 +96,27 @@
collapsedShowTitle: propTypes.bool, collapsedShowTitle: propTypes.bool,
}, },
setup(props) { setup(props) {
const instance = getCurrentInstance() const instance = getCurrentInstance();
const state = reactive({ const state = reactive({
active: false, active: false,
opened: false, opened: false,
}) });
const data = reactive({ const data = reactive({
timeout: null as TimeoutHandle | null, timeout: null as TimeoutHandle | null,
mouseInChild: false, mouseInChild: false,
isChild: false, isChild: false,
}) });
const { getParentSubMenu, getItemStyle, getParentMenu, getParentList } = useMenuItem(instance) const { getParentSubMenu, getItemStyle, getParentMenu, getParentList } =
useMenuItem(instance);
const { prefixCls } = useDesign('menu') const { prefixCls } = useDesign('menu');
const subMenuEmitter = mitt() const subMenuEmitter = mitt();
const { rootMenuEmitter } = useSimpleRootMenuContext() const { rootMenuEmitter } = useSimpleRootMenuContext();
const { const {
addSubMenu: parentAddSubmenu, addSubMenu: parentAddSubmenu,
@ -127,7 +128,7 @@
level, level,
props: rootProps, props: rootProps,
handleMouseleave: parentHandleMouseleave, handleMouseleave: parentHandleMouseleave,
} = inject<SubMenuProvider>(`subMenu:${getParentMenu.value?.uid}`)! } = inject<SubMenuProvider>(`subMenu:${getParentMenu.value?.uid}`)!;
const getClass = computed(() => { const getClass = computed(() => {
return [ return [
@ -139,29 +140,29 @@
[`${prefixCls}-submenu-has-parent-submenu`]: unref(getParentSubMenu), [`${prefixCls}-submenu-has-parent-submenu`]: unref(getParentSubMenu),
[`${prefixCls}-child-item-active`]: state.active, [`${prefixCls}-child-item-active`]: state.active,
}, },
] ];
}) });
const getAccordion = computed(() => rootProps.accordion) const getAccordion = computed(() => rootProps.accordion);
const getCollapse = computed(() => rootProps.collapse) const getCollapse = computed(() => rootProps.collapse);
const getTheme = computed(() => rootProps.theme) const getTheme = computed(() => rootProps.theme);
const getOverlayStyle = computed((): CSSProperties => { const getOverlayStyle = computed((): CSSProperties => {
return { return {
minWidth: '200px', minWidth: '200px',
} };
}) });
const getIsOpend = computed(() => { const getIsOpend = computed(() => {
const name = props.name const name = props.name;
if (unref(getCollapse)) { if (unref(getCollapse)) {
return parentGetOpenNames().includes(name) return parentGetOpenNames().includes(name);
} }
return state.opened return state.opened;
}) });
const getSubClass = computed(() => { const getSubClass = computed(() => {
const isActive = rootProps.activeSubMenuNames.includes(props.name) const isActive = rootProps.activeSubMenuNames.includes(props.name);
return [ return [
`${prefixCls}-submenu-title`, `${prefixCls}-submenu-title`,
{ {
@ -169,134 +170,134 @@
[`${prefixCls}-submenu-active-border`]: isActive && level === 0, [`${prefixCls}-submenu-active-border`]: isActive && level === 0,
[`${prefixCls}-submenu-collapse`]: unref(getCollapse) && level === 0, [`${prefixCls}-submenu-collapse`]: unref(getCollapse) && level === 0,
}, },
] ];
}) });
function getEvents(deep: boolean) { function getEvents(deep: boolean) {
if (!unref(getCollapse)) { if (!unref(getCollapse)) {
return {} return {};
} }
return { return {
onMouseenter: handleMouseenter, onMouseenter: handleMouseenter,
onMouseleave: () => handleMouseleave(deep), onMouseleave: () => handleMouseleave(deep),
} };
} }
function handleClick() { function handleClick() {
const { disabled } = props const { disabled } = props;
if (disabled || unref(getCollapse)) return if (disabled || unref(getCollapse)) return;
const opened = state.opened const opened = state.opened;
if (unref(getAccordion)) { if (unref(getAccordion)) {
const { uidList } = getParentList() const { uidList } = getParentList();
rootMenuEmitter.emit('on-update-opened', { rootMenuEmitter.emit('on-update-opened', {
opend: false, opend: false,
parent: instance?.parent, parent: instance?.parent,
uidList: uidList, uidList: uidList,
}) });
} else { } else {
rootMenuEmitter.emit('open-name-change', { rootMenuEmitter.emit('open-name-change', {
name: props.name, name: props.name,
opened: !opened, opened: !opened,
}) });
} }
state.opened = !opened state.opened = !opened;
} }
function handleMouseenter() { function handleMouseenter() {
const disabled = props.disabled const disabled = props.disabled;
if (disabled) return if (disabled) return;
subMenuEmitter.emit('submenu:mouse-enter-child') subMenuEmitter.emit('submenu:mouse-enter-child');
const index = parentGetOpenNames().findIndex((item) => item === props.name) const index = parentGetOpenNames().findIndex((item) => item === props.name);
sliceIndex(index) sliceIndex(index);
const isRoot = level === 0 && parentGetOpenNames().length === 2 const isRoot = level === 0 && parentGetOpenNames().length === 2;
if (isRoot) { if (isRoot) {
parentRemoveAll() parentRemoveAll();
} }
data.isChild = parentGetOpenNames().includes(props.name) data.isChild = parentGetOpenNames().includes(props.name);
clearTimeout(data.timeout!) clearTimeout(data.timeout!);
data.timeout = setTimeout(() => { data.timeout = setTimeout(() => {
parentAddSubmenu(props.name) parentAddSubmenu(props.name);
}, DELAY) }, DELAY);
} }
function handleMouseleave(deepDispatch = false) { function handleMouseleave(deepDispatch = false) {
const parentName = getParentMenu.value?.props.name const parentName = getParentMenu.value?.props.name;
if (!parentName) { if (!parentName) {
isRemoveAllPopup.value = true isRemoveAllPopup.value = true;
} }
if (parentGetOpenNames().slice(-1)[0] === props.name) { if (parentGetOpenNames().slice(-1)[0] === props.name) {
data.isChild = false data.isChild = false;
} }
subMenuEmitter.emit('submenu:mouse-leave-child') subMenuEmitter.emit('submenu:mouse-leave-child');
if (data.timeout) { if (data.timeout) {
clearTimeout(data.timeout!) clearTimeout(data.timeout!);
data.timeout = setTimeout(() => { data.timeout = setTimeout(() => {
if (isRemoveAllPopup.value) { if (isRemoveAllPopup.value) {
parentRemoveAll() parentRemoveAll();
} else if (!data.mouseInChild) { } else if (!data.mouseInChild) {
parentRemoveSubmenu(props.name) parentRemoveSubmenu(props.name);
} }
}, DELAY) }, DELAY);
} }
if (deepDispatch) { if (deepDispatch) {
if (getParentSubMenu.value) { if (getParentSubMenu.value) {
parentHandleMouseleave?.(true) parentHandleMouseleave?.(true);
} }
} }
} }
onBeforeMount(() => { onBeforeMount(() => {
subMenuEmitter.on('submenu:mouse-enter-child', () => { subMenuEmitter.on('submenu:mouse-enter-child', () => {
data.mouseInChild = true data.mouseInChild = true;
isRemoveAllPopup.value = false isRemoveAllPopup.value = false;
clearTimeout(data.timeout!) clearTimeout(data.timeout!);
}) });
subMenuEmitter.on('submenu:mouse-leave-child', () => { subMenuEmitter.on('submenu:mouse-leave-child', () => {
if (data.isChild) return if (data.isChild) return;
data.mouseInChild = false data.mouseInChild = false;
clearTimeout(data.timeout!) clearTimeout(data.timeout!);
}) });
rootMenuEmitter.on( rootMenuEmitter.on(
'on-update-opened', 'on-update-opened',
(data: boolean | (string | number)[] | Recordable) => { (data: boolean | (string | number)[] | Recordable) => {
if (unref(getCollapse)) return if (unref(getCollapse)) return;
if (isBoolean(data)) { if (isBoolean(data)) {
state.opened = data state.opened = data;
return return;
} }
if (isObject(data) && rootProps.accordion) { if (isObject(data) && rootProps.accordion) {
const { opend, parent, uidList } = data as Recordable const { opend, parent, uidList } = data as Recordable;
if (parent === instance?.parent) { if (parent === instance?.parent) {
state.opened = opend state.opened = opend;
} else if (!uidList.includes(instance?.uid)) { } else if (!uidList.includes(instance?.uid)) {
state.opened = false state.opened = false;
} }
return return;
} }
if (props.name && Array.isArray(data)) { if (props.name && Array.isArray(data)) {
state.opened = (data as (string | number)[]).includes(props.name) state.opened = (data as (string | number)[]).includes(props.name);
} }
}, },
) );
rootMenuEmitter.on('on-update-active-name:submenu', (data: number[]) => { rootMenuEmitter.on('on-update-active-name:submenu', (data: number[]) => {
if (instance?.uid) { if (instance?.uid) {
state.active = data.includes(instance?.uid) state.active = data.includes(instance?.uid);
} }
}) });
}) });
function handleVisibleChange(visible: boolean) { function handleVisibleChange(visible: boolean) {
state.opened = visible state.opened = visible;
} }
// provide // provide
@ -310,7 +311,7 @@
level: level + 1, level: level + 1,
handleMouseleave, handleMouseleave,
props: rootProps, props: rootProps,
}) });
return { return {
getClass, getClass,
@ -327,7 +328,7 @@
getSubClass, getSubClass,
...toRefs(state), ...toRefs(state),
...toRefs(data), ...toRefs(data),
} };
}, },
}) });
</script> </script>

View File

@ -13,8 +13,8 @@
bottom: 0; bottom: 0;
display: block; display: block;
width: 2px; width: 2px;
content: '';
background-color: @primary-color; background-color: @primary-color;
content: '';
} }
} }
@ -45,8 +45,8 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 18px; right: 18px;
transition: transform @transition-time @ease-in-out;
transform: translateY(-50%) rotate(-90deg); transform: translateY(-50%) rotate(-90deg);
transition: transform @transition-time @ease-in-out;
} }
} }
@ -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;
align-items: center;
justify-content: center; justify-content: center;
align-items: 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%;
content: '';
background-color: @primary-color; background-color: @primary-color;
content: '';
} }
} }
} }
@ -276,8 +276,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
content: '';
background-color: @primary-color; background-color: @primary-color;
content: '';
} }
.@{menu-prefix-cls}-submenu-collapse { .@{menu-prefix-cls}-submenu-collapse {

View File

@ -1,11 +1,10 @@
export { default as BasicTable } from './src/BasicTable.vue' 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' export { useTable } from './src/hooks/useTable';
export { useTable } from './src/hooks/useTable' export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form' export type { EditRecordRow } from './src/components/editable';
export type { EditRecordRow } from './src/components/editable'

View File

@ -1,7 +1,6 @@
<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"
@ -25,49 +24,48 @@
<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>
<script lang="ts"> <script lang="ts">
import type { BasicTableProps, TableActionType, SizeType, ColumnChangeParam } from './types/table' import type {
BasicTableProps,
TableActionType,
SizeType,
ColumnChangeParam,
} from './types/table';
import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect } from 'vue' import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect } from 'vue';
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 HeaderCell from './components/HeaderCell.vue' import expandIcon from './components/ExpandIcon';
import { InnerHandlers } from './types/table' import HeaderCell from './components/HeaderCell.vue';
import { InnerHandlers } from './types/table';
import { usePagination } from './hooks/usePagination' import { usePagination } from './hooks/usePagination';
import { useColumns } from './hooks/useColumns' import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource' import { useDataSource } from './hooks/useDataSource';
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' import { useTableExpand } from './hooks/useTableExpand';
import { useTableExpand } from './hooks/useTableExpand' import { createTableContext } from './hooks/useTableContext';
import { createTableContext } from './hooks/useTableContext' import { useTableFooter } from './hooks/useTableFooter';
import { useTableFooter } from './hooks/useTableFooter' import { useTableForm } from './hooks/useTableForm';
import { useTableForm } from './hooks/useTableForm' import { useDesign } from '/@/hooks/web/useDesign';
import { useDesign } from '/@/hooks/web/useDesign'
import { omit } from 'lodash-es' import { omit } from 'lodash-es';
import { basicProps } from './props' import { basicProps } from './props';
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
import { warn } from '/@/utils/log' import { warn } from '/@/utils/log';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -95,37 +93,36 @@
'columns-change', 'columns-change',
], ],
setup(props, { attrs, emit, slots, expose }) { setup(props, { attrs, emit, slots, expose }) {
const tableElRef = ref(null) const tableElRef = ref(null);
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');
const [registerForm, formActions] = useForm() const [registerForm, formActions] = useForm();
const getProps = computed(() => { const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
}) });
const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false) const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
watchEffect(() => { watchEffect(() => {
unref(isFixedHeightPage) && unref(isFixedHeightPage) &&
props.canResize && props.canResize &&
warn( warn(
"'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)", "'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)",
) );
}) });
const { getLoading, setLoading } = useLoading(getProps) const { getLoading, setLoading } = useLoading(getProps);
const { const {
getPaginationInfo, getPaginationInfo,
getPagination, getPagination,
setPagination, setPagination,
setShowPagination, setShowPagination,
getShowPagination, getShowPagination,
} = usePagination(getProps) } = usePagination(getProps);
const { const {
getRowSelection, getRowSelection,
@ -135,7 +132,7 @@
getSelectRowKeys, getSelectRowKeys,
deleteSelectRowByKey, deleteSelectRowByKey,
setSelectedRowKeys, setSelectedRowKeys,
} = useRowSelection(getProps, tableData, emit) } = useRowSelection(getProps, tableData, emit);
const { const {
handleTableChange: onTableChange, handleTableChange: onTableChange,
@ -163,14 +160,14 @@
clearSelectedRowKeys, clearSelectedRowKeys,
}, },
emit, emit,
) );
function handleTableChange(...args) { function handleTableChange(...args) {
onTableChange.call(undefined, ...args) onTableChange.call(undefined, ...args);
emit('change', ...args) emit('change', ...args);
// useTableonChange // useTableonChange
const { onChange } = unref(getProps) const { onChange } = unref(getProps);
onChange && isFunction(onChange) && onChange.call(undefined, ...args) onChange && isFunction(onChange) && onChange.call(undefined, ...args);
} }
const { const {
@ -180,7 +177,7 @@
setColumns, setColumns,
getColumnsRef, getColumnsRef,
getCacheColumns, getCacheColumns,
} = useColumns(getProps, getPaginationInfo) } = useColumns(getProps, getPaginationInfo);
const { getScrollRef, redoHeight } = useTableScroll( const { getScrollRef, redoHeight } = useTableScroll(
getProps, getProps,
@ -188,11 +185,7 @@
getColumnsRef, getColumnsRef,
getRowSelectionRef, getRowSelectionRef,
getDataSourceRef, getDataSourceRef,
wrapRef, );
formRef,
)
const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef)
const { customRow } = useCustomRow(getProps, { const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys, setSelectedRowKeys,
@ -200,41 +193,39 @@
clearSelectedRowKeys, clearSelectedRowKeys,
getAutoCreateKey, getAutoCreateKey,
emit, emit,
}) });
const { getRowClassName } = useTableStyle(getProps, prefixCls) const { getRowClassName } = useTableStyle(getProps, prefixCls);
const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand( const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit);
getProps,
tableData,
emit,
)
const handlers: InnerHandlers = { const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => { onColumnsChange: (data: ColumnChangeParam[]) => {
emit('columns-change', data) emit('columns-change', data);
// support useTable // support useTable
unref(getProps).onColumnsChange?.(data) unref(getProps).onColumnsChange?.(data);
}, },
} };
const { getHeaderProps } = useTableHeader(getProps, slots, handlers) const { getHeaderProps } = useTableHeader(getProps, slots, handlers);
const { getFooterProps } = useTableFooter( const { getFooterProps } = useTableFooter(
getProps, getProps,
getScrollRef, getScrollRef,
tableElRef, tableElRef,
getDataSourceRef, getDataSourceRef,
) );
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } =
useTableForm(getProps, slots, fetch, getLoading) useTableForm(getProps, slots, fetch, getLoading);
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),
@ -247,17 +238,17 @@
dataSource, dataSource,
footer: unref(getFooterProps), footer: unref(getFooterProps),
...unref(getExpandOption), ...unref(getExpandOption),
};
if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
} }
// if (slots.expandedRowRender) {
// propsData = omit(propsData, 'scroll');
// }
propsData = omit(propsData, ['class', 'onChange']) propsData = omit(propsData, ['class', 'onChange']);
return propsData return propsData;
}) });
const getWrapperClass = computed(() => { const getWrapperClass = computed(() => {
const values = unref(getBindValues) const values = unref(getBindValues);
return [ return [
prefixCls, prefixCls,
attrs.class, attrs.class,
@ -265,19 +256,19 @@
[`${prefixCls}-form-container`]: values.useSearchForm, [`${prefixCls}-form-container`]: values.useSearchForm,
[`${prefixCls}--inset`]: values.inset, [`${prefixCls}--inset`]: values.inset,
}, },
] ];
}) });
const getEmptyDataIsShowTable = computed(() => { const getEmptyDataIsShowTable = computed(() => {
const { emptyDataIsShowTable, useSearchForm } = unref(getProps) const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
if (emptyDataIsShowTable || !useSearchForm) { if (emptyDataIsShowTable || !useSearchForm) {
return true return true;
} }
return !!unref(getDataSourceRef).length return !!unref(getDataSourceRef).length;
}) });
function setProps(props: Partial<BasicTableProps>) { function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props } innerPropsRef.value = { ...unref(innerPropsRef), ...props };
} }
const tableAction: TableActionType = { const tableAction: TableActionType = {
@ -309,21 +300,18 @@
getShowPagination, getShowPagination,
setCacheColumnsByField, setCacheColumnsByField,
expandAll, expandAll,
expandRows,
collapseAll, collapseAll,
scrollTo,
getSize: () => { getSize: () => {
return unref(getBindValues).size as SizeType return unref(getBindValues).size as SizeType;
}, },
} };
createTableContext({ ...tableAction, wrapRef, getBindValues }) createTableContext({ ...tableAction, wrapRef, getBindValues });
expose(tableAction) expose(tableAction);
emit('register', tableAction, formActions) emit('register', tableAction, formActions);
return { return {
formRef,
tableElRef, tableElRef,
getBindValues, getBindValues,
getLoading, getLoading,
@ -340,9 +328,9 @@
getFormSlotKeys, getFormSlotKeys,
getWrapperClass, getWrapperClass,
columns: getViewColumns, columns: getViewColumns,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@border-color: #cecece4d; @border-color: #cecece4d;
@ -358,7 +346,6 @@
.@{prefix-cls} { .@{prefix-cls} {
max-width: 100%; max-width: 100%;
height: 100%;
&-row__striped { &-row__striped {
td { td {
@ -370,7 +357,6 @@
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

@ -1,4 +1,4 @@
import type { Component } from 'vue' import type { Component } from 'vue';
import { import {
Input, Input,
Select, Select,
@ -7,34 +7,28 @@ import {
Switch, Switch,
DatePicker, DatePicker,
TimePicker, TimePicker,
AutoComplete, } from 'ant-design-vue';
Radio, import type { ComponentType } from './types/componentType';
} from 'ant-design-vue' import { ApiSelect, ApiTreeSelect } from '/@/components/Form';
import type { ComponentType } from './types/componentType'
import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form'
const componentMap = new Map<ComponentType, Component>() const componentMap = new Map<ComponentType, Component>();
componentMap.set('Input', Input) 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);
} }
export function del(compName: ComponentType) { export function del(compName: ComponentType) {
componentMap.delete(compName) componentMap.delete(compName);
} }
export { componentMap } export { componentMap };

View File

@ -0,0 +1,23 @@
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

@ -6,12 +6,12 @@
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" /> <BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue' import type { PropType } from 'vue';
import type { BasicColumn } from '../types/table' import type { BasicColumn } from '../types/table';
import { defineComponent, computed } from 'vue' import { defineComponent, computed } from 'vue';
import BasicHelp from '/@/components/Basic/src/BasicHelp.vue' import BasicHelp from '/@/components/Basic/src/BasicHelp.vue';
import EditTableHeaderCell from './EditTableHeaderIcon.vue' import EditTableHeaderCell from './EditTableHeaderIcon.vue';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({ export default defineComponent({
name: 'TableHeaderCell', name: 'TableHeaderCell',
@ -26,15 +26,15 @@
}, },
}, },
setup(props) { setup(props) {
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 || props.column?.title) const getTitle = computed(() => props.column?.customTitle);
const getHelpMessage = computed(() => props.column?.helpMessage) const getHelpMessage = computed(() => props.column?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage } return { prefixCls, getIsEdit, getTitle, getHelpMessage };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header-cell'; @prefix-cls: ~'@{namespace}-basic-table-header-cell';

View File

@ -31,19 +31,19 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, computed, toRaw, unref } from 'vue' import { defineComponent, PropType, computed, toRaw, unref } from 'vue';
import { MoreOutlined } from '@ant-design/icons-vue' import { MoreOutlined } from '@ant-design/icons-vue';
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue' import { Divider, Tooltip, TooltipProps } from 'ant-design-vue';
import Icon from '/@/components/Icon/index' import Icon from '/@/components/Icon/index';
import { ActionItem, TableActionType } from '/@/components/Table' import { ActionItem, TableActionType } from '/@/components/Table';
import { PopConfirmButton } from '/@/components/Button' import { PopConfirmButton } from '/@/components/Button';
import { Dropdown } from '/@/components/Dropdown' import { Dropdown } from '/@/components/Dropdown';
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../hooks/useTableContext' import { useTableContext } from '../hooks/useTableContext';
import { usePermission } from '/@/hooks/web/usePermission' import { usePermission } from '/@/hooks/web/usePermission';
import { isBoolean, isFunction, isString } from '/@/utils/is' import { isBoolean, isFunction, isString } from '/@/utils/is';
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes';
import { ACTION_COLUMN_FLAG } from '../const' import { ACTION_COLUMN_FLAG } from '../const';
export default defineComponent({ export default defineComponent({
name: 'TableAction', name: 'TableAction',
@ -62,34 +62,34 @@
stopButtonPropagation: propTypes.bool.def(false), stopButtonPropagation: propTypes.bool.def(false),
}, },
setup(props) { setup(props) {
const { prefixCls } = useDesign('basic-table-action') const { prefixCls } = useDesign('basic-table-action');
let table: Partial<TableActionType> = {} let table: Partial<TableActionType> = {};
if (!props.outside) { if (!props.outside) {
table = useTableContext() table = useTableContext();
} }
const { hasPermission } = usePermission() const { hasPermission } = usePermission();
function isIfShow(action: ActionItem): boolean { function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow const ifShow = action.ifShow;
let isIfShow = true let isIfShow = true;
if (isBoolean(ifShow)) { if (isBoolean(ifShow)) {
isIfShow = ifShow isIfShow = ifShow;
} }
if (isFunction(ifShow)) { if (isFunction(ifShow)) {
isIfShow = ifShow(action) isIfShow = ifShow(action);
} }
return isIfShow return isIfShow;
} }
const getActions = computed(() => { const getActions = computed(() => {
return (toRaw(props.actions) || []) return (toRaw(props.actions) || [])
.filter((action) => { .filter((action) => {
return hasPermission(action.auth) && isIfShow(action) return hasPermission(action.auth) && isIfShow(action);
}) })
.map((action) => { .map((action) => {
const { popConfirm } = action const { popConfirm } = action;
return { return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body, getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
type: 'link', type: 'link',
@ -99,53 +99,54 @@
onConfirm: popConfirm?.confirm, onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel, onCancel: popConfirm?.cancel,
enable: !!popConfirm, enable: !!popConfirm,
} };
}) });
}) });
const getDropdownList = computed((): any[] => { const getDropdownList = computed((): any[] => {
const list = (toRaw(props.dropDownActions) || []).filter((action) => { return (toRaw(props.dropDownActions) || [])
return hasPermission(action.auth) && isIfShow(action) .filter((action) => {
}) return hasPermission(action.auth) && isIfShow(action);
return list.map((action, index) => { })
const { label, popConfirm } = action .map((action, index) => {
return { const { label, popConfirm } = action;
...action, return {
...popConfirm, ...action,
onConfirm: popConfirm?.confirm, ...popConfirm,
onCancel: popConfirm?.cancel, onConfirm: popConfirm?.confirm,
text: label, onCancel: popConfirm?.cancel,
divider: index < list.length - 1 ? props.divider : false, text: label,
} divider: index < props.dropDownActions.length - 1 ? props.divider : false,
}) };
}) });
});
const getAlign = computed(() => { const getAlign = computed(() => {
const columns = (table as TableActionType)?.getColumns?.() || [] const columns = (table as TableActionType)?.getColumns?.() || [];
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG) const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
return actionColumn?.align ?? 'left' return actionColumn?.align ?? 'left';
}) });
function getTooltip(data: string | TooltipProps): TooltipProps { function getTooltip(data: string | TooltipProps): TooltipProps {
return { return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body, getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
placement: 'bottom', placement: 'bottom',
...(isString(data) ? { title: data } : data), ...(isString(data) ? { title: data } : data),
} };
} }
function onCellClick(e: MouseEvent) { function onCellClick(e: MouseEvent) {
if (!props.stopButtonPropagation) return if (!props.stopButtonPropagation) return;
const path = e.composedPath() as HTMLElement[] const path = e.composedPath() as HTMLElement[];
const isInButton = path.find((ele) => { const isInButton = path.find((ele) => {
return ele.tagName?.toUpperCase() === 'BUTTON' return ele.tagName?.toUpperCase() === 'BUTTON';
}) });
isInButton && e.stopPropagation() isInButton && e.stopPropagation();
} }
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip } return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-action'; @prefix-cls: ~'@{namespace}-basic-table-action';

View File

@ -1,26 +1,62 @@
<script lang="tsx"> <template>
import type { CSSProperties, PropType } from 'vue' <div :class="prefixCls">
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue' <div
import type { BasicColumn } from '../../types/table' v-show="!isEdit"
import type { EditRecordRow } from './index' :class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }"
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue' @click="handleEdit"
import { CellComponent } from './CellComponent' >
<div class="cell-content" :title="column.ellipsis ? getValues ?? '' : ''">
{{ getValues ? getValues : '&nbsp;' }}
</div>
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
</div>
import { useDesign } from '/@/hooks/web/useDesign' <a-spin v-if="isEdit" :spinning="spinning">
import { useTableContext } from '../../hooks/useTableContext' <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 { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue';
import type { BasicColumn } from '../../types/table';
import type { EditRecordRow } from './index';
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue';
import { CellComponent } from './CellComponent';
import clickOutside from '/@/directives/clickOutside' import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../../hooks/useTableContext';
import { propTypes } from '/@/utils/propTypes' import clickOutside from '/@/directives/clickOutside';
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'
import { createPlaceholderMessage } from './helper' import { propTypes } from '/@/utils/propTypes';
import { pick, set } from 'lodash-es' import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is';
import { treeToList } from '/@/utils/helper/treeHelper' import { createPlaceholderMessage } from './helper';
import { Spin } from 'ant-design-vue' import { omit, pick, set } from 'lodash-es';
import { treeToList } from '/@/utils/helper/treeHelper';
import { Spin } from 'ant-design-vue';
export default defineComponent({ export default defineComponent({
name: 'EditableCell', name: 'EditableCell',
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, Spin }, components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin },
directives: { directives: {
clickOutside, clickOutside,
}, },
@ -39,293 +75,267 @@
index: propTypes.number, index: propTypes.number,
}, },
setup(props) { setup(props) {
const table = useTableContext() const table = useTableContext();
const isEdit = ref(false) const isEdit = ref(false);
const elRef = ref() const elRef = ref();
const ruleVisible = ref(false) const ruleVisible = ref(false);
const ruleMessage = ref('') const ruleMessage = ref('');
const optionsRef = ref<LabelValueOptions>([]) const optionsRef = ref<LabelValueOptions>([]);
const currentValueRef = ref<any>(props.value) const currentValueRef = ref<any>(props.value);
const defaultValueRef = ref<any>(props.value) const defaultValueRef = ref<any>(props.value);
const spinning = ref<boolean>(false) const spinning = ref<boolean>(false);
const { prefixCls } = useDesign('editable-cell') const { prefixCls } = useDesign('editable-cell');
const getComponent = computed(() => props.column?.editComponent || 'Input') const getComponent = computed(() => props.column?.editComponent || 'Input');
const getRule = computed(() => props.column?.editRule) const getRule = computed(() => props.column?.editRule);
const getRuleVisible = computed(() => { const getRuleVisible = computed(() => {
return unref(ruleMessage) && unref(ruleVisible) return unref(ruleMessage) && unref(ruleVisible);
}) });
const getIsCheckComp = computed(() => { const getIsCheckComp = computed(() => {
const component = unref(getComponent) const component = unref(getComponent);
return ['Checkbox', 'Switch'].includes(component) return ['Checkbox', 'Switch'].includes(component);
}) });
const getComponentProps = computed(() => { const getComponentProps = computed(() => {
const isCheckValue = unref(getIsCheckComp) const compProps = props.column?.editComponentProps ?? {};
const component = unref(getComponent);
const valueField = isCheckValue ? 'checked' : 'value' const apiSelectProps: Recordable = {};
const val = unref(currentValueRef)
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') { if (component === 'ApiSelect') {
apiSelectProps.cache = true apiSelectProps.cache = true;
} }
upEditDynamicDisabled(record, column, value)
const isCheckValue = unref(getIsCheckComp);
const valueField = isCheckValue ? 'checked' : 'value';
const val = unref(currentValueRef);
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
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,
...compProps, ...omit(compProps, 'onChange'),
[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 { editValueMap } = props.column
const value = unref(currentValueRef) const getValues = computed(() => {
const { editComponentProps, editValueMap } = props.column;
const value = unref(currentValueRef);
if (editValueMap && isFunction(editValueMap)) { if (editValueMap && isFunction(editValueMap)) {
return editValueMap(value) return editValueMap(value);
} }
const component = unref(getComponent) const component = unref(getComponent);
if (!component.includes('Select') && !component.includes('Radio')) { if (!component.includes('Select')) {
return value return value;
} }
const options: LabelValueOptions = const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
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;
}) });
const getWrapperStyle = computed((): CSSProperties => { const getWrapperStyle = computed((): CSSProperties => {
if (unref(getIsCheckComp) || unref(getRowEditable)) { if (unref(getIsCheckComp) || unref(getRowEditable)) {
return {} return {};
} }
return { return {
width: 'calc(100% - 48px)', width: 'calc(100% - 48px)',
} };
}) });
const getWrapperClass = computed(() => { const getWrapperClass = computed(() => {
const { align = 'center' } = props.column const { align = 'center' } = props.column;
return `edit-cell-align-${align}` return `edit-cell-align-${align}`;
}) });
const getRowEditable = computed(() => { const getRowEditable = computed(() => {
const { editable } = props.record || {} const { editable } = props.record || {};
return !!editable return !!editable;
}) });
watchEffect(() => { watchEffect(() => {
defaultValueRef.value = props.value defaultValueRef.value = props.value;
currentValueRef.value = props.value currentValueRef.value = props.value;
}) });
watchEffect(() => { watchEffect(() => {
const { editable } = props.column const { editable } = props.column;
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) { if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
isEdit.value = !!editable || unref(getRowEditable) isEdit.value = !!editable || unref(getRowEditable);
} }
}) });
function handleEdit() { function handleEdit() {
if (unref(getRowEditable) || unref(props.column?.editRow)) return if (unref(getRowEditable) || unref(props.column?.editRow)) return;
ruleMessage.value = '' ruleMessage.value = '';
isEdit.value = true isEdit.value = true;
nextTick(() => { nextTick(() => {
const el = unref(elRef) const el = unref(elRef);
el?.focus?.() el?.focus?.();
}) });
} }
async function handleChange(e: any) { async function handleChange(e: any) {
const component = unref(getComponent) const component = unref(getComponent);
if (!e) { if (!e) {
currentValueRef.value = e currentValueRef.value = e;
} else if (component === 'Checkbox') {
currentValueRef.value = (e as ChangeEvent).target.checked
} else if (component === 'Switch') {
currentValueRef.value = e
} else if (e?.target && Reflect.has(e.target, 'value')) { } else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value currentValueRef.value = (e as ChangeEvent).target.value;
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) { } else if (component === 'Checkbox') {
currentValueRef.value = e currentValueRef.value = (e as ChangeEvent).target.checked;
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
currentValueRef.value = e;
} }
const onChange = unref(getComponentProps)?.onChange const onChange = props.column?.editComponentProps?.onChange;
if (onChange && isFunction(onChange)) onChange(...arguments) if (onChange && isFunction(onChange)) onChange(...arguments);
table.emit?.('edit-change', { table.emit?.('edit-change', {
column: props.column, column: props.column,
value: unref(currentValueRef), value: unref(currentValueRef),
record: toRaw(props.record), record: toRaw(props.record),
}) });
handleSubmiRule() handleSubmiRule();
} }
async function handleSubmiRule() { async function handleSubmiRule() {
const { column, record } = props const { column, record } = props;
const { editRule } = column const { editRule } = column;
const currentValue = unref(currentValueRef) const currentValue = unref(currentValueRef);
if (editRule) { if (editRule) {
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) { if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
ruleVisible.value = true ruleVisible.value = true;
const component = unref(getComponent) const component = unref(getComponent);
ruleMessage.value = createPlaceholderMessage(component) ruleMessage.value = createPlaceholderMessage(component);
return false return false;
} }
if (isFunction(editRule)) { if (isFunction(editRule)) {
const res = await editRule(currentValue, record as Recordable) const res = await editRule(currentValue, record as Recordable);
if (!!res) { if (!!res) {
ruleMessage.value = res ruleMessage.value = res;
ruleVisible.value = true ruleVisible.value = true;
return false return false;
} else { } else {
ruleMessage.value = '' ruleMessage.value = '';
return true return true;
} }
} }
} }
ruleMessage.value = '' ruleMessage.value = '';
return true return true;
} }
async function handleSubmit(needEmit = true, valid = true) { async function handleSubmit(needEmit = true, valid = true) {
if (valid) { if (valid) {
const isPass = await handleSubmiRule() const isPass = await handleSubmiRule();
if (!isPass) return false if (!isPass) return false;
} }
const { column, index, record } = props const { column, index, record } = props;
if (!record) return false if (!record) return false;
const { key, dataIndex } = column const { key, dataIndex } = column;
const value = unref(currentValueRef) const value = unref(currentValueRef);
if (!key && !dataIndex) return if (!key && !dataIndex) return;
const dataKey = (dataIndex || key) as string const dataKey = (dataIndex || key) as string;
if (!record.editable) { if (!record.editable) {
const { getBindValues } = table const { getBindValues } = table;
const { beforeEditSubmit, columns } = unref(getBindValues) const { beforeEditSubmit, columns } = unref(getBindValues);
if (beforeEditSubmit && isFunction(beforeEditSubmit)) { if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
spinning.value = true spinning.value = true;
const keys: string[] = columns const keys: string[] = columns
.map((_column) => _column.dataIndex) .map((_column) => _column.dataIndex)
.filter((field) => !!field) as string[] .filter((field) => !!field) as string[];
let result: any = true let result: any = true;
try { try {
result = await beforeEditSubmit({ result = await beforeEditSubmit({
record: pick(record, keys), record: pick(record, keys),
index, index,
key: dataKey as string, key: key as string,
value, value,
}) });
} catch (e) { } catch (e) {
result = false result = false;
} finally { } finally {
spinning.value = false spinning.value = false;
} }
if (result === false) { if (result === false) {
return return;
} }
} }
} }
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: dataKey, value }) needEmit && table.emit?.('edit-end', { record, index, key, value });
isEdit.value = false isEdit.value = false;
} }
async function handleEnter() { async function handleEnter() {
if (props.column?.editRow) { if (props.column?.editRow) {
return return;
} }
handleSubmit() handleSubmit();
} }
function handleSubmitClick() { function handleSubmitClick() {
handleSubmit() handleSubmit();
} }
function handleCancel() { function handleCancel() {
isEdit.value = false isEdit.value = false;
currentValueRef.value = defaultValueRef.value currentValueRef.value = defaultValueRef.value;
const { column, index, record } = props const { column, index, record } = props;
const { key, dataIndex } = column const { key, dataIndex } = column;
table.emit?.('edit-cancel', { table.emit?.('edit-cancel', {
record, record,
index, index,
key: dataIndex || key, key: dataIndex || key,
value: unref(currentValueRef), value: unref(currentValueRef),
}) });
} }
function onClickOutside() { function onClickOutside() {
if (props.column?.editable || unref(getRowEditable)) { if (props.column?.editable || unref(getRowEditable)) {
return return;
} }
const component = unref(getComponent) const component = unref(getComponent);
if (component.includes('Input')) { if (component.includes('Input')) {
handleCancel() handleCancel();
} }
} }
// only ApiSelect or TreeSelect // only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) { function handleOptionsChange(options: LabelValueOptions) {
const { replaceFields } = unref(getComponentProps) const { replaceFields } = props.column?.editComponentProps ?? {};
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 || {};
let listOptions: Recordable[] = treeToList(options, { children }) let listOptions: Recordable[] = treeToList(options, { children });
listOptions = listOptions.map((item) => { listOptions = listOptions.map((item) => {
return { return {
label: item[title], label: item[title],
value: item[value], value: item[value],
} };
}) });
optionsRef.value = listOptions as LabelValueOptions optionsRef.value = listOptions as LabelValueOptions;
} else { } else {
optionsRef.value = options optionsRef.value = options;
} }
} }
@ -345,7 +355,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 as any] = currentValueRef; props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
} }
/* eslint-disable */ /* eslint-disable */
props.record.onCancelEdit = () => { props.record.onCancelEdit = () => {
@ -388,59 +398,6 @@
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

@ -1,17 +1,17 @@
import { ComponentType } from '../../types/componentType' import { ComponentType } from '../../types/componentType';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n() const { t } = useI18n();
/** /**
* @description: placeholder * @description: placeholder
*/ */
export function createPlaceholderMessage(component: ComponentType) { export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input') || component.includes('AutoComplete')) { if (component.includes('Input')) {
return t('common.inputText') return t('common.inputText');
} }
if (component.includes('Picker')) { if (component.includes('Picker')) {
return t('common.chooseText') return t('common.chooseText');
} }
if ( if (
@ -22,7 +22,7 @@ export function createPlaceholderMessage(component: ComponentType) {
component.includes('DatePicker') || component.includes('DatePicker') ||
component.includes('TimePicker') component.includes('TimePicker')
) { ) {
return t('common.chooseText') return t('common.chooseText');
} }
return '' return '';
} }

View File

@ -6,7 +6,7 @@
<Popover <Popover
placement="bottomLeft" placement="bottomLeft"
trigger="click" trigger="click"
@visible-change="handleVisibleChange" @visibleChange="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-column-drag-icon" /> <DragOutlined class="table-coulmn-drag-icon" />
<Checkbox :value="item.value"> <Checkbox :value="item.value">
{{ item.label }} {{ item.label }}
</Checkbox> </Checkbox>
@ -99,7 +99,7 @@
</Tooltip> </Tooltip>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { BasicColumn, ColumnChangeParam } from '../../types/table' import type { BasicColumn, ColumnChangeParam } from '../../types/table';
import { import {
defineComponent, defineComponent,
ref, ref,
@ -109,33 +109,29 @@
nextTick, nextTick,
unref, unref,
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[]
} }
interface Options { interface Options {
label: string label: string;
value: string value: string;
fixed?: boolean | 'left' | 'right' fixed?: boolean | 'left' | 'right';
} }
export default defineComponent({ export default defineComponent({
@ -154,232 +150,219 @@
emits: ['columns-change'], emits: ['columns-change'],
setup(_, { emit, attrs }) { setup(_, { emit, attrs }) {
const { t } = useI18n() const { t } = useI18n();
const table = useTableContext() const table = useTableContext();
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys') const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
let inited = false let inited = false;
const cachePlainOptions = ref<Options[]>([]) const cachePlainOptions = ref<Options[]>([]);
const plainOptions = ref<Options[] | any>([]) const plainOptions = ref<Options[]>([]);
const plainSortOptions = ref<Options[]>([]) const plainSortOptions = ref<Options[]>([]);
const columnListRef = ref<ComponentRef>(null) const columnListRef = ref<ComponentRef>(null);
const state = reactive<State>({ const state = reactive<State>({
checkAll: true, checkAll: true,
checkedList: [], checkedList: [],
defaultCheckList: [], defaultCheckList: [],
}) });
const checkIndex = ref(false) const checkIndex = ref(false);
const checkSelect = ref(false) const checkSelect = ref(false);
const { prefixCls } = useDesign('basic-column-setting') const { prefixCls } = useDesign('basic-column-setting');
const getValues = computed(() => { const getValues = computed(() => {
return unref(table?.getBindValues) || {} return unref(table?.getBindValues) || {};
}) });
watchEffect(() => { watchEffect(() => {
setTimeout(() => { const columns = table.getColumns();
const columns = table.getColumns() if (columns.length) {
if (columns.length && !state.isInit) { init();
init() }
} });
}, 0)
})
watchEffect(() => { watchEffect(() => {
const values = unref(getValues) const values = unref(getValues);
checkIndex.value = !!values.showIndexColumn checkIndex.value = !!values.showIndexColumn;
checkSelect.value = !!values.rowSelection checkSelect.value = !!values.rowSelection;
}) });
function getColumns() { function getColumns() {
const ret: Options[] = [] const ret: Options[] = [];
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({ ret.push({
label: (item.title as string) || (item.customTitle as string), label: (item.title as string) || (item.customTitle as string),
value: (item.dataIndex || item.title) as string, value: (item.dataIndex || item.title) as string,
...item, ...item,
}) });
}) });
return ret return ret;
} }
function init() { function init() {
const columns = getColumns() const columns = getColumns();
const checkList = table const checkList = table
.getColumns({ ignoreAction: true, ignoreIndex: true }) .getColumns({ ignoreAction: true })
.map((item) => { .map((item) => {
if (item.defaultHidden) { if (item.defaultHidden) {
return '' return '';
} }
return item.dataIndex || item.title return item.dataIndex || item.title;
}) })
.filter(Boolean) as string[] .filter(Boolean) as string[];
if (!plainOptions.value.length) { if (!plainOptions.value.length) {
plainOptions.value = columns plainOptions.value = columns;
plainSortOptions.value = columns plainSortOptions.value = columns;
cachePlainOptions.value = columns cachePlainOptions.value = columns;
state.defaultCheckList = checkList state.defaultCheckList = checkList;
} else { } else {
// const fixedColumns = columns.filter((item) => // const fixedColumns = columns.filter((item) =>
// Reflect.has(item, 'fixed') // Reflect.has(item, 'fixed')
// ) as BasicColumn[]; // ) as BasicColumn[];
unref(plainOptions).forEach((item: BasicColumn) => { unref(plainOptions).forEach((item: BasicColumn) => {
const findItem = columns.find((col: BasicColumn) => col.dataIndex === item.dataIndex) const findItem = columns.find((col: BasicColumn) => col.dataIndex === item.dataIndex);
if (findItem) { if (findItem) {
item.fixed = findItem.fixed item.fixed = findItem.fixed;
} }
}) });
} }
state.isInit = true state.checkedList = checkList;
state.checkedList = checkList
} }
// checkAll change // checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) { function onCheckAllChange(e: ChangeEvent) {
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;
setColumns(checkList) setColumns(checkList);
} else { } else {
state.checkedList = [] state.checkedList = [];
setColumns([]) setColumns([]);
} }
} }
const indeterminate = computed(() => { const indeterminate = computed(() => {
const len = plainOptions.value.length const len = plainOptions.value.length;
let checkedLen = state.checkedList.length let checkdedLen = state.checkedList.length;
// unref(checkIndex) && checkedLen--; unref(checkIndex) && checkdedLen--;
return checkedLen > 0 && checkedLen < len return checkdedLen > 0 && checkdedLen < len;
}) });
// Trigger when check/uncheck a column // Trigger when check/uncheck a column
function onChange(checkedList: string[]) { function onChange(checkedList: string[]) {
const len = plainSortOptions.value.length const len = plainOptions.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);
}) });
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];
state.checkAll = true state.checkAll = true;
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
function handleVisibleChange() { function handleVisibleChange() {
if (inited) return if (inited) return;
nextTick(() => { nextTick(() => {
const columnListEl = unref(columnListRef) const columnListEl = unref(columnListRef);
if (!columnListEl) return if (!columnListEl) return;
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
sortable = Sortablejs.create(unref(el), { const { initSortable } = useSortable(el, {
animation: 500, handle: '.table-coulmn-drag-icon ',
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 = cloneDeep(plainSortOptions.value) const columns = getColumns();
if (oldIndex > newIndex) { if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]) columns.splice(newIndex, 0, columns[oldIndex]);
columns.splice(oldIndex + 1, 1) columns.splice(oldIndex + 1, 1);
} else { } else {
columns.splice(newIndex + 1, 0, columns[oldIndex]) columns.splice(newIndex + 1, 0, columns[oldIndex]);
columns.splice(oldIndex, 1) columns.splice(oldIndex, 1);
} }
plainSortOptions.value = columns plainSortOptions.value = columns;
plainOptions.value = columns;
setColumns( setColumns(columns);
columns
.map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)),
)
}, },
}) });
// order initSortable();
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: CheckboxChangeEvent) { function handleIndexCheckChange(e: ChangeEvent) {
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: CheckboxChangeEvent) { function handleSelectCheckChange(e: ChangeEvent) {
table.setProps({ table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined, rowSelection: e.target.checked ? defaultRowSelection : undefined,
}) });
} }
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') { function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
if (!state.checkedList.includes(item.dataIndex as string)) return if (!state.checkedList.includes(item.dataIndex as string)) return;
const columns = getColumns() as BasicColumn[] const columns = getColumns() as BasicColumn[];
const isFixed = item.fixed === fixed ? false : fixed const isFixed = item.fixed === fixed ? false : fixed;
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex) const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
if (index !== -1) { if (index !== -1) {
columns[index].fixed = isFixed columns[index].fixed = isFixed;
} }
item.fixed = isFixed item.fixed = isFixed;
if (isFixed && !item.width) { if (isFixed && !item.width) {
item.width = 100 item.width = 100;
} }
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed }) table.setCacheColumnsByField?.(item.dataIndex, { 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(plainSortOptions).map((col) => { const data: ColumnChangeParam[] = unref(plainOptions).map((col) => {
const visible = const visible =
columns.findIndex( columns.findIndex(
(c: BasicColumn | string) => (c: BasicColumn | string) =>
c === col.value || (typeof c !== 'string' && c.dataIndex === col.value), c === col.value || (typeof c !== 'string' && c.dataIndex === col.value),
) !== -1 ) !== -1;
return { dataIndex: col.value, fixed: col.fixed, visible } return { dataIndex: col.value, fixed: col.fixed, visible };
}) });
emit('columns-change', data) emit('columns-change', data);
} }
function getPopupContainer() { function getPopupContainer() {
return isFunction(attrs.getPopupContainer) return isFunction(attrs.getPopupContainer)
? attrs.getPopupContainer() ? attrs.getPopupContainer()
: getParentContainer() : getParentContainer();
} }
return { return {
@ -400,14 +383,14 @@
defaultRowSelection, defaultRowSelection,
handleColumnFixed, handleColumnFixed,
getPopupContainer, getPopupContainer,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-column-setting'; @prefix-cls: ~'@{namespace}-basic-column-setting';
.table-column-drag-icon { .table-coulmn-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="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer"> <Dropdown placement="bottomCenter" :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">
@ -23,13 +23,13 @@
</Tooltip> </Tooltip>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { SizeType } from '../../types/table' import type { SizeType } from '../../types/table';
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue';
import { Tooltip, Dropdown, Menu } from 'ant-design-vue' import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
import { ColumnHeightOutlined } from '@ant-design/icons-vue' import { ColumnHeightOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext' import { useTableContext } from '../../hooks/useTableContext';
import { getPopupContainer } from '/@/utils' import { getPopupContainer } from '/@/utils';
export default defineComponent({ export default defineComponent({
name: 'SizeSetting', name: 'SizeSetting',
@ -41,16 +41,16 @@
MenuItem: Menu.Item, MenuItem: Menu.Item,
}, },
setup() { setup() {
const table = useTableContext() const table = useTableContext();
const { t } = useI18n() const { t } = useI18n();
const selectedKeysRef = ref<SizeType[]>([table.getSize()]) const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
function handleTitleClick({ key }: { key: SizeType }) { function handleTitleClick({ key }: { key: SizeType }) {
selectedKeysRef.value = [key] selectedKeysRef.value = [key];
table.setProps({ table.setProps({
size: key, size: key,
}) });
} }
return { return {
@ -58,7 +58,7 @@
selectedKeysRef, selectedKeysRef,
getPopupContainer, getPopupContainer,
t, t,
} };
}, },
}) });
</script> </script>

View File

@ -1,40 +1,40 @@
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, reactive, toRaw, unref, watch } from 'vue' import { computed, Ref, ref, 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';
import { isArray, isBoolean, isFunction, isMap, isString } from '/@/utils/is' import { isArray, isBoolean, isFunction, isMap, isString } from '/@/utils/is';
import { cloneDeep, isEqual } from 'lodash-es' import { cloneDeep, isEqual } from 'lodash-es';
import { formatToDate } from '/@/utils/dateUtil' import { formatToDate } from '/@/utils/dateUtil';
import { ACTION_COLUMN_FLAG, DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../const' import { ACTION_COLUMN_FLAG, DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../const';
function handleItem(item: BasicColumn, ellipsis: boolean) { function handleItem(item: BasicColumn, ellipsis: boolean) {
const { key, dataIndex, children } = item const { key, dataIndex, children } = item;
item.align = item.align || DEFAULT_ALIGN item.align = item.align || DEFAULT_ALIGN;
if (ellipsis) { if (ellipsis) {
if (!key) { if (!key) {
item.key = dataIndex item.key = dataIndex;
} }
if (!isBoolean(item.ellipsis)) { if (!isBoolean(item.ellipsis)) {
Object.assign(item, { Object.assign(item, {
ellipsis, ellipsis,
}) });
} }
} }
if (children && children.length) { if (children && children.length) {
handleChildren(children, !!ellipsis) handleChildren(children, !!ellipsis);
} }
} }
function handleChildren(children: BasicColumn[] | undefined, ellipsis: boolean) { function handleChildren(children: BasicColumn[] | undefined, ellipsis: boolean) {
if (!children) return if (!children) return;
children.forEach((item) => { children.forEach((item) => {
const { children } = item const { children } = item;
handleItem(item, ellipsis) handleItem(item, ellipsis);
handleChildren(children, ellipsis) handleChildren(children, ellipsis);
}) });
} }
function handleIndexColumn( function handleIndexColumn(
@ -42,26 +42,26 @@ function handleIndexColumn(
getPaginationRef: ComputedRef<boolean | PaginationProps>, getPaginationRef: ComputedRef<boolean | PaginationProps>,
columns: BasicColumn[], columns: BasicColumn[],
) { ) {
const { t } = useI18n() const { t } = useI18n();
const { showIndexColumn, indexColumnProps, isTreeTable } = unref(propsRef) const { showIndexColumn, indexColumnProps, isTreeTable } = unref(propsRef);
let pushIndexColumns = false let pushIndexColumns = false;
if (unref(isTreeTable)) { if (unref(isTreeTable)) {
return return;
} }
columns.forEach(() => { columns.forEach(() => {
const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG) const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG);
if (showIndexColumn) { if (showIndexColumn) {
pushIndexColumns = indIndex === -1 pushIndexColumns = indIndex === -1;
} else if (!showIndexColumn && indIndex !== -1) { } else if (!showIndexColumn && indIndex !== -1) {
columns.splice(indIndex, 1) columns.splice(indIndex, 1);
} }
}) });
if (!pushIndexColumns) return if (!pushIndexColumns) return;
const isFixedLeft = columns.some((item) => item.fixed === 'left') const isFixedLeft = columns.some((item) => item.fixed === 'left');
columns.unshift({ columns.unshift({
flag: INDEX_COLUMN_FLAG, flag: INDEX_COLUMN_FLAG,
@ -69,12 +69,12 @@ function handleIndexColumn(
title: t('component.table.index'), title: t('component.table.index'),
align: 'center', align: 'center',
customRender: ({ index }) => { customRender: ({ index }) => {
const getPagination = unref(getPaginationRef) const getPagination = unref(getPaginationRef);
if (isBoolean(getPagination)) { if (isBoolean(getPagination)) {
return `${index + 1}` return `${index + 1}`;
} }
const { current = 1, pageSize = PAGE_SIZE } = getPagination const { current = 1, pageSize = PAGE_SIZE } = getPagination;
return ((current < 1 ? 1 : current) - 1) * pageSize + index + 1 return ((current < 1 ? 1 : current) - 1) * pageSize + index + 1;
}, },
...(isFixedLeft ...(isFixedLeft
? { ? {
@ -82,21 +82,21 @@ function handleIndexColumn(
} }
: {}), : {}),
...indexColumnProps, ...indexColumnProps,
}) });
} }
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) { function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
const { actionColumn } = unref(propsRef) const { actionColumn } = unref(propsRef);
if (!actionColumn) return if (!actionColumn) return;
const hasIndex = columns.findIndex((column) => column.flag === ACTION_COLUMN_FLAG) const hasIndex = columns.findIndex((column) => column.flag === ACTION_COLUMN_FLAG);
if (hasIndex === -1) { if (hasIndex === -1) {
columns.push({ columns.push({
...columns[hasIndex], ...columns[hasIndex],
fixed: 'right', fixed: 'right',
...actionColumn, ...actionColumn,
flag: ACTION_COLUMN_FLAG, flag: ACTION_COLUMN_FLAG,
}) });
} }
} }
@ -104,154 +104,162 @@ export function useColumns(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
getPaginationRef: ComputedRef<boolean | PaginationProps>, getPaginationRef: ComputedRef<boolean | PaginationProps>,
) { ) {
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]> const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
let cacheColumns = unref(propsRef).columns let cacheColumns = unref(propsRef).columns;
const getColumnsRef = computed(() => { const getColumnsRef = computed(() => {
const columns = cloneDeep(unref(columnsRef)) const columns = cloneDeep(unref(columnsRef));
handleIndexColumn(propsRef, getPaginationRef, columns) handleIndexColumn(propsRef, getPaginationRef, columns);
handleActionColumn(propsRef, columns) handleActionColumn(propsRef, columns);
if (!columns) { if (!columns) {
return [] return [];
} }
const { ellipsis } = unref(propsRef) const { ellipsis } = unref(propsRef);
columns.forEach((item) => { columns.forEach((item) => {
const { customRender, slots } = item const { customRender, slots } = item;
handleItem( handleItem(
item, item,
Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots, Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots,
) );
}) });
return columns return columns;
}) });
function isIfShow(column: BasicColumn): boolean { function isIfShow(column: BasicColumn): boolean {
const ifShow = column.ifShow const ifShow = column.ifShow;
let isIfShow = true let isIfShow = true;
if (isBoolean(ifShow)) { if (isBoolean(ifShow)) {
isIfShow = ifShow isIfShow = ifShow;
} }
if (isFunction(ifShow)) { if (isFunction(ifShow)) {
isIfShow = ifShow(column) isIfShow = ifShow(column);
} }
return isIfShow return isIfShow;
} }
const { hasPermission } = usePermission() const { hasPermission } = usePermission();
const getViewColumns = computed(() => { const getViewColumns = computed(() => {
const viewColumns = sortFixedColumn(unref(getColumnsRef)) const viewColumns = sortFixedColumn(unref(getColumnsRef));
const columns = cloneDeep(viewColumns) const columns = cloneDeep(viewColumns);
return columns return columns
.filter((column) => { .filter((column) => {
return hasPermission(column.auth) && isIfShow(column) return hasPermission(column.auth) && isIfShow(column);
}) })
.map((column) => { .map((column) => {
const { slots, customRender, format, edit, editRow, flag } = column const { slots, dataIndex, 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');
} }
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!) const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
if (!customRender && format && !edit && !isDefaultAction) { if (!customRender && format && !edit && !isDefaultAction) {
column.customRender = ({ text, record, index }) => { column.customRender = ({ text, record, index }) => {
return formatCell(text, format, record, index) return formatCell(text, format, record, index);
} };
} }
// edit table // edit table
if ((edit || editRow) && !isDefaultAction) { if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column) column.customRender = renderEditCell(column);
} }
return reactive(column) return column;
}) });
}) });
watch( watch(
() => unref(propsRef).columns, () => unref(propsRef).columns,
(columns) => { (columns) => {
columnsRef.value = columns columnsRef.value = columns;
cacheColumns = columns?.filter((item) => !item.flag) ?? [] cacheColumns = columns?.filter((item) => !item.flag) ?? [];
}, },
) );
function setCacheColumnsByField(dataIndex: string | undefined, value: Partial<BasicColumn>) { function setCacheColumnsByField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
if (!dataIndex || !value) { if (!dataIndex || !value) {
return return;
} }
cacheColumns.forEach((item) => { cacheColumns.forEach((item) => {
if (item.dataIndex === dataIndex) { if (item.dataIndex === dataIndex) {
Object.assign(item, value) Object.assign(item, value);
return return;
} }
}) });
} }
/** /**
* set columns * set columns
* @param columnList keycolumn * @param columnList keycolumn
*/ */
function setColumns(columnList: Partial<BasicColumn>[] | (string | string[])[]) { function setColumns(columnList: Partial<BasicColumn>[] | string[]) {
const columns = cloneDeep(columnList) const columns = cloneDeep(columnList);
if (!isArray(columns)) return if (!isArray(columns)) return;
if (columns.length <= 0) { if (columns.length <= 0) {
columnsRef.value = [] columnsRef.value = [];
return return;
} }
const firstColumn = columns[0] const firstColumn = columns[0];
const cacheKeys = cacheColumns.map((item) => item.dataIndex) const cacheKeys = cacheColumns.map((item) => item.dataIndex);
if (!isString(firstColumn) && !isArray(firstColumn)) { if (!isString(firstColumn)) {
columnsRef.value = columns as BasicColumn[] columnsRef.value = columns as BasicColumn[];
} else { } else {
const columnKeys = (columns as (string | string[])[]).map((m) => m.toString()) const columnKeys = columns as string[];
const newColumns: BasicColumn[] = [] const newColumns: BasicColumn[] = [];
cacheColumns.forEach((item) => { cacheColumns.forEach((item) => {
newColumns.push({ if (columnKeys.includes(item.dataIndex! || (item.key as string))) {
...item, newColumns.push({
defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)), ...item,
}) 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 (
columnKeys.indexOf(prev.dataIndex?.toString() as string) - cacheKeys.indexOf(prev.dataIndex as string) -
columnKeys.indexOf(next.dataIndex?.toString() as string) cacheKeys.indexOf(next.dataIndex as string)
) );
}) });
} }
columnsRef.value = newColumns columnsRef.value = newColumns;
} }
} }
function getColumns(opt?: GetColumnsParams) { function getColumns(opt?: GetColumnsParams) {
const { ignoreIndex, ignoreAction, sort } = opt || {} const { ignoreIndex, ignoreAction, sort } = opt || {};
let columns = toRaw(unref(getColumnsRef)) let columns = toRaw(unref(getColumnsRef));
if (ignoreIndex) { if (ignoreIndex) {
columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG) columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
} }
if (ignoreAction) { if (ignoreAction) {
columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG) columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG);
} }
if (sort) { if (sort) {
columns = sortFixedColumn(columns) columns = sortFixedColumn(columns);
} }
return columns return columns;
} }
function getCacheColumns() { function getCacheColumns() {
return cacheColumns return cacheColumns;
} }
return { return {
@ -261,57 +269,57 @@ export function useColumns(
setColumns, setColumns,
getViewColumns, getViewColumns,
setCacheColumnsByField, setCacheColumnsByField,
} };
} }
function sortFixedColumn(columns: BasicColumn[]) { function sortFixedColumn(columns: BasicColumn[]) {
const fixedLeftColumns: BasicColumn[] = [] const fixedLeftColumns: BasicColumn[] = [];
const fixedRightColumns: BasicColumn[] = [] const fixedRightColumns: BasicColumn[] = [];
const defColumns: BasicColumn[] = [] const defColumns: BasicColumn[] = [];
for (const column of columns) { for (const column of columns) {
if (column.fixed === 'left') { if (column.fixed === 'left') {
fixedLeftColumns.push(column) fixedLeftColumns.push(column);
continue continue;
} }
if (column.fixed === 'right') { if (column.fixed === 'right') {
fixedRightColumns.push(column) fixedRightColumns.push(column);
continue continue;
} }
defColumns.push(column) defColumns.push(column);
} }
return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter( return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(
(item) => !item.defaultHidden, (item) => !item.defaultHidden,
) );
} }
// format cell // format cell
export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) { export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) {
if (!format) { if (!format) {
return text return text;
} }
// custom function // custom function
if (isFunction(format)) { if (isFunction(format)) {
return format(text, record, index) return format(text, record, index);
} }
try { try {
// date type // date type
const DATE_FORMAT_PREFIX = 'date|' const DATE_FORMAT_PREFIX = 'date|';
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX) && text) { if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
const dateFormat = format.replace(DATE_FORMAT_PREFIX, '') const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
if (!dateFormat) { if (!dateFormat) {
return text return text;
} }
return formatToDate(text, dateFormat) return formatToDate(text, dateFormat);
} }
// Map // Map
if (isMap(format)) { if (isMap(format)) {
return format.get(text) return format.get(text);
} }
} catch (error) { } catch (error) {
return text return text;
} }
} }

View File

@ -1,5 +1,5 @@
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table' import type { BasicTableProps, FetchParams, SorterResult } from '../types/table';
import type { PaginationProps } from '../types/pagination' import type { PaginationProps } from '../types/pagination';
import { import {
ref, ref,
unref, unref,
@ -10,25 +10,25 @@ import {
reactive, reactive,
Ref, Ref,
watchEffect, watchEffect,
} from 'vue' } from 'vue';
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, merge } from 'lodash-es' import { get, cloneDeep } 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 {
getPaginationInfo: ComputedRef<boolean | PaginationProps> getPaginationInfo: ComputedRef<boolean | PaginationProps>;
setPagination: (info: Partial<PaginationProps>) => void setPagination: (info: Partial<PaginationProps>) => void;
setLoading: (loading: boolean) => void setLoading: (loading: boolean) => void;
getFieldsValue: () => Recordable getFieldsValue: () => Recordable;
clearSelectedRowKeys: () => void clearSelectedRowKeys: () => void;
tableData: Ref<Recordable[]> tableData: Ref<Recordable[]>;
} }
interface SearchState { interface SearchState {
sortInfo: Recordable sortInfo: Recordable;
filterInfo: Record<string, string[]> filterInfo: Record<string, string[]>;
} }
export function useDataSource( export function useDataSource(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
@ -45,188 +45,190 @@ export function useDataSource(
const searchState = reactive<SearchState>({ const searchState = reactive<SearchState>({
sortInfo: {}, sortInfo: {},
filterInfo: {}, filterInfo: {},
}) });
const dataSourceRef = ref<Recordable[]>([]) const dataSourceRef = ref<Recordable[]>([]);
const rawDataSourceRef = ref<Recordable>({}) const rawDataSourceRef = ref<Recordable>({});
watchEffect(() => { watchEffect(() => {
tableData.value = unref(dataSourceRef) tableData.value = unref(dataSourceRef);
}) });
watch( watch(
() => unref(propsRef).dataSource, () => unref(propsRef).dataSource,
() => { () => {
const { dataSource, api } = unref(propsRef) const { dataSource, api } = unref(propsRef);
!api && dataSource && (dataSourceRef.value = dataSource) !api && dataSource && (dataSourceRef.value = dataSource);
}, },
{ {
immediate: true, immediate: true,
}, },
) );
function handleTableChange( function handleTableChange(
pagination: PaginationProps, pagination: PaginationProps,
filters: Partial<Recordable<string[]>>, filters: Partial<Recordable<string[]>>,
sorter: SorterResult, sorter: SorterResult,
) { ) {
const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef) const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef);
if (clearSelectOnPageChange) { if (clearSelectOnPageChange) {
clearSelectedRowKeys() clearSelectedRowKeys();
} }
setPagination(pagination) setPagination(pagination);
const params: Recordable = {} const params: Recordable = {};
if (sorter && isFunction(sortFn)) { if (sorter && isFunction(sortFn)) {
const sortInfo = sortFn(sorter) const sortInfo = sortFn(sorter);
searchState.sortInfo = sortInfo searchState.sortInfo = sortInfo;
params.sortInfo = sortInfo params.sortInfo = sortInfo;
} }
if (filters && isFunction(filterFn)) { if (filters && isFunction(filterFn)) {
const filterInfo = filterFn(filters) const filterInfo = filterFn(filters);
searchState.filterInfo = filterInfo searchState.filterInfo = filterInfo;
params.filterInfo = filterInfo params.filterInfo = filterInfo;
} }
fetch(params) fetch(params);
} }
function setTableKey(items: any[]) { function setTableKey(items: any[]) {
if (!items || !Array.isArray(items)) return if (!items || !Array.isArray(items)) return;
items.forEach((item) => { items.forEach((item) => {
if (!item[ROW_KEY]) { if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID() item[ROW_KEY] = buildUUID();
} }
if (item.children && item.children.length) { if (item.children && item.children.length) {
setTableKey(item.children) setTableKey(item.children);
} }
}) });
} }
const getAutoCreateKey = computed(() => { const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
}) });
const getRowKey = computed(() => { const getRowKey = computed(() => {
const { rowKey } = unref(propsRef) const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
}) });
const getDataSourceRef = computed(() => { const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef) const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) { if (!dataSource || dataSource.length === 0) {
return unref(dataSourceRef) return unref(dataSourceRef);
} }
if (unref(getAutoCreateKey)) { if (unref(getAutoCreateKey)) {
const firstItem = dataSource[0] const firstItem = dataSource[0];
const lastItem = dataSource[dataSource.length - 1] const lastItem = dataSource[dataSource.length - 1];
if (firstItem && lastItem) { if (firstItem && lastItem) {
if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) { if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
const data = cloneDeep(unref(dataSourceRef)) const data = cloneDeep(unref(dataSourceRef));
data.forEach((item) => { data.forEach((item) => {
if (!item[ROW_KEY]) { if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID() item[ROW_KEY] = buildUUID();
} }
if (item.children && item.children.length) { if (item.children && item.children.length) {
setTableKey(item.children) setTableKey(item.children);
} }
}) });
dataSourceRef.value = data dataSourceRef.value = data;
} }
} }
} }
return unref(dataSourceRef) return unref(dataSourceRef);
}) });
async function updateTableData(index: number, key: string, value: any) { async function updateTableData(index: number, key: string, value: any) {
const record = dataSourceRef.value[index] const record = dataSourceRef.value[index];
if (record) { if (record) {
dataSourceRef.value[index][key] = value dataSourceRef.value[index][key] = value;
} }
return dataSourceRef.value[index] return dataSourceRef.value[index];
} }
function updateTableDataRecord( function updateTableDataRecord(
rowKey: string | number, rowKey: string | number,
record: Recordable, record: Recordable,
): Recordable | undefined { ): Recordable | undefined {
const row = findTableDataRecord(rowKey) const row = findTableDataRecord(rowKey);
if (row) { if (row) {
for (const field in row) { for (const field in row) {
if (Reflect.has(record, field)) row[field] = record[field] if (Reflect.has(record, field)) row[field] = record[field];
} }
return row return row;
} }
} }
function deleteTableDataRecord(rowKey: string | number | string[] | number[]) { function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const rowKeyName = unref(getRowKey) const rowKeyName = unref(getRowKey);
if (!rowKeyName) return if (!rowKeyName) return;
const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
for (const key of rowKeys) { for (const key of rowKeys) {
let index: number | undefined = dataSourceRef.value.findIndex((row) => { let index: number | undefined = dataSourceRef.value.findIndex((row) => {
let targetKeyName: string let targetKeyName: string;
if (typeof rowKeyName === 'function') { if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row) targetKeyName = rowKeyName(row);
} else { } else {
targetKeyName = rowKeyName as string targetKeyName = rowKeyName as string;
} }
return row[targetKeyName] === key return row[targetKeyName] === key;
}) });
if (index >= 0) { if (index >= 0) {
dataSourceRef.value.splice(index, 1) dataSourceRef.value.splice(index, 1);
} }
index = unref(propsRef).dataSource?.findIndex((row) => { index = unref(propsRef).dataSource?.findIndex((row) => {
let targetKeyName: string let targetKeyName: string;
if (typeof rowKeyName === 'function') { if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row) targetKeyName = rowKeyName(row);
} else { } else {
targetKeyName = rowKeyName as string targetKeyName = rowKeyName as string;
} }
return row[targetKeyName] === key return row[targetKeyName] === key;
}) });
if (typeof index !== 'undefined' && index !== -1) unref(propsRef).dataSource?.splice(index, 1) if (typeof index !== 'undefined' && index !== -1)
unref(propsRef).dataSource?.splice(index, 1);
} }
setPagination({ setPagination({
total: unref(propsRef).dataSource?.length, total: unref(propsRef).dataSource?.length,
}) });
} }
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);
return unref(dataSourceRef) unref(propsRef).dataSource?.splice(index, 0, record);
return unref(propsRef).dataSource;
} }
function findTableDataRecord(rowKey: string | number) { function findTableDataRecord(rowKey: string | number) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const rowKeyName = unref(getRowKey) const rowKeyName = unref(getRowKey);
if (!rowKeyName) return if (!rowKeyName) return;
const { childrenColumnName = 'children' } = unref(propsRef) const { childrenColumnName = 'children' } = unref(propsRef);
const findRow = (array: any[]) => { const findRow = (array: any[]) => {
let ret let ret;
array.some(function iter(r) { array.some(function iter(r) {
if (typeof rowKeyName === 'function') { if (typeof rowKeyName === 'function') {
if ((rowKeyName(r) as string) === rowKey) { if ((rowKeyName(r) as string) === rowKey) {
ret = r ret = r;
return true return true;
} }
} else { } else {
if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) { if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
ret = r ret = r;
return true return true;
} }
} }
return r[childrenColumnName] && r[childrenColumnName].some(iter) return r[childrenColumnName] && r[childrenColumnName].some(iter);
}) });
return ret return ret;
} };
// const row = dataSourceRef.value.find(r => { // const row = dataSourceRef.value.find(r => {
// if (typeof rowKeyName === 'function') { // if (typeof rowKeyName === 'function') {
@ -235,7 +237,7 @@ export function useDataSource(
// return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey // return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
// } // }
// }) // })
return findRow(dataSourceRef.value) return findRow(dataSourceRef.value);
} }
async function fetch(opt?: FetchParams) { async function fetch(opt?: FetchParams) {
@ -248,111 +250,111 @@ export function useDataSource(
afterFetch, afterFetch,
useSearchForm, useSearchForm,
pagination, pagination,
} = unref(propsRef) } = unref(propsRef);
if (!api || !isFunction(api)) return if (!api || !isFunction(api)) return;
try { try {
setLoading(true) setLoading(true);
const { pageField, sizeField, listField, totalField } = Object.assign( const { pageField, sizeField, listField, totalField } = Object.assign(
{}, {},
FETCH_SETTING, FETCH_SETTING,
fetchSetting, fetchSetting,
) );
let pageParams: Recordable = {} let pageParams: Recordable = {};
const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps;
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) { if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
pageParams = {} pageParams = {};
} else { } else {
pageParams[pageField] = (opt && opt.page) || current pageParams[pageField] = (opt && opt.page) || current;
pageParams[sizeField] = pageSize pageParams[sizeField] = pageSize;
} }
const { sortInfo = {}, filterInfo } = searchState const { sortInfo = {}, filterInfo } = searchState;
let params: Recordable = merge( let params: Recordable = {
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;
} }
const res = await api(params) const res = await api(params);
rawDataSourceRef.value = res rawDataSourceRef.value = res;
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 ? res.length : get(res, totalField) const resultTotal: number = isArrayResult ? 0 : get(res, totalField);
// 假如数据变少导致总页数变少并小于当前选中页码通过getPaginationRef获取到的页码是不正确的需获取正确的页码再次执行 // 假如数据变少导致总页数变少并小于当前选中页码通过getPaginationRef获取到的页码是不正确的需获取正确的页码再次执行
if (resultTotal) { if (resultTotal) {
const currentTotalPage = Math.ceil(resultTotal / pageSize) const currentTotalPage = Math.ceil(resultTotal / pageSize);
if (current > currentTotalPage) { if (current > currentTotalPage) {
setPagination({ setPagination({
current: currentTotalPage, current: currentTotalPage,
}) });
return await fetch(opt) return await fetch(opt);
} }
} }
if (afterFetch && isFunction(afterFetch)) { if (afterFetch && isFunction(afterFetch)) {
resultItems = (await afterFetch(resultItems)) || resultItems resultItems = (await afterFetch(resultItems)) || resultItems;
} }
dataSourceRef.value = resultItems dataSourceRef.value = resultItems;
setPagination({ setPagination({
total: resultTotal || 0, total: resultTotal || 0,
}) });
if (opt && opt.page) { if (opt && opt.page) {
setPagination({ setPagination({
current: opt.page || 1, current: opt.page || 1,
}) });
} }
emit('fetch-success', { emit('fetch-success', {
items: unref(resultItems), items: unref(resultItems),
total: resultTotal, total: resultTotal,
}) });
return resultItems return resultItems;
} catch (error) { } catch (error) {
emit('fetch-error', error) emit('fetch-error', error);
dataSourceRef.value = [] dataSourceRef.value = [];
setPagination({ setPagination({
total: 0, total: 0,
}) });
} finally { } finally {
setLoading(false) setLoading(false);
} }
} }
function setTableData<T = Recordable>(values: T[]) { function setTableData<T = Recordable>(values: T[]) {
dataSourceRef.value = values dataSourceRef.value = values;
} }
function getDataSource<T = Recordable>() { function getDataSource<T = Recordable>() {
return getDataSourceRef.value as T[] return getDataSourceRef.value as T[];
} }
function getRawDataSource<T = Recordable>() { function getRawDataSource<T = Recordable>() {
return rawDataSourceRef.value as T return rawDataSourceRef.value as T;
} }
async function reload(opt?: FetchParams) { async function reload(opt?: FetchParams) {
return await fetch(opt) return await fetch(opt);
} }
onMounted(() => { onMounted(() => {
useTimeoutFn(() => { useTimeoutFn(() => {
unref(propsRef).immediate && fetch() unref(propsRef).immediate && fetch();
}, 16) }, 16);
}) });
return { return {
getDataSourceRef, getDataSourceRef,
@ -369,5 +371,5 @@ export function useDataSource(
insertTableDataRecord, insertTableDataRecord,
findTableDataRecord, findTableDataRecord,
handleTableChange, handleTableChange,
} };
} }

View File

@ -1,112 +1,115 @@
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is';
import type { BasicTableProps, TableRowSelection } from '../types/table' import type { BasicTableProps, TableRowSelection } from '../types/table';
import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue' import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue';
import { ROW_KEY } from '../const' import { ROW_KEY } from '../const';
import { omit } from 'lodash-es' import { omit } from 'lodash-es';
import { findNodeAll } from '/@/utils/helper/treeHelper' import { findNodeAll } from '/@/utils/helper/treeHelper';
export function useRowSelection( export function useRowSelection(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>, tableData: Ref<Recordable[]>,
emit: EmitType, emit: EmitType,
) { ) {
const selectedRowKeysRef = ref<string[]>([]) const selectedRowKeysRef = ref<string[]>([]);
const selectedRowRef = ref<Recordable[]>([]) const selectedRowRef = ref<Recordable[]>([]);
const getRowSelectionRef = computed((): TableRowSelection | null => { const getRowSelectionRef = computed((): TableRowSelection | null => {
const { rowSelection } = unref(propsRef) const { rowSelection } = unref(propsRef);
if (!rowSelection) { if (!rowSelection) {
return null return null;
} }
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']),
} };
}) });
watch( watch(
() => unref(propsRef).rowSelection?.selectedRowKeys, () => unref(propsRef).rowSelection?.selectedRowKeys,
(v: string[]) => { (v: string[]) => {
setSelectedRowKeys(v) setSelectedRowKeys(v);
}, },
) );
watch( watch(
() => unref(selectedRowKeysRef), () => unref(selectedRowKeysRef),
() => { () => {
nextTick(() => { nextTick(() => {
const { rowSelection } = unref(propsRef) const { rowSelection } = unref(propsRef);
if (rowSelection) { if (rowSelection) {
const { onChange } = rowSelection const { onChange } = rowSelection;
if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows()) if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows());
} }
emit('selection-change', { emit('selection-change', {
keys: getSelectRowKeys(), keys: getSelectRowKeys(),
rows: getSelectRows(), rows: getSelectRows(),
}) });
}) });
}, },
{ deep: true }, { deep: true },
) );
const getAutoCreateKey = computed(() => { const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
}) });
const getRowKey = computed(() => { const getRowKey = computed(() => {
const { rowKey } = unref(propsRef) const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
}) });
function setSelectedRowKeys(rowKeys: string[]) { function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys selectedRowKeysRef.value = rowKeys;
const allSelectedRows = findNodeAll( const allSelectedRows = findNodeAll(
toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))), toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
(item) => rowKeys.includes(item[unref(getRowKey) as string]), (item) => rowKeys.includes(item[unref(getRowKey) as string]),
{ {
children: propsRef.value.childrenColumnName ?? 'children', children: propsRef.value.childrenColumnName ?? 'children',
}, },
) );
const trueSelectedRows: any[] = [] const trueSelectedRows: any[] = [];
rowKeys.forEach((key: string) => { rowKeys.forEach((key: string) => {
const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key) const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key);
found && trueSelectedRows.push(found) found && trueSelectedRows.push(found);
}) });
selectedRowRef.value = trueSelectedRows selectedRowRef.value = trueSelectedRows;
} }
function setSelectedRows(rows: Recordable[]) { function setSelectedRows(rows: Recordable[]) {
selectedRowRef.value = rows selectedRowRef.value = rows;
} }
function clearSelectedRowKeys() { function clearSelectedRowKeys() {
selectedRowRef.value = [] selectedRowRef.value = [];
selectedRowKeysRef.value = [] selectedRowKeysRef.value = [];
} }
function deleteSelectRowByKey(key: string) { function deleteSelectRowByKey(key: string) {
const selectedRowKeys = unref(selectedRowKeysRef) const selectedRowKeys = unref(selectedRowKeysRef);
const index = selectedRowKeys.findIndex((item) => item === key) const index = selectedRowKeys.findIndex((item) => item === key);
if (index !== -1) { if (index !== -1) {
unref(selectedRowKeysRef).splice(index, 1) unref(selectedRowKeysRef).splice(index, 1);
} }
} }
function getSelectRowKeys() { function getSelectRowKeys() {
return unref(selectedRowKeysRef) return unref(selectedRowKeysRef);
} }
function getSelectRows<T = Recordable>() { function getSelectRows<T = Recordable>() {
// const ret = toRaw(unref(selectedRowRef)).map((item) => toRaw(item)); // const ret = toRaw(unref(selectedRowRef)).map((item) => toRaw(item));
return unref(selectedRowRef) as T[] return unref(selectedRowRef) as T[];
} }
function getRowSelection() { function getRowSelection() {
return unref(getRowSelectionRef)! return unref(getRowSelectionRef)!;
} }
return { return {
@ -118,5 +121,5 @@ export function useRowSelection(
clearSelectedRowKeys, clearSelectedRowKeys,
deleteSelectRowByKey, deleteSelectRowByKey,
setSelectedRows, setSelectedRows,
} };
} }

View File

@ -1,55 +0,0 @@
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

@ -1,167 +1,161 @@
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table' import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
import type { PaginationProps } from '../types/pagination' import type { PaginationProps } from '../types/pagination';
import type { DynamicProps } from '/#/utils' import type { DynamicProps } from '/#/utils';
import type { FormActionType } from '/@/components/Form' import type { FormActionType } from '/@/components/Form';
import type { WatchStopHandle } from 'vue' import type { WatchStopHandle } from 'vue';
import { getDynamicProps } from '/@/utils' import { getDynamicProps } from '/@/utils';
import { ref, onUnmounted, unref, watch, toRaw } from 'vue' import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
import { isProdMode } from '/@/utils/env' import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log' import { error } from '/@/utils/log';
type Props = Partial<DynamicProps<BasicTableProps>> type Props = Partial<DynamicProps<BasicTableProps>>;
type UseTableMethod = TableActionType & { type UseTableMethod = TableActionType & {
getForm: () => FormActionType getForm: () => FormActionType;
} };
export function useTable(tableProps?: Props): [ export function useTable(tableProps?: Props): [
(instance: TableActionType, formInstance: UseTableMethod) => void, (instance: TableActionType, formInstance: UseTableMethod) => void,
TableActionType & { TableActionType & {
getForm: () => FormActionType getForm: () => FormActionType;
}, },
] { ] {
const tableRef = ref<Nullable<TableActionType>>(null) const tableRef = ref<Nullable<TableActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false) const loadedRef = ref<Nullable<boolean>>(false);
const formRef = ref<Nullable<UseTableMethod>>(null) const formRef = ref<Nullable<UseTableMethod>>(null);
let stopWatch: WatchStopHandle let stopWatch: WatchStopHandle;
function register(instance: TableActionType, formInstance: UseTableMethod) { function register(instance: TableActionType, formInstance: UseTableMethod) {
isProdMode() && isProdMode() &&
onUnmounted(() => { onUnmounted(() => {
tableRef.value = null tableRef.value = null;
loadedRef.value = null loadedRef.value = null;
}) });
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return;
tableRef.value = instance tableRef.value = instance;
formRef.value = formInstance formRef.value = formInstance;
tableProps && instance.setProps(getDynamicProps(tableProps)) tableProps && instance.setProps(getDynamicProps(tableProps));
loadedRef.value = true loadedRef.value = true;
stopWatch?.() stopWatch?.();
stopWatch = watch( stopWatch = watch(
() => tableProps, () => tableProps,
() => { () => {
tableProps && instance.setProps(getDynamicProps(tableProps)) tableProps && instance.setProps(getDynamicProps(tableProps));
}, },
{ {
immediate: true, immediate: true,
deep: true, deep: true,
}, },
) );
} }
function getTableInstance(): TableActionType { function getTableInstance(): TableActionType {
const table = unref(tableRef) const table = unref(tableRef);
if (!table) { if (!table) {
error( error(
'The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!', 'The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!',
) );
} }
return table as TableActionType return table as TableActionType;
} }
const methods: TableActionType & { const methods: TableActionType & {
getForm: () => FormActionType getForm: () => FormActionType;
} = { } = {
reload: async (opt?: FetchParams) => { reload: async (opt?: FetchParams) => {
return await getTableInstance().reload(opt) return await getTableInstance().reload(opt);
}, },
setProps: (props: Partial<BasicTableProps>) => { setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props) getTableInstance().setProps(props);
}, },
redoHeight: () => { redoHeight: () => {
getTableInstance().redoHeight() getTableInstance().redoHeight();
}, },
setLoading: (loading: boolean) => { setLoading: (loading: boolean) => {
getTableInstance().setLoading(loading) getTableInstance().setLoading(loading);
}, },
getDataSource: () => { getDataSource: () => {
return getTableInstance().getDataSource() return getTableInstance().getDataSource();
}, },
getRawDataSource: () => { getRawDataSource: () => {
return getTableInstance().getRawDataSource() return getTableInstance().getRawDataSource();
}, },
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => { getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
const columns = getTableInstance().getColumns({ ignoreIndex }) || [] const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return toRaw(columns) return toRaw(columns);
}, },
setColumns: (columns: BasicColumn[]) => { setColumns: (columns: BasicColumn[]) => {
getTableInstance().setColumns(columns) getTableInstance().setColumns(columns);
}, },
setTableData: (values: any[]) => { setTableData: (values: any[]) => {
return getTableInstance().setTableData(values) return getTableInstance().setTableData(values);
}, },
setPagination: (info: Partial<PaginationProps>) => { setPagination: (info: Partial<PaginationProps>) => {
return getTableInstance().setPagination(info) return getTableInstance().setPagination(info);
}, },
deleteSelectRowByKey: (key: string) => { deleteSelectRowByKey: (key: string) => {
getTableInstance().deleteSelectRowByKey(key) getTableInstance().deleteSelectRowByKey(key);
}, },
getSelectRowKeys: () => { getSelectRowKeys: () => {
return toRaw(getTableInstance().getSelectRowKeys()) return toRaw(getTableInstance().getSelectRowKeys());
}, },
getSelectRows: () => { getSelectRows: () => {
return toRaw(getTableInstance().getSelectRows()) return toRaw(getTableInstance().getSelectRows());
}, },
clearSelectedRowKeys: () => { clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys() getTableInstance().clearSelectedRowKeys();
}, },
setSelectedRowKeys: (keys: string[] | number[]) => { setSelectedRowKeys: (keys: string[] | number[]) => {
getTableInstance().setSelectedRowKeys(keys) getTableInstance().setSelectedRowKeys(keys);
}, },
getPaginationRef: () => { getPaginationRef: () => {
return getTableInstance().getPaginationRef() return getTableInstance().getPaginationRef();
}, },
getSize: () => { getSize: () => {
return toRaw(getTableInstance().getSize()) return toRaw(getTableInstance().getSize());
}, },
updateTableData: (index: number, key: string, value: any) => { updateTableData: (index: number, key: string, value: any) => {
return getTableInstance().updateTableData(index, key, value) return getTableInstance().updateTableData(index, key, value);
}, },
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => { deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
return getTableInstance().deleteTableDataRecord(rowKey) return getTableInstance().deleteTableDataRecord(rowKey);
}, },
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => { insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
return getTableInstance().insertTableDataRecord(record, index) return getTableInstance().insertTableDataRecord(record, index);
}, },
updateTableDataRecord: (rowKey: string | number, record: Recordable) => { updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
return getTableInstance().updateTableDataRecord(rowKey, record) return getTableInstance().updateTableDataRecord(rowKey, record);
}, },
findTableDataRecord: (rowKey: string | number) => { findTableDataRecord: (rowKey: string | number) => {
return getTableInstance().findTableDataRecord(rowKey) return getTableInstance().findTableDataRecord(rowKey);
}, },
getRowSelection: () => { getRowSelection: () => {
return toRaw(getTableInstance().getRowSelection()) return toRaw(getTableInstance().getRowSelection());
}, },
getCacheColumns: () => { getCacheColumns: () => {
return toRaw(getTableInstance().getCacheColumns()) return toRaw(getTableInstance().getCacheColumns());
}, },
getForm: () => { getForm: () => {
return unref(formRef) as unknown as FormActionType return unref(formRef) as unknown as FormActionType;
}, },
setShowPagination: async (show: boolean) => { setShowPagination: async (show: boolean) => {
getTableInstance().setShowPagination(show) getTableInstance().setShowPagination(show);
}, },
getShowPagination: () => { getShowPagination: () => {
return toRaw(getTableInstance().getShowPagination()) return toRaw(getTableInstance().getShowPagination());
}, },
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

@ -1,65 +1,58 @@
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue';
import type { BasicTableProps } from '../types/table' import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, toRaw } from 'vue' import { computed, unref, ref, toRaw } from 'vue';
import { ROW_KEY } from '../const' import { ROW_KEY } from '../const';
export function useTableExpand( export function useTableExpand(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>, tableData: Ref<Recordable[]>,
emit: EmitType, emit: EmitType,
) { ) {
const expandedRowKeys = ref<string[]>([]) const expandedRowKeys = ref<string[]>([]);
const getAutoCreateKey = computed(() => { const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
}) });
const getRowKey = computed(() => { const getRowKey = computed(() => {
const { rowKey } = unref(propsRef) const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
}) });
const getExpandOption = computed(() => { const getExpandOption = computed(() => {
const { isTreeTable } = unref(propsRef) const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return {} if (!isTreeTable) return {};
return { return {
expandedRowKeys: unref(expandedRowKeys), expandedRowKeys: unref(expandedRowKeys),
onExpandedRowsChange: (keys: string[]) => { onExpandedRowsChange: (keys: string[]) => {
expandedRowKeys.value = keys expandedRowKeys.value = keys;
emit('expanded-rows-change', keys) emit('expanded-rows-change', keys);
}, },
} };
}) });
function expandAll() { function expandAll() {
const keys = getAllKeys() const keys = getAllKeys();
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);
toRaw(data || unref(tableData)).forEach((item) => { toRaw(data || unref(tableData)).forEach((item) => {
keys.push(item[unref(getRowKey) as string]) keys.push(item[unref(getRowKey) as string]);
const children = item[childrenColumnName || 'children'] const children = item[childrenColumnName || 'children'];
if (children?.length) { if (children?.length) {
keys.push(...getAllKeys(children)) keys.push(...getAllKeys(children));
} }
}) });
return keys return keys;
} }
function collapseAll() { function collapseAll() {
expandedRowKeys.value = [] expandedRowKeys.value = [];
} }
return { getExpandOption, expandAll, expandRows, collapseAll } return { getExpandOption, expandAll, collapseAll };
} }

View File

@ -1,56 +1,57 @@
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue';
import type { BasicTableProps } from '../types/table' import type { BasicTableProps } from '../types/table';
import { unref, computed, h, nextTick, watchEffect } from 'vue' import { unref, computed, h, nextTick, watchEffect } from 'vue';
import TableFooter from '../components/TableFooter.vue' import TableFooter from '../components/TableFooter.vue';
import { useEventListener } from '/@/hooks/event/useEventListener' import { useEventListener } from '/@/hooks/event/useEventListener';
export function useTableFooter( export function useTableFooter(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
scrollRef: ComputedRef<{ scrollRef: ComputedRef<{
x: string | number | true x: string | number | true;
y: string | number | null y: Nullable<number>;
scrollToFirstRowOnChange: boolean scrollToFirstRowOnChange: boolean;
}>, }>,
tableElRef: Ref<ComponentRef>, tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable>, getDataSourceRef: ComputedRef<Recordable>,
) { ) {
const getIsEmptyData = computed(() => { const getIsEmptyData = computed(() => {
return (unref(getDataSourceRef) || []).length === 0 return (unref(getDataSourceRef) || []).length === 0;
}) });
const getFooterProps = computed((): Recordable | undefined => { const getFooterProps = computed((): Recordable | undefined => {
const { summaryFunc, showSummary, summaryData } = unref(propsRef) const { summaryFunc, showSummary, summaryData } = unref(propsRef);
return showSummary && !unref(getIsEmptyData) return showSummary && !unref(getIsEmptyData)
? () => h(TableFooter, { summaryFunc, summaryData, scroll: unref(scrollRef) }) ? () => h(TableFooter, { summaryFunc, summaryData, scroll: unref(scrollRef) })
: undefined : undefined;
}) });
watchEffect(() => { watchEffect(() => {
handleSummary() handleSummary();
}) });
function handleSummary() { function handleSummary() {
const { showSummary } = unref(propsRef) const { showSummary } = unref(propsRef);
if (!showSummary || unref(getIsEmptyData)) return if (!showSummary || unref(getIsEmptyData)) return;
nextTick(() => { nextTick(() => {
const tableEl = unref(tableElRef) const tableEl = unref(tableElRef);
if (!tableEl) return if (!tableEl) return;
const bodyDom = tableEl.$el.querySelector('.ant-table-content') const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
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-content', '.ant-table-footer .ant-table-body',
) as HTMLDivElement ) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft footerBodyDom.scrollLeft = bodyDom.scrollLeft;
}, },
wait: 0, wait: 0,
options: true, options: true,
}) });
}) });
} }
return { getFooterProps } return { getFooterProps };
} }

View File

@ -1,161 +1,135 @@
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table' import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
import { Ref, ComputedRef, ref } from 'vue' import type { Ref, ComputedRef } from 'vue';
import { computed, unref, nextTick, watch } from 'vue' import { computed, unref, ref, 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';
import { useModalContext } from '/@/components/Modal' import { useModalContext } from '/@/components/Modal';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated' import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core';
export function useTableScroll( export function useTableScroll(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
tableElRef: Ref<ComponentRef>, tableElRef: Ref<ComponentRef>,
columnsRef: ComputedRef<BasicColumn[]>, columnsRef: ComputedRef<BasicColumn[]>,
rowSelectionRef: ComputedRef<TableRowSelection | null>, rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
getDataSourceRef: ComputedRef<Recordable[]>, getDataSourceRef: ComputedRef<Recordable[]>,
wrapRef: Ref<HTMLElement | null>,
formRef: Ref<ComponentRef>,
) { ) {
const tableHeightRef: Ref<Nullable<number | string>> = ref(167) const tableHeightRef: Ref<Nullable<number>> = ref(null);
const modalFn = useModalContext()
const modalFn = useModalContext();
// Greater than animation time 280 // Greater than animation time 280
const debounceRedoHeight = useDebounceFn(redoHeight, 100) const debounceRedoHeight = useDebounceFn(redoHeight, 100);
const getCanResize = computed(() => { const getCanResize = computed(() => {
const { canResize, scroll } = unref(propsRef) const { canResize, scroll } = unref(propsRef);
return canResize && !(scroll || {}).y return canResize && !(scroll || {}).y;
}) });
watch( watch(
() => [unref(getCanResize), unref(getDataSourceRef)?.length], () => [unref(getCanResize), unref(getDataSourceRef)?.length],
() => { () => {
debounceRedoHeight() debounceRedoHeight();
}, },
{ {
flush: 'post', flush: 'post',
}, },
) );
function redoHeight() { function redoHeight() {
nextTick(() => { nextTick(() => {
calcTableHeight() calcTableHeight();
}) });
} }
function setHeight(height: number) { function setHeight(heigh: number) {
tableHeightRef.value = height tableHeightRef.value = heigh;
// 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?.();
} }
// No need to repeat queries // No need to repeat queries
let paginationEl: HTMLElement | null let paginationEl: HTMLElement | null;
let footerEl: HTMLElement | null let footerEl: HTMLElement | null;
let bodyEl: HTMLElement | null let bodyEl: HTMLElement | null;
async function calcTableHeight() { async function calcTableHeight() {
const { resizeHeightOffset, pagination, maxHeight, isCanResizeParent, useSearchForm } = const { resizeHeightOffset, pagination, maxHeight } = unref(propsRef);
unref(propsRef) const tableData = unref(getDataSourceRef);
const tableData = unref(getDataSourceRef)
const table = unref(tableElRef) const table = unref(tableElRef);
if (!table) return if (!table) return;
const tableEl: Element = table.$el const tableEl: Element = table.$el;
if (!tableEl) return if (!tableEl) return;
if (!bodyEl) { if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body') bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return if (!bodyEl) return;
} }
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
if (hasScrollBarY) { if (hasScrollBarY) {
tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.remove('hide-scrollbar-y') tableEl.classList.contains('hide-scrollbar-y') &&
tableEl.classList.remove('hide-scrollbar-y');
} else { } else {
!tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.add('hide-scrollbar-y') !tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.add('hide-scrollbar-y');
} }
if (hasScrollBarX) { if (hasScrollBarX) {
tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.remove('hide-scrollbar-x') tableEl.classList.contains('hide-scrollbar-x') &&
tableEl.classList.remove('hide-scrollbar-x');
} else { } else {
!tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x') !tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
} }
bodyEl!.style.height = 'unset' bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return if (!unref(getCanResize) || 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)) {
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
if (paginationEl) { if (paginationEl) {
const offsetHeight = paginationEl.offsetHeight const offsetHeight = paginationEl.offsetHeight;
paginationHeight += offsetHeight || 0 paginationHeight += offsetHeight || 0;
} else { } else {
// TODO First fix 24 // TODO First fix 24
paginationHeight += 24 paginationHeight += 24;
} }
} else { } else {
paginationHeight = -8 paginationHeight = -8;
} }
let footerHeight = 0 let footerHeight = 0;
if (!isBoolean(pagination)) { if (!isBoolean(pagination)) {
if (!footerEl) { if (!footerEl) {
footerEl = tableEl.querySelector('.ant-table-footer') as HTMLElement footerEl = tableEl.querySelector('.ant-table-footer') as HTMLElement;
} else { } else {
const offsetHeight = footerEl.offsetHeight const offsetHeight = footerEl.offsetHeight;
footerHeight += offsetHeight || 0 footerHeight += offsetHeight || 0;
} }
} }
let headerHeight = 0 let headerHeight = 0;
if (headEl) { if (headEl) {
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 =
@ -164,55 +138,56 @@ export function useTableScroll(
paddingHeight - paddingHeight -
paginationHeight - paginationHeight -
footerHeight - footerHeight -
headerHeight headerHeight;
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height
setHeight(height)
bodyEl!.style.height = `${height}px` height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
setHeight(height);
bodyEl!.style.height = `${height}px`;
} }
useWindowSizeFn(calcTableHeight, 280) useWindowSizeFn(calcTableHeight, 280);
onMountedOrActivated(() => { onMountedOrActivated(() => {
calcTableHeight() calcTableHeight();
nextTick(() => { nextTick(() => {
debounceRedoHeight() debounceRedoHeight();
}) });
}) });
const getScrollX = computed(() => { const getScrollX = computed(() => {
let width = 0 let width = 0;
if (unref(rowSelectionRef)) { if (unref(rowSelectionRef)) {
width += 60 width += 60;
} }
// TODO props ?? 0; // TODO props ?? 0;
const NORMAL_WIDTH = 150 const NORMAL_WIDTH = 150;
const columns = unref(columnsRef).filter((item) => !item.defaultHidden) const columns = unref(columnsRef).filter((item) => !item.defaultHidden);
columns.forEach((item) => { columns.forEach((item) => {
width += Number.parseFloat(item.width as string) || 0 width += Number.parseInt(item.width as string) || 0;
}) });
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width')) const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
const len = unsetWidthColumns.length const len = unsetWidthColumns.length;
if (len !== 0) { if (len !== 0) {
width += len * NORMAL_WIDTH width += len * NORMAL_WIDTH;
} }
const table = unref(tableElRef) const table = unref(tableElRef);
const tableWidth = table?.$el?.offsetWidth ?? 0 const tableWidth = table?.$el?.offsetWidth ?? 0;
return tableWidth > width ? '100%' : width return tableWidth > width ? '100%' : width;
}) });
const getScrollRef = computed(() => { const getScrollRef = computed(() => {
const tableHeight = unref(tableHeightRef) const tableHeight = unref(tableHeightRef);
const { canResize, scroll } = unref(propsRef) const { canResize, scroll } = unref(propsRef);
return { return {
x: unref(getScrollX), x: unref(getScrollX),
y: canResize ? tableHeight : null, y: canResize ? tableHeight : null,
scrollToFirstRowOnChange: false, scrollToFirstRowOnChange: false,
...scroll, ...scroll,
} };
}) });
return { getScrollRef, redoHeight } return { getScrollRef, redoHeight };
} }

View File

@ -1,5 +1,5 @@
import type { PropType } from 'vue' import type { PropType } from 'vue';
import type { PaginationProps } from './types/pagination' import type { PaginationProps } from './types/pagination';
import type { import type {
BasicColumn, BasicColumn,
FetchSetting, FetchSetting,
@ -8,17 +8,16 @@ import type {
TableCustomRecord, TableCustomRecord,
TableRowSelection, TableRowSelection,
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: { type: Boolean, default: true }, clickToRowSelect: propTypes.bool.def(true),
isTreeTable: Boolean, isTreeTable: propTypes.bool.def(false),
tableSetting: propTypes.shape<TableSetting>({}), tableSetting: propTypes.shape<TableSetting>({}),
inset: Boolean, inset: propTypes.bool,
sortFn: { sortFn: {
type: Function as PropType<(sortInfo: SorterResult) => any>, type: Function as PropType<(sortInfo: SorterResult) => any>,
default: DEFAULT_SORT_FN, default: DEFAULT_SORT_FN,
@ -27,10 +26,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: Boolean, showTableSetting: propTypes.bool,
autoCreateKey: { type: Boolean, default: true }, autoCreateKey: propTypes.bool.def(true),
striped: { type: Boolean, default: true }, striped: propTypes.bool.def(true),
showSummary: Boolean, showSummary: propTypes.bool,
summaryFunc: { summaryFunc: {
type: [Function, Array] as PropType<(...arg: any[]) => any[]>, type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
default: null, default: null,
@ -40,7 +39,7 @@ export const basicProps = {
default: null, default: null,
}, },
indentSize: propTypes.number.def(24), indentSize: propTypes.number.def(24),
canColDrag: { type: Boolean, default: true }, canColDrag: propTypes.bool.def(true),
api: { api: {
type: Function as PropType<(...arg: any[]) => Promise<any>>, type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null, default: null,
@ -60,12 +59,12 @@ export const basicProps = {
fetchSetting: { fetchSetting: {
type: Object as PropType<FetchSetting>, type: Object as PropType<FetchSetting>,
default: () => { default: () => {
return FETCH_SETTING return FETCH_SETTING;
}, },
}, },
// 立即请求接口 // 立即请求接口
immediate: { type: Boolean, default: true }, immediate: propTypes.bool.def(true),
emptyDataIsShowTable: { type: Boolean, default: true }, emptyDataIsShowTable: propTypes.bool.def(true),
// 额外的请求参数 // 额外的请求参数
searchInfo: { searchInfo: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable>,
@ -87,7 +86,7 @@ export const basicProps = {
type: [Array] as PropType<BasicColumn[]>, type: [Array] as PropType<BasicColumn[]>,
default: () => [], default: () => [],
}, },
showIndexColumn: { type: Boolean, default: true }, showIndexColumn: propTypes.bool.def(true),
indexColumnProps: { indexColumnProps: {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
@ -96,9 +95,8 @@ export const basicProps = {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
}, },
ellipsis: { type: Boolean, default: true }, ellipsis: propTypes.bool.def(true),
isCanResizeParent: { type: Boolean, default: false }, canResize: propTypes.bool.def(true),
canResize: { type: Boolean, default: true },
clearSelectOnPageChange: propTypes.bool, clearSelectOnPageChange: propTypes.bool,
resizeHeightOffset: propTypes.number.def(0), resizeHeightOffset: propTypes.number.def(0),
rowSelection: { rowSelection: {
@ -137,10 +135,10 @@ export const basicProps = {
beforeEditSubmit: { beforeEditSubmit: {
type: Function as PropType< type: Function as PropType<
(data: { (data: {
record: Recordable record: Recordable;
index: number index: number;
key: string | number key: string | number;
value: any value: any;
}) => Promise<any> }) => Promise<any>
>, >,
}, },
@ -148,4 +146,4 @@ export const basicProps = {
type: String as PropType<SizeType>, type: String as PropType<SizeType>,
default: DEFAULT_SIZE, default: DEFAULT_SIZE,
}, },
} };

View File

@ -3,12 +3,8 @@ 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

@ -1,115 +1,99 @@
import Pagination from 'ant-design-vue/lib/pagination' import Pagination from 'ant-design-vue/lib/pagination';
import { VNodeChild } from 'vue' import { VNodeChild } from 'vue';
interface PaginationRenderProps { interface PaginationRenderProps {
page: number page: number;
type: 'page' | 'prev' | 'next' type: 'page' | 'prev' | 'next';
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?: PaginationPositon[] position?: 'top' | 'bottom' | 'both';
} }
export interface PaginationProps { export interface PaginationProps {
/** /**
* total number of data items * total number of data items
* @default 0 * @default 0
* @type number * @type number
*/ */
total?: number total?: number;
/** /**
* default initial page number * default initial page number
* @default 1 * @default 1
* @type number * @type number
*/ */
defaultCurrent?: number defaultCurrent?: number;
/** /**
* current page number * current page number
* @type number * @type number
*/ */
current?: number current?: number;
/** /**
* default number of data items per page * default number of data items per page
* @default 10 * @default 10
* @type number * @type number
*/ */
defaultPageSize?: number defaultPageSize?: number;
/** /**
* number of data items per page * number of data items per page
* @type number * @type number
*/ */
pageSize?: number pageSize?: number;
/** /**
* Whether to hide pager on single page * Whether to hide pager on single page
* @default false * @default false
* @type boolean * @type boolean
*/ */
hideOnSinglePage?: boolean hideOnSinglePage?: boolean;
/** /**
* determine whether pageSize can be changed * determine whether pageSize can be changed
* @default false * @default false
* @type boolean * @type boolean
*/ */
showSizeChanger?: boolean showSizeChanger?: boolean;
/** /**
* specify the sizeChanger options * specify the sizeChanger options
* @default ['10', '20', '30', '40'] * @default ['10', '20', '30', '40']
* @type string[] * @type string[]
*/ */
pageSizeOptions?: string[] pageSizeOptions?: string[];
/** /**
* determine whether you can jump to pages directly * determine whether you can jump to pages directly
* @default false * @default false
* @type boolean * @type boolean
*/ */
showQuickJumper?: boolean | object showQuickJumper?: boolean | object;
/** /**
* to display the total number and range * to display the total number and range
* @type Function * @type Function
*/ */
showTotal?: (total: number, range: [number, number]) => any showTotal?: (total: number, range: [number, number]) => any;
/** /**
* specify the size of Pagination, can be set to small * specify the size of Pagination, can be set to small
* @default '' * @default ''
* @type string * @type string
*/ */
size?: string size?: string;
/** /**
* whether to setting simple mode * whether to setting simple mode
* @type boolean * @type boolean
*/ */
simple?: boolean simple?: boolean;
/** /**
* to customize item innerHTML * to customize item innerHTML
* @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,17 +1,19 @@
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 { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface' import type {
import type { ColumnProps } from 'ant-design-vue/lib/table' ColumnProps,
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';
import { RoleEnum } from '/@/enums/roleEnum' import { RoleEnum } from '/@/enums/roleEnum';
export declare type SortOrder = 'ascend' | 'descend' export declare type SortOrder = 'ascend' | 'descend';
export interface TableCurrentDataSource<T = Recordable> { export interface TableCurrentDataSource<T = Recordable> {
currentDataSource: T[] currentDataSource: T[];
} }
export interface TableRowSelection<T = any> extends ITableRowSelection { export interface TableRowSelection<T = any> extends ITableRowSelection {
@ -19,289 +21,285 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
* Callback executed when selected rows change * Callback executed when selected rows change
* @type Function * @type Function
*/ */
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
/** /**
* Callback executed when select/deselect one row * Callback executed when select/deselect one row
* @type Function * @type Function
*/ */
onSelect?: (record: T, selected: boolean, selectedRows: Object[], nativeEvent: Event) => any onSelect?: (record: T, selected: boolean, selectedRows: Object[], nativeEvent: Event) => any;
/** /**
* Callback executed when select/deselect all rows * Callback executed when select/deselect all rows
* @type Function * @type Function
*/ */
onSelectAll?: (selected: boolean, selectedRows: T[], changeRows: T[]) => any onSelectAll?: (selected: boolean, selectedRows: T[], changeRows: T[]) => any;
/** /**
* Callback executed when row selection is inverted * Callback executed when row selection is inverted
* @type Function * @type Function
*/ */
onSelectInvert?: (selectedRows: string[] | number[]) => any onSelectInvert?: (selectedRows: string[] | number[]) => any;
} }
export interface TableCustomRecord<T> { export interface TableCustomRecord<T> {
record?: T record?: T;
index?: number index?: number;
} }
export interface ExpandedRowRenderRecord<T> extends TableCustomRecord<T> { export interface ExpandedRowRenderRecord<T> extends TableCustomRecord<T> {
indent?: number indent?: number;
expanded?: boolean expanded?: boolean;
} }
export interface ColumnFilterItem { export interface ColumnFilterItem {
text?: string text?: string;
value?: string value?: string;
children?: any children?: any;
} }
export interface TableCustomRecord<T = Recordable> { export interface TableCustomRecord<T = Recordable> {
record?: T record?: T;
index?: number index?: number;
} }
export interface SorterResult { export interface SorterResult {
column: ColumnProps column: ColumnProps;
order: SortOrder order: SortOrder;
field: string field: string;
columnKey: string columnKey: string;
} }
export interface FetchParams { export interface FetchParams {
searchInfo?: Recordable searchInfo?: Recordable;
page?: number page?: number;
sortInfo?: Recordable sortInfo?: Recordable;
filterInfo?: Recordable filterInfo?: Recordable;
} }
export interface GetColumnsParams { export interface GetColumnsParams {
ignoreIndex?: boolean ignoreIndex?: boolean;
ignoreAction?: boolean ignoreAction?: boolean;
sort?: boolean sort?: boolean;
} }
export type SizeType = 'default' | 'middle' | 'small' | 'large' export type SizeType = 'default' | 'middle' | 'small' | 'large';
export interface TableActionType { export interface TableActionType {
reload: (opt?: FetchParams) => Promise<void> reload: (opt?: FetchParams) => Promise<void>;
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 getSelectRowKeys: () => string[];
scrollTo: (pos: string) => void // pos: id | "top" | "bottom" deleteSelectRowByKey: (key: string) => void;
getSelectRowKeys: () => string[] setPagination: (info: Partial<PaginationProps>) => void;
deleteSelectRowByKey: (key: string) => void setTableData: <T = Recordable>(values: T[]) => void;
setPagination: (info: Partial<PaginationProps>) => void updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
setTableData: <T = Recordable>(values: T[]) => void deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void findTableDataRecord: (rowKey: string | number) => Recordable | void;
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void getColumns: (opt?: GetColumnsParams) => BasicColumn[];
findTableDataRecord: (rowKey: string | number) => Recordable | void setColumns: (columns: BasicColumn[] | string[]) => void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[] getDataSource: <T = Recordable>() => T[];
setColumns: (columns: BasicColumn[] | string[]) => void getRawDataSource: <T = Recordable>() => T;
getDataSource: <T = Recordable>() => T[] setLoading: (loading: boolean) => void;
getRawDataSource: <T = Recordable>() => T setProps: (props: Partial<BasicTableProps>) => void;
setLoading: (loading: boolean) => void redoHeight: () => void;
setProps: (props: Partial<BasicTableProps>) => void setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
redoHeight: () => void getPaginationRef: () => PaginationProps | boolean;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void getSize: () => SizeType;
getPaginationRef: () => PaginationProps | boolean getRowSelection: () => TableRowSelection<Recordable>;
getSize: () => SizeType getCacheColumns: () => BasicColumn[];
getRowSelection: () => TableRowSelection<Recordable> emit?: EmitType;
getCacheColumns: () => BasicColumn[] updateTableData: (index: number, key: string, value: any) => Recordable;
emit?: EmitType setShowPagination: (show: boolean) => Promise<void>;
updateTableData: (index: number, key: string, value: any) => Recordable getShowPagination: () => boolean;
setShowPagination: (show: boolean) => Promise<void> setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
getShowPagination: () => boolean
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void
} }
export interface FetchSetting { export interface FetchSetting {
// 请求接口当前页数 // 请求接口当前页数
pageField: string pageField: string;
// 每页显示多少条 // 每页显示多少条
sizeField: string sizeField: string;
// 请求结果列表字段 支持 a.b.c // 请求结果列表字段 支持 a.b.c
listField: string listField: string;
// 请求结果总数字段 支持 a.b.c // 请求结果总数字段 支持 a.b.c
totalField: string totalField: string;
} }
export interface TableSetting { export interface TableSetting {
redo?: boolean redo?: boolean;
size?: boolean size?: boolean;
setting?: boolean setting?: boolean;
fullScreen?: boolean fullScreen?: boolean;
} }
export interface BasicTableProps<T = any> { export interface BasicTableProps<T = any> {
// 点击行选中 // 点击行选中
clickToRowSelect?: boolean clickToRowSelect?: boolean;
isTreeTable?: boolean isTreeTable?: boolean;
// 自定义排序方法 // 自定义排序方法
sortFn?: (sortInfo: SorterResult) => any sortFn?: (sortInfo: SorterResult) => any;
// 排序方法 // 排序方法
filterFn?: (data: Partial<Recordable<string[]>>) => any filterFn?: (data: Partial<Recordable<string[]>>) => any;
// 取消表格的默认padding // 取消表格的默认padding
inset?: boolean inset?: boolean;
// 显示表格设置 // 显示表格设置
showTableSetting?: boolean showTableSetting?: boolean;
tableSetting?: TableSetting tableSetting?: TableSetting;
// 斑马纹 // 斑马纹
striped?: boolean striped?: boolean;
// 是否自动生成key // 是否自动生成key
autoCreateKey?: boolean autoCreateKey?: boolean;
// 计算合计行的方法 // 计算合计行的方法
summaryFunc?: (...arg: any) => Recordable[] summaryFunc?: (...arg: any) => Recordable[];
// 自定义合计表格内容 // 自定义合计表格内容
summaryData?: Recordable[] summaryData?: Recordable[];
// 是否显示合计行 // 是否显示合计行
showSummary?: boolean showSummary?: boolean;
// 是否可拖拽列 // 是否可拖拽列
canColDrag?: boolean canColDrag?: boolean;
// 接口请求对象 // 接口请求对象
api?: (...arg: any) => Promise<any> api?: (...arg: any) => Promise<any>;
// 请求之前处理参数 // 请求之前处理参数
beforeFetch?: Fn beforeFetch?: Fn;
// 自定义处理接口返回参数 // 自定义处理接口返回参数
afterFetch?: Fn afterFetch?: Fn;
// 查询条件请求之前处理 // 查询条件请求之前处理
handleSearchInfoFn?: Fn handleSearchInfoFn?: Fn;
// 请求接口配置 // 请求接口配置
fetchSetting?: Partial<FetchSetting> fetchSetting?: Partial<FetchSetting>;
// 立即请求接口 // 立即请求接口
immediate?: boolean immediate?: boolean;
// 在开起搜索表单的时候,如果没有数据是否显示表格 // 在开起搜索表单的时候,如果没有数据是否显示表格
emptyDataIsShowTable?: boolean emptyDataIsShowTable?: boolean;
// 额外的请求参数 // 额外的请求参数
searchInfo?: Recordable searchInfo?: Recordable;
// 默认的排序参数 // 默认的排序参数
defSort?: Recordable defSort?: Recordable;
// 使用搜索表单 // 使用搜索表单
useSearchForm?: boolean useSearchForm?: boolean;
// 表单配置 // 表单配置
formConfig?: Partial<FormProps> formConfig?: Partial<FormProps>;
// 列配置 // 列配置
columns: BasicColumn[] columns: BasicColumn[];
// 是否显示序号列 // 是否显示序号列
showIndexColumn?: boolean showIndexColumn?: boolean;
// 序号列配置 // 序号列配置
indexColumnProps?: BasicColumn indexColumnProps?: BasicColumn;
actionColumn?: BasicColumn actionColumn?: BasicColumn;
// 文本超过宽度是否显示。。。 // 文本超过宽度是否显示。。。
ellipsis?: boolean ellipsis?: boolean;
// 是否继承父级高度(父级高度-表单高度-padding高度
isCanResizeParent?: boolean
// 是否可以自适应高度 // 是否可以自适应高度
canResize?: boolean canResize?: boolean;
// 自适应高度偏移, 计算结果-偏移量 // 自适应高度偏移, 计算结果-偏移量
resizeHeightOffset?: number resizeHeightOffset?: number;
// 在分页改变的时候清空选项 // 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean clearSelectOnPageChange?: boolean;
// //
rowKey?: string | ((record: Recordable) => string) rowKey?: string | ((record: Recordable) => string);
// 数据 // 数据
dataSource?: Recordable[] dataSource?: Recordable[];
// 标题右侧提示 // 标题右侧提示
titleHelpMessage?: string | string[] titleHelpMessage?: string | string[];
// 表格滚动最大高度 // 表格滚动最大高度
maxHeight?: number maxHeight?: number;
// 是否显示边框 // 是否显示边框
bordered?: boolean bordered?: boolean;
// 分页配置 // 分页配置
pagination?: PaginationProps | boolean pagination?: PaginationProps | boolean;
// loading加载 // loading加载
loading?: boolean loading?: boolean;
/** /**
* The column contains children to display * The column contains children to display
* @default 'children' * @default 'children'
* @type string | string[] * @type string | string[]
*/ */
childrenColumnName?: string childrenColumnName?: string;
/** /**
* Override default table elements * Override default table elements
* @type object * @type object
*/ */
components?: object components?: object;
/** /**
* Expand all rows initially * Expand all rows initially
* @default false * @default false
* @type boolean * @type boolean
*/ */
defaultExpandAllRows?: boolean defaultExpandAllRows?: boolean;
/** /**
* Initial expanded row keys * Initial expanded row keys
* @type string[] * @type string[]
*/ */
defaultExpandedRowKeys?: string[] defaultExpandedRowKeys?: string[];
/** /**
* Current expanded row keys * Current expanded row keys
* @type string[] * @type string[]
*/ */
expandedRowKeys?: string[] expandedRowKeys?: string[];
/** /**
* Expanded container render for each row * Expanded container render for each row
* @type Function * @type Function
*/ */
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element;
/** /**
* Customize row expand Icon. * Customize row expand Icon.
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
expandIcon?: Function | VNodeChild | JSX.Element expandIcon?: Function | VNodeChild | JSX.Element;
/** /**
* Whether to expand row by clicking anywhere in the whole row * Whether to expand row by clicking anywhere in the whole row
* @default false * @default false
* @type boolean * @type boolean
*/ */
expandRowByClick?: boolean expandRowByClick?: boolean;
/** /**
* The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0 * The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
*/ */
expandIconColumnIndex?: number expandIconColumnIndex?: number;
/** /**
* Table footer renderer * Table footer renderer
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
footer?: Function | VNodeChild | JSX.Element footer?: Function | VNodeChild | JSX.Element;
/** /**
* Indent size in pixels of tree data * Indent size in pixels of tree data
* @default 15 * @default 15
* @type number * @type number
*/ */
indentSize?: number indentSize?: number;
/** /**
* i18n text including filter, sort, empty text, etc * i18n text including filter, sort, empty text, etc
* @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' } * @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
* @type object * @type object
*/ */
locale?: object locale?: object;
/** /**
* Row's className * Row's className
* @type Function * @type Function
*/ */
rowClassName?: (record: TableCustomRecord<T>, index: number) => string rowClassName?: (record: TableCustomRecord<T>, index: number) => string;
/** /**
* Row selection config * Row selection config
* @type object * @type object
*/ */
rowSelection?: TableRowSelection rowSelection?: TableRowSelection;
/** /**
* Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area. * Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area.
@ -309,39 +307,39 @@ export interface BasicTableProps<T = any> {
* you need to add style .ant-table td { white-space: nowrap; }. * you need to add style .ant-table td { white-space: nowrap; }.
* @type object * @type object
*/ */
scroll?: { x?: number | true; y?: number } scroll?: { x?: number | true; y?: number };
/** /**
* Whether to show table header * Whether to show table header
* @default true * @default true
* @type boolean * @type boolean
*/ */
showHeader?: boolean showHeader?: boolean;
/** /**
* Size of table * Size of table
* @default 'default' * @default 'default'
* @type string * @type string
*/ */
size?: SizeType size?: SizeType;
/** /**
* Table title renderer * Table title renderer
* @type Function | ScopedSlot * @type Function | ScopedSlot
*/ */
title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string) title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string);
/** /**
* Set props on per header row * Set props on per header row
* @type Function * @type Function
*/ */
customHeaderRow?: (column: ColumnProps, index: number) => object customHeaderRow?: (column: ColumnProps, index: number) => object;
/** /**
* Set props on per row * Set props on per row
* @type Function * @type Function
*/ */
customRow?: (record: T, index: number) => object customRow?: (record: T, index: number) => object;
/** /**
* `table-layout` attribute of table element * `table-layout` attribute of table element
@ -350,14 +348,14 @@ export interface BasicTableProps<T = any> {
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout * @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout
* @version 1.5.0 * @version 1.5.0
*/ */
tableLayout?: 'auto' | 'fixed' | string tableLayout?: 'auto' | 'fixed' | string;
/** /**
* the render container of dropdowns in table * the render container of dropdowns in table
* @param triggerNode * @param triggerNode
* @version 1.5.0 * @version 1.5.0
*/ */
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
/** /**
* Data can be changed again before rendering. * Data can be changed again before rendering.
@ -366,7 +364,7 @@ export interface BasicTableProps<T = any> {
* *
* @version 1.5.4 * @version 1.5.4
*/ */
transformCellText?: Function transformCellText?: Function;
/** /**
* Callback executed before editable cell submit value, not for row-editor * Callback executed before editable cell submit value, not for row-editor
@ -374,11 +372,11 @@ export interface BasicTableProps<T = any> {
* The cell will not submit data while callback return false * The cell will not submit data while callback return false
*/ */
beforeEditSubmit?: (data: { beforeEditSubmit?: (data: {
record: Recordable record: Recordable;
index: number index: number;
key: string | number key: string | number;
value: any value: any;
}) => Promise<any> }) => Promise<any>;
/** /**
* Callback executed when pagination, filters or sorter is changed * Callback executed when pagination, filters or sorter is changed
@ -387,7 +385,7 @@ export interface BasicTableProps<T = any> {
* @param sorter * @param sorter
* @param currentDataSource * @param currentDataSource
*/ */
onChange?: (pagination: any, filters: any, sorter: any, extra: any) => void onChange?: (pagination: any, filters: any, sorter: any, extra: any) => void;
/** /**
* Callback executed when the row expand icon is clicked * Callback executed when the row expand icon is clicked
@ -395,84 +393,68 @@ export interface BasicTableProps<T = any> {
* @param expanded * @param expanded
* @param record * @param record
*/ */
onExpand?: (expande: boolean, record: T) => void onExpand?: (expande: boolean, record: T) => void;
/** /**
* Callback executed when the expanded rows change * Callback executed when the expanded rows change
* @param expandedRows * @param expandedRows
*/ */
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
onColumnsChange?: (data: ColumnChangeParam[]) => void onColumnsChange?: (data: ColumnChangeParam[]) => void;
} }
export type CellFormat = export type CellFormat =
| string | string
| ((text: string, record: Recordable, index: number) => string | number) | ((text: string, record: Recordable, index: number) => string | number)
| Map<string | number, any> | Map<string | number, any>;
// @ts-ignore // @ts-ignore
export interface BasicColumn extends ColumnProps<Recordable> { export interface BasicColumn extends ColumnProps {
children?: BasicColumn[] children?: BasicColumn[];
filters?: { filters?: {
text: string text: string;
value: string value: string;
children?: children?:
| unknown[] | unknown[]
| (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[])) | (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
}[] }[];
// //
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION' flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
customTitle?: VueNode customTitle?: VueNode;
slots?: Recordable slots?: Recordable;
// Whether to hide the column by default, it can be displayed in the column configuration // Whether to hide the column by default, it can be displayed in the column configuration
defaultHidden?: boolean defaultHidden?: boolean;
// Help text for table column header // Help text for table column header
helpMessage?: string | string[] helpMessage?: string | string[];
format?: CellFormat format?: CellFormat;
// Editable // Editable
edit?: boolean edit?: boolean;
editRow?: boolean editRow?: boolean;
editable?: boolean editable?: boolean;
editComponent?: ComponentType editComponent?: ComponentType;
editComponentProps?: editComponentProps?: Recordable;
| ((opt: { editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
text: string | number | boolean | Recordable editValueMap?: (value: any) => string;
record: Recordable onEditRow?: () => void;
column: BasicColumn
index: number
}) => Recordable)
| Recordable
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>)
editValueMap?: (value: any) => string
onEditRow?: () => void
// 权限编码控制是否显示 // 权限编码控制是否显示
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 = {
dataIndex: string dataIndex: string;
fixed: boolean | 'left' | 'right' | undefined fixed: boolean | 'left' | 'right' | undefined;
visible: boolean visible: boolean;
} };
export interface InnerHandlers { export interface InnerHandlers {
onColumnsChange: (data: ColumnChangeParam[]) => void onColumnsChange: (data: ColumnChangeParam[]) => void;
} }

View File

@ -1,39 +1,26 @@
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes' import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip' import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
import { RoleEnum } from '/@/enums/roleEnum' import { RoleEnum } from '/@/enums/roleEnum';
export interface ActionItem extends ButtonProps { export interface ActionItem extends ButtonProps {
onClick?: Fn onClick?: Fn;
label?: string label?: string;
color?: 'success' | 'error' | 'warning' color?: 'success' | 'error' | 'warning';
icon?: string icon?: string;
popConfirm?: PopConfirm popConfirm?: PopConfirm;
disabled?: boolean disabled?: boolean;
divider?: boolean divider?: boolean;
// 权限编码控制是否显示 // 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[] auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示 // 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean) ifShow?: boolean | ((action: ActionItem) => boolean);
tooltip?: string | TooltipProps tooltip?: string | TooltipProps;
} }
export interface PopConfirm { export interface PopConfirm {
title: string title: string;
okText?: string okText?: string;
cancelText?: string cancelText?: string;
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>
<Space> <a-button-group>
<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,7 +18,8 @@
</template> </template>
</a-button> </a-button>
</Tooltip> </Tooltip>
</Space> </a-button-group>
<UploadModal <UploadModal
v-bind="bindValue" v-bind="bindValue"
:previewFileList="fileList" :previewFileList="fileList"
@ -36,72 +37,72 @@
</div> </div>
</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 { Icon } from '/@/components/Icon' import UploadModal from './UploadModal.vue';
import { Tooltip, Space } from 'ant-design-vue' import UploadPreviewModal from './UploadPreviewModal.vue';
import { useModal } from '/@/components/Modal' import { Icon } from '/@/components/Icon';
import { uploadContainerProps } from './props' import { Tooltip } from 'ant-design-vue';
import { omit } from 'lodash-es' import { useModal } from '/@/components/Modal';
import { useI18n } from '/@/hooks/web/useI18n' import { uploadContainerProps } from './props';
import { isArray } from '/@/utils/is' import { omit } from 'lodash-es';
import UploadModal from './UploadModal.vue' import { useI18n } from '/@/hooks/web/useI18n';
import UploadPreviewModal from './UploadPreviewModal.vue' import { isArray } from '/@/utils/is';
export default defineComponent({ export default defineComponent({
name: 'BasicUpload', name: 'BasicUpload',
components: { UploadModal, Space, UploadPreviewModal, Icon, Tooltip }, components: { UploadModal, UploadPreviewModal, Icon, Tooltip },
props: uploadContainerProps, props: uploadContainerProps,
emits: ['change', 'delete', 'preview-delete', 'update:value'], emits: ['change', 'delete', 'preview-delete', 'update:value'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const { t } = useI18n() const { t } = useI18n();
// modal // modal
const [registerUploadModal, { openModal: openUploadModal }] = useModal() const [registerUploadModal, { openModal: openUploadModal }] = useModal();
// modal // modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal() const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const fileList = ref<string[]>([]) const fileList = ref<string[]>([]);
const showPreview = computed(() => { const showPreview = computed(() => {
const { emptyHidePreview } = props const { emptyHidePreview } = props;
if (!emptyHidePreview) return true if (!emptyHidePreview) return true;
return emptyHidePreview ? fileList.value.length > 0 : true return emptyHidePreview ? fileList.value.length > 0 : true;
}) });
const bindValue = computed(() => { const bindValue = computed(() => {
const value = { ...attrs, ...props } const value = { ...attrs, ...props };
return omit(value, 'onChange') return omit(value, 'onChange');
}) });
watch( watch(
() => props.value, () => props.value,
(value = []) => { (value = []) => {
fileList.value = isArray(value) ? value : [] fileList.value = isArray(value) ? value : [];
}, },
{ immediate: true }, { immediate: true },
) );
// modal // modal
function handleChange(urls: string[]) { function handleChange(urls: string[]) {
fileList.value = [...unref(fileList), ...(urls || [])] fileList.value = [...unref(fileList), ...(urls || [])];
emit('update:value', fileList.value) emit('update:value', fileList.value);
emit('change', fileList.value) emit('change', fileList.value);
} }
// modal // modal
function handlePreviewChange(urls: string[]) { function handlePreviewChange(urls: string[]) {
fileList.value = [...(urls || [])] fileList.value = [...(urls || [])];
emit('update:value', fileList.value) emit('update:value', fileList.value);
emit('change', fileList.value) emit('change', fileList.value);
} }
function handleDelete(record: Recordable) { function handleDelete(record: Recordable) {
emit('delete', record) emit('delete', record);
} }
function handlePreviewDelete(url: string) { function handlePreviewDelete(url: string) {
emit('preview-delete', url) emit('preview-delete', url);
} }
return { return {
@ -117,7 +118,7 @@
handleDelete, handleDelete,
handlePreviewDelete, handlePreviewDelete,
t, t,
} };
}, },
}) });
</script> </script>

View File

@ -9,7 +9,7 @@
:closeFunc="handleCloseFunc" :closeFunc="handleCloseFunc"
:maskClosable="false" :maskClosable="false"
:keyboard="false" :keyboard="false"
class="upload-modal" wrapClassName="upload-modal"
:okButtonProps="getOkButtonProps" :okButtonProps="getOkButtonProps"
:cancelButtonProps="{ disabled: isUploadingRef }" :cancelButtonProps="{ disabled: isUploadingRef }"
> >
@ -31,7 +31,6 @@
: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">
@ -43,24 +42,24 @@
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, ref, toRefs, unref, computed, PropType } from 'vue' import { defineComponent, reactive, ref, toRefs, unref, computed, PropType } from 'vue';
import { Upload, Alert } from 'ant-design-vue' import { Upload, Alert } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal' import { BasicModal, useModalInner } from '/@/components/Modal';
// import { BasicTable, useTable } from '/@/components/Table'; // import { BasicTable, useTable } from '/@/components/Table';
// hooks // hooks
import { useUploadType } from './useUpload' import { useUploadType } from './useUpload';
import { useMessage } from '/@/hooks/web/useMessage' import { useMessage } from '/@/hooks/web/useMessage';
// types // types
import { FileItem, UploadResultStatus } from './typing' import { FileItem, UploadResultStatus } from './typing';
import { basicProps } from './props' import { basicProps } from './props';
import { createTableColumns, createActionColumn } from './data' import { createTableColumns, createActionColumn } from './data';
// utils // utils
import { checkImgType, getBase64WithFile } from './helper' import { checkFileType, 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';
import FileList from './FileList.vue' import FileList from './FileList.vue';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
components: { BasicModal, Upload, Alert, FileList }, components: { BasicModal, Upload, Alert, FileList },
@ -75,60 +74,68 @@
setup(props, { emit }) { setup(props, { emit }) {
const state = reactive<{ fileList: FileItem[] }>({ const state = reactive<{ fileList: FileItem[] }>({
fileList: [], fileList: [],
}) });
// //
const isUploadingRef = ref(false) const isUploadingRef = ref(false);
const fileListRef = ref<FileItem[]>([]) const fileListRef = ref<FileItem[]>([]);
const { accept, helpText, maxNumber, maxSize } = toRefs(props) const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const { t } = useI18n() const { t } = useI18n();
const [register, { closeModal }] = useModalInner() const [register, { closeModal }] = useModalInner();
const { getStringAccept, getHelpText } = useUploadType({ const { getAccept, getStringAccept, getHelpText } = useUploadType({
acceptRef: accept, acceptRef: accept,
helpTextRef: helpText, helpTextRef: helpText,
maxNumberRef: maxNumber, maxNumberRef: maxNumber,
maxSizeRef: maxSize, maxSizeRef: maxSize,
}) });
const { createMessage } = useMessage() const { createMessage } = useMessage();
const getIsSelectFile = computed(() => { const getIsSelectFile = computed(() => {
return ( return (
fileListRef.value.length > 0 && fileListRef.value.length > 0 &&
!fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS) !fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS)
) );
}) });
const getOkButtonProps = computed(() => { const getOkButtonProps = computed(() => {
const someSuccess = fileListRef.value.some( const someSuccess = fileListRef.value.some(
(item) => item.status === UploadResultStatus.SUCCESS, (item) => item.status === UploadResultStatus.SUCCESS,
) );
return { return {
disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess, disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess,
} };
}) });
const getUploadBtnText = computed(() => { const getUploadBtnText = computed(() => {
const someError = fileListRef.value.some((item) => item.status === UploadResultStatus.ERROR) const someError = fileListRef.value.some(
(item) => item.status === UploadResultStatus.ERROR,
);
return isUploadingRef.value return isUploadingRef.value
? t('component.upload.uploading') ? t('component.upload.uploading')
: someError : someError
? t('component.upload.reUploadFailed') ? t('component.upload.reUploadFailed')
: t('component.upload.startUpload') : t('component.upload.startUpload');
}) });
// //
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,
@ -136,7 +143,7 @@
name, name,
percent: 0, percent: 0,
type: name.split('.').pop(), type: name.split('.').pop(),
} };
// //
if (checkImgType(file)) { if (checkImgType(file)) {
// beforeUpload // beforeUpload
@ -148,19 +155,19 @@
thumbUrl, thumbUrl,
...commonItem, ...commonItem,
}, },
] ];
}) });
} else { } else {
fileListRef.value = [...unref(fileListRef), commonItem] fileListRef.value = [...unref(fileListRef), commonItem];
} }
return false return false;
} }
// //
function handleRemove(record: FileItem) { function handleRemove(record: FileItem) {
const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid) const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
index !== -1 && fileListRef.value.splice(index, 1) index !== -1 && fileListRef.value.splice(index, 1);
emit('delete', record) emit('delete', record);
} }
// //
@ -172,12 +179,12 @@
// } // }
async function uploadApiByItem(item: FileItem) { async function uploadApiByItem(item: FileItem) {
const { api } = props const { api } = props;
if (!api || !isFunction(api)) { if (!api || !isFunction(api)) {
return warn('upload api must exist and be a function') return warn('upload api must exist and be a function');
} }
try { try {
item.status = UploadResultStatus.UPLOADING item.status = UploadResultStatus.UPLOADING;
const { data } = await props.api?.( const { data } = await props.api?.(
{ {
data: { data: {
@ -188,87 +195,87 @@
filename: props.filename, filename: props.filename,
}, },
function onUploadProgress(progressEvent: ProgressEvent) { function onUploadProgress(progressEvent: ProgressEvent) {
const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0 const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
item.percent = complete item.percent = complete;
}, },
) );
item.status = UploadResultStatus.SUCCESS item.status = UploadResultStatus.SUCCESS;
item.responseData = data item.responseData = data;
return { return {
success: true, success: true,
error: null, error: null,
} };
} catch (e) { } catch (e) {
console.log(e) console.log(e);
item.status = UploadResultStatus.ERROR item.status = UploadResultStatus.ERROR;
return { return {
success: false, success: false,
error: e, error: e,
} };
} }
} }
// //
async function handleStartUpload() { async function handleStartUpload() {
const { maxNumber } = props const { maxNumber } = props;
if ((fileListRef.value.length + props.previewFileList?.length ?? 0) > maxNumber) { if ((fileListRef.value.length + props.previewFileList?.length ?? 0) > maxNumber) {
return createMessage.warning(t('component.upload.maxNumber', [maxNumber])) return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
} }
try { try {
isUploadingRef.value = true isUploadingRef.value = true;
// //
const uploadFileList = const uploadFileList =
fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [] fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [];
const data = await Promise.all( const data = await Promise.all(
uploadFileList.map((item) => { uploadFileList.map((item) => {
return uploadApiByItem(item) return uploadApiByItem(item);
}), }),
) );
isUploadingRef.value = false isUploadingRef.value = false;
// : // :
const errorList = data.filter((item: any) => !item.success) const errorList = data.filter((item: any) => !item.success);
if (errorList.length > 0) throw errorList if (errorList.length > 0) throw errorList;
} catch (e) { } catch (e) {
isUploadingRef.value = false isUploadingRef.value = false;
throw e throw e;
} }
} }
// //
function handleOk() { function handleOk() {
const { maxNumber } = props const { maxNumber } = props;
if (fileListRef.value.length > maxNumber) { if (fileListRef.value.length > maxNumber) {
return createMessage.warning(t('component.upload.maxNumber', [maxNumber])) return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
} }
if (isUploadingRef.value) { if (isUploadingRef.value) {
return createMessage.warning(t('component.upload.saveWarn')) return createMessage.warning(t('component.upload.saveWarn'));
} }
const fileList: string[] = [] const fileList: string[] = [];
for (const item of fileListRef.value) { for (const item of fileListRef.value) {
const { status, responseData } = item const { status, responseData } = item;
if (status === UploadResultStatus.SUCCESS && responseData) { if (status === UploadResultStatus.SUCCESS && responseData) {
fileList.push(responseData.url) fileList.push(responseData.url);
} }
} }
// //
if (fileList.length <= 0) { if (fileList.length <= 0) {
return createMessage.warning(t('component.upload.saveError')) return createMessage.warning(t('component.upload.saveError'));
} }
fileListRef.value = [] fileListRef.value = [];
closeModal() closeModal();
emit('change', fileList) emit('change', fileList);
} }
// //
async function handleCloseFunc() { async function handleCloseFunc() {
if (!isUploadingRef.value) { if (!isUploadingRef.value) {
fileListRef.value = [] fileListRef.value = [];
return true return true;
} else { } else {
createMessage.warning(t('component.upload.uploadWait')) createMessage.warning(t('component.upload.uploadWait'));
return false return false;
} }
} }
@ -291,9 +298,9 @@
getIsSelectFile, getIsSelectFile,
getUploadBtnText, getUploadBtnText,
t, t,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
.upload-modal { .upload-modal {

View File

@ -2,7 +2,7 @@
<BasicModal <BasicModal
width="800px" width="800px"
:title="t('component.upload.preview')" :title="t('component.upload.preview')"
class="upload-preview-modal" wrapClassName="upload-preview-modal"
v-bind="$attrs" v-bind="$attrs"
@register="register" @register="register"
:showOkBtn="false" :showOkBtn="false"
@ -11,30 +11,30 @@
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, watch, ref } from 'vue' import { defineComponent, watch, ref } from 'vue';
// import { BasicTable, useTable } from '/@/components/Table'; // import { BasicTable, useTable } from '/@/components/Table';
import FileList from './FileList.vue' import FileList from './FileList.vue';
import { BasicModal, useModalInner } from '/@/components/Modal' import { BasicModal, useModalInner } from '/@/components/Modal';
import { previewProps } from './props' import { previewProps } from './props';
import { PreviewFileItem } from './typing' import { PreviewFileItem } from './typing';
import { downloadByUrl } from '/@/utils/file/download' import { downloadByUrl } from '/@/utils/file/download';
import { createPreviewColumns, createPreviewActionColumn } from './data' import { createPreviewColumns, createPreviewActionColumn } from './data';
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n';
import { isArray } from '/@/utils/is' import { isArray } from '/@/utils/is';
export default defineComponent({ export default defineComponent({
components: { BasicModal, FileList }, components: { BasicModal, FileList },
props: previewProps, props: previewProps,
emits: ['list-change', 'register', 'delete'], emits: ['list-change', 'register', 'delete'],
setup(props, { emit }) { setup(props, { emit }) {
const [register, { closeModal }] = useModalInner() const [register, { closeModal }] = useModalInner();
const { t } = useI18n() const { t } = useI18n();
const fileListRef = ref<PreviewFileItem[]>([]) const fileListRef = ref<PreviewFileItem[]>([]);
watch( watch(
() => props.value, () => props.value,
(value) => { (value) => {
if (!isArray(value)) value = [] if (!isArray(value)) value = [];
fileListRef.value = value fileListRef.value = value
.filter((item) => !!item) .filter((item) => !!item)
.map((item) => { .map((item) => {
@ -42,22 +42,22 @@
url: item, url: item,
type: item.split('.').pop() || '', type: item.split('.').pop() || '',
name: item.split('/').pop() || '', name: item.split('/').pop() || '',
} };
}) });
}, },
{ immediate: true }, { immediate: true },
) );
// //
function handleRemove(record: PreviewFileItem) { function handleRemove(record: PreviewFileItem) {
const index = fileListRef.value.findIndex((item) => item.url === record.url) const index = fileListRef.value.findIndex((item) => item.url === record.url);
if (index !== -1) { if (index !== -1) {
const removed = fileListRef.value.splice(index, 1) const removed = fileListRef.value.splice(index, 1);
emit('delete', removed[0].url) emit('delete', removed[0].url);
emit( emit(
'list-change', 'list-change',
fileListRef.value.map((item) => item.url), fileListRef.value.map((item) => item.url),
) );
} }
} }
@ -71,8 +71,8 @@
// //
function handleDownload(record: PreviewFileItem) { function handleDownload(record: PreviewFileItem) {
const { url = '' } = record const { url = '' } = record;
downloadByUrl({ url }) downloadByUrl({ url });
} }
return { return {
@ -82,9 +82,9 @@
fileListRef, fileListRef,
columns: createPreviewColumns() as any[], columns: createPreviewColumns() as any[],
actionColumn: createPreviewActionColumn({ handleRemove, handleDownload }) as any, actionColumn: createPreviewActionColumn({ handleRemove, handleDownload }) as any,
} };
}, },
}) });
</script> </script>
<style lang="less"> <style lang="less">
.upload-preview-modal { .upload-preview-modal {

View File

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

View File

@ -1,5 +1,19 @@
// 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 {
@ -15,12 +29,23 @@
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;
@ -102,6 +127,13 @@
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,6 +1,12 @@
@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 {
@ -57,11 +63,3 @@ 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,8 +22,3 @@
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; overflow: visible !important;
overflow-x: hidden; overflow-x: hidden !important;
&.color-weak { &.color-weak {
filter: invert(80%); filter: invert(80%);
@ -40,5 +40,5 @@ button,
div, div,
svg, svg,
span { span {
outline: none; outline: none !important;
} }

View File

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

View File

@ -1,15 +1,3 @@
.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;
@ -17,7 +5,7 @@
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0%;
} }
/* fade-slide */ /* fade-slide */
@ -27,12 +15,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);
} }
@ -47,12 +35,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%);
} }
@ -63,12 +51,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);
} }
@ -83,11 +71,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);
} }
} }

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