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',
},
};
}

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 {
mixLighten: Fn;
mixDarken: Fn;
tinycolor: any;
color?: string;
mixLighten: Fn
mixDarken: Fn
tinycolor: any
color?: string
}
export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
return generate(color, {
theme,
});
})
}
export function getThemeColors(color?: string) {
const tc = color || primaryColor;
const lightColors = generateAntColors(tc);
const primary = lightColors[5];
const modeColors = generateAntColors(primary, 'dark');
const tc = color || primaryColor
const lightColors = generateAntColors(tc)
const primary = lightColors[5]
const modeColors = generateAntColors(primary, 'dark')
return [...lightColors, ...modeColors];
return [...lightColors, ...modeColors]
}
export function generateColors({
@ -36,38 +36,38 @@ export function generateColors({
mixDarken,
tinycolor,
}: GenerateColorsParams) {
const arr = new Array(19).fill(0);
const arr = new Array(19).fill(0)
const lightens = arr.map((_t, i) => {
return mixLighten(color, i / 5);
});
return mixLighten(color, i / 5)
})
const darkens = arr.map((_t, i) => {
return mixDarken(color, i / 5);
});
return mixDarken(color, i / 5)
})
const alphaColors = arr.map((_t, i) => {
return tinycolor(color)
.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
.map((_t, i) => {
return tinycolor(color)
.lighten(i * 5)
.toHexString();
.toHexString()
})
.filter((item) => item !== '#ffffff');
.filter((item) => item !== '#ffffff')
const tinycolorDarkens = arr
.map((_t, i) => {
return tinycolor(color)
.darken(i * 5)
.toHexString();
.toHexString()
})
.filter((item) => item !== '#000000');
.filter((item) => item !== '#000000')
return [
...lightens,
...darkens,
@ -75,5 +75,5 @@ export function generateColors({
...shortAlphaColors,
...tinycolorDarkens,
...tinycolorLightens,
].filter((item) => !item.includes('-'));
].filter((item) => !item.includes('-'))
}

View File

@ -1,20 +1,20 @@
import path from 'path';
import fs from 'fs-extra';
import inquirer from 'inquirer';
import colors from 'picocolors';
import pkg from '../../../package.json';
import path from 'path'
import fs from 'fs-extra'
import inquirer from 'inquirer'
import colors from 'picocolors'
import pkg from '../../../package.json'
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]) => ({
...(v as any),
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
.prompt([
@ -41,32 +41,32 @@ async function generateIcon() {
},
])
.then(async (answers) => {
const { iconSet, output, useType } = answers;
const outputDir = path.resolve(process.cwd(), output);
fs.ensureDir(outputDir);
const genCollections = collections.filter((item) => [iconSet].includes(item.id));
const prefixSet: string[] = [];
const { iconSet, output, useType } = answers
const outputDir = path.resolve(process.cwd(), output)
fs.ensureDir(outputDir)
const genCollections = collections.filter((item) => [iconSet].includes(item.id))
const prefixSet: string[] = []
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) {
const { prefix } = data;
const isLocal = useType === 'local';
const { prefix } = data
const isLocal = useType === 'local'
const icons = Object.keys(data.icons).map(
(item) => `${isLocal ? prefix + ':' : ''}${item}`,
);
)
await fs.writeFileSync(
path.join(output, `icons.data.ts`),
`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(
`${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
*/
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra';
import colors from 'picocolors';
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'
import fs, { writeFileSync } from 'fs-extra'
import colors from 'picocolors'
import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName';
import { getEnvConfig, getRootPath } from '../utils'
import { getConfigFileName } from '../getConfigFileName'
import pkg from '../../package.json';
import pkg from '../../package.json'
interface CreateConfigParams {
configName: string;
config: any;
configFileName?: string;
configName: string
config: any
configFileName?: string
}
function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params;
const { configName, config, configFileName } = params
try {
const windowConf = `window.${configName}`;
const windowConf = `window.${configName}`
// Ensure that the variable will not be modified
let configStr = `${windowConf}=${JSON.stringify(config)};`;
let configStr = `${windowConf}=${JSON.stringify(config)};`
configStr += `
Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '');
`.replace(/\s/g, '')
fs.mkdirp(getRootPath(OUTPUT_DIR));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
fs.mkdirp(getRootPath(OUTPUT_DIR))
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`)
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n')
} catch (error) {
console.log(colors.red('configuration file configuration file failed to package:\n' + error));
console.log(colors.red('configuration file configuration file failed to package:\n' + error))
}
}
export function runBuildConfig() {
const config = getEnvConfig();
const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
const config = getEnvConfig()
const configFileName = getConfigFileName(config)
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME })
}

View File

@ -1,23 +1,23 @@
// #!/usr/bin/env node
import { runBuildConfig } from './buildConf';
import colors from 'picocolors';
import { runBuildConfig } from './buildConf'
import colors from 'picocolors'
import pkg from '../../package.json';
import pkg from '../../package.json'
export const runBuild = async () => {
try {
const argvList = process.argv.splice(2);
const argvList = process.argv.splice(2)
// Generate configuration file
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) {
console.log(colors.red('vite build error:\n' + error));
process.exit(1);
console.log(colors.red('vite build error:\n' + error))
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
* https://github.com/anncwb/vite-plugin-compression
*/
import type { PluginOption } from 'vite';
import compressPlugin from 'vite-plugin-compression';
import type { PluginOption } from 'vite'
import compressPlugin from 'vite-plugin-compression'
export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false,
): PluginOption | PluginOption[] {
const compressList = compress.split(',');
const compressList = compress.split(',')
const plugins: PluginOption[] = [];
const plugins: PluginOption[] = []
if (compressList.includes('gzip')) {
plugins.push(
@ -19,7 +19,7 @@ export function configCompressPlugin(
ext: '.gz',
deleteOriginFile,
}),
);
)
}
if (compressList.includes('brotli')) {
@ -29,7 +29,7 @@ export function configCompressPlugin(
algorithm: 'brotliCompress',
deleteOriginFile,
}),
);
)
}
return plugins;
return plugins
}

View File

@ -2,19 +2,19 @@
* Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html
*/
import type { PluginOption } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
import type { PluginOption } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'
import pkg from '../../../package.json'
import { GLOB_CONFIG_FILE_NAME } from '../../constant'
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 = () => {
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({
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
*/
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export function configSvgIconsPlugin(isBuild: boolean) {
const svgIconsPlugin = createSvgIconsPlugin({
@ -12,6 +12,6 @@ export function configSvgIconsPlugin(isBuild: boolean) {
svgoOptions: isBuild,
// default
symbolId: 'icon-[dir]-[name]',
});
return svgIconsPlugin;
})
return svgIconsPlugin
}

View File

@ -2,46 +2,46 @@
* Vite plugin for website theme color switching
* https://github.com/anncwb/vite-plugin-theme
*/
import type { PluginOption } from 'vite';
import path from 'path';
import type { PluginOption } from 'vite'
import path from 'path'
import {
viteThemePlugin,
antdDarkThemePlugin,
mixLighten,
mixDarken,
tinycolor,
} from 'vite-plugin-theme';
import { getThemeColors, generateColors } from '../../config/themeConfig';
import { generateModifyVars } from '../../generate/generateModifyVars';
} from 'vite-plugin-theme'
import { getThemeColors, generateColors } from '../../config/themeConfig'
import { generateModifyVars } from '../../generate/generateModifyVars'
export function configThemePlugin(isBuild: boolean): PluginOption[] {
const colors = generateColors({
mixDarken,
mixLighten,
tinycolor,
});
})
const plugin = [
viteThemePlugin({
resolveSelector: (s) => {
s = s.trim();
s = s.trim()
switch (s) {
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):hover':
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':
return s;
return s
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s;
return s
default:
if (s.indexOf('.ant-btn') >= -1) {
// 按钮被重新定制过需要过滤掉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],
}),
@ -83,7 +83,7 @@ export function configThemePlugin(isBuild: boolean): PluginOption[] {
'alert-error-icon-color': '#a61d24',
},
}),
];
]
return plugin as unknown as PluginOption[];
return plugin as unknown as PluginOption[]
}

View File

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

View File

@ -23,17 +23,17 @@
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue';
import { ref } from 'vue';
import { isNil } from 'lodash-es';
import type { PropType } from 'vue'
import { ref } from 'vue'
import { isNil } from 'lodash-es'
// component
import { Skeleton } from 'ant-design-vue';
import { CollapseTransition } from '/@/components/Transition';
import CollapseHeader from './CollapseHeader.vue';
import { triggerWindowResize } from '/@/utils/event';
import { Skeleton } from 'ant-design-vue'
import { CollapseTransition } from '/@/components/Transition'
import CollapseHeader from './CollapseHeader.vue'
import { triggerWindowResize } from '/@/utils/event'
// hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useDesign } from '/@/hooks/web/useDesign';
import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { useDesign } from '/@/hooks/web/useDesign'
const props = defineProps({
title: { type: String, default: '' },
@ -58,26 +58,26 @@
* Delayed loading time
*/
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
*/
function handleExpand(val: boolean) {
show.value = isNil(val) ? !show.value : val;
show.value = isNil(val) ? !show.value : val
if (props.triggerWindowResize) {
// 200 milliseconds here is because the expansion has animation,
useTimeoutFn(triggerWindowResize, 200);
useTimeoutFn(triggerWindowResize, 200)
}
}
defineExpose({
handleExpand,
});
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container';

View File

@ -1,11 +1,11 @@
<script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
import type { FunctionalComponent, CSSProperties, PropType } from 'vue';
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue';
import type { ContextMenuItem, ItemContentProps, Axis } from './typing'
import type { FunctionalComponent, CSSProperties, PropType } from 'vue'
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'
import Icon from '/@/components/Icon'
import { Menu, Divider } from 'ant-design-vue'
const prefixCls = 'context-menu';
const prefixCls = 'context-menu'
const props = {
width: { type: Number, default: 156 },
@ -16,20 +16,20 @@
// The position of the right mouse button click
type: Object as PropType<Axis>,
default() {
return { x: 0, y: 0 };
return { x: 0, y: 0 }
},
},
items: {
// The most important list, if not, will not be displayed
type: Array as PropType<ContextMenuItem[]>,
default() {
return [];
return []
},
},
};
}
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props;
const { item } = props
return (
<span
style="display: inline-block; width: 100%; "
@ -39,25 +39,25 @@
{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
<span>{item.label}</span>
</span>
);
};
)
}
export default defineComponent({
name: 'ContextMenu',
props,
setup(props) {
const wrapRef = ref(null);
const showRef = ref(false);
const wrapRef = ref(null)
const showRef = ref(false)
const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props;
const { x, y } = axis || { x: 0, y: 0 };
const menuHeight = (items || []).length * 40;
const menuWidth = width;
const body = document.body;
const { axis, items, styles, width } = props
const { x, y } = axis || { x: 0, y: 0 }
const menuHeight = (items || []).length * 40
const menuWidth = width
const body = document.body
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y
return {
...styles,
position: 'absolute',
@ -65,39 +65,39 @@
left: `${left + 1}px`,
top: `${top + 1}px`,
zIndex: 9999,
};
});
}
})
onMounted(() => {
nextTick(() => (showRef.value = true));
});
nextTick(() => (showRef.value = true))
})
onUnmounted(() => {
const el = unref(wrapRef);
el && document.body.removeChild(el);
});
const el = unref(wrapRef)
el && document.body.removeChild(el)
})
function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item;
const { handler, disabled } = item
if (disabled) {
return;
return
}
showRef.value = false;
e?.stopPropagation();
e?.preventDefault();
handler?.();
showRef.value = false
e?.stopPropagation()
e?.preventDefault()
handler?.()
}
function renderMenuItem(items: ContextMenuItem[]) {
const visibleItems = items.filter((item) => !item.hidden);
const visibleItems = items.filter((item) => !item.hidden)
return visibleItems.map((item) => {
const { disabled, label, children, divider = false } = item;
const { disabled, label, children, divider = false } = item
const contentProps = {
item,
handler: handleAction,
showIcon: props.showIcon,
};
}
if (!children || children.length === 0) {
return (
@ -107,9 +107,9 @@
</Menu.Item>
{divider ? <Divider key={`d-${label}`} /> : null}
</>
);
)
}
if (!unref(showRef)) return null;
if (!unref(showRef)) return null
return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
@ -118,24 +118,24 @@
default: () => renderMenuItem(children),
}}
</Menu.SubMenu>
);
});
)
})
}
return () => {
if (!unref(showRef)) {
return null;
return null
}
const { items } = props;
const { items } = props
return (
<div class={prefixCls}>
<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
{renderMenuItem(items)}
</Menu>
</div>
);
};
)
}
},
});
})
</script>
<style lang="less">
@default-height: 42px !important;

View File

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

View File

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

View File

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

View File

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

View File

@ -37,32 +37,32 @@
</Form>
</template>
<script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form';
import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue';
import type { FormActionType, FormProps, FormSchema } from './types/form'
import type { AdvanceState } from './types/hooks'
import type { Ref } from 'vue'
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
import { Form, Row } from 'ant-design-vue';
import FormItem from './components/FormItem.vue';
import FormAction from './components/FormAction.vue';
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue'
import { Form, Row } from 'ant-design-vue'
import FormItem from './components/FormItem.vue'
import FormAction from './components/FormAction.vue'
import { dateItemType } from './helper';
import { dateUtil } from '/@/utils/dateUtil';
import { dateItemType } from './helper'
import { dateUtil } from '/@/utils/dateUtil'
// import { cloneDeep } from 'lodash-es';
import { deepMerge } from '/@/utils';
import { deepMerge } from '/@/utils'
import { useFormValues } from './hooks/useFormValues';
import useAdvanced from './hooks/useAdvanced';
import { useFormEvents } from './hooks/useFormEvents';
import { createFormContext } from './hooks/useFormContext';
import { useAutoFocus } from './hooks/useAutoFocus';
import { useModalContext } from '/@/components/Modal';
import { useDebounceFn } from '@vueuse/core';
import { useFormValues } from './hooks/useFormValues'
import useAdvanced from './hooks/useAdvanced'
import { useFormEvents } from './hooks/useFormEvents'
import { createFormContext } from './hooks/useFormContext'
import { useAutoFocus } from './hooks/useAutoFocus'
import { useModalContext } from '/@/components/Modal'
import { useDebounceFn } from '@vueuse/core'
import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign';
import { cloneDeep } from 'lodash-es';
import { basicProps } from './props'
import { useDesign } from '/@/hooks/web/useDesign'
import { cloneDeep } from 'lodash-es'
export default defineComponent({
name: 'BasicForm',
@ -70,28 +70,28 @@
props: basicProps,
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({});
const modalFn = useModalContext();
const formModel = reactive<Recordable>({})
const modalFn = useModalContext()
const advanceState = reactive<AdvanceState>({
isAdvanced: true,
hideAdvanceBtn: false,
isLoad: false,
actionSpan: 6,
});
})
const defaultValueRef = ref<Recordable>({});
const isInitedDefaultRef = ref(false);
const propsRef = ref<Partial<FormProps>>({});
const schemaRef = ref<Nullable<FormSchema[]>>(null);
const formElRef = ref<Nullable<FormActionType>>(null);
const defaultValueRef = ref<Recordable>({})
const isInitedDefaultRef = ref(false)
const propsRef = ref<Partial<FormProps>>({})
const schemaRef = ref<Nullable<FormSchema[]>>(null)
const formElRef = ref<Nullable<FormActionType>>(null)
const { prefixCls } = useDesign('basic-form');
const { prefixCls } = useDesign('basic-form')
// Get the basic configuration of the form
const getProps = computed((): FormProps => {
return { ...props, ...unref(propsRef) } as FormProps;
});
return { ...props, ...unref(propsRef) } as FormProps
})
const getFormClass = computed(() => {
return [
@ -99,47 +99,47 @@
{
[`${prefixCls}--compact`]: unref(getProps).compact,
},
];
});
]
})
// Get uniform row style and Row configuration for the entire form
const getRow = computed((): Recordable => {
const { baseRowStyle = {}, rowProps } = unref(getProps);
const { baseRowStyle = {}, rowProps } = unref(getProps)
return {
style: baseRowStyle,
...rowProps,
};
});
}
})
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
);
)
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) {
const { defaultValue, component } = schema;
const { defaultValue, component } = schema
// handle date type
if (defaultValue && dateItemType.includes(component)) {
if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue);
schema.defaultValue = dateUtil(defaultValue)
} else {
const def: any[] = [];
const def: any[] = []
defaultValue.forEach((item) => {
def.push(dateUtil(item));
});
schema.defaultValue = def;
def.push(dateUtil(item))
})
schema.defaultValue = def
}
}
}
if (unref(getProps).showAdvancedButton) {
return cloneDeep(
schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
);
)
} else {
return cloneDeep(schemas as FormSchema[]);
return cloneDeep(schemas as FormSchema[])
}
});
})
const { handleToggleAdvanced } = useAdvanced({
advanceState,
@ -148,21 +148,21 @@
getSchema,
formModel,
defaultValueRef,
});
})
const { handleFormValues, initDefault } = useFormValues({
getProps,
defaultValueRef,
getSchema,
formModel,
});
})
useAutoFocus({
getSchema,
getProps,
isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>,
});
})
const {
handleSubmit,
@ -186,77 +186,77 @@
formElRef: formElRef as Ref<FormActionType>,
schemaRef: schemaRef as Ref<FormSchema[]>,
handleFormValues,
});
})
createFormContext({
resetAction: resetFields,
submitAction: handleSubmit,
});
})
watch(
() => unref(getProps).model,
() => {
const { model } = unref(getProps);
if (!model) return;
setFieldsValue(model);
const { model } = unref(getProps)
if (!model) return
setFieldsValue(model)
},
{
immediate: true,
},
);
)
watch(
() => unref(getProps).schemas,
(schemas) => {
resetSchema(schemas ?? []);
resetSchema(schemas ?? [])
},
);
)
watch(
() => getSchema.value,
(schema) => {
nextTick(() => {
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.();
});
modalFn?.redoModalHeight?.()
})
if (unref(isInitedDefaultRef)) {
return;
return
}
if (schema?.length) {
initDefault();
isInitedDefaultRef.value = true;
initDefault()
isInitedDefaultRef.value = true
}
},
);
)
watch(
() => formModel,
useDebounceFn(() => {
unref(getProps).submitOnChange && handleSubmit();
unref(getProps).submitOnChange && handleSubmit()
}, 300),
{ deep: true },
);
)
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) {
formModel[key] = value;
const { validateTrigger } = unref(getBindValue);
formModel[key] = value
const { validateTrigger } = unref(getBindValue)
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) {
const { autoSubmitOnEnter } = unref(getProps);
if (!autoSubmitOnEnter) return;
const { autoSubmitOnEnter } = unref(getProps)
if (!autoSubmitOnEnter) return
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') {
handleSubmit();
handleSubmit()
}
}
}
@ -275,12 +275,12 @@
validate,
submit: handleSubmit,
scrollToField: scrollToField,
};
}
onMounted(() => {
initDefault();
emit('register', formActionType);
});
initDefault()
emit('register', formActionType)
})
return {
getBindValue,
@ -300,9 +300,9 @@
(): Recordable => ({ ...getProps.value, ...advanceState }),
),
...formActionType,
};
}
},
});
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-form';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,20 @@
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { ComponentType } from './types/index';
import { useI18n } from '/@/hooks/web/useI18n';
import { dateUtil } from '/@/utils/dateUtil';
import { isNumber, isObject } from '/@/utils/is';
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'
import type { ComponentType } from './types/index'
import { useI18n } from '/@/hooks/web/useI18n'
import { dateUtil } from '/@/utils/dateUtil'
import { isNumber, isObject } from '/@/utils/is'
const { t } = useI18n();
const { t } = useI18n()
/**
* @description: placeholder
*/
export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input') || component.includes('Complete')) {
return t('common.inputText');
return t('common.inputText')
}
if (component.includes('Picker')) {
return t('common.chooseText');
return t('common.chooseText')
}
if (
component.includes('Select') ||
@ -24,15 +24,15 @@ export function createPlaceholderMessage(component: ComponentType) {
component.includes('Switch')
) {
// 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() {
return [...DATE_TYPE, 'RangePicker'];
return [...DATE_TYPE, 'RangePicker']
}
export function setComponentRuleType(
@ -41,34 +41,34 @@ export function setComponentRuleType(
valueFormat: string,
) {
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)) {
rule.type = 'array';
rule.type = 'array'
} else if (['InputNumber'].includes(component)) {
rule.type = 'number';
rule.type = 'number'
}
}
export function processDateValue(attr: Recordable, component: string) {
const { valueFormat, value } = attr;
const { valueFormat, value } = attr
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) {
attr.value = dateUtil(attr.value);
attr.value = dateUtil(attr.value)
}
}
export function handleInputNumberValue(component?: ComponentType, val?: any) {
if (!component) return val;
if (!component) return val
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 { AdvanceState } from '../types/hooks';
import { ComputedRef, getCurrentInstance, Ref } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import { computed, unref, watch } from 'vue';
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
import { useDebounceFn } from '@vueuse/core';
import type { ColEx } from '../types'
import type { AdvanceState } from '../types/hooks'
import { ComputedRef, getCurrentInstance, Ref } from 'vue'
import type { FormProps, FormSchema } from '../types/form'
import { computed, unref, watch } from 'vue'
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'
import { useDebounceFn } from '@vueuse/core'
const BASIC_COL_LEN = 24;
const BASIC_COL_LEN = 24
interface UseAdvancedContext {
advanceState: AdvanceState;
emit: EmitType;
getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]>;
formModel: Recordable;
defaultValueRef: Ref<Recordable>;
advanceState: AdvanceState
emit: EmitType
getProps: ComputedRef<FormProps>
getSchema: ComputedRef<FormSchema[]>
formModel: Recordable
defaultValueRef: Ref<Recordable>
}
export default function ({
@ -26,104 +26,104 @@ export default function ({
formModel,
defaultValueRef,
}: UseAdvancedContext) {
const vm = getCurrentInstance();
const vm = getCurrentInstance()
const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
const { realWidthRef, screenEnum, screenRef } = useBreakpoint()
const getEmptySpan = computed((): number => {
if (!advanceState.isAdvanced) {
return 0;
return 0
}
// 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)) {
return emptySpan;
return emptySpan
}
if (isObject(emptySpan)) {
const { span = 0 } = emptySpan;
const screen = unref(screenRef) as string;
const { span = 0 } = emptySpan
const screen = unref(screenRef) as string
const screenSpan = (emptySpan as any)[screen.toLowerCase()];
return screenSpan || span || 0;
const screenSpan = (emptySpan as any)[screen.toLowerCase()]
return screenSpan || span || 0
}
return 0;
});
return 0
})
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30);
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30)
watch(
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
() => {
const { showAdvancedButton } = unref(getProps);
const { showAdvancedButton } = unref(getProps)
if (showAdvancedButton) {
debounceUpdateAdvanced();
debounceUpdateAdvanced()
}
},
{ immediate: true },
);
)
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
const width = unref(realWidthRef);
const width = unref(realWidthRef)
const mdWidth =
parseInt(itemCol.md as string) ||
parseInt(itemCol.xs as string) ||
parseInt(itemCol.sm as string) ||
(itemCol.span as number) ||
BASIC_COL_LEN;
BASIC_COL_LEN
const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
const lgWidth = parseInt(itemCol.lg as string) || mdWidth
const xlWidth = parseInt(itemCol.xl as string) || lgWidth
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth
if (width <= screenEnum.LG) {
itemColSum += mdWidth;
itemColSum += mdWidth
} else if (width < screenEnum.XL) {
itemColSum += lgWidth;
itemColSum += lgWidth
} else if (width < screenEnum.XXL) {
itemColSum += xlWidth;
itemColSum += xlWidth
} else {
itemColSum += xxlWidth;
itemColSum += xxlWidth
}
if (isLastAction) {
advanceState.hideAdvanceBtn = false;
advanceState.hideAdvanceBtn = false
if (itemColSum <= BASIC_COL_LEN * 2) {
// When less than or equal to 2 lines, the collapse and expand buttons are not displayed
advanceState.hideAdvanceBtn = true;
advanceState.isAdvanced = true;
advanceState.hideAdvanceBtn = true
advanceState.isAdvanced = true
} else if (
itemColSum > BASIC_COL_LEN * 2 &&
itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
) {
advanceState.hideAdvanceBtn = false;
advanceState.hideAdvanceBtn = false
// More than 3 lines collapsed by default
} else if (!advanceState.isLoad) {
advanceState.isLoad = true;
advanceState.isAdvanced = !advanceState.isAdvanced;
advanceState.isLoad = true
advanceState.isAdvanced = !advanceState.isAdvanced
}
return { isAdvanced: advanceState.isAdvanced, itemColSum };
return { isAdvanced: advanceState.isAdvanced, itemColSum }
}
if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) {
return { isAdvanced: advanceState.isAdvanced, itemColSum };
return { isAdvanced: advanceState.isAdvanced, itemColSum }
} else {
// The first line is always displayed
return { isAdvanced: true, itemColSum };
return { isAdvanced: true, itemColSum }
}
}
function updateAdvanced() {
let itemColSum = 0;
let realItemColSum = 0;
const { baseColProps = {} } = unref(getProps);
let itemColSum = 0
let realItemColSum = 0
const { baseColProps = {} } = unref(getProps)
for (const schema of unref(getSchema)) {
const { show, colProps } = schema;
let isShow = true;
const { show, colProps } = schema
let isShow = true
if (isBoolean(show)) {
isShow = show;
isShow = show
}
if (isFunction(show)) {
@ -135,36 +135,36 @@ export default function ({
...unref(defaultValueRef),
...formModel,
},
});
})
}
if (isShow && (colProps || baseColProps)) {
const { itemColSum: sum, isAdvanced } = getAdvanced(
{ ...baseColProps, ...colProps },
itemColSum,
);
)
itemColSum = sum || 0;
itemColSum = sum || 0
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() {
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 { NamePath } from 'ant-design-vue/lib/form/interface';
import type { DynamicProps } from '/#/utils';
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import { getDynamicProps } from '/@/utils';
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'
import type { NamePath } from 'ant-design-vue/lib/form/interface'
import type { DynamicProps } from '/#/utils'
import { ref, onUnmounted, unref, nextTick, watch } from 'vue'
import { isProdMode } from '/@/utils/env'
import { error } from '/@/utils/log'
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 {
const formRef = ref<Nullable<FormActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false);
const formRef = ref<Nullable<FormActionType>>(null)
const loadedRef = ref<Nullable<boolean>>(false)
async function getForm() {
const form = unref(formRef);
const form = unref(formRef)
if (!form) {
error(
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
);
)
}
await nextTick();
return form as FormActionType;
await nextTick()
return form as FormActionType
}
function register(instance: FormActionType) {
isProdMode() &&
onUnmounted(() => {
formRef.value = null;
loadedRef.value = null;
});
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
formRef.value = null
loadedRef.value = null
})
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return
formRef.value = instance;
loadedRef.value = true;
formRef.value = instance
loadedRef.value = true
watch(
() => props,
() => {
props && instance.setProps(getDynamicProps(props));
props && instance.setProps(getDynamicProps(props))
},
{
immediate: true,
deep: true,
},
);
)
}
const methods: FormActionType = {
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
const form = await getForm();
form.scrollToField(name, options);
const form = await getForm()
form.scrollToField(name, options)
},
setProps: async (formProps: Partial<FormProps>) => {
const form = await getForm();
form.setProps(formProps);
const form = await getForm()
form.setProps(formProps)
},
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm();
form.updateSchema(data);
const form = await getForm()
form.updateSchema(data)
},
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
const form = await getForm();
form.resetSchema(data);
const form = await getForm()
form.resetSchema(data)
},
clearValidate: async (name?: string | string[]) => {
const form = await getForm();
form.clearValidate(name);
const form = await getForm()
form.clearValidate(name)
},
resetFields: async () => {
getForm().then(async (form) => {
await form.resetFields();
});
await form.resetFields()
})
},
removeSchemaByField: async (field: string | string[]) => {
unref(formRef)?.removeSchemaByField(field);
unref(formRef)?.removeSchemaByField(field)
},
// TODO promisify
getFieldsValue: <T>() => {
return unref(formRef)?.getFieldsValue() as T;
return unref(formRef)?.getFieldsValue() as T
},
setFieldsValue: async <T>(values: T) => {
const form = await getForm();
form.setFieldsValue<T>(values);
const form = await getForm()
form.setFieldsValue<T>(values)
},
appendSchemaByField: async (
@ -98,25 +98,25 @@ export function useForm(props?: Props): UseFormReturnType {
prefixField: string | undefined,
first: boolean,
) => {
const form = await getForm();
form.appendSchemaByField(schema, prefixField, first);
const form = await getForm()
form.appendSchemaByField(schema, prefixField, first)
},
submit: async (): Promise<any> => {
const form = await getForm();
return form.submit();
const form = await getForm()
return form.submit()
},
validate: async (nameList?: NamePath[]): Promise<Recordable> => {
const form = await getForm();
return form.validate(nameList);
const form = await getForm()
return form.validate(nameList)
},
validateFields: async (nameList?: NamePath[]): Promise<Recordable> => {
const form = await getForm();
return form.validateFields(nameList);
const form = await getForm()
return form.validateFields(nameList)
},
};
}
return [register, methods];
return [register, methods]
}

View File

@ -1,23 +1,23 @@
import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw, nextTick } from 'vue';
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
import { dateUtil } from '/@/utils/dateUtil';
import { cloneDeep, uniqBy } from 'lodash-es';
import { error } from '/@/utils/log';
import type { ComputedRef, Ref } from 'vue'
import type { FormProps, FormSchema, FormActionType } from '../types/form'
import type { NamePath } from 'ant-design-vue/lib/form/interface'
import { unref, toRaw, nextTick } from 'vue'
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is'
import { deepMerge } from '/@/utils'
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper'
import { dateUtil } from '/@/utils/dateUtil'
import { cloneDeep, uniqBy } from 'lodash-es'
import { error } from '/@/utils/log'
interface UseFormActionContext {
emit: EmitType;
getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]>;
formModel: Recordable;
defaultValueRef: Ref<Recordable>;
formElRef: Ref<FormActionType>;
schemaRef: Ref<FormSchema[]>;
handleFormValues: Fn;
emit: EmitType
getProps: ComputedRef<FormProps>
getSchema: ComputedRef<FormSchema[]>
formModel: Recordable
defaultValueRef: Ref<Recordable>
formElRef: Ref<FormActionType>
schemaRef: Ref<FormSchema[]>
handleFormValues: Fn
}
export function useFormEvents({
emit,
@ -30,22 +30,22 @@ export function useFormEvents({
handleFormValues,
}: UseFormActionContext) {
async function resetFields(): Promise<void> {
const { resetFunc, submitOnReset } = unref(getProps);
resetFunc && isFunction(resetFunc) && (await resetFunc());
const { resetFunc, submitOnReset } = unref(getProps)
resetFunc && isFunction(resetFunc) && (await resetFunc())
const formEl = unref(formElRef);
if (!formEl) return;
const formEl = unref(formElRef)
if (!formEl) return
Object.keys(formModel).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key);
const isInput = schema?.component && defaultValueComponents.includes(schema.component);
const defaultValue = cloneDeep(defaultValueRef.value[key]);
formModel[key] = isInput ? defaultValue || '' : defaultValue;
});
nextTick(() => clearValidate());
const schema = unref(getSchema).find((item) => item.field === key)
const isInput = schema?.component && defaultValueComponents.includes(schema.component)
const defaultValue = cloneDeep(defaultValueRef.value[key])
formModel[key] = isInput ? defaultValue || '' : defaultValue
})
nextTick(() => clearValidate())
emit('reset', toRaw(formModel));
submitOnReset && handleSubmit();
emit('reset', toRaw(formModel))
submitOnReset && handleSubmit()
}
/**
@ -54,78 +54,78 @@ export function useFormEvents({
async function setFieldsValue(values: Recordable): Promise<void> {
const fields = unref(getSchema)
.map((item) => item.field)
.filter(Boolean);
.filter(Boolean)
// key 支持 a.b.c 的嵌套写法
const delimiter = '.';
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0);
const delimiter = '.'
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0)
const validKeys: string[] = [];
const validKeys: string[] = []
Object.keys(values).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key);
let value = values[key];
const schema = unref(getSchema).find((item) => item.field === 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
if (hasKey && fields.includes(key)) {
// time type
if (itemIsDateType(key)) {
if (Array.isArray(value)) {
const arr: any[] = [];
const arr: any[] = []
for (const ele of value) {
arr.push(ele ? dateUtil(ele) : null);
arr.push(ele ? dateUtil(ele) : null)
}
formModel[key] = arr;
formModel[key] = arr
} else {
const { componentProps } = schema || {};
let _props = componentProps as any;
const { componentProps } = schema || {}
let _props = componentProps as any
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 {
formModel[key] = value;
formModel[key] = value
}
validKeys.push(key);
validKeys.push(key)
} else {
nestKeyArray.forEach((nestKey: string) => {
try {
const value = eval('values' + delimiter + nestKey);
const value = eval('values' + delimiter + nestKey)
if (isDef(value)) {
formModel[nestKey] = value;
validKeys.push(nestKey);
formModel[nestKey] = value
validKeys.push(nestKey)
}
} catch (e) {
// key not exist
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
*/
async function removeSchemaByField(fields: string | string[]): Promise<void> {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
const schemaList: FormSchema[] = cloneDeep(unref(getSchema))
if (!fields) {
return;
return
}
let fieldList: string[] = isString(fields) ? [fields] : fields;
let fieldList: string[] = isString(fields) ? [fields] : fields
if (isString(fields)) {
fieldList = [fields];
fieldList = [fields]
}
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 {
if (isString(field)) {
const index = schemaList.findIndex((schema) => schema.field === field);
const index = schemaList.findIndex((schema) => schema.field === field)
if (index !== -1) {
delete formModel[field];
schemaList.splice(index, 1);
delete formModel[field]
schemaList.splice(index, 1)
}
}
}
@ -145,92 +145,92 @@ export function useFormEvents({
* @description: Insert after a certain field, if not insert the last
*/
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) {
first ? schemaList.unshift(schema) : schemaList.push(schema);
schemaRef.value = schemaList;
_setDefaultValue(schema);
return;
first ? schemaList.unshift(schema) : schemaList.push(schema)
schemaRef.value = schemaList
_setDefaultValue(schema)
return
}
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>[]) {
let updateData: Partial<FormSchema>[] = [];
let updateData: Partial<FormSchema>[] = []
if (isObject(data)) {
updateData.push(data as FormSchema);
updateData.push(data as FormSchema)
}
if (isArray(data)) {
updateData = [...data];
updateData = [...data]
}
const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
);
)
if (!hasField) {
error(
'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>[]) {
let updateData: Partial<FormSchema>[] = [];
let updateData: Partial<FormSchema>[] = []
if (isObject(data)) {
updateData.push(data as FormSchema);
updateData.push(data as FormSchema)
}
if (isArray(data)) {
updateData = [...data];
updateData = [...data]
}
const hasField = updateData.every(
(item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field),
);
)
if (!hasField) {
error(
'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) => {
unref(getSchema).forEach((val) => {
if (val.field === item.field) {
const newSchema = deepMerge(val, item);
schema.push(newSchema as FormSchema);
const newSchema = deepMerge(val, item)
schema.push(newSchema as FormSchema)
} 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[]) {
let schemas: FormSchema[] = [];
let schemas: FormSchema[] = []
if (isObject(data)) {
schemas.push(data as FormSchema);
schemas.push(data as FormSchema)
}
if (isArray(data)) {
schemas = [...data];
schemas = [...data]
}
const obj: Recordable = {};
const currentFieldsValue = getFieldsValue();
const obj: Recordable = {}
const currentFieldsValue = getFieldsValue()
schemas.forEach((item) => {
if (
item.component != 'Divider' &&
@ -239,16 +239,16 @@ export function useFormEvents({
!isNullOrUnDef(item.defaultValue) &&
!(item.field in currentFieldsValue)
) {
obj[item.field] = item.defaultValue;
obj[item.field] = item.defaultValue
}
});
setFieldsValue(obj);
})
setFieldsValue(obj)
}
function getFieldsValue(): Recordable {
const formEl = unref(formElRef);
if (!formEl) return {};
return handleFormValues(toRaw(unref(formModel)));
const formEl = unref(formElRef)
if (!formEl) return {}
return handleFormValues(toRaw(unref(formModel)))
}
/**
@ -256,44 +256,44 @@ export function useFormEvents({
*/
function itemIsDateType(key: string) {
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) {
return unref(formElRef)?.validateFields(nameList);
return unref(formElRef)?.validateFields(nameList)
}
async function validate(nameList?: NamePath[] | undefined) {
return await unref(formElRef)?.validate(nameList);
return await unref(formElRef)?.validate(nameList)
}
async function clearValidate(name?: string | string[]) {
await unref(formElRef)?.clearValidate(name);
await unref(formElRef)?.clearValidate(name)
}
async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
await unref(formElRef)?.scrollToField(name, options);
await unref(formElRef)?.scrollToField(name, options)
}
/**
* @description: Form submission
*/
async function handleSubmit(e?: Event): Promise<void> {
e && e.preventDefault();
const { submitFunc } = unref(getProps);
e && e.preventDefault()
const { submitFunc } = unref(getProps)
if (submitFunc && isFunction(submitFunc)) {
await submitFunc();
return;
await submitFunc()
return
}
const formEl = unref(formElRef);
if (!formEl) return;
const formEl = unref(formElRef)
if (!formEl) return
try {
const values = await validate();
const res = handleFormValues(values);
emit('submit', res);
const values = await validate()
const res = handleFormValues(values)
emit('submit', res)
} catch (error: any) {
throw new Error(error);
throw new Error(error)
}
}
@ -310,5 +310,5 @@ export function useFormEvents({
resetFields,
setFieldsValue,
scrollToField,
};
}
}

View File

@ -1,31 +1,31 @@
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
import { dateUtil } from '/@/utils/dateUtil';
import { unref } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import { cloneDeep, set } from 'lodash-es';
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'
import { dateUtil } from '/@/utils/dateUtil'
import { unref } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import type { FormProps, FormSchema } from '../types/form'
import { cloneDeep, set } from 'lodash-es'
interface UseFormValuesContext {
defaultValueRef: Ref<any>;
getSchema: ComputedRef<FormSchema[]>;
getProps: ComputedRef<FormProps>;
formModel: Recordable;
defaultValueRef: Ref<any>
getSchema: ComputedRef<FormSchema[]>
getProps: ComputedRef<FormProps>
formModel: Recordable
}
/**
* @desription deconstruct array-link key. This method will mutate the target.
*/
function tryDeconstructArray(key: string, value: any, target: Recordable) {
const pattern = /^\[(.+)\]$/;
const pattern = /^\[(.+)\]$/
if (pattern.test(key)) {
const match = key.match(pattern);
const match = key.match(pattern)
if (match && match[1]) {
const keys = match[1].split(',');
value = Array.isArray(value) ? value : [value];
const keys = match[1].split(',')
value = Array.isArray(value) ? value : [value]
keys.forEach((k, index) => {
set(target, k.trim(), value[index]);
});
return true;
set(target, k.trim(), value[index])
})
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.
*/
function tryDeconstructObject(key: string, value: any, target: Recordable) {
const pattern = /^\{(.+)\}$/;
const pattern = /^\{(.+)\}$/
if (pattern.test(key)) {
const match = key.match(pattern);
const match = key.match(pattern)
if (match && match[1]) {
const keys = match[1].split(',');
value = isObject(value) ? value : {};
const keys = match[1].split(',')
value = isObject(value) ? value : {}
keys.forEach((k) => {
set(target, k.trim(), value[k.trim()]);
});
return true;
set(target, k.trim(), value[k.trim()])
})
return true
}
}
}
@ -57,75 +57,75 @@ export function useFormValues({
// Processing form values
function handleFormValues(values: Recordable) {
if (!isObject(values)) {
return {};
return {}
}
const res: Recordable = {};
const res: Recordable = {}
for (const item of Object.entries(values)) {
let [, value] = item;
const [key] = item;
let [, value] = item
const [key] = item
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
continue;
continue
}
const transformDateFunc = unref(getProps).transformDateFunc;
const transformDateFunc = unref(getProps).transformDateFunc
if (isObject(value)) {
value = transformDateFunc?.(value);
value = transformDateFunc?.(value)
}
if (isArray(value) && value[0]?.format && value[1]?.format) {
value = value.map((item) => transformDateFunc?.(item));
value = value.map((item) => transformDateFunc?.(item))
}
// Remove spaces
if (isString(value)) {
value = value.trim();
value = value.trim()
}
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
// 没有解构成功的,按原样赋值
set(res, key, value);
set(res, key, value)
}
}
return handleRangeTimeValue(res);
return handleRangeTimeValue(res)
}
/**
* @description: Processing time interval parameters
*/
function handleRangeTimeValue(values: Recordable) {
const fieldMapToTime = unref(getProps).fieldMapToTime;
const fieldMapToTime = unref(getProps).fieldMapToTime
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
return values;
return values
}
for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) {
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[endTimeKey] = dateUtil(endTime).format(format);
Reflect.deleteProperty(values, field);
values[startTimeKey] = dateUtil(startTime).format(format)
values[endTimeKey] = dateUtil(endTime).format(format)
Reflect.deleteProperty(values, field)
}
return values;
return values
}
function initDefault() {
const schemas = unref(getSchema);
const obj: Recordable = {};
const schemas = unref(getSchema)
const obj: Recordable = {}
schemas.forEach((item) => {
const { defaultValue } = item;
const { defaultValue } = item
if (!isNullOrUnDef(defaultValue)) {
obj[item.field] = defaultValue;
obj[item.field] = defaultValue
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 { computed, unref } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import { isNumber } from '/@/utils/is';
import type { Ref } from 'vue'
import { computed, unref } from 'vue'
import type { FormProps, FormSchema } from '../types/form'
import { isNumber } from '/@/utils/is'
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
return computed(() => {
const schemaItem = unref(schemaItemRef);
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
const { labelWidth, disabledLabelWidth } = schemaItem;
const schemaItem = unref(schemaItemRef)
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}
const { labelWidth, disabledLabelWidth } = schemaItem
const {
labelWidth: globalLabelWidth,
labelCol: globalLabelCol,
wrapperCol: globWrapperCol,
layout,
} = unref(propsRef);
} = unref(propsRef)
// If labelWidth is set globally, all items setting
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
labelCol.style = {
textAlign: 'left',
};
return { labelCol, wrapperCol };
}
return { labelCol, wrapperCol }
}
let width = labelWidth || globalLabelWidth;
const col = { ...globalLabelCol, ...labelCol };
const wrapCol = { ...globWrapperCol, ...wrapperCol };
let width = labelWidth || globalLabelWidth
const col = { ...globalLabelCol, ...labelCol }
const wrapCol = { ...globWrapperCol, ...wrapperCol }
if (width) {
width = isNumber(width) ? `${width}px` : width;
width = isNumber(width) ? `${width}px` : width
}
return {
@ -37,6 +37,6 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
...wrapCol,
},
};
});
}
})
}

View File

@ -1,10 +1,10 @@
import type { FieldMapToTime, FormSchema } from './types/form';
import type { CSSProperties, PropType } from 'vue';
import type { ColEx } from './types';
import type { TableActionType } from '/@/components/Table';
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
import { propTypes } from '/@/utils/propTypes';
import type { FieldMapToTime, FormSchema } from './types/form'
import type { CSSProperties, PropType } from 'vue'
import type { ColEx } from './types'
import type { TableActionType } from '/@/components/Table'
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
import { propTypes } from '/@/utils/propTypes'
export const basicProps = {
model: {
@ -54,7 +54,7 @@ export const basicProps = {
transformDateFunc: {
type: Function as PropType<Fn>,
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),
@ -100,4 +100,4 @@ export const basicProps = {
labelAlign: propTypes.string,
rowProps: Object as PropType<RowProps>,
};
}

View File

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

View File

@ -1,83 +1,83 @@
type ColSpanType = number | string;
type ColSpanType = number | string
export interface ColEx {
style?: any;
style?: any
/**
* raster number of cells to occupy, 0 corresponds to display: none
* @default none (0)
* @type ColSpanType
*/
span?: ColSpanType;
span?: ColSpanType
/**
* raster order, used in flex layout mode
* @default 0
* @type ColSpanType
*/
order?: ColSpanType;
order?: ColSpanType
/**
* the layout fill of flex
* @default none
* @type ColSpanType
*/
flex?: ColSpanType;
flex?: ColSpanType
/**
* the number of cells to offset Col from the left
* @default 0
* @type ColSpanType
*/
offset?: ColSpanType;
offset?: ColSpanType
/**
* the number of cells that raster is moved to the right
* @default 0
* @type ColSpanType
*/
push?: ColSpanType;
push?: ColSpanType
/**
* the number of cells that raster is moved to the left
* @default 0
* @type ColSpanType
*/
pull?: ColSpanType;
pull?: ColSpanType
/**
* <576px and also default setting, could be a span value or an object containing above props
* @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
* @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
* @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
* @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
* @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
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
*/
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType
}
export type ComponentType =
@ -114,4 +114,4 @@ export type ComponentType =
| 'Slider'
| 'Rate'
| 'Divider'
| 'ApiTransfer';
| 'ApiTransfer'

View File

@ -18,21 +18,21 @@
</Menu>
</template>
<script lang="ts">
import type { MenuState } from './types';
import { computed, defineComponent, unref, reactive, watch, toRefs, ref } from 'vue';
import { Menu } from 'ant-design-vue';
import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { useOpenKeys } from './useOpenKeys';
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router';
import { isFunction } from '/@/utils/is';
import { basicProps } from './props';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { REDIRECT_NAME } from '/@/router/constant';
import { useDesign } from '/@/hooks/web/useDesign';
import { getCurrentParentPath } from '/@/router/menus';
import { listenerRouteChange } from '/@/logics/mitt/routeChange';
import { getAllParentPath } from '/@/router/helper/menuHelper';
import type { MenuState } from './types'
import { computed, defineComponent, unref, reactive, watch, toRefs, ref } from 'vue'
import { Menu } from 'ant-design-vue'
import BasicSubMenuItem from './components/BasicSubMenuItem.vue'
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'
import { useOpenKeys } from './useOpenKeys'
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
import { isFunction } from '/@/utils/is'
import { basicProps } from './props'
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { REDIRECT_NAME } from '/@/router/constant'
import { useDesign } from '/@/hooks/web/useDesign'
import { getCurrentParentPath } from '/@/router/menus'
import { listenerRouteChange } from '/@/logics/mitt/routeChange'
import { getAllParentPath } from '/@/router/helper/menuHelper'
export default defineComponent({
name: 'BasicMenu',
@ -43,41 +43,41 @@
props: basicProps,
emits: ['menuClick'],
setup(props, { emit }) {
const isClickGo = ref(false);
const currentActiveMenu = ref('');
const isClickGo = ref(false)
const currentActiveMenu = ref('')
const menuState = reactive<MenuState>({
defaultSelectedKeys: [],
openKeys: [],
selectedKeys: [],
collapsedOpenKeys: [],
});
})
const { prefixCls } = useDesign('basic-menu');
const { items, mode, accordion } = toRefs(props);
const { prefixCls } = useDesign('basic-menu')
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(
menuState,
items,
mode as any,
accordion,
);
)
const getIsTopMenu = computed(() => {
const { type, mode } = props;
const { type, mode } = props
return (
(type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL) ||
(props.isHorizontal && unref(getSplit))
);
});
)
})
const getMenuClass = computed(() => {
const align = props.isHorizontal && unref(getSplit) ? 'start' : unref(getTopMenuAlign);
const align = props.isHorizontal && unref(getSplit) ? 'start' : unref(getTopMenuAlign)
return [
prefixCls,
`justify-${align}`,
@ -85,66 +85,66 @@
[`${prefixCls}__second`]: !props.isHorizontal && unref(getSplit),
[`${prefixCls}__sidebar-hor`]: unref(getIsTopMenu),
},
];
});
]
})
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) {
inlineCollapseOptions.inlineCollapsed = props.mixSider ? false : unref(getCollapsed);
inlineCollapseOptions.inlineCollapsed = props.mixSider ? false : unref(getCollapsed)
}
return inlineCollapseOptions;
});
return inlineCollapseOptions
})
listenerRouteChange((route) => {
if (route.name === REDIRECT_NAME) return;
handleMenuChange(route);
currentActiveMenu.value = route.meta?.currentActiveMenu as string;
if (route.name === REDIRECT_NAME) return
handleMenuChange(route)
currentActiveMenu.value = route.meta?.currentActiveMenu as string
if (unref(currentActiveMenu)) {
menuState.selectedKeys = [unref(currentActiveMenu)];
setOpenKeys(unref(currentActiveMenu));
menuState.selectedKeys = [unref(currentActiveMenu)]
setOpenKeys(unref(currentActiveMenu))
}
});
})
!props.mixSider &&
watch(
() => props.items,
() => {
handleMenuChange();
handleMenuChange()
},
);
)
async function handleMenuClick({ key }: { key: string; keyPath: string[] }) {
const { beforeClickFn } = props;
const { beforeClickFn } = props
if (beforeClickFn && isFunction(beforeClickFn)) {
const flag = await beforeClickFn(key);
if (!flag) return;
const flag = await beforeClickFn(key)
if (!flag) return
}
emit('menuClick', key);
emit('menuClick', key)
isClickGo.value = true;
menuState.selectedKeys = [key];
isClickGo.value = true
menuState.selectedKeys = [key]
}
async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
if (unref(isClickGo)) {
isClickGo.value = false;
return;
isClickGo.value = false
return
}
const path =
(route || unref(currentRoute)).meta?.currentActiveMenu ||
(route || unref(currentRoute)).path;
setOpenKeys(path);
if (unref(currentActiveMenu)) return;
(route || unref(currentRoute)).path
setOpenKeys(path)
if (unref(currentActiveMenu)) return
if (props.isHorizontal && unref(getSplit)) {
const parentPath = await getCurrentParentPath(path);
menuState.selectedKeys = [parentPath];
const parentPath = await getCurrentParentPath(path)
menuState.selectedKeys = [parentPath]
} else {
const parentPaths = await getAllParentPath(props.items, path);
menuState.selectedKeys = parentPaths;
const parentPaths = await getAllParentPath(props.items, path)
menuState.selectedKeys = parentPaths
}
}
@ -155,9 +155,9 @@
handleOpenChange,
getOpenKeys,
...toRefs(menuState),
};
}
},
});
})
</script>
<style lang="less">
@import './index.less';

View File

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

View File

@ -110,16 +110,19 @@
.ant-modal-confirm .ant-modal-body {
padding: 24px !important;
}
@media screen and (max-height: 600px) {
.ant-modal {
top: 60px;
}
}
@media screen and (max-height: 540px) {
.ant-modal {
top: 30px;
}
}
@media screen and (max-height: 480px) {
.ant-modal {
top: 10px;

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction.vue';
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
export { default as TableImg } from './src/components/TableImg.vue';
export { default as BasicTable } from './src/BasicTable.vue'
export { default as TableAction } from './src/components/TableAction.vue'
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'
export { default as TableImg } from './src/components/TableImg.vue'
export * from './src/types/table';
export * from './src/types/pagination';
export * from './src/types/tableAction';
export { useTable } from './src/hooks/useTable';
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
export type { EditRecordRow } from './src/components/editable';
export * from './src/types/table'
export * from './src/types/pagination'
export * from './src/types/tableAction'
export { useTable } from './src/hooks/useTable'
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'
export type { EditRecordRow } from './src/components/editable'

View File

@ -39,40 +39,35 @@
</div>
</template>
<script lang="ts">
import type {
BasicTableProps,
TableActionType,
SizeType,
ColumnChangeParam,
} from './types/table';
import type { BasicTableProps, TableActionType, SizeType, ColumnChangeParam } from './types/table'
import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect } from 'vue';
import { Table } from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { PageWrapperFixedHeightKey } from '/@/components/Page';
import HeaderCell from './components/HeaderCell.vue';
import { InnerHandlers } from './types/table';
import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect } from 'vue'
import { Table } from 'ant-design-vue'
import { BasicForm, useForm } from '/@/components/Form/index'
import { PageWrapperFixedHeightKey } from '/@/components/Page'
import HeaderCell from './components/HeaderCell.vue'
import { InnerHandlers } from './types/table'
import { usePagination } from './hooks/usePagination';
import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource';
import { useLoading } from './hooks/useLoading';
import { useRowSelection } from './hooks/useRowSelection';
import { useTableScroll } from './hooks/useTableScroll';
import { useTableScrollTo } from './hooks/useScrollTo';
import { useCustomRow } from './hooks/useCustomRow';
import { useTableStyle } from './hooks/useTableStyle';
import { useTableHeader } from './hooks/useTableHeader';
import { useTableExpand } from './hooks/useTableExpand';
import { createTableContext } from './hooks/useTableContext';
import { useTableFooter } from './hooks/useTableFooter';
import { useTableForm } from './hooks/useTableForm';
import { useDesign } from '/@/hooks/web/useDesign';
import { usePagination } from './hooks/usePagination'
import { useColumns } from './hooks/useColumns'
import { useDataSource } from './hooks/useDataSource'
import { useLoading } from './hooks/useLoading'
import { useRowSelection } from './hooks/useRowSelection'
import { useTableScroll } from './hooks/useTableScroll'
import { useTableScrollTo } from './hooks/useScrollTo'
import { useCustomRow } from './hooks/useCustomRow'
import { useTableStyle } from './hooks/useTableStyle'
import { useTableHeader } from './hooks/useTableHeader'
import { useTableExpand } from './hooks/useTableExpand'
import { createTableContext } from './hooks/useTableContext'
import { useTableFooter } from './hooks/useTableFooter'
import { useTableForm } from './hooks/useTableForm'
import { useDesign } from '/@/hooks/web/useDesign'
import { omit } from 'lodash-es';
import { basicProps } from './props';
import { isFunction } from '/@/utils/is';
import { warn } from '/@/utils/log';
import { omit } from 'lodash-es'
import { basicProps } from './props'
import { isFunction } from '/@/utils/is'
import { warn } from '/@/utils/log'
export default defineComponent({
components: {
@ -100,37 +95,37 @@
'columns-change',
],
setup(props, { attrs, emit, slots, expose }) {
const tableElRef = ref(null);
const tableData = ref<Recordable[]>([]);
const tableElRef = ref(null)
const tableData = ref<Recordable[]>([])
const wrapRef = ref(null);
const formRef = ref(null);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const wrapRef = ref(null)
const formRef = ref(null)
const innerPropsRef = ref<Partial<BasicTableProps>>()
const { prefixCls } = useDesign('basic-table');
const [registerForm, formActions] = useForm();
const { prefixCls } = useDesign('basic-table')
const [registerForm, formActions] = useForm()
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(() => {
unref(isFixedHeightPage) &&
props.canResize &&
warn(
"'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 {
getPaginationInfo,
getPagination,
setPagination,
setShowPagination,
getShowPagination,
} = usePagination(getProps);
} = usePagination(getProps)
const {
getRowSelection,
@ -140,7 +135,7 @@
getSelectRowKeys,
deleteSelectRowByKey,
setSelectedRowKeys,
} = useRowSelection(getProps, tableData, emit);
} = useRowSelection(getProps, tableData, emit)
const {
handleTableChange: onTableChange,
@ -168,14 +163,14 @@
clearSelectedRowKeys,
},
emit,
);
)
function handleTableChange(...args) {
onTableChange.call(undefined, ...args);
emit('change', ...args);
onTableChange.call(undefined, ...args)
emit('change', ...args)
// useTableonChange
const { onChange } = unref(getProps);
onChange && isFunction(onChange) && onChange.call(undefined, ...args);
const { onChange } = unref(getProps)
onChange && isFunction(onChange) && onChange.call(undefined, ...args)
}
const {
@ -185,7 +180,7 @@
setColumns,
getColumnsRef,
getCacheColumns,
} = useColumns(getProps, getPaginationInfo);
} = useColumns(getProps, getPaginationInfo)
const { getScrollRef, redoHeight } = useTableScroll(
getProps,
@ -195,9 +190,9 @@
getDataSourceRef,
wrapRef,
formRef,
);
)
const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef);
const { scrollTo } = useTableScrollTo(tableElRef, getDataSourceRef)
const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys,
@ -205,38 +200,38 @@
clearSelectedRowKeys,
getAutoCreateKey,
emit,
});
})
const { getRowClassName } = useTableStyle(getProps, prefixCls);
const { getRowClassName } = useTableStyle(getProps, prefixCls)
const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(
getProps,
tableData,
emit,
);
)
const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => {
emit('columns-change', data);
emit('columns-change', data)
// 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(
getProps,
getScrollRef,
tableElRef,
getDataSourceRef,
);
)
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } =
useTableForm(getProps, slots, fetch, getLoading);
useTableForm(getProps, slots, fetch, getLoading)
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef);
const dataSource = unref(getDataSourceRef)
let propsData: Recordable = {
...attrs,
customRow,
@ -252,17 +247,17 @@
dataSource,
footer: unref(getFooterProps),
...unref(getExpandOption),
};
}
// if (slots.expandedRowRender) {
// propsData = omit(propsData, 'scroll');
// }
propsData = omit(propsData, ['class', 'onChange']);
return propsData;
});
propsData = omit(propsData, ['class', 'onChange'])
return propsData
})
const getWrapperClass = computed(() => {
const values = unref(getBindValues);
const values = unref(getBindValues)
return [
prefixCls,
attrs.class,
@ -270,19 +265,19 @@
[`${prefixCls}-form-container`]: values.useSearchForm,
[`${prefixCls}--inset`]: values.inset,
},
];
});
]
})
const getEmptyDataIsShowTable = computed(() => {
const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
const { emptyDataIsShowTable, useSearchForm } = unref(getProps)
if (emptyDataIsShowTable || !useSearchForm) {
return true;
return true
}
return !!unref(getDataSourceRef).length;
});
return !!unref(getDataSourceRef).length
})
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
innerPropsRef.value = { ...unref(innerPropsRef), ...props }
}
const tableAction: TableActionType = {
@ -318,14 +313,14 @@
collapseAll,
scrollTo,
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 {
formRef,
@ -345,9 +340,9 @@
getFormSlotKeys,
getWrapperClass,
columns: getViewColumns,
};
}
},
});
})
</script>
<style lang="less">
@border-color: #cecece4d;

View File

@ -1,4 +1,4 @@
import type { Component } from 'vue';
import type { Component } from 'vue'
import {
Input,
Select,
@ -9,32 +9,32 @@ import {
TimePicker,
AutoComplete,
Radio,
} from 'ant-design-vue';
import type { ComponentType } from './types/componentType';
import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form';
} from 'ant-design-vue'
import type { ComponentType } from './types/componentType'
import { ApiSelect, ApiTreeSelect, RadioButtonGroup, ApiRadioGroup } from '/@/components/Form'
const componentMap = new Map<ComponentType, Component>();
const componentMap = new Map<ComponentType, Component>()
componentMap.set('Input', Input);
componentMap.set('InputNumber', InputNumber);
componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect);
componentMap.set('AutoComplete', AutoComplete);
componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('Switch', Switch);
componentMap.set('Checkbox', Checkbox);
componentMap.set('DatePicker', DatePicker);
componentMap.set('TimePicker', TimePicker);
componentMap.set('RadioGroup', Radio.Group);
componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('ApiRadioGroup', ApiRadioGroup);
componentMap.set('Input', Input)
componentMap.set('InputNumber', InputNumber)
componentMap.set('Select', Select)
componentMap.set('ApiSelect', ApiSelect)
componentMap.set('AutoComplete', AutoComplete)
componentMap.set('ApiTreeSelect', ApiTreeSelect)
componentMap.set('Switch', Switch)
componentMap.set('Checkbox', Checkbox)
componentMap.set('DatePicker', DatePicker)
componentMap.set('TimePicker', TimePicker)
componentMap.set('RadioGroup', Radio.Group)
componentMap.set('RadioButtonGroup', RadioButtonGroup)
componentMap.set('ApiRadioGroup', ApiRadioGroup)
export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component);
componentMap.set(compName, component)
}
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`" />
</template>
<script lang="ts">
import type { PropType } from 'vue';
import type { BasicColumn } from '../types/table';
import { defineComponent, computed } from 'vue';
import BasicHelp from '/@/components/Basic/src/BasicHelp.vue';
import EditTableHeaderCell from './EditTableHeaderIcon.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import type { PropType } from 'vue'
import type { BasicColumn } from '../types/table'
import { defineComponent, computed } from 'vue'
import BasicHelp from '/@/components/Basic/src/BasicHelp.vue'
import EditTableHeaderCell from './EditTableHeaderIcon.vue'
import { useDesign } from '/@/hooks/web/useDesign'
export default defineComponent({
name: 'TableHeaderCell',
@ -26,15 +26,15 @@
},
},
setup(props) {
const { prefixCls } = useDesign('basic-table-header-cell');
const { prefixCls } = useDesign('basic-table-header-cell')
const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => props.column?.customTitle || props.column?.title);
const getHelpMessage = computed(() => props.column?.helpMessage);
const getIsEdit = computed(() => !!props.column?.edit)
const getTitle = computed(() => props.column?.customTitle || props.column?.title)
const getHelpMessage = computed(() => props.column?.helpMessage)
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
return { prefixCls, getIsEdit, getTitle, getHelpMessage }
},
});
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header-cell';

View File

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

View File

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

View File

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

View File

@ -99,7 +99,7 @@
</Tooltip>
</template>
<script lang="ts">
import type { BasicColumn, ColumnChangeParam } from '../../types/table';
import type { BasicColumn, ColumnChangeParam } from '../../types/table'
import {
defineComponent,
ref,
@ -109,33 +109,33 @@
nextTick,
unref,
computed,
} from 'vue';
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
import { Icon } from '/@/components/Icon';
import { ScrollContainer } from '/@/components/Container';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
import { useDesign } from '/@/hooks/web/useDesign';
} from 'vue'
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue'
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface'
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'
import { Icon } from '/@/components/Icon'
import { ScrollContainer } from '/@/components/Container'
import { useI18n } from '/@/hooks/web/useI18n'
import { useTableContext } from '../../hooks/useTableContext'
import { useDesign } from '/@/hooks/web/useDesign'
// import { useSortable } from '/@/hooks/web/useSortable';
import { isFunction, isNullAndUnDef } from '/@/utils/is';
import { getPopupContainer as getParentContainer } from '/@/utils';
import { cloneDeep, omit } from 'lodash-es';
import Sortablejs from 'sortablejs';
import type Sortable from 'sortablejs';
import { isFunction, isNullAndUnDef } from '/@/utils/is'
import { getPopupContainer as getParentContainer } from '/@/utils'
import { cloneDeep, omit } from 'lodash-es'
import Sortablejs from 'sortablejs'
import type Sortable from 'sortablejs'
interface State {
checkAll: boolean;
isInit?: boolean;
checkedList: string[];
defaultCheckList: string[];
checkAll: boolean
isInit?: boolean
checkedList: string[]
defaultCheckList: string[]
}
interface Options {
label: string;
value: string;
fixed?: boolean | 'left' | 'right';
label: string
value: string
fixed?: boolean | 'left' | 'right'
}
export default defineComponent({
@ -154,145 +154,145 @@
emits: ['columns-change'],
setup(_, { emit, attrs }) {
const { t } = useI18n();
const table = useTableContext();
const { t } = useI18n()
const table = useTableContext()
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
let inited = false;
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys')
let inited = false
const cachePlainOptions = ref<Options[]>([]);
const plainOptions = ref<Options[] | any>([]);
const cachePlainOptions = ref<Options[]>([])
const plainOptions = ref<Options[] | any>([])
const plainSortOptions = ref<Options[]>([]);
const plainSortOptions = ref<Options[]>([])
const columnListRef = ref<ComponentRef>(null);
const columnListRef = ref<ComponentRef>(null)
const state = reactive<State>({
checkAll: true,
checkedList: [],
defaultCheckList: [],
});
})
const checkIndex = ref(false);
const checkSelect = ref(false);
const checkIndex = ref(false)
const checkSelect = ref(false)
const { prefixCls } = useDesign('basic-column-setting');
const { prefixCls } = useDesign('basic-column-setting')
const getValues = computed(() => {
return unref(table?.getBindValues) || {};
});
return unref(table?.getBindValues) || {}
})
watchEffect(() => {
setTimeout(() => {
const columns = table.getColumns();
const columns = table.getColumns()
if (columns.length && !state.isInit) {
init();
init()
}
}, 0);
});
}, 0)
})
watchEffect(() => {
const values = unref(getValues);
checkIndex.value = !!values.showIndexColumn;
checkSelect.value = !!values.rowSelection;
});
const values = unref(getValues)
checkIndex.value = !!values.showIndexColumn
checkSelect.value = !!values.rowSelection
})
function getColumns() {
const ret: Options[] = [];
const ret: Options[] = []
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({
label: (item.title as string) || (item.customTitle as string),
value: (item.dataIndex || item.title) as string,
...item,
});
});
return ret;
})
})
return ret
}
function init() {
const columns = getColumns();
const columns = getColumns()
const checkList = table
.getColumns({ ignoreAction: true, ignoreIndex: true })
.map((item) => {
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) {
plainOptions.value = columns;
plainSortOptions.value = columns;
cachePlainOptions.value = columns;
state.defaultCheckList = checkList;
plainOptions.value = columns
plainSortOptions.value = columns
cachePlainOptions.value = columns
state.defaultCheckList = checkList
} else {
// const fixedColumns = columns.filter((item) =>
// Reflect.has(item, 'fixed')
// ) as 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) {
item.fixed = findItem.fixed;
item.fixed = findItem.fixed
}
});
})
}
state.isInit = true;
state.checkedList = checkList;
state.isInit = true
state.checkedList = checkList
}
// checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainOptions.value.map((item) => item.value);
const checkList = plainOptions.value.map((item) => item.value)
if (e.target.checked) {
state.checkedList = checkList;
setColumns(checkList);
state.checkedList = checkList
setColumns(checkList)
} else {
state.checkedList = [];
setColumns([]);
state.checkedList = []
setColumns([])
}
}
const indeterminate = computed(() => {
const len = plainOptions.value.length;
let checkedLen = state.checkedList.length;
const len = plainOptions.value.length
let checkedLen = state.checkedList.length
// unref(checkIndex) && checkedLen--;
return checkedLen > 0 && checkedLen < len;
});
return checkedLen > 0 && checkedLen < len
})
// Trigger when check/uncheck a column
function onChange(checkedList: string[]) {
const len = plainSortOptions.value.length;
state.checkAll = checkedList.length === len;
const sortList = unref(plainSortOptions).map((item) => item.value);
const len = plainSortOptions.value.length
state.checkAll = checkedList.length === len
const sortList = unref(plainSortOptions).map((item) => item.value)
checkedList.sort((prev, next) => {
return sortList.indexOf(prev) - sortList.indexOf(next);
});
setColumns(checkedList);
return sortList.indexOf(prev) - sortList.indexOf(next)
})
setColumns(checkedList)
}
let sortable: Sortable;
let sortableOrder: string[] = [];
let sortable: Sortable
let sortableOrder: string[] = []
// reset columns
function reset() {
state.checkedList = [...state.defaultCheckList];
state.checkAll = true;
plainOptions.value = unref(cachePlainOptions);
plainSortOptions.value = unref(cachePlainOptions);
setColumns(table.getCacheColumns());
sortable.sort(sortableOrder);
state.checkedList = [...state.defaultCheckList]
state.checkAll = true
plainOptions.value = unref(cachePlainOptions)
plainSortOptions.value = unref(cachePlainOptions)
setColumns(table.getCacheColumns())
sortable.sort(sortableOrder)
}
// Open the pop-up window for drag and drop initialization
function handleVisibleChange() {
if (inited) return;
if (inited) return
nextTick(() => {
const columnListEl = unref(columnListRef);
if (!columnListEl) return;
const el = columnListEl.$el as any;
if (!el) return;
const columnListEl = unref(columnListRef)
if (!columnListEl) return
const el = columnListEl.$el as any
if (!el) return
// Drag and drop sort
sortable = Sortablejs.create(unref(el), {
animation: 500,
@ -300,86 +300,86 @@
delayOnTouchOnly: true,
handle: '.table-column-drag-icon ',
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
const { oldIndex, newIndex } = evt
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return;
return
}
// Sort column
const columns = cloneDeep(plainSortOptions.value);
const columns = cloneDeep(plainSortOptions.value)
if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]);
columns.splice(oldIndex + 1, 1);
columns.splice(newIndex, 0, columns[oldIndex])
columns.splice(oldIndex + 1, 1)
} else {
columns.splice(newIndex + 1, 0, columns[oldIndex]);
columns.splice(oldIndex, 1);
columns.splice(newIndex + 1, 0, columns[oldIndex])
columns.splice(oldIndex, 1)
}
plainSortOptions.value = columns;
plainSortOptions.value = columns
setColumns(
columns
.map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)),
);
)
},
});
})
// order
sortableOrder = sortable.toArray();
inited = true;
});
sortableOrder = sortable.toArray()
inited = true
})
}
// Control whether the serial number column is displayed
function handleIndexCheckChange(e: CheckboxChangeEvent) {
table.setProps({
showIndexColumn: e.target.checked,
});
})
}
// Control whether the check box is displayed
function handleSelectCheckChange(e: CheckboxChangeEvent) {
table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined,
});
})
}
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 isFixed = item.fixed === fixed ? false : fixed;
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
const columns = getColumns() as BasicColumn[]
const isFixed = item.fixed === fixed ? false : fixed
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex)
if (index !== -1) {
columns[index].fixed = isFixed;
columns[index].fixed = isFixed
}
item.fixed = isFixed;
item.fixed = isFixed
if (isFixed && !item.width) {
item.width = 100;
item.width = 100
}
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
setColumns(columns);
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed })
setColumns(columns)
}
function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns);
table.setColumns(columns)
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const visible =
columns.findIndex(
(c: BasicColumn | string) =>
c === col.value || (typeof c !== 'string' && c.dataIndex === col.value),
) !== -1;
return { dataIndex: col.value, fixed: col.fixed, visible };
});
) !== -1
return { dataIndex: col.value, fixed: col.fixed, visible }
})
emit('columns-change', data);
emit('columns-change', data)
}
function getPopupContainer() {
return isFunction(attrs.getPopupContainer)
? attrs.getPopupContainer()
: getParentContainer();
: getParentContainer()
}
return {
@ -400,9 +400,9 @@
defaultRowSelection,
handleColumnFixed,
getPopupContainer,
};
}
},
});
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-column-setting';

