new-map
ihzero 2022-10-21 11:51:29 +08:00
parent dbdc655406
commit 8aaaa449d6
92 changed files with 3591 additions and 3628 deletions

View File

@ -73,4 +73,4 @@ module.exports = {
], ],
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
}, },
}; }

View File

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

View File

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

View File

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

View File

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

View File

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

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

@ -2,19 +2,19 @@
* Plugin to minimize and use ejs template syntax in index.html. * Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html * https://github.com/anncwb/vite-plugin-html
*/ */
import type { PluginOption } from 'vite'; import type { PluginOption } from 'vite'
import { createHtmlPlugin } 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: PluginOption[] = createHtmlPlugin({ const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild, minify: isBuild,
@ -35,6 +35,6 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
] ]
: [], : [],
}, },
}); })
return htmlPlugin; return htmlPlugin
} }

View File

@ -3,8 +3,8 @@
* https://github.com/anncwb/vite-plugin-svg-icons * https://github.com/anncwb/vite-plugin-svg-icons
*/ */
import { createSvgIconsPlugin } 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 = createSvgIconsPlugin({ const svgIconsPlugin = createSvgIconsPlugin({
@ -12,6 +12,6 @@ export function configSvgIconsPlugin(isBuild: boolean) {
svgoOptions: isBuild, svgoOptions: isBuild,
// default // default
symbolId: 'icon-[dir]-[name]', symbolId: 'icon-[dir]-[name]',
}); })
return svgIconsPlugin; return svgIconsPlugin
} }

View File

@ -2,46 +2,46 @@
* Vite plugin for website theme color switching * Vite plugin for website theme color switching
* https://github.com/anncwb/vite-plugin-theme * https://github.com/anncwb/vite-plugin-theme
*/ */
import type { PluginOption } from 'vite'; import type { 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): PluginOption[] { 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): PluginOption[] {
'alert-error-icon-color': '#a61d24', 'alert-error-icon-color': '#a61d24',
}, },
}), }),
]; ]
return plugin as unknown as PluginOption[]; return plugin as unknown as PluginOption[]
} }

View File

@ -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

