Compare commits

...

3 Commits

Author SHA1 Message Date
ihzero 8aaaa449d6 3 2022-10-21 11:51:29 +08:00
ihzero dbdc655406 ant3 2022-10-21 11:51:11 +08:00
ihzero 1f265d6b85 修改 2022-10-20 14:53:47 +08:00
268 changed files with 14297 additions and 16252 deletions

View File

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

4
.gitignore vendored
View File

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

View File

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

View File

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

23
.vscode/settings.json vendored
View File

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

View File

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

View File

102
README.md
View File

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

View File

@ -1,33 +1,33 @@
import { generate } from '@ant-design/colors'; import { generate } from '@ant-design/colors'
export const primaryColor = '#4ba06c'; export const primaryColor = '#0960bd'
export const darkMode = 'light'; export const darkMode = 'light'
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 chalk from 'chalk'; import colors from 'picocolors'
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(
`${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`, `${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`,
); )
}); })
} }
generateIcon(); generateIcon()

View File

@ -1,45 +1,47 @@
/** /**
* 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 chalk from 'chalk'; import colors from 'picocolors'
import { getEnvConfig, getRootPath } from '../utils'; import { getEnvConfig, getRootPath } from '../utils'
import { getConfigFileName } from '../getConfigFileName'; import { getConfigFileName } from '../getConfigFileName'
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
const configStr = `${windowConf}=${JSON.stringify(config)}; let configStr = `${windowConf}=${JSON.stringify(config)};`
configStr += `
Object.freeze(${windowConf}); Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", { Object.defineProperty(window, "${configName}", {
configurable: false, configurable: false,
writable: false, writable: false,
}); });
`.replace(/\s/g, ''); `.replace(/\s/g, '')
fs.mkdirp(getRootPath(OUTPUT_DIR));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`); fs.mkdirp(getRootPath(OUTPUT_DIR))
console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n'); writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
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(chalk.red('configuration file configuration file failed to package:\n' + error)); console.log(colors.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 chalk from 'chalk'; import colors from 'picocolors'
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(`${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!')
} catch (error) { } catch (error) {
console.log(chalk.red('vite build error:\n' + error)); console.log(colors.red('vite build error:\n' + error))
process.exit(1); process.exit(1)
} }
}; }
runBuild(); runBuild()

View File

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

View File

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

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

View File

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

View File

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

View File

@ -3,15 +3,15 @@
* https://github.com/anncwb/vite-plugin-svg-icons * https://github.com/anncwb/vite-plugin-svg-icons
*/ */
import SvgIconsPlugin from 'vite-plugin-svg-icons'; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'; import path from 'path'
export function configSvgIconsPlugin(isBuild: boolean) { export function configSvgIconsPlugin(isBuild: boolean) {
const svgIconsPlugin = SvgIconsPlugin({ const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild, svgoOptions: isBuild,
// default // default
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 { Plugin } from 'vite'; import type { PluginOption } 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): Plugin[] { export function configThemePlugin(isBuild: boolean): PluginOption[] {
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): Plugin[] {
'alert-error-icon-color': '#a61d24', 'alert-error-icon-color': '#a61d24',
}, },
}), }),
]; ]
return plugin as unknown as Plugin[]; return plugin as unknown as PluginOption[]
} }

View File

@ -1,3 +1,23 @@
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, ''))
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '')
/** @type {import('cz-git').UserConfig} */
module.exports = { module.exports = {
ignores: [(commit) => commit.includes('init')], ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'], extends: ['@commitlint/config-conventional'],
@ -30,4 +50,58 @@ module.exports = {
], ],
], ],
}, },
}; prompt: {
/** @use `yarn commit :f` */
alias: {
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
b: 'build: bump dependencies',
c: 'chore: update config',
},
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
defaultScope: scopeComplete,
scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
allowCustomIssuePrefixs: false,
// English
typesAppend: [
{ value: 'wip', name: 'wip: work in process' },
{ value: 'workflow', name: 'workflow: workflow improvements' },
{ value: 'types', name: 'types: type definition file changes' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

View File

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

View File

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

@ -1,22 +0,0 @@
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 = '/logout', Logout = '/api/users/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.get({ url: Api.Logout }); return defHttp.delete({ url: Api.Logout });
} }

View File

@ -4,11 +4,11 @@
--> -->
<template> <template>
<Dropdown <Dropdown
placement="bottomCenter" placement="bottom"
:trigger="['click']" :trigger="['click']"
:dropMenuList="localeList" :dropMenuList="localeList"
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
@menuEvent="handleMenuEvent" @menu-event="handleMenuEvent"
overlayClassName="app-locale-picker-overlay" overlayClassName="app-locale-picker-overlay"
> >
<span class="cursor-pointer flex items-center"> <span class="cursor-pointer flex items-center">
@ -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,16 +23,17 @@
</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: '' },
@ -57,22 +58,26 @@
* 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() { function handleExpand(val: boolean) {
show.value = !show.value; show.value = isNil(val) ? !show.value : val
if (props.triggerWindowResize) { if (props.triggerWindowResize) {
// 200 milliseconds here is because the expansion has animation, // 200 milliseconds here is because the expansion has animation,
useTimeoutFn(triggerWindowResize, 200); useTimeoutFn(triggerWindowResize, 200)
} }
} }
defineExpose({
handleExpand,
})
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container'; @prefix-cls: ~'@{namespace}-collapse-container';

View File