View File

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

View File

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

View File

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

View File

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

View File

@ -1,55 +1,55 @@
import type { ComputedRef, Ref } from 'vue';
import { nextTick, unref } from 'vue';
import { warn } from '/@/utils/log';
import type { ComputedRef, Ref } from 'vue'
import { nextTick, unref } from 'vue'
import { warn } from '/@/utils/log'
export function useTableScrollTo(
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable[]>,
) {
let bodyEl: HTMLElement | null;
let bodyEl: HTMLElement | null
async function findTargetRowToScroll(targetRowData: Recordable) {
const { id } = targetRowData;
const { id } = targetRowData
const targetRowEl: HTMLElement | null | undefined = bodyEl?.querySelector(
`[data-row-key="${id}"]`,
);
)
//Add a delay to get new dataSource
await nextTick();
await nextTick()
bodyEl?.scrollTo({
top: targetRowEl?.offsetTop ?? 0,
behavior: 'smooth',
});
})
}
function scrollTo(pos: string): void {
const table = unref(tableElRef);
if (!table) return;
const table = unref(tableElRef)
if (!table) return
const tableEl: Element = table.$el;
if (!tableEl) return;
const tableEl: Element = table.$el
if (!tableEl) return
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return;
bodyEl = tableEl.querySelector('.ant-table-body')
if (!bodyEl) return
}
const dataSource = unref(getDataSourceRef);
if (!dataSource) return;
const dataSource = unref(getDataSourceRef)
if (!dataSource) return
// judge pos type
if (pos === 'top') {
findTargetRowToScroll(dataSource[0]);
findTargetRowToScroll(dataSource[0])
} else if (pos === 'bottom') {
findTargetRowToScroll(dataSource[dataSource.length - 1]);
findTargetRowToScroll(dataSource[dataSource.length - 1])
} else {
const targetRowData = dataSource.find((data) => data.id === pos);
const targetRowData = dataSource.find((data) => data.id === pos)
if (targetRowData) {
findTargetRowToScroll(targetRowData);
findTargetRowToScroll(targetRowData)
} 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 { PaginationProps } from '../types/pagination';
import type { DynamicProps } from '/#/utils';
import type { FormActionType } from '/@/components/Form';
import type { WatchStopHandle } from 'vue';
import { getDynamicProps } from '/@/utils';
import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'
import type { PaginationProps } from '../types/pagination'
import type { DynamicProps } from '/#/utils'
import type { FormActionType } from '/@/components/Form'
import type { WatchStopHandle } from 'vue'
import { getDynamicProps } from '/@/utils'
import { ref, onUnmounted, unref, watch, toRaw } from 'vue'
import { isProdMode } from '/@/utils/env'
import { error } from '/@/utils/log'
type Props = Partial<DynamicProps<BasicTableProps>>;
type Props = Partial<DynamicProps<BasicTableProps>>
type UseTableMethod = TableActionType & {
getForm: () => FormActionType;
};
getForm: () => FormActionType
}
export function useTable(tableProps?: Props): [
(instance: TableActionType, formInstance: UseTableMethod) => void,
TableActionType & {
getForm: () => FormActionType;
getForm: () => FormActionType
},
] {
const tableRef = ref<Nullable<TableActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false);
const formRef = ref<Nullable<UseTableMethod>>(null);
const tableRef = ref<Nullable<TableActionType>>(null)
const loadedRef = ref<Nullable<boolean>>(false)
const formRef = ref<Nullable<UseTableMethod>>(null)
let stopWatch: WatchStopHandle;
let stopWatch: WatchStopHandle
function register(instance: TableActionType, formInstance: UseTableMethod) {
isProdMode() &&
onUnmounted(() => {
tableRef.value = null;
loadedRef.value = null;
});
tableRef.value = null
loadedRef.value = null
})
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return;
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return
tableRef.value = instance;
formRef.value = formInstance;
tableProps && instance.setProps(getDynamicProps(tableProps));
loadedRef.value = true;
tableRef.value = instance
formRef.value = formInstance
tableProps && instance.setProps(getDynamicProps(tableProps))
loadedRef.value = true
stopWatch?.();
stopWatch?.()
stopWatch = watch(
() => tableProps,
() => {
tableProps && instance.setProps(getDynamicProps(tableProps));
tableProps && instance.setProps(getDynamicProps(tableProps))
},
{
immediate: true,
deep: true,
},
);
)
}
function getTableInstance(): TableActionType {
const table = unref(tableRef);
const table = unref(tableRef)
if (!table) {
error(
'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 & {
getForm: () => FormActionType;
getForm: () => FormActionType
} = {
reload: async (opt?: FetchParams) => {
return await getTableInstance().reload(opt);
return await getTableInstance().reload(opt)
},
setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props);
getTableInstance().setProps(props)
},
redoHeight: () => {
getTableInstance().redoHeight();
getTableInstance().redoHeight()
},
setLoading: (loading: boolean) => {
getTableInstance().setLoading(loading);
getTableInstance().setLoading(loading)
},
getDataSource: () => {
return getTableInstance().getDataSource();
return getTableInstance().getDataSource()
},
getRawDataSource: () => {
return getTableInstance().getRawDataSource();
return getTableInstance().getRawDataSource()
},
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return toRaw(columns);
const columns = getTableInstance().getColumns({ ignoreIndex }) || []
return toRaw(columns)
},
setColumns: (columns: BasicColumn[]) => {
getTableInstance().setColumns(columns);
getTableInstance().setColumns(columns)
},
setTableData: (values: any[]) => {
return getTableInstance().setTableData(values);
return getTableInstance().setTableData(values)
},
setPagination: (info: Partial<PaginationProps>) => {
return getTableInstance().setPagination(info);
return getTableInstance().setPagination(info)
},
deleteSelectRowByKey: (key: string) => {
getTableInstance().deleteSelectRowByKey(key);
getTableInstance().deleteSelectRowByKey(key)
},
getSelectRowKeys: () => {
return toRaw(getTableInstance().getSelectRowKeys());
return toRaw(getTableInstance().getSelectRowKeys())
},
getSelectRows: () => {
return toRaw(getTableInstance().getSelectRows());
return toRaw(getTableInstance().getSelectRows())
},
clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys();
getTableInstance().clearSelectedRowKeys()
},
setSelectedRowKeys: (keys: string[] | number[]) => {
getTableInstance().setSelectedRowKeys(keys);
getTableInstance().setSelectedRowKeys(keys)
},
getPaginationRef: () => {
return getTableInstance().getPaginationRef();
return getTableInstance().getPaginationRef()
},
getSize: () => {
return toRaw(getTableInstance().getSize());
return toRaw(getTableInstance().getSize())
},
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[]) => {
return getTableInstance().deleteTableDataRecord(rowKey);
return getTableInstance().deleteTableDataRecord(rowKey)
},
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
return getTableInstance().insertTableDataRecord(record, index);
return getTableInstance().insertTableDataRecord(record, index)
},
updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
return getTableInstance().updateTableDataRecord(rowKey, record);
return getTableInstance().updateTableDataRecord(rowKey, record)
},
findTableDataRecord: (rowKey: string | number) => {
return getTableInstance().findTableDataRecord(rowKey);
return getTableInstance().findTableDataRecord(rowKey)
},
getRowSelection: () => {
return toRaw(getTableInstance().getRowSelection());
return toRaw(getTableInstance().getRowSelection())
},
getCacheColumns: () => {
return toRaw(getTableInstance().getCacheColumns());
return toRaw(getTableInstance().getCacheColumns())
},
getForm: () => {
return unref(formRef) as unknown as FormActionType;
return unref(formRef) as unknown as FormActionType
},
setShowPagination: async (show: boolean) => {
getTableInstance().setShowPagination(show);
getTableInstance().setShowPagination(show)
},
getShowPagination: () => {
return toRaw(getTableInstance().getShowPagination());
return toRaw(getTableInstance().getShowPagination())
},
expandAll: () => {
getTableInstance().expandAll();
getTableInstance().expandAll()
},
expandRows: (keys: string[]) => {
getTableInstance().expandRows(keys);
getTableInstance().expandRows(keys)
},
collapseAll: () => {
getTableInstance().collapseAll();
getTableInstance().collapseAll()
},
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 { BasicTableProps } from '../types/table';
import { computed, unref, ref, toRaw } from 'vue';
import { ROW_KEY } from '../const';
import type { ComputedRef, Ref } from 'vue'
import type { BasicTableProps } from '../types/table'
import { computed, unref, ref, toRaw } from 'vue'
import { ROW_KEY } from '../const'
export function useTableExpand(
propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>,
emit: EmitType,
) {
const expandedRowKeys = ref<string[]>([]);
const expandedRowKeys = ref<string[]>([])
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey
})
const getRowKey = computed(() => {
const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
const { rowKey } = unref(propsRef)
return unref(getAutoCreateKey) ? ROW_KEY : rowKey
})
const getExpandOption = computed(() => {
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return {};
const { isTreeTable } = unref(propsRef)
if (!isTreeTable) return {}
return {
expandedRowKeys: unref(expandedRowKeys),
onExpandedRowsChange: (keys: string[]) => {
expandedRowKeys.value = keys;
emit('expanded-rows-change', keys);
expandedRowKeys.value = keys
emit('expanded-rows-change', keys)
},
};
});
}
})
function expandAll() {
const keys = getAllKeys();
expandedRowKeys.value = keys;
const keys = getAllKeys()
expandedRowKeys.value = keys
}
function expandRows(keys: string[]) {
// use row ID expands the specified table row
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return;
expandedRowKeys.value = [...expandedRowKeys.value, ...keys];
const { isTreeTable } = unref(propsRef)
if (!isTreeTable) return
expandedRowKeys.value = [...expandedRowKeys.value, ...keys]
}
function getAllKeys(data?: Recordable[]) {
const keys: string[] = [];
const { childrenColumnName } = unref(propsRef);
const keys: string[] = []
const { childrenColumnName } = unref(propsRef)
toRaw(data || unref(tableData)).forEach((item) => {
keys.push(item[unref(getRowKey) as string]);
const children = item[childrenColumnName || 'children'];
keys.push(item[unref(getRowKey) as string])
const children = item[childrenColumnName || 'children']
if (children?.length) {
keys.push(...getAllKeys(children));
keys.push(...getAllKeys(children))
}
});
return keys;
})
return keys
}
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 { BasicTableProps } from '../types/table';
import { unref, computed, h, nextTick, watchEffect } from 'vue';
import TableFooter from '../components/TableFooter.vue';
import { useEventListener } from '/@/hooks/event/useEventListener';
import type { ComputedRef, Ref } from 'vue'
import type { BasicTableProps } from '../types/table'
import { unref, computed, h, nextTick, watchEffect } from 'vue'
import TableFooter from '../components/TableFooter.vue'
import { useEventListener } from '/@/hooks/event/useEventListener'
export function useTableFooter(
propsRef: ComputedRef<BasicTableProps>,
scrollRef: ComputedRef<{
x: string | number | true;
y: string | number | null;
scrollToFirstRowOnChange: boolean;
x: string | number | true
y: string | number | null
scrollToFirstRowOnChange: boolean
}>,
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable>,
) {
const getIsEmptyData = computed(() => {
return (unref(getDataSourceRef) || []).length === 0;
});
return (unref(getDataSourceRef) || []).length === 0
})
const getFooterProps = computed((): Recordable | undefined => {
const { summaryFunc, showSummary, summaryData } = unref(propsRef);
const { summaryFunc, showSummary, summaryData } = unref(propsRef)
return showSummary && !unref(getIsEmptyData)
? () => h(TableFooter, { summaryFunc, summaryData, scroll: unref(scrollRef) })
: undefined;
});
: undefined
})
watchEffect(() => {
handleSummary();
});
handleSummary()
})
function handleSummary() {
const { showSummary } = unref(propsRef);
if (!showSummary || unref(getIsEmptyData)) return;
const { showSummary } = unref(propsRef)
if (!showSummary || unref(getIsEmptyData)) return
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) return;
const bodyDom = tableEl.$el.querySelector('.ant-table-content');
const tableEl = unref(tableElRef)
if (!tableEl) return
const bodyDom = tableEl.$el.querySelector('.ant-table-content')
useEventListener({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-content',
) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
) as HTMLDivElement
if (!footerBodyDom || !bodyDom) return
footerBodyDom.scrollLeft = bodyDom.scrollLeft
},
wait: 0,
options: true,
});
});
})
})
}
return { getFooterProps };
return { getFooterProps }
}