@ -23,17 +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'; import { isNil } from 'lodash-es'
// component // component
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue'
import { CollapseTransition } from '/@/components/Transition'; import { CollapseTransition } from '/@/components/Transition'
import CollapseHeader from './CollapseHeader.vue'; import CollapseHeader from './CollapseHeader.vue'
import { triggerWindowResize } from '/@/utils/event'; import { triggerWindowResize } from '/@/utils/event'
// hook // hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
const props = defineProps({ const props = defineProps({
title: { type: String, default: '' }, title: { type: String, default: '' },
@ -58,26 +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(val: boolean) { function handleExpand(val: boolean) {
show.value = isNil(val) ? !show.value : val; 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({ defineExpose({
handleExpand, handleExpand,
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container'; @prefix-cls: ~'@{namespace}-collapse-container';

View File

@ -1,11 +1,11 @@
<script lang="tsx"> <script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing'; import type { ContextMenuItem, ItemContentProps, Axis } from './typing'
import type { FunctionalComponent, CSSProperties, PropType } from 'vue'; import type { FunctionalComponent, CSSProperties, 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,25 +39,25 @@
{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', position: 'absolute',
@ -65,39 +65,39 @@
left: `${left + 1}px`, left: `${left + 1}px`,
top: `${top + 1}px`, top: `${top + 1}px`,
zIndex: 9999, zIndex: 9999,
}; }
}); })
onMounted(() => { onMounted(() => {
nextTick(() => (showRef.value = true)); nextTick(() => (showRef.value = true))
}); })
onUnmounted(() => { onUnmounted(() => {
const el = unref(wrapRef); const el = unref(wrapRef)
el && document.body.removeChild(el); el && document.body.removeChild(el)
}); })
function handleAction(item: ContextMenuItem, e: MouseEvent) { function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item; const { handler, disabled } = item
if (disabled) { if (disabled) {
return; return
} }
showRef.value = false; showRef.value = false
e?.stopPropagation(); e?.stopPropagation()
e?.preventDefault(); e?.preventDefault()
handler?.(); handler?.()
} }
function renderMenuItem(items: ContextMenuItem[]) { function renderMenuItem(items: ContextMenuItem[]) {
const visibleItems = items.filter((item) => !item.hidden); const visibleItems = items.filter((item) => !item.hidden)
return visibleItems.map((item) => { return visibleItems.map((item) => {
const { disabled, label, children, divider = false } = item; const { disabled, label, children, divider = false } = item
const contentProps = { const contentProps = {
item, item,
handler: handleAction, handler: handleAction,
showIcon: props.showIcon, showIcon: props.showIcon,
}; }
if (!children || children.length === 0) { if (!children || children.length === 0) {
return ( return (
@ -107,9 +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`}>
@ -118,24 +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 (
<div class={prefixCls}> <div class={prefixCls}>
<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}> <Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
{renderMenuItem(items)} {renderMenuItem(items)}
</Menu> </Menu>
</div> </div>
); )
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@default-height: 42px !important; @default-height: 42px !important;

View File

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

View File

@ -31,8 +31,8 @@
</Drawer> </Drawer>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { DrawerInstance, DrawerProps } from './typing'; import type { DrawerInstance, DrawerProps } from './typing'
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue'
import { import {
defineComponent, defineComponent,
ref, ref,
@ -42,17 +42,17 @@
nextTick, nextTick,
toRaw, toRaw,
getCurrentInstance, getCurrentInstance,
} from 'vue'; } from 'vue'
import { Drawer } from 'ant-design-vue'; import { Drawer } from 'ant-design-vue'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { isFunction, isNumber } from '/@/utils/is'; import { isFunction, isNumber } from '/@/utils/is'
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils'
import DrawerFooter from './components/DrawerFooter.vue'; import DrawerFooter from './components/DrawerFooter.vue'
import DrawerHeader from './components/DrawerHeader.vue'; import DrawerHeader from './components/DrawerHeader.vue'
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container'
import { basicProps } from './props'; import { basicProps } from './props'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs'
export default defineComponent({ export default defineComponent({
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader }, components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
@ -60,25 +60,25 @@
props: basicProps, props: basicProps,
emits: ['visible-change', 'ok', 'close', 'register'], emits: ['visible-change', 'ok', 'close', 'register'],
setup(props, { emit }) { setup(props, { emit }) {
const visibleRef = ref(false); const visibleRef = ref(false)
const attrs = useAttrs(); const attrs = useAttrs()
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null); const propsRef = ref<Partial<Nullable<DrawerProps>>>(null)
const { t } = useI18n(); const { t } = useI18n()
const { prefixVar, prefixCls } = useDesign('basic-drawer'); const { prefixVar, prefixCls } = useDesign('basic-drawer')
const drawerInstance: DrawerInstance = { const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps, setDrawerProps: setDrawerProps,
emitVisible: undefined, emitVisible: undefined,
}; }
const instance = getCurrentInstance(); const instance = getCurrentInstance()
instance && emit('register', drawerInstance, instance.uid); instance && emit('register', drawerInstance, instance.uid)
const getMergeProps = computed((): DrawerProps => { const getMergeProps = computed((): DrawerProps => {
return deepMerge(toRaw(props), unref(propsRef)); return deepMerge(toRaw(props), unref(propsRef))
}); })
const getProps = computed((): DrawerProps => { const getProps = computed((): DrawerProps => {
const opt = { const opt = {
@ -86,95 +86,95 @@
...unref(attrs), ...unref(attrs),
...unref(getMergeProps), ...unref(getMergeProps),
visible: unref(visibleRef), visible: unref(visibleRef),
}; }
opt.title = undefined; opt.title = undefined
const { isDetail, width, wrapClassName, getContainer } = opt; const { isDetail, width, wrapClassName, getContainer } = opt
if (isDetail) { if (isDetail) {
if (!width) { if (!width) {
opt.width = '100%'; opt.width = '100%'
} }
const detailCls = `${prefixCls}__detail`; const detailCls = `${prefixCls}__detail`
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls; opt.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: `calc(${heightStr} - 1px)`, 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,193 +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; class?: string
/** /**
* Style of wrapper element which **contains mask** compare to `drawerStyle` * Style of wrapper element which **contains mask** compare to `drawerStyle`
* @type object * @type object
*/ */
wrapStyle?: CSSProperties; wrapStyle?: CSSProperties
/** /**
* Style of the popup layer element * Style of the popup layer element
* @type object * @type object
*/ */
drawerStyle?: CSSProperties; drawerStyle?: CSSProperties
/** /**
* Style of floating layer, typically used for adjusting its position. * Style of floating layer, typically used for adjusting its position.
* @type object * @type object
*/ */
bodyStyle?: CSSProperties; bodyStyle?: CSSProperties
headerStyle?: CSSProperties; headerStyle?: CSSProperties
/** /**
* Width of the Drawer dialog. * Width of the Drawer dialog.
* @default 256 * @default 256
* @type string | number * @type string | number
*/ */
width?: string | number; width?: string | number
/** /**
* placement is top or bottom, height of the Drawer dialog. * placement is top or bottom, height of the Drawer dialog.
* @type string | number * @type string | number
*/ */
height?: string | number; height?: string | number
/** /**
* The z-index of the Drawer. * The z-index of the Drawer.
* @default 1000 * @default 1000
* @type number * @type number
*/ */
zIndex?: number; zIndex?: number
/** /**
* The placement of the Drawer. * The placement of the Drawer.
* @default 'right' * @default 'right'
* @type string * @type string
*/ */
placement?: 'top' | 'right' | 'bottom' | 'left'; placement?: 'top' | 'right' | 'bottom' | 'left'
afterVisibleChange?: (visible?: boolean) => void; afterVisibleChange?: (visible?: boolean) => void
keyboard?: boolean; keyboard?: boolean
/** /**
* Specify a callback that will be called when a user clicks mask, close button or Cancel button. * Specify a callback that will be called when a user clicks mask, close button or Cancel button.
*/ */
onClose?: (e?: Event) => void; onClose?: (e?: Event) => void
} }
export interface DrawerActionType { export interface DrawerActionType {
scrollBottom: () => void; scrollBottom: () => void
scrollTo: (to: number) => void; scrollTo: (to: number) => void
getScrollWrap: () => Element | null; getScrollWrap: () => Element | null
} }

View File

@ -1,17 +1,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 ApiTree } from './src/components/ApiTree.vue'; export { default as ApiTree } from './src/components/ApiTree.vue'
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'
export { default as ApiCascader } from './src/components/ApiCascader.vue'; export { default as ApiCascader } from './src/components/ApiCascader.vue'
export { default as ApiTransfer } from './src/components/ApiTransfer.vue'; export { default as ApiTransfer } from './src/components/ApiTransfer.vue'
export { BasicForm }; export { BasicForm }

View File

@ -37,32 +37,32 @@
</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 { 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'; import { cloneDeep } from 'lodash-es'
export default defineComponent({ export default defineComponent({
name: 'BasicForm', name: 'BasicForm',
@ -70,28 +70,28 @@
props: basicProps, props: basicProps,
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'], 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 [
@ -99,47 +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: any[] = []; 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 cloneDeep( return cloneDeep(
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[], schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
); )
} else { } else {
return cloneDeep(schemas as FormSchema[]); return cloneDeep(schemas as FormSchema[])
} }
}); })
const { handleToggleAdvanced } = useAdvanced({ const { handleToggleAdvanced } = useAdvanced({
advanceState, advanceState,
@ -148,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,
@ -186,77 +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( watch(
() => formModel, () => formModel,
useDebounceFn(() => { useDebounceFn(() => {
unref(getProps).submitOnChange && handleSubmit(); unref(getProps).submitOnChange && handleSubmit()
}, 300), }, 300),
{ deep: true }, { 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); 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()
} }
} }
} }
@ -275,12 +275,12 @@
validate, validate,
submit: handleSubmit, submit: handleSubmit,
scrollToField: scrollToField, scrollToField: scrollToField,
}; }
onMounted(() => { onMounted(() => {
initDefault(); initDefault()
emit('register', formActionType); emit('register', formActionType)
}); })
return { return {
getBindValue, getBindValue,
@ -300,9 +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,65 +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 ApiTree from './components/ApiTree.vue'; import ApiTree from './components/ApiTree.vue'
import ApiTreeSelect from './components/ApiTreeSelect.vue'; import ApiTreeSelect from './components/ApiTreeSelect.vue'
import ApiCascader from './components/ApiCascader.vue'; import ApiCascader from './components/ApiCascader.vue'
import ApiTransfer from './components/ApiTransfer.vue'; import ApiTransfer from './components/ApiTransfer.vue'
import { BasicUpload } from '/@/components/Upload'; import { BasicUpload } from '/@/components/Upload'
import { StrengthMeter } from '/@/components/StrengthMeter'; import { StrengthMeter } from '/@/components/StrengthMeter'
import { IconPicker } from '/@/components/Icon'; import { IconPicker } from '/@/components/Icon'
import { CountdownInput } from '/@/components/CountDown'; import { CountdownInput } from '/@/components/CountDown'
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>()
componentMap.set('Input', Input); componentMap.set('Input', Input)
componentMap.set('InputGroup', Input.Group); componentMap.set('InputGroup', Input.Group)
componentMap.set('InputPassword', Input.Password); componentMap.set('InputPassword', Input.Password)
componentMap.set('InputSearch', Input.Search); componentMap.set('InputSearch', Input.Search)
componentMap.set('InputTextArea', Input.TextArea); componentMap.set('InputTextArea', Input.TextArea)
componentMap.set('InputNumber', InputNumber); componentMap.set('InputNumber', InputNumber)
componentMap.set('AutoComplete', AutoComplete); componentMap.set('AutoComplete', AutoComplete)
componentMap.set('Select', Select); componentMap.set('Select', Select)
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect)
componentMap.set('ApiTree', ApiTree); componentMap.set('ApiTree', ApiTree)
componentMap.set('TreeSelect', TreeSelect); componentMap.set('TreeSelect', TreeSelect)
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('ApiTreeSelect', ApiTreeSelect)
componentMap.set('ApiRadioGroup', ApiRadioGroup); componentMap.set('ApiRadioGroup', ApiRadioGroup)
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch)
componentMap.set('RadioButtonGroup', RadioButtonGroup); componentMap.set('RadioButtonGroup', RadioButtonGroup)
componentMap.set('RadioGroup', Radio.Group); componentMap.set('RadioGroup', Radio.Group)
componentMap.set('Checkbox', Checkbox); componentMap.set('Checkbox', Checkbox)
componentMap.set('CheckboxGroup', Checkbox.Group); componentMap.set('CheckboxGroup', Checkbox.Group)
componentMap.set('ApiCascader', ApiCascader); componentMap.set('ApiCascader', ApiCascader)
componentMap.set('Cascader', Cascader); componentMap.set('Cascader', Cascader)
componentMap.set('Slider', Slider); componentMap.set('Slider', Slider)
componentMap.set('Rate', Rate); componentMap.set('Rate', Rate)
componentMap.set('ApiTransfer', ApiTransfer); componentMap.set('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'; 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,117 +71,117 @@
}, },
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(); 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 {
@ -192,7 +192,7 @@
handleChange, handleChange,
loadData, loadData,
handleRenderDisplay, handleRenderDisplay,
}; }
}, },
}); })
</script> </script>

View File

@ -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',
@ -61,87 +61,87 @@
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change'],
setup(props, { emit }) { setup(props, { emit }) {
const options = ref<OptionsItem[]>([]); const options = ref<OptionsItem[]>([])
const loading = ref(false); const loading = ref(false)
const isFirstLoad = ref(true); const isFirstLoad = ref(true)
const emitData = ref<any[]>([]); const emitData = ref<any[]>([])
const attrs = useAttrs(); const attrs = useAttrs()
const { t } = useI18n(); const { t } = useI18n()
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData); const [state] = useRuleFormItem(props, 'value', 'change', emitData)
const getOptions = computed(() => { const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props; const { labelField, valueField, numberToString } = props
return unref(options).reduce((prev, next: Recordable) => { return unref(options).reduce((prev, next: Recordable) => {
if (next) { if (next) {
const value = next[valueField]; const value = next[valueField]
prev.push({ prev.push({
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
label: next[labelField], label: next[labelField],
value: numberToString ? `${value}` : value, value: numberToString ? `${value}` : value,
}); })
} }
return prev; return prev
}, [] as OptionsItem[]); }, [] as OptionsItem[])
}); })
watchEffect(() => { watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch(); props.immediate && !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(visible) { async function handleFetch(visible) {
if (visible) { if (visible) {
if (props.alwaysLoad) { if (props.alwaysLoad) {
await fetch(); await fetch()
} else if (!props.immediate && unref(isFirstLoad)) { } else if (!props.immediate && unref(isFirstLoad)) {
await fetch(); await fetch()
isFirstLoad.value = false; 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

@ -12,13 +12,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue'; import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue'
import { Transfer } from 'ant-design-vue'; import { Transfer } from 'ant-design-vue'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
import { get, omit } from 'lodash-es'; import { get, omit } from 'lodash-es'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'; import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'
export default defineComponent({ export default defineComponent({
name: 'ApiTransfer', name: 'ApiTransfer',
components: { Transfer }, components: { Transfer },
@ -47,18 +47,18 @@
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change'],
setup(props, { attrs, emit }) { setup(props, { attrs, emit }) {
const _dataSource = ref<TransferItem[]>([]); const _dataSource = ref<TransferItem[]>([])
const _targetKeys = ref<string[]>([]); const _targetKeys = ref<string[]>([])
const { t } = useI18n(); const { t } = useI18n()
const getAttrs = computed(() => { const getAttrs = computed(() => {
return { return {
...(!props.api ? { dataSource: unref(_dataSource) } : {}), ...(!props.api ? { dataSource: unref(_dataSource) } : {}),
...attrs, ...attrs,
}; }
}); })
const getdataSource = computed(() => { const getdataSource = computed(() => {
const { labelField, valueField } = props; const { labelField, valueField } = props
return unref(_dataSource).reduce((prev, next: Recordable) => { return unref(_dataSource).reduce((prev, next: Recordable) => {
if (next) { if (next) {
@ -66,69 +66,69 @@
...omit(next, [labelField, valueField]), ...omit(next, [labelField, valueField]),
title: next[labelField], title: next[labelField],
key: next[valueField], key: next[valueField],
}); })
} }
return prev; return prev
}, [] as TransferItem[]); }, [] as TransferItem[])
}); })
const getTargetKeys = computed<string[]>(() => { const getTargetKeys = computed<string[]>(() => {
if (unref(_targetKeys).length > 0) { if (unref(_targetKeys).length > 0) {
return unref(_targetKeys); return unref(_targetKeys)
} }
if (Array.isArray(props.value)) { if (Array.isArray(props.value)) {
return props.value; return props.value
} }
return []; return []
}); })
function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) { function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
_targetKeys.value = keys; _targetKeys.value = keys
console.log(direction); console.log(direction)
console.log(moveKeys); console.log(moveKeys)
emit('change', keys); emit('change', keys)
} }
watchEffect(() => { watchEffect(() => {
props.immediate && !props.alwaysLoad && fetch(); props.immediate && !props.alwaysLoad && fetch()
}); })
watch( watch(
() => props.params, () => props.params,
() => { () => {
fetch(); fetch()
}, },
{ deep: true }, { deep: true },
); )
async function fetch() { async function fetch() {
const api = props.api; const api = props.api
if (!api || !isFunction(api)) { if (!api || !isFunction(api)) {
if (Array.isArray(props.dataSource)) { if (Array.isArray(props.dataSource)) {
_dataSource.value = props.dataSource; _dataSource.value = props.dataSource
} }
return; return
} }
_dataSource.value = []; _dataSource.value = []
try { try {
const res = await api(props.params); const res = await api(props.params)
if (Array.isArray(res)) { if (Array.isArray(res)) {
_dataSource.value = res; _dataSource.value = res
emitChange(); emitChange()
return; return
} }
if (props.resultField) { if (props.resultField) {
_dataSource.value = get(res, props.resultField) || []; _dataSource.value = get(res, props.resultField) || []
} }
emitChange(); emitChange()
} catch (error) { } catch (error) {
console.warn(error); console.warn(error)
} finally { } finally {
} }
} }
function emitChange() { function emitChange() {
emit('options-change', unref(getdataSource)); emit('options-change', unref(getdataSource))
} }
return { getTargetKeys, getdataSource, t, getAttrs, handleChange }; return { getTargetKeys, getdataSource, t, getAttrs, handleChange }
}, },
}); })
</script> </script>

View File

@ -10,12 +10,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue'; import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue'
import { Tree } from 'ant-design-vue'; import { Tree } from 'ant-design-vue'
import { isArray, isFunction } from '/@/utils/is'; import { isArray, isFunction } from '/@/utils/is'
import { get } from 'lodash-es'; import { get } from 'lodash-es'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { LoadingOutlined } from '@ant-design/icons-vue'; import { LoadingOutlined } from '@ant-design/icons-vue'
export default defineComponent({ export default defineComponent({
name: 'ApiTree', name: 'ApiTree',
components: { ATree: Tree, LoadingOutlined }, components: { ATree: Tree, LoadingOutlined },
@ -28,63 +28,63 @@
}, },
emits: ['options-change', 'change'], emits: ['options-change', 'change'],
setup(props, { attrs, emit }) { setup(props, { attrs, emit }) {
const treeData = ref<Recordable[]>([]); const treeData = ref<Recordable[]>([])
const isFirstLoaded = ref<Boolean>(false); const isFirstLoaded = ref<Boolean>(false)
const loading = ref(false); const loading = ref(false)
const getAttrs = computed(() => { const getAttrs = computed(() => {
return { return {
...(props.api ? { treeData: unref(treeData) } : {}), ...(props.api ? { treeData: unref(treeData) } : {}),
...attrs, ...attrs,
}; }
}); })
function handleChange(...args) { function handleChange(...args) {
emit('change', ...args); emit('change', ...args)
} }
watch( watch(
() => props.params, () => props.params,
() => { () => {
!unref(isFirstLoaded) && fetch(); !unref(isFirstLoaded) && fetch()
}, },
{ deep: true }, { deep: true },
); )
watch( watch(
() => props.immediate, () => props.immediate,
(v) => { (v) => {
v && !isFirstLoaded.value && fetch(); v && !isFirstLoaded.value && fetch()
}, },
); )
onMounted(() => { onMounted(() => {
props.immediate && fetch(); props.immediate && fetch()
}); })
async function fetch() { async function fetch() {
const { api, afterFetch } = props; const { api, afterFetch } = props
if (!api || !isFunction(api)) return; if (!api || !isFunction(api)) return
loading.value = true; loading.value = true
treeData.value = []; treeData.value = []
let result; let result
try { try {
result = await api(props.params); result = await api(props.params)
} catch (e) { } catch (e) {
console.error(e); console.error(e)
} }
if (afterFetch && isFunction(afterFetch)) { if (afterFetch && isFunction(afterFetch)) {
result = afterFetch(result); result = afterFetch(result)
} }
loading.value = false; loading.value = false
if (!result) return; if (!result) return
if (!isArray(result)) { if (!isArray(result)) {
result = get(result, props.resultField); result = get(result, props.resultField)
} }
treeData.value = (result as Recordable[]) || []; treeData.value = (result as Recordable[]) || []
isFirstLoaded.value = true; isFirstLoaded.value = true
emit('options-change', treeData.value); emit('options-change', treeData.value)
} }
return { getAttrs, loading, handleChange }; return { getAttrs, loading, handleChange }
}, },
}); })
</script> </script>

View File

@ -1,18 +1,18 @@
<script lang="tsx"> <script lang="tsx">
import type { PropType, Ref } from 'vue'; import type { PropType, Ref } from 'vue'
import { computed, defineComponent, toRefs, unref } from 'vue'; import { computed, defineComponent, toRefs, unref } from 'vue'
import type { FormActionType, FormProps, 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 { Col, Divider, Form } from 'ant-design-vue'; import { Col, Divider, Form } 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 { cloneDeep, upperFirst } from 'lodash-es'
import { useItemLabelWidth } from '../hooks/useLabelWidth'; import { useItemLabelWidth } from '../hooks/useLabelWidth'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
export default defineComponent({ export default defineComponent({
name: 'BasicFormItem', name: 'BasicFormItem',
@ -46,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,
@ -67,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[] {
@ -135,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') &&
@ -170,12 +170,12 @@
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
/* /*
* 1若设置了required属性又没有其他的rules就创建一个验证规则 * 1若设置了required属性又没有其他的rules就创建一个验证规则
@ -184,49 +184,49 @@
*/ */
if (getRequired) { if (getRequired) {
if (!rules || rules.length === 0) { if (!rules || rules.length === 0) {
rules = [{ required: getRequired, validator }]; rules = [{ required: getRequired, validator }]
} else { } else {
const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required')); const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required'))
if (requiredIndex === -1) { if (requiredIndex === -1) {
rules.push({ required: getRequired, validator }); rules.push({ required: getRequired, validator })
} }
} }
} }
const requiredRuleIndex: number = rules.findIndex( const requiredRuleIndex: number = rules.findIndex(
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'), (rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
); )
if (requiredRuleIndex !== -1) { if (requiredRuleIndex !== -1) {
const rule = rules[requiredRuleIndex]; const rule = rules[requiredRuleIndex]
const { isShow } = getShow(); const { isShow } = getShow()
if (!isShow) { if (!isShow) {
rule.required = false; rule.required = false
} }
if (component) { if (component) {
if (!Reflect.has(rule, 'type')) { if (!Reflect.has(rule, 'type')) {
rule.type = component === 'InputNumber' ? 'number' : 'string'; rule.type = component === 'InputNumber' ? 'number' : 'string'
} }
rule.message = rule.message || defaultMsg; rule.message = rule.message || defaultMsg
if (component.includes('Input') || component.includes('Textarea')) { if (component.includes('Input') || component.includes('Textarea')) {
rule.whitespace = true; rule.whitespace = true
} }
const valueFormat = unref(getComponentsProps)?.valueFormat; const valueFormat = unref(getComponentsProps)?.valueFormat
setComponentRuleType(rule, component, valueFormat); setComponentRuleType(rule, component, valueFormat)
} }
} }
// Maximum input length rule check // Maximum input length rule check
const characterInx = rules.findIndex((val) => val.max); const characterInx = rules.findIndex((val) => val.max)
if (characterInx !== -1 && !rules[characterInx].validator) { if (characterInx !== -1 && !rules[characterInx].validator) {
rules[characterInx].message = rules[characterInx].message =
rules[characterInx].message || rules[characterInx].message ||
t('component.form.maxTip', [rules[characterInx].max] as Recordable); t('component.form.maxTip', [rules[characterInx].max] as Recordable)
} }
return rules; return rules
} }
function renderComponent() { function renderComponent() {
@ -236,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
@ -356,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 && (
@ -385,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,34 +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']; export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'InputTextArea']

View File

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

View File

@ -1,96 +1,96 @@
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'; import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'
import type { NamePath } from 'ant-design-vue/lib/form/interface'; import type { NamePath } from 'ant-design-vue/lib/form/interface'
import type { DynamicProps } from '/#/utils'; import type { DynamicProps } from '/#/utils'
import { ref, onUnmounted, unref, nextTick, watch } from 'vue'; import { ref, onUnmounted, unref, nextTick, watch } from 'vue'
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env'
import { error } from '/@/utils/log'; import { error } from '/@/utils/log'
import { getDynamicProps } from '/@/utils'; import { getDynamicProps } from '/@/utils'
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>; export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>
type Props = Partial<DynamicProps<FormProps>>; type Props = Partial<DynamicProps<FormProps>>
export function useForm(props?: Props): UseFormReturnType { export function useForm(props?: Props): UseFormReturnType {
const formRef = ref<Nullable<FormActionType>>(null); const formRef = ref<Nullable<FormActionType>>(null)
const loadedRef = ref<Nullable<boolean>>(false); const loadedRef = ref<Nullable<boolean>>(false)
async function getForm() { async function getForm() {
const form = unref(formRef); const form = unref(formRef)
if (!form) { if (!form) {
error( error(
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!', 'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
); )
} }
await nextTick(); await nextTick()
return form as FormActionType; return form as FormActionType
} }
function register(instance: FormActionType) { function register(instance: FormActionType) {
isProdMode() && isProdMode() &&
onUnmounted(() => { onUnmounted(() => {
formRef.value = null; formRef.value = null
loadedRef.value = null; loadedRef.value = null
}); })
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return; if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return
formRef.value = instance; formRef.value = instance
loadedRef.value = true; loadedRef.value = true
watch( watch(
() => props, () => props,
() => { () => {
props && instance.setProps(getDynamicProps(props)); props && instance.setProps(getDynamicProps(props))
}, },
{ {
immediate: true, immediate: true,
deep: true, deep: true,
}, },
); )
} }
const methods: FormActionType = { const methods: FormActionType = {
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => { scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
const form = await getForm(); const form = await getForm()
form.scrollToField(name, options); form.scrollToField(name, options)
}, },
setProps: async (formProps: Partial<FormProps>) => { setProps: async (formProps: Partial<FormProps>) => {
const form = await getForm(); const form = await getForm()
form.setProps(formProps); form.setProps(formProps)
}, },
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => { updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm(); const form = await getForm()
form.updateSchema(data); form.updateSchema(data)
}, },
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => { resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm(); const form = await getForm()
form.resetSchema(data); form.resetSchema(data)
}, },
clearValidate: async (name?: string | string[]) => { clearValidate: async (name?: string | string[]) => {
const form = await getForm(); const form = await getForm()
form.clearValidate(name); form.clearValidate(name)
}, },
resetFields: async () => { resetFields: async () => {
getForm().then(async (form) => { getForm().then(async (form) => {
await form.resetFields(); await form.resetFields()
}); })
}, },
removeSchemaByField: async (field: string | string[]) => { removeSchemaByField: async (field: string | string[]) => {
unref(formRef)?.removeSchemaByField(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, nextTick } from 'vue'; import { unref, toRaw, nextTick } from 'vue'
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is'; import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is'
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils'
import { dateItemType, handleInputNumberValue, defaultValueComponents } 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,22 +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) => {
const schema = unref(getSchema).find((item) => item.field === key); const schema = unref(getSchema).find((item) => item.field === key)
const isInput = schema?.component && defaultValueComponents.includes(schema.component); const isInput = schema?.component && defaultValueComponents.includes(schema.component)
const defaultValue = cloneDeep(defaultValueRef.value[key]); const defaultValue = cloneDeep(defaultValueRef.value[key])
formModel[key] = isInput ? defaultValue || '' : defaultValue; formModel[key] = isInput ? defaultValue || '' : defaultValue
}); })
nextTick(() => clearValidate()); nextTick(() => clearValidate())
emit('reset', toRaw(formModel)); emit('reset', toRaw(formModel))
submitOnReset && handleSubmit(); submitOnReset && handleSubmit()
} }
/** /**
@ -54,78 +54,78 @@ export function useFormEvents({
async function setFieldsValue(values: Recordable): Promise<void> { async function setFieldsValue(values: Recordable): Promise<void> {
const fields = unref(getSchema) const fields = unref(getSchema)
.map((item) => item.field) .map((item) => item.field)
.filter(Boolean); .filter(Boolean)
// key 支持 a.b.c 的嵌套写法 // key 支持 a.b.c 的嵌套写法
const delimiter = '.'; const delimiter = '.'
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0); const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0)
const validKeys: string[] = []; const validKeys: string[] = []
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key); const schema = unref(getSchema).find((item) => item.field === key)
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 { } else {
nestKeyArray.forEach((nestKey: string) => { nestKeyArray.forEach((nestKey: string) => {
try { try {
const value = eval('values' + delimiter + nestKey); const value = eval('values' + delimiter + nestKey)
if (isDef(value)) { if (isDef(value)) {
formModel[nestKey] = value; formModel[nestKey] = value
validKeys.push(nestKey); validKeys.push(nestKey)
} }
} catch (e) { } catch (e) {
// key not exist // key not exist
if (isDef(defaultValueRef.value[nestKey])) { if (isDef(defaultValueRef.value[nestKey])) {
formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey]); formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey])
} }
} }
}); })
} }
}); })
validateFields(validKeys).catch((_) => {}); validateFields(validKeys).catch((_) => {})
} }
/** /**
* @description: Delete based on field name * @description: Delete based on field name
*/ */
async function removeSchemaByField(fields: string | string[]): Promise<void> { async function 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) {
_removeSchemaByFeild(field, schemaList); _removeSchemaByFeild(field, schemaList)
} }
schemaRef.value = schemaList; schemaRef.value = schemaList
} }
/** /**
@ -133,10 +133,10 @@ export function useFormEvents({
*/ */
function _removeSchemaByFeild(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)
} }
} }
} }
@ -145,92 +145,92 @@ 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)
if (!prefixField || index === -1 || first) { if (!prefixField || index === -1 || first) {
first ? schemaList.unshift(schema) : schemaList.push(schema); first ? schemaList.unshift(schema) : schemaList.push(schema)
schemaRef.value = schemaList; schemaRef.value = schemaList
_setDefaultValue(schema); _setDefaultValue(schema)
return; return
} }
if (index !== -1) { if (index !== -1) {
schemaList.splice(index + 1, 0, schema); schemaList.splice(index + 1, 0, schema)
} }
_setDefaultValue(schema); _setDefaultValue(schema)
schemaRef.value = schemaList; schemaRef.value = schemaList
} }
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) { async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = []; let updateData: Partial<FormSchema>[] = []
if (isObject(data)) { if (isObject(data)) {
updateData.push(data as FormSchema); updateData.push(data as FormSchema)
} }
if (isArray(data)) { if (isArray(data)) {
updateData = [...data]; updateData = [...data]
} }
const hasField = updateData.every( const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field), (item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
); )
if (!hasField) { if (!hasField) {
error( error(
'All children of the form Schema array that need to be updated must contain the `field` field', 'All children of the form Schema array that need to be updated must contain the `field` field',
); )
return; return
} }
schemaRef.value = updateData as FormSchema[]; schemaRef.value = updateData as FormSchema[]
} }
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) { async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = []; let updateData: Partial<FormSchema>[] = []
if (isObject(data)) { if (isObject(data)) {
updateData.push(data as FormSchema); updateData.push(data as FormSchema)
} }
if (isArray(data)) { if (isArray(data)) {
updateData = [...data]; updateData = [...data]
} }
const hasField = updateData.every( const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field), (item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
); )
if (!hasField) { if (!hasField) {
error( error(
'All children of the form Schema array that need to be updated must contain the `field` field', 'All children of the form Schema array that need to be updated must contain the `field` field',
); )
return; return
} }
const schema: FormSchema[] = []; const schema: FormSchema[] = []
updateData.forEach((item) => { updateData.forEach((item) => {
unref(getSchema).forEach((val) => { unref(getSchema).forEach((val) => {
if (val.field === item.field) { if (val.field === item.field) {
const newSchema = deepMerge(val, item); const newSchema = deepMerge(val, item)
schema.push(newSchema as FormSchema); schema.push(newSchema as FormSchema)
} else { } else {
schema.push(val); schema.push(val)
} }
}); })
}); })
_setDefaultValue(schema); _setDefaultValue(schema)
schemaRef.value = uniqBy(schema, 'field'); schemaRef.value = uniqBy(schema, 'field')
} }
function _setDefaultValue(data: FormSchema | FormSchema[]) { function _setDefaultValue(data: FormSchema | FormSchema[]) {
let schemas: FormSchema[] = []; let schemas: FormSchema[] = []
if (isObject(data)) { if (isObject(data)) {
schemas.push(data as FormSchema); schemas.push(data as FormSchema)
} }
if (isArray(data)) { if (isArray(data)) {
schemas = [...data]; schemas = [...data]
} }
const obj: Recordable = {}; const obj: Recordable = {}
const currentFieldsValue = getFieldsValue(); const currentFieldsValue = getFieldsValue()
schemas.forEach((item) => { schemas.forEach((item) => {
if ( if (
item.component != 'Divider' && item.component != 'Divider' &&
@ -239,16 +239,16 @@ export function useFormEvents({
!isNullOrUnDef(item.defaultValue) && !isNullOrUnDef(item.defaultValue) &&
!(item.field in currentFieldsValue) !(item.field in currentFieldsValue)
) { ) {
obj[item.field] = item.defaultValue; obj[item.field] = item.defaultValue
} }
}); })
setFieldsValue(obj); setFieldsValue(obj)
} }
function getFieldsValue(): Recordable { function getFieldsValue(): Recordable {
const formEl = unref(formElRef); const formEl = unref(formElRef)
if (!formEl) return {}; if (!formEl) return {}
return handleFormValues(toRaw(unref(formModel))); return handleFormValues(toRaw(unref(formModel)))
} }
/** /**
@ -256,44 +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)
} }
} }
@ -310,5 +310,5 @@ export function useFormEvents({
resetFields, resetFields,
setFieldsValue, setFieldsValue,
scrollToField, scrollToField,
}; }
} }

View File

@ -1,31 +1,31 @@
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'; import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil'
import { unref } from 'vue'; import { unref } from 'vue'
import type { Ref, ComputedRef } from 'vue'; import type { Ref, ComputedRef } from 'vue'
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form'
import { cloneDeep, set } from 'lodash-es'; import { 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. * @desription deconstruct array-link key. This method will mutate the target.
*/ */
function tryDeconstructArray(key: string, value: any, target: Recordable) { function tryDeconstructArray(key: string, value: any, target: Recordable) {
const pattern = /^\[(.+)\]$/; const pattern = /^\[(.+)\]$/
if (pattern.test(key)) { if (pattern.test(key)) {
const match = key.match(pattern); const match = key.match(pattern)
if (match && match[1]) { if (match && match[1]) {
const keys = match[1].split(','); const keys = match[1].split(',')
value = Array.isArray(value) ? value : [value]; value = Array.isArray(value) ? value : [value]
keys.forEach((k, index) => { keys.forEach((k, index) => {
set(target, k.trim(), value[index]); set(target, k.trim(), value[index])
}); })
return true; return true
} }
} }
} }
@ -34,16 +34,16 @@ function tryDeconstructArray(key: string, value: any, target: Recordable) {
* @desription deconstruct object-link key. This method will mutate the target. * @desription deconstruct object-link key. This method will mutate the target.
*/ */
function tryDeconstructObject(key: string, value: any, target: Recordable) { function tryDeconstructObject(key: string, value: any, target: Recordable) {
const pattern = /^\{(.+)\}$/; const pattern = /^\{(.+)\}$/
if (pattern.test(key)) { if (pattern.test(key)) {
const match = key.match(pattern); const match = key.match(pattern)
if (match && match[1]) { if (match && match[1]) {
const keys = match[1].split(','); const keys = match[1].split(',')
value = isObject(value) ? value : {}; value = isObject(value) ? value : {}
keys.forEach((k) => { keys.forEach((k) => {
set(target, k.trim(), value[k.trim()]); set(target, k.trim(), value[k.trim()])
}); })
return true; return true
} }
} }
} }
@ -57,75 +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]?.format && value[1]?.format) { if (isArray(value) && value[0]?.format && value[1]?.format) {
value = value.map((item) => transformDateFunc?.(item)); value = value.map((item) => transformDateFunc?.(item))
} }
// Remove spaces // Remove spaces
if (isString(value)) { if (isString(value)) {
value = value.trim(); value = value.trim()
} }
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) { 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
if (formModel[item.field] === undefined) { if (formModel[item.field] === undefined) {
formModel[item.field] = defaultValue; formModel[item.field] = defaultValue
} }
} }
}); })
defaultValueRef.value = cloneDeep(obj); defaultValueRef.value = cloneDeep(obj)
} }
return { handleFormValues, initDefault }; return { handleFormValues, initDefault }
} }

View File

@ -1,34 +1,34 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue'
import { computed, unref } from 'vue'; import { computed, unref } from 'vue'
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form'
import { isNumber } from '/@/utils/is'; import { isNumber } from '/@/utils/is'
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) { export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
return computed(() => { return computed(() => {
const schemaItem = unref(schemaItemRef); const schemaItem = unref(schemaItemRef)
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}; const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}
const { labelWidth, disabledLabelWidth } = schemaItem; const { labelWidth, disabledLabelWidth } = schemaItem
const { const {
labelWidth: globalLabelWidth, labelWidth: globalLabelWidth,
labelCol: globalLabelCol, labelCol: globalLabelCol,
wrapperCol: globWrapperCol, wrapperCol: globWrapperCol,
layout, layout,
} = unref(propsRef); } = unref(propsRef)
// If labelWidth is set globally, all items setting // If labelWidth is set globally, all items setting
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) { if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
labelCol.style = { labelCol.style = {
textAlign: 'left', textAlign: 'left',
}; }
return { labelCol, wrapperCol }; return { labelCol, wrapperCol }
} }
let width = labelWidth || globalLabelWidth; let width = labelWidth || globalLabelWidth
const col = { ...globalLabelCol, ...labelCol }; const col = { ...globalLabelCol, ...labelCol }
const wrapCol = { ...globWrapperCol, ...wrapperCol }; const wrapCol = { ...globWrapperCol, ...wrapperCol }
if (width) { if (width) {
width = isNumber(width) ? `${width}px` : width; width = isNumber(width) ? `${width}px` : width
} }
return { return {
@ -37,6 +37,6 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` }, style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
...wrapCol, ...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: {
@ -54,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?.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),
@ -100,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,223 +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>
removeSchemaByField: (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 {
name?: string; name?: string
layout?: 'vertical' | 'inline' | 'horizontal'; layout?: 'vertical' | 'inline' | 'horizontal'
// Form value // Form value
model?: Recordable; model?: Recordable
// The width of all items in the entire form // The width of all items in the entire form
labelWidth?: number | string; labelWidth?: number | string
// alignment // alignment
labelAlign?: 'left' | 'right'; labelAlign?: 'left' | 'right'
// Row configuration for the entire form // Row configuration for the entire form
rowProps?: RowProps; rowProps?: RowProps
// Submit form on reset // Submit form on reset
submitOnReset?: boolean; submitOnReset?: boolean
// Submit form on form changing // Submit form on form changing
submitOnChange?: boolean; 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 =
@ -114,4 +114,4 @@ export type ComponentType =
| 'Slider' | 'Slider'
| 'Rate' | 'Rate'
| 'Divider' | 'Divider'
| 'ApiTransfer'; | 'ApiTransfer'

View File

@ -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,9 +1,9 @@
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',
@ -11,21 +11,21 @@ export default defineComponent({
props: basicProps, props: basicProps,
emits: ['cancel'], emits: ['cancel'],
setup(props, { slots, emit }) { 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) => { const onCancel = (e: Event) => {
emit('cancel', e); emit('cancel', e)
}; }
return () => { return () => {
const propsData = { ...unref(attrs), ...props, onCancel } as Recordable; const propsData = { ...unref(attrs), ...props, onCancel } as Recordable
return <Modal {...propsData}>{extendSlots(slots)}</Modal>; return <Modal {...propsData}>{extendSlots(slots)}</Modal>
}; }
}, },
}); })

View File

@ -110,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

@ -33,17 +33,17 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { CSSProperties, PropType, provide } from 'vue'; import { CSSProperties, PropType, provide } from 'vue'
import { defineComponent, computed, watch, ref, unref } from 'vue'; import { defineComponent, computed, watch, ref, unref } from 'vue'
import PageFooter from './PageFooter.vue'; import PageFooter from './PageFooter.vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { omit } from 'lodash-es'; import { omit } from 'lodash-es'
import { PageHeader } from 'ant-design-vue'; import { PageHeader } from 'ant-design-vue'
import { useContentHeight } from '/@/hooks/web/useContentHeight'; import { useContentHeight } from '/@/hooks/web/useContentHeight'
import { PageWrapperFixedHeightKey } from '..'; import { PageWrapperFixedHeightKey } from '..'
export default defineComponent({ export default defineComponent({
name: 'PageWrapper', name: 'PageWrapper',
@ -64,30 +64,30 @@
upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0), upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
}, },
setup(props, { slots, attrs }) { setup(props, { slots, attrs }) {
const wrapperRef = ref(null); const wrapperRef = ref(null)
const headerRef = ref(null); const headerRef = ref(null)
const contentRef = ref(null); const contentRef = ref(null)
const footerRef = ref(null); const footerRef = ref(null)
const { prefixCls } = useDesign('page-wrapper'); const { prefixCls } = useDesign('page-wrapper')
provide( provide(
PageWrapperFixedHeightKey, PageWrapperFixedHeightKey,
computed(() => props.fixedHeight), computed(() => props.fixedHeight),
); )
const getIsContentFullHeight = computed(() => { const getIsContentFullHeight = computed(() => {
return props.contentFullHeight; return props.contentFullHeight
}); })
const getUpwardSpace = computed(() => props.upwardSpace); const getUpwardSpace = computed(() => props.upwardSpace)
const { redoHeight, setCompensation, contentHeight } = useContentHeight( const { redoHeight, setCompensation, contentHeight } = useContentHeight(
getIsContentFullHeight, getIsContentFullHeight,
wrapperRef, wrapperRef,
[headerRef, footerRef], [headerRef, footerRef],
[contentRef], [contentRef],
getUpwardSpace, getUpwardSpace,
); )
setCompensation({ useLayoutFooter: true, elements: [footerRef] }); setCompensation({ useLayoutFooter: true, elements: [footerRef] })
const getClass = computed(() => { const getClass = computed(() => {
return [ return [
@ -96,54 +96,54 @@
[`${prefixCls}--dense`]: props.dense, [`${prefixCls}--dense`]: props.dense,
}, },
attrs.class ?? {}, attrs.class ?? {},
]; ]
}); })
const getShowHeader = computed( const getShowHeader = computed(
() => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length, () => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
); )
const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter); const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter)
const getHeaderSlots = computed(() => { const getHeaderSlots = computed(() => {
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,
@ -158,9 +158,9 @@
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

@ -43,18 +43,18 @@
</SubMenu> </SubMenu>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import type { Menu } from '/@/router/types'; import type { Menu } from '/@/router/types'
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import Icon from '/@/components/Icon/index'; import Icon from '/@/components/Icon/index'
import MenuItem from './components/MenuItem.vue'; import MenuItem from './components/MenuItem.vue'
import SubMenu from './components/SubMenuItem.vue'; import SubMenu from './components/SubMenuItem.vue'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'
export default defineComponent({ export default defineComponent({
name: 'SimpleSubMenu', name: 'SimpleSubMenu',
@ -75,22 +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 (
@ -98,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 {
@ -110,7 +110,7 @@
getShowSubTitle, getShowSubTitle,
getLevelClass, getLevelClass,
getIsCollapseParent, getIsCollapseParent,
}; }
}, },
}); })
</script> </script>

View File

@ -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

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

View File

@ -39,40 +39,35 @@
</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 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 { useTableScrollTo } from './hooks/useScrollTo'; import { useTableScrollTo } from './hooks/useScrollTo'
import { useCustomRow } from './hooks/useCustomRow'; import { useCustomRow } from './hooks/useCustomRow'
import { useTableStyle } from './hooks/useTableStyle'; import { useTableStyle } from './hooks/useTableStyle'
import { useTableHeader } from './hooks/useTableHeader'; import { useTableHeader } from './hooks/useTableHeader'
import { useTableExpand } from './hooks/useTableExpand'; import { useTableExpand } from './hooks/useTableExpand'
import { createTableContext } from './hooks/useTableContext'; import { createTableContext } from './hooks/useTableContext'
import { useTableFooter } from './hooks/useTableFooter'; import { useTableFooter } from './hooks/useTableFooter'
import { useTableForm } from './hooks/useTableForm'; import { useTableForm } from './hooks/useTableForm'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { omit } from 'lodash-es'; import { omit } from 'lodash-es'
import { basicProps } from './props'; import { basicProps } from './props'
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is'
import { warn } from '/@/utils/log'; import { warn } from '/@/utils/log'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -100,37 +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 formRef = ref(null); const formRef = ref(null)
const innerPropsRef = ref<Partial<BasicTableProps>>(); const innerPropsRef = ref<Partial<BasicTableProps>>()
const { prefixCls } = useDesign('basic-table'); const { prefixCls } = useDesign('basic-table')
const [registerForm, formActions] = useForm(); const [registerForm, formActions] = useForm()
const getProps = computed(() => { const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps; return { ...props, ...unref(innerPropsRef) } as BasicTableProps
}); })
const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false); const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false)
watchEffect(() => { watchEffect(() => {
unref(isFixedHeightPage) && unref(isFixedHeightPage) &&
props.canResize && props.canResize &&
warn( warn(
"'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)", "'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)",
); )
}); })
const { getLoading, setLoading } = useLoading(getProps); const { getLoading, setLoading } = useLoading(getProps)
const { const {
getPaginationInfo, getPaginationInfo,
getPagination, getPagination,
setPagination, setPagination,
setShowPagination, setShowPagination,
getShowPagination, getShowPagination,
} = usePagination(getProps); } = usePagination(getProps)
const { const {
getRowSelection, getRowSelection,
@ -140,7 +135,7 @@
getSelectRowKeys, getSelectRowKeys,
deleteSelectRowByKey, deleteSelectRowByKey,
setSelectedRowKeys, setSelectedRowKeys,
} = useRowSelection(getProps, tableData, emit); } = useRowSelection(getProps, tableData, emit)
const { const {
handleTableChange: onTableChange, handleTableChange: onTableChange,
@ -168,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 {
@ -185,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,
@ -195,9 +190,9 @@
getDataSourceRef, getDataSourceRef,
wrapRef, wrapRef,
formRef, formRef,
); )
const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef); const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef)
const { customRow } = useCustomRow(getProps, { const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys, setSelectedRowKeys,
@ -205,38 +200,38 @@
clearSelectedRowKeys, clearSelectedRowKeys,
getAutoCreateKey, getAutoCreateKey,
emit, emit,
}); })
const { getRowClassName } = useTableStyle(getProps, prefixCls); const { getRowClassName } = useTableStyle(getProps, prefixCls)
const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand( const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(
getProps, getProps,
tableData, tableData,
emit, 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 = {
...attrs, ...attrs,
customRow, customRow,
@ -252,17 +247,17 @@
dataSource, dataSource,
footer: unref(getFooterProps), footer: unref(getFooterProps),
...unref(getExpandOption), ...unref(getExpandOption),
}; }
// if (slots.expandedRowRender) { // if (slots.expandedRowRender) {
// propsData = omit(propsData, 'scroll'); // propsData = omit(propsData, 'scroll');
// } // }
propsData = omit(propsData, ['class', 'onChange']); propsData = omit(propsData, ['class', 'onChange'])
return propsData; return propsData
}); })
const getWrapperClass = computed(() => { const getWrapperClass = computed(() => {
const values = unref(getBindValues); const values = unref(getBindValues)
return [ return [
prefixCls, prefixCls,
attrs.class, attrs.class,
@ -270,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 = {
@ -318,14 +313,14 @@
collapseAll, collapseAll,
scrollTo, 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, formRef,
@ -345,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;

View File

@ -1,4 +1,4 @@
import type { Component } from 'vue'; import type { Component } from 'vue'
import { import {
Input, Input,
Select, Select,
@ -9,32 +9,32 @@ import {
TimePicker, TimePicker,
AutoComplete, AutoComplete,
Radio, Radio,
} from 'ant-design-vue'; } from 'ant-design-vue'
import type { ComponentType } from './types/componentType'; import type { ComponentType } from './types/componentType'
import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form'; import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form'
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>()
componentMap.set('Input', Input); componentMap.set('Input', Input)
componentMap.set('InputNumber', InputNumber); componentMap.set('InputNumber', InputNumber)
componentMap.set('Select', Select); componentMap.set('Select', Select)
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect)
componentMap.set('AutoComplete', AutoComplete); componentMap.set('AutoComplete', AutoComplete)
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('ApiTreeSelect', ApiTreeSelect)
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch)
componentMap.set('Checkbox', Checkbox); componentMap.set('Checkbox', Checkbox)
componentMap.set('DatePicker', DatePicker); componentMap.set('DatePicker', DatePicker)
componentMap.set('TimePicker', TimePicker); componentMap.set('TimePicker', TimePicker)
componentMap.set('RadioGroup', Radio.Group); componentMap.set('RadioGroup', Radio.Group)
componentMap.set('RadioButtonGroup', RadioButtonGroup); componentMap.set('RadioButtonGroup', RadioButtonGroup)
componentMap.set('ApiRadioGroup', ApiRadioGroup); 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

@ -6,12 +6,12 @@
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" /> <BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import type { BasicColumn } from '../types/table'; import type { BasicColumn } from '../types/table'
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue'
import BasicHelp from '/@/components/Basic/src/BasicHelp.vue'; import BasicHelp from '/@/components/Basic/src/BasicHelp.vue'
import EditTableHeaderCell from './EditTableHeaderIcon.vue'; import EditTableHeaderCell from './EditTableHeaderIcon.vue'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
export default defineComponent({ export default defineComponent({
name: 'TableHeaderCell', name: 'TableHeaderCell',
@ -26,15 +26,15 @@
}, },
}, },
setup(props) { setup(props) {
const { prefixCls } = useDesign('basic-table-header-cell'); const { prefixCls } = useDesign('basic-table-header-cell')
const getIsEdit = computed(() => !!props.column?.edit); const getIsEdit = computed(() => !!props.column?.edit)
const getTitle = computed(() => props.column?.customTitle || props.column?.title); const getTitle = computed(() => props.column?.customTitle || 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,16 +99,16 @@
onConfirm: popConfirm?.confirm, onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel, onCancel: popConfirm?.cancel,
enable: !!popConfirm, enable: !!popConfirm,
}; }
}); })
}); })
const getDropdownList = computed((): any[] => { const getDropdownList = computed((): any[] => {
const list = (toRaw(props.dropDownActions) || []).filter((action) => { const list = (toRaw(props.dropDownActions) || []).filter((action) => {
return hasPermission(action.auth) && isIfShow(action); return hasPermission(action.auth) && isIfShow(action)
}); })
return list.map((action, index) => { return list.map((action, index) => {
const { label, popConfirm } = action; const { label, popConfirm } = action
return { return {
...action, ...action,
...popConfirm, ...popConfirm,
@ -116,36 +116,36 @@
onCancel: popConfirm?.cancel, onCancel: popConfirm?.cancel,
text: label, text: label,
divider: index < list.length - 1 ? props.divider : false, divider: index < list.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,22 +1,22 @@
<script lang="tsx"> <script lang="tsx">
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue'
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'; import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'
import type { BasicColumn } from '../../types/table'; import type { BasicColumn } from '../../types/table'
import type { EditRecordRow } from './index'; import type { EditRecordRow } from './index'
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue'; import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue'
import { CellComponent } from './CellComponent'; import { CellComponent } from './CellComponent'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useTableContext } from '../../hooks/useTableContext'; import { useTableContext } from '../../hooks/useTableContext'
import clickOutside from '/@/directives/clickOutside'; import clickOutside from '/@/directives/clickOutside'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'; import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is'
import { createPlaceholderMessage } from './helper'; import { createPlaceholderMessage } from './helper'
import { pick, set } from 'lodash-es'; import { pick, set } from 'lodash-es'
import { treeToList } from '/@/utils/helper/treeHelper'; import { treeToList } from '/@/utils/helper/treeHelper'
import { Spin } from 'ant-design-vue'; import { Spin } from 'ant-design-vue'
export default defineComponent({ export default defineComponent({
name: 'EditableCell', name: 'EditableCell',
@ -39,50 +39,50 @@
index: propTypes.number, index: propTypes.number,
}, },
setup(props) { setup(props) {
const table = useTableContext(); const table = useTableContext()
const isEdit = ref(false); const isEdit = ref(false)
const elRef = ref(); const elRef = ref()
const ruleVisible = ref(false); const ruleVisible = ref(false)
const ruleMessage = ref(''); const ruleMessage = ref('')
const optionsRef = ref<LabelValueOptions>([]); const optionsRef = ref<LabelValueOptions>([])
const currentValueRef = ref<any>(props.value); const currentValueRef = ref<any>(props.value)
const defaultValueRef = ref<any>(props.value); const defaultValueRef = ref<any>(props.value)
const spinning = ref<boolean>(false); const spinning = ref<boolean>(false)
const { prefixCls } = useDesign('editable-cell'); const { prefixCls } = useDesign('editable-cell')
const getComponent = computed(() => props.column?.editComponent || 'Input'); const getComponent = computed(() => props.column?.editComponent || 'Input')
const getRule = computed(() => props.column?.editRule); const getRule = computed(() => props.column?.editRule)
const getRuleVisible = computed(() => { const getRuleVisible = computed(() => {
return unref(ruleMessage) && unref(ruleVisible); return unref(ruleMessage) && unref(ruleVisible)
}); })
const getIsCheckComp = computed(() => { const getIsCheckComp = computed(() => {
const component = unref(getComponent); const component = unref(getComponent)
return ['Checkbox', 'Switch'].includes(component); return ['Checkbox', 'Switch'].includes(component)
}); })
const getComponentProps = computed(() => { const getComponentProps = computed(() => {
const isCheckValue = unref(getIsCheckComp); const isCheckValue = unref(getIsCheckComp)
const valueField = isCheckValue ? 'checked' : 'value'; const valueField = isCheckValue ? 'checked' : 'value'
const val = unref(currentValueRef); const val = unref(currentValueRef)
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val; const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val
let compProps = props.column?.editComponentProps ?? {}; let compProps = props.column?.editComponentProps ?? {}
const { record, column, index } = props; const { record, column, index } = props
if (isFunction(compProps)) { if (isFunction(compProps)) {
compProps = compProps({ text: val, record, column, index }) ?? {}; compProps = compProps({ text: val, record, column, index }) ?? {}
} }
const component = unref(getComponent); const component = unref(getComponent)
const apiSelectProps: Recordable = {}; const apiSelectProps: Recordable = {}
if (component === 'ApiSelect') { if (component === 'ApiSelect') {
apiSelectProps.cache = true; apiSelectProps.cache = true
} }
upEditDynamicDisabled(record, column, value); upEditDynamicDisabled(record, column, value)
return { return {
size: 'small', size: 'small',
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body, getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
@ -91,241 +91,241 @@
...compProps, ...compProps,
[valueField]: value, [valueField]: value,
disabled: unref(getDisable), disabled: unref(getDisable),
} as any; } as any
}); })
function upEditDynamicDisabled(record, column, value) { function upEditDynamicDisabled(record, column, value) {
if (!record) return false; if (!record) return false
const { key, dataIndex } = column; const { key, dataIndex } = column
if (!key && !dataIndex) return; if (!key && !dataIndex) return
const dataKey = (dataIndex || key) as string; const dataKey = (dataIndex || key) as string
set(record, dataKey, value); set(record, dataKey, value)
} }
const getDisable = computed(() => { const getDisable = computed(() => {
const { editDynamicDisabled } = props.column; const { editDynamicDisabled } = props.column
let disabled = false; let disabled = false
if (isBoolean(editDynamicDisabled)) { if (isBoolean(editDynamicDisabled)) {
disabled = editDynamicDisabled; disabled = editDynamicDisabled
} }
if (isFunction(editDynamicDisabled)) { if (isFunction(editDynamicDisabled)) {
const { record } = props; const { record } = props
disabled = editDynamicDisabled({ record }); disabled = editDynamicDisabled({ record })
} }
return disabled; return disabled
}); })
const getValues = computed(() => { const getValues = computed(() => {
const { 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') && !component.includes('Radio')) { if (!component.includes('Select') && !component.includes('Radio')) {
return value; return value
} }
const options: LabelValueOptions = const options: LabelValueOptions =
unref(getComponentProps)?.options ?? (unref(optionsRef) || []); unref(getComponentProps)?.options ?? (unref(optionsRef) || [])
const option = options.find((item) => `${item.value}` === `${value}`); const option = options.find((item) => `${item.value}` === `${value}`)
return option?.label ?? value; return option?.label ?? value
}); })
const getWrapperStyle = computed((): CSSProperties => { const getWrapperStyle = computed((): CSSProperties => {
if (unref(getIsCheckComp) || unref(getRowEditable)) { if (unref(getIsCheckComp) || unref(getRowEditable)) {
return {}; return {}
} }
return { return {
width: 'calc(100% - 48px)', width: 'calc(100% - 48px)',
}; }
}); })
const getWrapperClass = computed(() => { const getWrapperClass = computed(() => {
const { align = 'center' } = props.column; const { align = 'center' } = props.column
return `edit-cell-align-${align}`; return `edit-cell-align-${align}`
}); })
const getRowEditable = computed(() => { const getRowEditable = computed(() => {
const { editable } = props.record || {}; const { editable } = props.record || {}
return !!editable; return !!editable
}); })
watchEffect(() => { watchEffect(() => {
defaultValueRef.value = props.value; defaultValueRef.value = props.value
currentValueRef.value = props.value; currentValueRef.value = props.value
}); })
watchEffect(() => { watchEffect(() => {
const { editable } = props.column; const { editable } = props.column
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) { if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
isEdit.value = !!editable || unref(getRowEditable); isEdit.value = !!editable || unref(getRowEditable)
} }
}); })
function handleEdit() { function handleEdit() {
if (unref(getRowEditable) || unref(props.column?.editRow)) return; if (unref(getRowEditable) || unref(props.column?.editRow)) return
ruleMessage.value = ''; ruleMessage.value = ''
isEdit.value = true; isEdit.value = true
nextTick(() => { nextTick(() => {
const el = unref(elRef); const el = unref(elRef)
el?.focus?.(); el?.focus?.()
}); })
} }
async function handleChange(e: any) { async function handleChange(e: any) {
const component = unref(getComponent); const component = unref(getComponent)
if (!e) { if (!e) {
currentValueRef.value = e; currentValueRef.value = e
} else if (component === 'Checkbox') { } else if (component === 'Checkbox') {
currentValueRef.value = (e as ChangeEvent).target.checked; currentValueRef.value = (e as ChangeEvent).target.checked
} else if (component === 'Switch') { } else if (component === 'Switch') {
currentValueRef.value = e; currentValueRef.value = e
} else if (e?.target && Reflect.has(e.target, 'value')) { } else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value; currentValueRef.value = (e as ChangeEvent).target.value
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) { } else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
currentValueRef.value = e; currentValueRef.value = e
} }
const onChange = unref(getComponentProps)?.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: dataKey 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: dataKey, 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 } = unref(getComponentProps); 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
} }
} }

View File

@ -1,17 +1,17 @@
import { ComponentType } from '../../types/componentType'; import { ComponentType } from '../../types/componentType'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
const { t } = useI18n(); const { t } = useI18n()
/** /**
* @description: placeholder * @description: placeholder
*/ */
export function createPlaceholderMessage(component: ComponentType) { export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input') || component.includes('AutoComplete')) { if (component.includes('Input') || 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

@ -99,7 +99,7 @@
</Tooltip> </Tooltip>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { BasicColumn, ColumnChangeParam } from '../../types/table'; import type { BasicColumn, ColumnChangeParam } from '../../types/table'
import { import {
defineComponent, defineComponent,
ref, ref,
@ -109,33 +109,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 type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface'; import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface'
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'; import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useTableContext } from '../../hooks/useTableContext'; import { useTableContext } from '../../hooks/useTableContext'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
// import { useSortable } from '/@/hooks/web/useSortable'; // import { useSortable } from '/@/hooks/web/useSortable';
import { isFunction, isNullAndUnDef } from '/@/utils/is'; import { isFunction, isNullAndUnDef } from '/@/utils/is'
import { getPopupContainer as getParentContainer } from '/@/utils'; import { getPopupContainer as getParentContainer } from '/@/utils'
import { cloneDeep, omit } from 'lodash-es'; import { cloneDeep, omit } from 'lodash-es'
import Sortablejs from 'sortablejs'; import Sortablejs from 'sortablejs'
import type Sortable from 'sortablejs'; import type Sortable from 'sortablejs'
interface State { interface State {
checkAll: boolean; checkAll: boolean
isInit?: boolean; isInit?: boolean
checkedList: string[]; checkedList: string[]
defaultCheckList: string[]; defaultCheckList: string[]
} }
interface Options { interface Options {
label: string; label: string
value: string; value: string
fixed?: boolean | 'left' | 'right'; fixed?: boolean | 'left' | 'right'
} }
export default defineComponent({ export default defineComponent({
@ -154,145 +154,145 @@
emits: ['columns-change'], emits: ['columns-change'],
setup(_, { emit, attrs }) { setup(_, { emit, attrs }) {
const { t } = useI18n(); const { t } = useI18n()
const table = useTableContext(); const table = useTableContext()
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys'); const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys')
let inited = false; let inited = false
const cachePlainOptions = ref<Options[]>([]); const cachePlainOptions = ref<Options[]>([])
const plainOptions = ref<Options[] | any>([]); const plainOptions = ref<Options[] | 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(() => {
setTimeout(() => { setTimeout(() => {
const columns = table.getColumns(); const columns = table.getColumns()
if (columns.length && !state.isInit) { if (columns.length && !state.isInit) {
init(); init()
} }
}, 0); }, 0)
}); })
watchEffect(() => { watchEffect(() => {
const values = unref(getValues); const values = unref(getValues)
checkIndex.value = !!values.showIndexColumn; checkIndex.value = !!values.showIndexColumn
checkSelect.value = !!values.rowSelection; checkSelect.value = !!values.rowSelection
}); })
function getColumns() { function getColumns() {
const ret: Options[] = []; const ret: Options[] = []
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({ ret.push({
label: (item.title as string) || (item.customTitle as string), label: (item.title as string) || (item.customTitle as string),
value: (item.dataIndex || item.title) as string, value: (item.dataIndex || item.title) as string,
...item, ...item,
}); })
}); })
return ret; return ret
} }
function init() { function init() {
const columns = getColumns(); const columns = getColumns()
const checkList = table const checkList = table
.getColumns({ ignoreAction: true, ignoreIndex: true }) .getColumns({ ignoreAction: true, 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.isInit = true; state.isInit = true
state.checkedList = checkList; state.checkedList = checkList
} }
// checkAll change // checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) { 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 checkedLen = state.checkedList.length; let checkedLen = state.checkedList.length
// unref(checkIndex) && checkedLen--; // unref(checkIndex) && checkedLen--;
return checkedLen > 0 && checkedLen < 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 = plainSortOptions.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 sortable: Sortable
let sortableOrder: string[] = []; 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); sortable.sort(sortableOrder)
} }
// Open the pop-up window for drag and drop initialization // Open the pop-up window for drag and drop initialization
function handleVisibleChange() { function handleVisibleChange() {
if (inited) return; if (inited) return
nextTick(() => { nextTick(() => {
const columnListEl = unref(columnListRef); const columnListEl = unref(columnListRef)
if (!columnListEl) return; if (!columnListEl) return
const el = columnListEl.$el as any; const el = columnListEl.$el as any
if (!el) return; if (!el) return
// Drag and drop sort // Drag and drop sort
sortable = Sortablejs.create(unref(el), { sortable = Sortablejs.create(unref(el), {
animation: 500, animation: 500,
@ -300,86 +300,86 @@
delayOnTouchOnly: true, delayOnTouchOnly: true,
handle: '.table-column-drag-icon ', handle: '.table-column-drag-icon ',
onEnd: (evt) => { onEnd: (evt) => {
const { oldIndex, newIndex } = evt; const { oldIndex, newIndex } = evt
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return; return
} }
// Sort column // Sort column
const columns = cloneDeep(plainSortOptions.value); const columns = 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
setColumns( setColumns(
columns columns
.map((col: Options) => col.value) .map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)), .filter((value: string) => state.checkedList.includes(value)),
); )
}, },
}); })
// order // order
sortableOrder = sortable.toArray(); sortableOrder = sortable.toArray()
inited = true; inited = true
}); })
} }
// Control whether the serial number column is displayed // Control whether the serial number column is displayed
function handleIndexCheckChange(e: CheckboxChangeEvent) { function handleIndexCheckChange(e: 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: CheckboxChangeEvent) { 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 as string, { 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(plainSortOptions).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 {
@ -400,9 +400,9 @@
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';

View File

@ -23,13 +23,13 @@
</Tooltip> </Tooltip>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { SizeType } from '../../types/table'; import type { SizeType } from '../../types/table'
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue'
import { Tooltip, Dropdown, Menu } from 'ant-design-vue'; import { Tooltip, Dropdown, Menu } from 'ant-design-vue'
import { ColumnHeightOutlined } from '@ant-design/icons-vue'; import { ColumnHeightOutlined } from '@ant-design/icons-vue'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useTableContext } from '../../hooks/useTableContext'; import { useTableContext } from '../../hooks/useTableContext'
import { getPopupContainer } from '/@/utils'; import { getPopupContainer } from '/@/utils'
export default defineComponent({ export default defineComponent({
name: 'SizeSetting', name: 'SizeSetting',
@ -41,16 +41,16 @@
MenuItem: Menu.Item, MenuItem: Menu.Item,
}, },
setup() { setup() {
const table = useTableContext(); const table = useTableContext()
const { t } = useI18n(); const { t } = useI18n()
const selectedKeysRef = ref<SizeType[]>([table.getSize()]); const selectedKeysRef = ref<SizeType[]>([table.getSize()])
function handleTitleClick({ key }: { key: SizeType }) { function handleTitleClick({ key }: { key: SizeType }) {
selectedKeysRef.value = [key]; selectedKeysRef.value = [key]
table.setProps({ table.setProps({
size: key, size: key,
}); })
} }
return { return {
@ -58,7 +58,7 @@
selectedKeysRef, selectedKeysRef,
getPopupContainer, getPopupContainer,
t, t,
}; }
}, },
}); })
</script> </script>

View File

@ -1,40 +1,40 @@
import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table'; import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table'
import type { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination'
import type { ComputedRef } from 'vue'; import type { ComputedRef } from 'vue'
import { computed, Ref, ref, reactive, toRaw, unref, watch } from 'vue'; import { computed, Ref, ref, 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,154 +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, 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 reactive(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 | 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) && !isArray(firstColumn)) { if (!isString(firstColumn) && !isArray(firstColumn)) {
columnsRef.value = columns as BasicColumn[]; columnsRef.value = columns as BasicColumn[]
} else { } else {
const columnKeys = (columns as (string | string[])[]).map((m) => m.toString()); const columnKeys = (columns as (string | string[])[]).map((m) => m.toString())
const newColumns: BasicColumn[] = []; const newColumns: BasicColumn[] = []
cacheColumns.forEach((item) => { cacheColumns.forEach((item) => {
newColumns.push({ newColumns.push({
...item, ...item,
defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)), defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)),
}); })
}); })
// Sort according to another array // Sort according to another array
if (!isEqual(cacheKeys, columns)) { if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => { newColumns.sort((prev, next) => {
return ( return (
columnKeys.indexOf(prev.dataIndex?.toString() as string) - columnKeys.indexOf(prev.dataIndex?.toString() as string) -
columnKeys.indexOf(next.dataIndex?.toString() 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 {
@ -261,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) && text) { 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, merge } 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,189 +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)
return unref(dataSourceRef); return unref(dataSourceRef)
} }
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') {
@ -236,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) {
@ -249,27 +248,27 @@ export function useDataSource(
afterFetch, afterFetch,
useSearchForm, useSearchForm,
pagination, pagination,
} = unref(propsRef); } = unref(propsRef)
if (!api || !isFunction(api)) return; if (!api || !isFunction(api)) return
try { try {
setLoading(true); setLoading(true)
const { pageField, sizeField, listField, totalField } = Object.assign( const { pageField, sizeField, listField, totalField } = Object.assign(
{}, {},
FETCH_SETTING, FETCH_SETTING,
fetchSetting, fetchSetting,
); )
let pageParams: Recordable = {}; let pageParams: Recordable = {}
const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps; const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) { if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
pageParams = {}; pageParams = {}
} else { } else {
pageParams[pageField] = (opt && opt.page) || current; pageParams[pageField] = (opt && opt.page) || current
pageParams[sizeField] = pageSize; pageParams[sizeField] = pageSize
} }
const { sortInfo = {}, filterInfo } = searchState; const { sortInfo = {}, filterInfo } = searchState
let params: Recordable = merge( let params: Recordable = merge(
pageParams, pageParams,
@ -281,79 +280,79 @@ export function useDataSource(
filterInfo, filterInfo,
opt?.sortInfo ?? {}, opt?.sortInfo ?? {},
opt?.filterInfo ?? {}, opt?.filterInfo ?? {},
); )
if (beforeFetch && isFunction(beforeFetch)) { if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params; params = (await beforeFetch(params)) || params
} }
const res = await api(params); const res = await api(params)
rawDataSourceRef.value = res; rawDataSourceRef.value = res
const isArrayResult = Array.isArray(res); const isArrayResult = Array.isArray(res)
let resultItems: Recordable[] = isArrayResult ? res : get(res, listField); let resultItems: Recordable[] = isArrayResult ? res : get(res, listField)
const resultTotal: number = isArrayResult ? res.length : get(res, totalField); const resultTotal: number = isArrayResult ? 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,
@ -370,5 +369,5 @@ export function useDataSource(
insertTableDataRecord, insertTableDataRecord,
findTableDataRecord, findTableDataRecord,
handleTableChange, handleTableChange,
}; }
} }

View File

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

View File

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

View File

@ -1,167 +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[]) => { expandRows: (keys: string[]) => {
getTableInstance().expandRows(keys); getTableInstance().expandRows(keys)
}, },
collapseAll: () => { collapseAll: () => {
getTableInstance().collapseAll(); getTableInstance().collapseAll()
}, },
scrollTo: (pos: string) => { scrollTo: (pos: string) => {
getTableInstance().scrollTo(pos); getTableInstance().scrollTo(pos)
}, },
}; }
return [register, methods]; return [register, methods]
} }