@ -1,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 } from 'vue'; import type { FunctionalComponent, CSSProperties, PropType } from 'vue'
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'; import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon'
import { Menu, Divider } from 'ant-design-vue'; import { Menu, Divider } from 'ant-design-vue'
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,62 +39,65 @@
{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[]) {
return items.map((item) => { const visibleItems = items.filter((item) => !item.hidden)
const { disabled, label, children, divider = false } = item; return visibleItems.map((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 (
@ -104,9 +107,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`}>
@ -115,28 +118,24 @@
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 (
<Menu <div class={prefixCls}>
inlineIndent={12} <Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
mode="vertical" {renderMenuItem(items)}
class={prefixCls} </Menu>
ref={wrapRef} </div>
style={unref(getStyle)} )
> }
{renderMenuItem(items)}
</Menu>
);
};
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@default-height: 42px !important; @default-height: 42px !important;
@ -185,6 +184,9 @@
background-clip: padding-box; background-clip: padding-box;
user-select: none; user-select: none;
&__item {
margin: 0 !important;
}
.item-style(); .item-style();
.ant-divider { .ant-divider {

View File

@ -1,35 +1,36 @@
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
disabled?: boolean; hidden?: boolean
handler?: Fn; disabled?: boolean
divider?: boolean; handler?: Fn
children?: ContextMenuItem[]; divider?: boolean
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.wrapClassName = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls; opt.class = 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: heightStr, lineHeight: `calc(${heightStr} - 1px)`,
}; }
}); })
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,194 +1,193 @@
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,15 +1,17 @@
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 ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; export { default as ApiTree } from './src/components/ApiTree.vue'
export { default as ApiCascader } from './src/components/ApiCascader.vue'; export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'
export { default as ApiCascader } from './src/components/ApiCascader.vue'
export { default as ApiTransfer } from './src/components/ApiTransfer.vue'
export { BasicForm }; export { BasicForm }

View File

@ -37,59 +37,61 @@
</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'], emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({}); const formModel = reactive<Recordable>({})
const modalFn = useModalContext(); const modalFn = useModalContext()
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 [
@ -97,45 +99,47 @@
{ {
[`${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: moment.Moment[] = []; const def: any[] = []
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 schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[]; return cloneDeep(
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
)
} else { } else {
return schemas as FormSchema[]; return cloneDeep(schemas as FormSchema[])
} }
}); })
const { handleToggleAdvanced } = useAdvanced({ const { handleToggleAdvanced } = useAdvanced({
advanceState, advanceState,
@ -144,21 +148,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,
@ -170,7 +174,7 @@
updateSchema, updateSchema,
resetSchema, resetSchema,
appendSchemaByField, appendSchemaByField,
removeSchemaByFiled, removeSchemaByField,
resetFields, resetFields,
scrollToField, scrollToField,
} = useFormEvents({ } = useFormEvents({
@ -182,68 +186,77 @@
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()
} }
} }
} }
@ -255,19 +268,19 @@
updateSchema, updateSchema,
resetSchema, resetSchema,
setProps, setProps,
removeSchemaByFiled, removeSchemaByField,
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,
@ -287,9 +300,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,61 +19,65 @@ 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 ApiTreeSelect from './components/ApiTreeSelect.vue'; import ApiTree from './components/ApiTree.vue'
import ApiCascader from './components/ApiCascader.vue'; import ApiTreeSelect from './components/ApiTreeSelect.vue'
import { BasicUpload } from '/@/components/Upload'; import ApiCascader from './components/ApiCascader.vue'
import { StrengthMeter } from '/@/components/StrengthMeter'; import ApiTransfer from './components/ApiTransfer.vue'
import { IconPicker } from '/@/components/Icon'; import { BasicUpload } from '/@/components/Upload'
import { CountdownInput } from '/@/components/CountDown'; import { StrengthMeter } from '/@/components/StrengthMeter'
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('TreeSelect', TreeSelect); componentMap.set('ApiTree', ApiTree)
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('TreeSelect', TreeSelect)
componentMap.set('ApiRadioGroup', ApiRadioGroup); componentMap.set('ApiTreeSelect', ApiTreeSelect)
componentMap.set('Switch', Switch); componentMap.set('ApiRadioGroup', ApiRadioGroup)
componentMap.set('RadioButtonGroup', RadioButtonGroup); componentMap.set('Switch', Switch)
componentMap.set('RadioGroup', Radio.Group); componentMap.set('RadioButtonGroup', RadioButtonGroup)
componentMap.set('Checkbox', Checkbox); componentMap.set('RadioGroup', Radio.Group)
componentMap.set('CheckboxGroup', Checkbox.Group); componentMap.set('Checkbox', Checkbox)
componentMap.set('ApiCascader', ApiCascader); componentMap.set('CheckboxGroup', Checkbox.Group)
componentMap.set('Cascader', Cascader); componentMap.set('ApiCascader', ApiCascader)
componentMap.set('Slider', Slider); componentMap.set('Cascader', Cascader)
componentMap.set('Rate', Rate); componentMap.set('Slider', Slider)
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,127 +71,128 @@
}, },
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);
if (children) {
Reflect.set(item, childrenField, generatorOptions(children));
} }
prev.push(item); const children = Reflect.get(next, childrenField)
if (children) {
Reflect.set(item, childrenField, generatorOptions(children))
}
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
@dropdownVisibleChange="handleFetch" @dropdown-visible-change="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,86 +57,91 @@
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 && fetch(); props.immediate && !props.alwaysLoad && 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() { async function handleFetch(visible) {
if (!props.immediate && unref(isFirstLoad)) { if (visible) {
await fetch(); if (props.alwaysLoad) {
isFirstLoad.value = false; await fetch()
} else if (!props.immediate && unref(isFirstLoad)) {
await fetch()
isFirstLoad.value = false
}
} }
} }
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

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

View File

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

View File

