64 changed files with 1859 additions and 930 deletions
@ -1,5 +1,6 @@ |
|||
import Button from './src/BasicButton.vue'; |
|||
import PopConfirmButton from './src/PopConfirmButton.vue'; |
|||
import { withInstall } from '../util'; |
|||
|
|||
withInstall(Button); |
|||
export { Button }; |
|||
withInstall(Button, PopConfirmButton); |
|||
export { Button, PopConfirmButton }; |
|||
|
|||
@ -0,0 +1,34 @@ |
|||
<script lang="ts"> |
|||
import { defineComponent, h, unref } from 'vue'; |
|||
|
|||
import { Popconfirm } from 'ant-design-vue'; |
|||
import BasicButton from './BasicButton.vue'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { extendSlots } from '/@/utils/helper/tsxHelper'; |
|||
import { omit } from 'lodash-es'; |
|||
const { t } = useI18n(); |
|||
|
|||
export default defineComponent({ |
|||
name: 'PopButton', |
|||
inheritAttrs: false, |
|||
components: { Popconfirm, BasicButton }, |
|||
props: { |
|||
enable: propTypes.bool.def(true), |
|||
okText: propTypes.string.def(t('component.drawer.okText')), |
|||
cancelText: propTypes.string.def(t('component.drawer.cancelText')), |
|||
}, |
|||
setup(props, { slots, attrs }) { |
|||
return () => { |
|||
const popValues = { ...props, ...unref(attrs) }; |
|||
|
|||
const Button = h(BasicButton, omit(unref(attrs), 'icon'), extendSlots(slots)); |
|||
if (!props.enable) { |
|||
return Button; |
|||
} |
|||
|
|||
return h(Popconfirm, omit(popValues, 'icon'), { default: () => Button }); |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -1 +1,4 @@ |
|||
export { createImgPreview } from './src/functional'; |
|||
|
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
export const ImagePreview = createAsyncComponent(() => import('./src/index.vue')); |
|||
|
|||
@ -0,0 +1,69 @@ |
|||
<template> |
|||
<PreviewGroup :class="prefixCls"> |
|||
<slot v-if="!imageList || $slots.default" /> |
|||
<template v-else> |
|||
<template v-for="item in getImageList" :key="item.src"> |
|||
<Image v-bind="item"> |
|||
<template #placeholder v-if="item.placeholder"> |
|||
<Image v-bind="item" :src="item.placeholder" :preview="false" /> |
|||
</template> |
|||
</Image> |
|||
</template> |
|||
</template> |
|||
</PreviewGroup> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { PropType } from 'vue'; |
|||
import { defineComponent, computed } from 'vue'; |
|||
|
|||
import { Image } from 'ant-design-vue'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
import { ImageItem } from './types'; |
|||
import { isString } from '/@/utils/is'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'ImagePreview', |
|||
components: { |
|||
Image, |
|||
PreviewGroup: Image.PreviewGroup, |
|||
}, |
|||
props: { |
|||
functional: propTypes.bool, |
|||
imageList: { |
|||
type: Array as PropType<ImageItem[]>, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('image-preview'); |
|||
|
|||
const getImageList = computed(() => { |
|||
const { imageList } = props; |
|||
if (!imageList) { |
|||
return []; |
|||
} |
|||
return imageList.map((item) => { |
|||
if (isString(item)) { |
|||
return { |
|||
src: item, |
|||
placeholder: false, |
|||
}; |
|||
} |
|||
return item; |
|||
}); |
|||
}); |
|||
|
|||
return { prefixCls, getImageList }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import (reference) '../../../design/index.less'; |
|||
@prefix-cls: ~'@{namespace}-image-preview'; |
|||
|
|||
.@{prefix-cls} { |
|||
.ant-image-preview-operations { |
|||
background: rgba(0, 0, 0, 0.4); |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,131 +0,0 @@ |
|||
import { defineComponent, PropType } from 'vue'; |
|||
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'; |
|||
import Icon from '/@/components/Icon/index'; |
|||
import { DownOutlined } from '@ant-design/icons-vue'; |
|||
import { ActionItem } from '/@/components/Table'; |
|||
import { Button } from '/@/components/Button'; |
|||
import { snowUuid } from '/@/utils/uuid'; |
|||
const prefixCls = 'basic-table-action'; |
|||
export default defineComponent({ |
|||
name: 'TableAction', |
|||
props: { |
|||
actions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
dropDownActions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
|
|||
moreText: { |
|||
type: String as PropType<string>, |
|||
default: '更多', |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
function renderButton(action: ActionItem) { |
|||
const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action; |
|||
const button = ( |
|||
<Button |
|||
type={type} |
|||
size="small" |
|||
disabled={disabled} |
|||
color={color} |
|||
{...actionProps} |
|||
key={`${snowUuid()}`} |
|||
> |
|||
{() => ( |
|||
<> |
|||
{icon && <Icon icon={icon} class="mr-1" />} |
|||
{label} |
|||
</> |
|||
)} |
|||
</Button> |
|||
); |
|||
return button; |
|||
} |
|||
|
|||
function renderPopConfirm(action: ActionItem) { |
|||
const { popConfirm = null } = action; |
|||
if (!popConfirm) { |
|||
return renderButton(action); |
|||
} |
|||
const { |
|||
title, |
|||
okText = '确定', |
|||
cancelText = '取消', |
|||
confirm = () => {}, |
|||
cancel = () => {}, |
|||
icon = '', |
|||
} = popConfirm; |
|||
return ( |
|||
<Popconfirm |
|||
key={`${snowUuid()}`} |
|||
title={title} |
|||
onConfirm={confirm} |
|||
onCancel={cancel} |
|||
okText={okText} |
|||
cancelText={cancelText} |
|||
icon={icon} |
|||
> |
|||
{() => renderButton(action)} |
|||
</Popconfirm> |
|||
); |
|||
} |
|||
|
|||
const dropdownDefaultSLot = () => ( |
|||
<Button type="link" size="small"> |
|||
{{ |
|||
default: () => ( |
|||
<> |
|||
{props.moreText} |
|||
<DownOutlined /> |
|||
</> |
|||
), |
|||
}} |
|||
</Button> |
|||
); |
|||
|
|||
// 增加按钮的TYPE和COLOR
|
|||
return () => { |
|||
const { dropDownActions = [], actions } = props; |
|||
return ( |
|||
<div class={prefixCls}> |
|||
{actions && |
|||
actions.map((action) => { |
|||
return renderPopConfirm(action); |
|||
})} |
|||
{dropDownActions && dropDownActions.length && ( |
|||
<Dropdown overlayClassName="basic-tale-action-dropdown"> |
|||
{{ |
|||
default: dropdownDefaultSLot, |
|||
overlay: () => { |
|||
return ( |
|||
<Menu> |
|||
{{ |
|||
default: () => { |
|||
return dropDownActions.map((action) => { |
|||
const { disabled = false } = action; |
|||
action.ghost = true; |
|||
return ( |
|||
<Menu.Item key={`${snowUuid()}`} disabled={disabled}> |
|||
{() => { |
|||
return renderPopConfirm(action); |
|||
}} |
|||
</Menu.Item> |
|||
); |
|||
}); |
|||
}, |
|||
}} |
|||
</Menu> |
|||
); |
|||
}, |
|||
}} |
|||
</Dropdown> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,126 @@ |
|||
<template> |
|||
<div :class="[prefixCls, getAlign]"> |
|||
<template v-for="(action, index) in getActions" :key="`${index}`"> |
|||
<PopConfirmButton v-bind="action"> |
|||
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" /> |
|||
{{ action.label }} |
|||
</PopConfirmButton> |
|||
<Divider type="vertical" v-if="divider && index < getActions.length" /> |
|||
</template> |
|||
|
|||
<Dropdown :trigger="['hover']" :dropMenuList="getDropList"> |
|||
<slot name="more" /> |
|||
<a-button type="link" size="small" v-if="!$slots.more"> |
|||
<MoreOutlined class="icon-more" /> |
|||
</a-button> |
|||
</Dropdown> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, PropType, computed } from 'vue'; |
|||
import Icon from '/@/components/Icon/index'; |
|||
import { ActionItem } from '/@/components/Table'; |
|||
import { PopConfirmButton } from '/@/components/Button'; |
|||
import { Divider } from 'ant-design-vue'; |
|||
import { Dropdown } from '/@/components/Dropdown'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { MoreOutlined } from '@ant-design/icons-vue'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
import { useTableContext } from '../hooks/useTableContext'; |
|||
import { ACTION_COLUMN_FLAG } from '../const'; |
|||
export default defineComponent({ |
|||
name: 'TableAction', |
|||
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined }, |
|||
props: { |
|||
actions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
dropDownActions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
divider: propTypes.bool.def(true), |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('basic-table-action'); |
|||
const table = useTableContext(); |
|||
const getActions = computed(() => { |
|||
return props.actions.map((action) => { |
|||
const { popConfirm } = action; |
|||
return { |
|||
...action, |
|||
...(popConfirm || {}), |
|||
onConfirm: popConfirm?.confirm, |
|||
onCancel: popConfirm?.cancel, |
|||
enable: !!popConfirm, |
|||
type: 'link', |
|||
size: 'small', |
|||
}; |
|||
}); |
|||
}); |
|||
|
|||
const getDropList = computed(() => { |
|||
return props.dropDownActions.map((action, index) => { |
|||
const { label } = action; |
|||
return { |
|||
...action, |
|||
text: label, |
|||
divider: index < props.dropDownActions.length - 1 ? props.divider : false, |
|||
}; |
|||
}); |
|||
}); |
|||
|
|||
const getAlign = computed(() => { |
|||
const columns = table.getColumns(); |
|||
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG); |
|||
return actionColumn?.align ?? 'left'; |
|||
}); |
|||
|
|||
return { prefixCls, getActions, getDropList, getAlign }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@prefix-cls: ~'@{namespace}-basic-table-action'; |
|||
|
|||
.@{prefix-cls} { |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
&.left { |
|||
justify-content: flex-start; |
|||
} |
|||
|
|||
&.center { |
|||
justify-content: center; |
|||
} |
|||
|
|||
&.right { |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
button { |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
span { |
|||
margin-left: 0 !important; |
|||
} |
|||
} |
|||
|
|||
.ant-divider, |
|||
.ant-divider-vertical { |
|||
margin: 0 2px; |
|||
} |
|||
|
|||
.icon-more { |
|||
transform: rotate(90deg); |
|||
|
|||
svg { |
|||
font-size: 1.1em; |
|||
font-weight: 700; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,85 @@ |
|||
<template> |
|||
<Table |
|||
v-if="summaryFunc" |
|||
:showHeader="false" |
|||
:bordered="false" |
|||
:pagination="false" |
|||
:dataSource="getDataSource" |
|||
:rowKey="(r) => r[rowKey]" |
|||
:columns="getColumns" |
|||
tableLayout="fixed" |
|||
:scroll="scroll" |
|||
/> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { PropType } from 'vue'; |
|||
|
|||
import { defineComponent, unref, computed, toRaw } from 'vue'; |
|||
import { Table } from 'ant-design-vue'; |
|||
import { cloneDeep } from 'lodash-es'; |
|||
import { isFunction } from '/@/utils/is'; |
|||
import type { BasicColumn } from '../types/table'; |
|||
import { INDEX_COLUMN_FLAG } from '../const'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
import { useTableContext } from '../hooks/useTableContext'; |
|||
|
|||
const SUMMARY_ROW_KEY = '_row'; |
|||
const SUMMARY_INDEX_KEY = '_index'; |
|||
export default defineComponent({ |
|||
name: 'BasicTableFooter', |
|||
components: { Table }, |
|||
props: { |
|||
summaryFunc: { |
|||
type: Function as PropType<Fn>, |
|||
}, |
|||
scroll: { |
|||
type: Object as PropType<Recordable>, |
|||
}, |
|||
rowKey: propTypes.string.def('key'), |
|||
}, |
|||
setup(props) { |
|||
const table = useTableContext(); |
|||
|
|||
const getDataSource = computed((): Recordable[] => { |
|||
const { summaryFunc } = props; |
|||
if (!isFunction(summaryFunc)) { |
|||
return []; |
|||
} |
|||
let dataSource = toRaw(unref(table.getDataSource())); |
|||
dataSource = summaryFunc(dataSource); |
|||
dataSource.forEach((item, i) => { |
|||
item[props.rowKey] = `${i}`; |
|||
}); |
|||
return dataSource; |
|||
}); |
|||
|
|||
const getColumns = computed(() => { |
|||
const dataSource = unref(getDataSource); |
|||
const columns: BasicColumn[] = cloneDeep(table.getColumns()); |
|||
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG); |
|||
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY)); |
|||
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY)); |
|||
|
|||
if (index !== -1) { |
|||
if (hasIndexSummary) { |
|||
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY]; |
|||
columns[index].ellipsis = false; |
|||
} else { |
|||
Reflect.deleteProperty(columns[index], 'customRender'); |
|||
} |
|||
} |
|||
if (table.getRowSelection() && hasRowSummary) { |
|||
columns.unshift({ |
|||
width: 60, |
|||
title: 'selection', |
|||
key: 'selectionKey', |
|||
align: 'center', |
|||
customRender: ({ record }) => record[SUMMARY_ROW_KEY], |
|||
}); |
|||
} |
|||
return columns; |
|||
}); |
|||
return { getColumns, getDataSource }; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,64 @@ |
|||
<template> |
|||
<slot name="tableTitle" v-if="$slots.tableTitle" /> |
|||
<TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" /> |
|||
|
|||
<div :class="`${prefixCls}__toolbar`"> |
|||
<slot name="toolbar" /> |
|||
<Divider type="vertical" v-if="$slots.toolbar" /> |
|||
<TableSetting :setting="tableSetting" v-if="showTableSetting" /> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { TableSetting } from '../types/table'; |
|||
import type { PropType } from 'vue'; |
|||
import { Divider } from 'ant-design-vue'; |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import TableSettingComp from './settings/index.vue'; |
|||
import TableTitle from './TableTitle.vue'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'BasicTableHeader', |
|||
components: { |
|||
Divider, |
|||
TableTitle, |
|||
TableSetting: TableSettingComp, |
|||
}, |
|||
props: { |
|||
title: { |
|||
type: [Function, String] as PropType<string | ((data: Recordable) => string)>, |
|||
}, |
|||
tableSetting: { |
|||
type: Object as PropType<TableSetting>, |
|||
}, |
|||
showTableSetting: { |
|||
type: Boolean, |
|||
}, |
|||
titleHelpMessage: { |
|||
type: [String, Array] as PropType<string | string[]>, |
|||
default: '', |
|||
}, |
|||
}, |
|||
setup() { |
|||
const { prefixCls } = useDesign('basic-table-header'); |
|||
return { prefixCls }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@prefix-cls: ~'@{namespace}-basic-table-header'; |
|||
|
|||
.@{prefix-cls} { |
|||
&__toolbar { |
|||
flex: 1; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: flex-end; |
|||
|
|||
> * { |
|||
margin-right: 8px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,283 +0,0 @@ |
|||
<template> |
|||
<div class="table-settings"> |
|||
<Divider type="vertical" /> |
|||
|
|||
<Tooltip placement="top" v-if="getSetting.redo"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingRedo') }}</span> |
|||
</template> |
|||
<RedoOutlined @click="redo" /> |
|||
</Tooltip> |
|||
|
|||
<Tooltip placement="top" v-if="getSetting.size"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingDens') }}</span> |
|||
</template> |
|||
<Dropdown placement="bottomCenter" :trigger="['click']"> |
|||
<ColumnHeightOutlined /> |
|||
<template #overlay> |
|||
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef"> |
|||
<MenuItem key="default"> |
|||
<span>{{ t('component.table.settingDensDefault') }}</span> |
|||
</MenuItem> |
|||
<MenuItem key="middle"> |
|||
<span>{{ t('component.table.settingDensMiddle') }}</span> |
|||
</MenuItem> |
|||
<MenuItem key="small"> |
|||
<span>{{ t('component.table.settingDensSmall') }}</span> |
|||
</MenuItem> |
|||
</Menu> |
|||
</template> |
|||
</Dropdown> |
|||
</Tooltip> |
|||
|
|||
<Tooltip placement="top" v-if="getSetting.setting"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingColumn') }}</span> |
|||
</template> |
|||
<Popover |
|||
placement="bottomLeft" |
|||
trigger="click" |
|||
overlayClassName="table-settings__cloumn-list" |
|||
> |
|||
<template #content> |
|||
<CheckboxGroup v-model:value="checkedList" @change="onChange"> |
|||
<template v-for="item in plainOptions" :key="item.value"> |
|||
<div class="table-settings__check-item"> |
|||
<Checkbox :value="item.value"> |
|||
{{ item.label }} |
|||
</Checkbox> |
|||
</div> |
|||
</template> |
|||
</CheckboxGroup> |
|||
</template> |
|||
<template #title> |
|||
<div class="table-settings__popover-title"> |
|||
<Checkbox |
|||
:indeterminate="indeterminate" |
|||
v-model:checked="checkAll" |
|||
@change="onCheckAllChange" |
|||
> |
|||
{{ t('component.table.settingColumnShow') }} |
|||
</Checkbox> |
|||
<a-button size="small" type="link" @click="reset"> |
|||
{{ t('component.table.settingReset') }}</a-button |
|||
> |
|||
</div> |
|||
</template> |
|||
<SettingOutlined /> |
|||
</Popover> |
|||
</Tooltip> |
|||
|
|||
<Tooltip placement="top" v-if="getSetting.fullScreen"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingFullScreen') }}</span> |
|||
</template> |
|||
<FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" /> |
|||
<FullscreenExitOutlined @click="handleFullScreen" v-else /> |
|||
</Tooltip> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, ref, reactive, toRefs, PropType, computed, watchEffect } from 'vue'; |
|||
import { injectTable } from '../hooks/useProvinceTable'; |
|||
import { Tooltip, Divider, Dropdown, Menu, Popover, Checkbox } from 'ant-design-vue'; |
|||
import { |
|||
RedoOutlined, |
|||
ColumnHeightOutlined, |
|||
FullscreenOutlined, |
|||
FullscreenExitOutlined, |
|||
SettingOutlined, |
|||
} from '@ant-design/icons-vue'; |
|||
import { useFullscreen } from '/@/hooks/web/useFullScreen'; |
|||
import type { SizeType, TableSetting } from '../types/table'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
interface Options { |
|||
label: string; |
|||
value: string; |
|||
} |
|||
interface State { |
|||
indeterminate: boolean; |
|||
checkAll: boolean; |
|||
// defaultColumns: BasicColumn[]; |
|||
// columns: BasicColumn[]; |
|||
checkedList: string[]; |
|||
defaultCheckList: string[]; |
|||
} |
|||
export default defineComponent({ |
|||
name: 'TableSetting', |
|||
components: { |
|||
RedoOutlined, |
|||
ColumnHeightOutlined, |
|||
FullscreenExitOutlined, |
|||
FullscreenOutlined, |
|||
SettingOutlined, |
|||
Popover, |
|||
Tooltip, |
|||
Divider, |
|||
Dropdown, |
|||
Checkbox, |
|||
CheckboxGroup: Checkbox.Group, |
|||
Menu, |
|||
MenuItem: Menu.Item, |
|||
}, |
|||
props: { |
|||
setting: { |
|||
type: Object as PropType<TableSetting>, |
|||
default: {}, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const table = injectTable(); |
|||
const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef); |
|||
const selectedKeysRef = ref<SizeType[]>([table.getSize()]); |
|||
|
|||
const plainOptions = ref<Options[]>([]); |
|||
const state = reactive<State>({ |
|||
indeterminate: false, |
|||
checkAll: true, |
|||
checkedList: [], |
|||
defaultCheckList: [], |
|||
}); |
|||
|
|||
const { t } = useI18n(); |
|||
|
|||
watchEffect(() => { |
|||
const columns = table.getColumns(); |
|||
if (columns.length) { |
|||
init(); |
|||
} |
|||
}); |
|||
|
|||
function init() { |
|||
let ret: Options[] = []; |
|||
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { |
|||
ret.push({ |
|||
label: item.title as string, |
|||
value: (item.dataIndex || item.title) as string, |
|||
}); |
|||
}); |
|||
if (!plainOptions.value.length) { |
|||
plainOptions.value = ret; |
|||
} |
|||
const checkList = table |
|||
.getColumns() |
|||
.map((item) => item.dataIndex || item.title) as string[]; |
|||
state.checkedList = checkList; |
|||
state.defaultCheckList = checkList; |
|||
} |
|||
|
|||
function handleTitleClick({ key }: { key: SizeType }) { |
|||
selectedKeysRef.value = [key]; |
|||
table.setProps({ |
|||
size: key, |
|||
}); |
|||
} |
|||
|
|||
function handleFullScreen() { |
|||
toggleFullscreen(); |
|||
} |
|||
|
|||
function onCheckAllChange(e: ChangeEvent) { |
|||
state.indeterminate = false; |
|||
const checkList = plainOptions.value.map((item) => item.value); |
|||
if (e.target.checked) { |
|||
state.checkedList = checkList; |
|||
table.setColumns(checkList); |
|||
} else { |
|||
state.checkedList = []; |
|||
table.setColumns([]); |
|||
} |
|||
} |
|||
|
|||
function onChange(checkedList: string[]) { |
|||
const len = plainOptions.value.length; |
|||
state.indeterminate = !!checkedList.length && checkedList.length < len; |
|||
state.checkAll = checkedList.length === len; |
|||
table.setColumns(checkedList); |
|||
} |
|||
|
|||
function reset() { |
|||
if (state.checkAll) return; |
|||
state.checkedList = [...state.defaultCheckList]; |
|||
state.checkAll = true; |
|||
state.indeterminate = false; |
|||
table.setColumns(state.defaultCheckList); |
|||
} |
|||
|
|||
const getSetting = computed( |
|||
(): TableSetting => { |
|||
return { |
|||
redo: true, |
|||
size: true, |
|||
setting: true, |
|||
fullScreen: true, |
|||
...props.setting, |
|||
}; |
|||
} |
|||
); |
|||
|
|||
return { |
|||
redo: () => table.reload(), |
|||
handleTitleClick, |
|||
selectedKeysRef, |
|||
handleFullScreen, |
|||
isFullscreenRef, |
|||
onCheckAllChange, |
|||
onChange, |
|||
plainOptions, |
|||
reset, |
|||
getSetting, |
|||
...toRefs(state), |
|||
t, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import (reference) '../../../../design/index.less'; |
|||
|
|||
.table-settings { |
|||
& > * { |
|||
margin-right: 12px; |
|||
} |
|||
|
|||
svg { |
|||
width: 1.2em; |
|||
height: 1.2em; |
|||
} |
|||
|
|||
&__popover-title { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
&__check-item { |
|||
width: 100%; |
|||
padding: 4px 16px 4px 16px; |
|||
|
|||
.ant-checkbox-wrapper { |
|||
width: 100%; |
|||
} |
|||
|
|||
&:hover { |
|||
background: fade(@primary-color, 10%); |
|||
} |
|||
} |
|||
|
|||
&__cloumn-list { |
|||
.ant-popover-inner-content { |
|||
max-height: 360px; |
|||
padding-right: 0; |
|||
padding-left: 0; |
|||
overflow: auto; |
|||
} |
|||
|
|||
.ant-checkbox-group { |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,64 +0,0 @@ |
|||
import { Table } from 'ant-design-vue'; |
|||
import { cloneDeep } from 'lodash-es'; |
|||
import { unref, ComputedRef } from 'vue'; |
|||
import { isFunction } from '/@/utils/is'; |
|||
import type { BasicColumn, TableRowSelection } from '../types/table'; |
|||
|
|||
export default ({ |
|||
scroll = {}, |
|||
columnsRef, |
|||
summaryFunc, |
|||
rowKey = 'key', |
|||
dataSourceRef, |
|||
rowSelectionRef, |
|||
}: { |
|||
scroll: { x?: number | true; y?: number }; |
|||
columnsRef: ComputedRef<BasicColumn[]>; |
|||
summaryFunc: any; |
|||
rowKey?: string; |
|||
dataSourceRef: ComputedRef<any[]>; |
|||
rowSelectionRef: ComputedRef<TableRowSelection | null>; |
|||
}) => { |
|||
if (!summaryFunc) { |
|||
return; |
|||
} |
|||
const dataSource: any[] = isFunction(summaryFunc) ? summaryFunc(unref(dataSourceRef)) : []; |
|||
const columns: BasicColumn[] = cloneDeep(unref(columnsRef)); |
|||
const index = columns.findIndex((item) => item.flag === 'INDEX'); |
|||
const hasRowSummary = dataSource.some((item) => Reflect.has(item, '_row')); |
|||
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, '_index')); |
|||
|
|||
if (index !== -1) { |
|||
if (hasIndexSummary) { |
|||
columns[index].customRender = ({ record }) => record._index; |
|||
columns[index].ellipsis = false; |
|||
} else { |
|||
Reflect.deleteProperty(columns[index], 'customRender'); |
|||
} |
|||
} |
|||
if (unref(rowSelectionRef) && hasRowSummary) { |
|||
columns.unshift({ |
|||
width: 60, |
|||
title: 'selection', |
|||
key: 'selectionKey', |
|||
align: 'center', |
|||
customRender: ({ record }) => record._row, |
|||
}); |
|||
} |
|||
|
|||
dataSource.forEach((item, i) => { |
|||
item[rowKey] = i; |
|||
}); |
|||
return ( |
|||
<Table |
|||
showHeader={false} |
|||
bordered={false} |
|||
pagination={false} |
|||
dataSource={dataSource} |
|||
rowKey={rowKey} |
|||
columns={columns} |
|||
tableLayout="fixed" |
|||
scroll={scroll as any} |
|||
/> |
|||
); |
|||
}; |
|||
@ -1,29 +0,0 @@ |
|||
import { Slots } from 'vue'; |
|||
import TableTitle from './TableTitle.vue'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
import TableSettingComp from './TableSetting.vue'; |
|||
|
|||
import type { TableSetting } from '../types/table'; |
|||
|
|||
export default ( |
|||
title: any, |
|||
titleHelpMessage: string | string[], |
|||
slots: Slots, |
|||
showTableSetting: boolean, |
|||
tableSetting: TableSetting |
|||
) => { |
|||
return ( |
|||
<> |
|||
{getSlot(slots, 'tableTitle') || |
|||
(title && <TableTitle helpMessage={titleHelpMessage} title={title} />) || ( |
|||
<span> </span> |
|||
)} |
|||
{ |
|||
<div class="basic-table-toolbar"> |
|||
{slots.toolbar && getSlot(slots, 'toolbar')} |
|||
{showTableSetting && <TableSettingComp setting={tableSetting} />} |
|||
</div> |
|||
} |
|||
</> |
|||
); |
|||
}; |
|||
@ -0,0 +1,430 @@ |
|||
<template> |
|||
<Tooltip placement="top"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingColumn') }}</span> |
|||
</template> |
|||
<Popover |
|||
:getPopupContainer="getPopupContainer" |
|||
placement="bottomLeft" |
|||
trigger="click" |
|||
@visibleChange="handleVisibleChange" |
|||
:overlayClassName="`${prefixCls}__cloumn-list`" |
|||
> |
|||
<template #title> |
|||
<div :class="`${prefixCls}__popover-title`"> |
|||
<Checkbox |
|||
:indeterminate="indeterminate" |
|||
v-model:checked="checkAll" |
|||
@change="onCheckAllChange" |
|||
> |
|||
{{ t('component.table.settingColumnShow') }} |
|||
</Checkbox> |
|||
|
|||
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange"> |
|||
{{ t('component.table.settingIndexColumnShow') }} |
|||
</Checkbox> |
|||
|
|||
<Checkbox |
|||
v-model:checked="checkSelect" |
|||
@change="handleSelectCheckChange" |
|||
:disabled="!defaultRowSelection" |
|||
> |
|||
{{ t('component.table.settingSelectColumnShow') }} |
|||
</Checkbox> |
|||
|
|||
<a-button size="small" type="link" @click="reset"> |
|||
{{ t('component.table.settingReset') }} |
|||
</a-button> |
|||
</div> |
|||
</template> |
|||
|
|||
<template #content> |
|||
<ScrollContainer> |
|||
<CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef"> |
|||
<template v-for="item in plainOptions" :key="item.value"> |
|||
<div :class="`${prefixCls}__check-item`"> |
|||
<DragOutlined class="table-coulmn-drag-icon" /> |
|||
<Checkbox :value="item.value"> {{ item.label }} </Checkbox> |
|||
|
|||
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4"> |
|||
<template #title> {{ t('component.table.settingFixedLeft') }}</template> |
|||
<Icon |
|||
icon="line-md:arrow-align-left" |
|||
:class="[ |
|||
`${prefixCls}__fixed-left`, |
|||
{ |
|||
active: item.fixed === 'left', |
|||
disabled: !checkedList.includes(item.value), |
|||
}, |
|||
]" |
|||
@click="handleColumnFixed(item, 'left')" |
|||
/> |
|||
</Tooltip> |
|||
<Divider type="vertical" /> |
|||
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4"> |
|||
<template #title> {{ t('component.table.settingFixedRight') }}</template> |
|||
<Icon |
|||
icon="line-md:arrow-align-left" |
|||
:class="[ |
|||
`${prefixCls}__fixed-right`, |
|||
{ |
|||
active: item.fixed === 'right', |
|||
disabled: !checkedList.includes(item.value), |
|||
}, |
|||
]" |
|||
@click="handleColumnFixed(item, 'right')" |
|||
/> |
|||
</Tooltip> |
|||
</div> |
|||
</template> |
|||
</CheckboxGroup> |
|||
</ScrollContainer> |
|||
</template> |
|||
<SettingOutlined /> |
|||
</Popover> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { |
|||
defineComponent, |
|||
ref, |
|||
reactive, |
|||
toRefs, |
|||
watchEffect, |
|||
nextTick, |
|||
unref, |
|||
computed, |
|||
} from 'vue'; |
|||
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue'; |
|||
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 { isNullAndUnDef } from '/@/utils/is'; |
|||
import { getPopupContainer } from '/@/utils'; |
|||
|
|||
import type { BasicColumn } from '../../types/table'; |
|||
|
|||
interface State { |
|||
indeterminate: boolean; |
|||
checkAll: boolean; |
|||
checkedList: string[]; |
|||
defaultCheckList: string[]; |
|||
} |
|||
|
|||
interface Options { |
|||
label: string; |
|||
value: string; |
|||
fixed?: boolean | 'left' | 'right'; |
|||
} |
|||
|
|||
export default defineComponent({ |
|||
name: 'ColumnSetting', |
|||
components: { |
|||
SettingOutlined, |
|||
Popover, |
|||
Tooltip, |
|||
Checkbox, |
|||
CheckboxGroup: Checkbox.Group, |
|||
DragOutlined, |
|||
ScrollContainer, |
|||
Divider, |
|||
Icon, |
|||
}, |
|||
|
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const table = useTableContext(); |
|||
|
|||
const defaultRowSelection = table.getRowSelection(); |
|||
let inited = false; |
|||
|
|||
const cachePlainOptions = ref<Options[]>([]); |
|||
const plainOptions = ref<Options[]>([]); |
|||
|
|||
const plainSortOptions = ref<Options[]>([]); |
|||
|
|||
const columnListRef = ref<ComponentRef>(null); |
|||
|
|||
const state = reactive<State>({ |
|||
indeterminate: false, |
|||
checkAll: true, |
|||
checkedList: [], |
|||
defaultCheckList: [], |
|||
}); |
|||
|
|||
const checkIndex = ref(false); |
|||
const checkSelect = ref(false); |
|||
|
|||
const { prefixCls } = useDesign('basic-column-setting'); |
|||
|
|||
const getValues = computed(() => { |
|||
return unref(table?.getBindValues) || {}; |
|||
}); |
|||
|
|||
watchEffect(() => { |
|||
const columns = table.getColumns(); |
|||
if (columns.length) { |
|||
init(); |
|||
} |
|||
}); |
|||
|
|||
watchEffect(() => { |
|||
const values = unref(getValues); |
|||
checkIndex.value = !!values.showIndexColumn; |
|||
checkSelect.value = !!values.rowSelection; |
|||
}); |
|||
|
|||
function getColumns() { |
|||
const ret: Options[] = []; |
|||
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { |
|||
ret.push({ |
|||
label: item.title as string, |
|||
value: (item.dataIndex || item.title) as string, |
|||
...item, |
|||
}); |
|||
}); |
|||
return ret; |
|||
} |
|||
|
|||
function init() { |
|||
const columns = getColumns(); |
|||
|
|||
const checkList = table |
|||
.getColumns() |
|||
.map((item) => { |
|||
if (item.defaultHidden) { |
|||
return ''; |
|||
} |
|||
return item.dataIndex || item.title; |
|||
}) |
|||
.filter(Boolean) as string[]; |
|||
|
|||
if (!plainOptions.value.length) { |
|||
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 = fixedColumns.find((fCol) => fCol.dataIndex === item.dataIndex); |
|||
if (findItem) { |
|||
item.fixed = findItem.fixed; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
state.checkedList = checkList; |
|||
} |
|||
|
|||
// checkAll change |
|||
function onCheckAllChange(e: ChangeEvent) { |
|||
state.indeterminate = false; |
|||
const checkList = plainOptions.value.map((item) => item.value); |
|||
if (e.target.checked) { |
|||
state.checkedList = checkList; |
|||
table.setColumns(checkList); |
|||
} else { |
|||
state.checkedList = []; |
|||
table.setColumns([]); |
|||
} |
|||
} |
|||
|
|||
// Trigger when check/uncheck a column |
|||
function onChange(checkedList: string[]) { |
|||
const len = plainOptions.value.length; |
|||
state.indeterminate = !!checkedList.length && checkedList.length < len; |
|||
state.checkAll = checkedList.length === len; |
|||
|
|||
const sortList = unref(plainSortOptions).map((item) => item.value); |
|||
checkedList.sort((prev, next) => { |
|||
return sortList.indexOf(prev) - sortList.indexOf(next); |
|||
}); |
|||
table.setColumns(checkedList); |
|||
} |
|||
|
|||
// reset columns |
|||
function reset() { |
|||
state.checkedList = [...state.defaultCheckList]; |
|||
state.checkAll = true; |
|||
state.indeterminate = false; |
|||
plainOptions.value = unref(cachePlainOptions); |
|||
plainSortOptions.value = unref(cachePlainOptions); |
|||
table.setColumns(table.getCacheColumns()); |
|||
} |
|||
|
|||
// Open the pop-up window for drag and drop initialization |
|||
function handleVisibleChange() { |
|||
if (inited) return; |
|||
nextTick(() => { |
|||
const columnListEl = unref(columnListRef); |
|||
if (!columnListEl) return; |
|||
const el = columnListEl.$el; |
|||
if (!el) return; |
|||
// Drag and drop sort |
|||
const { initSortable } = useSortable(el, { |
|||
handle: '.table-coulmn-drag-icon ', |
|||
onEnd: (evt) => { |
|||
const { oldIndex, newIndex } = evt; |
|||
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { |
|||
return; |
|||
} |
|||
// Sort column |
|||
const columns = getColumns(); |
|||
|
|||
if (oldIndex > newIndex) { |
|||
columns.splice(newIndex, 0, columns[oldIndex]); |
|||
columns.splice(oldIndex + 1, 1); |
|||
} else { |
|||
columns.splice(newIndex + 1, 0, columns[oldIndex]); |
|||
columns.splice(oldIndex, 1); |
|||
} |
|||
|
|||
plainSortOptions.value = columns; |
|||
plainOptions.value = columns; |
|||
table.setColumns(columns); |
|||
}, |
|||
}); |
|||
initSortable(); |
|||
inited = true; |
|||
}); |
|||
} |
|||
|
|||
// Control whether the serial number column is displayed |
|||
function handleIndexCheckChange(e: ChangeEvent) { |
|||
table.setProps({ |
|||
showIndexColumn: e.target.checked, |
|||
}); |
|||
} |
|||
|
|||
// Control whether the check box is displayed |
|||
function handleSelectCheckChange(e: ChangeEvent) { |
|||
table.setProps({ |
|||
rowSelection: e.target.checked ? defaultRowSelection : undefined, |
|||
}); |
|||
} |
|||
|
|||
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') { |
|||
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); |
|||
if (index !== -1) { |
|||
columns[index].fixed = isFixed; |
|||
} |
|||
item.fixed = isFixed; |
|||
|
|||
if (isFixed && !item.width) { |
|||
item.width = 100; |
|||
} |
|||
|
|||
table.setColumns(columns); |
|||
} |
|||
|
|||
return { |
|||
t, |
|||
...toRefs(state), |
|||
onCheckAllChange, |
|||
onChange, |
|||
plainOptions, |
|||
reset, |
|||
prefixCls, |
|||
columnListRef, |
|||
handleVisibleChange, |
|||
checkIndex, |
|||
checkSelect, |
|||
handleIndexCheckChange, |
|||
handleSelectCheckChange, |
|||
defaultRowSelection, |
|||
handleColumnFixed, |
|||
getPopupContainer, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@prefix-cls: ~'@{namespace}-basic-column-setting'; |
|||
|
|||
.table-coulmn-drag-icon { |
|||
margin: 0 5px; |
|||
cursor: move; |
|||
} |
|||
|
|||
.@{prefix-cls} { |
|||
&__popover-title { |
|||
position: relative; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
&__check-item { |
|||
display: flex; |
|||
align-items: center; |
|||
min-width: 100%; |
|||
padding: 4px 16px 8px 0; |
|||
|
|||
.ant-checkbox-wrapper { |
|||
width: 100%; |
|||
|
|||
&:hover { |
|||
color: @primary-color; |
|||
} |
|||
} |
|||
} |
|||
|
|||
&__fixed-left, |
|||
&__fixed-right { |
|||
color: rgba(0, 0, 0, 0.45); |
|||
cursor: pointer; |
|||
|
|||
&.active, |
|||
&:hover { |
|||
color: @primary-color; |
|||
} |
|||
|
|||
&.disabled { |
|||
color: @disabled-color; |
|||
cursor: not-allowed; |
|||
} |
|||
} |
|||
|
|||
&__fixed-right { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
&__cloumn-list { |
|||
svg { |
|||
width: 1em !important; |
|||
height: 1em !important; |
|||
} |
|||
|
|||
.ant-popover-inner-content { |
|||
// max-height: 360px; |
|||
padding-right: 0; |
|||
padding-left: 0; |
|||
// overflow: auto; |
|||
} |
|||
|
|||
.ant-checkbox-group { |
|||
width: 100%; |
|||
min-width: 260px; |
|||
// flex-wrap: wrap; |
|||
} |
|||
|
|||
.scroll-container { |
|||
height: 220px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,43 @@ |
|||
<template> |
|||
<Tooltip placement="top"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingFullScreen') }}</span> |
|||
</template> |
|||
<FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" /> |
|||
<FullscreenExitOutlined @click="handleFullScreen" v-else /> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { useTableContext } from '../../hooks/useTableContext'; |
|||
import { Tooltip } from 'ant-design-vue'; |
|||
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue'; |
|||
import { useFullscreen } from '/@/hooks/web/useFullScreen'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'FullScreenSetting', |
|||
components: { |
|||
FullscreenExitOutlined, |
|||
FullscreenOutlined, |
|||
Tooltip, |
|||
}, |
|||
|
|||
setup() { |
|||
const table = useTableContext(); |
|||
const { t } = useI18n(); |
|||
|
|||
const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef); |
|||
|
|||
function handleFullScreen() { |
|||
toggleFullscreen(); |
|||
} |
|||
|
|||
return { |
|||
handleFullScreen, |
|||
isFullscreenRef, |
|||
t, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,37 @@ |
|||
<template> |
|||
<Tooltip placement="top"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingRedo') }}</span> |
|||
</template> |
|||
<RedoOutlined @click="redo" /> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { useTableContext } from '../../hooks/useTableContext'; |
|||
import { Tooltip } from 'ant-design-vue'; |
|||
import { RedoOutlined } from '@ant-design/icons-vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'RedoSetting', |
|||
components: { |
|||
RedoOutlined, |
|||
Tooltip, |
|||
}, |
|||
|
|||
setup() { |
|||
const table = useTableContext(); |
|||
const { t } = useI18n(); |
|||
|
|||
function redo() { |
|||
table.reload(); |
|||
} |
|||
|
|||
return { |
|||
redo, |
|||
t, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,65 @@ |
|||
<template> |
|||
<Tooltip placement="top"> |
|||
<template #title> |
|||
<span>{{ t('component.table.settingDens') }}</span> |
|||
</template> |
|||
|
|||
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer"> |
|||
<ColumnHeightOutlined /> |
|||
<template #overlay> |
|||
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef"> |
|||
<MenuItem key="default"> |
|||
<span>{{ t('component.table.settingDensDefault') }}</span> |
|||
</MenuItem> |
|||
<MenuItem key="middle"> |
|||
<span>{{ t('component.table.settingDensMiddle') }}</span> |
|||
</MenuItem> |
|||
<MenuItem key="small"> |
|||
<span>{{ t('component.table.settingDensSmall') }}</span> |
|||
</MenuItem> |
|||
</Menu> |
|||
</template> |
|||
</Dropdown> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, ref } from 'vue'; |
|||
import { useTableContext } from '../../hooks/useTableContext'; |
|||
import { Tooltip, Dropdown, Menu } from 'ant-design-vue'; |
|||
import { ColumnHeightOutlined } from '@ant-design/icons-vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { getPopupContainer } from '/@/utils'; |
|||
|
|||
import type { SizeType } from '../../types/table'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'SizeSetting', |
|||
components: { |
|||
ColumnHeightOutlined, |
|||
Tooltip, |
|||
Dropdown, |
|||
Menu, |
|||
MenuItem: Menu.Item, |
|||
}, |
|||
setup() { |
|||
const table = useTableContext(); |
|||
const { t } = useI18n(); |
|||
|
|||
const selectedKeysRef = ref<SizeType[]>([table.getSize()]); |
|||
|
|||
function handleTitleClick({ key }: { key: SizeType }) { |
|||
selectedKeysRef.value = [key]; |
|||
table.setProps({ |
|||
size: key, |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
handleTitleClick, |
|||
selectedKeysRef, |
|||
getPopupContainer, |
|||
t, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,61 @@ |
|||
<template> |
|||
<div class="table-settings"> |
|||
<RedoSetting v-if="getSetting.size" /> |
|||
<SizeSetting v-if="getSetting.redo" /> |
|||
|
|||
<ColumnSetting v-if="getSetting.setting" /> |
|||
|
|||
<FullScreenSetting v-if="getSetting.fullScreen" /> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, PropType, computed } from 'vue'; |
|||
import type { TableSetting } from '../../types/table'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
import ColumnSetting from './ColumnSetting.vue'; |
|||
export default defineComponent({ |
|||
name: 'TableSetting', |
|||
components: { |
|||
ColumnSetting, |
|||
SizeSetting: createAsyncComponent(() => import('./SizeSetting.vue')), |
|||
RedoSetting: createAsyncComponent(() => import('./RedoSetting.vue')), |
|||
FullScreenSetting: createAsyncComponent(() => import('./FullScreenSetting.vue')), |
|||
}, |
|||
props: { |
|||
setting: { |
|||
type: Object as PropType<TableSetting>, |
|||
default: {}, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const { t } = useI18n(); |
|||
|
|||
const getSetting = computed( |
|||
(): TableSetting => { |
|||
return { |
|||
redo: true, |
|||
size: true, |
|||
setting: true, |
|||
fullScreen: true, |
|||
...props.setting, |
|||
}; |
|||
} |
|||
); |
|||
|
|||
return { getSetting, t }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
.table-settings { |
|||
& > * { |
|||
margin-right: 12px; |
|||
} |
|||
|
|||
svg { |
|||
width: 1.3em; |
|||
height: 1.3em; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,23 @@ |
|||
import type { Ref } from 'vue'; |
|||
import type { BasicTableProps, TableActionType } from '../types/table'; |
|||
|
|||
import { provide, inject, ComputedRef } from 'vue'; |
|||
|
|||
const key = Symbol('basic-table'); |
|||
|
|||
type Instance = TableActionType & { |
|||
wrapRef: Ref<Nullable<HTMLElement>>; |
|||
getBindValues: ComputedRef<Recordable>; |
|||
}; |
|||
|
|||
type RetInstance = Omit<Instance, 'getBindValues'> & { |
|||
getBindValues: ComputedRef<BasicTableProps>; |
|||
}; |
|||
|
|||
export function createTableContext(instance: Instance) { |
|||
provide(key, instance); |
|||
} |
|||
|
|||
export function useTableContext(): RetInstance { |
|||
return inject(key) as RetInstance; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
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: Nullable<number>; |
|||
scrollToFirstRowOnChange: boolean; |
|||
}>, |
|||
tableElRef: Ref<ComponentRef>, |
|||
getDataSourceRef: ComputedRef<Recordable> |
|||
) { |
|||
const getIsEmptyData = computed(() => { |
|||
return (unref(getDataSourceRef) || []).length === 0; |
|||
}); |
|||
|
|||
const getFooterProps = computed((): Recordable | undefined => { |
|||
const { summaryFunc, showSummary } = unref(propsRef); |
|||
return showSummary && !unref(getIsEmptyData) |
|||
? () => h(TableFooter, { summaryFunc, scroll: unref(scrollRef) }) |
|||
: undefined; |
|||
}); |
|||
|
|||
watchEffect(() => { |
|||
handleSummary(); |
|||
}); |
|||
|
|||
function handleSummary() { |
|||
const { showSummary } = unref(propsRef); |
|||
if (!showSummary || unref(getIsEmptyData)) return; |
|||
|
|||
nextTick(() => { |
|||
const tableEl = unref(tableElRef); |
|||
if (!tableEl) return; |
|||
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body'); |
|||
const bodyDom = bodyDomList[0]; |
|||
useEventListener({ |
|||
el: bodyDom, |
|||
name: 'scroll', |
|||
listener: () => { |
|||
const footerBodyDom = tableEl.$el.querySelector( |
|||
'.ant-table-footer .ant-table-body' |
|||
) as HTMLDivElement; |
|||
if (!footerBodyDom || !bodyDom) return; |
|||
footerBodyDom.scrollLeft = bodyDom.scrollLeft; |
|||
}, |
|||
wait: 0, |
|||
options: true, |
|||
}); |
|||
}); |
|||
} |
|||
return { getFooterProps }; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
import type { ComputedRef, Slots } from 'vue'; |
|||
import type { BasicTableProps, FetchParams } from '../types/table'; |
|||
import { unref, computed } from 'vue'; |
|||
import type { FormProps } from '/@/components/Form'; |
|||
import { isFunction } from '/@/utils/is'; |
|||
export function useTableForm( |
|||
propsRef: ComputedRef<BasicTableProps>, |
|||
slots: Slots, |
|||
fetch: (opt?: FetchParams | undefined) => Promise<void> |
|||
) { |
|||
const getFormProps = computed( |
|||
(): Partial<FormProps> => { |
|||
const { formConfig } = unref(propsRef); |
|||
return { |
|||
showAdvancedButton: true, |
|||
...formConfig, |
|||
compact: true, |
|||
}; |
|||
} |
|||
); |
|||
|
|||
const getFormSlotKeys = computed(() => { |
|||
const keys = Object.keys(slots); |
|||
return keys.map((item) => (item.startsWith('form-') ? item : null)).filter(Boolean); |
|||
}); |
|||
|
|||
function replaceFormSlotKey(key: string) { |
|||
if (!key) return ''; |
|||
return key?.replace?.(/form\-/, '') ?? ''; |
|||
} |
|||
|
|||
function handleSearchInfoChange(info: Recordable) { |
|||
const { handleSearchInfoFn } = unref(propsRef); |
|||
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) { |
|||
info = handleSearchInfoFn(info) || info; |
|||
} |
|||
fetch({ searchInfo: info, page: 1 }); |
|||
} |
|||
|
|||
return { |
|||
getFormProps, |
|||
replaceFormSlotKey, |
|||
getFormSlotKeys, |
|||
handleSearchInfoChange, |
|||
}; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
import type { ComputedRef, Slots } from 'vue'; |
|||
import type { BasicTableProps } from '../types/table'; |
|||
import { unref, computed, h } from 'vue'; |
|||
import { isString } from '/@/utils/is'; |
|||
import TableHeader from '../components/TableHeader.vue'; |
|||
import { getSlot } from '../../../../utils/helper/tsxHelper'; |
|||
|
|||
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots) { |
|||
const getHeaderProps = computed( |
|||
(): Recordable => { |
|||
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef); |
|||
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting; |
|||
if (hideTitle && !isString(title)) { |
|||
return {}; |
|||
} |
|||
|
|||
return { |
|||
title: hideTitle |
|||
? null |
|||
: () => |
|||
h( |
|||
TableHeader, |
|||
{ |
|||
title, |
|||
titleHelpMessage, |
|||
showTableSetting, |
|||
tableSetting, |
|||
}, |
|||
{ |
|||
...(slots.toolbar |
|||
? { |
|||
toolbar: () => getSlot(slots, 'toolbar'), |
|||
} |
|||
: {}), |
|||
...(slots.tableTitle |
|||
? { |
|||
tableTitle: () => getSlot(slots, 'tableTitle'), |
|||
} |
|||
: {}), |
|||
} |
|||
), |
|||
}; |
|||
} |
|||
); |
|||
return { getHeaderProps }; |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
@import 'color.less'; |
|||
@import 'var/index.less'; |
|||
@import 'mixins.less'; |
|||
@ -0,0 +1,19 @@ |
|||
import Sortable from 'sortablejs'; |
|||
import { nextTick, unref } from 'vue'; |
|||
import type { Ref } from 'vue'; |
|||
|
|||
export function useSortable(el: HTMLElement | Ref<HTMLElement>, options?: Sortable.Options) { |
|||
function initSortable() { |
|||
nextTick(() => { |
|||
if (!el) return; |
|||
Sortable.create(unref(el), { |
|||
animation: 500, |
|||
delay: 400, |
|||
delayOnTouchOnly: true, |
|||
...options, |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return { initSortable }; |
|||
} |
|||
Loading…
Reference in new issue