View File

@ -1,65 +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[]) { function expandRows(keys: string[]) {
// use row ID expands the specified table row // use row ID expands the specified table row
const { isTreeTable } = unref(propsRef); const { isTreeTable } = unref(propsRef)
if (!isTreeTable) return; if (!isTreeTable) return
expandedRowKeys.value = [...expandedRowKeys.value, ...keys]; expandedRowKeys.value = [...expandedRowKeys.value, ...keys]
} }
function getAllKeys(data?: Recordable[]) { function getAllKeys(data?: Recordable[]) {
const keys: string[] = []; const keys: string[] = []
const { childrenColumnName } = unref(propsRef); const { childrenColumnName } = unref(propsRef)
toRaw(data || unref(tableData)).forEach((item) => { toRaw(data || unref(tableData)).forEach((item) => {
keys.push(item[unref(getRowKey) as string]); keys.push(item[unref(getRowKey) as string])
const children = item[childrenColumnName || 'children']; const children = item[childrenColumnName || 'children']
if (children?.length) { if (children?.length) {
keys.push(...getAllKeys(children)); keys.push(...getAllKeys(children))
} }
}); })
return keys; return keys
} }
function collapseAll() { function collapseAll() {
expandedRowKeys.value = []; expandedRowKeys.value = []
} }
return { getExpandOption, expandAll, expandRows, collapseAll }; return { getExpandOption, expandAll, expandRows, collapseAll }
} }