View File

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

View File

@ -1,5 +1,5 @@
import type { PropType } from 'vue';
import type { PaginationProps } from './types/pagination';
import type { PropType } from 'vue'
import type { PaginationProps } from './types/pagination'
import type {
BasicColumn,
FetchSetting,
@ -8,11 +8,11 @@ import type {
TableCustomRecord,
TableRowSelection,
SizeType,
} from './types/table';
import type { FormProps } from '/@/components/Form';
} from './types/table'
import type { FormProps } from '/@/components/Form'
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
import { propTypes } from '/@/utils/propTypes';
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'
import { propTypes } from '/@/utils/propTypes'
export const basicProps = {
clickToRowSelect: { type: Boolean, default: true },
@ -60,7 +60,7 @@ export const basicProps = {
fetchSetting: {
type: Object as PropType<FetchSetting>,
default: () => {
return FETCH_SETTING;
return FETCH_SETTING
},
},
// 立即请求接口
@ -137,10 +137,10 @@ export const basicProps = {
beforeEditSubmit: {
type: Function as PropType<
(data: {
record: Recordable;
index: number;
key: string | number;
value: any;
record: Recordable
index: number
key: string | number
value: any
}) => Promise<any>
>,
},
@ -148,4 +148,4 @@ export const basicProps = {
type: String as PropType<SizeType>,
default: DEFAULT_SIZE,
},
};
}

View File

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

View File

@ -1,10 +1,10 @@
import Pagination from 'ant-design-vue/lib/pagination';
import { VNodeChild } from 'vue';
import Pagination from 'ant-design-vue/lib/pagination'
import { VNodeChild } from 'vue'
interface PaginationRenderProps {
page: number;
type: 'page' | 'prev' | 'next';
originalElement: any;
page: number
type: 'page' | 'prev' | 'next'
originalElement: any
}
type PaginationPositon =
@ -13,10 +13,10 @@ type PaginationPositon =
| 'topRight'
| 'bottomLeft'
| 'bottomCenter'
| 'bottomRight';
| 'bottomRight'
export declare class PaginationConfig extends Pagination {
position?: PaginationPositon[];
position?: PaginationPositon[]
}
export interface PaginationProps {
@ -25,91 +25,91 @@ export interface PaginationProps {
* @default 0
* @type number
*/
total?: number;
total?: number
/**
* default initial page number
* @default 1
* @type number
*/
defaultCurrent?: number;
defaultCurrent?: number
/**
* current page number
* @type number
*/
current?: number;
current?: number
/**
* default number of data items per page
* @default 10
* @type number
*/
defaultPageSize?: number;
defaultPageSize?: number
/**
* number of data items per page
* @type number
*/
pageSize?: number;
pageSize?: number
/**
* Whether to hide pager on single page
* @default false
* @type boolean
*/
hideOnSinglePage?: boolean;
hideOnSinglePage?: boolean
/**
* determine whether pageSize can be changed
* @default false
* @type boolean
*/
showSizeChanger?: boolean;
showSizeChanger?: boolean
/**
* specify the sizeChanger options
* @default ['10', '20', '30', '40']
* @type string[]
*/
pageSizeOptions?: string[];
pageSizeOptions?: string[]
/**
* determine whether you can jump to pages directly
* @default false
* @type boolean
*/
showQuickJumper?: boolean | object;
showQuickJumper?: boolean | object
/**
* to display the total number and range
* @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
* @default ''
* @type string
*/
size?: string;
size?: string
/**
* whether to setting simple mode
* @type boolean
*/
simple?: boolean;
simple?: boolean
/**
* to customize item innerHTML
* @type Function
*/
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element
/**
* specify the position of Pagination
* @default ['bottomRight']
* @type string[]
*/
position?: PaginationPositon[];
position?: PaginationPositon[]
}

View File

@ -1,17 +1,17 @@
import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import type { VNodeChild } from 'vue'
import type { PaginationProps } from './pagination'
import type { FormProps } from '/@/components/Form'
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'
import type { ColumnProps } from 'ant-design-vue/lib/table'
import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';
import { RoleEnum } from '/@/enums/roleEnum';
import { ComponentType } from './componentType'
import { VueNode } from '/@/utils/propTypes'
import { RoleEnum } from '/@/enums/roleEnum'
export declare type SortOrder = 'ascend' | 'descend';
export declare type SortOrder = 'ascend' | 'descend'
export interface TableCurrentDataSource<T = Recordable> {
currentDataSource: T[];
currentDataSource: T[]
}
export interface TableRowSelection<T = any> extends ITableRowSelection {
@ -19,289 +19,289 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
* Callback executed when selected rows change
* @type Function
*/
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any
/**
* Callback executed when select/deselect one row
* @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
* @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
* @type Function
*/
onSelectInvert?: (selectedRows: string[] | number[]) => any;
onSelectInvert?: (selectedRows: string[] | number[]) => any
}
export interface TableCustomRecord<T> {
record?: T;
index?: number;
record?: T
index?: number
}
export interface ExpandedRowRenderRecord<T> extends TableCustomRecord<T> {
indent?: number;
expanded?: boolean;
indent?: number
expanded?: boolean
}
export interface ColumnFilterItem {
text?: string;
value?: string;
children?: any;
text?: string
value?: string
children?: any
}
export interface TableCustomRecord<T = Recordable> {
record?: T;
index?: number;
record?: T
index?: number
}
export interface SorterResult {
column: ColumnProps;
order: SortOrder;
field: string;
columnKey: string;
column: ColumnProps
order: SortOrder
field: string
columnKey: string
}
export interface FetchParams {
searchInfo?: Recordable;
page?: number;
sortInfo?: Recordable;
filterInfo?: Recordable;
searchInfo?: Recordable
page?: number
sortInfo?: Recordable
filterInfo?: Recordable
}
export interface GetColumnsParams {
ignoreIndex?: boolean;
ignoreAction?: boolean;
sort?: boolean;
ignoreIndex?: boolean
ignoreAction?: boolean
sort?: boolean
}
export type SizeType = 'default' | 'middle' | 'small' | 'large';
export type SizeType = 'default' | 'middle' | 'small' | 'large'
export interface TableActionType {
reload: (opt?: FetchParams) => Promise<void>;
getSelectRows: <T = Recordable>() => T[];
clearSelectedRowKeys: () => void;
expandAll: () => void;
expandRows: (keys: string[] | number[]) => void;
collapseAll: () => void;
scrollTo: (pos: string) => void; // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => void;
setPagination: (info: Partial<PaginationProps>) => void;
setTableData: <T = Recordable>(values: T[]) => void;
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
findTableDataRecord: (rowKey: string | number) => Recordable | void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
getDataSource: <T = Recordable>() => T[];
getRawDataSource: <T = Recordable>() => T;
setLoading: (loading: boolean) => void;
setProps: (props: Partial<BasicTableProps>) => void;
redoHeight: () => void;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
getPaginationRef: () => PaginationProps | boolean;
getSize: () => SizeType;
getRowSelection: () => TableRowSelection<Recordable>;
getCacheColumns: () => BasicColumn[];
emit?: EmitType;
updateTableData: (index: number, key: string, value: any) => Recordable;
setShowPagination: (show: boolean) => Promise<void>;
getShowPagination: () => boolean;
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
reload: (opt?: FetchParams) => Promise<void>
getSelectRows: <T = Recordable>() => T[]
clearSelectedRowKeys: () => void
expandAll: () => void
expandRows: (keys: string[] | number[]) => void
collapseAll: () => void
scrollTo: (pos: string) => void // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[]
deleteSelectRowByKey: (key: string) => void
setPagination: (info: Partial<PaginationProps>) => void
setTableData: <T = Recordable>(values: T[]) => void
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void
findTableDataRecord: (rowKey: string | number) => Recordable | void
getColumns: (opt?: GetColumnsParams) => BasicColumn[]
setColumns: (columns: BasicColumn[] | string[]) => void
getDataSource: <T = Recordable>() => T[]
getRawDataSource: <T = Recordable>() => T
setLoading: (loading: boolean) => void
setProps: (props: Partial<BasicTableProps>) => void
redoHeight: () => void
setSelectedRowKeys: (rowKeys: string[] | number[]) => void
getPaginationRef: () => PaginationProps | boolean
getSize: () => SizeType
getRowSelection: () => TableRowSelection<Recordable>
getCacheColumns: () => BasicColumn[]
emit?: EmitType
updateTableData: (index: number, key: string, value: any) => Recordable
setShowPagination: (show: boolean) => Promise<void>
getShowPagination: () => boolean
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void
}
export interface FetchSetting {
// 请求接口当前页数
pageField: string;
pageField: string
// 每页显示多少条
sizeField: string;
sizeField: string
// 请求结果列表字段 支持 a.b.c
listField: string;
listField: string
// 请求结果总数字段 支持 a.b.c
totalField: string;
totalField: string
}
export interface TableSetting {
redo?: boolean;
size?: boolean;
setting?: boolean;
fullScreen?: boolean;
redo?: boolean
size?: boolean
setting?: boolean
fullScreen?: boolean
}
export interface BasicTableProps<T = any> {
// 点击行选中
clickToRowSelect?: boolean;
isTreeTable?: boolean;
clickToRowSelect?: boolean
isTreeTable?: boolean
// 自定义排序方法
sortFn?: (sortInfo: SorterResult) => any;
sortFn?: (sortInfo: SorterResult) => any
// 排序方法
filterFn?: (data: Partial<Recordable<string[]>>) => any;
filterFn?: (data: Partial<Recordable<string[]>>) => any
// 取消表格的默认padding
inset?: boolean;
inset?: boolean
// 显示表格设置
showTableSetting?: boolean;
tableSetting?: TableSetting;
showTableSetting?: boolean
tableSetting?: TableSetting
// 斑马纹
striped?: boolean;
striped?: boolean
// 是否自动生成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;
actionColumn?: BasicColumn;
indexColumnProps?: BasicColumn
actionColumn?: BasicColumn
// 文本超过宽度是否显示。。。
ellipsis?: boolean;
ellipsis?: boolean
// 是否继承父级高度(父级高度-表单高度-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?: boolean;
loading?: boolean
/**
* The column contains children to display
* @default 'children'
* @type string | string[]
*/
childrenColumnName?: string;
childrenColumnName?: string
/**
* Override default table elements
* @type object
*/
components?: object;
components?: object
/**
* Expand all rows initially
* @default false
* @type boolean
*/
defaultExpandAllRows?: boolean;
defaultExpandAllRows?: boolean
/**
* Initial expanded row keys
* @type string[]
*/
defaultExpandedRowKeys?: string[];
defaultExpandedRowKeys?: string[]
/**
* Current expanded row keys
* @type string[]
*/
expandedRowKeys?: string[];
expandedRowKeys?: string[]
/**
* Expanded container render for each row
* @type Function
*/
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element;
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element
/**
* Customize row expand Icon.
* @type Function | VNodeChild
*/
expandIcon?: Function | VNodeChild | JSX.Element;
expandIcon?: Function | VNodeChild | JSX.Element
/**
* Whether to expand row by clicking anywhere in the whole row
* @default false
* @type boolean
*/
expandRowByClick?: boolean;
expandRowByClick?: boolean
/**
* The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
*/
expandIconColumnIndex?: number;
expandIconColumnIndex?: number
/**
* Table footer renderer
* @type Function | VNodeChild
*/
footer?: Function | VNodeChild | JSX.Element;
footer?: Function | VNodeChild | JSX.Element
/**
* Indent size in pixels of tree data
* @default 15
* @type number
*/
indentSize?: number;
indentSize?: number
/**
* i18n text including filter, sort, empty text, etc
* @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
* @type object
*/
locale?: object;
locale?: object
/**
* Row's className
* @type Function
*/
rowClassName?: (record: TableCustomRecord<T>, index: number) => string;
rowClassName?: (record: TableCustomRecord<T>, index: number) => string
/**
* Row selection config
* @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.
@ -309,39 +309,39 @@ export interface BasicTableProps<T = any> {
* you need to add style .ant-table td { white-space: nowrap; }.
* @type object
*/
scroll?: { x?: number | true; y?: number };
scroll?: { x?: number | true; y?: number }
/**
* Whether to show table header
* @default true
* @type boolean
*/
showHeader?: boolean;
showHeader?: boolean
/**
* Size of table
* @default 'default'
* @type string
*/
size?: SizeType;
size?: SizeType
/**
* Table title renderer
* @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
* @type Function
*/
customHeaderRow?: (column: ColumnProps, index: number) => object;
customHeaderRow?: (column: ColumnProps, index: number) => object
/**
* Set props on per row
* @type Function
*/
customRow?: (record: T, index: number) => object;
customRow?: (record: T, index: number) => object
/**
* `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
* @version 1.5.0
*/
tableLayout?: 'auto' | 'fixed' | string;
tableLayout?: 'auto' | 'fixed' | string
/**
* the render container of dropdowns in table
* @param triggerNode
* @version 1.5.0
*/
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement
/**
* Data can be changed again before rendering.
@ -366,7 +366,7 @@ export interface BasicTableProps<T = any> {
*
* @version 1.5.4
*/
transformCellText?: Function;
transformCellText?: Function
/**
* 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
*/
beforeEditSubmit?: (data: {
record: Recordable;
index: number;
key: string | number;
value: any;
}) => Promise<any>;
record: Recordable
index: number
key: string | number
value: any
}) => Promise<any>
/**
* Callback executed when pagination, filters or sorter is changed
@ -387,7 +387,7 @@ export interface BasicTableProps<T = any> {
* @param sorter
* @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
@ -395,84 +395,84 @@ export interface BasicTableProps<T = any> {
* @param expanded
* @param record
*/
onExpand?: (expande: boolean, record: T) => void;
onExpand?: (expande: boolean, record: T) => void
/**
* Callback executed when the expanded rows change
* @param expandedRows
*/
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void
onColumnsChange?: (data: ColumnChangeParam[]) => void;
onColumnsChange?: (data: ColumnChangeParam[]) => void
}
export type CellFormat =
| string
| ((text: string, record: Recordable, index: number) => string | number)
| Map<string | number, any>;
| Map<string | number, any>
// @ts-ignore
export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[];
children?: BasicColumn[]
filters?: {
text: string;
value: string;
text: string
value: string
children?:
| unknown[]
| (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
}[];
| (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]))
}[]
//
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
customTitle?: VueNode;
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION'
customTitle?: VueNode
slots?: Recordable;
slots?: Recordable
// 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
helpMessage?: string | string[];
helpMessage?: string | string[]
format?: CellFormat;
format?: CellFormat
// Editable
edit?: boolean;
editRow?: boolean;
editable?: boolean;
editComponent?: ComponentType;
edit?: boolean
editRow?: boolean
editable?: boolean
editComponent?: ComponentType
editComponentProps?:
| ((opt: {
text: string | number | boolean | Recordable;
record: Recordable;
column: BasicColumn;
index: number;
text: string | number | boolean | Recordable
record: Recordable
column: BasicColumn
index: number
}) => Recordable)
| Recordable;
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
editValueMap?: (value: any) => string;
onEditRow?: () => void;
| Recordable
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>)
editValueMap?: (value: any) => string
onEditRow?: () => void
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
auth?: RoleEnum | RoleEnum[] | string | string[]
// 业务控制是否显示
ifShow?: boolean | ((column: BasicColumn) => boolean);
ifShow?: boolean | ((column: BasicColumn) => boolean)
// 自定义修改后显示的内容
editRender?: (opt: {
text: string | number | boolean | Recordable;
record: Recordable;
column: BasicColumn;
index: number;
}) => VNodeChild | JSX.Element;
text: string | number | boolean | Recordable
record: Recordable
column: BasicColumn
index: number
}) => VNodeChild | JSX.Element
// 动态 Disabled
editDynamicDisabled?: boolean | ((record: Recordable) => boolean);
editDynamicDisabled?: boolean | ((record: Recordable) => boolean)
}
export type ColumnChangeParam = {
dataIndex: string;
fixed: boolean | 'left' | 'right' | undefined;
visible: boolean;
};
dataIndex: string
fixed: boolean | 'left' | 'right' | undefined
visible: boolean
}
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 { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
import { RoleEnum } from '/@/enums/roleEnum';
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'
import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'
import { RoleEnum } from '/@/enums/roleEnum'
export interface ActionItem extends ButtonProps {
onClick?: Fn;
label?: string;
color?: 'success' | 'error' | 'warning';
icon?: string;
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
onClick?: Fn
label?: string
color?: 'success' | 'error' | 'warning'
icon?: string
popConfirm?: PopConfirm
disabled?: boolean
divider?: boolean
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
auth?: RoleEnum | RoleEnum[] | string | string[]
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
tooltip?: string | TooltipProps;
ifShow?: boolean | ((action: ActionItem) => boolean)
tooltip?: string | TooltipProps
}
export interface PopConfirm {
title: string;
okText?: string;
cancelText?: string;
confirm: Fn;
cancel?: Fn;
icon?: string;
title: string
okText?: string
cancelText?: string
confirm: Fn
cancel?: Fn
icon?: string
placement?:
| 'top'
| 'left'
@ -35,5 +35,5 @@ export interface PopConfirm {
| 'rightTop'
| 'rightBottom'
| 'bottomLeft'
| 'bottomRight';
| 'bottomRight'
}

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import type { App } from 'vue';
import { Button } from './Button';
import { Input, Layout } from 'ant-design-vue';
import type { App } from 'vue'
import { Button } from './Button'
import { Input, Layout } from 'ant-design-vue'
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;
max-width: 100%;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,26 +78,26 @@
</div>
</template>
<script lang="ts">
import type { Menu } from '/@/router/types';
import type { CSSProperties } from 'vue';
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue';
import type { RouteLocationNormalized } from 'vue-router';
import { ScrollContainer } from '/@/components/Container';
import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu';
import { Icon } from '/@/components/Icon';
import { AppLogo } from '/@/components/Application';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { usePermissionStore } from '/@/store/modules/permission';
import { useDragLine } from './useLayoutSider';
import { useGlobSetting } from '/@/hooks/setting';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { useGo } from '/@/hooks/web/usePage';
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
import clickOutside from '/@/directives/clickOutside';
import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus';
import { listenerRouteChange } from '/@/logics/mitt/routeChange';
import LayoutTrigger from '../trigger/index.vue';
import type { Menu } from '/@/router/types'
import type { CSSProperties } from 'vue'
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import { ScrollContainer } from '/@/components/Container'
import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu'
import { Icon } from '/@/components/Icon'
import { AppLogo } from '/@/components/Application'
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { usePermissionStore } from '/@/store/modules/permission'
import { useDragLine } from './useLayoutSider'
import { useGlobSetting } from '/@/hooks/setting'
import { useDesign } from '/@/hooks/web/useDesign'
import { useI18n } from '/@/hooks/web/useI18n'
import { useGo } from '/@/hooks/web/usePage'
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'
import clickOutside from '/@/directives/clickOutside'
import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus'
import { listenerRouteChange } from '/@/logics/mitt/routeChange'
import LayoutTrigger from '../trigger/index.vue'
export default defineComponent({
name: 'LayoutMixSider',
@ -113,17 +113,17 @@
clickOutside,
},
setup() {
let menuModules = ref<Menu[]>([]);
const activePath = ref('');
const childrenMenus = ref<Menu[]>([]);
const openMenu = ref(false);
const dragBarRef = ref<ElRef>(null);
const sideRef = ref<ElRef>(null);
const currentRoute = ref<Nullable<RouteLocationNormalized>>(null);
let menuModules = ref<Menu[]>([])
const activePath = ref('')
const childrenMenus = ref<Menu[]>([])
const openMenu = ref(false)
const dragBarRef = ref<ElRef>(null)
const sideRef = ref<ElRef>(null)
const currentRoute = ref<Nullable<RouteLocationNormalized>>(null)
const { prefixCls } = useDesign('layout-mix-sider');
const go = useGo();
const { t } = useI18n();
const { prefixCls } = useDesign('layout-mix-sider')
const go = useGo()
const { t } = useI18n()
const {
getMenuWidth,
getCanDrag,
@ -136,81 +136,81 @@
setMenuSetting,
getIsMixSidebar,
getCollapsed,
} = useMenuSetting();
} = useMenuSetting()
const { title } = useGlobSetting();
const permissionStore = usePermissionStore();
const { title } = useGlobSetting()
const permissionStore = usePermissionStore()
useDragLine(sideRef, dragBarRef, true);
useDragLine(sideRef, dragBarRef, true)
const getMenuStyle = computed((): CSSProperties => {
return {
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
left: `${unref(getMixSideWidth)}px`,
};
});
}
})
const getIsFixed = computed(() => {
/* eslint-disable-next-line */
mixSideHasChildren.value = unref(childrenMenus).length > 0;
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren);
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren)
if (isFixed) {
/* eslint-disable-next-line */
openMenu.value = true;
}
return isFixed;
});
return isFixed
})
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 fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0;
const width = `${unref(getMixSideWidth) + fixedWidth}px`;
return getWrapCommonStyle(width);
});
const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0
const width = `${unref(getMixSideWidth) + fixedWidth}px`
return getWrapCommonStyle(width)
})
const getWrapStyle = computed((): CSSProperties => {
const width = `${unref(getMixSideWidth)}px`;
return getWrapCommonStyle(width);
});
const width = `${unref(getMixSideWidth)}px`
return getWrapCommonStyle(width)
})
const getMenuEvents = computed(() => {
return !unref(getMixSideFixed)
? {
onMouseleave: () => {
setActive(true);
closeMenu();
setActive(true)
closeMenu()
},
}
: {};
});
: {}
})
const getShowDragBar = computed(() => unref(getCanDrag));
const getShowDragBar = computed(() => unref(getCanDrag))
onMounted(async () => {
menuModules.value = await getShallowMenus();
});
menuModules.value = await getShallowMenus()
})
// Menu changes
watch(
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
async () => {
menuModules.value = await getShallowMenus();
menuModules.value = await getShallowMenus()
},
{
immediate: true,
},
);
)
listenerRouteChange((route) => {
currentRoute.value = route;
setActive(true);
currentRoute.value = route
setActive(true)
if (unref(getCloseMixSidebarOnChange)) {
closeMenu();
closeMenu()
}
});
})
function getWrapCommonStyle(width: string): CSSProperties {
return {
@ -218,73 +218,73 @@
maxWidth: width,
minWidth: width,
flex: `0 0 ${width}`,
};
}
}
// Process module menu click
async function handleModuleClick(path: string, hover = false) {
const children = await getChildrenMenus(path);
const children = await getChildrenMenus(path)
if (unref(activePath) === path) {
if (!hover) {
if (!unref(openMenu)) {
openMenu.value = true;
openMenu.value = true
} else {
closeMenu();
closeMenu()
}
} else {
if (!unref(openMenu)) {
openMenu.value = true;
openMenu.value = true
}
}
if (!unref(openMenu)) {
setActive();
setActive()
}
} else {
openMenu.value = true;
activePath.value = path;
openMenu.value = true
activePath.value = path
}
if (!children || children.length === 0) {
if (!hover) go(path);
childrenMenus.value = [];
closeMenu();
return;
if (!hover) go(path)
childrenMenus.value = []
closeMenu()
return
}
childrenMenus.value = children;
childrenMenus.value = children
}
// Set the currently active menu and submenu
async function setActive(setChildren = false) {
const path = currentRoute.value?.path;
if (!path) return;
activePath.value = await getCurrentParentPath(path);
const path = currentRoute.value?.path
if (!path) return
activePath.value = await getCurrentParentPath(path)
// hanldeModuleClick(parentPath);
if (unref(getIsMixSidebar)) {
const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath));
const p = activeMenu?.path;
const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath))
const p = activeMenu?.path
if (p) {
const children = await getChildrenMenus(p);
const children = await getChildrenMenus(p)
if (setChildren) {
childrenMenus.value = children;
childrenMenus.value = children
if (unref(getMixSideFixed)) {
openMenu.value = children.length > 0;
openMenu.value = children.length > 0
}
}
if (children.length === 0) {
childrenMenus.value = [];
childrenMenus.value = []
}
}
}
}
function handleMenuClick(path: string) {
go(path);
go(path)
}
function handleClickOutside() {
setActive(true);
closeMenu();
setActive(true)
closeMenu()
}
function getItemEvents(item: Menu) {
@ -292,26 +292,26 @@
return {
onMouseenter: () => handleModuleClick(item.path, true),
onClick: async () => {
const children = await getChildrenMenus(item.path);
if (item.path && (!children || children.length === 0)) go(item.path);
const children = await getChildrenMenus(item.path)
if (item.path && (!children || children.length === 0)) go(item.path)
},
};
}
}
return {
onClick: () => handleModuleClick(item.path),
};
}
}
function handleFixedMenu() {
setMenuSetting({
mixSideFixed: !unref(getIsFixed),
});
})
}
// Close menu
function closeMenu() {
if (!unref(getIsFixed)) {
openMenu.value = false;
openMenu.value = false
}
}
@ -338,9 +338,9 @@
getMixSideFixed,
getWrapStyle,
getCollapsed,
};
}
},
});
})
</script>
<style lang="less">
@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 { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '/@/store/modules/app';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'
import { useDebounceFn } from '@vueuse/core'
import { useAppStore } from '/@/store/modules/app'
/**
* Handle related operations of menu events
*/
export function useSiderEvent() {
const appStore = useAppStore();
const { getMiniWidthNumber } = useMenuSetting();
const appStore = useAppStore()
const { getMiniWidthNumber } = useMenuSetting()
const getCollapsedWidth = computed(() => {
return unref(getMiniWidthNumber);
});
return unref(getMiniWidthNumber)
})
function onBreakpointChange(broken: boolean) {
appStore.setProjectConfig({
menuSetting: {
siderHidden: broken,
},
});
})
}
return { getCollapsedWidth, onBreakpointChange };
return { getCollapsedWidth, onBreakpointChange }
}
/**
* Handle related operations of menu folding
*/
export function useTrigger(getIsMobile: Ref<boolean>) {
const { getTrigger, getSplit } = useMenuSetting();
const { getTrigger, getSplit } = useMenuSetting()
const getShowTrigger = computed(() => {
const trigger = unref(getTrigger);
const trigger = unref(getTrigger)
return (
trigger !== TriggerEnum.NONE &&
!unref(getIsMobile) &&
(trigger === TriggerEnum.FOOTER || unref(getSplit))
);
});
)
})
const getTriggerAttr = computed(() => {
if (unref(getShowTrigger)) {
return {};
return {}
}
return {
trigger: null,
};
});
}
})
return { getTriggerAttr, getShowTrigger };
return { getTriggerAttr, getShowTrigger }
}
/**
@ -64,80 +64,80 @@ export function useTrigger(getIsMobile: Ref<boolean>) {
* @param dragBarRef
*/
export function useDragLine(siderRef: Ref<any>, dragBarRef: Ref<any>, mix = false) {
const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting();
const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting()
onMounted(() => {
nextTick(() => {
const exec = useDebounceFn(changeWrapWidth, 80);
exec();
});
});
const exec = useDebounceFn(changeWrapWidth, 80)
exec()
})
})
function getEl(elRef: Ref<ElRef | ComponentRef>): any {
const el = unref(elRef);
if (!el) return null;
const el = unref(elRef)
if (!el) return null
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) {
document.onmousemove = function (innerE) {
let iT = (ele as any).left + (innerE.clientX - clientX);
innerE = innerE || window.event;
const maxT = 800;
const minT = unref(getMiniWidthNumber);
iT < 0 && (iT = 0);
iT > maxT && (iT = maxT);
iT < minT && (iT = minT);
ele.style.left = wrap.style.width = iT + 'px';
return false;
};
let iT = (ele as any).left + (innerE.clientX - clientX)
innerE = innerE || window.event
const maxT = 800
const minT = unref(getMiniWidthNumber)
iT < 0 && (iT = 0)
iT > maxT && (iT = maxT)
iT < minT && (iT = minT)
ele.style.left = wrap.style.width = iT + 'px'
return false
}
}
// Drag and drop in the menu area-release the mouse
function removeMouseup(ele: any) {
const wrap = getEl(siderRef);
const wrap = getEl(siderRef)
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
wrap.style.transition = 'width 0.2s';
const width = parseInt(wrap.style.width);
document.onmousemove = null
document.onmouseup = null
wrap.style.transition = 'width 0.2s'
const width = parseInt(wrap.style.width)
if (!mix) {
const miniWidth = unref(getMiniWidthNumber);
const miniWidth = unref(getMiniWidthNumber)
if (!unref(getCollapsed)) {
width > miniWidth + 20
? setMenuSetting({ menuWidth: width })
: setMenuSetting({ collapsed: true });
: setMenuSetting({ collapsed: true })
} else {
width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width });
width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width })
}
} else {
setMenuSetting({ menuWidth: width });
setMenuSetting({ menuWidth: width })
}
ele.releaseCapture?.();
};
ele.releaseCapture?.()
}
}
function changeWrapWidth() {
const ele = getEl(dragBarRef);
if (!ele) return;
const wrap = getEl(siderRef);
if (!wrap) return;
const ele = getEl(dragBarRef)
if (!ele) return
const wrap = getEl(siderRef)
if (!wrap) return
ele.onmousedown = (e: any) => {
wrap.style.transition = 'unset';
const clientX = e?.clientX;
ele.left = ele.offsetLeft;
handleMouseMove(ele, wrap, clientX);
removeMouseup(ele);
ele.setCapture?.();
return false;
};
wrap.style.transition = 'unset'
const clientX = e?.clientX
ele.left = ele.offsetLeft
handleMouseMove(ele, wrap, clientX)
removeMouseup(ele)
ele.setCapture?.()
return false
}
}
return {};
return {}
}