@ -1,19 +1,18 @@
<script lang="tsx"> <script lang="tsx">
import type { PropType, Ref } from 'vue'; import type { PropType, Ref } from 'vue'
import type { FormActionType, FormProps } from '../types/form'; import { computed, defineComponent, toRefs, unref } from 'vue'
import type { FormSchema } from '../types/form'; import type { FormActionType, FormProps, FormSchema } from '../types/form'
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
import type { TableActionType } from '/@/components/Table'; import type { TableActionType } from '/@/components/Table'
import { defineComponent, computed, unref, toRefs } from 'vue'; import { Col, Divider, Form } from 'ant-design-vue'
import { Form, Col, Divider } from 'ant-design-vue'; import { componentMap } from '../componentMap'
import { componentMap } from '../componentMap'; import { BasicHelp } from '/@/components/Basic'
import { BasicHelp } from '/@/components/Basic'; import { isBoolean, isFunction, isNull } from '/@/utils/is'
import { isBoolean, isFunction, isNull } from '/@/utils/is'; import { getSlot } from '/@/utils/helper/tsxHelper'
import { getSlot } from '/@/utils/helper/tsxHelper'; import { createPlaceholderMessage, setComponentRuleType } from '../helper'
import { createPlaceholderMessage, setComponentRuleType } from '../helper'; import { cloneDeep, upperFirst } from 'lodash-es'
import { upperFirst, cloneDeep } from 'lodash-es'; import { useItemLabelWidth } from '../hooks/useLabelWidth'
import { useItemLabelWidth } from '../hooks/useLabelWidth'; import { useI18n } from '/@/hooks/web/useI18n'
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
name: 'BasicFormItem', name: 'BasicFormItem',
@ -47,18 +46,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,
@ -68,64 +67,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[] {
@ -136,31 +135,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') &&
@ -171,50 +170,63 @@
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) { /*
rules = [{ required: getRequired, validator }]; * 1若设置了required属性又没有其他的rules就创建一个验证规则
* 2若设置了required属性又存在其他的rules则只rules中不存在required属性时才添加验证required的规则
* 也就是说rules中的required优先级大于required
*/
if (getRequired) {
if (!rules || rules.length === 0) {
rules = [{ required: getRequired, validator }]
} else {
const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required'))
if (requiredIndex === -1) {
rules.push({ required: getRequired, validator })
}
}
} }
const requiredRuleIndex: number = rules.findIndex( const requiredRuleIndex: number = rules.findIndex(
(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() {
@ -224,109 +236,107 @@
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) const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : 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
@ -344,28 +354,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 && (
@ -373,8 +383,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,32 +41,34 @@ 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 type { ComputedRef, Ref } from 'vue'; import { ComputedRef, getCurrentInstance, Ref } from 'vue'
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form'
import { computed, unref, watch } from 'vue'; import { computed, unref, watch } from 'vue'
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'; import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'
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,102 +26,104 @@ export default function ({
formModel, formModel,
defaultValueRef, defaultValueRef,
}: UseAdvancedContext) { }: UseAdvancedContext) {
const { realWidthRef, screenEnum, screenRef } = useBreakpoint(); const vm = getCurrentInstance()
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)) {
@ -133,33 +135,36 @@ 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()
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true); advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan)
emit('advanced-change'); getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true)
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()
}); })
}, },
removeSchemaByFiled: async (field: string | string[]) => { removeSchemaByField: async (field: string | string[]) => {
unref(formRef)?.removeSchemaByFiled(field); unref(formRef)?.removeSchemaByField(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 } from 'vue'; import { unref, toRaw, nextTick } from 'vue'
import { isArray, isFunction, isObject, isString } from '/@/utils/is'; import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is'
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils'
import { dateItemType, handleInputNumberValue } from '../helper'; import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper'
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil'
import { cloneDeep, uniqBy } from 'lodash-es'; import { cloneDeep, uniqBy } from 'lodash-es'
import { error } from '/@/utils/log'; import { error } from '/@/utils/log'
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,18 +30,22 @@ 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) => {
formModel[key] = defaultValueRef.value[key]; const schema = unref(getSchema).find((item) => item.field === key)
}); const isInput = schema?.component && defaultValueComponents.includes(schema.component)
clearValidate(); const defaultValue = cloneDeep(defaultValueRef.value[key])
emit('reset', toRaw(formModel)); formModel[key] = isInput ? defaultValue || '' : defaultValue
submitOnReset && handleSubmit(); })
nextTick(() => clearValidate())
emit('reset', toRaw(formModel))
submitOnReset && handleSubmit()
} }
/** /**
@ -50,70 +54,89 @@ 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)
const validKeys: string[] = []; // key 支持 a.b.c 的嵌套写法
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 removeSchemaByFiled(fields: string | string[]): Promise<void> { async function removeSchemaByField(fields: string | string[]): Promise<void> {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema)); const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
if (!fields) { if (!fields) {
return; return
} }
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) {
_removeSchemaByFiled(field, schemaList); _removeSchemaByFeild(field, schemaList)
} }
schemaRef.value = schemaList; schemaRef.value = schemaList
} }
/** /**
* @description: Delete based on field name * @description: Delete based on field name
*/ */
function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void { function _removeSchemaByFeild(field: string, schemaList: FormSchema[]): void {
if (isString(field)) { if (isString(field)) {
const index = schemaList.findIndex((schema) => schema.field === field); const index = schemaList.findIndex((schema) => schema.field === field)
if (index !== -1) { if (index !== -1) {
delete formModel[field]; delete formModel[field]
schemaList.splice(index, 1); schemaList.splice(index, 1)
} }
} }
} }
@ -122,83 +145,110 @@ 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
return; _setDefaultValue(schema)
return
} }
if (index !== -1) { if (index !== -1) {
schemaList.splice(index + 1, 0, schema); schemaList.splice(index + 1, 0, schema)
} }
schemaRef.value = schemaList; _setDefaultValue(schema)
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)
} }
}); })
}); })
schemaRef.value = uniqBy(schema, 'field'); _setDefaultValue(schema)
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)))
} }
/** /**
@ -206,44 +256,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)
} }
} }
@ -256,9 +306,9 @@ export function useFormEvents({
updateSchema, updateSchema,
resetSchema, resetSchema,
appendSchemaByField, appendSchemaByField,
removeSchemaByFiled, removeSchemaByField,
resetFields, resetFields,
setFieldsValue, setFieldsValue,
scrollToField, scrollToField,
}; }
} }

View File

@ -1,16 +1,53 @@
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 { set } from 'lodash-es'; import { cloneDeep, 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,
@ -20,68 +57,75 @@ 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) {
value = value.map((item) => transformDateFunc?.(item)); if (isArray(value) && value[0]?.format && value[1]?.format) {
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 = obj; defaultValueRef.value = cloneDeep(obj)
} }
return { handleFormValues, initDefault }; return { handleFormValues, initDefault }
} }

View File

@ -1,39 +1,42 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue'
import type { FormProps, FormSchema } from '../types/form'; import { computed, unref } from 'vue'
import type { FormProps, FormSchema } from '../types/form'
import { computed, unref } from 'vue'; import { isNumber } from '/@/utils/is'
import { isNumber } from '/@/utils/is';
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) { export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
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,
} = unref(propsRef); layout,
} = 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: { style: { width: `calc(100% - ${width})` }, ...wrapCol }, wrapperCol: {
}; 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,6 +40,7 @@ export const basicProps = {
// 在INPUT组件上单击回车时是否自动提交 // 在INPUT组件上单击回车时是否自动提交
autoSubmitOnEnter: propTypes.bool.def(false), autoSubmitOnEnter: propTypes.bool.def(false),
submitOnReset: propTypes.bool, submitOnReset: propTypes.bool,
submitOnChange: propTypes.bool,
size: propTypes.oneOf(['default', 'small', 'large']).def('default'), size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
// 禁用表单 // 禁用表单
disabled: propTypes.bool, disabled: propTypes.bool,
@ -53,7 +54,7 @@ export const basicProps = {
transformDateFunc: { transformDateFunc: {
type: Function as PropType<Fn>, type: Function as PropType<Fn>,
default: (date: any) => { default: (date: any) => {
return date._isAMomentObject ? date?.format('YYYY-MM-DD HH:mm:ss') : date; return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date
}, },
}, },
rulesMessageJoinLabel: propTypes.bool.def(true), rulesMessageJoinLabel: propTypes.bool.def(true),
@ -99,4 +100,4 @@ export const basicProps = {
labelAlign: propTypes.string, labelAlign: propTypes.string,
rowProps: Object as PropType<RowProps>, rowProps: Object as PropType<RowProps>,
}; }

View File

@ -1,220 +1,223 @@
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>
removeSchemaByFiled: (field: string | string[]) => Promise<void>; removeSchemaByField: (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 {
layout?: 'vertical' | 'inline' | 'horizontal'; name?: string
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,6 +91,7 @@ export type ComponentType =
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'TreeSelect' | 'TreeSelect'
| 'ApiTree'
| 'ApiTreeSelect' | 'ApiTreeSelect'
| 'ApiRadioGroup' | 'ApiRadioGroup'
| 'RadioButtonGroup' | 'RadioButtonGroup'
@ -112,4 +113,5 @@ export type ComponentType =
| 'Render' | 'Render'
| 'Slider' | 'Slider'
| 'Rate' | 'Rate'
| 'Divider'; | 'Divider'
| 'ApiTransfer'

View File

@ -6,7 +6,7 @@
:openKeys="getOpenKeys" :openKeys="getOpenKeys"
:inlineIndent="inlineIndent" :inlineIndent="inlineIndent"
:theme="theme" :theme="theme"
@openChange="handleOpenChange" @open-change="handleOpenChange"
:class="getMenuClass" :class="getMenuClass"
@click="handleMenuClick" @click="handleMenuClick"
:subMenuOpenDelay="0.2" :subMenuOpenDelay="0.2"
@ -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,27 +1,31 @@
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 }) { setup(props, { slots, emit }) {
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 } as Recordable; const propsData = { ...unref(attrs), ...props, onCancel } as Recordable
return <Modal {...propsData}>{extendSlots(slots)}</Modal>; return <Modal {...propsData}>{extendSlots(slots)}</Modal>
}; }
}, },
}); })

View File

@ -26,7 +26,6 @@
&-title { &-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
line-height: 16px;
.base-title { .base-title {
cursor: move !important; cursor: move !important;
@ -111,16 +110,19 @@
.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="content || $slots.headerContent || title || getHeaderSlots.length" v-if="getShowHeader"
> >
<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,50 +96,54 @@
[`${prefixCls}--dense`]: props.dense, [`${prefixCls}--dense`]: props.dense,
}, },
attrs.class ?? {}, attrs.class ?? {},
]; ]
}); })
const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter); const getShowHeader = computed(
() => 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,
@ -150,12 +154,13 @@
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,24 +34,27 @@
</span> </span>
<SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" /> <SimpleMenuTag :item="item" :collapseParent="!!collapse && !!parent" />
</template> </template>
<template v-for="childrenItem in item.children || []" :key="childrenItem.path"> <template
v-for="childrenItem in item.children || []"
:key="childrenItem.paramPath || childrenItem.path"
>
<SimpleSubMenu v-bind="$props" :item="childrenItem" :parent="false" /> <SimpleSubMenu v-bind="$props" :item="childrenItem" :parent="false" />
</template> </template>
</SubMenu> </SubMenu>
</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',
@ -72,22 +75,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 (
@ -95,7 +98,7 @@
Reflect.has(menuTreeItem, 'children') && Reflect.has(menuTreeItem, 'children') &&
!!menuTreeItem.children && !!menuTreeItem.children &&
menuTreeItem.children.length > 0 menuTreeItem.children.length > 0
); )
} }
return { return {
@ -107,7 +110,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"
@visibleChange="handleVisibleChange" @visible-change="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,27 +96,26 @@
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 } = const { getParentSubMenu, getItemStyle, getParentMenu, getParentList } = useMenuItem(instance)
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,
@ -128,7 +127,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 [
@ -140,29 +139,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`,
{ {
@ -170,134 +169,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
@ -311,7 +310,7 @@
level: level + 1, level: level + 1,
handleMouseleave, handleMouseleave,
props: rootProps, props: rootProps,
}); })
return { return {
getClass, getClass,
@ -328,7 +327,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;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
} }
@ -45,8 +45,8 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 18px; right: 18px;
transform: translateY(-50%) rotate(-90deg);
transition: transform @transition-time @ease-in-out; transition: transform @transition-time @ease-in-out;
transform: translateY(-50%) rotate(-90deg);
} }
} }
@ -128,12 +128,12 @@
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex; display: flex;
align-items: center;
font-size: @font-size-base; font-size: @font-size-base;
color: inherit; color: inherit;
list-style: none; list-style: none;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
align-items: center;
&:hover, &:hover,
&:active { &:active {
@ -178,8 +178,8 @@
&-vertical &-submenu-collapse { &-vertical &-submenu-collapse {
.@{submenu-popup-prefix-cls} { .@{submenu-popup-prefix-cls} {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
justify-content: center;
} }
.@{menu-prefix-cls}-submenu-collapsed-show-tit { .@{menu-prefix-cls}-submenu-collapsed-show-tit {
flex-direction: column; flex-direction: column;
@ -244,8 +244,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
} }
} }
@ -276,8 +276,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
.@{menu-prefix-cls}-submenu-collapse { .@{menu-prefix-cls}-submenu-collapse {

View File

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

View File

@ -1,6 +1,7 @@
<template> <template>
<div ref="wrapRef" :class="getWrapperClass"> <div ref="wrapRef" :class="getWrapperClass">
<BasicForm <BasicForm
ref="formRef"
submitOnReset submitOnReset
v-bind="getFormProps" v-bind="getFormProps"
v-if="getBindValues.useSearchForm" v-if="getBindValues.useSearchForm"
@ -24,48 +25,49 @@
<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 { import type { BasicTableProps, TableActionType, SizeType, ColumnChangeParam } from './types/table'
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 expandIcon from './components/ExpandIcon'; import HeaderCell from './components/HeaderCell.vue'
import HeaderCell from './components/HeaderCell.vue'; import { InnerHandlers } from './types/table'
import { InnerHandlers } from './types/table';
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 { useCustomRow } from './hooks/useCustomRow'; import { useTableScrollTo } from './hooks/useScrollTo'
import { useTableStyle } from './hooks/useTableStyle'; import { useCustomRow } from './hooks/useCustomRow'
import { useTableHeader } from './hooks/useTableHeader'; import { useTableStyle } from './hooks/useTableStyle'
import { useTableExpand } from './hooks/useTableExpand'; import { useTableHeader } from './hooks/useTableHeader'
import { createTableContext } from './hooks/useTableContext'; import { useTableExpand } from './hooks/useTableExpand'
import { useTableFooter } from './hooks/useTableFooter'; import { createTableContext } from './hooks/useTableContext'
import { useTableForm } from './hooks/useTableForm'; import { useTableFooter } from './hooks/useTableFooter'
import { useDesign } from '/@/hooks/web/useDesign'; import { useTableForm } from './hooks/useTableForm'
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: {
@ -93,36 +95,37 @@
'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 innerPropsRef = ref<Partial<BasicTableProps>>(); const formRef = ref(null)
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,
@ -132,7 +135,7 @@
getSelectRowKeys, getSelectRowKeys,
deleteSelectRowByKey, deleteSelectRowByKey,
setSelectedRowKeys, setSelectedRowKeys,
} = useRowSelection(getProps, tableData, emit); } = useRowSelection(getProps, tableData, emit)
const { const {
handleTableChange: onTableChange, handleTableChange: onTableChange,
@ -160,14 +163,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 {
@ -177,7 +180,7 @@
setColumns, setColumns,
getColumnsRef, getColumnsRef,
getCacheColumns, getCacheColumns,
} = useColumns(getProps, getPaginationInfo); } = useColumns(getProps, getPaginationInfo)
const { getScrollRef, redoHeight } = useTableScroll( const { getScrollRef, redoHeight } = useTableScroll(
getProps, getProps,
@ -185,7 +188,11 @@
getColumnsRef, getColumnsRef,
getRowSelectionRef, getRowSelectionRef,
getDataSourceRef, getDataSourceRef,
); wrapRef,
formRef,
)
const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef)
const { customRow } = useCustomRow(getProps, { const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys, setSelectedRowKeys,
@ -193,39 +200,41 @@
clearSelectedRowKeys, clearSelectedRowKeys,
getAutoCreateKey, getAutoCreateKey,
emit, emit,
}); })
const { getRowClassName } = useTableStyle(getProps, prefixCls); const { getRowClassName } = useTableStyle(getProps, prefixCls)
const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit); const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(
getProps,
tableData,
emit,
)
const handlers: InnerHandlers = { const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => { onColumnsChange: (data: ColumnChangeParam[]) => {
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),
@ -238,17 +247,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,
@ -256,19 +265,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 = {
@ -300,18 +309,21 @@
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,
@ -328,9 +340,9 @@
getFormSlotKeys, getFormSlotKeys,
getWrapperClass, getWrapperClass,
columns: getViewColumns, columns: getViewColumns,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@border-color: #cecece4d; @border-color: #cecece4d;
@ -346,6 +358,7 @@
.@{prefix-cls} { .@{prefix-cls} {
max-width: 100%; max-width: 100%;
height: 100%;
&-row__striped { &-row__striped {
td { td {
@ -357,6 +370,7 @@
padding: 16px; padding: 16px;
.ant-form { .ant-form {
width: 100%;
padding: 12px 10px 6px; padding: 12px 10px 6px;
margin-bottom: 16px; margin-bottom: 16px;
background-color: @component-background; background-color: @component-background;

View File

@ -1,4 +1,4 @@
import type { Component } from 'vue'; import type { Component } from 'vue'
import { import {
Input, Input,
Select, Select,
@ -7,28 +7,34 @@ import {
Switch, Switch,
DatePicker, DatePicker,
TimePicker, TimePicker,
} from 'ant-design-vue'; AutoComplete,
import type { ComponentType } from './types/componentType'; Radio,
import { ApiSelect, ApiTreeSelect } from '/@/components/Form'; } from 'ant-design-vue'
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('ApiTreeSelect', ApiTreeSelect); componentMap.set('AutoComplete', AutoComplete)
componentMap.set('Switch', Switch); componentMap.set('ApiTreeSelect', ApiTreeSelect)
componentMap.set('Checkbox', Checkbox); componentMap.set('Switch', Switch)
componentMap.set('DatePicker', DatePicker); componentMap.set('Checkbox', Checkbox)
componentMap.set('TimePicker', TimePicker); componentMap.set('DatePicker', DatePicker)
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

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

View File

@ -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); const getTitle = computed(() => props.column?.customTitle || props.column?.title)
const getHelpMessage = computed(() => props.column?.helpMessage); const getHelpMessage = computed(() => props.column?.helpMessage)
return { prefixCls, getIsEdit, getTitle, getHelpMessage }; return { prefixCls, getIsEdit, getTitle, getHelpMessage }
}, },
}); })
</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,54 +99,53 @@
onConfirm: popConfirm?.confirm, onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel, onCancel: popConfirm?.cancel,
enable: !!popConfirm, enable: !!popConfirm,
}; }
}); })
}); })
const getDropdownList = computed((): any[] => { const getDropdownList = computed((): any[] => {
return (toRaw(props.dropDownActions) || []) const list = (toRaw(props.dropDownActions) || []).filter((action) => {
.filter((action) => { return hasPermission(action.auth) && isIfShow(action)
return hasPermission(action.auth) && isIfShow(action); })
}) return list.map((action, index) => {
.map((action, index) => { const { label, popConfirm } = action
const { label, popConfirm } = action; return {
return { ...action,
...action, ...popConfirm,
...popConfirm, onConfirm: popConfirm?.confirm,
onConfirm: popConfirm?.confirm, onCancel: popConfirm?.cancel,
onCancel: popConfirm?.cancel, text: label,
text: label, divider: index < list.length - 1 ? props.divider : false,
divider: index < props.dropDownActions.length - 1 ? props.divider : false, }
}; })
}); })
});
const getAlign = computed(() => { const getAlign = computed(() => {
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,62 +1,26 @@
<template> <script lang="tsx">
<div :class="prefixCls"> import type { CSSProperties, PropType } from 'vue'
<div import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'
v-show="!isEdit" import type { BasicColumn } from '../../types/table'
:class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }" import type { EditRecordRow } from './index'
@click="handleEdit" import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue'
> 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>
<a-spin v-if="isEdit" :spinning="spinning"> import { useDesign } from '/@/hooks/web/useDesign'
<div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside"> import { useTableContext } from '../../hooks/useTableContext'
<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 { useDesign } from '/@/hooks/web/useDesign'; import clickOutside from '/@/directives/clickOutside'
import { useTableContext } from '../../hooks/useTableContext';
import clickOutside from '/@/directives/clickOutside'; import { propTypes } from '/@/utils/propTypes'
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'
import { propTypes } from '/@/utils/propTypes'; import { createPlaceholderMessage } from './helper'
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'; import { pick, set } from 'lodash-es'
import { createPlaceholderMessage } from './helper'; import { treeToList } from '/@/utils/helper/treeHelper'
import { omit, pick, set } from 'lodash-es'; import { Spin } from 'ant-design-vue'
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, ASpin: Spin }, components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, Spin },
directives: { directives: {
clickOutside, clickOutside,
}, },
@ -75,267 +39,293 @@
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 compProps = props.column?.editComponentProps ?? {}; const isCheckValue = unref(getIsCheckComp)
const component = unref(getComponent);
const apiSelectProps: Recordable = {}; const valueField = isCheckValue ? 'checked' : 'value'
if (component === 'ApiSelect') { const val = unref(currentValueRef)
apiSelectProps.cache = true;
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 isCheckValue = unref(getIsCheckComp); const apiSelectProps: Recordable = {}
if (component === 'ApiSelect') {
const valueField = isCheckValue ? 'checked' : 'value'; apiSelectProps.cache = true
const val = unref(currentValueRef); }
upEditDynamicDisabled(record, column, value)
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,
...omit(compProps, 'onChange'), ...compProps,
[valueField]: value, [valueField]: value,
}; disabled: unref(getDisable),
}); } as any
})
function upEditDynamicDisabled(record, column, value) {
if (!record) return false
const { key, dataIndex } = column
if (!key && !dataIndex) return
const dataKey = (dataIndex || key) as string
set(record, dataKey, value)
}
const getDisable = computed(() => {
const { editDynamicDisabled } = props.column
let disabled = false
if (isBoolean(editDynamicDisabled)) {
disabled = editDynamicDisabled
}
if (isFunction(editDynamicDisabled)) {
const { record } = props
disabled = editDynamicDisabled({ record })
}
return disabled
})
const getValues = computed(() => { const getValues = computed(() => {
const { editComponentProps, editValueMap } = props.column; const { editValueMap } = props.column
const value = unref(currentValueRef); const value = unref(currentValueRef)
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')) { if (!component.includes('Select') && !component.includes('Radio')) {
return value; return value
} }
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []); const options: LabelValueOptions =
const option = options.find((item) => `${item.value}` === `${value}`); unref(getComponentProps)?.options ?? (unref(optionsRef) || [])
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 (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
} else if (component === 'Checkbox') { } else if (component === 'Checkbox') {
currentValueRef.value = (e as ChangeEvent).target.checked; currentValueRef.value = (e as ChangeEvent).target.checked
} else if (isString(e) || isBoolean(e) || isNumber(e)) { } else if (component === 'Switch') {
currentValueRef.value = e; currentValueRef.value = e
} else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
currentValueRef.value = e
} }
const onChange = props.column?.editComponentProps?.onChange; const onChange = unref(getComponentProps)?.onChange
if (onChange && isFunction(onChange)) onChange(...arguments); if (onChange && isFunction(onChange)) onChange(...arguments)
table.emit?.('edit-change', { table.emit?.('edit-change', {
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: key as string, key: dataKey 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, value }); needEmit && table.emit?.('edit-end', { record, index, key: dataKey, 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 } = props.column?.editComponentProps ?? {}; const { replaceFields } = unref(getComponentProps)
const component = unref(getComponent); const component = unref(getComponent)
if (component === 'ApiTreeSelect') { if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {}; const { title = 'title', value = 'value', children = 'children' } = replaceFields || {}
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
} }
} }
@ -355,7 +345,7 @@
if (props.column.dataIndex) { if (props.column.dataIndex) {
if (!props.record.editValueRefs) props.record.editValueRefs = {}; if (!props.record.editValueRefs) props.record.editValueRefs = {};
props.record.editValueRefs[props.column.dataIndex] = currentValueRef; props.record.editValueRefs[props.column.dataIndex as any] = currentValueRef;
} }
/* eslint-disable */ /* eslint-disable */
props.record.onCancelEdit = () => { props.record.onCancelEdit = () => {
@ -398,6 +388,59 @@
spinning, spinning,
}; };
}, },
render() {
return (
<div class={this.prefixCls}>
<div
v-show={!this.isEdit}
class={{ [`${this.prefixCls}__normal`]: true, 'ellipsis-cell': this.column.ellipsis }}
onClick={this.handleEdit}
>
<div class="cell-content" title={this.column.ellipsis ? this.getValues ?? '' : ''}>
{this.column.editRender
? this.column.editRender({
text: this.value,
record: this.record as Recordable,
column: this.column,
index: this.index,
})
: this.getValues
? this.getValues
: '\u00A0'}
</div>
{!this.column.editRow && <FormOutlined class={`${this.prefixCls}__normal-icon`} />}
</div>
{this.isEdit && (
<Spin spinning={this.spinning}>
<div class={`${this.prefixCls}__wrapper`} v-click-outside={this.onClickOutside}>
<CellComponent
{...this.getComponentProps}
component={this.getComponent}
style={this.getWrapperStyle}
popoverVisible={this.getRuleVisible}
rule={this.getRule}
ruleMessage={this.ruleMessage}
class={this.getWrapperClass}
ref="elRef"
onChange={this.handleChange}
onOptionsChange={this.handleOptionsChange}
onPressEnter={this.handleEnter}
/>
{!this.getRowEditable && (
<div class={`${this.prefixCls}__action`}>
<CheckOutlined
class={[`${this.prefixCls}__icon`, 'mx-2']}
onClick={this.handleSubmitClick}
/>
<CloseOutlined class={`${this.prefixCls}__icon `} onClick={this.handleCancel} />
</div>
)}
</div>
</Spin>
)}
</div>
);
},
}); });
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -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')) { if (component.includes('Input') || component.includes('AutoComplete')) {
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"
@visibleChange="handleVisibleChange" @visible-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`" :overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer" :getPopupContainer="getPopupContainer"
> >
@ -43,7 +43,7 @@
<CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef"> <CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef">
<template v-for="item in plainOptions" :key="item.value"> <template v-for="item in plainOptions" :key="item.value">
<div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)"> <div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)">
<DragOutlined class="table-coulmn-drag-icon" /> <DragOutlined class="table-column-drag-icon" />
<Checkbox :value="item.value"> <Checkbox :value="item.value">
{{ item.label }} {{ item.label }}
</Checkbox> </Checkbox>
@ -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,29 +109,33 @@
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 { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'; import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface'
import { Icon } from '/@/components/Icon'; import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'
import { ScrollContainer } from '/@/components/Container'; import { Icon } from '/@/components/Icon'
import { useI18n } from '/@/hooks/web/useI18n'; import { ScrollContainer } from '/@/components/Container'
import { useTableContext } from '../../hooks/useTableContext'; import { useI18n } from '/@/hooks/web/useI18n'
import { useDesign } from '/@/hooks/web/useDesign'; import { useTableContext } from '../../hooks/useTableContext'
import { useSortable } from '/@/hooks/web/useSortable'; import { useDesign } from '/@/hooks/web/useDesign'
import { isFunction, isNullAndUnDef } from '/@/utils/is'; // import { useSortable } from '/@/hooks/web/useSortable';
import { getPopupContainer as getParentContainer } from '/@/utils'; import { isFunction, isNullAndUnDef } from '/@/utils/is'
import { omit } from 'lodash-es'; import { getPopupContainer as getParentContainer } from '/@/utils'
import { cloneDeep, omit } from 'lodash-es'
import Sortablejs from 'sortablejs'
import type Sortable from 'sortablejs'
interface State { interface State {
checkAll: boolean; checkAll: boolean
checkedList: string[]; isInit?: boolean
defaultCheckList: string[]; checkedList: 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({
@ -150,219 +154,232 @@
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[]>([]); const plainOptions = ref<Options[] | any>([])
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(() => {
const columns = table.getColumns(); setTimeout(() => {
if (columns.length) { const columns = table.getColumns()
init(); if (columns.length && !state.isInit) {
} 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 }) .getColumns({ ignoreAction: true, ignoreIndex: 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.checkedList = checkList; state.isInit = true
state.checkedList = checkList
} }
// checkAll change // checkAll change
function onCheckAllChange(e: ChangeEvent) { function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainOptions.value.map((item) => item.value); const checkList = plainOptions.value.map((item) => item.value)
if (e.target.checked) { if (e.target.checked) {
state.checkedList = checkList; state.checkedList = checkList
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 checkdedLen = state.checkedList.length; let checkedLen = state.checkedList.length
unref(checkIndex) && checkdedLen--; // unref(checkIndex) && checkedLen--;
return checkdedLen > 0 && checkdedLen < len; return checkedLen > 0 && checkedLen < len
}); })
// Trigger when check/uncheck a column // Trigger when check/uncheck a column
function onChange(checkedList: string[]) { function onChange(checkedList: string[]) {
const len = plainOptions.value.length; const len = plainSortOptions.value.length
state.checkAll = checkedList.length === len; state.checkAll = checkedList.length === len
const sortList = unref(plainSortOptions).map((item) => item.value)
const sortList = unref(plainSortOptions).map((item) => item.value);
checkedList.sort((prev, next) => { checkedList.sort((prev, next) => {
return sortList.indexOf(prev) - sortList.indexOf(next); return sortList.indexOf(prev) - sortList.indexOf(next)
}); })
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
const { initSortable } = useSortable(el, { sortable = Sortablejs.create(unref(el), {
handle: '.table-coulmn-drag-icon ', animation: 500,
delay: 400,
delayOnTouchOnly: true,
handle: '.table-column-drag-icon ',
onEnd: (evt) => { onEnd: (evt) => {
const { oldIndex, newIndex } = evt; const { oldIndex, newIndex } = evt
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return; return
} }
// Sort column // Sort column
const columns = getColumns(); const columns = cloneDeep(plainSortOptions.value)
if (oldIndex > newIndex) { if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]); columns.splice(newIndex, 0, columns[oldIndex])
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(columns); setColumns(
columns
.map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)),
)
}, },
}); })
initSortable(); // order
inited = true; sortableOrder = sortable.toArray()
}); inited = true
})
} }
// Control whether the serial number column is displayed // Control whether the serial number column is displayed
function handleIndexCheckChange(e: ChangeEvent) { function handleIndexCheckChange(e: CheckboxChangeEvent) {
table.setProps({ table.setProps({
showIndexColumn: e.target.checked, showIndexColumn: e.target.checked,
}); })
} }
// Control whether the check box is displayed // Control whether the check box is displayed
function handleSelectCheckChange(e: ChangeEvent) { function handleSelectCheckChange(e: CheckboxChangeEvent) {
table.setProps({ table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined, rowSelection: e.target.checked ? defaultRowSelection : undefined,
}); })
} }
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, { fixed: isFixed }); table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed })
setColumns(columns); setColumns(columns)
} }
function setColumns(columns: BasicColumn[] | string[]) { function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns); table.setColumns(columns)
const data: ColumnChangeParam[] = unref(plainOptions).map((col) => { const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const visible = const visible =
columns.findIndex( columns.findIndex(
(c: BasicColumn | string) => (c: BasicColumn | string) =>
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 {
@ -383,14 +400,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-coulmn-drag-icon { .table-column-drag-icon {
margin: 0 5px; margin: 0 5px;
cursor: move; cursor: move;
} }

View File

@ -4,7 +4,7 @@
<span>{{ t('component.table.settingDens') }}</span> <span>{{ t('component.table.settingDens') }}</span>
</template> </template>
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer"> <Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
<ColumnHeightOutlined /> <ColumnHeightOutlined />
<template #overlay> <template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef"> <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
@ -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, toRaw, unref, watch } from 'vue'; import { computed, Ref, ref, reactive, toRaw, unref, watch } from 'vue'
import { renderEditCell } from '../components/editable'; import { renderEditCell } from '../components/editable'
import { usePermission } from '/@/hooks/web/usePermission'; import { usePermission } from '/@/hooks/web/usePermission'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
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,162 +104,154 @@ 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, dataIndex, customRender, format, edit, editRow, flag } = column; const { slots, customRender, format, edit, editRow, flag } = column
if (!slots || !slots?.title) { if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) }; // column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title; column.customTitle = column.title
Reflect.deleteProperty(column, 'title'); Reflect.deleteProperty(column, 'title')
} }
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 column; return reactive(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[]) { function setColumns(columnList: Partial<BasicColumn>[] | (string | 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)) { if (!isString(firstColumn) && !isArray(firstColumn)) {
columnsRef.value = columns as BasicColumn[]; columnsRef.value = columns as BasicColumn[]
} else { } else {
const columnKeys = columns as string[]; const columnKeys = (columns as (string | string[])[]).map((m) => m.toString())
const newColumns: BasicColumn[] = []; const newColumns: BasicColumn[] = []
cacheColumns.forEach((item) => { cacheColumns.forEach((item) => {
if (columnKeys.includes(item.dataIndex! || (item.key as string))) { newColumns.push({
newColumns.push({ ...item,
...item, defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)),
defaultHidden: false, })
}); })
} else {
newColumns.push({
...item,
defaultHidden: true,
});
}
});
// Sort according to another array // Sort according to another array
if (!isEqual(cacheKeys, columns)) { if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => { newColumns.sort((prev, next) => {
return ( return (
cacheKeys.indexOf(prev.dataIndex as string) - columnKeys.indexOf(prev.dataIndex?.toString() as string) -
cacheKeys.indexOf(next.dataIndex as string) columnKeys.indexOf(next.dataIndex?.toString() as string)
); )
}); })
} }
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 {
@ -269,57 +261,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)) { if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX) && text) {
const dateFormat = format.replace(DATE_FORMAT_PREFIX, ''); const dateFormat = format.replace(DATE_FORMAT_PREFIX, '')
if (!dateFormat) { if (!dateFormat) {
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 } from 'lodash-es'; import { get, cloneDeep, merge } from 'lodash-es'
import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const'; import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const'
interface ActionType { interface ActionType {
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,190 +45,188 @@ 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) if (typeof index !== 'undefined' && index !== -1) unref(propsRef).dataSource?.splice(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)
unref(propsRef).dataSource?.splice(index, 0, record); return unref(dataSourceRef)
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') {
@ -237,7 +235,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) {
@ -250,111 +248,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 = { let params: Recordable = merge(
...pageParams, pageParams,
...(useSearchForm ? getFieldsValue() : {}), useSearchForm ? getFieldsValue() : {},
...searchInfo, searchInfo,
...(opt?.searchInfo ?? {}), opt?.searchInfo ?? {},
...defSort, defSort,
...sortInfo, sortInfo,
...filterInfo, filterInfo,
...(opt?.sortInfo ?? {}), opt?.sortInfo ?? {},
...(opt?.filterInfo ?? {}), opt?.filterInfo ?? {},
}; )
if (beforeFetch && isFunction(beforeFetch)) { if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params; params = (await beforeFetch(params)) || params
} }
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 ? 0 : get(res, totalField); const resultTotal: number = isArrayResult ? res.length : 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,
@ -371,5 +369,5 @@ export function useDataSource(
insertTableDataRecord, insertTableDataRecord,
findTableDataRecord, findTableDataRecord,
handleTableChange, handleTableChange,
}; }
} }

View File

@ -1,115 +1,112 @@
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 {
@ -121,5 +118,5 @@ export function useRowSelection(
clearSelectedRowKeys, clearSelectedRowKeys,
deleteSelectRowByKey, deleteSelectRowByKey,
setSelectedRows, setSelectedRows,
}; }
} }

View File

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

View File

@ -1,161 +1,167 @@
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,58 +1,65 @@
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, collapseAll }; return { getExpandOption, expandAll, expandRows, collapseAll }
} }

View File

@ -1,57 +1,56 @@
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: Nullable<number>; y: string | number | null
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 bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body'); const bodyDom = tableEl.$el.querySelector('.ant-table-content')
const bodyDom = bodyDomList[0];
useEventListener({ useEventListener({
el: bodyDom, el: bodyDom,
name: 'scroll', name: 'scroll',
listener: () => { listener: () => {
const footerBodyDom = tableEl.$el.querySelector( const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-body', '.ant-table-footer .ant-table-content',
) as HTMLDivElement; ) as HTMLDivElement
if (!footerBodyDom || !bodyDom) return; if (!footerBodyDom || !bodyDom) return
footerBodyDom.scrollLeft = bodyDom.scrollLeft; footerBodyDom.scrollLeft = bodyDom.scrollLeft
}, },
wait: 0, wait: 0,
options: true, options: true,
}); })
}); })
} }
return { getFooterProps }; return { getFooterProps }
} }

View File

@ -1,135 +1,161 @@
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table'; import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table'
import type { Ref, ComputedRef } from 'vue'; import { Ref, ComputedRef, ref } from 'vue'
import { computed, unref, ref, nextTick, watch } from 'vue'; import { computed, unref, nextTick, watch } from 'vue'
import { getViewportOffset } from '/@/utils/domUtils'; import { getViewportOffset } from '/@/utils/domUtils'
import { isBoolean } from '/@/utils/is'; import { isBoolean } from '/@/utils/is'
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'
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<any> | null>, rowSelectionRef: ComputedRef<TableRowSelection | null>,
getDataSourceRef: ComputedRef<Recordable[]>, getDataSourceRef: ComputedRef<Recordable[]>,
wrapRef: Ref<HTMLElement | null>,
formRef: Ref<ComponentRef>,
) { ) {
const tableHeightRef: Ref<Nullable<number>> = ref(null); const tableHeightRef: Ref<Nullable<number | string>> = ref(167)
const modalFn = useModalContext()
const modalFn = useModalContext();
// Greater than animation time 280 // Greater than animation time 280
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(heigh: number) { function setHeight(height: number) {
tableHeightRef.value = heigh; tableHeightRef.value = height
// Solve the problem of modal adaptive height calculation when the form is placed in the modal // Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.(); modalFn?.redoModalHeight?.()
} }
// 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 } = unref(propsRef); const { resizeHeightOffset, pagination, maxHeight, isCanResizeParent, useSearchForm } =
const tableData = unref(getDataSourceRef); unref(propsRef)
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.contains('hide-scrollbar-y') && tableEl.classList.remove('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.contains('hide-scrollbar-x') && tableEl.classList.remove('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) || tableData.length === 0) return; if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return
await nextTick(); await nextTick()
//Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight // Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
const headEl = tableEl.querySelector('.ant-table-thead '); const headEl = tableEl.querySelector('.ant-table-thead ')
if (!headEl) return; if (!headEl) return
// Table height from bottom
const { bottomIncludeBody } = getViewportOffset(headEl);
// Table height from bottom height-custom offset // Table height from bottom height-custom offset
let paddingHeight = 32
const paddingHeight = 32;
// Pager height // Pager height
let paginationHeight = 2; let paginationHeight = 2
if (!isBoolean(pagination)) { if (!isBoolean(pagination)) {
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 =
@ -138,56 +164,55 @@ export function useTableScroll(
paddingHeight - paddingHeight -
paginationHeight - paginationHeight -
footerHeight - footerHeight -
headerHeight; headerHeight
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height
setHeight(height)
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height; bodyEl!.style.height = `${height}px`
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.parseInt(item.width as string) || 0; width += Number.parseFloat(item.width as string) || 0
}); })
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width')); const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'))
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,16 +8,17 @@ 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 { propTypes } from '/@/utils/propTypes'; import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'
import { propTypes } from '/@/utils/propTypes'
export const basicProps = { export const basicProps = {
clickToRowSelect: propTypes.bool.def(true), clickToRowSelect: { type: Boolean, default: true },
isTreeTable: propTypes.bool.def(false), isTreeTable: Boolean,
tableSetting: propTypes.shape<TableSetting>({}), tableSetting: propTypes.shape<TableSetting>({}),
inset: propTypes.bool, inset: Boolean,
sortFn: { sortFn: {
type: Function as PropType<(sortInfo: SorterResult) => any>, type: Function as PropType<(sortInfo: SorterResult) => any>,
default: DEFAULT_SORT_FN, default: DEFAULT_SORT_FN,
@ -26,10 +27,10 @@ export const basicProps = {
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>, type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
default: DEFAULT_FILTER_FN, default: DEFAULT_FILTER_FN,
}, },
showTableSetting: propTypes.bool, showTableSetting: Boolean,
autoCreateKey: propTypes.bool.def(true), autoCreateKey: { type: Boolean, default: true },
striped: propTypes.bool.def(true), striped: { type: Boolean, default: true },
showSummary: propTypes.bool, showSummary: Boolean,
summaryFunc: { summaryFunc: {
type: [Function, Array] as PropType<(...arg: any[]) => any[]>, type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
default: null, default: null,
@ -39,7 +40,7 @@ export const basicProps = {
default: null, default: null,
}, },
indentSize: propTypes.number.def(24), indentSize: propTypes.number.def(24),
canColDrag: propTypes.bool.def(true), canColDrag: { type: Boolean, default: true },
api: { api: {
type: Function as PropType<(...arg: any[]) => Promise<any>>, type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null, default: null,
@ -59,12 +60,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: propTypes.bool.def(true), immediate: { type: Boolean, default: true },
emptyDataIsShowTable: propTypes.bool.def(true), emptyDataIsShowTable: { type: Boolean, default: true },
// 额外的请求参数 // 额外的请求参数
searchInfo: { searchInfo: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable>,
@ -86,7 +87,7 @@ export const basicProps = {
type: [Array] as PropType<BasicColumn[]>, type: [Array] as PropType<BasicColumn[]>,
default: () => [], default: () => [],
}, },
showIndexColumn: propTypes.bool.def(true), showIndexColumn: { type: Boolean, default: true },
indexColumnProps: { indexColumnProps: {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
@ -95,8 +96,9 @@ export const basicProps = {
type: Object as PropType<BasicColumn>, type: Object as PropType<BasicColumn>,
default: null, default: null,
}, },
ellipsis: propTypes.bool.def(true), ellipsis: { type: Boolean, default: true },
canResize: propTypes.bool.def(true), isCanResizeParent: { type: Boolean, default: false },
canResize: { type: Boolean, default: true },
clearSelectOnPageChange: propTypes.bool, clearSelectOnPageChange: propTypes.bool,
resizeHeightOffset: propTypes.number.def(0), resizeHeightOffset: propTypes.number.def(0),
rowSelection: { rowSelection: {
@ -135,10 +137,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>
>, >,
}, },
@ -146,4 +148,4 @@ export const basicProps = {
type: String as PropType<SizeType>, type: String as PropType<SizeType>,
default: DEFAULT_SIZE, default: DEFAULT_SIZE,
}, },
}; }

View File

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

View File

@ -1,99 +1,115 @@
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?: 'top' | 'bottom' | 'both'; position?: PaginationPositon[]
} }
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,19 +1,17 @@
import type { VNodeChild } from 'vue'; import type { VNodeChild } from 'vue'
import type { PaginationProps } from './pagination'; import type { PaginationProps } from './pagination'
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '/@/components/Form'
import type { import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'
ColumnProps, import type { ColumnProps } from 'ant-design-vue/lib/table'
TableRowSelection as ITableRowSelection,
} from 'ant-design-vue/lib/table/interface';
import { ComponentType } from './componentType'; import { ComponentType } from './componentType'
import { VueNode } from '/@/utils/propTypes'; import { VueNode } from '/@/utils/propTypes'
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 {
@ -21,285 +19,289 @@ 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
collapseAll: () => void; expandRows: (keys: string[] | number[]) => void
getSelectRowKeys: () => string[]; collapseAll: () => void
deleteSelectRowByKey: (key: string) => void; scrollTo: (pos: string) => void // pos: id | "top" | "bottom"
setPagination: (info: Partial<PaginationProps>) => void; getSelectRowKeys: () => string[]
setTableData: <T = Recordable>(values: T[]) => void; deleteSelectRowByKey: (key: string) => void
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void; setPagination: (info: Partial<PaginationProps>) => void
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void; setTableData: <T = Recordable>(values: T[]) => void
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void; updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void
findTableDataRecord: (rowKey: string | number) => Recordable | void; deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void
getColumns: (opt?: GetColumnsParams) => BasicColumn[]; insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void
setColumns: (columns: BasicColumn[] | string[]) => void; findTableDataRecord: (rowKey: string | number) => Recordable | void
getDataSource: <T = Recordable>() => T[]; getColumns: (opt?: GetColumnsParams) => BasicColumn[]
getRawDataSource: <T = Recordable>() => T; setColumns: (columns: BasicColumn[] | string[]) => void
setLoading: (loading: boolean) => void; getDataSource: <T = Recordable>() => T[]
setProps: (props: Partial<BasicTableProps>) => void; getRawDataSource: <T = Recordable>() => T
redoHeight: () => void; setLoading: (loading: boolean) => void
setSelectedRowKeys: (rowKeys: string[] | number[]) => void; setProps: (props: Partial<BasicTableProps>) => void
getPaginationRef: () => PaginationProps | boolean; redoHeight: () => void
getSize: () => SizeType; setSelectedRowKeys: (rowKeys: string[] | number[]) => void
getRowSelection: () => TableRowSelection<Recordable>; getPaginationRef: () => PaginationProps | boolean
getCacheColumns: () => BasicColumn[]; getSize: () => SizeType
emit?: EmitType; getRowSelection: () => TableRowSelection<Recordable>
updateTableData: (index: number, key: string, value: any) => Recordable; getCacheColumns: () => BasicColumn[]
setShowPagination: (show: boolean) => Promise<void>; emit?: EmitType
getShowPagination: () => boolean; updateTableData: (index: number, key: string, value: any) => Recordable
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void; setShowPagination: (show: boolean) => Promise<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.
@ -307,39 +309,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
@ -348,14 +350,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.
@ -364,7 +366,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
@ -372,11 +374,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
@ -385,7 +387,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
@ -393,68 +395,84 @@ 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 { export interface BasicColumn extends ColumnProps<Recordable> {
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?: Recordable; editComponentProps?:
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>); | ((opt: {
editValueMap?: (value: any) => string; text: string | number | boolean | Recordable
onEditRow?: () => void; record: Recordable
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,26 +1,39 @@
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>
<a-button-group> <Space>
<a-button type="primary" @click="openUploadModal" preIcon="carbon:cloud-upload"> <a-button type="primary" @click="openUploadModal" preIcon="carbon:cloud-upload">
{{ t('component.upload.upload') }} {{ t('component.upload.upload') }}
</a-button> </a-button>
@ -18,8 +18,7 @@
</template> </template>
</a-button> </a-button>
</Tooltip> </Tooltip>
</a-button-group> </Space>
<UploadModal <UploadModal
v-bind="bindValue" v-bind="bindValue"
:previewFileList="fileList" :previewFileList="fileList"
@ -37,72 +36,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 UploadModal from './UploadModal.vue'; import { Icon } from '/@/components/Icon'
import UploadPreviewModal from './UploadPreviewModal.vue'; import { Tooltip, Space } from 'ant-design-vue'
import { Icon } from '/@/components/Icon'; import { useModal } from '/@/components/Modal'
import { Tooltip } from 'ant-design-vue'; import { uploadContainerProps } from './props'
import { useModal } from '/@/components/Modal'; import { omit } from 'lodash-es'
import { uploadContainerProps } from './props'; import { useI18n } from '/@/hooks/web/useI18n'
import { omit } from 'lodash-es'; import { isArray } from '/@/utils/is'
import { useI18n } from '/@/hooks/web/useI18n'; import UploadModal from './UploadModal.vue'
import { isArray } from '/@/utils/is'; import UploadPreviewModal from './UploadPreviewModal.vue'
export default defineComponent({ export default defineComponent({
name: 'BasicUpload', name: 'BasicUpload',
components: { UploadModal, UploadPreviewModal, Icon, Tooltip }, components: { UploadModal, Space, UploadPreviewModal, Icon, Tooltip },
props: uploadContainerProps, props: uploadContainerProps,
emits: ['change', 'delete', 'preview-delete', 'update:value'], emits: ['change', 'delete', 'preview-delete', 'update:value'],
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 {
@ -118,7 +117,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"
wrapClassName="upload-modal" class="upload-modal"
:okButtonProps="getOkButtonProps" :okButtonProps="getOkButtonProps"
:cancelButtonProps="{ disabled: isUploadingRef }" :cancelButtonProps="{ disabled: isUploadingRef }"
> >
@ -31,6 +31,7 @@
:accept="getStringAccept" :accept="getStringAccept"
:multiple="multiple" :multiple="multiple"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:show-upload-list="false"
class="upload-modal-toolbar__btn" class="upload-modal-toolbar__btn"
> >
<a-button type="primary"> <a-button type="primary">
@ -42,24 +43,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 { checkFileType, checkImgType, getBase64WithFile } from './helper'; import { checkImgType, getBase64WithFile } from './helper'
import { buildUUID } from '/@/utils/uuid'; import { buildUUID } from '/@/utils/uuid'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
import { warn } from '/@/utils/log'; import { warn } from '/@/utils/log'
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 },
@ -74,68 +75,60 @@
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 { getAccept, getStringAccept, getHelpText } = useUploadType({ const { 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( const someError = fileListRef.value.some((item) => item.status === UploadResultStatus.ERROR)
(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,
@ -143,7 +136,7 @@
name, name,
percent: 0, percent: 0,
type: name.split('.').pop(), type: name.split('.').pop(),
}; }
// //
if (checkImgType(file)) { if (checkImgType(file)) {
// beforeUpload // beforeUpload
@ -155,19 +148,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)
} }
// //
@ -179,12 +172,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: {
@ -195,87 +188,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
} }
} }
@ -298,9 +291,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')"
wrapClassName="upload-preview-modal" class="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,18 +1,7 @@
import type { App } from 'vue'; import type { App } from 'vue'
import { Button } from './Button'; import { Button } from './Button'
import { import { Input, Layout } from 'ant-design-vue'
// Need
Button as AntButton,
Input,
Layout,
} from 'ant-design-vue';
const compList = [AntButton.Group];
export function registerGlobComp(app: App) { export function registerGlobComp(app: App) {
compList.forEach((comp) => { app.use(Input).use(Button).use(Layout)
app.component(comp.name || comp.displayName, comp);
});
app.use(Input).use(Button).use(Layout);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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