View File

@ -1,56 +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: string | number | null; 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 bodyDom = tableEl.$el.querySelector('.ant-table-content'); const bodyDom = tableEl.$el.querySelector('.ant-table-content')
useEventListener({ useEventListener({
el: bodyDom, el: bodyDom,
name: 'scroll', name: 'scroll',
listener: () => { listener: () => {
const footerBodyDom = tableEl.$el.querySelector( const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-content', '.ant-table-footer .ant-table-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,12 +1,12 @@
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table'; import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table'
import { Ref, ComputedRef, ref } from 'vue'; import { Ref, ComputedRef, ref } from 'vue'
import { computed, unref, 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>,
@ -17,147 +17,145 @@ export function useTableScroll(
wrapRef: Ref<HTMLElement | null>, wrapRef: Ref<HTMLElement | null>,
formRef: Ref<ComponentRef>, formRef: Ref<ComponentRef>,
) { ) {
const tableHeightRef: Ref<Nullable<number | string>> = ref(167); 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(height: number) { function setHeight(height: number) {
tableHeightRef.value = height; 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, isCanResizeParent, useSearchForm } = const { resizeHeightOffset, pagination, maxHeight, isCanResizeParent, useSearchForm } =
unref(propsRef); unref(propsRef)
const tableData = unref(getDataSourceRef); const tableData = unref(getDataSourceRef)
const table = unref(tableElRef); const table = unref(tableElRef)
if (!table) return; if (!table) return
const tableEl: Element = table.$el; const tableEl: Element = table.$el
if (!tableEl) return; if (!tableEl) return
if (!bodyEl) { if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body'); bodyEl = tableEl.querySelector('.ant-table-body')
if (!bodyEl) return; if (!bodyEl) return
} }
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight; const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth; const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth
if (hasScrollBarY) { if (hasScrollBarY) {
tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.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) || !unref(tableData) || 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 height-custom offset // Table height from bottom height-custom offset
let paddingHeight = 32; let 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; let bottomIncludeBody = 0
if (unref(wrapRef) && isCanResizeParent) { if (unref(wrapRef) && isCanResizeParent) {
const tablePadding = 12; const tablePadding = 12
const formMargin = 16; const formMargin = 16
let paginationMargin = 10; let paginationMargin = 10
const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0; const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0
let formHeight = unref(formRef)?.$el.offsetHeight ?? 0; let formHeight = unref(formRef)?.$el.offsetHeight ?? 0
if (formHeight) { if (formHeight) {
formHeight += formMargin; formHeight += formMargin
} }
if (isBoolean(pagination) && !pagination) { if (isBoolean(pagination) && !pagination) {
paginationMargin = 0; paginationMargin = 0
} }
if (isBoolean(useSearchForm) && !useSearchForm) { if (isBoolean(useSearchForm) && !useSearchForm) {
paddingHeight = 0; paddingHeight = 0
} }
const headerCellHeight = const headerCellHeight =
(tableEl.querySelector('.ant-table-title') as HTMLElement)?.offsetHeight ?? 0; (tableEl.querySelector('.ant-table-title') as HTMLElement)?.offsetHeight ?? 0
console.log(wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin); console.log(wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin)
bottomIncludeBody = bottomIncludeBody =
wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin; wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin
} else { } else {
// Table height from bottom // Table height from bottom
bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody; bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody
} }
let height = let height =
@ -166,55 +164,55 @@ export function useTableScroll(
paddingHeight - paddingHeight -
paginationHeight - paginationHeight -
footerHeight - footerHeight -
headerHeight; headerHeight
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height; height = (height > maxHeight! ? (maxHeight as number) : height) ?? height
setHeight(height); setHeight(height)
bodyEl!.style.height = `${height}px`; bodyEl!.style.height = `${height}px`
} }
useWindowSizeFn(calcTableHeight, 280); useWindowSizeFn(calcTableHeight, 280)
onMountedOrActivated(() => { onMountedOrActivated(() => {
calcTableHeight(); calcTableHeight()
nextTick(() => { nextTick(() => {
debounceRedoHeight(); debounceRedoHeight()
}); })
}); })
const getScrollX = computed(() => { const getScrollX = computed(() => {
let width = 0; let width = 0
if (unref(rowSelectionRef)) { if (unref(rowSelectionRef)) {
width += 60; width += 60
} }
// TODO props ?? 0; // TODO props ?? 0;
const NORMAL_WIDTH = 150; const NORMAL_WIDTH = 150
const columns = unref(columnsRef).filter((item) => !item.defaultHidden); const columns = unref(columnsRef).filter((item) => !item.defaultHidden)
columns.forEach((item) => { columns.forEach((item) => {
width += Number.parseFloat(item.width as string) || 0; width += Number.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,11 +8,11 @@ import type {
TableCustomRecord, TableCustomRecord,
TableRowSelection, TableRowSelection,
SizeType, SizeType,
} from './types/table'; } from './types/table'
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '/@/components/Form'
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'; import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
export const basicProps = { export const basicProps = {
clickToRowSelect: { type: Boolean, default: true }, clickToRowSelect: { type: Boolean, default: true },
@ -60,7 +60,7 @@ export const basicProps = {
fetchSetting: { fetchSetting: {
type: Object as PropType<FetchSetting>, type: Object as PropType<FetchSetting>,
default: () => { default: () => {
return FETCH_SETTING; return FETCH_SETTING
}, },
}, },
// 立即请求接口 // 立即请求接口
@ -137,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>
>, >,
}, },
@ -148,4 +148,4 @@ export const basicProps = {
type: String as PropType<SizeType>, type: String as PropType<SizeType>,
default: DEFAULT_SIZE, default: DEFAULT_SIZE,
}, },
}; }

View File

@ -11,4 +11,4 @@ export type ComponentType =
| 'TimePicker' | 'TimePicker'
| 'RadioGroup' | 'RadioGroup'
| 'RadioButtonGroup' | 'RadioButtonGroup'
| 'ApiRadioGroup'; | 'ApiRadioGroup'

View File

@ -1,10 +1,10 @@
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 = type PaginationPositon =
@ -13,10 +13,10 @@ type PaginationPositon =
| 'topRight' | 'topRight'
| 'bottomLeft' | 'bottomLeft'
| 'bottomCenter' | 'bottomCenter'
| 'bottomRight'; | 'bottomRight'
export declare class PaginationConfig extends Pagination { export declare class PaginationConfig extends Pagination {
position?: PaginationPositon[]; position?: PaginationPositon[]
} }
export interface PaginationProps { export interface PaginationProps {
@ -25,91 +25,91 @@ export interface PaginationProps {
* @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 * specify the position of Pagination
* @default ['bottomRight'] * @default ['bottomRight']
* @type string[] * @type string[]
*/ */
position?: PaginationPositon[]; position?: PaginationPositon[]
} }

View File

@ -1,17 +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 { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'; import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'
import type { ColumnProps } from 'ant-design-vue/lib/table'; import type { ColumnProps } from 'ant-design-vue/lib/table'
import { ComponentType } from './componentType'; import { ComponentType } from './componentType'
import { VueNode } from '/@/utils/propTypes'; import { VueNode } from '/@/utils/propTypes'
import { RoleEnum } from '/@/enums/roleEnum'; import { RoleEnum } from '/@/enums/roleEnum'
export declare type SortOrder = 'ascend' | 'descend'; export declare type SortOrder = 'ascend' | 'descend'
export interface TableCurrentDataSource<T = Recordable> { export interface TableCurrentDataSource<T = Recordable> {
currentDataSource: T[]; currentDataSource: T[]
} }
export interface TableRowSelection<T = any> extends ITableRowSelection { export interface TableRowSelection<T = any> extends ITableRowSelection {
@ -19,289 +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
expandRows: (keys: string[] | number[]) => void; expandRows: (keys: string[] | number[]) => void
collapseAll: () => void; collapseAll: () => void
scrollTo: (pos: string) => void; // pos: id | "top" | "bottom" scrollTo: (pos: string) => void // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[]; getSelectRowKeys: () => string[]
deleteSelectRowByKey: (key: string) => void; deleteSelectRowByKey: (key: string) => void
setPagination: (info: Partial<PaginationProps>) => void; setPagination: (info: Partial<PaginationProps>) => void
setTableData: <T = Recordable>(values: T[]) => void; setTableData: <T = Recordable>(values: T[]) => void
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void; updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void; deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void; insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void
findTableDataRecord: (rowKey: string | number) => Recordable | void; findTableDataRecord: (rowKey: string | number) => Recordable | void
getColumns: (opt?: GetColumnsParams) => BasicColumn[]; getColumns: (opt?: GetColumnsParams) => BasicColumn[]
setColumns: (columns: BasicColumn[] | string[]) => void; setColumns: (columns: BasicColumn[] | string[]) => void
getDataSource: <T = Recordable>() => T[]; getDataSource: <T = Recordable>() => T[]
getRawDataSource: <T = Recordable>() => T; getRawDataSource: <T = Recordable>() => T
setLoading: (loading: boolean) => void; setLoading: (loading: boolean) => void
setProps: (props: Partial<BasicTableProps>) => void; setProps: (props: Partial<BasicTableProps>) => void
redoHeight: () => void; redoHeight: () => void
setSelectedRowKeys: (rowKeys: string[] | number[]) => void; setSelectedRowKeys: (rowKeys: string[] | number[]) => void
getPaginationRef: () => PaginationProps | boolean; getPaginationRef: () => PaginationProps | boolean
getSize: () => SizeType; getSize: () => SizeType
getRowSelection: () => TableRowSelection<Recordable>; getRowSelection: () => TableRowSelection<Recordable>
getCacheColumns: () => BasicColumn[]; getCacheColumns: () => BasicColumn[]
emit?: EmitType; emit?: EmitType
updateTableData: (index: number, key: string, value: any) => Recordable; updateTableData: (index: number, key: string, value: any) => Recordable
setShowPagination: (show: boolean) => Promise<void>; setShowPagination: (show: boolean) => Promise<void>
getShowPagination: () => boolean; getShowPagination: () => boolean
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void; 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高度 // 是否继承父级高度(父级高度-表单高度-padding高度
isCanResizeParent?: boolean; isCanResizeParent?: boolean
// 是否可以自适应高度 // 是否可以自适应高度
canResize?: boolean; canResize?: boolean
// 自适应高度偏移, 计算结果-偏移量 // 自适应高度偏移, 计算结果-偏移量
resizeHeightOffset?: number; resizeHeightOffset?: number
// 在分页改变的时候清空选项 // 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean; clearSelectOnPageChange?: boolean
// //
rowKey?: string | ((record: Recordable) => string); rowKey?: string | ((record: Recordable) => string)
// 数据 // 数据
dataSource?: Recordable[]; dataSource?: Recordable[]
// 标题右侧提示 // 标题右侧提示
titleHelpMessage?: string | string[]; titleHelpMessage?: string | string[]
// 表格滚动最大高度 // 表格滚动最大高度
maxHeight?: number; maxHeight?: number
// 是否显示边框 // 是否显示边框
bordered?: boolean; bordered?: boolean
// 分页配置 // 分页配置
pagination?: PaginationProps | boolean; pagination?: PaginationProps | boolean
// loading加载 // loading加载
loading?: boolean; loading?: boolean
/** /**
* The column contains children to display * The column contains children to display
* @default 'children' * @default 'children'
* @type string | string[] * @type string | string[]
*/ */
childrenColumnName?: string; childrenColumnName?: string
/** /**
* Override default table elements * Override default table elements
* @type object * @type object
*/ */
components?: object; components?: object
/** /**
* Expand all rows initially * Expand all rows initially
* @default false * @default false
* @type boolean * @type boolean
*/ */
defaultExpandAllRows?: boolean; defaultExpandAllRows?: boolean
/** /**
* Initial expanded row keys * Initial expanded row keys
* @type string[] * @type string[]
*/ */
defaultExpandedRowKeys?: string[]; defaultExpandedRowKeys?: string[]
/** /**
* Current expanded row keys * Current expanded row keys
* @type string[] * @type string[]
*/ */
expandedRowKeys?: string[]; expandedRowKeys?: string[]
/** /**
* Expanded container render for each row * Expanded container render for each row
* @type Function * @type Function
*/ */
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element; expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element
/** /**
* Customize row expand Icon. * Customize row expand Icon.
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
expandIcon?: Function | VNodeChild | JSX.Element; expandIcon?: Function | VNodeChild | JSX.Element
/** /**
* Whether to expand row by clicking anywhere in the whole row * Whether to expand row by clicking anywhere in the whole row
* @default false * @default false
* @type boolean * @type boolean
*/ */
expandRowByClick?: boolean; expandRowByClick?: boolean
/** /**
* The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0 * The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
*/ */
expandIconColumnIndex?: number; expandIconColumnIndex?: number
/** /**
* Table footer renderer * Table footer renderer
* @type Function | VNodeChild * @type Function | VNodeChild
*/ */
footer?: Function | VNodeChild | JSX.Element; footer?: Function | VNodeChild | JSX.Element
/** /**
* Indent size in pixels of tree data * Indent size in pixels of tree data
* @default 15 * @default 15
* @type number * @type number
*/ */
indentSize?: number; indentSize?: number
/** /**
* i18n text including filter, sort, empty text, etc * i18n text including filter, sort, empty text, etc
* @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' } * @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
* @type object * @type object
*/ */
locale?: object; locale?: object
/** /**
* Row's className * Row's className
* @type Function * @type Function
*/ */
rowClassName?: (record: TableCustomRecord<T>, index: number) => string; rowClassName?: (record: TableCustomRecord<T>, index: number) => string
/** /**
* Row selection config * Row selection config
* @type object * @type object
*/ */
rowSelection?: TableRowSelection; rowSelection?: TableRowSelection
/** /**
* Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area. * Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area.
@ -309,39 +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
@ -350,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.
@ -366,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
@ -374,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
@ -387,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
@ -395,84 +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<Recordable> { 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?: editComponentProps?:
| ((opt: { | ((opt: {
text: string | number | boolean | Recordable; text: string | number | boolean | Recordable
record: Recordable; record: Recordable
column: BasicColumn; column: BasicColumn
index: number; index: number
}) => Recordable) }) => Recordable)
| Recordable; | Recordable
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>); editRule?: boolean | ((text: string, record: Recordable) => Promise<string>)
editValueMap?: (value: any) => string; editValueMap?: (value: any) => string
onEditRow?: () => void; onEditRow?: () => void
// 权限编码控制是否显示 // 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[]; auth?: RoleEnum | RoleEnum[] | string | string[]
// 业务控制是否显示 // 业务控制是否显示
ifShow?: boolean | ((column: BasicColumn) => boolean); ifShow?: boolean | ((column: BasicColumn) => boolean)
// 自定义修改后显示的内容 // 自定义修改后显示的内容
editRender?: (opt: { editRender?: (opt: {
text: string | number | boolean | Recordable; text: string | number | boolean | Recordable
record: Recordable; record: Recordable
column: BasicColumn; column: BasicColumn
index: number; index: number
}) => VNodeChild | JSX.Element; }) => VNodeChild | JSX.Element
// 动态 Disabled // 动态 Disabled
editDynamicDisabled?: boolean | ((record: Recordable) => boolean); 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,28 +1,28 @@
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?: placement?:
| 'top' | 'top'
| 'left' | 'left'
@ -35,5 +35,5 @@ export interface PopConfirm {
| 'rightTop' | 'rightTop'
| 'rightBottom' | 'rightBottom'
| 'bottomLeft' | 'bottomLeft'
| 'bottomRight'; | 'bottomRight'
} }

View File

@ -36,16 +36,16 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watch, unref, computed } from 'vue'; import { defineComponent, ref, watch, unref, computed } from 'vue'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { Tooltip, Space } from 'ant-design-vue'; import { Tooltip, Space } from 'ant-design-vue'
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal'
import { uploadContainerProps } from './props'; import { uploadContainerProps } from './props'
import { omit } from 'lodash-es'; import { omit } from 'lodash-es'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { isArray } from '/@/utils/is'; import { isArray } from '/@/utils/is'
import UploadModal from './UploadModal.vue'; import UploadModal from './UploadModal.vue'
import UploadPreviewModal from './UploadPreviewModal.vue'; import UploadPreviewModal from './UploadPreviewModal.vue'
export default defineComponent({ export default defineComponent({
name: 'BasicUpload', name: 'BasicUpload',
@ -54,54 +54,54 @@
emits: ['change', 'delete', 'preview-delete', 'update:value'], emits: ['change', 'delete', 'preview-delete', 'update:value'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const { t } = useI18n(); const { t } = useI18n()
// modal // modal
const [registerUploadModal, { openModal: openUploadModal }] = useModal(); const [registerUploadModal, { openModal: openUploadModal }] = useModal()
// modal // modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal(); const [registerPreviewModal, { openModal: openPreviewModal }] = useModal()
const fileList = ref<string[]>([]); const fileList = ref<string[]>([])
const showPreview = computed(() => { const showPreview = computed(() => {
const { emptyHidePreview } = props; const { emptyHidePreview } = props
if (!emptyHidePreview) return true; if (!emptyHidePreview) return true
return emptyHidePreview ? fileList.value.length > 0 : true; return emptyHidePreview ? fileList.value.length > 0 : true
}); })
const bindValue = computed(() => { const bindValue = computed(() => {
const value = { ...attrs, ...props }; const value = { ...attrs, ...props }
return omit(value, 'onChange'); return omit(value, 'onChange')
}); })
watch( watch(
() => props.value, () => props.value,
(value = []) => { (value = []) => {
fileList.value = isArray(value) ? value : []; fileList.value = isArray(value) ? value : []
}, },
{ immediate: true }, { immediate: true },
); )
// modal // modal
function handleChange(urls: string[]) { function handleChange(urls: string[]) {
fileList.value = [...unref(fileList), ...(urls || [])]; fileList.value = [...unref(fileList), ...(urls || [])]
emit('update:value', fileList.value); emit('update:value', fileList.value)
emit('change', fileList.value); emit('change', fileList.value)
} }
// modal // modal
function handlePreviewChange(urls: string[]) { function handlePreviewChange(urls: string[]) {
fileList.value = [...(urls || [])]; fileList.value = [...(urls || [])]
emit('update:value', fileList.value); emit('update:value', fileList.value)
emit('change', fileList.value); emit('change', fileList.value)
} }
function handleDelete(record: Recordable) { function handleDelete(record: Recordable) {
emit('delete', record); emit('delete', record)
} }
function handlePreviewDelete(url: string) { function handlePreviewDelete(url: string) {
emit('preview-delete', url); emit('preview-delete', url)
} }
return { return {
@ -117,7 +117,7 @@
handleDelete, handleDelete,
handlePreviewDelete, handlePreviewDelete,
t, t,
}; }
}, },
}); })
</script> </script>

View File

@ -43,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 { 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 },
@ -75,60 +75,58 @@
setup(props, { emit }) { setup(props, { emit }) {
const state = reactive<{ fileList: FileItem[] }>({ const state = reactive<{ fileList: FileItem[] }>({
fileList: [], fileList: [],
}); })
// //
const isUploadingRef = ref(false); const isUploadingRef = ref(false)
const fileListRef = ref<FileItem[]>([]); const fileListRef = ref<FileItem[]>([])
const { accept, helpText, maxNumber, maxSize } = toRefs(props); const { accept, helpText, maxNumber, maxSize } = toRefs(props)
const { t } = useI18n(); const { t } = useI18n()
const [register, { closeModal }] = useModalInner(); const [register, { closeModal }] = useModalInner()
const { getStringAccept, getHelpText } = useUploadType({ const { 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
// //
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
} }
const commonItem = { const commonItem = {
@ -138,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
@ -150,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)
} }
// //
@ -174,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: {
@ -190,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
} }
} }
@ -293,9 +291,9 @@
getIsSelectFile, getIsSelectFile,
getUploadBtnText, getUploadBtnText,
t, t,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
.upload-modal { .upload-modal {

View File

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

View File

@ -1,7 +1,7 @@
import type { App } from 'vue'; import type { App } from 'vue'
import { Button } from './Button'; import { Button } from './Button'
import { Input, Layout } from 'ant-design-vue'; import { Input, Layout } from 'ant-design-vue'
export function registerGlobComp(app: App) { export function registerGlobComp(app: App) {
app.use(Input).use(Button).use(Layout); app.use(Input).use(Button).use(Layout)
} }

View File

@ -27,5 +27,3 @@
width: 100% !important; width: 100% !important;
max-width: 100%; max-width: 100%;
} }

View File

@ -1,16 +1,16 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, unref } from 'vue'; import { defineComponent, computed, unref } from 'vue'
import { BackTop } from 'ant-design-vue'; import { BackTop } from 'ant-design-vue'
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting'
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useUserStoreWithOut } from '/@/store/modules/user'; import { useUserStoreWithOut } from '/@/store/modules/user'
import { SettingButtonPositionEnum } from '/@/enums/appEnum'; import { SettingButtonPositionEnum } from '/@/enums/appEnum'
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'
import SessionTimeoutLogin from '/@/views/sys/login/SessionTimeoutLogin.vue'; import SessionTimeoutLogin from '/@/views/sys/login/SessionTimeoutLogin.vue'
export default defineComponent({ export default defineComponent({
name: 'LayoutFeatures', name: 'LayoutFeatures',
components: { components: {
@ -21,24 +21,24 @@
}, },
setup() { setup() {
const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } = const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } =
useRootSetting(); useRootSetting()
const userStore = useUserStoreWithOut(); const userStore = useUserStoreWithOut()
const { prefixCls } = useDesign('setting-drawer-feature'); const { prefixCls } = useDesign('setting-drawer-feature')
const { getShowHeader } = useHeaderSetting(); const { getShowHeader } = useHeaderSetting()
const getIsSessionTimeout = computed(() => userStore.getSessionTimeout); const getIsSessionTimeout = computed(() => userStore.getSessionTimeout)
const getIsFixedSettingDrawer = computed(() => { const getIsFixedSettingDrawer = computed(() => {
if (!unref(getShowSettingButton)) { if (!unref(getShowSettingButton)) {
return false; return false
} }
const settingButtonPosition = unref(getSettingButtonPosition); const settingButtonPosition = unref(getSettingButtonPosition)
if (settingButtonPosition === SettingButtonPositionEnum.AUTO) { if (settingButtonPosition === SettingButtonPositionEnum.AUTO) {
return !unref(getShowHeader) || unref(getFullContent); return !unref(getShowHeader) || unref(getFullContent)
} }
return settingButtonPosition === SettingButtonPositionEnum.FIXED; return settingButtonPosition === SettingButtonPositionEnum.FIXED
}); })
return { return {
getTarget: () => document.body, getTarget: () => document.body,
@ -46,9 +46,9 @@
getIsFixedSettingDrawer, getIsFixedSettingDrawer,
prefixCls, prefixCls,
getIsSessionTimeout, getIsSessionTimeout,
}; }
}, },
}); })
</script> </script>
<template> <template>

View File

@ -14,27 +14,27 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { RouteLocationMatched } from 'vue-router'; import type { RouteLocationMatched } from 'vue-router'
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
import type { Menu } from '/@/router/types'; import type { Menu } from '/@/router/types'
import { defineComponent, ref, watchEffect } from 'vue'; import { defineComponent, ref, watchEffect } from 'vue'
import { Breadcrumb } from 'ant-design-vue'; import { Breadcrumb } from 'ant-design-vue'
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting'
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { isString } from '/@/utils/is'; import { isString } from '/@/utils/is'
import { filter } from '/@/utils/helper/treeHelper'; import { filter } from '/@/utils/helper/treeHelper'
import { getMenus } from '/@/router/menus'; import { getMenus } from '/@/router/menus'
import { REDIRECT_NAME } from '/@/router/constant'; import { REDIRECT_NAME } from '/@/router/constant'
import { getAllParentPath } from '/@/router/helper/menuHelper'; import { getAllParentPath } from '/@/router/helper/menuHelper'
export default defineComponent({ export default defineComponent({
name: 'LayoutBreadcrumb', name: 'LayoutBreadcrumb',
@ -43,111 +43,111 @@
theme: propTypes.oneOf(['dark', 'light']), theme: propTypes.oneOf(['dark', 'light']),
}, },
setup() { setup() {
const routes = ref<RouteLocationMatched[]>([]); const routes = ref<RouteLocationMatched[]>([])
const { currentRoute } = useRouter(); const { currentRoute } = useRouter()
const { prefixCls } = useDesign('layout-breadcrumb'); const { prefixCls } = useDesign('layout-breadcrumb')
const { getShowBreadCrumbIcon } = useRootSetting(); const { getShowBreadCrumbIcon } = useRootSetting()
const go = useGo(); const go = useGo()
const { t } = useI18n(); const { t } = useI18n()
watchEffect(async () => { watchEffect(async () => {
if (currentRoute.value.name === REDIRECT_NAME) return; if (currentRoute.value.name === REDIRECT_NAME) return
const menus = await getMenus(); const menus = await getMenus()
const routeMatched = currentRoute.value.matched; const routeMatched = currentRoute.value.matched
const cur = routeMatched?.[routeMatched.length - 1]; const cur = routeMatched?.[routeMatched.length - 1]
let path = currentRoute.value.path; let path = currentRoute.value.path
if (cur && cur?.meta?.currentActiveMenu) { if (cur && cur?.meta?.currentActiveMenu) {
path = cur.meta.currentActiveMenu as string; path = cur.meta.currentActiveMenu as string
} }
const parent = getAllParentPath(menus, path); const parent = getAllParentPath(menus, path)
const filterMenus = menus.filter((item) => item.path === parent[0]); const filterMenus = menus.filter((item) => item.path === parent[0])
const matched = getMatched(filterMenus, parent) as any; const matched = getMatched(filterMenus, parent) as any
if (!matched || matched.length === 0) return; if (!matched || matched.length === 0) return
const breadcrumbList = filterItem(matched); const breadcrumbList = filterItem(matched)
if (currentRoute.value.meta?.currentActiveMenu) { if (currentRoute.value.meta?.currentActiveMenu) {
breadcrumbList.push({ breadcrumbList.push({
...currentRoute.value, ...currentRoute.value,
name: currentRoute.value.meta?.title || currentRoute.value.name, name: currentRoute.value.meta?.title || currentRoute.value.name,
} as unknown as RouteLocationMatched); } as unknown as RouteLocationMatched)
} }
routes.value = breadcrumbList; routes.value = breadcrumbList
}); })
function getMatched(menus: Menu[], parent: string[]) { function getMatched(menus: Menu[], parent: string[]) {
const metched: Menu[] = []; const metched: Menu[] = []
menus.forEach((item) => { menus.forEach((item) => {
if (parent.includes(item.path)) { if (parent.includes(item.path)) {
metched.push({ metched.push({
...item, ...item,
name: item.meta?.title || item.name, name: item.meta?.title || item.name,
}); })
} }
if (item.children?.length) { if (item.children?.length) {
metched.push(...getMatched(item.children, parent)); metched.push(...getMatched(item.children, parent))
} }
}); })
return metched; return metched
} }
function filterItem(list: RouteLocationMatched[]) { function filterItem(list: RouteLocationMatched[]) {
return filter(list, (item) => { return filter(list, (item) => {
const { meta, name } = item; const { meta, name } = item
if (!meta) { if (!meta) {
return !!name; return !!name
} }
const { title, hideBreadcrumb, hideMenu } = meta; const { title, hideBreadcrumb, hideMenu } = meta
if (!title || hideBreadcrumb || hideMenu) { if (!title || hideBreadcrumb || hideMenu) {
return false; return false
} }
return true; return true
}).filter((item) => !item.meta?.hideBreadcrumb); }).filter((item) => !item.meta?.hideBreadcrumb)
} }
function handleClick(route: RouteLocationMatched, paths: string[], e: Event) { function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
e?.preventDefault(); e?.preventDefault()
const { children, redirect, meta } = route; const { children, redirect, meta } = route
if (children?.length && !redirect) { if (children?.length && !redirect) {
e?.stopPropagation(); e?.stopPropagation()
return; return
} }
if (meta?.carryParam) { if (meta?.carryParam) {
return; return
} }
if (redirect && isString(redirect)) { if (redirect && isString(redirect)) {
go(redirect); go(redirect)
} else { } else {
let goPath = ''; let goPath = ''
if (paths.length === 1) { if (paths.length === 1) {
goPath = paths[0]; goPath = paths[0]
} else { } else {
const ps = paths.slice(1); const ps = paths.slice(1)
const lastPath = ps.pop() || ''; const lastPath = ps.pop() || ''
goPath = `${lastPath}`; goPath = `${lastPath}`
} }
goPath = /^\//.test(goPath) ? goPath : `/${goPath}`; goPath = /^\//.test(goPath) ? goPath : `/${goPath}`
go(goPath); go(goPath)
} }
} }
function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) { function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
return routes.indexOf(route) !== routes.length - 1; return routes.indexOf(route) !== routes.length - 1
} }
function getIcon(route) { function getIcon(route) {
return route.icon || route.meta?.icon; return route.icon || route.meta?.icon
} }
return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect }; return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-layout-breadcrumb'; @prefix-cls: ~'@{namespace}-layout-breadcrumb';

View File

@ -25,27 +25,27 @@
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { BasicModal, useModalInner } from '/@/components/Modal/index'; import { BasicModal, useModalInner } from '/@/components/Modal/index'
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index'
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user'
import { useLockStore } from '/@/store/modules/lock'; import { useLockStore } from '/@/store/modules/lock'
import headerImg from '/@/assets/images/header.jpg'; import headerImg from '/@/assets/images/header.jpg'
export default defineComponent({ export default defineComponent({
name: 'LockModal', name: 'LockModal',
components: { BasicModal, BasicForm }, components: { BasicModal, BasicForm },
setup() { setup() {
const { t } = useI18n(); const { t } = useI18n()
const { prefixCls } = useDesign('header-lock-modal'); const { prefixCls } = useDesign('header-lock-modal')
const userStore = useUserStore(); const userStore = useUserStore()
const lockStore = useLockStore(); const lockStore = useLockStore()
const getRealName = computed(() => userStore.getUserInfo?.realName); const getRealName = computed(() => userStore.getUserInfo?.realName)
const [register, { closeModal }] = useModalInner(); const [register, { closeModal }] = useModalInner()
const [registerForm, { validateFields, resetFields }] = useForm({ const [registerForm, { validateFields, resetFields }] = useForm({
showActionButtonGroup: false, showActionButtonGroup: false,
@ -60,24 +60,24 @@
required: true, required: true,
}, },
], ],
}); })
async function handleLock() { async function handleLock() {
const values = (await validateFields()) as any; const values = (await validateFields()) as any
const password: string | undefined = values.password; const password: string | undefined = values.password
closeModal(); closeModal()
lockStore.setLockInfo({ lockStore.setLockInfo({
isLock: true, isLock: true,
pwd: password, pwd: password,
}); })
await resetFields(); await resetFields()
} }
const avatar = computed(() => { const avatar = computed(() => {
const { avatar } = userStore.getUserInfo; const { avatar } = userStore.getUserInfo
return avatar || headerImg; return avatar || headerImg
}); })
return { return {
t, t,
@ -87,9 +87,9 @@
registerForm, registerForm,
handleLock, handleLock,
avatar, avatar,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-header-lock-modal'; @prefix-cls: ~'@{namespace}-header-lock-modal';

View File

@ -7,12 +7,12 @@
</MenuItem> </MenuItem>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Menu } from 'ant-design-vue'; import { Menu } from 'ant-design-vue'
import { computed, defineComponent, getCurrentInstance } from 'vue'; import { computed, defineComponent, getCurrentInstance } from 'vue'
import Icon from '/@/components/Icon/index'; import Icon from '/@/components/Icon/index'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
export default defineComponent({ export default defineComponent({
name: 'DropdownMenuItem', name: 'DropdownMenuItem',
@ -24,9 +24,9 @@
icon: propTypes.string, icon: propTypes.string,
}, },
setup(props) { setup(props) {
const instance = getCurrentInstance(); const instance = getCurrentInstance()
const itemKey = computed(() => props.key || instance?.vnode?.props?.key); const itemKey = computed(() => props.key || instance?.vnode?.props?.key)
return { itemKey }; return { itemKey }
}, },
}); })
</script> </script>

View File

@ -36,26 +36,26 @@
</template> </template>
<script lang="ts"> <script lang="ts">
// components // components
import { Dropdown, Menu } from 'ant-design-vue'; import { Dropdown, Menu } from 'ant-design-vue'
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'; import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue'
import { DOC_URL } from '/@/settings/siteSetting'; import { DOC_URL } from '/@/settings/siteSetting'
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user'
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal'
import headerImg from '/@/assets/images/header.jpg'; import headerImg from '/@/assets/images/header.jpg'
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes'
import { openWindow } from '/@/utils'; import { openWindow } from '/@/utils'
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'
type MenuEvent = 'logout' | 'doc' | 'lock'; type MenuEvent = 'logout' | 'doc' | 'lock'
export default defineComponent({ export default defineComponent({
name: 'UserDropdown', name: 'UserDropdown',
@ -70,43 +70,43 @@
theme: propTypes.oneOf(['dark', 'light']), theme: propTypes.oneOf(['dark', 'light']),
}, },
setup() { setup() {
const { prefixCls } = useDesign('header-user-dropdown'); const { prefixCls } = useDesign('header-user-dropdown')
const { t } = useI18n(); const { t } = useI18n()
const { getShowDoc, getUseLockPage } = useHeaderSetting(); const { getShowDoc, getUseLockPage } = useHeaderSetting()
const userStore = useUserStore(); const userStore = useUserStore()
const getUserInfo = computed(() => { const getUserInfo = computed(() => {
const { realName = '', avatar, desc } = userStore.getUserInfo || {}; const { realName = '', avatar, desc } = userStore.getUserInfo || {}
return { realName, avatar: avatar || headerImg, desc }; return { realName, avatar: avatar || headerImg, desc }
}); })
const [register, { openModal }] = useModal(); const [register, { openModal }] = useModal()
function handleLock() { function handleLock() {
openModal(true); openModal(true)
} }
// login out // login out
function handleLoginOut() { function handleLoginOut() {
userStore.confirmLoginOut(); userStore.confirmLoginOut()
} }
// open doc // open doc
function openDoc() { function openDoc() {
openWindow(DOC_URL); openWindow(DOC_URL)
} }
function handleMenuClick(e: MenuInfo) { function handleMenuClick(e: MenuInfo) {
switch (e.key as MenuEvent) { switch (e.key as MenuEvent) {
case 'logout': case 'logout':
handleLoginOut(); handleLoginOut()
break; break
case 'doc': case 'doc':
openDoc(); openDoc()
break; break
case 'lock': case 'lock':
handleLock(); handleLock()
break; break
} }
} }
@ -118,9 +118,9 @@
getShowDoc, getShowDoc,
register, register,
getUseLockPage, getUseLockPage,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-header-user-dropdown'; @prefix-cls: ~'@{namespace}-header-user-dropdown';

View File

@ -1,6 +1,6 @@
import { defineComponent, computed, unref } from 'vue'; import { defineComponent, computed, unref } from 'vue'
import { BasicDrawer } from '/@/components/Drawer/index'; import { BasicDrawer } from '/@/components/Drawer/index'
import { Divider } from 'ant-design-vue'; import { Divider } from 'ant-design-vue'
import { import {
TypePicker, TypePicker,
ThemeColorPicker, ThemeColorPicker,
@ -8,20 +8,20 @@ import {
SwitchItem, SwitchItem,
SelectItem, SelectItem,
InputNumberItem, InputNumberItem,
} from './components'; } from './components'
import { AppDarkModeToggle } from '/@/components/Application'; import { AppDarkModeToggle } from '/@/components/Application'
import { MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; import { MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting'
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { baseHandler } from './handler'; import { baseHandler } from './handler'
import { import {
HandlerEnum, HandlerEnum,
@ -31,15 +31,15 @@ import {
routerTransitionOptions, routerTransitionOptions,
menuTypeList, menuTypeList,
mixSidebarTriggerOptions, mixSidebarTriggerOptions,
} from './enum'; } from './enum'
import { import {
HEADER_PRESET_BG_COLOR_LIST, HEADER_PRESET_BG_COLOR_LIST,
SIDE_BAR_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST,
APP_PRESET_COLOR_LIST, APP_PRESET_COLOR_LIST,
} from '/@/settings/designSetting'; } from '/@/settings/designSetting'
const { t } = useI18n(); const { t } = useI18n()
export default defineComponent({ export default defineComponent({
name: 'SettingDrawer', name: 'SettingDrawer',
@ -56,10 +56,10 @@ export default defineComponent({
getLockTime, getLockTime,
getShowDarkModeToggle, getShowDarkModeToggle,
getThemeColor, getThemeColor,
} = useRootSetting(); } = useRootSetting()
const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } = const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } =
useTransitionSetting(); useTransitionSetting()
const { const {
getIsHorizontal, getIsHorizontal,
@ -80,20 +80,20 @@ export default defineComponent({
getCloseMixSidebarOnChange, getCloseMixSidebarOnChange,
getMixSideTrigger, getMixSideTrigger,
getMixSideFixed, getMixSideFixed,
} = useMenuSetting(); } = useMenuSetting()
const { const {
getShowHeader, getShowHeader,
getFixed: getHeaderFixed, getFixed: getHeaderFixed,
getHeaderBgColor, getHeaderBgColor,
getShowSearch, getShowSearch,
} = useHeaderSetting(); } = useHeaderSetting()
const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting(); const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting()
const getShowMenuRef = computed(() => { const getShowMenuRef = computed(() => {
return unref(getShowMenu) && !unref(getIsHorizontal); return unref(getShowMenu) && !unref(getIsHorizontal)
}); })
function renderSidebar() { function renderSidebar() {
return ( return (
@ -105,12 +105,12 @@ export default defineComponent({
mode: item.mode, mode: item.mode,
type: item.type, type: item.type,
split: unref(getIsHorizontal) ? false : undefined, split: unref(getIsHorizontal) ? false : undefined,
}); })
}} }}
def={unref(getMenuType)} def={unref(getMenuType)}
/> />
</> </>
); )
} }
function renderHeaderTheme() { function renderHeaderTheme() {
@ -120,7 +120,7 @@ export default defineComponent({
def={unref(getHeaderBgColor)} def={unref(getHeaderBgColor)}
event={HandlerEnum.HEADER_THEME} event={HandlerEnum.HEADER_THEME}
/> />
); )
} }
function renderSiderTheme() { function renderSiderTheme() {
@ -130,7 +130,7 @@ export default defineComponent({
def={unref(getMenuBgColor)} def={unref(getMenuBgColor)}
event={HandlerEnum.MENU_THEME} event={HandlerEnum.MENU_THEME}
/> />
); )
} }
function renderMainTheme() { function renderMainTheme() {
@ -140,19 +140,19 @@ export default defineComponent({
def={unref(getThemeColor)} def={unref(getThemeColor)}
event={HandlerEnum.CHANGE_THEME_COLOR} event={HandlerEnum.CHANGE_THEME_COLOR}
/> />
); )
} }
/** /**
* @description: * @description:
*/ */
function renderFeatures() { function renderFeatures() {
let triggerDef = unref(getTrigger); let triggerDef = unref(getTrigger)
const triggerOptions = getMenuTriggerOptions(unref(getSplit)); const triggerOptions = getMenuTriggerOptions(unref(getSplit))
const some = triggerOptions.some((item) => item.value === triggerDef); const some = triggerOptions.some((item) => item.value === triggerDef)
if (!some) { if (!some) {
triggerDef = TriggerEnum.FOOTER; triggerDef = TriggerEnum.FOOTER
} }
return ( return (
@ -261,7 +261,7 @@ export default defineComponent({
formatter={(value: string) => { formatter={(value: string) => {
return parseInt(value) === 0 return parseInt(value) === 0
? `0(${t('layout.setting.notAutoScreenLock')})` ? `0(${t('layout.setting.notAutoScreenLock')})`
: `${value}${t('layout.setting.minute')}`; : `${value}${t('layout.setting.minute')}`
}} }}
/> />
<InputNumberItem <InputNumberItem
@ -275,7 +275,7 @@ export default defineComponent({
formatter={(value: string) => `${parseInt(value)}px`} formatter={(value: string) => `${parseInt(value)}px`}
/> />
</> </>
); )
} }
function renderContent() { function renderContent() {
@ -362,7 +362,7 @@ export default defineComponent({
def={unref(getColorWeak)} def={unref(getColorWeak)}
/> />
</> </>
); )
} }
function renderTransition() { function renderTransition() {
@ -393,7 +393,7 @@ export default defineComponent({
disabled={!unref(getEnableTransition)} disabled={!unref(getEnableTransition)}
/> />
</> </>
); )
} }
return () => ( return () => (
@ -422,6 +422,6 @@ export default defineComponent({
<Divider /> <Divider />
<SettingFooter /> <SettingFooter />
</BasicDrawer> </BasicDrawer>
); )
}, },
}); })

View File

@ -78,26 +78,26 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { Menu } from '/@/router/types'; import type { Menu } from '/@/router/types'
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue'
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'; import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'; import type { RouteLocationNormalized } from 'vue-router'
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container'
import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu'; import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { AppLogo } from '/@/components/Application'; import { AppLogo } from '/@/components/Application'
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { usePermissionStore } from '/@/store/modules/permission'; import { usePermissionStore } from '/@/store/modules/permission'
import { useDragLine } from './useLayoutSider'; import { useDragLine } from './useLayoutSider'
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage'
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'
import clickOutside from '/@/directives/clickOutside'; import clickOutside from '/@/directives/clickOutside'
import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus'; import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus'
import { listenerRouteChange } from '/@/logics/mitt/routeChange'; import { listenerRouteChange } from '/@/logics/mitt/routeChange'
import LayoutTrigger from '../trigger/index.vue'; import LayoutTrigger from '../trigger/index.vue'
export default defineComponent({ export default defineComponent({
name: 'LayoutMixSider', name: 'LayoutMixSider',
@ -113,17 +113,17 @@
clickOutside, clickOutside,
}, },
setup() { setup() {
let menuModules = ref<Menu[]>([]); let menuModules = ref<Menu[]>([])
const activePath = ref(''); const activePath = ref('')
const childrenMenus = ref<Menu[]>([]); const childrenMenus = ref<Menu[]>([])
const openMenu = ref(false); const openMenu = ref(false)
const dragBarRef = ref<ElRef>(null); const dragBarRef = ref<ElRef>(null)
const sideRef = ref<ElRef>(null); const sideRef = ref<ElRef>(null)
const currentRoute = ref<Nullable<RouteLocationNormalized>>(null); const currentRoute = ref<Nullable<RouteLocationNormalized>>(null)
const { prefixCls } = useDesign('layout-mix-sider'); const { prefixCls } = useDesign('layout-mix-sider')
const go = useGo(); const go = useGo()
const { t } = useI18n(); const { t } = useI18n()
const { const {
getMenuWidth, getMenuWidth,
getCanDrag, getCanDrag,
@ -136,81 +136,81 @@
setMenuSetting, setMenuSetting,
getIsMixSidebar, getIsMixSidebar,
getCollapsed, getCollapsed,
} = useMenuSetting(); } = useMenuSetting()
const { title } = useGlobSetting(); const { title } = useGlobSetting()
const permissionStore = usePermissionStore(); const permissionStore = usePermissionStore()
useDragLine(sideRef, dragBarRef, true); useDragLine(sideRef, dragBarRef, true)
const getMenuStyle = computed((): CSSProperties => { const getMenuStyle = computed((): CSSProperties => {
return { return {
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0, width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
left: `${unref(getMixSideWidth)}px`, left: `${unref(getMixSideWidth)}px`,
}; }
}); })
const getIsFixed = computed(() => { const getIsFixed = computed(() => {
/* eslint-disable-next-line */ /* eslint-disable-next-line */
mixSideHasChildren.value = unref(childrenMenus).length > 0; mixSideHasChildren.value = unref(childrenMenus).length > 0;
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren); const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren)
if (isFixed) { if (isFixed) {
/* eslint-disable-next-line */ /* eslint-disable-next-line */
openMenu.value = true; openMenu.value = true;
} }
return isFixed; return isFixed
}); })
const getMixSideWidth = computed(() => { const getMixSideWidth = computed(() => {
return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH; return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH
}); })
const getDomStyle = computed((): CSSProperties => { const getDomStyle = computed((): CSSProperties => {
const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0; const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0
const width = `${unref(getMixSideWidth) + fixedWidth}px`; const width = `${unref(getMixSideWidth) + fixedWidth}px`
return getWrapCommonStyle(width); return getWrapCommonStyle(width)
}); })
const getWrapStyle = computed((): CSSProperties => { const getWrapStyle = computed((): CSSProperties => {
const width = `${unref(getMixSideWidth)}px`; const width = `${unref(getMixSideWidth)}px`
return getWrapCommonStyle(width); return getWrapCommonStyle(width)
}); })
const getMenuEvents = computed(() => { const getMenuEvents = computed(() => {
return !unref(getMixSideFixed) return !unref(getMixSideFixed)
? { ? {
onMouseleave: () => { onMouseleave: () => {
setActive(true); setActive(true)
closeMenu(); closeMenu()
}, },
} }
: {}; : {}
}); })
const getShowDragBar = computed(() => unref(getCanDrag)); const getShowDragBar = computed(() => unref(getCanDrag))
onMounted(async () => { onMounted(async () => {
menuModules.value = await getShallowMenus(); menuModules.value = await getShallowMenus()
}); })
// Menu changes // Menu changes
watch( watch(
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList], [() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
async () => { async () => {
menuModules.value = await getShallowMenus(); menuModules.value = await getShallowMenus()
}, },
{ {
immediate: true, immediate: true,
}, },
); )
listenerRouteChange((route) => { listenerRouteChange((route) => {
currentRoute.value = route; currentRoute.value = route
setActive(true); setActive(true)
if (unref(getCloseMixSidebarOnChange)) { if (unref(getCloseMixSidebarOnChange)) {
closeMenu(); closeMenu()
} }
}); })
function getWrapCommonStyle(width: string): CSSProperties { function getWrapCommonStyle(width: string): CSSProperties {
return { return {
@ -218,73 +218,73 @@
maxWidth: width, maxWidth: width,
minWidth: width, minWidth: width,
flex: `0 0 ${width}`, flex: `0 0 ${width}`,
}; }
} }
// Process module menu click // Process module menu click
async function handleModuleClick(path: string, hover = false) { async function handleModuleClick(path: string, hover = false) {
const children = await getChildrenMenus(path); const children = await getChildrenMenus(path)
if (unref(activePath) === path) { if (unref(activePath) === path) {
if (!hover) { if (!hover) {
if (!unref(openMenu)) { if (!unref(openMenu)) {
openMenu.value = true; openMenu.value = true
} else { } else {
closeMenu(); closeMenu()
} }
} else { } else {
if (!unref(openMenu)) { if (!unref(openMenu)) {
openMenu.value = true; openMenu.value = true
} }
} }
if (!unref(openMenu)) { if (!unref(openMenu)) {
setActive(); setActive()
} }
} else { } else {
openMenu.value = true; openMenu.value = true
activePath.value = path; activePath.value = path
} }
if (!children || children.length === 0) { if (!children || children.length === 0) {
if (!hover) go(path); if (!hover) go(path)
childrenMenus.value = []; childrenMenus.value = []
closeMenu(); closeMenu()
return; return
} }
childrenMenus.value = children; childrenMenus.value = children
} }
// Set the currently active menu and submenu // Set the currently active menu and submenu
async function setActive(setChildren = false) { async function setActive(setChildren = false) {
const path = currentRoute.value?.path; const path = currentRoute.value?.path
if (!path) return; if (!path) return
activePath.value = await getCurrentParentPath(path); activePath.value = await getCurrentParentPath(path)
// hanldeModuleClick(parentPath); // hanldeModuleClick(parentPath);
if (unref(getIsMixSidebar)) { if (unref(getIsMixSidebar)) {
const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath)); const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath))
const p = activeMenu?.path; const p = activeMenu?.path
if (p) { if (p) {
const children = await getChildrenMenus(p); const children = await getChildrenMenus(p)
if (setChildren) { if (setChildren) {
childrenMenus.value = children; childrenMenus.value = children
if (unref(getMixSideFixed)) { if (unref(getMixSideFixed)) {
openMenu.value = children.length > 0; openMenu.value = children.length > 0
} }
} }
if (children.length === 0) { if (children.length === 0) {
childrenMenus.value = []; childrenMenus.value = []
} }
} }
} }
} }
function handleMenuClick(path: string) { function handleMenuClick(path: string) {
go(path); go(path)
} }
function handleClickOutside() { function handleClickOutside() {
setActive(true); setActive(true)
closeMenu(); closeMenu()
} }
function getItemEvents(item: Menu) { function getItemEvents(item: Menu) {
@ -292,26 +292,26 @@
return { return {
onMouseenter: () => handleModuleClick(item.path, true), onMouseenter: () => handleModuleClick(item.path, true),
onClick: async () => { onClick: async () => {
const children = await getChildrenMenus(item.path); const children = await getChildrenMenus(item.path)
if (item.path && (!children || children.length === 0)) go(item.path); if (item.path && (!children || children.length === 0)) go(item.path)
}, },
}; }
} }
return { return {
onClick: () => handleModuleClick(item.path), onClick: () => handleModuleClick(item.path),
}; }
} }
function handleFixedMenu() { function handleFixedMenu() {
setMenuSetting({ setMenuSetting({
mixSideFixed: !unref(getIsFixed), mixSideFixed: !unref(getIsFixed),
}); })
} }
// Close menu // Close menu
function closeMenu() { function closeMenu() {
if (!unref(getIsFixed)) { if (!unref(getIsFixed)) {
openMenu.value = false; openMenu.value = false
} }
} }
@ -338,9 +338,9 @@
getMixSideFixed, getMixSideFixed,
getWrapStyle, getWrapStyle,
getCollapsed, getCollapsed,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-layout-mix-sider'; @prefix-cls: ~'@{namespace}-layout-mix-sider';

View File

@ -1,61 +1,61 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue'
import { computed, unref, onMounted, nextTick } from 'vue'; import { computed, unref, onMounted, nextTick } from 'vue'
import { TriggerEnum } from '/@/enums/menuEnum'; import { TriggerEnum } from '/@/enums/menuEnum'
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core'
import { useAppStore } from '/@/store/modules/app'; import { useAppStore } from '/@/store/modules/app'
/** /**
* Handle related operations of menu events * Handle related operations of menu events
*/ */
export function useSiderEvent() { export function useSiderEvent() {
const appStore = useAppStore(); const appStore = useAppStore()
const { getMiniWidthNumber } = useMenuSetting(); const { getMiniWidthNumber } = useMenuSetting()
const getCollapsedWidth = computed(() => { const getCollapsedWidth = computed(() => {
return unref(getMiniWidthNumber); return unref(getMiniWidthNumber)
}); })
function onBreakpointChange(broken: boolean) { function onBreakpointChange(broken: boolean) {
appStore.setProjectConfig({ appStore.setProjectConfig({
menuSetting: { menuSetting: {
siderHidden: broken, siderHidden: broken,
}, },
}); })
} }
return { getCollapsedWidth, onBreakpointChange }; return { getCollapsedWidth, onBreakpointChange }
} }
/** /**
* Handle related operations of menu folding * Handle related operations of menu folding
*/ */
export function useTrigger(getIsMobile: Ref<boolean>) { export function useTrigger(getIsMobile: Ref<boolean>) {
const { getTrigger, getSplit } = useMenuSetting(); const { getTrigger, getSplit } = useMenuSetting()
const getShowTrigger = computed(() => { const getShowTrigger = computed(() => {
const trigger = unref(getTrigger); const trigger = unref(getTrigger)
return ( return (
trigger !== TriggerEnum.NONE && trigger !== TriggerEnum.NONE &&
!unref(getIsMobile) && !unref(getIsMobile) &&
(trigger === TriggerEnum.FOOTER || unref(getSplit)) (trigger === TriggerEnum.FOOTER || unref(getSplit))
); )
}); })
const getTriggerAttr = computed(() => { const getTriggerAttr = computed(() => {
if (unref(getShowTrigger)) { if (unref(getShowTrigger)) {
return {}; return {}
} }
return { return {
trigger: null, trigger: null,
}; }
}); })
return { getTriggerAttr, getShowTrigger }; return { getTriggerAttr, getShowTrigger }
} }
/** /**
@ -64,80 +64,80 @@ export function useTrigger(getIsMobile: Ref<boolean>) {
* @param dragBarRef * @param dragBarRef
*/ */
export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>, mix = false) { export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>, mix = false) {
const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting(); const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting()
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
const exec = useDebounceFn(changeWrapWidth, 80); const exec = useDebounceFn(changeWrapWidth, 80)
exec(); exec()
}); })
}); })
function getEl(elRef: Ref<ElRef | ComponentRef>): any { function getEl(elRef: Ref<ElRef | ComponentRef>): any {
const el = unref(elRef); const el = unref(elRef)
if (!el) return null; if (!el) return null
if (Reflect.has(el, '$el')) { if (Reflect.has(el, '$el')) {
return (unref(elRef) as ComponentRef)?.$el; return (unref(elRef) as ComponentRef)?.$el
} }
return unref(elRef); return unref(elRef)
} }
function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) {
document.onmousemove = function (innerE) { document.onmousemove = function (innerE) {
let iT = (ele as any).left + (innerE.clientX - clientX); let iT = (ele as any).left + (innerE.clientX - clientX)
innerE = innerE || window.event; innerE = innerE || window.event
const maxT = 800; const maxT = 800
const minT = unref(getMiniWidthNumber); const minT = unref(getMiniWidthNumber)
iT < 0 && (iT = 0); iT < 0 && (iT = 0)
iT > maxT && (iT = maxT); iT > maxT && (iT = maxT)
iT < minT && (iT = minT); iT < minT && (iT = minT)
ele.style.left = wrap.style.width = iT + 'px'; ele.style.left = wrap.style.width = iT + 'px'
return false; return false
}; }
} }
// Drag and drop in the menu area-release the mouse // Drag and drop in the menu area-release the mouse
function removeMouseup(ele: any) { function removeMouseup(ele: any) {
const wrap = getEl(siderRef); const wrap = getEl(siderRef)
document.onmouseup = function () { document.onmouseup = function () {
document.onmousemove = null; document.onmousemove = null
document.onmouseup = null; document.onmouseup = null
wrap.style.transition = 'width 0.2s'; wrap.style.transition = 'width 0.2s'
const width = parseInt(wrap.style.width); const width = parseInt(wrap.style.width)
if (!mix) { if (!mix) {
const miniWidth = unref(getMiniWidthNumber); const miniWidth = unref(getMiniWidthNumber)
if (!unref(getCollapsed)) { if (!unref(getCollapsed)) {
width > miniWidth + 20 width > miniWidth + 20
? setMenuSetting({ menuWidth: width }) ? setMenuSetting({ menuWidth: width })
: setMenuSetting({ collapsed: true }); : setMenuSetting({ collapsed: true })
} else { } else {
width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width }); width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width })
} }
} else { } else {
setMenuSetting({ menuWidth: width }); setMenuSetting({ menuWidth: width })
} }
ele.releaseCapture?.(); ele.releaseCapture?.()
}; }
} }
function changeWrapWidth() { function changeWrapWidth() {
const ele = getEl(dragBarRef); const ele = getEl(dragBarRef)
if (!ele) return; if (!ele) return
const wrap = getEl(siderRef); const wrap = getEl(siderRef)
if (!wrap) return; if (!wrap) return
ele.onmousedown = (e: any) => { ele.onmousedown = (e: any) => {
wrap.style.transition = 'unset'; wrap.style.transition = 'unset'
const clientX = e?.clientX; const clientX = e?.clientX
ele.left = ele.offsetLeft; ele.left = ele.offsetLeft
handleMouseMove(ele, wrap, clientX); handleMouseMove(ele, wrap, clientX)
removeMouseup(ele); removeMouseup(ele)
ele.setCapture?.(); ele.setCapture?.()
return false; return false
}; }
} }
return {}; return {}
} }