View File

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

View File

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

View File

@ -1,80 +1,80 @@
import { toRaw, ref, nextTick } from 'vue';
import type { RouteLocationNormalized } from 'vue-router';
import { useDesign } from '/@/hooks/web/useDesign';
import { useSortable } from '/@/hooks/web/useSortable';
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import { isNullAndUnDef } from '/@/utils/is';
import projectSetting from '/@/settings/projectSetting';
import { useRouter } from 'vue-router';
import { toRaw, ref, nextTick } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import { useDesign } from '/@/hooks/web/useDesign'
import { useSortable } from '/@/hooks/web/useSortable'
import { useMultipleTabStore } from '/@/store/modules/multipleTab'
import { isNullAndUnDef } from '/@/utils/is'
import projectSetting from '/@/settings/projectSetting'
import { useRouter } from 'vue-router'
export function initAffixTabs(): string[] {
const affixList = ref<RouteLocationNormalized[]>([]);
const affixList = ref<RouteLocationNormalized[]>([])
const tabStore = useMultipleTabStore();
const router = useRouter();
const tabStore = useMultipleTabStore()
const router = useRouter()
/**
* @description: Filter all fixed routes
*/
function filterAffixTabs(routes: RouteLocationNormalized[]) {
const tabs: RouteLocationNormalized[] = [];
const tabs: RouteLocationNormalized[] = []
routes &&
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
tabs.push(toRaw(route));
tabs.push(toRaw(route))
}
});
return tabs;
})
return tabs
}
/**
* @description: Set fixed tabs
*/
function addAffixTabs(): void {
const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[]);
affixList.value = affixTabs;
const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[])
affixList.value = affixTabs
for (const tab of affixTabs) {
tabStore.addTab({
meta: tab.meta,
name: tab.name,
path: tab.path,
} as unknown as RouteLocationNormalized);
} as unknown as RouteLocationNormalized)
}
}
let isAddAffix = false;
let isAddAffix = false
if (!isAddAffix) {
addAffixTabs();
isAddAffix = true;
addAffixTabs()
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[]) {
const tabStore = useMultipleTabStore();
const { multiTabsSetting } = projectSetting;
const { prefixCls } = useDesign('multiple-tabs');
const tabStore = useMultipleTabStore()
const { multiTabsSetting } = projectSetting
const { prefixCls } = useDesign('multiple-tabs')
nextTick(() => {
if (!multiTabsSetting.canDrag) return;
if (!multiTabsSetting.canDrag) return
const el = document.querySelectorAll(
`.${prefixCls} .ant-tabs-nav-wrap > div`,
)?.[0] as HTMLElement;
)?.[0] as HTMLElement
const { initSortable } = useSortable(el, {
filter: (e: ChangeEvent) => {
const text = e?.target?.innerText;
if (!text) return false;
return affixTextList.includes(text);
const text = e?.target?.innerText
if (!text) return false
return affixTextList.includes(text)
},
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
const { oldIndex, newIndex } = evt
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>
<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 { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { getTransitionName } from './transition';
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'
import { getTransitionName } from './transition'
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import { useMultipleTabStore } from '/@/store/modules/multipleTab'
export default defineComponent({
name: 'PageLayout',
components: { FrameLayout },
setup() {
const { getShowMultipleTab } = useMultipleTabSetting();
const tabStore = useMultipleTabStore();
const { getShowMultipleTab } = useMultipleTabSetting()
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[] => {
if (!unref(getOpenKeepAlive)) {
return [];
return []
}
return tabStore.getCachedTabList;
});
return tabStore.getCachedTabList
})
return {
getTransitionName,
@ -66,7 +66,7 @@
getBasicTransition,
getCaches,
getCanEmbedIFramePage,
};
}
},
});
})
</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 { useUserStoreWithOut } from '/@/store/modules/user';
import { PageEnum } from '/@/enums/pageEnum'
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) {
const userStore = useUserStoreWithOut();
const permissionStore = usePermissionStoreWithOut();
const userStore = useUserStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
router.beforeEach(async (to, from, next) => {
if (
from.path === ROOT_PATH &&
@ -25,49 +25,49 @@ export function createPermissionGuard(router: Router) {
userStore.getUserInfo.homePath &&
userStore.getUserInfo.homePath !== PageEnum.BASE_HOME
) {
next(userStore.getUserInfo.homePath);
return;
next(userStore.getUserInfo.homePath)
return
}
const token = userStore.getToken;
const token = userStore.getToken
// Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) {
if (to.path === LOGIN_PATH && token) {
const isSessionTimeout = userStore.getSessionTimeout;
const isSessionTimeout = userStore.getSessionTimeout
try {
await userStore.afterLoginAction();
await userStore.afterLoginAction()
if (!isSessionTimeout) {
next((to.query?.redirect as string) || '/');
return;
next((to.query?.redirect as string) || '/')
return
}
} catch {}
}
next();
return;
next()
return
}
// token does not exist
if (!token) {
// You can access without permission. You need to set the routing meta.ignoreAuth to true
if (to.meta.ignoreAuth) {
next();
return;
next()
return
}
// redirect login page
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH,
replace: true,
};
}
if (to.path) {
redirectData.query = {
...redirectData.query,
redirect: to.path,
};
}
}
next(redirectData);
return;
next(redirectData)
return
}
// 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.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
) {
next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
return;
next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
return
}
// get userinfo while last fetch time is empty
if (userStore.getLastUpdateTime === 0) {
try {
await userStore.getUserInfoAction();
await userStore.getUserInfoAction()
} catch (err) {
next();
return;
next()
return
}
}
if (permissionStore.getIsDynamicAddedRoute) {
next();
return;
next()
return
}
const routes = await permissionStore.buildRoutesAction();
const routes = await permissionStore.buildRoutesAction()
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) {
// 动态添加路由后此处应当重定向到fullPath否则会加载404页面内容
next({ path: to.fullPath, replace: true, query: to.query });
next({ path: to.fullPath, replace: true, query: to.query })
} else {
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
next(nextData);
const redirectPath = (from.query.redirect || to.path) as string
const redirect = decodeURIComponent(redirectPath)
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
next(nextData)
}
});
})
}