View File

@ -15,18 +15,18 @@
</Dropdown> </Dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'; import type { RouteLocationNormalized } from 'vue-router'
import { defineComponent, computed, unref } from 'vue'; import { defineComponent, computed, unref } from 'vue'
import { Dropdown } from '/@/components/Dropdown/index'; import { Dropdown } from '/@/components/Dropdown/index'
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon'
import { TabContentProps } from '../types'; import { TabContentProps } from '../types'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useTabDropdown } from '../useTabDropdown'; import { useTabDropdown } from '../useTabDropdown'
export default defineComponent({ export default defineComponent({
name: 'TabContent', name: 'TabContent',
@ -39,27 +39,27 @@
isExtra: Boolean, isExtra: Boolean,
}, },
setup(props) { setup(props) {
const { prefixCls } = useDesign('multiple-tabs-content'); const { prefixCls } = useDesign('multiple-tabs-content')
const { t } = useI18n(); const { t } = useI18n()
const getTitle = computed(() => { const getTitle = computed(() => {
const { tabItem: { meta } = {} } = props; const { tabItem: { meta } = {} } = props
return meta && t(meta.title as string); return meta && t(meta.title as string)
}); })
const getIsTabs = computed(() => !props.isExtra); const getIsTabs = computed(() => !props.isExtra)
const getTrigger = computed((): ('contextmenu' | 'click' | 'hover')[] => const getTrigger = computed((): ('contextmenu' | 'click' | 'hover')[] =>
unref(getIsTabs) ? ['contextmenu'] : ['click'], unref(getIsTabs) ? ['contextmenu'] : ['click'],
); )
const { getDropMenuList, handleMenuEvent, handleContextMenu } = useTabDropdown( const { getDropMenuList, handleMenuEvent, handleContextMenu } = useTabDropdown(
props as TabContentProps, props as TabContentProps,
getIsTabs, getIsTabs,
); )
function handleContext(e) { function handleContext(e) {
props.tabItem && handleContextMenu(props.tabItem)(e); props.tabItem && handleContextMenu(props.tabItem)(e)
} }
return { return {
@ -70,7 +70,7 @@
getTrigger, getTrigger,
getIsTabs, getIsTabs,
getTitle, getTitle,
}; }
}, },
}); })
</script> </script>

View File

@ -27,28 +27,28 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { RouteLocationNormalized, RouteMeta } from 'vue-router'; import type { RouteLocationNormalized, RouteMeta } from 'vue-router'
import { defineComponent, computed, unref, ref } from 'vue'; import { defineComponent, computed, unref, ref } from 'vue'
import { Tabs } from 'ant-design-vue'; import { Tabs } from 'ant-design-vue'
import TabContent from './components/TabContent.vue'; import TabContent from './components/TabContent.vue'
import FoldButton from './components/FoldButton.vue'; import FoldButton from './components/FoldButton.vue'
import TabRedo from './components/TabRedo.vue'; import TabRedo from './components/TabRedo.vue'
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage'
import { useMultipleTabStore } from '/@/store/modules/multipleTab'; import { useMultipleTabStore } from '/@/store/modules/multipleTab'
import { useUserStore } from '/@/store/modules/user'; import { useUserStore } from '/@/store/modules/user'
import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; import { initAffixTabs, useTabsDrag } from './useMultipleTabs'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'
import { REDIRECT_NAME } from '/@/router/constant'; import { REDIRECT_NAME } from '/@/router/constant'
import { listenerRouteChange } from '/@/logics/mitt/routeChange'; import { listenerRouteChange } from '/@/logics/mitt/routeChange'
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
export default defineComponent({ export default defineComponent({
name: 'MultipleTabs', name: 'MultipleTabs',
@ -60,23 +60,23 @@
TabContent, TabContent,
}, },
setup() { setup() {
const affixTextList = initAffixTabs(); const affixTextList = initAffixTabs()
const activeKeyRef = ref(''); const activeKeyRef = ref('')
useTabsDrag(affixTextList); useTabsDrag(affixTextList)
const tabStore = useMultipleTabStore(); const tabStore = useMultipleTabStore()
const userStore = useUserStore(); const userStore = useUserStore()
const router = useRouter(); const router = useRouter()
const { prefixCls } = useDesign('multiple-tabs'); const { prefixCls } = useDesign('multiple-tabs')
const go = useGo(); const go = useGo()
const { getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting(); const { getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting()
const getTabsState = computed(() => { const getTabsState = computed(() => {
return tabStore.getTabList.filter((item) => !item.meta?.hideTab); return tabStore.getTabList.filter((item) => !item.meta?.hideTab)
}); })
const unClose = computed(() => unref(getTabsState).length === 1); const unClose = computed(() => unref(getTabsState).length === 1)
const getWrapClass = computed(() => { const getWrapClass = computed(() => {
return [ return [
@ -84,47 +84,45 @@
{ {
[`${prefixCls}--hide-close`]: unref(unClose), [`${prefixCls}--hide-close`]: unref(unClose),
}, },
]; ]
}); })
listenerRouteChange((route) => { listenerRouteChange((route) => {
const { name } = route; const { name } = route
if (name === REDIRECT_NAME || !route || !userStore.getToken) { if (name === REDIRECT_NAME || !route || !userStore.getToken) {
return; return
} }
const { path, fullPath, meta = {} } = route; const { path, fullPath, meta = {} } = route
const { currentActiveMenu, hideTab } = meta as RouteMeta; const { currentActiveMenu, hideTab } = meta as RouteMeta
const isHide = !hideTab ? null : currentActiveMenu; const isHide = !hideTab ? null : currentActiveMenu
const p = isHide || fullPath || path; const p = isHide || fullPath || path
if (activeKeyRef.value !== p) { if (activeKeyRef.value !== p) {
activeKeyRef.value = p as string; activeKeyRef.value = p as string
} }
if (isHide) { if (isHide) {
const findParentRoute = router const findParentRoute = router.getRoutes().find((item) => item.path === currentActiveMenu)
.getRoutes()
.find((item) => item.path === currentActiveMenu);
findParentRoute && tabStore.addTab(findParentRoute as unknown as RouteLocationNormalized); findParentRoute && tabStore.addTab(findParentRoute as unknown as RouteLocationNormalized)
} else { } else {
tabStore.addTab(unref(route)); tabStore.addTab(unref(route))
} }
}); })
function handleChange(activeKey: any) { function handleChange(activeKey: any) {
activeKeyRef.value = activeKey; activeKeyRef.value = activeKey
go(activeKey, false); go(activeKey, false)
} }
// Close the current tab // Close the current tab
function handleEdit(targetKey: string) { function handleEdit(targetKey: string) {
// Added operation to hide, currently only use delete operation // Added operation to hide, currently only use delete operation
if (unref(unClose)) { if (unref(unClose)) {
return; return
} }
tabStore.closeTabByKey(targetKey, router); tabStore.closeTabByKey(targetKey, router)
} }
return { return {
getWrapClass, getWrapClass,
@ -135,9 +133,9 @@
getShowQuick, getShowQuick,
getShowRedo, getShowRedo,
getShowFold, getShowFold,
}; }
}, },
}); })
</script> </script>
<style lang="less"> <style lang="less">
@import './index.less'; @import './index.less';