View File

@ -1,20 +1,20 @@
import { AppRouteModule } from '/@/router/types';
import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
import { findPath, treeMap } from '/@/utils/helper/treeHelper';
import { cloneDeep } from 'lodash-es';
import { isUrl } from '/@/utils/is';
import { RouteParams } from 'vue-router';
import { toRaw } from 'vue';
import { AppRouteModule } from '/@/router/types'
import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'
import { findPath, treeMap } from '/@/utils/helper/treeHelper'
import { cloneDeep } from 'lodash-es'
import { isUrl } from '/@/utils/is'
import { RouteParams } from 'vue-router'
import { toRaw } from 'vue'
export function getAllParentPath<T = Recordable>(treeData: T[], path: string) {
const menuList = findPath(treeData, (n) => n.path === path) as Menu[];
return (menuList || []).map((item) => item.path);
const menuList = findPath(treeData, (n) => n.path === path) as Menu[]
return (menuList || []).map((item) => item.path)
}
// 路径处理
function joinParentPath(menus: Menu[], parentPath = '') {
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
// 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))) {
// path doesn't start with /, nor is it a url, join parent path
// 路径不以 / 开头,也不是 url加入父路径
menu.path = `${parentPath}/${menu.path}`;
menu.path = `${parentPath}/${menu.path}`
}
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
export function transformMenuModule(menuModule: MenuModule): Menu {
const { menu } = menuModule;
const { menu } = menuModule
const menuList = [menu];
const menuList = [menu]
joinParentPath(menuList);
return menuList[0];
joinParentPath(menuList)
return menuList[0]
}
// 将路由转换成菜单
export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) {
// 借助 lodash 深拷贝
const cloneRouteModList = cloneDeep(routeModList);
const routeList: AppRouteRecordRaw[] = [];
const cloneRouteModList = cloneDeep(routeModList)
const routeList: AppRouteRecordRaw[] = []
// 对路由项进行修改
cloneRouteModList.forEach((item) => {
if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') {
item.path = item.redirect;
item.path = item.redirect
}
if (item.meta?.single) {
const realItem = item?.children?.[0];
realItem && routeList.push(realItem);
const realItem = item?.children?.[0]
realItem && routeList.push(realItem)
} else {
routeList.push(item);
routeList.push(item)
}
});
})
// 提取树指定结构
const list = treeMap(routeList, {
conversion: (node: AppRouteRecordRaw) => {
const { meta: { title, hideMenu = false } = {} } = node;
const { meta: { title, hideMenu = false } = {} } = node
return {
...(node.meta || {}),
@ -72,35 +72,35 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi
hideMenu,
path: node.path,
...(node.redirect ? { redirect: node.redirect } : {}),
};
}
},
});
})
// 路径处理
joinParentPath(list);
return cloneDeep(list);
joinParentPath(list)
return cloneDeep(list)
}
/**
* config menu with given params
*/
const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g;
const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g
export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) {
const { path, paramPath } = toRaw(menu);
let realPath = paramPath ? paramPath : path;
const matchArr = realPath.match(menuParamRegex);
const { path, paramPath } = toRaw(menu)
let realPath = paramPath ? paramPath : path
const matchArr = realPath.match(menuParamRegex)
matchArr?.forEach((it) => {
const realIt = it.substr(1);
const realIt = it.substr(1)
if (params[realIt]) {
realPath = realPath.replace(`:${realIt}`, params[realIt] as string);
realPath = realPath.replace(`:${realIt}`, params[realIt] as string)
}
});
})
// save original param path.
if (!paramPath && matchArr && matchArr.length > 0) {
menu.paramPath = path;
menu.paramPath = path
}
menu.path = realPath;
menu.path = realPath
// 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 { Router, RouteRecordNormalized } from 'vue-router';
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'
import type { Router, RouteRecordNormalized } from 'vue-router'
import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant';
import { cloneDeep, omit } from 'lodash-es';
import { warn } from '/@/utils/log';
import { createRouter, createWebHashHistory } from 'vue-router';
import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'
import { cloneDeep, omit } from 'lodash-es'
import { warn } from '/@/utils/log'
import { createRouter, createWebHashHistory } from 'vue-router'
export type LayoutMapKey = 'LAYOUT';
const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue');
export type LayoutMapKey = 'LAYOUT'
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('IFRAME', IFRAME);
LayoutMap.set('LAYOUT', LAYOUT)
LayoutMap.set('IFRAME', IFRAME)
let dynamicViewsModules: Record<string, () => Promise<Recordable>>;
let dynamicViewsModules: Record<string, () => Promise<Recordable>>
// Dynamic introduction
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}');
if (!routes) return;
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}')
if (!routes) return
routes.forEach((item) => {
if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME';
item.component = 'IFRAME'
}
const { component, name } = item;
const { children } = item;
const { component, name } = item
const { children } = item
if (component) {
const layoutFound = LayoutMap.get(component.toUpperCase());
const layoutFound = LayoutMap.get(component.toUpperCase())
if (layoutFound) {
item.component = layoutFound;
item.component = layoutFound
} else {
item.component = dynamicImport(dynamicViewsModules, component as string);
item.component = dynamicImport(dynamicViewsModules, component as string)
}
} else if (name) {
item.component = getParentLayout();
item.component = getParentLayout()
}
children && asyncImportRoute(children);
});
children && asyncImportRoute(children)
})
}
function dynamicImport(
dynamicViewsModules: Record<string, () => Promise<Recordable>>,
component: string,
) {
const keys = Object.keys(dynamicViewsModules);
const keys = Object.keys(dynamicViewsModules)
const matchKeys = keys.filter((key) => {
const k = key.replace('../../views', '');
const startFlag = component.startsWith('/');
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx');
const startIndex = startFlag ? 0 : 1;
const lastIndex = endFlag ? k.length : k.lastIndexOf('.');
return k.substring(startIndex, lastIndex) === component;
});
const k = key.replace('../../views', '')
const startFlag = component.startsWith('/')
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx')
const startIndex = startFlag ? 0 : 1
const lastIndex = endFlag ? k.length : k.lastIndexOf('.')
return k.substring(startIndex, lastIndex) === component
})
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
const matchKey = matchKeys[0]
return dynamicViewsModules[matchKey]
} else if (matchKeys?.length > 1) {
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',
);
return;
)
return
} else {
warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!');
return EXCEPTION_COMPONENT;
warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!')
return EXCEPTION_COMPONENT
}
}
@ -71,26 +71,26 @@ function dynamicImport(
// 将背景对象变成路由对象
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
routeList.forEach((route) => {
const component = route.component as string;
const component = route.component as string
if (component) {
if (component.toUpperCase() === 'LAYOUT') {
route.component = LayoutMap.get(component.toUpperCase());
route.component = LayoutMap.get(component.toUpperCase())
} else {
route.children = [cloneDeep(route)];
route.component = LAYOUT;
route.name = `${route.name}Parent`;
route.path = '';
const meta = route.meta || {};
meta.single = true;
meta.affix = false;
route.meta = meta;
route.children = [cloneDeep(route)]
route.component = LAYOUT
route.name = `${route.name}Parent`
route.path = ''
const meta = route.meta || {}
meta.single = true
meta.affix = false
route.meta = meta
}
} else {
warn('请正确配置路由:' + route?.name + '的component属性');
warn('请正确配置路由:' + route?.name + '的component属性')
}
route.children && asyncImportRoute(route.children);
});
return routeList as unknown as T[];
route.children && asyncImportRoute(route.children)
})
return routeList as unknown as T[]
}
/**
@ -98,19 +98,19 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
* 2
*/
export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) {
const modules: AppRouteModule[] = cloneDeep(routeModules);
const modules: AppRouteModule[] = cloneDeep(routeModules)
for (let index = 0; index < modules.length; index++) {
const routeModule = modules[index];
const routeModule = modules[index]
// 判断级别是否 多级 路由
if (!isMultipleRoute(routeModule)) {
// 声明终止当前循环, 即跳过此次循环,进行下一轮
continue;
continue
}
// 路由等级提升
promoteRouteLevel(routeModule);
promoteRouteLevel(routeModule)
}
return modules;
return modules
}
// Routing level upgrade
@ -122,15 +122,15 @@ function promoteRouteLevel(routeModule: AppRouteModule) {
let router: Router | null = createRouter({
routes: [routeModule as unknown as RouteRecordNormalized],
history: createWebHashHistory(),
});
})
// getRoutes 获取所有 路由记录的完整列表。
const routes = router.getRoutes();
const routes = router.getRoutes()
// 将所有子路由添加到二级路由
addToChildren(routes, routeModule.children || [], routeModule);
router = null;
addToChildren(routes, routeModule.children || [], routeModule)
router = null
// 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
@ -141,17 +141,17 @@ function addToChildren(
routeModule: AppRouteModule,
) {
for (let index = 0; index < children.length; index++) {
const child = children[index];
const route = routes.find((item) => item.name === child.name);
const child = children[index]
const route = routes.find((item) => item.name === child.name)
if (!route) {
continue;
continue
}
routeModule.children = routeModule.children || [];
routeModule.children = routeModule.children || []
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) {
addToChildren(routes, child.children, routeModule);
addToChildren(routes, child.children, routeModule)
}
}
}
@ -161,18 +161,18 @@ function addToChildren(
function isMultipleRoute(routeModule: AppRouteModule) {
// Reflect.has 与 in 操作符 相同, 用于检查一个对象(包括它原型链上)是否拥有某个属性
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++) {
const child = children[index];
const child = children[index]
if (child.children?.length) {
flag = true;
break;
flag = true
break
}
}
return flag;
return flag
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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