View File

@ -1,80 +1,80 @@
import { toRaw, ref, nextTick } from 'vue'; import { toRaw, ref, nextTick } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'; import type { RouteLocationNormalized } from 'vue-router'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useSortable } from '/@/hooks/web/useSortable'; import { useSortable } from '/@/hooks/web/useSortable'
import { useMultipleTabStore } from '/@/store/modules/multipleTab'; import { useMultipleTabStore } from '/@/store/modules/multipleTab'
import { isNullAndUnDef } from '/@/utils/is'; import { isNullAndUnDef } from '/@/utils/is'
import projectSetting from '/@/settings/projectSetting'; import projectSetting from '/@/settings/projectSetting'
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
export function initAffixTabs(): string[] { export function initAffixTabs(): string[] {
const affixList = ref<RouteLocationNormalized[]>([]); const affixList = ref<RouteLocationNormalized[]>([])
const tabStore = useMultipleTabStore(); const tabStore = useMultipleTabStore()
const router = useRouter(); const router = useRouter()
/** /**
* @description: Filter all fixed routes * @description: Filter all fixed routes
*/ */
function filterAffixTabs(routes: RouteLocationNormalized[]) { function filterAffixTabs(routes: RouteLocationNormalized[]) {
const tabs: RouteLocationNormalized[] = []; const tabs: RouteLocationNormalized[] = []
routes && routes &&
routes.forEach((route) => { routes.forEach((route) => {
if (route.meta && route.meta.affix) { if (route.meta && route.meta.affix) {
tabs.push(toRaw(route)); tabs.push(toRaw(route))
} }
}); })
return tabs; return tabs
} }
/** /**
* @description: Set fixed tabs * @description: Set fixed tabs
*/ */
function addAffixTabs(): void { function addAffixTabs(): void {
const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[]); const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[])
affixList.value = affixTabs; affixList.value = affixTabs
for (const tab of affixTabs) { for (const tab of affixTabs) {
tabStore.addTab({ tabStore.addTab({
meta: tab.meta, meta: tab.meta,
name: tab.name, name: tab.name,
path: tab.path, path: tab.path,
} as unknown as RouteLocationNormalized); } as unknown as RouteLocationNormalized)
} }
} }
let isAddAffix = false; let isAddAffix = false
if (!isAddAffix) { if (!isAddAffix) {
addAffixTabs(); addAffixTabs()
isAddAffix = true; isAddAffix = true
} }
return affixList.value.map((item) => item.meta?.title).filter(Boolean) as string[]; return affixList.value.map((item) => item.meta?.title).filter(Boolean) as string[]
} }
export function useTabsDrag(affixTextList: string[]) { export function useTabsDrag(affixTextList: string[]) {
const tabStore = useMultipleTabStore(); const tabStore = useMultipleTabStore()
const { multiTabsSetting } = projectSetting; const { multiTabsSetting } = projectSetting
const { prefixCls } = useDesign('multiple-tabs'); const { prefixCls } = useDesign('multiple-tabs')
nextTick(() => { nextTick(() => {
if (!multiTabsSetting.canDrag) return; if (!multiTabsSetting.canDrag) return
const el = document.querySelectorAll( const el = document.querySelectorAll(
`.${prefixCls} .ant-tabs-nav-wrap > div`, `.${prefixCls} .ant-tabs-nav-wrap > div`,
)?.[0] as HTMLElement; )?.[0] as HTMLElement
const { initSortable } = useSortable(el, { const { initSortable } = useSortable(el, {
filter: (e: ChangeEvent) => { filter: (e: ChangeEvent) => {
const text = e?.target?.innerText; const text = e?.target?.innerText
if (!text) return false; if (!text) return false
return affixTextList.includes(text); return affixTextList.includes(text)
}, },
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
} }
tabStore.sortTabs(oldIndex, newIndex); tabStore.sortTabs(oldIndex, newIndex)
}, },
}); })
initSortable(); initSortable()
}); })
} }

View File

@ -27,37 +27,37 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, unref } from 'vue'; import { computed, defineComponent, unref } from 'vue'
import FrameLayout from '/@/layouts/iframe/index.vue'; import FrameLayout from '/@/layouts/iframe/index.vue'
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting'
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'
import { getTransitionName } from './transition'; import { getTransitionName } from './transition'
import { useMultipleTabStore } from '/@/store/modules/multipleTab'; import { useMultipleTabStore } from '/@/store/modules/multipleTab'
export default defineComponent({ export default defineComponent({
name: 'PageLayout', name: 'PageLayout',
components: { FrameLayout }, components: { FrameLayout },
setup() { setup() {
const { getShowMultipleTab } = useMultipleTabSetting(); const { getShowMultipleTab } = useMultipleTabSetting()
const tabStore = useMultipleTabStore(); const tabStore = useMultipleTabStore()
const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting(); const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting()
const { getBasicTransition, getEnableTransition } = useTransitionSetting(); const { getBasicTransition, getEnableTransition } = useTransitionSetting()
const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab)); const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab))
const getCaches = computed((): string[] => { const getCaches = computed((): string[] => {
if (!unref(getOpenKeepAlive)) { if (!unref(getOpenKeepAlive)) {
return []; return []
} }
return tabStore.getCachedTabList; return tabStore.getCachedTabList
}); })
return { return {
getTransitionName, getTransitionName,
@ -66,7 +66,7 @@
getBasicTransition, getBasicTransition,
getCaches, getCaches,
getCanEmbedIFramePage, getCanEmbedIFramePage,
}; }
}, },
}); })
</script> </script>

View File

@ -1,23 +1,23 @@
import type { Router, RouteRecordRaw } from 'vue-router'; import type { Router, RouteRecordRaw } from 'vue-router'
import { usePermissionStoreWithOut } from '/@/store/modules/permission'; import { usePermissionStoreWithOut } from '/@/store/modules/permission'
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum'
import { useUserStoreWithOut } from '/@/store/modules/user'; import { useUserStoreWithOut } from '/@/store/modules/user'
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'
import { RootRoute } from '/@/router/routes'; import { RootRoute } from '/@/router/routes'
const LOGIN_PATH = PageEnum.BASE_LOGIN; const LOGIN_PATH = PageEnum.BASE_LOGIN
const ROOT_PATH = RootRoute.path; const ROOT_PATH = RootRoute.path
const whitePathList: PageEnum[] = [LOGIN_PATH]; const whitePathList: PageEnum[] = [LOGIN_PATH]
export function createPermissionGuard(router: Router) { export function createPermissionGuard(router: Router) {
const userStore = useUserStoreWithOut(); const userStore = useUserStoreWithOut()
const permissionStore = usePermissionStoreWithOut(); const permissionStore = usePermissionStoreWithOut()
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
if ( if (
from.path === ROOT_PATH && from.path === ROOT_PATH &&
@ -25,49 +25,49 @@ export function createPermissionGuard(router: Router) {
userStore.getUserInfo.homePath && userStore.getUserInfo.homePath &&
userStore.getUserInfo.homePath !== PageEnum.BASE_HOME userStore.getUserInfo.homePath !== PageEnum.BASE_HOME
) { ) {
next(userStore.getUserInfo.homePath); next(userStore.getUserInfo.homePath)
return; return
} }
const token = userStore.getToken; const token = userStore.getToken
// Whitelist can be directly entered // Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) { if (whitePathList.includes(to.path as PageEnum)) {
if (to.path === LOGIN_PATH && token) { if (to.path === LOGIN_PATH && token) {
const isSessionTimeout = userStore.getSessionTimeout; const isSessionTimeout = userStore.getSessionTimeout
try { try {
await userStore.afterLoginAction(); await userStore.afterLoginAction()
if (!isSessionTimeout) { if (!isSessionTimeout) {
next((to.query?.redirect as string) || '/'); next((to.query?.redirect as string) || '/')
return; return
} }
} catch {} } catch {}
} }
next(); next()
return; return
} }
// token does not exist // token does not exist
if (!token) { if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true // You can access without permission. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) { if (to.meta.ignoreAuth) {
next(); next()
return; return
} }
// redirect login page // redirect login page
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = { const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH, path: LOGIN_PATH,
replace: true, replace: true,
}; }
if (to.path) { if (to.path) {
redirectData.query = { redirectData.query = {
...redirectData.query, ...redirectData.query,
redirect: to.path, redirect: to.path,
}; }
} }
next(redirectData); next(redirectData)
return; return
} }
// Jump to the 404 page after processing the login // Jump to the 404 page after processing the login
@ -76,43 +76,43 @@ export function createPermissionGuard(router: Router) {
to.name === PAGE_NOT_FOUND_ROUTE.name && to.name === PAGE_NOT_FOUND_ROUTE.name &&
to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME) to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
) { ) {
next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME); next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
return; return
} }
// get userinfo while last fetch time is empty // get userinfo while last fetch time is empty
if (userStore.getLastUpdateTime === 0) { if (userStore.getLastUpdateTime === 0) {
try { try {
await userStore.getUserInfoAction(); await userStore.getUserInfoAction()
} catch (err) { } catch (err) {
next(); next()
return; return
} }
} }
if (permissionStore.getIsDynamicAddedRoute) { if (permissionStore.getIsDynamicAddedRoute) {
next(); next()
return; return
} }
const routes = await permissionStore.buildRoutesAction(); const routes = await permissionStore.buildRoutesAction()
routes.forEach((route) => { routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw); router.addRoute(route as unknown as RouteRecordRaw)
}); })
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw); router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw)
permissionStore.setDynamicAddedRoute(true); permissionStore.setDynamicAddedRoute(true)
if (to.name === PAGE_NOT_FOUND_ROUTE.name) { if (to.name === PAGE_NOT_FOUND_ROUTE.name) {
// 动态添加路由后此处应当重定向到fullPath否则会加载404页面内容 // 动态添加路由后此处应当重定向到fullPath否则会加载404页面内容
next({ path: to.fullPath, replace: true, query: to.query }); next({ path: to.fullPath, replace: true, query: to.query })
} else { } else {
const redirectPath = (from.query.redirect || to.path) as string; const redirectPath = (from.query.redirect || to.path) as string
const redirect = decodeURIComponent(redirectPath); const redirect = decodeURIComponent(redirectPath)
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
next(nextData); next(nextData)
} }
}); })
} }

View File

@ -1,20 +1,20 @@
import { AppRouteModule } from '/@/router/types'; import { AppRouteModule } from '/@/router/types'
import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'
import { findPath, treeMap } from '/@/utils/helper/treeHelper'; import { findPath, treeMap } from '/@/utils/helper/treeHelper'
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es'
import { isUrl } from '/@/utils/is'; import { isUrl } from '/@/utils/is'
import { RouteParams } from 'vue-router'; import { RouteParams } from 'vue-router'
import { toRaw } from 'vue'; import { toRaw } from 'vue'
export function getAllParentPath<T = Recordable>(treeData: T[], path: string) { export function getAllParentPath<T = Recordable>(treeData: T[], path: string) {
const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; const menuList = findPath(treeData, (n) => n.path === path) as Menu[]
return (menuList || []).map((item) => item.path); return (menuList || []).map((item) => item.path)
} }
// 路径处理 // 路径处理
function joinParentPath(menus: Menu[], parentPath = '') { function joinParentPath(menus: Menu[], parentPath = '') {
for (let index = 0; index < menus.length; index++) { for (let index = 0; index < menus.length; index++) {
const menu = menus[index]; const menu = menus[index]
// https://next.router.vuejs.org/guide/essentials/nested-routes.html // https://next.router.vuejs.org/guide/essentials/nested-routes.html
// Note that nested paths that start with / will be treated as a root path. // Note that nested paths that start with / will be treated as a root path.
// 请注意,以 / 开头的嵌套路径将被视为根路径。 // 请注意,以 / 开头的嵌套路径将被视为根路径。
@ -23,47 +23,47 @@ function joinParentPath(menus: Menu[], parentPath = '') {
if (!(menu.path.startsWith('/') || isUrl(menu.path))) { if (!(menu.path.startsWith('/') || isUrl(menu.path))) {
// path doesn't start with /, nor is it a url, join parent path // path doesn't start with /, nor is it a url, join parent path
// 路径不以 / 开头,也不是 url加入父路径 // 路径不以 / 开头,也不是 url加入父路径
menu.path = `${parentPath}/${menu.path}`; menu.path = `${parentPath}/${menu.path}`
} }
if (menu?.children?.length) { if (menu?.children?.length) {
joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path); joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path)
} }
} }
} }
// Parsing the menu module // Parsing the menu module
export function transformMenuModule(menuModule: MenuModule): Menu { export function transformMenuModule(menuModule: MenuModule): Menu {
const { menu } = menuModule; const { menu } = menuModule
const menuList = [menu]; const menuList = [menu]
joinParentPath(menuList); joinParentPath(menuList)
return menuList[0]; return menuList[0]
} }
// 将路由转换成菜单 // 将路由转换成菜单
export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) { export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) {
// 借助 lodash 深拷贝 // 借助 lodash 深拷贝
const cloneRouteModList = cloneDeep(routeModList); const cloneRouteModList = cloneDeep(routeModList)
const routeList: AppRouteRecordRaw[] = []; const routeList: AppRouteRecordRaw[] = []
// 对路由项进行修改 // 对路由项进行修改
cloneRouteModList.forEach((item) => { cloneRouteModList.forEach((item) => {
if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') { if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') {
item.path = item.redirect; item.path = item.redirect
} }
if (item.meta?.single) { if (item.meta?.single) {
const realItem = item?.children?.[0]; const realItem = item?.children?.[0]
realItem && routeList.push(realItem); realItem && routeList.push(realItem)
} else { } else {
routeList.push(item); routeList.push(item)
} }
}); })
// 提取树指定结构 // 提取树指定结构
const list = treeMap(routeList, { const list = treeMap(routeList, {
conversion: (node: AppRouteRecordRaw) => { conversion: (node: AppRouteRecordRaw) => {
const { meta: { title, hideMenu = false } = {} } = node; const { meta: { title, hideMenu = false } = {} } = node
return { return {
...(node.meta || {}), ...(node.meta || {}),
@ -72,35 +72,35 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi
hideMenu, hideMenu,
path: node.path, path: node.path,
...(node.redirect ? { redirect: node.redirect } : {}), ...(node.redirect ? { redirect: node.redirect } : {}),
}; }
}, },
}); })
// 路径处理 // 路径处理
joinParentPath(list); joinParentPath(list)
return cloneDeep(list); return cloneDeep(list)
} }
/** /**
* config menu with given params * config menu with given params
*/ */
const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g; const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g
export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) { export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) {
const { path, paramPath } = toRaw(menu); const { path, paramPath } = toRaw(menu)
let realPath = paramPath ? paramPath : path; let realPath = paramPath ? paramPath : path
const matchArr = realPath.match(menuParamRegex); const matchArr = realPath.match(menuParamRegex)
matchArr?.forEach((it) => { matchArr?.forEach((it) => {
const realIt = it.substr(1); const realIt = it.substr(1)
if (params[realIt]) { if (params[realIt]) {
realPath = realPath.replace(`:${realIt}`, params[realIt] as string); realPath = realPath.replace(`:${realIt}`, params[realIt] as string)
} }
}); })
// save original param path. // save original param path.
if (!paramPath && matchArr && matchArr.length > 0) { if (!paramPath && matchArr && matchArr.length > 0) {
menu.paramPath = path; menu.paramPath = path
} }
menu.path = realPath; menu.path = realPath
// children // children
menu.children?.forEach((item) => configureDynamicParamsMenu(item, params)); menu.children?.forEach((item) => configureDynamicParamsMenu(item, params))
} }

View File

@ -1,69 +1,69 @@
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'
import type { Router, RouteRecordNormalized } from 'vue-router'; import type { Router, RouteRecordNormalized } from 'vue-router'
import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'; import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'
import { cloneDeep, omit } from 'lodash-es'; import { cloneDeep, omit } from 'lodash-es'
import { warn } from '/@/utils/log'; import { warn } from '/@/utils/log'
import { createRouter, createWebHashHistory } from 'vue-router'; import { createRouter, createWebHashHistory } from 'vue-router'
export type LayoutMapKey = 'LAYOUT'; export type LayoutMapKey = 'LAYOUT'
const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue'); const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue')
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>(); const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>()
LayoutMap.set('LAYOUT', LAYOUT); LayoutMap.set('LAYOUT', LAYOUT)
LayoutMap.set('IFRAME', IFRAME); LayoutMap.set('IFRAME', IFRAME)
let dynamicViewsModules: Record<string, () => Promise<Recordable>>; let dynamicViewsModules: Record<string, () => Promise<Recordable>>
// Dynamic introduction // Dynamic introduction
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}'); dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}')
if (!routes) return; if (!routes) return
routes.forEach((item) => { routes.forEach((item) => {
if (!item.component && item.meta?.frameSrc) { if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME'; item.component = 'IFRAME'
} }
const { component, name } = item; const { component, name } = item
const { children } = item; const { children } = item
if (component) { if (component) {
const layoutFound = LayoutMap.get(component.toUpperCase()); const layoutFound = LayoutMap.get(component.toUpperCase())
if (layoutFound) { if (layoutFound) {
item.component = layoutFound; item.component = layoutFound
} else { } else {
item.component = dynamicImport(dynamicViewsModules, component as string); item.component = dynamicImport(dynamicViewsModules, component as string)
} }
} else if (name) { } else if (name) {
item.component = getParentLayout(); item.component = getParentLayout()
} }
children && asyncImportRoute(children); children && asyncImportRoute(children)
}); })
} }
function dynamicImport( function dynamicImport(
dynamicViewsModules: Record<string, () => Promise<Recordable>>, dynamicViewsModules: Record<string, () => Promise<Recordable>>,
component: string, component: string,
) { ) {
const keys = Object.keys(dynamicViewsModules); const keys = Object.keys(dynamicViewsModules)
const matchKeys = keys.filter((key) => { const matchKeys = keys.filter((key) => {
const k = key.replace('../../views', ''); const k = key.replace('../../views', '')
const startFlag = component.startsWith('/'); const startFlag = component.startsWith('/')
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx'); const endFlag = component.endsWith('.vue') || component.endsWith('.tsx')
const startIndex = startFlag ? 0 : 1; const startIndex = startFlag ? 0 : 1
const lastIndex = endFlag ? k.length : k.lastIndexOf('.'); const lastIndex = endFlag ? k.length : k.lastIndexOf('.')
return k.substring(startIndex, lastIndex) === component; return k.substring(startIndex, lastIndex) === component
}); })
if (matchKeys?.length === 1) { if (matchKeys?.length === 1) {
const matchKey = matchKeys[0]; const matchKey = matchKeys[0]
return dynamicViewsModules[matchKey]; return dynamicViewsModules[matchKey]
} else if (matchKeys?.length > 1) { } else if (matchKeys?.length > 1) {
warn( warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure', 'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure',
); )
return; return
} else { } else {
warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!'); warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!')
return EXCEPTION_COMPONENT; return EXCEPTION_COMPONENT
} }
} }
@ -71,26 +71,26 @@ function dynamicImport(
// 将背景对象变成路由对象 // 将背景对象变成路由对象
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] { export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
routeList.forEach((route) => { routeList.forEach((route) => {
const component = route.component as string; const component = route.component as string
if (component) { if (component) {
if (component.toUpperCase() === 'LAYOUT') { if (component.toUpperCase() === 'LAYOUT') {
route.component = LayoutMap.get(component.toUpperCase()); route.component = LayoutMap.get(component.toUpperCase())
} else { } else {
route.children = [cloneDeep(route)]; route.children = [cloneDeep(route)]
route.component = LAYOUT; route.component = LAYOUT
route.name = `${route.name}Parent`; route.name = `${route.name}Parent`
route.path = ''; route.path = ''
const meta = route.meta || {}; const meta = route.meta || {}
meta.single = true; meta.single = true
meta.affix = false; meta.affix = false
route.meta = meta; route.meta = meta
} }
} else { } else {
warn('请正确配置路由:' + route?.name + '的component属性'); warn('请正确配置路由:' + route?.name + '的component属性')
} }
route.children && asyncImportRoute(route.children); route.children && asyncImportRoute(route.children)
}); })
return routeList as unknown as T[]; return routeList as unknown as T[]
} }
/** /**
@ -98,19 +98,19 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
* 2 * 2
*/ */
export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) { export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) {
const modules: AppRouteModule[] = cloneDeep(routeModules); const modules: AppRouteModule[] = cloneDeep(routeModules)
for (let index = 0; index < modules.length; index++) { for (let index = 0; index < modules.length; index++) {
const routeModule = modules[index]; const routeModule = modules[index]
// 判断级别是否 多级 路由 // 判断级别是否 多级 路由
if (!isMultipleRoute(routeModule)) { if (!isMultipleRoute(routeModule)) {
// 声明终止当前循环, 即跳过此次循环,进行下一轮 // 声明终止当前循环, 即跳过此次循环,进行下一轮
continue; continue
} }
// 路由等级提升 // 路由等级提升
promoteRouteLevel(routeModule); promoteRouteLevel(routeModule)
} }
return modules; return modules
} }
// Routing level upgrade // Routing level upgrade
@ -122,15 +122,15 @@ function promoteRouteLevel(routeModule: AppRouteModule) {
let router: Router | null = createRouter({ let router: Router | null = createRouter({
routes: [routeModule as unknown as RouteRecordNormalized], routes: [routeModule as unknown as RouteRecordNormalized],
history: createWebHashHistory(), history: createWebHashHistory(),
}); })
// getRoutes 获取所有 路由记录的完整列表。 // getRoutes 获取所有 路由记录的完整列表。
const routes = router.getRoutes(); const routes = router.getRoutes()
// 将所有子路由添加到二级路由 // 将所有子路由添加到二级路由
addToChildren(routes, routeModule.children || [], routeModule); addToChildren(routes, routeModule.children || [], routeModule)
router = null; router = null
// omit lodash的函数 对传入的item对象的children进行删除 // omit lodash的函数 对传入的item对象的children进行删除
routeModule.children = routeModule.children?.map((item) => omit(item, 'children')); routeModule.children = routeModule.children?.map((item) => omit(item, 'children'))
} }
// Add all sub-routes to the secondary route // Add all sub-routes to the secondary route
@ -141,17 +141,17 @@ function addToChildren(
routeModule: AppRouteModule, routeModule: AppRouteModule,
) { ) {
for (let index = 0; index < children.length; index++) { for (let index = 0; index < children.length; index++) {
const child = children[index]; const child = children[index]
const route = routes.find((item) => item.name === child.name); const route = routes.find((item) => item.name === child.name)
if (!route) { if (!route) {
continue; continue
} }
routeModule.children = routeModule.children || []; routeModule.children = routeModule.children || []
if (!routeModule.children.find((item) => item.name === route.name)) { if (!routeModule.children.find((item) => item.name === route.name)) {
routeModule.children?.push(route as unknown as AppRouteModule); routeModule.children?.push(route as unknown as AppRouteModule)
} }
if (child.children?.length) { if (child.children?.length) {
addToChildren(routes, child.children, routeModule); addToChildren(routes, child.children, routeModule)
} }
} }
} }
@ -161,18 +161,18 @@ function addToChildren(
function isMultipleRoute(routeModule: AppRouteModule) { function isMultipleRoute(routeModule: AppRouteModule) {
// Reflect.has 与 in 操作符 相同, 用于检查一个对象(包括它原型链上)是否拥有某个属性 // Reflect.has 与 in 操作符 相同, 用于检查一个对象(包括它原型链上)是否拥有某个属性
if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) { if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) {
return false; return false
} }
const children = routeModule.children; const children = routeModule.children
let flag = false; let flag = false
for (let index = 0; index < children.length; index++) { for (let index = 0; index < children.length; index++) {
const child = children[index]; const child = children[index]
if (child.children?.length) { if (child.children?.length) {
flag = true; flag = true
break; break
} }
} }
return flag; return flag
} }

View File

@ -1,17 +1,17 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
import type { App } from 'vue'; import type { App } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'; import { createRouter, createWebHashHistory } from 'vue-router'
import { basicRoutes } from './routes'; import { basicRoutes } from './routes'
// 白名单应该包含基本静态路由 // 白名单应该包含基本静态路由
const WHITE_NAME_LIST: string[] = []; const WHITE_NAME_LIST: string[] = []
const getRouteNames = (array: any[]) => const getRouteNames = (array: any[]) =>
array.forEach((item) => { array.forEach((item) => {
WHITE_NAME_LIST.push(item.name); WHITE_NAME_LIST.push(item.name)
getRouteNames(item.children || []); getRouteNames(item.children || [])
}); })
getRouteNames(basicRoutes); getRouteNames(basicRoutes)
// app router // app router
// 创建一个可以被 Vue 应用程序使用的路由实例 // 创建一个可以被 Vue 应用程序使用的路由实例
@ -23,20 +23,20 @@ export const router = createRouter({
// 是否应该禁止尾部斜杠。默认为假 // 是否应该禁止尾部斜杠。默认为假
strict: true, strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
}); })
// reset router // reset router
export function resetRouter() { export function resetRouter() {
router.getRoutes().forEach((route) => { router.getRoutes().forEach((route) => {
const { name } = route; const { name } = route
if (name && !WHITE_NAME_LIST.includes(name as string)) { if (name && !WHITE_NAME_LIST.includes(name as string)) {
router.hasRoute(name) && router.removeRoute(name); router.hasRoute(name) && router.removeRoute(name)
} }
}); })
} }
// config router // config router
// 配置路由器 // 配置路由器
export function setupRouter(app: App<Element>) { export function setupRouter(app: App<Element>) {
app.use(router); app.use(router)
} }

View File

@ -1,7 +1,7 @@
import type { AppRouteModule } from '/@/router/types'; import type { AppRouteModule } from '/@/router/types'
import { LAYOUT } from '/@/router/constant'; import { LAYOUT } from '/@/router/constant'
import { t } from '/@/hooks/web/useI18n'; import { t } from '/@/hooks/web/useI18n'
const about: AppRouteModule = { const about: AppRouteModule = {
path: '/about', path: '/about',
@ -26,6 +26,6 @@ const about: AppRouteModule = {
}, },
}, },
], ],
}; }
export default about; export default about

View File

@ -1,7 +1,7 @@
import type { AppRouteModule } from '/@/router/types'; import type { AppRouteModule } from '/@/router/types'
import { LAYOUT } from '/@/router/constant'; import { LAYOUT } from '/@/router/constant'
import { t } from '/@/hooks/web/useI18n'; import { t } from '/@/hooks/web/useI18n'
const dashboard: AppRouteModule = { const dashboard: AppRouteModule = {
path: '/dashboard', path: '/dashboard',
@ -32,6 +32,6 @@ const dashboard: AppRouteModule = {
}, },
}, },
], ],
}; }
export default dashboard; export default dashboard

View File

@ -33,23 +33,23 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { ErrorLogInfo } from '/#/store'; import type { ErrorLogInfo } from '/#/store'
import { watch, ref, nextTick } from 'vue'; import { watch, ref, nextTick } from 'vue'
import DetailModal from './DetailModal.vue'; import DetailModal from './DetailModal.vue'
import { BasicTable, useTable, TableAction } from '/@/components/Table/index'; import { BasicTable, useTable, TableAction } from '/@/components/Table/index'
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal'
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useErrorLogStore } from '/@/store/modules/errorLog'; import { useErrorLogStore } from '/@/store/modules/errorLog'
import { fireErrorApi } from '/@/api/demo/error'; import { fireErrorApi } from '/@/api/demo/error'
import { getColumns } from './data'; import { getColumns } from './data'
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es'
const rowInfo = ref<ErrorLogInfo>(); const rowInfo = ref<ErrorLogInfo>()
const imgList = ref<string[]>([]); const imgList = ref<string[]>([])
const { t } = useI18n(); const { t } = useI18n()
const errorLogStore = useErrorLogStore(); const errorLogStore = useErrorLogStore()
const [register, { setTableData }] = useTable({ const [register, { setTableData }] = useTable({
title: t('sys.errorLog.tableTitle'), title: t('sys.errorLog.tableTitle'),
columns: getColumns(), columns: getColumns(),
@ -59,39 +59,39 @@
dataIndex: 'action', dataIndex: 'action',
// slots: { customRender: 'action' }, // slots: { customRender: 'action' },
}, },
}); })
const [registerModal, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal()
watch( watch(
() => errorLogStore.getErrorLogInfoList, () => errorLogStore.getErrorLogInfoList,
(list) => { (list) => {
nextTick(() => { nextTick(() => {
setTableData(cloneDeep(list)); setTableData(cloneDeep(list))
}); })
}, },
{ {
immediate: true, immediate: true,
}, },
); )
const { createMessage } = useMessage(); const { createMessage } = useMessage()
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
createMessage.info(t('sys.errorLog.enableMessage')); createMessage.info(t('sys.errorLog.enableMessage'))
} }
// //
function handleDetail(row: ErrorLogInfo) { function handleDetail(row: ErrorLogInfo) {
rowInfo.value = row; rowInfo.value = row
openModal(true); openModal(true)
} }
function fireVueError() { function fireVueError() {
throw new Error('fire vue error!'); throw new Error('fire vue error!')
} }
function fireResourceError() { function fireResourceError() {
imgList.value.push(`${new Date().getTime()}.png`); imgList.value.push(`${new Date().getTime()}.png`)
} }
async function fireAjaxError() { async function fireAjaxError() {
await fireErrorApi(); await fireErrorApi()
} }
</script> </script>

View File

@ -1,9 +1,9 @@
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil'
import { reactive, toRefs } from 'vue'; import { reactive, toRefs } from 'vue'
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
export function useNow(immediate = true) { export function useNow(immediate = true) {
let timer: IntervalHandle; let timer: IntervalHandle
const state = reactive({ const state = reactive({
year: 0, year: 0,
@ -14,47 +14,47 @@ export function useNow(immediate = true) {
minute: '', minute: '',
second: 0, second: 0,
meridiem: '', meridiem: '',
}); })
const update = () => { const update = () => {
const now = dateUtil(); const now = dateUtil()
const h = now.format('HH'); const h = now.format('HH')
const m = now.format('mm'); const m = now.format('mm')
const s = now.get('s'); const s = now.get('s')
state.year = now.get('y'); state.year = now.get('y')
state.month = now.get('M') + 1; state.month = now.get('M') + 1
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]; state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
state.day = now.get('date'); state.day = now.get('date')
state.hour = h; state.hour = h
state.minute = m; state.minute = m
state.second = s; state.second = s
state.meridiem = now.format('A'); state.meridiem = now.format('A')
}; }
function start() { function start() {
update(); update()
clearInterval(timer); clearInterval(timer)
timer = setInterval(() => update(), 1000); timer = setInterval(() => update(), 1000)
} }
function stop() { function stop() {
clearInterval(timer); clearInterval(timer)
} }
tryOnMounted(() => { tryOnMounted(() => {
immediate && start(); immediate && start()
}); })
tryOnUnmounted(() => { tryOnUnmounted(() => {
stop(); stop()
}); })
return { return {
...toRefs(state), ...toRefs(state),
start, start,
stop, stop,
}; }
} }

View File

@ -48,31 +48,31 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue'
import { AppLogo } from '/@/components/Application'; import { AppLogo } from '/@/components/Application'
import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application'; import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application'
import LoginForm from './LoginForm.vue'; import LoginForm from './LoginForm.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue'; import ForgetPasswordForm from './ForgetPasswordForm.vue'
import RegisterForm from './RegisterForm.vue'; import RegisterForm from './RegisterForm.vue'
import MobileForm from './MobileForm.vue'; import MobileForm from './MobileForm.vue'
import QrCodeForm from './QrCodeForm.vue'; import QrCodeForm from './QrCodeForm.vue'
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting'
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n'
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign'
import { useLocaleStore } from '/@/store/modules/locale'; import { useLocaleStore } from '/@/store/modules/locale'
defineProps({ defineProps({
sessionTimeout: { sessionTimeout: {
type: Boolean, type: Boolean,
}, },
}); })
const globSetting = useGlobSetting(); const globSetting = useGlobSetting()
const { prefixCls } = useDesign('login'); const { prefixCls } = useDesign('login')
const { t } = useI18n(); const { t } = useI18n()
const localeStore = useLocaleStore(); const localeStore = useLocaleStore()
const showLocale = localeStore.getShowPicker; const showLocale = localeStore.getShowPicker
const title = computed(() => globSetting?.title ?? ''); const title = computed(() => globSetting?.title ?? '')
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-login'; @prefix-cls: ~'@{namespace}-login';

View File

@ -97,4 +97,4 @@ module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'], extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'],
}, },
], ],
}; }

View File

@ -1,5 +1,5 @@
import { defineConfig } from 'vite-plugin-windicss'; import { defineConfig } from 'vite-plugin-windicss'
import { primaryColor } from './build/config/themeConfig'; import { primaryColor } from './build/config/themeConfig'
export default defineConfig({ export default defineConfig({
darkMode: 'class', darkMode: 'class',
@ -21,7 +21,7 @@ export default defineConfig({
}, },
}, },
}, },
}); })
/** /**
* Used for animation when the element is displayed. * Used for animation when the element is displayed.
@ -29,7 +29,7 @@ export default defineConfig({
*/ */
function createEnterPlugin(maxOutput = 6) { function createEnterPlugin(maxOutput = 6) {
const createCss = (index: number, d = 'x') => { const createCss = (index: number, d = 'x') => {
const upd = d.toUpperCase(); const upd = d.toUpperCase()
return { return {
[`*> .enter-${d}:nth-child(${index})`]: { [`*> .enter-${d}:nth-child(${index})`]: {
transform: `translate${upd}(50px)`, transform: `translate${upd}(50px)`,
@ -44,15 +44,15 @@ function createEnterPlugin(maxOutput = 6) {
'animation-fill-mode': 'forwards', 'animation-fill-mode': 'forwards',
'animation-delay': `${(index * 1) / 10}s`, 'animation-delay': `${(index * 1) / 10}s`,
}, },
}; }
}; }
const handler = ({ addBase }) => { const handler = ({ addBase }) => {
const addRawCss = {}; const addRawCss = {}
for (let index = 1; index < maxOutput; index++) { for (let index = 1; index < maxOutput; index++) {
Object.assign(addRawCss, { Object.assign(addRawCss, {
...createCss(index, 'x'), ...createCss(index, 'x'),
...createCss(index, 'y'), ...createCss(index, 'y'),
}); })
} }
addBase({ addBase({
...addRawCss, ...addRawCss,
@ -68,7 +68,7 @@ function createEnterPlugin(maxOutput = 6) {
transform: 'translateY(0)', transform: 'translateY(0)',
}, },
}, },
}); })
}; }
return { handler }; return { handler }
} }