72 changed files with 6966 additions and 14 deletions
@ -1,9 +1,12 @@ |
|||
import type { ButtonProps as AndtButtonProps } from 'ant-design-vue'; |
|||
import { withInstall } from '@/utils'; |
|||
import type { ExtractPropTypes } from 'vue'; |
|||
import button from './src/BasicButton.vue'; |
|||
import popConfirmButton from './src/PopConfirmButton.vue'; |
|||
import { buttonProps } from './src/props'; |
|||
import popConfirmVxeButton from './src/PopConfirmVxeButton.vue'; |
|||
|
|||
export const Button = withInstall(button); |
|||
export const PopConfirmButton = withInstall(popConfirmButton); |
|||
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>; |
|||
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>> & AndtButtonProps; |
|||
export const PopConfirmVxeButton = withInstall(popConfirmVxeButton); |
|||
|
|||
@ -0,0 +1,53 @@ |
|||
<script lang="ts"> |
|||
import { computed, defineComponent, h, unref, useAttrs } from 'vue'; |
|||
import { Popconfirm } from 'ant-design-vue'; |
|||
import { extendSlots } from '@/utils/helper/tsxHelper'; |
|||
import { omit } from 'lodash-es'; |
|||
import { useI18n } from '@/hooks/web/useI18n'; |
|||
import { VxeButton } from 'vxe-table'; |
|||
|
|||
const props = { |
|||
/** |
|||
* Whether to enable the drop-down menu |
|||
* @default: true |
|||
*/ |
|||
enable: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
}; |
|||
|
|||
export default defineComponent({ |
|||
name: 'PopConfirmVxeButton', |
|||
inheritAttrs: false, |
|||
props, |
|||
setup(props, { slots }) { |
|||
const { t } = useI18n(); |
|||
const attrs = useAttrs(); |
|||
|
|||
// get inherit binding value |
|||
const getBindValues = computed(() => { |
|||
return Object.assign( |
|||
{ |
|||
okText: t('common.okText'), |
|||
cancelText: t('common.cancelText'), |
|||
}, |
|||
{ ...props, ...unref(attrs) }, |
|||
); |
|||
}); |
|||
|
|||
return () => { |
|||
const bindValues = omit(unref(getBindValues), 'icon'); |
|||
const btnBind = omit(bindValues, 'title') as Recordable; |
|||
if (btnBind.disabled) btnBind.color = ''; |
|||
const Button = h(VxeButton, btnBind, extendSlots(slots)); |
|||
|
|||
// If it is not enabled, it is a normal button |
|||
if (!props.enable) { |
|||
return Button; |
|||
} |
|||
return h(Popconfirm, bindValues, { default: () => Button }); |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -1,4 +1,5 @@ |
|||
import SvgIcon from './src/SvgIcon.vue'; |
|||
import IconPicker from './src/IconPicker.vue'; |
|||
import Icon from './Icon.vue'; |
|||
|
|||
export { IconPicker, SvgIcon }; |
|||
export { IconPicker, SvgIcon, Icon }; |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
import TableSearchLayout from './src/TableSearchLayout'; |
|||
|
|||
export { TableSearchLayout }; |
|||
@ -0,0 +1,23 @@ |
|||
.smart-table-search-layout { |
|||
display: flex; |
|||
flex-flow: column; |
|||
height: 100%; |
|||
} |
|||
|
|||
.smart-search-container { |
|||
width: 100%; |
|||
margin-bottom: 6px; |
|||
padding: 12px 10px 0; |
|||
background-color: @component-background; |
|||
} |
|||
|
|||
.smart-table-container { |
|||
flex: 1; |
|||
min-height: 1px; |
|||
padding: 0 10px 5px; |
|||
background-color: @component-background; |
|||
} |
|||
|
|||
.smart-table-search-hidden { |
|||
display: none; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
import { defineComponent, computed, unref, toRefs } from 'vue'; |
|||
import { propTypes } from '@/utils/propTypes'; |
|||
|
|||
import './TableSearchLayout.less'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'TableSearchLayout', |
|||
props: { |
|||
/** |
|||
* 是否显示搜索栏 |
|||
*/ |
|||
showSearch: propTypes.bool.def(true), |
|||
}, |
|||
setup(props, { slots }) { |
|||
const { showSearch: showSearchRef } = toRefs(props); |
|||
const { search, default: tableSlot } = slots; |
|||
const hasSearchForm = computed(() => search !== undefined); |
|||
|
|||
// const getTableContainerStyle = computed((): StyleValue => {
|
|||
// let height = '100%'
|
|||
// if (unref(hasSearchForm) && unref(showSearchRef)) {
|
|||
// height = 'calc(100% - 70px)'
|
|||
// }
|
|||
// return {
|
|||
// height,
|
|||
// }
|
|||
// })
|
|||
|
|||
const getSearchContainerClass = computed(() => { |
|||
const classList = ['smart-search-container']; |
|||
if (!unref(showSearchRef)) { |
|||
classList.push('smart-table-search-hidden'); |
|||
} |
|||
return classList; |
|||
}); |
|||
|
|||
return () => ( |
|||
<div class="smart-table-search-layout"> |
|||
{unref(hasSearchForm) ? ( |
|||
<div class={unref(getSearchContainerClass)}>{search && search()}</div> |
|||
) : ( |
|||
'' |
|||
)} |
|||
<div class="smart-table-container">{tableSlot && tableSlot()}</div> |
|||
</div> |
|||
); |
|||
}, |
|||
}); |
|||
@ -0,0 +1 @@ |
|||
export { default as VXETablePluginAntd } from './src/renderer/VxeTableAntdRenderer'; |
|||
@ -0,0 +1,17 @@ |
|||
export { default as SmartTable } from './src/SmartTable'; |
|||
export { default as TableAction } from './src/components/TableAction.vue'; |
|||
export { default as SmartVxeTableAction } from './src/components/SmartVxeTableAction.vue'; |
|||
|
|||
export { useSmartTable } from './src/hooks/useSmartTable'; |
|||
|
|||
export * from './src/types/SmartTableType'; |
|||
|
|||
export * from './src/types/SmartSearchFormType'; |
|||
|
|||
export * from './src/types/SmartTableAddEditType'; |
|||
|
|||
export * from './src/types/SmartTableActionType'; |
|||
export * from './src/types/SmartTableButton'; |
|||
export * from './src/types/SmartTableColumnType'; |
|||
export * from './src/types/SmartTableAuthType'; |
|||
export * from './src/types/SmartTableToolbarConfigType'; |
|||
@ -0,0 +1,97 @@ |
|||
@import './style/SmartTableColumnConfig'; |
|||
|
|||
/* 表格样式 */ |
|||
.smart-table { |
|||
.vxe-tools--wrapper { |
|||
margin-right: 12px; |
|||
} |
|||
|
|||
/* 文本颜色 */ |
|||
|
|||
.text-color--success { |
|||
color: green; |
|||
} |
|||
|
|||
.text-color--lime { |
|||
color: #a0d911; |
|||
} |
|||
|
|||
.text-color--link { |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.text-color--warning { |
|||
color: #faad14; |
|||
} |
|||
|
|||
.text-color--warning-add { |
|||
color: #fa8c16; |
|||
} |
|||
|
|||
.text-color--danger { |
|||
color: #f5222d; |
|||
} |
|||
|
|||
/* 文本颜色加粗 */ |
|||
|
|||
.text-color--success-bold { |
|||
color: green; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.text-color--lime-bold { |
|||
color: #a0d911; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.text-color--link-bold { |
|||
color: #1890ff; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.text-color--warning-bold { |
|||
color: #faad14; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.text-color--warning-add-bold { |
|||
color: #fa8c16; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.text-color--danger-bold { |
|||
color: #f5222d; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
/* 背景色 */ |
|||
|
|||
.bg--success { |
|||
background: #7ce57cad; |
|||
} |
|||
|
|||
.bg--lime { |
|||
background: #a0d911; |
|||
} |
|||
|
|||
.bg--link { |
|||
background: #1890ff; |
|||
} |
|||
|
|||
.bg--warning { |
|||
background: #faad14; |
|||
} |
|||
|
|||
.bg--warning-add { |
|||
background: #fa8c16; |
|||
} |
|||
|
|||
.bg--danger { |
|||
background: #f5222d; |
|||
} |
|||
} |
|||
|
|||
/* 表单内label样式 */ |
|||
.text-color--danger > label { |
|||
color: #f5222d; |
|||
} |
|||
@ -0,0 +1,434 @@ |
|||
import type { SmartTableProps, TableActionType } from './types/SmartTableType'; |
|||
import type { VxeGridInstance } from 'vxe-table'; |
|||
|
|||
import { computed, defineComponent, provide, Ref, ref, unref } from 'vue'; |
|||
|
|||
import { TableSearchLayout } from '@/components/Layout'; |
|||
import { BasicForm } from '@/components/Form'; |
|||
import { useModal } from '@/components/Modal'; |
|||
import { useI18n } from '@/hooks/web/useI18n'; |
|||
|
|||
import { smartTableProps } from './props'; |
|||
import { useTableSearchForm } from './hooks/useTableSearchForm'; |
|||
import { useLoading } from './hooks/useLoading'; |
|||
import { usePagination } from './hooks/usePagination'; |
|||
import { useTableAjax } from './hooks/useTableAjax'; |
|||
import { useTableToolbarConfig } from './hooks/useTableToolbarConfig'; |
|||
import { useTableModalAddEditConfig } from './hooks/useTableModalAddEdit'; |
|||
import { createTableContext } from './hooks/userSmartTableContext'; |
|||
import { useTableRowDrag } from './hooks/useTableDrag'; |
|||
import { useSmartTableColumn } from './hooks/useSmartTableColumn'; |
|||
import SmartTableAddEditModal from './components/SmartTableAddEditModal'; |
|||
import './renderer/VxeTableButtonRenderer'; |
|||
import { error } from '@/utils/log'; |
|||
|
|||
import './SmartTable.less'; |
|||
import { useToolbarEvent } from './hooks/useToolbarEvent'; |
|||
import { useSmartTableCheckbox } from './hooks/useSmartTableCheckbox'; |
|||
import { useSmartTableDynamicClassStyle } from './hooks/useSmartTableDynamicClassStyle'; |
|||
import { useSmartTableColumnConfig } from '@/components/SmartTable/src/hooks/useSmartTableColumnConfig'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'SmartTable', |
|||
props: smartTableProps, |
|||
emits: [ |
|||
'register', |
|||
'toolbar-tool-click', |
|||
'proxy-query', |
|||
'proxy-delete', |
|||
'add-edit-modal-show', |
|||
'cell-click', |
|||
], |
|||
setup(props, { emit, slots, attrs }) { |
|||
const { t } = useI18n(); |
|||
const tableElRef = ref<VxeGridInstance>() as Ref<VxeGridInstance>; |
|||
const wrapRef = ref(null); |
|||
|
|||
const innerPropsRef = ref<Partial<SmartTableProps>>(); |
|||
/** |
|||
* 表格计算属性 |
|||
*/ |
|||
const getTableProps = computed(() => { |
|||
return { |
|||
...props, |
|||
...unref(innerPropsRef), |
|||
}; |
|||
}); |
|||
|
|||
const setProps = (props: Partial<SmartTableProps>) => { |
|||
innerPropsRef.value = { ...unref(innerPropsRef), ...props }; |
|||
}; |
|||
|
|||
// -------------- 分页 ---------------------------
|
|||
const { |
|||
getPaginationInfo, |
|||
getPagination, |
|||
setPagination, |
|||
getShowPagination, |
|||
setShowPagination, |
|||
} = usePagination(getTableProps); |
|||
|
|||
/** |
|||
* vxe-table函数 |
|||
*/ |
|||
const getTableInstance = () => unref(tableElRef); |
|||
provide('getTableInstance', getTableInstance); |
|||
const commitVxeProxy = (code, ...args) => getTableInstance()?.commitProxy(code, args); |
|||
const getCheckboxRecords = (isFull: boolean) => |
|||
getTableInstance()?.getCheckboxRecords(isFull) || []; |
|||
const getRadioRecord = (isFull: boolean) => getTableInstance()?.getRadioRecord(isFull); |
|||
const setRadioRow = (row: any) => getTableInstance()!.setRadioRow(row); |
|||
const setCheckboxRow = (rows: any | any[], checked: boolean) => |
|||
getTableInstance()!.setCheckboxRow(rows, checked); |
|||
|
|||
/** |
|||
* 加载状态 |
|||
*/ |
|||
const { getLoading, setLoading } = useLoading(getTableProps); |
|||
|
|||
// -------------- 搜索表单 ------------------------
|
|||
const { |
|||
getSearchFormProps, |
|||
handleSearchInfoChange, |
|||
getSearchFormSlot, |
|||
getSearchFormColumnSlot, |
|||
registerSearchForm, |
|||
searchFormAction, |
|||
getSearchFormVisible, |
|||
} = useTableSearchForm( |
|||
getTableProps, |
|||
slots, |
|||
(params) => query(params), |
|||
getLoading, |
|||
setPagination, |
|||
); |
|||
|
|||
// -------------- 加载函数 ------------------------
|
|||
const { |
|||
reload, |
|||
query, |
|||
getProxyConfigRef, |
|||
deleteByRow, |
|||
deleteByCheckbox, |
|||
getProxyEvents, |
|||
useYnByCheckbox, |
|||
useYnByRow, |
|||
} = useTableAjax(getTableProps, tableElRef, emit, { |
|||
commitVxeProxy, |
|||
getSearchFormParameter: searchFormAction.getSearchFormParameter, |
|||
getCheckboxRecords, |
|||
setLoading, |
|||
}); |
|||
|
|||
// -------------- 添加修改操作 ---------------------
|
|||
const [registerAddEditModal, { openModal: openAddEditModal }] = useModal(); |
|||
const addEditModalRef = ref(); |
|||
const getAddEditFieldsValue = () => unref(addEditModalRef).getFieldsValue(); |
|||
const resetAddEditFields = () => unref(addEditModalRef).resetFields(); |
|||
const setAddEditFieldsValue = (data: Recordable) => unref(addEditModalRef).setFieldsValue(data); |
|||
const validateAddEdit = () => unref(addEditModalRef).validate(); |
|||
const validateAddEditFields = () => unref(addEditModalRef).validateFields(); |
|||
const getAddEditForm = () => unref(addEditModalRef).getFormAction(); |
|||
const { |
|||
getHasAddEdit, |
|||
showAddModal, |
|||
editByCheckbox, |
|||
editByRowModal, |
|||
getAddEditFormProps, |
|||
getAddEditModalProps, |
|||
getAddEditFormSlots, |
|||
} = useTableModalAddEditConfig(emit, getTableProps, slots, { |
|||
getCheckboxRecords, |
|||
openAddEditModal, |
|||
query, |
|||
}); |
|||
|
|||
const getCustomConfig = computed(() => { |
|||
const tableProps = unref(getTableProps); |
|||
if (tableProps.customConfig) { |
|||
return tableProps.customConfig; |
|||
} |
|||
return { |
|||
storage: { |
|||
visible: true, |
|||
resizable: true, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 列排序存储 |
|||
*/ |
|||
const { setColumnSortConfig, computedColumnSort } = useSmartTableColumnConfig(getTableInstance); |
|||
|
|||
// ------------- toolbar配置 ----------------------
|
|||
|
|||
const { getToolbarConfigInfo } = useTableToolbarConfig(getTableProps, t, { |
|||
deleteByCheckbox, |
|||
showAddModal, |
|||
editByCheckbox, |
|||
query, |
|||
getSearchFormVisible: searchFormAction.getSearchFormVisible, |
|||
useYnByCheckbox, |
|||
setColumnSortConfig, |
|||
}); |
|||
|
|||
/** |
|||
* 表格事件 |
|||
*/ |
|||
const { getToolbarEvents } = useToolbarEvent(emit, { |
|||
setSearchFormVisible: searchFormAction.setSearchFormVisible, |
|||
}); |
|||
const getTableEvents = computed(() => { |
|||
return { |
|||
...unref(getToolbarEvents), |
|||
...getProxyEvents, |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 表格拖拽支持 |
|||
*/ |
|||
const { getTableDragColumn, getTableDragSlot } = useTableRowDrag(getTableProps, tableElRef, { |
|||
getData: () => getTableInstance().getData(), |
|||
loadData: (data) => getTableInstance().loadData(data), |
|||
}); |
|||
|
|||
/** |
|||
* checkbox 功能加强 |
|||
*/ |
|||
const { computeCheckboxTableProps } = useSmartTableCheckbox( |
|||
getTableProps, |
|||
emit, |
|||
getTableInstance, |
|||
); |
|||
|
|||
const { getTableColumns, computedEditRules } = useSmartTableColumn(getTableProps, t); |
|||
|
|||
/** |
|||
* 列动态class style支持 |
|||
*/ |
|||
const { computedTableClassStyle } = useSmartTableDynamicClassStyle(getTableProps); |
|||
|
|||
/** |
|||
* 获取table v-bing |
|||
*/ |
|||
const getTableBindValues = computed<SmartTableProps>(() => { |
|||
const tableProps = unref(getTableProps); |
|||
return { |
|||
...attrs, |
|||
editRules: unref(computedEditRules), |
|||
...tableProps, |
|||
// loading: unref(getLoading),
|
|||
...unref(computeCheckboxTableProps), |
|||
// data: dataSource,
|
|||
toolbarConfig: unref(getToolbarConfigInfo), |
|||
proxyConfig: unref(getProxyConfigRef), |
|||
customConfig: unref(getCustomConfig), |
|||
...unref(getTableEvents), |
|||
...unref(computedTableClassStyle), |
|||
}; |
|||
}); |
|||
|
|||
const getTableSlots = computed(() => { |
|||
return { |
|||
...slots, |
|||
...unref(getTableDragSlot), |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
const tableAction: TableActionType = { |
|||
reload, |
|||
query, |
|||
setProps, |
|||
setLoading, |
|||
// 分页
|
|||
getPagination, |
|||
setPagination, |
|||
commitVxeProxy, |
|||
getShowPagination, |
|||
setShowPagination, |
|||
deleteByCheckbox, |
|||
getCheckboxRecords, |
|||
getRadioRecord, |
|||
// openAddEditModal,
|
|||
showAddModal, |
|||
editByCheckbox, |
|||
editByRowModal, |
|||
getAddEditFieldsValue, |
|||
resetAddEditFields, |
|||
setAddEditFieldsValue, |
|||
deleteByRow, |
|||
setRadioRow, |
|||
setCheckboxRow, |
|||
validateAddEdit, |
|||
validateAddEditFields, |
|||
getTableInstance, |
|||
getData: (rowIndex?: number) => { |
|||
if (rowIndex) { |
|||
return getTableInstance().getData(rowIndex); |
|||
} |
|||
return getTableInstance().getData(); |
|||
}, |
|||
useYnByCheckbox, |
|||
useYnByRow, |
|||
}; |
|||
|
|||
const computedTableColumns = computed(() => { |
|||
const columns = [...unref(getTableDragColumn), ...unref(getTableColumns)]; |
|||
const columnSort = unref(computedColumnSort); |
|||
if (!columnSort) { |
|||
return columns; |
|||
} |
|||
return columns.sort((a, b) => { |
|||
if (!a.field) { |
|||
return 0; |
|||
} |
|||
if (columnSort.indexOf(a.field) === -1) { |
|||
return 0; |
|||
} |
|||
return columnSort.indexOf(a.field) - columnSort.indexOf(b.field); |
|||
}); |
|||
}); |
|||
|
|||
createTableContext({ ...tableAction, wrapRef, getBindValues: getTableBindValues }); |
|||
|
|||
emit('register', tableAction, searchFormAction, getAddEditForm); |
|||
|
|||
return { |
|||
registerSearchForm, |
|||
getSearchFormProps, |
|||
getTableProps, |
|||
tableAction, |
|||
getTableBindValues, |
|||
getPaginationInfo, |
|||
tableElRef, |
|||
handleSearchInfoChange, |
|||
getSearchFormSlot, |
|||
getSearchFormColumnSlot, |
|||
getHasAddEdit, |
|||
registerAddEditModal, |
|||
getAddEditFormProps, |
|||
getAddEditModalProps, |
|||
addEditModalRef, |
|||
wrapRef, |
|||
getAddEditFormSlots, |
|||
getSearchFormVisible, |
|||
getTableEvents, |
|||
getTableSlots, |
|||
tableColumns: computedTableColumns, |
|||
pagerConfig: getPaginationInfo, |
|||
setColumnSortConfig, |
|||
}; |
|||
}, |
|||
render() { |
|||
const { |
|||
getTableProps: { useSearchForm }, |
|||
getSearchFormVisible, |
|||
} = this; |
|||
|
|||
// const $this = this
|
|||
|
|||
const slots: any = { |
|||
default: renderTable(this), |
|||
}; |
|||
/** |
|||
* 渲染搜索表格 |
|||
*/ |
|||
if (useSearchForm) { |
|||
slots.search = renderSearchForm(this); |
|||
} |
|||
// @ts-ignore
|
|||
return ( |
|||
<TableSearchLayout showSearch={getSearchFormVisible} class="smart-table" ref="wrapRef"> |
|||
{slots} |
|||
</TableSearchLayout> |
|||
); |
|||
}, |
|||
}); |
|||
|
|||
/** |
|||
* 渲染搜索表单 |
|||
* @param smartTableInstance |
|||
*/ |
|||
const renderSearchForm = (smartTableInstance) => { |
|||
return () => { |
|||
const { |
|||
registerSearchForm, |
|||
getSearchFormProps, |
|||
handleSearchInfoChange, |
|||
getSearchFormSlot, |
|||
getSearchFormColumnSlot, |
|||
id, |
|||
} = smartTableInstance; |
|||
const formAttrs = { |
|||
...getSearchFormProps, |
|||
onSubmit: handleSearchInfoChange, |
|||
submitOnReset: true, |
|||
onRegister: registerSearchForm, |
|||
}; |
|||
// 获取插槽
|
|||
const searchFormSlots = { |
|||
...getSearchFormSlot, |
|||
...getSearchFormColumnSlot, |
|||
}; |
|||
if ( |
|||
Object.keys(getSearchFormSlot).length + Object.keys(getSearchFormColumnSlot).length > |
|||
Object.keys(searchFormSlots).length |
|||
) { |
|||
error('搜索表单插槽命名重复'); |
|||
} |
|||
return ( |
|||
<BasicForm name={`${id}_search_form`} {...formAttrs}> |
|||
{searchFormSlots} |
|||
</BasicForm> |
|||
); |
|||
}; |
|||
}; |
|||
|
|||
/** |
|||
* 渲染表格 |
|||
*/ |
|||
const renderTable = (instance) => { |
|||
return () => { |
|||
const { |
|||
getTableBindValues, |
|||
getTableSlots, |
|||
getHasAddEdit, |
|||
registerAddEditModal, |
|||
getAddEditFormProps, |
|||
getAddEditModalProps, |
|||
getAddEditFormSlots, |
|||
id, |
|||
tableColumns, |
|||
pagerConfig, |
|||
} = instance; |
|||
const tableProps = { |
|||
...getTableBindValues, |
|||
columns: tableColumns, |
|||
pagerConfig, |
|||
}; |
|||
const result = [ |
|||
<vxe-grid ref="tableElRef" {...tableProps}> |
|||
{{ ...getTableSlots }} |
|||
</vxe-grid>, |
|||
]; |
|||
if (getHasAddEdit) { |
|||
result.push( |
|||
<SmartTableAddEditModal |
|||
ref="addEditModalRef" |
|||
{...getAddEditModalProps} |
|||
tableId={id} |
|||
onRegister={registerAddEditModal} |
|||
formConfig={getAddEditFormProps} |
|||
> |
|||
{getAddEditFormSlots} |
|||
</SmartTableAddEditModal>, |
|||
); |
|||
} |
|||
return result; |
|||
}; |
|||
}; |
|||
@ -0,0 +1,154 @@ |
|||
import type { SmartAddEditModalCallbackData } from '@/components/SmartTable'; |
|||
|
|||
import { defineComponent, ref, unref } from 'vue'; |
|||
|
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
import { BasicModal, useModalInner } from '@/components/Modal'; |
|||
import { BasicForm, FormProps, useForm } from '@/components/Form'; |
|||
import { isBoolean, isPromise, isFunction } from '@/utils/is'; |
|||
import { useI18n } from '@/hooks/web/useI18n'; |
|||
import { propTypes } from '@/utils/propTypes'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'SmartTableAddEditModal', |
|||
props: { |
|||
formConfig: { |
|||
type: Object as PropType<FormProps>, |
|||
required: true, |
|||
}, |
|||
beforeSave: { |
|||
type: Function as PropType<(data) => any | Promise<any>>, |
|||
}, |
|||
saveFunction: Function as PropType<(data) => Promise<any>>, |
|||
afterSave: Function as PropType<(data?) => boolean | Promise<boolean> | undefined>, |
|||
tableId: propTypes.string, |
|||
}, |
|||
setup(props) { |
|||
const { t } = useI18n(); |
|||
const isAddRef = ref(true); |
|||
|
|||
const [registerForm, formAction] = useForm(); |
|||
const { resetFields, getFieldsValue, setFieldsValue, validate, validateFields } = formAction; |
|||
const [register, { changeLoading, changeOkLoading, closeModal }] = useModalInner( |
|||
(data: SmartAddEditModalCallbackData) => { |
|||
const { isAdd, formData } = data; |
|||
resetFields(); |
|||
isAddRef.value = isAdd; |
|||
if (isAdd) { |
|||
if (formData) { |
|||
setFieldsValue({ |
|||
...formData, |
|||
isAdd, |
|||
}); |
|||
} |
|||
return false; |
|||
} |
|||
loadEditData(data); |
|||
}, |
|||
); |
|||
|
|||
const loadEditData = async (data: SmartAddEditModalCallbackData) => { |
|||
const { getFunction, validateFunction, selectData, isAdd, formData } = data; |
|||
try { |
|||
if (!isFunction(getFunction)) { |
|||
throw new Error('proxyConfig.ajax.getById未定义'); |
|||
} |
|||
changeLoading(true); |
|||
const editData = await getFunction(selectData); |
|||
// todo: 触发事件
|
|||
if (isFunction(validateFunction)) { |
|||
const result = validateFunction(data, selectData); |
|||
if (isBoolean(result) && !result) { |
|||
return false; |
|||
} |
|||
if (isPromise(result)) { |
|||
const promiseResult = await result; |
|||
if (isBoolean(promiseResult) && !promiseResult) { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
setFieldsValue({ |
|||
...editData, |
|||
formData, |
|||
isAdd, |
|||
}); |
|||
} finally { |
|||
changeLoading(false); |
|||
} |
|||
}; |
|||
|
|||
const handleSubmit = async () => { |
|||
try { |
|||
let data = await validate(); |
|||
changeOkLoading(true); |
|||
// 保存前对参数进行处理
|
|||
const beforeSave = props.beforeSave; |
|||
if (beforeSave) { |
|||
if (!isFunction(beforeSave)) { |
|||
throw new Error('参数错误,beforeSave必须是Function'); |
|||
} |
|||
const result = beforeSave(data); |
|||
if (isPromise(result)) { |
|||
data = await result; |
|||
} else { |
|||
data = result; |
|||
} |
|||
} |
|||
const saveFunction = props.saveFunction; |
|||
if (!saveFunction) { |
|||
throw new Error('proxyConfig.ajax.save未定义,无法执行保存操作'); |
|||
} |
|||
const saveResult = await saveFunction({ |
|||
body: { |
|||
insertRecords: unref(isAddRef) ? [data] : [], |
|||
updateRecords: unref(isAddRef) ? [] : [data], |
|||
}, |
|||
}); |
|||
if (props.afterSave) { |
|||
let afterSaveResult = props.afterSave(saveResult); |
|||
if (isPromise(afterSaveResult)) { |
|||
afterSaveResult = await afterSaveResult; |
|||
} |
|||
if (isBoolean(afterSaveResult) && !afterSaveResult) { |
|||
// 返回结果验证错误,停止执行
|
|||
return false; |
|||
} |
|||
} |
|||
message.success( |
|||
unref(isAddRef) ? t('common.message.saveSuccess') : t('common.message.editSuccess'), |
|||
); |
|||
closeModal(); |
|||
} finally { |
|||
changeOkLoading(false); |
|||
} |
|||
}; |
|||
|
|||
return { |
|||
register, |
|||
registerForm, |
|||
resetFields, |
|||
getFieldsValue, |
|||
setFieldsValue, |
|||
handleSubmit, |
|||
validate, |
|||
validateFields, |
|||
getFormAction: () => formAction, |
|||
}; |
|||
}, |
|||
render() { |
|||
const { $attrs, register, registerForm, handleSubmit, $slots, tableId, formConfig } = this; |
|||
const attrs = { |
|||
...$attrs, |
|||
onRegister: register, |
|||
}; |
|||
return ( |
|||
<BasicModal {...attrs} onOk={handleSubmit}> |
|||
<BasicForm {...formConfig} name={`${tableId}_addEdit_form`} onRegister={registerForm}> |
|||
{$slots} |
|||
</BasicForm> |
|||
</BasicModal> |
|||
); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,375 @@ |
|||
import type { |
|||
VxeGridProps, |
|||
VxeGridPropTypes, |
|||
VxeColumnPropTypes, |
|||
VxeGridInstance, |
|||
VxeButtonProps, |
|||
VxePulldownInstance, |
|||
VxeTableDefines, |
|||
} from 'vxe-table'; |
|||
|
|||
import { t as vxeI18n } from 'vxe-table'; |
|||
|
|||
import { computed, defineComponent, inject, nextTick, Ref, ref, unref } from 'vue'; |
|||
import type { SmartTableToolbarColumnConfig } from '@/components/SmartTable'; |
|||
|
|||
import Sortable from 'sortablejs'; |
|||
|
|||
const DRAG_CLASS = 'smart-table-column-config--drag'; |
|||
|
|||
/** |
|||
* 表格列设置组件 |
|||
*/ |
|||
export default defineComponent({ |
|||
name: 'SmartTableColumnConfig', |
|||
props: { |
|||
config: { |
|||
type: Object as PropType<SmartTableToolbarColumnConfig>, |
|||
default: (): SmartTableToolbarColumnConfig => { |
|||
return { |
|||
trigger: 'click', |
|||
buttonProps: { |
|||
icon: 'vxe-icon-chart-radar', |
|||
}, |
|||
columnOrder: false, |
|||
}; |
|||
}, |
|||
}, |
|||
setColumnSortConfig: { |
|||
type: Function, |
|||
required: true, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const getTableInstance = inject('getTableInstance') as () => VxeGridInstance; |
|||
const pullDownRef = ref<VxePulldownInstance>(); |
|||
const configGridRef = ref<VxeGridInstance>(); |
|||
|
|||
/** |
|||
* 添加行顺序调整 |
|||
*/ |
|||
const { openDrag } = useDrag(configGridRef, getTableInstance, props.setColumnSortConfig); |
|||
/** |
|||
* 显示弹窗面板 |
|||
*/ |
|||
const showPanel = () => { |
|||
unref(pullDownRef)?.showPanel(); |
|||
nextTick(() => { |
|||
if (props.config.columnOrder) { |
|||
openDrag(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const computedTableColumns = computed(() => { |
|||
return getTableInstance()?.getTableColumn().fullColumn; |
|||
}); |
|||
|
|||
/** |
|||
* FIX变更触发 |
|||
* @param event |
|||
* @param fixed |
|||
* @param row |
|||
*/ |
|||
const handleFixedChange = (event: MouseEvent, fixed: VxeColumnPropTypes.Fixed, row: any) => { |
|||
event.preventDefault(); |
|||
if (fixed === row.fixed) { |
|||
//@ts-ignore
|
|||
getTableInstance().extendTableMethods(['clearColumnFixed']).clearColumnFixed(row.field); |
|||
} else { |
|||
//@ts-ignore
|
|||
getTableInstance().extendTableMethods(['setColumnFixed']).setColumnFixed(row.field, fixed); |
|||
} |
|||
}; |
|||
|
|||
const { handleShowHideOk, handleRestShowHide, handleCheckboxChange } = |
|||
useColumnShowHide(getTableInstance); |
|||
|
|||
/** |
|||
* 表格props |
|||
*/ |
|||
const computedGridProps = computed<VxeGridProps>(() => { |
|||
return { |
|||
columns: getGridColumns(props.config.columnOrder || false, handleFixedChange), |
|||
size: 'mini', |
|||
height: '396px', |
|||
headerAlign: 'center', |
|||
checkboxConfig: { |
|||
// trigger: 'row',
|
|||
checkField: 'checked', |
|||
}, |
|||
rowConfig: { |
|||
keyField: 'field', |
|||
}, |
|||
onCheckboxChange: handleCheckboxChange, |
|||
data: unref(computedTableColumns)?.map((item) => { |
|||
const { visible, fixed, field } = item; |
|||
return { |
|||
title: item.getTitle(), |
|||
checked: visible, |
|||
fixed, |
|||
field, |
|||
id: item.id, |
|||
column: item, |
|||
}; |
|||
}), |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 按钮时间,触发下拉显示 |
|||
*/ |
|||
const buttonEvent = { |
|||
onClick: () => { |
|||
const trigger = props.config.trigger; |
|||
if (trigger === 'click') { |
|||
showPanel(); |
|||
} |
|||
}, |
|||
onMouseover: () => { |
|||
const trigger = props.config.trigger; |
|||
if (trigger === 'hover') { |
|||
showPanel(); |
|||
} |
|||
}, |
|||
}; |
|||
return { |
|||
buttonEvent, |
|||
pullDownRef, |
|||
computedTableColumns, |
|||
computedGridProps, |
|||
configGridRef, |
|||
handleShowHideOk, |
|||
handleRestShowHide, |
|||
}; |
|||
}, |
|||
render() { |
|||
const { config, buttonEvent, computedGridProps, handleShowHideOk, handleRestShowHide } = this; |
|||
const slots = { |
|||
default: renderButton(config, buttonEvent), |
|||
dropdown: renderDropdown(computedGridProps, handleShowHideOk, handleRestShowHide), |
|||
}; |
|||
return ( |
|||
<vxe-pulldown popupClassName="smart-table-column-config" transfer={true} ref="pullDownRef"> |
|||
{slots} |
|||
</vxe-pulldown> |
|||
); |
|||
}, |
|||
}); |
|||
|
|||
/** |
|||
* 渲染按钮 |
|||
* @param config |
|||
* @param event |
|||
*/ |
|||
const renderButton = (config: SmartTableToolbarColumnConfig, event: any) => { |
|||
const props: VxeButtonProps = { |
|||
circle: true, |
|||
icon: 'vxe-icon-chart-radar', |
|||
...event, |
|||
...config.buttonProps, |
|||
}; |
|||
return () => { |
|||
return <vxe-button title={vxeI18n('vxe.toolbar.custom')} {...props}></vxe-button>; |
|||
}; |
|||
}; |
|||
|
|||
/** |
|||
* 渲染下拉表格 |
|||
* @param props |
|||
* @param handleShowHideOk |
|||
* @param handleRestShowHide |
|||
*/ |
|||
const renderDropdown = (props: VxeGridProps, handleShowHideOk, handleRestShowHide) => { |
|||
return () => { |
|||
return ( |
|||
<div> |
|||
<vxe-grid ref="configGridRef" {...props} /> |
|||
<div class="bottom-button"> |
|||
<vxe-button type="text" onClick={handleRestShowHide}> |
|||
{vxeI18n('vxe.toolbar.customRestore')} |
|||
</vxe-button> |
|||
<vxe-button type="text" onClick={handleShowHideOk}> |
|||
{vxeI18n('vxe.toolbar.customConfirm')} |
|||
</vxe-button> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
}; |
|||
|
|||
const getGridColumns = (columnOrder: boolean, changeFixed: Function): VxeGridPropTypes.Columns => { |
|||
const columns: VxeGridPropTypes.Columns = []; |
|||
|
|||
if (columnOrder) { |
|||
columns.push({ |
|||
title: '#', |
|||
field: 'drag', |
|||
align: 'left', |
|||
width: '40', |
|||
slots: { |
|||
default: ({ rowIndex, row }) => { |
|||
const fixed = !(row.fixed === undefined || row.fixed === null); |
|||
return ( |
|||
<div |
|||
data-id={rowIndex} |
|||
title={fixed ? '锁定列不可调整顺序' : '拖动调整顺序'} |
|||
class={fixed ? 'smart-table-column-config--drag-fixed' : DRAG_CLASS} |
|||
> |
|||
<i data-id={rowIndex} class="vxe-icon-num-list" /> |
|||
</div> |
|||
); |
|||
}, |
|||
}, |
|||
}); |
|||
} |
|||
columns.push( |
|||
...([ |
|||
{ |
|||
type: 'checkbox', |
|||
width: '60', |
|||
headerAlign: 'left', |
|||
}, |
|||
{ |
|||
field: 'title', |
|||
width: 100, |
|||
title: '列名', |
|||
}, |
|||
{ |
|||
field: 'fixed', |
|||
width: 80, |
|||
title: '列冻结', |
|||
slots: { |
|||
default: ({ row }) => { |
|||
return ( |
|||
<div class="smart-table-column-config-fixed-option"> |
|||
<span |
|||
onClick={(event) => changeFixed(event, 'left', row)} |
|||
class={[ |
|||
row.fixed === 'left' ? 'vxe-icon-fixed-left-fill' : 'vxe-icon-fixed-left', |
|||
'fixed-span', |
|||
{ |
|||
'is--checked': row.fixed === 'left', |
|||
}, |
|||
]} |
|||
title={vxeI18n( |
|||
row.fixed === 'left' ? 'vxe.toolbar.cancelfixed' : 'vxe.toolbar.fixedLeft', |
|||
)} |
|||
/> |
|||
<span |
|||
onClick={(event) => changeFixed(event, 'right', row)} |
|||
class={[ |
|||
row.fixed === 'right' ? 'vxe-icon-fixed-right-fill' : 'vxe-icon-fixed-right', |
|||
'fixed-span', |
|||
{ |
|||
'is--checked': row.fixed === 'right', |
|||
}, |
|||
]} |
|||
title={vxeI18n( |
|||
row.fixed === 'right' ? 'vxe.toolbar.cancelfixed' : 'vxe.toolbar.fixedRight', |
|||
)} |
|||
/> |
|||
</div> |
|||
); |
|||
}, |
|||
}, |
|||
}, |
|||
] as VxeGridPropTypes.Columns), |
|||
); |
|||
return columns; |
|||
}; |
|||
|
|||
interface ChangeColumn { |
|||
column: VxeTableDefines.ColumnInfo; |
|||
checked: boolean; |
|||
} |
|||
|
|||
const useColumnShowHide = (getTable: () => VxeGridInstance) => { |
|||
/** |
|||
* 变更的列 |
|||
*/ |
|||
const changeColumnsRef = ref<Recordable<ChangeColumn | null>>({}); |
|||
|
|||
/** |
|||
* 显示隐藏变更触发 |
|||
* @param row |
|||
* @param checked |
|||
*/ |
|||
const handleCheckboxChange = ({ row, checked }) => { |
|||
const { column } = row; |
|||
const changeColumn = unref(changeColumnsRef)[column.id]; |
|||
if (changeColumn === null || changeColumn === undefined) { |
|||
changeColumnsRef.value[column.id] = { column, checked }; |
|||
} else { |
|||
changeColumnsRef.value[column.id] = null; |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* 重置列显示隐藏 |
|||
*/ |
|||
const handleRestShowHide = () => { |
|||
getTable().resetColumn({}); |
|||
changeColumnsRef.value = {}; |
|||
}; |
|||
|
|||
/** |
|||
* 点击确定显示隐藏列 |
|||
*/ |
|||
const handleShowHideOk = () => { |
|||
const table = getTable(); |
|||
Object.entries(unref(changeColumnsRef)).forEach(([_, column]) => { |
|||
if (column !== null) { |
|||
if (column.checked) { |
|||
table.showColumn(column.column); |
|||
} else { |
|||
table.hideColumn(column.column); |
|||
} |
|||
} |
|||
}); |
|||
changeColumnsRef.value = {}; |
|||
}; |
|||
|
|||
return { |
|||
changeColumnsRef, |
|||
handleCheckboxChange, |
|||
handleRestShowHide, |
|||
handleShowHideOk, |
|||
}; |
|||
}; |
|||
|
|||
const useDrag = ( |
|||
configTableRef: Ref<VxeGridInstance | undefined>, |
|||
getTable: () => VxeGridInstance, |
|||
setColumnSortConfig: Function, |
|||
) => { |
|||
let sortable: Sortable | null = null; |
|||
const selector = '.body--wrapper>.vxe-table--body tbody'; |
|||
const openDrag = async () => { |
|||
if (sortable != null) { |
|||
return false; |
|||
} |
|||
const sortEl = unref(configTableRef)?.$el.querySelector(selector); |
|||
sortable = Sortable.create(sortEl, { |
|||
handle: `.${DRAG_CLASS}`, |
|||
dataIdAttr: 'rowid', |
|||
onEnd: ({ newIndex, oldIndex }) => { |
|||
const table = getTable(); |
|||
const fullColumn = table.getTableColumn().fullColumn; |
|||
const keySorts = fullColumn.map((item) => item.field); |
|||
const currentRow = fullColumn.splice(oldIndex!, 1)[0]; |
|||
fullColumn.splice(newIndex!, 0, currentRow); |
|||
sortable?.sort(keySorts); |
|||
table.reloadColumn(fullColumn); |
|||
nextTick(() => { |
|||
if (table.id && table.customConfig?.storage === true) { |
|||
setColumnSortConfig(); |
|||
} |
|||
}); |
|||
}, |
|||
}); |
|||
}; |
|||
return { |
|||
openDrag, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,225 @@ |
|||
<template> |
|||
<div :class="[prefixCls, getAlign]" @click.stop="onCellClick"> |
|||
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`"> |
|||
<template v-if="action.slot"> |
|||
<slot name="customButton"></slot> |
|||
</template> |
|||
<template v-else> |
|||
<Tooltip v-if="!action.hasAuth" :title="$t('common.message.noPermission')" color="red"> |
|||
<!-- 无权限 --> |
|||
<PopConfirmVxeButton disabled v-bind="action"> |
|||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
|||
<template v-if="action.label">{{ action.label }}</template> |
|||
</PopConfirmVxeButton> |
|||
</Tooltip> |
|||
<Tooltip v-else-if="action.tooltip" v-bind="getTooltip(action.tooltip)"> |
|||
<PopConfirmVxeButton v-bind="action"> |
|||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
|||
<template v-if="action.label">{{ action.label }}</template> |
|||
</PopConfirmVxeButton> |
|||
</Tooltip> |
|||
<PopConfirmVxeButton v-else v-bind="action"> |
|||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
|||
<template v-if="action.label">{{ action.label }}</template> |
|||
</PopConfirmVxeButton> |
|||
</template> |
|||
|
|||
<Divider |
|||
type="vertical" |
|||
class="action-divider" |
|||
v-if="divider && index < getActions.length - 1" |
|||
/> |
|||
</template> |
|||
<Dropdown |
|||
:trigger="['hover']" |
|||
:dropMenuList="getDropdownList" |
|||
popconfirm |
|||
v-if="dropDownActions && getDropdownList.length > 0" |
|||
> |
|||
<slot name="more"></slot> |
|||
<vxe-button type="text" status="primary" size="small" v-if="!$slots.more"> |
|||
更多 |
|||
<Icon icon="mdi-light:chevron-down" /> |
|||
</vxe-button> |
|||
</Dropdown> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { ActionItem } from '../types/SmartTableActionType'; |
|||
import type { TableActionType } from '../types/SmartTableType'; |
|||
|
|||
import { defineComponent, PropType, computed, toRaw, unref } from 'vue'; |
|||
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue'; |
|||
import { Icon } from '@/components/Icon'; |
|||
import { PopConfirmVxeButton } from '@/components/Button'; |
|||
import { Dropdown } from '@/components/Dropdown'; |
|||
import { useDesign } from '@/hooks/web/useDesign'; |
|||
import { useTableContext } from '../hooks/userSmartTableContext'; |
|||
import { hasPermission } from '@/utils/auth'; |
|||
import { isBoolean, isFunction, isString } from '@/utils/is'; |
|||
import { propTypes } from '@/utils/propTypes'; |
|||
import { ACTION_COLUMN_FLAG } from '../const'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'TableAction', |
|||
components: { Icon, PopConfirmVxeButton, Divider, Dropdown, Tooltip }, |
|||
props: { |
|||
actions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
dropDownActions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
divider: propTypes.bool.def(true), |
|||
outside: propTypes.bool, |
|||
stopButtonPropagation: propTypes.bool.def(false), |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('basic-table-action'); |
|||
let table: Partial<TableActionType> = {}; |
|||
if (!props.outside) { |
|||
table = useTableContext(); |
|||
} |
|||
function isIfShow(action: ActionItem): boolean { |
|||
const ifShow = action.ifShow; |
|||
|
|||
let isIfShow = true; |
|||
|
|||
if (isBoolean(ifShow)) { |
|||
isIfShow = ifShow; |
|||
} |
|||
if (isFunction(ifShow)) { |
|||
isIfShow = ifShow(action); |
|||
} |
|||
return isIfShow; |
|||
} |
|||
|
|||
const getActions = computed(() => { |
|||
return (toRaw(props.actions) || []) |
|||
.filter((action) => { |
|||
return isIfShow(action); |
|||
}) |
|||
.map((action) => { |
|||
const { popConfirm } = action; |
|||
return { |
|||
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body, |
|||
type: 'text', |
|||
size: 'small', |
|||
status: action.danger ? 'danger' : 'primary', |
|||
...action, |
|||
...(popConfirm || {}), |
|||
onConfirm: popConfirm?.confirm, |
|||
onCancel: popConfirm?.cancel, |
|||
enable: !!popConfirm, |
|||
hasAuth: hasPermission(action.auth), |
|||
}; |
|||
}); |
|||
}); |
|||
|
|||
const getDropdownList = computed((): any[] => { |
|||
//过滤掉隐藏的dropdown,避免出现多余的分割线 |
|||
const list = (toRaw(props.dropDownActions) || []).filter((action) => { |
|||
return isIfShow(action); |
|||
}); |
|||
return list.map((action, index) => { |
|||
const { label, popConfirm } = action; |
|||
return { |
|||
...action, |
|||
...popConfirm, |
|||
onConfirm: popConfirm?.confirm, |
|||
onCancel: popConfirm?.cancel, |
|||
text: label, |
|||
divider: index < list.length - 1 ? props.divider : false, |
|||
hasAuth: hasPermission(action.auth), |
|||
disabled: !hasPermission(action.auth) || action.disabled, |
|||
}; |
|||
}); |
|||
}); |
|||
|
|||
const getAlign = computed(() => { |
|||
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[]; |
|||
const isInButton = path.find((ele) => { |
|||
return ele.tagName?.toUpperCase() === 'BUTTON'; |
|||
}); |
|||
isInButton && e.stopPropagation(); |
|||
} |
|||
|
|||
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@prefix-cls: ~'@{namespace}-basic-table-action'; |
|||
|
|||
.@{prefix-cls} { |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
/* update-begin-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px */ |
|||
height: 22px; |
|||
|
|||
/* update-end-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px */ |
|||
|
|||
.action-divider { |
|||
display: table; |
|||
} |
|||
|
|||
&.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; |
|||
} |
|||
} |
|||
|
|||
button.ant-btn-circle { |
|||
span { |
|||
margin: auto !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,215 @@ |
|||
<template> |
|||
<div :class="[prefixCls, getAlign]" @click="onCellClick"> |
|||
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`"> |
|||
<template v-if="action.slot"> |
|||
<slot name="customButton"></slot> |
|||
</template> |
|||
<template v-else> |
|||
<Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)"> |
|||
<PopConfirmButton v-bind="action"> |
|||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
|||
<template v-if="action.label">{{ action.label }}</template> |
|||
</PopConfirmButton> |
|||
</Tooltip> |
|||
<PopConfirmButton v-else v-bind="action"> |
|||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
|||
<template v-if="action.label">{{ action.label }}</template> |
|||
</PopConfirmButton> |
|||
</template> |
|||
|
|||
<Divider |
|||
type="vertical" |
|||
class="action-divider" |
|||
v-if="divider && index < getActions.length - 1" |
|||
/> |
|||
</template> |
|||
<Dropdown |
|||
:trigger="['hover']" |
|||
:dropMenuList="getDropdownList" |
|||
popconfirm |
|||
v-if="dropDownActions && getDropdownList.length > 0" |
|||
> |
|||
<slot name="more"></slot> |
|||
<a-button type="link" size="small" v-if="!$slots.more"> |
|||
更多 |
|||
<Icon icon="mdi-light:chevron-down" /> |
|||
</a-button> |
|||
</Dropdown> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { ActionItem } from '../types/SmartTableActionType'; |
|||
import type { TableActionType } from '../types/SmartTableType'; |
|||
|
|||
import { defineComponent, PropType, computed, toRaw, unref } from 'vue'; |
|||
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue'; |
|||
import { Icon } from '@/components/Icon'; |
|||
import { PopConfirmButton } from '@/components/Button'; |
|||
import { Dropdown } from '@/components/Dropdown'; |
|||
import { useDesign } from '@/hooks/web/useDesign'; |
|||
import { useTableContext } from '../hooks/userSmartTableContext'; |
|||
import { isBoolean, isFunction, isString } from '@/utils/is'; |
|||
import { propTypes } from '@/utils/propTypes'; |
|||
import { ACTION_COLUMN_FLAG } from '../const'; |
|||
import { hasPermission } from '@/utils/auth'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'TableAction', |
|||
components: { Icon, PopConfirmButton, Divider, Dropdown, Tooltip }, |
|||
props: { |
|||
actions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
dropDownActions: { |
|||
type: Array as PropType<ActionItem[]>, |
|||
default: null, |
|||
}, |
|||
divider: propTypes.bool.def(true), |
|||
outside: propTypes.bool, |
|||
stopButtonPropagation: propTypes.bool.def(false), |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('basic-table-action'); |
|||
let table: Partial<TableActionType> = {}; |
|||
if (!props.outside) { |
|||
table = useTableContext(); |
|||
} |
|||
|
|||
function isIfShow(action: ActionItem): boolean { |
|||
const ifShow = action.ifShow; |
|||
|
|||
let isIfShow = true; |
|||
|
|||
if (isBoolean(ifShow)) { |
|||
isIfShow = ifShow; |
|||
} |
|||
if (isFunction(ifShow)) { |
|||
isIfShow = ifShow(action); |
|||
} |
|||
return isIfShow; |
|||
} |
|||
|
|||
const getActions = computed(() => { |
|||
return (toRaw(props.actions) || []) |
|||
.filter((action) => { |
|||
return hasPermission(action.auth) && isIfShow(action); |
|||
}) |
|||
.map((action) => { |
|||
const { popConfirm } = action; |
|||
return { |
|||
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body, |
|||
type: 'link', |
|||
size: 'small', |
|||
...action, |
|||
...(popConfirm || {}), |
|||
onConfirm: popConfirm?.confirm, |
|||
onCancel: popConfirm?.cancel, |
|||
enable: !!popConfirm, |
|||
}; |
|||
}); |
|||
}); |
|||
|
|||
const getDropdownList = computed((): any[] => { |
|||
//过滤掉隐藏的dropdown,避免出现多余的分割线 |
|||
const list = (toRaw(props.dropDownActions) || []).filter((action) => { |
|||
return hasPermission(action.auth) && isIfShow(action); |
|||
}); |
|||
return list.map((action, index) => { |
|||
const { label, popConfirm } = action; |
|||
return { |
|||
...action, |
|||
...popConfirm, |
|||
onConfirm: popConfirm?.confirm, |
|||
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'; |
|||
}); |
|||
|
|||
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[]; |
|||
const isInButton = path.find((ele) => { |
|||
return ele.tagName?.toUpperCase() === 'BUTTON'; |
|||
}); |
|||
isInButton && e.stopPropagation(); |
|||
} |
|||
|
|||
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@prefix-cls: ~'@{namespace}-basic-table-action'; |
|||
|
|||
.@{prefix-cls} { |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
/* update-begin-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px */ |
|||
height: 22px; |
|||
|
|||
/* update-end-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px */ |
|||
|
|||
.action-divider { |
|||
display: table; |
|||
} |
|||
|
|||
&.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; |
|||
} |
|||
} |
|||
|
|||
button.ant-btn-circle { |
|||
span { |
|||
margin: auto !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,32 @@ |
|||
import componentSetting from '@/settings/componentSetting'; |
|||
|
|||
const { table } = componentSetting; |
|||
|
|||
const { pageSizeOptions, defaultPageSize, fetchSetting, pageLayouts, defaultSize } = table; |
|||
|
|||
// Optional display number per page;
|
|||
export const PAGE_SIZE_OPTIONS = pageSizeOptions; |
|||
|
|||
// Number of items displayed per page
|
|||
export const PAGE_SIZE = defaultPageSize; |
|||
|
|||
// Common interface field settings
|
|||
export const FETCH_SETTING = fetchSetting; |
|||
|
|||
// Default Size
|
|||
export const DEFAULT_SIZE = defaultSize; |
|||
|
|||
export const DEFAULT_PAGE_LAYOUTS = pageLayouts; |
|||
|
|||
export const ACTION_COLUMN_FLAG = 'ACTION'; |
|||
|
|||
export enum SmartTableCode { |
|||
/** |
|||
* 是否显示搜索 |
|||
*/ |
|||
showSearch = 'show_search', |
|||
/** |
|||
* 列配置 |
|||
*/ |
|||
column = 'column', |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
import type { SmartCheckboxConfig } from '@/components/SmartTable'; |
|||
|
|||
/** |
|||
* 默认的checkbox配置 |
|||
*/ |
|||
export const defaultCheckboxConfig: SmartCheckboxConfig = { |
|||
rowTrigger: 'single', |
|||
rowCtrl: true, |
|||
rowShift: true, |
|||
}; |
|||
@ -0,0 +1,124 @@ |
|||
import type { SmartTableProps, FetchParams, SmartTableProxyConfig } from '@/components/SmartTable'; |
|||
import type { ComputedRef, Ref } from 'vue'; |
|||
import type { VxeGridInstance, VxeGridPropTypes } from 'vxe-table'; |
|||
|
|||
import { computed, nextTick, ref, unref, watch } from 'vue'; |
|||
import { isArray } from '@/utils/is'; |
|||
import { error } from '@/utils/log'; |
|||
|
|||
interface ActionType { |
|||
setLoading: (loading: boolean) => void; |
|||
setPagination: (info: Partial<VxeGridPropTypes.PagerConfig>) => void; |
|||
} |
|||
|
|||
export const useLoadData = ( |
|||
tableProps: ComputedRef<SmartTableProps>, |
|||
tableRef: Ref<VxeGridInstance>, |
|||
pagerConfigRef: ComputedRef<VxeGridPropTypes.PagerConfig | undefined>, |
|||
proxyConfigRef: ComputedRef<SmartTableProxyConfig | undefined>, |
|||
emit: Function, |
|||
{ setLoading, setPagination }: ActionType, |
|||
) => { |
|||
const tableDataRef = ref<any[]>([]); |
|||
const sortListRef = ref<VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[]>(); |
|||
watch( |
|||
() => unref(tableProps).sortConfig?.defaultSort, |
|||
(defaultSort) => { |
|||
if (defaultSort) { |
|||
if (isArray(defaultSort)) { |
|||
sortListRef.value = defaultSort.map((item) => { |
|||
return { |
|||
...item, |
|||
sortBy: '', |
|||
column: {}, |
|||
property: '', |
|||
} as VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams; |
|||
}); |
|||
} else { |
|||
sortListRef.value = [ |
|||
{ |
|||
...defaultSort, |
|||
sortBy: '', |
|||
column: {}, |
|||
property: '', |
|||
} as VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams, |
|||
]; |
|||
} |
|||
} |
|||
}, |
|||
); |
|||
const handleSortChange = (data) => { |
|||
sortListRef.value = data.sortList; |
|||
nextTick(() => query()); |
|||
emit('sort-change', data); |
|||
}; |
|||
/** |
|||
* 获取表格事件信息 |
|||
*/ |
|||
const getTableLoadDataEvent = computed(() => { |
|||
return { |
|||
onSortChange: handleSortChange, |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 查询 |
|||
* @param opt |
|||
*/ |
|||
const query = (opt?: FetchParams) => doQuery(opt); |
|||
|
|||
const doQuery = async (opt?: FetchParams) => { |
|||
const proxyConfig = unref(proxyConfigRef); |
|||
if (!proxyConfig || !proxyConfig.ajax?.query) { |
|||
if (process.env.NODE_ENV === 'development') { |
|||
error('proxy-config.ajax.query未定义'); |
|||
} |
|||
} |
|||
const vxeTable = unref(tableRef); |
|||
// 过滤信息
|
|||
const filterList = vxeTable.getCheckedFilters(); |
|||
// 分页信息
|
|||
let pageParams: any = {}; |
|||
const pagerConfig = unref(pagerConfigRef); |
|||
if (pagerConfig) { |
|||
pageParams = { |
|||
currentPage: pagerConfig.currentPage, |
|||
pageSize: pagerConfig.pageSize, |
|||
}; |
|||
} |
|||
const sortList = unref(sortListRef)!; |
|||
const params: VxeGridPropTypes.ProxyAjaxQueryParams = { |
|||
$grid: unref(tableRef), |
|||
filters: filterList, |
|||
page: pageParams, |
|||
sorts: sortList, |
|||
form: {}, |
|||
// @ts-ignore
|
|||
sort: sortList.length > 0 ? sortList[0] : {}, |
|||
}; |
|||
try { |
|||
setLoading(true); |
|||
const proxyData = await proxyConfig?.ajax?.query!(params, opt); |
|||
if (pagerConfig && !pagerConfig.enabled) { |
|||
const { result, total } = proxyConfig?.props || {}; |
|||
tableDataRef.value = proxyData[result || 'rows']; |
|||
setPagination({ |
|||
total: proxyData[total || 'total'], |
|||
}); |
|||
} else { |
|||
tableDataRef.value = proxyData; |
|||
} |
|||
nextTick(() => { |
|||
emit('after-load'); |
|||
}); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
return { |
|||
tableDataRef, |
|||
getTableLoadDataEvent, |
|||
query, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,24 @@ |
|||
import { computed, ref, unref, watch } from 'vue'; |
|||
import type { ComputedRef } from 'vue'; |
|||
|
|||
import type { SmartTableProps } from '@/components/SmartTable'; |
|||
|
|||
export const useLoading = (props: ComputedRef<SmartTableProps>) => { |
|||
const loadingRef = ref(unref(props).loading); |
|||
|
|||
watch( |
|||
() => unref(props).loading, |
|||
(loading) => { |
|||
loadingRef.value = loading; |
|||
}, |
|||
); |
|||
|
|||
const getLoading = computed(() => unref(loadingRef)); |
|||
|
|||
const setLoading = (loading: boolean) => { |
|||
// getTableInstance().loading = loading
|
|||
loadingRef.value = loading; |
|||
}; |
|||
|
|||
return { getLoading, setLoading }; |
|||
}; |
|||
@ -0,0 +1,73 @@ |
|||
import { computed, ref, unref, watch } from 'vue'; |
|||
|
|||
import type { ComputedRef } from 'vue'; |
|||
import type { SmartTableProps } from '@/components/SmartTable'; |
|||
import { isBoolean } from '@/utils/is'; |
|||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, DEFAULT_PAGE_LAYOUTS } from '../const'; |
|||
import { VxeGridPropTypes } from 'vxe-table'; |
|||
|
|||
/** |
|||
* 分页支持 |
|||
* @author zhongming4762 |
|||
* @param propsRef |
|||
*/ |
|||
export const usePagination = (propsRef: ComputedRef<SmartTableProps>) => { |
|||
const configRef = ref<VxeGridPropTypes.PagerConfig>({}); |
|||
const show = ref(true); |
|||
|
|||
watch( |
|||
() => unref(propsRef).pagerConfig, |
|||
(pagerConfig) => { |
|||
if (!isBoolean(pagerConfig) && pagerConfig) { |
|||
configRef.value = { |
|||
...unref(configRef), |
|||
...(pagerConfig ?? {}), |
|||
}; |
|||
} |
|||
}, |
|||
); |
|||
|
|||
const getPaginationInfo = computed((): VxeGridPropTypes.PagerConfig | undefined => { |
|||
const { pagerConfig } = unref(propsRef); |
|||
if (!pagerConfig) { |
|||
return undefined; |
|||
} |
|||
if (!unref(show) || (isBoolean(pagerConfig) && !pagerConfig)) { |
|||
return undefined; |
|||
} |
|||
return { |
|||
currentPage: 1, |
|||
pageSize: PAGE_SIZE, |
|||
pageSizes: PAGE_SIZE_OPTIONS, |
|||
layouts: DEFAULT_PAGE_LAYOUTS, |
|||
...(isBoolean(pagerConfig) ? {} : pagerConfig), |
|||
...unref(configRef), |
|||
}; |
|||
}); |
|||
|
|||
const setPagination = (info: Partial<VxeGridPropTypes.PagerConfig>) => { |
|||
const paginationInfo = unref(getPaginationInfo); |
|||
configRef.value = { |
|||
...(!isBoolean(paginationInfo) ? paginationInfo : {}), |
|||
...info, |
|||
}; |
|||
}; |
|||
|
|||
const getPagination = () => unref(getPaginationInfo); |
|||
|
|||
function getShowPagination() { |
|||
return unref(show); |
|||
} |
|||
|
|||
async function setShowPagination(flag: boolean) { |
|||
show.value = flag; |
|||
} |
|||
|
|||
return { |
|||
getPaginationInfo, |
|||
setPagination, |
|||
getPagination, |
|||
getShowPagination, |
|||
setShowPagination, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,174 @@ |
|||
import { onUnmounted, ref, unref, watch } from 'vue'; |
|||
import type { WatchStopHandle } from 'vue'; |
|||
import type { NamePath } from 'ant-design-vue/es/form/interface'; |
|||
|
|||
import type { FetchParams, SmartTableProps, TableActionType } from '../types/SmartTableType'; |
|||
import type { FormActionType } from '@/components/Form'; |
|||
import type { SmartSearchFormActionType } from '../types/SmartSearchFormType'; |
|||
|
|||
import { isProdMode } from '@/utils/env'; |
|||
import type { DynamicProps } from '#/utils'; |
|||
import { getDynamicProps } from '@/utils'; |
|||
import { error } from '@/utils/log'; |
|||
import { VxeGridPropTypes } from 'vxe-table'; |
|||
|
|||
type Props = Partial<DynamicProps<SmartTableProps>>; |
|||
type UseTableMethod = TableActionType & { |
|||
getSearchForm: () => SmartSearchFormActionType; |
|||
getAddEditForm: () => FormActionType | undefined; |
|||
}; |
|||
|
|||
export const useSmartTable = ( |
|||
tableProps?: Props, |
|||
): [ |
|||
( |
|||
instance: TableActionType, |
|||
formInstance: UseTableMethod, |
|||
getAddEditForm: () => FormActionType, |
|||
) => void, |
|||
UseTableMethod, |
|||
] => { |
|||
// 表格是否加载
|
|||
const loadedRef = ref<Nullable<boolean>>(false); |
|||
const tableRef = ref<Nullable<TableActionType>>(null); |
|||
const searchFormRef = ref<Nullable<UseTableMethod>>(null); |
|||
const addEditFormAction = ref<Nullable<() => FormActionType>>(null); |
|||
|
|||
let stopWatch: WatchStopHandle; |
|||
|
|||
const register = ( |
|||
instance: TableActionType, |
|||
searchFormInstance: UseTableMethod, |
|||
getAddEditForm: () => FormActionType, |
|||
) => { |
|||
isProdMode() && |
|||
onUnmounted(() => { |
|||
tableRef.value = null; |
|||
searchFormRef.value = null; |
|||
addEditFormAction.value = null; |
|||
}); |
|||
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return; |
|||
|
|||
tableRef.value = instance; |
|||
searchFormRef.value = searchFormInstance; |
|||
addEditFormAction.value = getAddEditForm; |
|||
loadedRef.value = true; |
|||
// 设置函数传递的props
|
|||
tableProps && instance.setProps(getDynamicProps(tableProps)); |
|||
|
|||
/** |
|||
* 监控table props变化 |
|||
*/ |
|||
stopWatch?.(); |
|||
stopWatch = watch( |
|||
() => tableProps, |
|||
() => { |
|||
tableProps && instance.setProps(getDynamicProps(tableProps)); |
|||
}, |
|||
{ |
|||
// todo:是否重复执行
|
|||
immediate: true, |
|||
deep: true, |
|||
}, |
|||
); |
|||
}; |
|||
|
|||
const getTableAction = (): TableActionType => { |
|||
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; |
|||
}; |
|||
|
|||
const methods: UseTableMethod = { |
|||
setProps: (props: Partial<SmartTableProps>) => { |
|||
getTableAction().setProps(props); |
|||
}, |
|||
getSearchForm: () => { |
|||
return unref(searchFormRef) as unknown as SmartSearchFormActionType; |
|||
}, |
|||
getAddEditForm: () => { |
|||
return unref(addEditFormAction)?.call(null); |
|||
}, |
|||
reload: async (opt?: FetchParams) => { |
|||
return await getTableAction().reload(opt); |
|||
}, |
|||
query: async (opt?: FetchParams) => { |
|||
return await getTableAction().query(opt); |
|||
}, |
|||
setLoading: (loading: boolean) => { |
|||
getTableAction().setLoading(loading); |
|||
}, |
|||
setPagination: (info: Partial<VxeGridPropTypes.PagerConfig>) => { |
|||
getTableAction().setPagination(info); |
|||
}, |
|||
setShowPagination: async (show: boolean) => { |
|||
await getTableAction().setShowPagination(show); |
|||
}, |
|||
getPagination: () => getTableAction().getPagination(), |
|||
getShowPagination: () => { |
|||
return getTableAction().getShowPagination(); |
|||
}, |
|||
commitVxeProxy: (code, ...args) => { |
|||
return getTableAction().commitVxeProxy(code, args); |
|||
}, |
|||
deleteByCheckbox: () => { |
|||
getTableAction().deleteByCheckbox(); |
|||
}, |
|||
getCheckboxRecords: (isFull: boolean) => { |
|||
return getTableAction().getCheckboxRecords(isFull); |
|||
}, |
|||
getRadioRecord: (isFull: boolean) => { |
|||
return getTableAction().getRadioRecord(isFull); |
|||
}, |
|||
showAddModal: (formData?: Recordable, selectData?: Recordable) => { |
|||
getTableAction().showAddModal(formData, selectData); |
|||
}, |
|||
editByCheckbox: () => { |
|||
return getTableAction().editByCheckbox(); |
|||
}, |
|||
getAddEditFieldsValue: () => { |
|||
return getTableAction().getAddEditFieldsValue(); |
|||
}, |
|||
resetAddEditFields: () => { |
|||
return getTableAction().resetAddEditFields(); |
|||
}, |
|||
setAddEditFieldsValue: (data: any) => { |
|||
return getTableAction().setAddEditFieldsValue(data); |
|||
}, |
|||
editByRowModal: (data, formData) => { |
|||
return getTableAction().editByRowModal(data, formData); |
|||
}, |
|||
deleteByRow: (data) => { |
|||
return getTableAction().deleteByRow(data); |
|||
}, |
|||
setRadioRow: (row) => { |
|||
return getTableAction().setRadioRow(row); |
|||
}, |
|||
setCheckboxRow(rows: any, checked: boolean): Promise<any> { |
|||
return getTableAction().setCheckboxRow(rows, checked); |
|||
}, |
|||
validateAddEdit: (nameList?: NamePath[]) => { |
|||
return getTableAction().validateAddEdit(nameList); |
|||
}, |
|||
validateAddEditFields: (nameList?: NamePath[]) => { |
|||
return getTableAction().validateAddEdit(nameList); |
|||
}, |
|||
getTableInstance: () => { |
|||
return getTableAction().getTableInstance(); |
|||
}, |
|||
getData: (rowIndex?: number) => { |
|||
return getTableAction().getData(rowIndex); |
|||
}, |
|||
useYnByCheckbox: (useYn: boolean) => { |
|||
return getTableAction().useYnByCheckbox(useYn); |
|||
}, |
|||
useYnByRow: (row: any | any[], useYn: boolean) => { |
|||
return getTableAction().useYnByRow(row, useYn); |
|||
}, |
|||
}; |
|||
return [register, methods]; |
|||
}; |
|||
@ -0,0 +1,88 @@ |
|||
import type { ComputedRef } from 'vue'; |
|||
import type { SmartTableProps } from '@/components/SmartTable'; |
|||
import type { VxeGridDefines, VxeGridInstance } from 'vxe-table'; |
|||
import { computed, unref } from 'vue'; |
|||
|
|||
import { defaultCheckboxConfig } from '../defaultConfig'; |
|||
|
|||
export const useSmartTableCheckbox = ( |
|||
tableProps: ComputedRef<SmartTableProps>, |
|||
emit: Function, |
|||
getTableInstance: () => VxeGridInstance, |
|||
) => { |
|||
let lastRowIndex: number | null = null; |
|||
const computeCheckboxTableProps = computed<SmartTableProps>(() => { |
|||
const checkboxConfig = { |
|||
...defaultCheckboxConfig, |
|||
...unref(tableProps).checkboxConfig, |
|||
}; |
|||
const { rowTrigger, rowShift, rowCtrl } = checkboxConfig; |
|||
if (!rowTrigger) { |
|||
return { |
|||
onCellClick: (params: VxeGridDefines.CellClickEventParams) => emit('cell-click', params), |
|||
}; |
|||
} |
|||
if (rowTrigger === 'multiple') { |
|||
return { |
|||
onCellClick: (params: VxeGridDefines.CellClickEventParams) => emit('cell-click', params), |
|||
checkboxConfig: { |
|||
...checkboxConfig, |
|||
trigger: 'row', |
|||
}, |
|||
}; |
|||
} |
|||
return { |
|||
onCellClick: (params: VxeGridDefines.CellClickEventParams) => |
|||
handleCellClick(params, rowShift, rowCtrl), |
|||
checkboxConfig: { |
|||
...checkboxConfig, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 单元格被点击事件 |
|||
*/ |
|||
const handleCellClick = async ( |
|||
params: VxeGridDefines.CellClickEventParams, |
|||
rowShift: boolean | undefined, |
|||
rowCtrl: boolean | undefined, |
|||
) => { |
|||
emit('cell-click', params); |
|||
|
|||
const gridInstance = getTableInstance(); |
|||
const { row, $event, rowIndex } = params; |
|||
const { ctrlKey, shiftKey } = $event as PointerEvent; |
|||
if (rowCtrl && ctrlKey) { |
|||
// 点击了ctrl
|
|||
gridInstance.toggleCheckboxRow(row); |
|||
} else { |
|||
gridInstance.clearCheckboxRow(); |
|||
gridInstance.setCheckboxRow(row, true); |
|||
} |
|||
if (rowShift && shiftKey) { |
|||
if (lastRowIndex == null) { |
|||
lastRowIndex = rowIndex; |
|||
} else { |
|||
// 第二次shift点击
|
|||
const tableData = gridInstance.getData(); |
|||
const indexList = [lastRowIndex, rowIndex]; |
|||
indexList.sort((a, b) => a - b); |
|||
const checkboxList: any[] = []; |
|||
for (let i = 0; i < tableData.length; i++) { |
|||
if (i >= indexList[0] && i <= indexList[1]) { |
|||
checkboxList.push(tableData[i]); |
|||
} |
|||
} |
|||
gridInstance.clearCheckboxRow(); |
|||
gridInstance.setCheckboxRow(checkboxList, true); |
|||
} |
|||
} else { |
|||
lastRowIndex = null; |
|||
} |
|||
}; |
|||
|
|||
return { |
|||
computeCheckboxTableProps, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,190 @@ |
|||
import type { ComputedRef, VNode } from 'vue'; |
|||
import { computed, unref } from 'vue'; |
|||
import type { SmartColumn, SmartTableProps } from '@/components/SmartTable'; |
|||
import type { VxeColumnSlotTypes } from 'vxe-table'; |
|||
import { isBoolean, isFunction } from '@/utils/is'; |
|||
import { getFormSize } from '../utils'; |
|||
import { VxeTablePropTypes } from 'vxe-table/types/table'; |
|||
|
|||
const getComponentProps = ( |
|||
params: VxeColumnSlotTypes.DefaultSlotParams, |
|||
column: SmartColumn, |
|||
): Recordable => { |
|||
const componentProps = column.componentProps; |
|||
let props; |
|||
if (isFunction(componentProps)) { |
|||
props = componentProps(params); |
|||
} else { |
|||
props = componentProps; |
|||
} |
|||
return props; |
|||
}; |
|||
|
|||
const componentMap: { |
|||
[index: string]: ( |
|||
column: SmartColumn, |
|||
t: Function, |
|||
) => (params: VxeColumnSlotTypes.DefaultSlotParams) => VNode | string; |
|||
} = { |
|||
switch: (column) => { |
|||
return (params: VxeColumnSlotTypes.DefaultSlotParams) => { |
|||
const props = { |
|||
checked: params.row[params.column.field], |
|||
...getComponentProps(params, column), |
|||
}; |
|||
return <a-switch {...props} />; |
|||
}; |
|||
}, |
|||
tag: (column) => { |
|||
return (params: VxeColumnSlotTypes.DefaultSlotParams) => { |
|||
const props = getComponentProps(params, column); |
|||
const defaultValue = props?.default || params.row[params.column.field]; |
|||
return <a-tag {...props}>{defaultValue}</a-tag>; |
|||
}; |
|||
}, |
|||
button: (column) => { |
|||
return (params: VxeColumnSlotTypes.DefaultSlotParams) => { |
|||
const props: Recordable = { |
|||
size: params.$table.props.size, |
|||
...getComponentProps(params, column), |
|||
}; |
|||
const defaultValue = props?.default || params.row[params.column.field]; |
|||
return <a-button {...props}>{defaultValue}</a-button>; |
|||
}; |
|||
}, |
|||
booleanTag: (column, t) => { |
|||
return (params) => { |
|||
const componentProps = getComponentProps(params, column); |
|||
const defaultValue = componentProps?.default || params.row[params.column.field]; |
|||
if (!isBoolean(defaultValue)) { |
|||
return ''; |
|||
} |
|||
const props: Recordable = { |
|||
...componentProps, |
|||
color: defaultValue ? '#108ee9' : '#f50', |
|||
}; |
|||
return <a-tag {...props}>{defaultValue ? t('common.form.yes') : t('common.form.no')}</a-tag>; |
|||
}; |
|||
}, |
|||
useYnTag: (column, t) => { |
|||
return (params) => { |
|||
const componentProps = getComponentProps(params, column); |
|||
const defaultValue = componentProps?.default || params.row[params.column.field]; |
|||
if (!isBoolean(defaultValue)) { |
|||
return ''; |
|||
} |
|||
const props: Recordable = { |
|||
...componentProps, |
|||
color: defaultValue ? '#108ee9' : '#f50', |
|||
}; |
|||
return ( |
|||
<a-tag {...props}>{defaultValue ? t('common.form.use') : t('common.form.noUse')}</a-tag> |
|||
); |
|||
}; |
|||
}, |
|||
}; |
|||
|
|||
export const useSmartTableColumn = (tableProps: ComputedRef<SmartTableProps>, t: Function) => { |
|||
const getTableColumns = computed<Array<SmartColumn>>((): SmartColumn[] => { |
|||
const propsColumns = unref(tableProps).columns || []; |
|||
const tableSize = unref(tableProps).size; |
|||
const result = propsColumns.map((column) => { |
|||
const { component, slots } = column; |
|||
if (!component || slots) { |
|||
return column; |
|||
} |
|||
const defaultSlot = componentMap[component]; |
|||
if (!defaultSlot) { |
|||
return column; |
|||
} |
|||
return { |
|||
...column, |
|||
slots: { |
|||
default: defaultSlot(column, t), |
|||
}, |
|||
}; |
|||
}); |
|||
convertEditRender(result, tableSize); |
|||
return result; |
|||
}); |
|||
|
|||
/** |
|||
* 可编辑表格计算属性 |
|||
*/ |
|||
const computedEditRules = computed<VxeTablePropTypes.EditRules | null>(() => { |
|||
const rules: VxeTablePropTypes.EditRules = {}; |
|||
const propsColumns = unref(tableProps).columns || []; |
|||
for (const { editRender, field } of propsColumns) { |
|||
if (!editRender) { |
|||
continue; |
|||
} |
|||
if (editRender.rules) { |
|||
rules[field!] = editRender.rules; |
|||
continue; |
|||
} |
|||
if (!editRender.required) { |
|||
continue; |
|||
} |
|||
rules[field!] = [ |
|||
{ |
|||
required: true, |
|||
content: '请输入', |
|||
}, |
|||
]; |
|||
} |
|||
if (Object.keys(rules).length === 0) { |
|||
return null; |
|||
} |
|||
return rules; |
|||
}); |
|||
return { |
|||
getTableColumns, |
|||
computedEditRules, |
|||
}; |
|||
}; |
|||
|
|||
/** |
|||
* 转换可编辑列 |
|||
* @param columns |
|||
* @param tableSize |
|||
*/ |
|||
const convertEditRender = (columns: SmartColumn[], tableSize) => { |
|||
for (const column of columns) { |
|||
const { editRender } = column; |
|||
if (!editRender) { |
|||
continue; |
|||
} |
|||
if (!editRender.props) { |
|||
editRender.props = {}; |
|||
} |
|||
// 处理尺寸
|
|||
if (!editRender.props?.size) { |
|||
const size = getFormSize(tableSize); |
|||
if (editRender.props) { |
|||
editRender.props.size = size; |
|||
} else { |
|||
editRender.props = { |
|||
size, |
|||
}; |
|||
} |
|||
} |
|||
// 处理自动聚焦
|
|||
const { autofocus, name } = editRender; |
|||
if (isBoolean(autofocus) && autofocus) { |
|||
if (name === 'ASelect') { |
|||
editRender.autofocus = '.ant-select-selection-search-input'; |
|||
} |
|||
if (name === 'ADatePicker') { |
|||
editRender.autofocus = '.ant-picker-input input'; |
|||
} |
|||
} |
|||
// 处理事件冒泡
|
|||
if (editRender.stopEnterBubbling !== false) { |
|||
editRender.props.onKeydown = (event: KeyboardEvent) => { |
|||
if (event.key === 'Enter') { |
|||
event.stopPropagation(); |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,48 @@ |
|||
import type { VxeGridInstance } from 'vxe-table'; |
|||
import { computed } from 'vue'; |
|||
|
|||
const SMART_TABLE_CUSTOM_COLUMN_SORT = 'SMART_TABLE_CUSTOM_COLUMN_SORT'; |
|||
|
|||
export const useSmartTableColumnConfig = (getTableInstance: () => VxeGridInstance) => { |
|||
const setColumnSortConfig = () => { |
|||
const fullColumnList = getTableInstance()?.getTableColumn()?.fullColumn || []; |
|||
const tableId = getTableInstance()?.id; |
|||
if (!tableId) { |
|||
return; |
|||
} |
|||
const columns = fullColumnList |
|||
.map((item) => { |
|||
const field = item.field; |
|||
if (!field) { |
|||
console.warn(item); |
|||
console.warn('SMART-TABLE:列未设置field,列顺序设置无法正常使用'); |
|||
} |
|||
return field; |
|||
}) |
|||
.filter((item) => item !== undefined && item !== null); |
|||
const allConfig = |
|||
JSON.parse(localStorage.getItem(SMART_TABLE_CUSTOM_COLUMN_SORT) || '{}') || {}; |
|||
allConfig[tableId] = columns; |
|||
localStorage.setItem(SMART_TABLE_CUSTOM_COLUMN_SORT, JSON.stringify(allConfig)); |
|||
}; |
|||
|
|||
const getColumnSortConfig = (tableId: string) => { |
|||
return JSON.parse(localStorage.getItem(SMART_TABLE_CUSTOM_COLUMN_SORT) || '{}')[tableId]; |
|||
}; |
|||
|
|||
const computedColumnSort = computed(() => { |
|||
const tableId = getTableInstance()?.id; |
|||
if (!tableId) { |
|||
return null; |
|||
} |
|||
if (getTableInstance()?.customConfig?.storage !== true) { |
|||
return null; |
|||
} |
|||
return getColumnSortConfig(tableId); |
|||
}); |
|||
|
|||
return { |
|||
setColumnSortConfig, |
|||
computedColumnSort, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,124 @@ |
|||
import type { ComputedRef } from 'vue'; |
|||
import type { |
|||
SmartTableProps, |
|||
SmartColumnDynamicClass, |
|||
SmartColumnDynamicStyle, |
|||
SmartColumnAutoClass, |
|||
} from '@/components/SmartTable'; |
|||
import type { VxeTablePropTypes } from 'vxe-table'; |
|||
|
|||
import { computed, unref } from 'vue'; |
|||
import { warn } from '@/utils/log'; |
|||
import { isObject, isString } from '@/utils/is'; |
|||
|
|||
const autoClass: { [index in SmartColumnAutoClass]: SmartColumnDynamicClass } = { |
|||
Boolean: ({ row, column }) => { |
|||
const value = row[column.field]; |
|||
if (value === null || value === undefined) { |
|||
return ''; |
|||
} |
|||
if (value) { |
|||
return 'text-color--success-bold'; |
|||
} |
|||
return 'text-color--danger-bold'; |
|||
}, |
|||
}; |
|||
|
|||
export const useSmartTableDynamicClassStyle = (tablePropsRef: ComputedRef<SmartTableProps>) => { |
|||
/** |
|||
* 动态class计算属性 |
|||
*/ |
|||
const computedClassName = computed<VxeTablePropTypes.CellClassName | undefined>(() => { |
|||
const tableProps = unref(tablePropsRef); |
|||
const tableDynamicClass: Recordable<SmartColumnDynamicClass> = {}; |
|||
tableProps.columns?.forEach((column) => { |
|||
if (column.dynamicClass) { |
|||
if (!column.field) { |
|||
warn('列未设置field,dynamicClass失效'); |
|||
console.warn(column); |
|||
} else { |
|||
tableDynamicClass[column.field] = column.dynamicClass; |
|||
} |
|||
} else if (column.autoClass) { |
|||
if (!column.field) { |
|||
warn('列未设置field,dynamicClass失效'); |
|||
console.warn(column); |
|||
} else { |
|||
tableDynamicClass[column.field] = autoClass[column.autoClass]; |
|||
} |
|||
} |
|||
}); |
|||
if (tableProps.cellClassName || tableProps.rowClassName) { |
|||
if (Object.keys(tableDynamicClass).length > 0) { |
|||
warn('表格设置了cellClassName或rowClassName,列dynamicClass失效'); |
|||
} |
|||
return undefined; |
|||
} |
|||
if (Object.keys(tableDynamicClass).length === 0) { |
|||
return undefined; |
|||
} |
|||
return (params) => { |
|||
const { column } = params; |
|||
const field = column.field; |
|||
const dynamicClass = tableDynamicClass[field]; |
|||
if (!dynamicClass) { |
|||
return undefined; |
|||
} |
|||
if (isString(dynamicClass)) { |
|||
return dynamicClass; |
|||
} |
|||
return dynamicClass(params); |
|||
}; |
|||
}); |
|||
|
|||
const computedStyle = computed(() => { |
|||
const tableProps = unref(tablePropsRef); |
|||
const tableDynamicStyle: Recordable<SmartColumnDynamicStyle> = {}; |
|||
tableProps.columns?.forEach((column) => { |
|||
if (column.dynamicStyle) { |
|||
if (!column.field) { |
|||
warn('列未设置field,dynamicStyle失效'); |
|||
console.warn(column); |
|||
} else { |
|||
tableDynamicStyle[column.field] = column.dynamicStyle; |
|||
} |
|||
} |
|||
}); |
|||
if (tableProps.cellStyle || tableProps.rowStyle) { |
|||
if (Object.keys(tableDynamicStyle).length > 0) { |
|||
warn('表格设置了cellStyle或rowStyle,列dynamicStyle失效'); |
|||
} |
|||
return undefined; |
|||
} |
|||
if (Object.keys(tableDynamicStyle).length === 0) { |
|||
return undefined; |
|||
} |
|||
return (params) => { |
|||
const { column } = params; |
|||
const field = column.field; |
|||
const dynamicStyle = tableDynamicStyle[field]; |
|||
if (!dynamicStyle) { |
|||
return undefined; |
|||
} |
|||
if (isObject(dynamicStyle)) { |
|||
return dynamicStyle; |
|||
} |
|||
return dynamicStyle(params); |
|||
}; |
|||
}); |
|||
|
|||
const computedTableClassStyle = computed(() => { |
|||
const props: SmartTableProps = {}; |
|||
if (unref(computedClassName)) { |
|||
props.cellClassName = unref(computedClassName); |
|||
} |
|||
if (unref(computedStyle)) { |
|||
props.cellStyle = unref(computedStyle); |
|||
} |
|||
return props; |
|||
}); |
|||
|
|||
return { |
|||
computedTableClassStyle, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,260 @@ |
|||
import type { |
|||
SmartTableAjaxQueryParams, |
|||
FetchParams, |
|||
SmartTableProps, |
|||
SmartTableProxyConfig, |
|||
SmartSearchFormParameter, |
|||
} from '@/components/SmartTable'; |
|||
import type { VxeGridInstance, VxeGridPropTypes } from 'vxe-table'; |
|||
import type { ComputedRef, Ref } from 'vue'; |
|||
|
|||
import { computed, createVNode, unref } from 'vue'; |
|||
|
|||
import { message, Modal } from 'ant-design-vue'; |
|||
|
|||
import { isArray } from '@/utils/is'; |
|||
import { omit, merge } from 'lodash-es'; |
|||
import { error } from '@/utils/log'; |
|||
import { useI18n } from '@/hooks/web/useI18n'; |
|||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
import { useMessage } from '@/hooks/web/useMessage'; |
|||
|
|||
interface ActionType { |
|||
commitVxeProxy: (code: string, ...args) => void; |
|||
getSearchFormParameter: () => SmartSearchFormParameter; |
|||
setLoading: (loading: boolean) => void; |
|||
getCheckboxRecords: (isFull: boolean) => Array<any>; |
|||
} |
|||
|
|||
const { createConfirm, warnMessage, successMessage } = useMessage(); |
|||
|
|||
export const useTableAjax = ( |
|||
propsRef: ComputedRef<SmartTableProps>, |
|||
vxeGridRef: Ref<VxeGridInstance>, |
|||
emit, |
|||
{ commitVxeProxy, getSearchFormParameter, setLoading, getCheckboxRecords }: ActionType, |
|||
) => { |
|||
const { t } = useI18n(); |
|||
|
|||
const getProxyConfigRef = computed<SmartTableProxyConfig | undefined>(() => { |
|||
const { proxyConfig, useSearchForm, sortConfig } = unref(propsRef); |
|||
if (!proxyConfig) { |
|||
return; |
|||
} |
|||
const ajax = proxyConfig.ajax || {}; |
|||
let queryAjax: any = {}; |
|||
if (ajax.query) { |
|||
queryAjax = { |
|||
query: async (params, args) => { |
|||
try { |
|||
const { form, filters, page, sorts, sort, $grid } = |
|||
params as VxeGridPropTypes.ProxyAjaxQueryParams; |
|||
let fetchParams: FetchParams = {}; |
|||
if (args && args.length > 0) { |
|||
fetchParams = args[0]; |
|||
} |
|||
const searchParameter: SmartTableAjaxQueryParams = merge( |
|||
{ |
|||
$grid, |
|||
form, |
|||
filters, |
|||
page, |
|||
sorts, |
|||
sort, |
|||
}, |
|||
fetchParams, |
|||
); |
|||
let ajaxParameter: Recordable = { |
|||
...form, |
|||
...page, |
|||
}; |
|||
if (sorts.length > 0) { |
|||
const sortNameList: string[] = []; |
|||
const sortOrderList: string[] = []; |
|||
for (const item of sorts) { |
|||
sortNameList.push(item.field); |
|||
sortOrderList.push(item.order); |
|||
} |
|||
ajaxParameter.sortName = sortNameList.join(','); |
|||
ajaxParameter.sortOrder = sortOrderList.join(','); |
|||
} |
|||
const { searchForm, searchSymbolForm, noSymbolForm, searchWithSymbol } = |
|||
getSearchFormParameter(); |
|||
if (useSearchForm) { |
|||
if (searchWithSymbol) { |
|||
// 处理搜索符号
|
|||
searchParameter.searchFormSymbol = searchSymbolForm; |
|||
Object.assign( |
|||
ajaxParameter, |
|||
{ |
|||
parameter: searchSymbolForm, |
|||
}, |
|||
noSymbolForm, |
|||
); |
|||
} else { |
|||
Object.assign(ajaxParameter, searchForm); |
|||
} |
|||
searchParameter.searchForm = searchForm; |
|||
} |
|||
ajaxParameter = omit(ajaxParameter, ['total']); |
|||
ajaxParameter = merge(ajaxParameter, searchParameter.searchInfo); |
|||
// 添加额外的查询条件
|
|||
searchParameter.ajaxParameter = ajaxParameter; |
|||
let result = await ajax.query!(searchParameter); |
|||
if (proxyConfig.afterLoad) { |
|||
result = proxyConfig.afterLoad(result); |
|||
} |
|||
return result; |
|||
} catch (e) { |
|||
console.error(e); |
|||
throw e; |
|||
} |
|||
}, |
|||
}; |
|||
} |
|||
const sort = sortConfig?.remote === true; |
|||
return { |
|||
// autoLoad: false,
|
|||
sort, |
|||
props: { |
|||
result: 'rows', |
|||
total: 'total', |
|||
}, |
|||
...proxyConfig, |
|||
ajax: { |
|||
...ajax, |
|||
...queryAjax, |
|||
}, |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 重载数据函数 |
|||
* @param opt |
|||
*/ |
|||
const reload = async (opt?: FetchParams) => { |
|||
try { |
|||
setLoading(true); |
|||
await commitVxeProxy('reload', opt); |
|||
emit('proxy-query', { status: true, isReload: true, isInited: false }); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
const query = async (opt?: FetchParams) => { |
|||
try { |
|||
setLoading(true); |
|||
await commitVxeProxy('query', opt); |
|||
emit('proxy-query', { status: true, isReload: false, isInited: false }); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* 通过checkbox删除 |
|||
*/ |
|||
const deleteByCheckbox = async () => { |
|||
const rows = getCheckboxRecords(false); |
|||
if (!rows.length) { |
|||
message.warn(t('common.notice.deleteChoose')); |
|||
return false; |
|||
} |
|||
return await doDelete(rows); |
|||
}; |
|||
|
|||
const deleteByRow = (row: any | any[]) => { |
|||
if (isArray(row)) { |
|||
return doDelete(row); |
|||
} |
|||
return doDelete([row]); |
|||
}; |
|||
|
|||
const getProxyEvents = { |
|||
onProxyQuery: (...args) => emit('proxy-query', args), |
|||
onProxyDelete: (...args) => emit('proxy-delete', args), |
|||
}; |
|||
|
|||
const doDelete = async (rows: any[]): Promise<boolean | undefined> => { |
|||
const proxyConfig = unref(propsRef)?.proxyConfig; |
|||
const deleteMethod = proxyConfig?.ajax?.delete; |
|||
if (!deleteMethod) { |
|||
error('proxyConfig.ajax.delete未配置,无法删除'); |
|||
return false; |
|||
} |
|||
if (rows.length === 0) { |
|||
return false; |
|||
} |
|||
Modal.confirm({ |
|||
title: t('common.button.confirm'), |
|||
icon: createVNode(ExclamationCircleOutlined), |
|||
content: t('common.notice.deleteConfirm'), |
|||
onOk: async () => { |
|||
const result = await deleteMethod({ |
|||
$grid: unref(vxeGridRef), |
|||
body: { |
|||
removeRecords: rows, |
|||
}, |
|||
}); |
|||
message.success(t('common.message.deleteSuccess')); |
|||
emit('proxy-delete', { status: true }); |
|||
const afterDelete = proxyConfig?.afterDelete || query; |
|||
afterDelete && afterDelete(result); |
|||
return Promise.resolve(true); |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
const useYnByCheckbox = async (useYn: boolean) => { |
|||
const rows = getCheckboxRecords(false); |
|||
if (!rows.length) { |
|||
warnMessage(t('common.notice.select')); |
|||
return false; |
|||
} |
|||
return doUseYn(rows, useYn); |
|||
}; |
|||
|
|||
const useYnByRow = (row: any | any[], useYn: boolean) => { |
|||
if (isArray(row)) { |
|||
return doUseYn(row, useYn); |
|||
} |
|||
return doUseYn([row], useYn); |
|||
}; |
|||
|
|||
const doUseYn = async (rows: any[], useYn: boolean): Promise<boolean | undefined> => { |
|||
const proxyConfig = unref(propsRef)?.proxyConfig; |
|||
const useYnMethod = proxyConfig?.ajax?.useYn; |
|||
if (!useYnMethod) { |
|||
error('proxyConfig.ajax.useYn未配置,无法执行启用停用操作'); |
|||
return false; |
|||
} |
|||
if (rows.length === 0) { |
|||
return false; |
|||
} |
|||
createConfirm({ |
|||
iconType: 'warning', |
|||
content: useYn ? t('common.notice.useYnTrueConfirm') : t('common.notice.useYnFalseConfirm'), |
|||
onOk: async () => { |
|||
const result = await useYnMethod(rows, useYn); |
|||
successMessage({ |
|||
message: t('common.message.OperationSucceeded'), |
|||
}); |
|||
const afterHandler = proxyConfig?.afterUserYn || query; |
|||
afterHandler && afterHandler(result); |
|||
return Promise.resolve(true); |
|||
}, |
|||
}); |
|||
}; |
|||
return { |
|||
reload, |
|||
query, |
|||
getProxyConfigRef, |
|||
deleteByRow, |
|||
deleteByCheckbox, |
|||
getProxyEvents, |
|||
useYnByCheckbox, |
|||
useYnByRow, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,195 @@ |
|||
import type { ComputedRef, Ref } from 'vue'; |
|||
import type { |
|||
SmartColumn, |
|||
SmartTableProps, |
|||
SmartTableRowDragConfig, |
|||
} from '@/components/SmartTable'; |
|||
import type { VxeGridInstance } from 'vxe-table'; |
|||
|
|||
import { h, onMounted, nextTick, computed, onUnmounted, unref, watch } from 'vue'; |
|||
|
|||
import Sortable from 'sortablejs'; |
|||
|
|||
import { isBoolean } from '@/utils/is'; |
|||
import { Icon } from '@/components/Icon'; |
|||
|
|||
export const TABLE_DRAG_SLOT_NAME = 'smart-table-drop-slot'; |
|||
|
|||
interface TableAction { |
|||
getData: (rowIndex?: number) => any[]; |
|||
loadData: (data: any[]) => Promise<any>; |
|||
} |
|||
|
|||
const defaultRowDragConfig: SmartTableRowDragConfig = { |
|||
handle: 'smart-table-drop-handle', |
|||
animation: 150, |
|||
icon: 'ant-design:menu-outlined', |
|||
ghostClass: 'blue-background-class', |
|||
dataIdAttr: 'rowid', |
|||
}; |
|||
|
|||
/** |
|||
* 支持行拖动功能 |
|||
*/ |
|||
export const useTableRowDrag = ( |
|||
tablePropsRef: ComputedRef<SmartTableProps>, |
|||
tableRef: Ref<VxeGridInstance>, |
|||
{ getData }: TableAction, |
|||
) => { |
|||
let sortable: Sortable | null = null; |
|||
|
|||
const getKeyField = computed(() => unref(tablePropsRef).rowConfig?.keyField); |
|||
// 是否有左侧锁定列
|
|||
const getHasFixedLeft = computed(() => { |
|||
const tableColumns = unref(tablePropsRef).columns; |
|||
if (!tableColumns) { |
|||
return false; |
|||
} |
|||
return tableColumns.some((item) => item.fixed === 'left'); |
|||
}); |
|||
|
|||
/** |
|||
* 获取拖拽配置 |
|||
*/ |
|||
const getRowDragConfig = computed<SmartTableRowDragConfig | false>(() => { |
|||
const { dragConfig } = unref(tablePropsRef).rowConfig || {}; |
|||
if (!dragConfig) { |
|||
return false; |
|||
} |
|||
let config: SmartTableRowDragConfig; |
|||
if (isBoolean(dragConfig)) { |
|||
config = defaultRowDragConfig; |
|||
} else { |
|||
config = { |
|||
...defaultRowDragConfig, |
|||
...dragConfig, |
|||
}; |
|||
} |
|||
return { |
|||
...config, |
|||
handle: `.${config.handle}`, |
|||
onEnd: async ({ newIndex, oldIndex }) => { |
|||
if (newIndex !== oldIndex) { |
|||
const tableData = getData(); |
|||
// 记录原顺序
|
|||
const keySorts = tableData.map((item) => { |
|||
return item[unref(getKeyField)!] + ''; |
|||
}); |
|||
const currentRow = tableData.splice(oldIndex!, 1)[0]; |
|||
tableData.splice(newIndex!, 0, currentRow); |
|||
// 还原顺序
|
|||
sortable?.sort(keySorts); |
|||
// 更新数据排序
|
|||
unref(tableRef).reactData.tableData = tableData; |
|||
} |
|||
}, |
|||
}; |
|||
}); |
|||
/** |
|||
* 获取拖拽插槽 |
|||
*/ |
|||
const getTableDragSlot = computed(() => { |
|||
const dragConfig = unref(getRowDragConfig); |
|||
if (!dragConfig) { |
|||
return {}; |
|||
} |
|||
if (dragConfig.custom) { |
|||
// 自定义列,返回null
|
|||
return {}; |
|||
} |
|||
const slots: Recordable = {}; |
|||
slots[TABLE_DRAG_SLOT_NAME] = ({ rowIndex }) => { |
|||
return h( |
|||
'div', |
|||
{ 'data-id': rowIndex, class: [dragConfig.handle?.slice(1)] }, |
|||
h(Icon, { icon: dragConfig.icon, 'data-id': rowIndex }), |
|||
); |
|||
}; |
|||
return slots; |
|||
}); |
|||
/** |
|||
* 获取拖拽列信息 |
|||
*/ |
|||
const getTableDragColumn = computed<SmartColumn[]>(() => { |
|||
const dragConfig = unref(getRowDragConfig); |
|||
if (!dragConfig) { |
|||
return []; |
|||
} |
|||
if (dragConfig.custom) { |
|||
// 自定义列,返回null
|
|||
return []; |
|||
} |
|||
const column: SmartColumn = { |
|||
title: '#', |
|||
field: 'drag', |
|||
width: 60, |
|||
// fixed: 'left',
|
|||
slots: { |
|||
default: TABLE_DRAG_SLOT_NAME, |
|||
}, |
|||
}; |
|||
if (unref(getHasFixedLeft)) { |
|||
column.fixed = 'left'; |
|||
} |
|||
return [column]; |
|||
}); |
|||
|
|||
/** |
|||
* 开启拖拽功能 |
|||
*/ |
|||
const openRowDrop = async () => { |
|||
if (!unref(getKeyField)) { |
|||
throw new Error('row-config.key-field无法开启行拖拽,请设置key-field'); |
|||
} |
|||
if (sortable !== null) { |
|||
sortable.destroy(); |
|||
} |
|||
const el = unref(tableRef).$el; |
|||
let sortEl; |
|||
if (!unref(getHasFixedLeft)) { |
|||
sortEl = el.querySelector('.body--wrapper>.vxe-table--body tbody'); |
|||
} else { |
|||
const selector = '.fixed-left--wrapper>.vxe-table--body tbody'; |
|||
sortEl = el.querySelector(selector); |
|||
if (sortEl == null) { |
|||
while (sortEl === null) { |
|||
await nextTick(() => (sortEl = el.querySelector(selector))); |
|||
} |
|||
} |
|||
} |
|||
sortable = Sortable.create(sortEl, unref(getRowDragConfig) as SmartTableRowDragConfig); |
|||
}; |
|||
/** |
|||
* 关闭行拖拽 |
|||
*/ |
|||
const closeRowDrop = () => { |
|||
if (sortable !== null) { |
|||
sortable.destroy(); |
|||
sortable = null; |
|||
} |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
if (unref(getRowDragConfig)) { |
|||
openRowDrop(); |
|||
} |
|||
}); |
|||
onUnmounted(() => closeRowDrop()); |
|||
|
|||
watch(getRowDragConfig, (value) => { |
|||
if (value) { |
|||
if (sortable === null && unref(tableRef)) { |
|||
openRowDrop(); |
|||
} |
|||
} else { |
|||
closeRowDrop(); |
|||
} |
|||
}); |
|||
|
|||
return { |
|||
openRowDrop, |
|||
closeRowDrop, |
|||
getTableDragColumn, |
|||
getTableDragSlot, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,213 @@ |
|||
import type { ComputedRef, Ref, Slots } from 'vue'; |
|||
import type { SmartAddEditModalCallbackData, SmartTableProps } from '@/components/SmartTable'; |
|||
import type { FormProps } from '@/components/Form'; |
|||
import type { ModalProps } from '@/components/Modal'; |
|||
|
|||
import { computed, nextTick, ref, Slot, unref } from 'vue'; |
|||
import { useI18n } from '@/hooks/web/useI18n'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { error } from '@/utils/log'; |
|||
|
|||
interface TableAction { |
|||
getCheckboxRecords: (isFull: boolean) => Array<any>; |
|||
openAddEditModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void; |
|||
query: (parameter?) => Promise<void>; |
|||
} |
|||
|
|||
const SizeMap: { [index: string]: 'default' | 'small' | 'large' } = { |
|||
midum: 'default', |
|||
small: 'small', |
|||
mini: 'small', |
|||
tiny: 'small', |
|||
}; |
|||
|
|||
export const useTableModalAddEditConfig = ( |
|||
emit: Function, |
|||
tableProps: ComputedRef<SmartTableProps>, |
|||
slots: Slots, |
|||
{ getCheckboxRecords, openAddEditModal, query }: TableAction, |
|||
) => { |
|||
const { t } = useI18n(); |
|||
const isAddRef = ref(true); |
|||
|
|||
/** |
|||
* 是否有添加修改弹窗 |
|||
*/ |
|||
const getHasAddEdit = computed<boolean>(() => { |
|||
return unref(tableProps).addEditConfig !== undefined; |
|||
}); |
|||
|
|||
/** |
|||
* 添加修改弹窗props |
|||
*/ |
|||
const getAddEditFormProps = computed<FormProps>(() => { |
|||
const { size, addEditConfig } = unref(tableProps); |
|||
const formConfig = addEditConfig?.formConfig; |
|||
if (!formConfig) { |
|||
return {}; |
|||
} |
|||
const props: FormProps = { |
|||
...getDefaultFormConfig(), |
|||
...formConfig, |
|||
}; |
|||
if (size) { |
|||
props.size = formConfig.size || SizeMap[size]; |
|||
} |
|||
return props; |
|||
}); |
|||
|
|||
function replaceFormSlotKey(key: string) { |
|||
if (!key) return ''; |
|||
return key?.replace?.(/addEditForm-/, '') ?? ''; |
|||
} |
|||
/** |
|||
* 添加修改表单form插槽 |
|||
*/ |
|||
const getAddEditFormSlots: ComputedRef<Slots> = computed(() => { |
|||
// 获取form插槽
|
|||
const formSlots: { [name: string]: Slot | undefined } = {}; |
|||
Object.keys(slots) |
|||
.map((item) => (item.startsWith('addEditForm-') ? item : null)) |
|||
.filter((item) => !!item) |
|||
.forEach((item) => { |
|||
const formKey = replaceFormSlotKey(item as string); |
|||
formSlots[formKey] = slots[item as string]; |
|||
}); |
|||
// 获取column插槽
|
|||
const columnSlots: { [name: string]: Slot | undefined } = {}; |
|||
const schemas = unref(tableProps).addEditConfig?.formConfig?.schemas || []; |
|||
schemas |
|||
.map((item: any) => item.slot) |
|||
.filter((item) => !!item) |
|||
.forEach((item) => { |
|||
columnSlots[item as string] = slots[item as string]; |
|||
}); |
|||
const result = { |
|||
...formSlots, |
|||
...columnSlots, |
|||
}; |
|||
if ( |
|||
Object.keys(formSlots).length + Object.keys(columnSlots).length > |
|||
Object.keys(result).length |
|||
) { |
|||
error('添加修改表单插槽命名重复'); |
|||
} |
|||
return result; |
|||
}); |
|||
|
|||
/** |
|||
* 添加修改弹窗props |
|||
*/ |
|||
const getAddEditModalProps = computed<Partial<ModalProps>>(() => { |
|||
const { addEditConfig, proxyConfig } = unref(tableProps); |
|||
if (!addEditConfig) { |
|||
return {}; |
|||
} |
|||
const { modalConfig, beforeSave, afterSave } = addEditConfig!; |
|||
const saveFunction = proxyConfig?.ajax?.save; |
|||
return { |
|||
...getDefaultModalConfig(isAddRef, t), |
|||
...(modalConfig || {}), |
|||
beforeSave, |
|||
saveFunction, |
|||
afterSave: |
|||
afterSave || |
|||
(() => { |
|||
query(); |
|||
return true; |
|||
}), |
|||
}; |
|||
}); |
|||
|
|||
const showAddModal = (formData?: Recordable, selectData?: Recordable) => { |
|||
if (!unref(getHasAddEdit)) { |
|||
throw new Error('addEditConfig未定义'); |
|||
} |
|||
isAddRef.value = true; |
|||
nextTick(() => { |
|||
emit('add-edit-modal-show', { isAdd: true }); |
|||
}); |
|||
openAddEditModal(true, getCallbackData(true, selectData, formData)); |
|||
}; |
|||
|
|||
const editByRowModal = (row, formData?: Recordable) => { |
|||
return doEdit(row, formData); |
|||
}; |
|||
|
|||
const editByCheckbox = () => { |
|||
const selectRows = getCheckboxRecords(false); |
|||
if (selectRows.length !== 1) { |
|||
message.warn(t('common.notice.choseOne')); |
|||
return false; |
|||
} |
|||
const editRow = selectRows[0]; |
|||
return doEdit(editRow); |
|||
}; |
|||
|
|||
const doEdit = async (row, formData?: Recordable) => { |
|||
if (!unref(getHasAddEdit)) { |
|||
throw new Error('addEditConfig未定义'); |
|||
} |
|||
isAddRef.value = false; |
|||
nextTick(() => { |
|||
emit('add-edit-modal-show', { isAdd: false, row, formData }); |
|||
}); |
|||
if (unref(tableProps).addEditConfig?.openModalHandler) { |
|||
unref(tableProps).addEditConfig?.openModalHandler?.(row, formData); |
|||
} else { |
|||
openAddEditModal(true, getCallbackData(false, row, formData)); |
|||
} |
|||
return true; |
|||
}; |
|||
|
|||
const getCallbackData = ( |
|||
isAdd: boolean, |
|||
selectData?: Recordable, |
|||
formData?: Recordable, |
|||
): SmartAddEditModalCallbackData => { |
|||
const getByIdFunction = unref(tableProps)?.proxyConfig?.ajax?.getById; |
|||
if (!getByIdFunction) { |
|||
throw new Error('proxyConfig.ajax.getById未设置'); |
|||
} |
|||
return { |
|||
getFunction: getByIdFunction, |
|||
isAdd, |
|||
selectData, |
|||
formData, |
|||
validateFunction: unref(tableProps)?.addEditConfig?.afterLoadData, |
|||
}; |
|||
}; |
|||
|
|||
return { |
|||
showAddModal, |
|||
editByCheckbox, |
|||
getHasAddEdit, |
|||
getAddEditFormProps, |
|||
getAddEditModalProps, |
|||
editByRowModal, |
|||
getAddEditFormSlots, |
|||
}; |
|||
}; |
|||
|
|||
const getDefaultFormConfig = (): Partial<FormProps> => { |
|||
return { |
|||
showActionButtonGroup: false, |
|||
labelCol: { |
|||
span: 5, |
|||
}, |
|||
wrapperCol: { |
|||
span: 19, |
|||
}, |
|||
baseColProps: { |
|||
span: 24, |
|||
xxl: 12, |
|||
}, |
|||
}; |
|||
}; |
|||
|
|||
const getDefaultModalConfig = (isAddRef: Ref<boolean>, t: Function): Partial<ModalProps> => { |
|||
const title = unref(isAddRef) ? t('common.title.add') : t('common.title.edit'); |
|||
return { |
|||
title, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,83 @@ |
|||
import type { SmartTableProps } from '@/components/SmartTable'; |
|||
import { computed, ComputedRef, ref, unref } from 'vue'; |
|||
import type { VxeGridPropTypes } from 'vxe-table'; |
|||
import { isBoolean } from '@/utils/is'; |
|||
import { |
|||
DEFAULT_PAGE_LAYOUTS, |
|||
PAGE_SIZE, |
|||
PAGE_SIZE_OPTIONS, |
|||
} from '@/components/SmartTable/src/const'; |
|||
|
|||
export const useTablePager = (tableProps: ComputedRef<SmartTableProps>, emit: Function) => { |
|||
const configRef = ref<VxeGridPropTypes.PagerConfig>({}); |
|||
const show = ref(true); |
|||
|
|||
/** |
|||
* 获取分页配置 |
|||
*/ |
|||
const getPagerConfig = computed((): VxeGridPropTypes.PagerConfig | undefined => { |
|||
const { pagerConfig } = unref(tableProps); |
|||
if (!pagerConfig) { |
|||
return undefined; |
|||
} |
|||
if (!unref(show) || (isBoolean(pagerConfig) && !pagerConfig)) { |
|||
return undefined; |
|||
} |
|||
return { |
|||
currentPage: 1, |
|||
pageSize: PAGE_SIZE, |
|||
pageSizes: PAGE_SIZE_OPTIONS, |
|||
layouts: DEFAULT_PAGE_LAYOUTS, |
|||
...(isBoolean(pagerConfig) ? {} : pagerConfig), |
|||
...unref(configRef), |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* 分页触发 |
|||
*/ |
|||
const handlePageChange = (eventData) => { |
|||
configRef.value.pageSize = eventData.pageSize; |
|||
configRef.value.currentPage = eventData.currentPage; |
|||
emit('page-change', eventData); |
|||
}; |
|||
|
|||
/** |
|||
* 插槽信息 |
|||
*/ |
|||
const getPagerSlots = computed(() => { |
|||
if (!unref(getPagerConfig)) { |
|||
return {}; |
|||
} |
|||
return { |
|||
pager: () => <vxe-pager {...unref(getPagerConfig)} onPageChange={handlePageChange} />, |
|||
}; |
|||
}); |
|||
|
|||
async function setShowPagination(flag: boolean) { |
|||
show.value = flag; |
|||
} |
|||
|
|||
function getShowPagination() { |
|||
return unref(show); |
|||
} |
|||
|
|||
const getPagination = () => unref(getPagerConfig); |
|||
|
|||
const setPagination = (info: Partial<VxeGridPropTypes.PagerConfig>) => { |
|||
const paginationInfo = unref(getPagerConfig); |
|||
configRef.value = { |
|||
...(!isBoolean(paginationInfo) ? paginationInfo : {}), |
|||
...info, |
|||
}; |
|||
}; |
|||
|
|||
return { |
|||
getPagerSlots, |
|||
setShowPagination, |
|||
getShowPagination, |
|||
getPagination, |
|||
setPagination, |
|||
getPagerConfig, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,205 @@ |
|||
import type { ComputedRef, Slot, Slots } from 'vue'; |
|||
import type { SmartSearchFormProps, SmartSearchFormSchema } from '@/components/SmartTable'; |
|||
import type { FormProps } from '@/components/Form'; |
|||
import type { FetchParams, SmartTableProps } from '../types/SmartTableType'; |
|||
import type { SmartSearchFormParameter } from '../types/SmartSearchFormType'; |
|||
import type { VxeGridPropTypes } from 'vxe-table'; |
|||
|
|||
import { computed, ref, unref } from 'vue'; |
|||
import { useForm } from '@/components/Form'; |
|||
import { isArray, isBoolean } from '@/utils/is'; |
|||
import { getFormSize } from '../utils'; |
|||
|
|||
export const useTableSearchForm = ( |
|||
propsRef: ComputedRef<SmartTableProps>, |
|||
slots: Slots, |
|||
fetch: (opt?: FetchParams | undefined) => Promise<void>, |
|||
getLoading: ComputedRef<boolean | undefined>, |
|||
setPagination: (info: Partial<VxeGridPropTypes.PagerConfig>) => void, |
|||
) => { |
|||
/** |
|||
* 搜索form显示状态 |
|||
*/ |
|||
const searchFormVisibleRef = ref(unref(propsRef)?.searchFormConfig?.defaultVisible !== false); |
|||
const setSearchFormVisible = (visible?: boolean) => { |
|||
if (isBoolean(visible)) { |
|||
searchFormVisibleRef.value = visible; |
|||
} else { |
|||
searchFormVisibleRef.value = !unref(searchFormVisibleRef); |
|||
} |
|||
}; |
|||
const getSearchFormVisible = computed(() => unref(searchFormVisibleRef)); |
|||
|
|||
const [registerSearchForm, searchFormAction] = useForm(); |
|||
|
|||
/** |
|||
* searchForm props计算属性 |
|||
*/ |
|||
const getSearchFormProps = computed((): Partial<FormProps> => { |
|||
const { searchFormConfig, size } = unref(propsRef); |
|||
const { submitButtonOptions, resetButtonOptions } = searchFormConfig || {}; |
|||
const props: FormProps = { |
|||
...searchFormConfig, |
|||
submitButtonOptions: { |
|||
loading: unref(getLoading), |
|||
...{ preIcon: 'ant-design:search-outlined' }, |
|||
...submitButtonOptions, |
|||
}, |
|||
resetButtonOptions: { |
|||
...{ preIcon: 'ic:baseline-restart-alt' }, |
|||
...resetButtonOptions, |
|||
}, |
|||
}; |
|||
if (size) { |
|||
props.size = searchFormConfig?.size || getFormSize(size); |
|||
} |
|||
return props; |
|||
}); |
|||
|
|||
/** |
|||
* 搜索条件触发 |
|||
*/ |
|||
const handleSearchInfoChange = () => { |
|||
setPagination({ |
|||
currentPage: 1, |
|||
}); |
|||
fetch({ |
|||
page: { |
|||
currentPage: 1, |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
function replaceFormSlotKey(key: string) { |
|||
if (!key) return ''; |
|||
return key?.replace?.(/searchForm-/, '') ?? ''; |
|||
} |
|||
|
|||
/** |
|||
* form slot key计算属性 |
|||
*/ |
|||
const getSearchFormSlot: ComputedRef<Slots> = computed(() => { |
|||
const result: { [name: string]: Slot | undefined } = {}; |
|||
Object.keys(slots) |
|||
.map((item) => (item.startsWith('searchForm-') ? item : null)) |
|||
.filter((item) => !!item) |
|||
.forEach((item) => { |
|||
const formKey = replaceFormSlotKey(item as string); |
|||
result[formKey] = slots[item as string]; |
|||
}); |
|||
return result; |
|||
}); |
|||
|
|||
const getSearchFormColumnSlot = computed<Slots>(() => { |
|||
const { searchFormConfig } = unref(propsRef); |
|||
const slotNames = |
|||
(searchFormConfig?.schemas |
|||
?.map((item: any) => { |
|||
const { slot } = item; |
|||
return slot; |
|||
}) |
|||
.filter((item) => !!item) as string[]) || []; |
|||
const result: { [name: string]: Slot | undefined } = {}; |
|||
slotNames.forEach((item) => { |
|||
result[item] = slots[item]; |
|||
}); |
|||
return result; |
|||
}); |
|||
|
|||
/** |
|||
* 获取搜索表单参数 |
|||
*/ |
|||
const getSearchFormParameter = (): SmartSearchFormParameter => { |
|||
if (!unref(propsRef).useSearchForm) { |
|||
return {}; |
|||
} |
|||
const searchForm = searchFormAction.getFieldsValue(); |
|||
const searchWithSymbol = unref(propsRef).searchFormConfig?.searchWithSymbol; |
|||
const result: SmartSearchFormParameter = { |
|||
searchWithSymbol: isBoolean(searchWithSymbol) && searchWithSymbol, |
|||
}; |
|||
if (result.searchWithSymbol) { |
|||
// 处理搜索符号
|
|||
const { symbolForm, noSymbolForm } = dealSearchSymbol(searchForm); |
|||
result.searchSymbolForm = symbolForm; |
|||
result.noSymbolForm = noSymbolForm; |
|||
} |
|||
result.searchForm = searchForm; |
|||
return result; |
|||
}; |
|||
|
|||
/** |
|||
* 获取搜索符号 |
|||
*/ |
|||
const getSearchFormSymbolRef = computed<{ [index: string]: SmartSearchFormSchema } | boolean>( |
|||
() => { |
|||
const { searchFormConfig, useSearchForm } = unref(propsRef); |
|||
const searchWithSymbol = searchFormConfig?.searchWithSymbol; |
|||
if (!useSearchForm || !searchWithSymbol) { |
|||
return false; |
|||
} |
|||
const { schemas } = searchFormConfig as Partial<SmartSearchFormProps>; |
|||
const result: { [index: string]: SmartSearchFormSchema } = {}; |
|||
schemas?.forEach((item) => { |
|||
result[item.field] = item; |
|||
}); |
|||
return result; |
|||
}, |
|||
); |
|||
|
|||
const dealSearchSymbol = (info: Recordable) => { |
|||
const symbolForm: Recordable = {}; |
|||
const noSymbolForm: Recordable = {}; |
|||
const getSearchFormSymbol = unref(getSearchFormSymbolRef); |
|||
if (isBoolean(getSearchFormSymbol)) { |
|||
return info; |
|||
} |
|||
Object.keys(info).forEach((key) => { |
|||
const value = info[key]; |
|||
const schema = getSearchFormSymbol[key]; |
|||
const { searchSymbol: symbol, customSymbol } = schema; |
|||
if (customSymbol) { |
|||
// 自定义符号
|
|||
const customSymbolResult = customSymbol({ |
|||
schema, |
|||
value, |
|||
model: info, |
|||
}); |
|||
if (customSymbolResult) { |
|||
Object.assign(symbolForm, customSymbolResult); |
|||
} |
|||
} else if (schema.searchSymbol) { |
|||
if (symbol === 'between') { |
|||
// between特殊处理
|
|||
if (value && isArray(value) && value.length === 2) { |
|||
symbolForm[`${key}@>=`] = value[0]; |
|||
symbolForm[`${key}@<=`] = value[1]; |
|||
} |
|||
} else { |
|||
symbolForm[`${key}@${symbol}`] = value; |
|||
} |
|||
} else { |
|||
noSymbolForm[key] = value; |
|||
} |
|||
}); |
|||
return { |
|||
symbolForm, |
|||
noSymbolForm, |
|||
}; |
|||
}; |
|||
|
|||
return { |
|||
getSearchFormProps, |
|||
handleSearchInfoChange, |
|||
getSearchFormSlot, |
|||
getSearchFormColumnSlot, |
|||
registerSearchForm, |
|||
getSearchFormVisible, |
|||
searchFormAction: { |
|||
...searchFormAction, |
|||
getSearchFormParameter, |
|||
getSearchFormVisible, |
|||
setSearchFormVisible, |
|||
}, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,358 @@ |
|||
import type { ComputedRef } from 'vue'; |
|||
import type { ButtonProps } from '@/components/Button'; |
|||
|
|||
import type { |
|||
SmartTableProps, |
|||
SmartTableToolbarConfig, |
|||
FetchParams, |
|||
} from '../types/SmartTableType'; |
|||
import type { SmartTableButton, SmartTableToolbarTool } from '../types/SmartTableButton'; |
|||
import type { SizeType } from 'ant-design-vue/es/config-provider'; |
|||
import type { VxeToolbarPropTypes } from 'vxe-table'; |
|||
|
|||
import { computed, ref, unref } from 'vue'; |
|||
import { error, warn } from '@/utils/log'; |
|||
|
|||
import { merge } from 'lodash-es'; |
|||
import { isArray, isBoolean, isPromise } from '@/utils/is'; |
|||
|
|||
import SmartTableColumnConfig from '../components/SmartTableColumnConfig'; |
|||
|
|||
import { |
|||
VxeTableToolButtonCustomRenderer, |
|||
VxeTableToolAntRenderer, |
|||
VxeTableToolButtonSlotRenderer, |
|||
VxeTableToolComponentRenderer, |
|||
} from '../renderer/VxeTableButtonRenderer'; |
|||
import { SmartTableCode } from '../const'; |
|||
|
|||
const tableButtonSizeMap: { [key: string]: SizeType } = { |
|||
medium: 'middle', |
|||
small: 'small', |
|||
mini: 'small', |
|||
tiny: 'small', |
|||
}; |
|||
|
|||
interface TableAction { |
|||
deleteByCheckbox: () => Promise<boolean | undefined>; |
|||
showAddModal: () => void; |
|||
editByCheckbox: () => Promise<boolean> | boolean; |
|||
query: (opt?: FetchParams) => Promise<any>; |
|||
getSearchFormVisible: ComputedRef<boolean>; |
|||
useYnByCheckbox: (useYn: boolean) => Promise<boolean | undefined>; |
|||
setColumnSortConfig: () => void; |
|||
} |
|||
|
|||
export const useTableToolbarConfig = ( |
|||
tableProps: ComputedRef<SmartTableProps>, |
|||
t: Function, |
|||
{ |
|||
deleteByCheckbox, |
|||
showAddModal, |
|||
editByCheckbox, |
|||
query, |
|||
getSearchFormVisible, |
|||
useYnByCheckbox, |
|||
setColumnSortConfig, |
|||
}: TableAction, |
|||
) => { |
|||
// const configRef = ref<SmartTableToolbarConfig>({})
|
|||
|
|||
const getToolbarConfigInfo = computed<SmartTableToolbarConfig | undefined>(() => { |
|||
const { toolbarConfig, size: tableSize } = unref(tableProps); |
|||
if (!toolbarConfig) { |
|||
return undefined; |
|||
} |
|||
let buttons = dealButtons(toolbarConfig.buttons, tableSize); |
|||
buttons = dealButtonAuth(buttons); |
|||
let refresh = toolbarConfig.refresh; |
|||
if (refresh) { |
|||
refresh = getDefaultRefresh(); |
|||
} |
|||
return { |
|||
...toolbarConfig, |
|||
buttons, |
|||
refresh, |
|||
tools: getTools(toolbarConfig), |
|||
}; |
|||
}); |
|||
|
|||
const dealButtonAuth = ( |
|||
buttons: SmartTableButton[] | undefined, |
|||
): SmartTableButton[] | undefined => { |
|||
if (!buttons) { |
|||
return undefined; |
|||
} |
|||
const authConfig = unref(tableProps).authConfig; |
|||
if (!authConfig) { |
|||
// 未配置权限
|
|||
return buttons; |
|||
} |
|||
const { toolbar, authHandler, displayMode } = authConfig; |
|||
if (!authHandler) { |
|||
error('未设置authConfig.authHandler'); |
|||
} |
|||
return buttons |
|||
.map((button) => { |
|||
const { auth, code } = button; |
|||
const configAuth = code && toolbar ? toolbar[code] : undefined; |
|||
if (auth && configAuth) { |
|||
warn('toolbarConfig与AuthConfig权限配置冲突,toolbarConfig配置'); |
|||
} |
|||
const buttonAuth = auth || configAuth; |
|||
if (!buttonAuth) { |
|||
return button; |
|||
} |
|||
const hasAuth = authHandler!(buttonAuth); |
|||
if (hasAuth) { |
|||
return button; |
|||
} |
|||
if (displayMode === 'hide') { |
|||
return null; |
|||
} else { |
|||
return { |
|||
...button, |
|||
props: { |
|||
...unref(button.props), |
|||
disabled: true, |
|||
}, |
|||
}; |
|||
} |
|||
}) |
|||
.filter((item) => item !== null) as SmartTableButton[]; |
|||
}; |
|||
|
|||
const getTools = (toolbarConfig: SmartTableToolbarConfig) => { |
|||
const { tools, showSearch, column } = toolbarConfig; |
|||
if (!tools && !showSearch && !column) { |
|||
return undefined; |
|||
} |
|||
const result: SmartTableToolbarTool[] = [...(tools || [])]; |
|||
if (showSearch && unref(tableProps).useSearchForm) { |
|||
if (isBoolean(showSearch)) { |
|||
const props = computed(() => { |
|||
return { |
|||
circle: true, |
|||
icon: 'vxe-icon-search', |
|||
title: unref(getSearchFormVisible) |
|||
? t('component.table.hideSearch') |
|||
: t('component.table.showSearch'), |
|||
}; |
|||
}); |
|||
result.push({ |
|||
code: SmartTableCode.showSearch, |
|||
toolRender: { |
|||
name: VxeTableToolAntRenderer, |
|||
}, |
|||
props, |
|||
}); |
|||
} else { |
|||
result.push(showSearch); |
|||
} |
|||
} |
|||
// 处理列配置
|
|||
if (column) { |
|||
const columnConfig = isBoolean(column) ? undefined : column; |
|||
result.push({ |
|||
code: SmartTableCode.column, |
|||
toolRender: { |
|||
name: VxeTableToolComponentRenderer, |
|||
props: { |
|||
config: columnConfig, |
|||
setColumnSortConfig, |
|||
}, |
|||
}, |
|||
component: SmartTableColumnConfig, |
|||
}); |
|||
} |
|||
return result; |
|||
}; |
|||
|
|||
const getDefaultRefresh = (): VxeToolbarPropTypes.RefreshConfig => { |
|||
return { |
|||
queryMethod: (params) => { |
|||
return query(params); |
|||
}, |
|||
}; |
|||
}; |
|||
|
|||
const dealButtons = ( |
|||
buttons: SmartTableButton[] | undefined, |
|||
tableSize, |
|||
): SmartTableButton[] | undefined => { |
|||
if (!buttons) { |
|||
return undefined; |
|||
} |
|||
return buttons.map((item) => { |
|||
if (item.code === 'ModalAdd') { |
|||
// 添加按钮处理
|
|||
const defaultConfig = getDefaultAddButtonConfig(t); |
|||
return merge( |
|||
{ size: tableButtonSizeMap[tableSize] }, |
|||
defaultConfig, |
|||
{ |
|||
props: { |
|||
onClick: () => { |
|||
showAddModal(); |
|||
}, |
|||
}, |
|||
}, |
|||
item, |
|||
) as SmartTableButton; |
|||
} else if (item.code == 'ModalEdit') { |
|||
return merge( |
|||
{ size: tableButtonSizeMap[tableSize] }, |
|||
getDefaultEditButtonConfig(t), |
|||
{ |
|||
props: { |
|||
onClick: () => { |
|||
editByCheckbox(); |
|||
}, |
|||
}, |
|||
}, |
|||
item, |
|||
) as SmartTableButton; |
|||
} else if (item.code === 'delete') { |
|||
return merge( |
|||
{ size: tableButtonSizeMap[tableSize] }, |
|||
getDefaultDeleteButtonConfig(t), |
|||
{ |
|||
props: { |
|||
onClick: () => { |
|||
deleteByCheckbox && deleteByCheckbox(); |
|||
}, |
|||
}, |
|||
}, |
|||
item, |
|||
) as SmartTableButton; |
|||
} else if (item.code === 'useYnFalse' || item.code === 'useYnTrue') { |
|||
const useYn = item.code === 'useYnTrue'; |
|||
return merge( |
|||
{ size: tableButtonSizeMap[tableSize] }, |
|||
getDefaultUseYnButtonConfig(t, useYn), |
|||
{ |
|||
props: { |
|||
onClick: () => { |
|||
useYnByCheckbox && useYnByCheckbox(useYn); |
|||
}, |
|||
}, |
|||
}, |
|||
item, |
|||
) as SmartTableButton; |
|||
} |
|||
// props添加响应性
|
|||
const loading = ref(false); |
|||
const props = computed<ButtonProps>(() => { |
|||
const buttonProps = unref(item.props) as ButtonProps | undefined; |
|||
const result: ButtonProps = { |
|||
...buttonProps, |
|||
}; |
|||
// 点击事件加载状态添加操作
|
|||
if (item.clickLoading && buttonProps?.loading === undefined) { |
|||
result.loading = unref(loading); |
|||
const defaultClickHandler = buttonProps?.onClick; |
|||
if (defaultClickHandler) { |
|||
const handler = isArray(defaultClickHandler) |
|||
? defaultClickHandler[0] |
|||
: defaultClickHandler; |
|||
result.onClick = async () => { |
|||
try { |
|||
loading.value = true; |
|||
// @ts-ignore
|
|||
const handlerResult = handler(); |
|||
if (isPromise(handlerResult)) { |
|||
await handlerResult; |
|||
} |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
return result; |
|||
}); |
|||
if (item.slot) { |
|||
return { |
|||
size: tableButtonSizeMap[tableSize], |
|||
buttonRender: { |
|||
name: VxeTableToolButtonSlotRenderer, |
|||
}, |
|||
...item, |
|||
props, |
|||
}; |
|||
} |
|||
// 如果是ant 按钮使用VxeTableToolButtonRenderer进行渲染
|
|||
if (item.customRender) { |
|||
return { |
|||
size: tableButtonSizeMap[tableSize], |
|||
buttonRender: { |
|||
name: VxeTableToolButtonCustomRenderer, |
|||
}, |
|||
...item, |
|||
props, |
|||
}; |
|||
} |
|||
return item; |
|||
}); |
|||
}; |
|||
|
|||
return { |
|||
getToolbarConfigInfo, |
|||
}; |
|||
}; |
|||
|
|||
const getDefaultAddButtonConfig = (t: Function): SmartTableButton => { |
|||
return { |
|||
name: t('common.button.add'), |
|||
code: 'ModalAdd', |
|||
props: { |
|||
preIcon: 'ant-design:plus-outlined', |
|||
type: 'primary', |
|||
}, |
|||
buttonRender: { |
|||
name: VxeTableToolButtonCustomRenderer, |
|||
}, |
|||
}; |
|||
}; |
|||
|
|||
const getDefaultEditButtonConfig = (t: Function): SmartTableButton => { |
|||
return { |
|||
name: t('common.button.edit'), |
|||
code: 'ModalEdit', |
|||
props: { |
|||
color: 'warning', |
|||
preIcon: 'ant-design:edit-outlined', |
|||
}, |
|||
buttonRender: { |
|||
name: VxeTableToolButtonCustomRenderer, |
|||
}, |
|||
}; |
|||
}; |
|||
|
|||
const getDefaultDeleteButtonConfig = (t: Function): SmartTableButton => { |
|||
return { |
|||
name: t('common.button.delete'), |
|||
code: 'delete', |
|||
props: { |
|||
color: 'error', |
|||
preIcon: 'ant-design:edit-outlined', |
|||
}, |
|||
buttonRender: { |
|||
name: VxeTableToolButtonCustomRenderer, |
|||
}, |
|||
}; |
|||
}; |
|||
|
|||
const getDefaultUseYnButtonConfig = (t: Function, useYn: boolean): SmartTableButton => { |
|||
return { |
|||
name: useYn ? t('common.button.use') : t('common.button.noUse'), |
|||
code: useYn ? 'useYnTrue' : 'useYnFalse', |
|||
props: { |
|||
color: useYn ? '' : 'error', |
|||
preIcon: useYn ? 'ant-design:check-outlined' : 'ant-design:close-outlined', |
|||
}, |
|||
buttonRender: { |
|||
name: VxeTableToolButtonCustomRenderer, |
|||
}, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,35 @@ |
|||
import type { VxeGridListeners } from 'vxe-table'; |
|||
import { computed } from 'vue'; |
|||
|
|||
import { SmartTableCode } from '../const'; |
|||
|
|||
interface Action { |
|||
setSearchFormVisible: (visible?: boolean) => void; |
|||
} |
|||
|
|||
export const useToolbarEvent = (emit: Function, { setSearchFormVisible }: Action) => { |
|||
const events: VxeGridListeners = { |
|||
toolbarToolClick: (params) => { |
|||
emit('toolbar-tool-click', params); |
|||
const { code } = params; |
|||
switch (code) { |
|||
case SmartTableCode.showSearch: { |
|||
setSearchFormVisible(); |
|||
break; |
|||
} |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
const getToolbarEvents = computed(() => { |
|||
const result: any = {}; |
|||
Object.keys(events).forEach((item) => { |
|||
result[`on${item.slice(0, 1).toUpperCase() + item.slice(1)}`] = events[item]; |
|||
}); |
|||
return result; |
|||
}); |
|||
|
|||
return { |
|||
getToolbarEvents, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,22 @@ |
|||
import type { SmartTableProps, TableActionType } from '../types/SmartTableType'; |
|||
import type { ComputedRef, Ref } from 'vue'; |
|||
import { inject, provide } from 'vue'; |
|||
|
|||
const key = Symbol('smart-table'); |
|||
|
|||
type Instance = TableActionType & { |
|||
wrapRef: Ref<Nullable<HTMLElement>>; |
|||
getBindValues: ComputedRef<Recordable>; |
|||
}; |
|||
|
|||
type RetInstance = Omit<Instance, 'getBindValues'> & { |
|||
getBindValues: ComputedRef<SmartTableProps>; |
|||
}; |
|||
|
|||
export function createTableContext(instance: Instance) { |
|||
provide(key, instance); |
|||
} |
|||
|
|||
export function useTableContext(): RetInstance { |
|||
return inject(key) as RetInstance; |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
import type { |
|||
SmartTableProps, |
|||
SmartTableProxyConfig, |
|||
TableHeightType, |
|||
SmartTableToolbarConfig, |
|||
SmartCheckboxConfig, |
|||
} from './types/SmartTableType'; |
|||
import type { SmartColumn } from './types/SmartTableColumnType'; |
|||
import type { VxeGridPropTypes } from 'vxe-table'; |
|||
|
|||
import { ComponentPropsOptions } from 'vue'; |
|||
import { propTypes } from '@/utils/propTypes'; |
|||
import { SmartTableAddEditConfig } from '@/components/SmartTable'; |
|||
import { buildUUID } from '@/utils/uuid'; |
|||
import { defaultCheckboxConfig } from './defaultConfig'; |
|||
|
|||
export const smartTableProps: ComponentPropsOptions<SmartTableProps> = { |
|||
columns: { |
|||
type: [Array] as PropType<SmartColumn[]>, |
|||
default: () => [], |
|||
}, |
|||
height: { |
|||
type: [String, Number] as PropType<TableHeightType>, |
|||
}, |
|||
loading: propTypes.bool, |
|||
// 使用搜索表单
|
|||
useSearchForm: propTypes.bool, |
|||
toolbarConfig: Object as PropType<SmartTableToolbarConfig>, |
|||
pagerConfig: [Object, Boolean] as PropType<boolean | VxeGridPropTypes.PagerConfig>, |
|||
proxyConfig: Object as PropType<SmartTableProxyConfig>, |
|||
addEditConfig: Object as PropType<SmartTableAddEditConfig>, |
|||
|
|||
size: { |
|||
type: String as PropType<VxeGridPropTypes.Size>, |
|||
default: 'small', |
|||
}, |
|||
id: propTypes.string.def(buildUUID()), |
|||
checkboxConfig: { |
|||
type: Object as PropType<SmartCheckboxConfig>, |
|||
default: () => { |
|||
return defaultCheckboxConfig; |
|||
}, |
|||
}, |
|||
}; |
|||
@ -0,0 +1,75 @@ |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-time-picker .ant-time-picker-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-time-picker .ant-time-picker-input, .vxe-table.cell--highlight .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input, .vxe-table.cell--highlight .vxe-cell > .ant-cascader-picker .ant-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-cascader-picker .ant-input, .vxe-table.cell--highlight .vxe-cell > .ant-select .ant-select-selection, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-select .ant-select-selection, .vxe-table.cell--highlight .vxe-cell > .ant-input-number .ant-input-number-handler-wrap, |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-input-number .ant-input-number-handler-down, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input-number .ant-input-number-handler-wrap, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input-number .ant-input-number-handler-down, .vxe-table.cell--highlight .vxe-cell > .ant-select .ant-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-select .ant-input, .vxe-table.cell--highlight .vxe-cell > .ant-input, .vxe-table.cell--highlight .vxe-cell > .ant-input-number, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input-number { |
|||
border: 0; |
|||
box-shadow: none; |
|||
} |
|||
|
|||
.vxe-cell > .ant-input, |
|||
.vxe-tree-cell > .ant-input, .vxe-form .vxe-form--item-content > .ant-input, .vxe-cell > .ant-input-number, |
|||
.vxe-tree-cell > .ant-input-number, .vxe-form .vxe-form--item-content > .ant-input-number, .vxe-cell > .ant-select, |
|||
.vxe-tree-cell > .ant-select, .vxe-form .vxe-form--item-content > .ant-select, .vxe-cell > .ant-cascader-picker, |
|||
.vxe-tree-cell > .ant-cascader-picker, .vxe-form .vxe-form--item-content > .ant-cascader-picker, .vxe-cell > .ant-calendar-picker, |
|||
.vxe-tree-cell > .ant-calendar-picker, .vxe-form .vxe-form--item-content > .ant-calendar-picker, .vxe-cell > .ant-time-picker, |
|||
.vxe-tree-cell > .ant-time-picker, .vxe-form .vxe-form--item-content > .ant-time-picker { |
|||
width: 100%; |
|||
} |
|||
|
|||
.vxe-table--filter-antd-wrapper { |
|||
padding: 0.8em 1em; |
|||
} |
|||
.vxe-table--filter-antd-wrapper > .ant-input, .vxe-table--filter-antd-wrapper > .ant-input-number, .vxe-table--filter-antd-wrapper > .ant-select, .vxe-table--filter-antd-wrapper > .ant-rate { |
|||
width: 180px; |
|||
} |
|||
|
|||
.vxe-cell > .ant-rate, |
|||
.vxe-tree-cell > .ant-rate { |
|||
vertical-align: bottom; |
|||
} |
|||
.vxe-cell > .ant-rate .anticon-star, |
|||
.vxe-tree-cell > .ant-rate .anticon-star { |
|||
display: block; |
|||
} |
|||
|
|||
.col--valid-error > .vxe-cell > .ant-input, .col--valid-error > .vxe-cell > .ant-select .ant-input, .col--valid-error > .vxe-cell > .ant-select .ant-select-selection, .col--valid-error > .vxe-cell > .ant-input-number, .col--valid-error > .vxe-cell > .ant-cascader-picker .ant-cascader-input, .col--valid-error > .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input, .col--valid-error > .vxe-tree-cell > .ant-input, .col--valid-error > .vxe-tree-cell > .ant-select .ant-input, .col--valid-error > .vxe-tree-cell > .ant-select .ant-select-selection, .col--valid-error > .vxe-tree-cell > .ant-input-number, .col--valid-error > .vxe-tree-cell > .ant-cascader-picker .ant-cascader-input, .col--valid-error > .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input { |
|||
border-color: #f56c6c; |
|||
box-shadow: none; |
|||
} |
|||
|
|||
.vxe-table.cell--highlight .vxe-cell > .ant-input, .vxe-table.cell--highlight .vxe-cell > .ant-input-number, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input-number { |
|||
padding: 0; |
|||
} |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-select .ant-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-select .ant-input { |
|||
padding: 0; |
|||
} |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-input-number .ant-input-number-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-input-number .ant-input-number-input { |
|||
padding: 0; |
|||
} |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-select .ant-select-selection .ant-select-selection__rendered, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-select .ant-select-selection .ant-select-selection__rendered { |
|||
margin: 0; |
|||
} |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-cascader-picker .ant-cascader-picker-label, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-cascader-picker .ant-cascader-picker-label { |
|||
padding: 0; |
|||
} |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input { |
|||
padding: 0; |
|||
} |
|||
.vxe-table.cell--highlight .vxe-cell > .ant-time-picker .ant-time-picker-input, |
|||
.vxe-table.cell--highlight .vxe-tree-cell > .ant-time-picker .ant-time-picker-input { |
|||
padding: 0; |
|||
} |
|||
@ -0,0 +1,998 @@ |
|||
import { h, resolveComponent, ComponentOptions } from 'vue'; |
|||
import XEUtils, { isArray, isString } from 'xe-utils'; |
|||
import './VxeTableAntRenderer.css'; |
|||
import { |
|||
VXETableCore, |
|||
VxeTableDefines, |
|||
VxeColumnPropTypes, |
|||
VxeGlobalRendererHandles, |
|||
VxeGlobalInterceptorHandles, |
|||
FormItemRenderOptions, |
|||
FormItemContentRenderParams, |
|||
} from 'vxe-table'; |
|||
|
|||
function isEmptyValue(cellValue: any) { |
|||
return cellValue === null || cellValue === undefined || cellValue === ''; |
|||
} |
|||
|
|||
function getOnName(type: string) { |
|||
return 'on' + type.substring(0, 1).toLocaleUpperCase() + type.substring(1); |
|||
} |
|||
|
|||
function getModelProp(renderOpts: VxeGlobalRendererHandles.RenderOptions) { |
|||
let prop = 'value'; |
|||
switch (renderOpts.name) { |
|||
case 'ASwitch': |
|||
prop = 'checked'; |
|||
break; |
|||
} |
|||
return prop; |
|||
} |
|||
|
|||
function getModelEvent(renderOpts: VxeGlobalRendererHandles.RenderOptions) { |
|||
let type = 'update:value'; |
|||
switch (renderOpts.name) { |
|||
case 'ASwitch': |
|||
type = 'update:checked'; |
|||
break; |
|||
} |
|||
return type; |
|||
} |
|||
|
|||
function getChangeEvent(_: VxeGlobalRendererHandles.RenderOptions) { |
|||
return 'change'; |
|||
} |
|||
|
|||
function getCellEditFilterProps( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
_: VxeGlobalRendererHandles.RenderEditParams | VxeGlobalRendererHandles.RenderFilterParams, |
|||
value: any, |
|||
defaultProps?: { [prop: string]: any }, |
|||
) { |
|||
return XEUtils.assign({}, defaultProps, renderOpts.props, { [getModelProp(renderOpts)]: value }); |
|||
} |
|||
|
|||
function getItemProps( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
_: FormItemContentRenderParams, |
|||
value: any, |
|||
defaultProps?: { [prop: string]: any }, |
|||
) { |
|||
return XEUtils.assign({}, defaultProps, renderOpts.props, { [getModelProp(renderOpts)]: value }); |
|||
} |
|||
|
|||
function formatText(cellValue: any) { |
|||
return '' + (isEmptyValue(cellValue) ? '' : cellValue); |
|||
} |
|||
|
|||
function getCellLabelVNs( |
|||
renderOpts: VxeColumnPropTypes.EditRender, |
|||
_: VxeGlobalRendererHandles.RenderCellParams, |
|||
cellLabel: any, |
|||
) { |
|||
const { placeholder } = renderOpts; |
|||
return [ |
|||
h( |
|||
'span', |
|||
{ |
|||
class: 'vxe-cell--label', |
|||
}, |
|||
placeholder && isEmptyValue(cellLabel) |
|||
? [ |
|||
h( |
|||
'span', |
|||
{ |
|||
class: 'vxe-cell--placeholder', |
|||
}, |
|||
formatText(placeholder), |
|||
), |
|||
] |
|||
: formatText(cellLabel), |
|||
), |
|||
]; |
|||
} |
|||
|
|||
function getOns( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: VxeGlobalRendererHandles.RenderParams, |
|||
inputFunc?: Function, |
|||
changeFunc?: Function, |
|||
) { |
|||
const { events } = renderOpts; |
|||
const modelEvent = getModelEvent(renderOpts); |
|||
const changeEvent = getChangeEvent(renderOpts); |
|||
const isSameEvent = changeEvent === modelEvent; |
|||
const ons: { [type: string]: Function } = {}; |
|||
XEUtils.objectEach(events, (func: Function, key: string) => { |
|||
ons[getOnName(key)] = function (...args: any[]) { |
|||
func(params, ...args); |
|||
}; |
|||
}); |
|||
if (inputFunc) { |
|||
ons[getOnName(modelEvent)] = function (targetEvnt: any) { |
|||
inputFunc(targetEvnt); |
|||
if (events && events[modelEvent]) { |
|||
events[modelEvent](params, targetEvnt); |
|||
} |
|||
if (isSameEvent && changeFunc) { |
|||
changeFunc(targetEvnt); |
|||
} |
|||
}; |
|||
} |
|||
if (!isSameEvent && changeFunc) { |
|||
ons[getOnName(changeEvent)] = function (...args: any[]) { |
|||
changeFunc(...args); |
|||
if (events && events[changeEvent]) { |
|||
events[changeEvent](params, ...args); |
|||
} |
|||
}; |
|||
} |
|||
return ons; |
|||
} |
|||
|
|||
function getEditOns( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: VxeGlobalRendererHandles.RenderEditParams, |
|||
) { |
|||
const { $table, row, column } = params; |
|||
return getOns( |
|||
renderOpts, |
|||
params, |
|||
(value: any) => { |
|||
// 处理 model 值双向绑定
|
|||
XEUtils.set(row, column.field, value); |
|||
}, |
|||
() => { |
|||
// 处理 change 事件相关逻辑
|
|||
$table.updateStatus(params); |
|||
}, |
|||
); |
|||
} |
|||
|
|||
function getFilterOns( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: VxeGlobalRendererHandles.RenderFilterParams, |
|||
option: VxeTableDefines.FilterOption, |
|||
changeFunc: Function, |
|||
) { |
|||
return getOns( |
|||
renderOpts, |
|||
params, |
|||
(value: any) => { |
|||
// 处理 model 值双向绑定
|
|||
option.data = value; |
|||
}, |
|||
changeFunc, |
|||
); |
|||
} |
|||
|
|||
function getItemOns( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: FormItemContentRenderParams, |
|||
) { |
|||
const { $form, data, field } = params; |
|||
return getOns( |
|||
renderOpts, |
|||
params, |
|||
(value: any) => { |
|||
// 处理 model 值双向绑定
|
|||
XEUtils.set(data, field, value); |
|||
}, |
|||
() => { |
|||
// 处理 change 事件相关逻辑
|
|||
$form.updateStatus(params); |
|||
}, |
|||
); |
|||
} |
|||
|
|||
function matchCascaderData(index: number, list: any[], values: any[], labels: any[]) { |
|||
const val = values[index]; |
|||
if (list && values.length > index) { |
|||
XEUtils.each(list, (item) => { |
|||
if (item.value === val) { |
|||
labels.push(item.label); |
|||
matchCascaderData(++index, item.children, values, labels); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
function formatDatePicker(defaultFormat: string) { |
|||
return function ( |
|||
renderOpts: VxeColumnPropTypes.EditRender, |
|||
params: VxeGlobalRendererHandles.RenderCellParams, |
|||
) { |
|||
return getCellLabelVNs( |
|||
renderOpts, |
|||
params, |
|||
getDatePickerCellValue(renderOpts, params, defaultFormat), |
|||
); |
|||
}; |
|||
} |
|||
|
|||
function getSelectCellValue( |
|||
renderOpts: VxeColumnPropTypes.EditRender, |
|||
params: VxeGlobalRendererHandles.RenderCellParams, |
|||
) { |
|||
const { |
|||
options = [], |
|||
optionGroups, |
|||
props = {}, |
|||
optionProps = {}, |
|||
optionGroupProps = {}, |
|||
} = renderOpts; |
|||
const { row, column } = params; |
|||
const labelProp = optionProps.label || 'label'; |
|||
const valueProp = optionProps.value || 'value'; |
|||
const groupOptions = optionGroupProps.options || 'options'; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
if (!isEmptyValue(cellValue)) { |
|||
return XEUtils.map( |
|||
props.mode === 'multiple' ? cellValue : [cellValue], |
|||
optionGroups |
|||
? (value) => { |
|||
let selectItem; |
|||
for (let index = 0; index < optionGroups.length; index++) { |
|||
selectItem = XEUtils.find( |
|||
optionGroups[index][groupOptions], |
|||
(item) => item[valueProp] === value, |
|||
); |
|||
if (selectItem) { |
|||
break; |
|||
} |
|||
} |
|||
return selectItem ? selectItem[labelProp] : value; |
|||
} |
|||
: (value) => { |
|||
const selectItem = XEUtils.find(options, (item) => item[valueProp] === value); |
|||
return selectItem ? selectItem[labelProp] : value; |
|||
}, |
|||
).join(', '); |
|||
} |
|||
return ''; |
|||
} |
|||
|
|||
function getCascaderCellValue( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.ExportMethodParams, |
|||
) { |
|||
const { props = {} } = renderOpts; |
|||
const { row, column } = params; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
const values = cellValue || []; |
|||
const labels: Array<any> = []; |
|||
matchCascaderData(0, props.options, values, labels); |
|||
return ( |
|||
props.showAllLevels === false ? labels.slice(labels.length - 1, labels.length) : labels |
|||
).join(` ${props.separator || '/'} `); |
|||
} |
|||
|
|||
function getRangePickerCellValue( |
|||
renderOpts: VxeColumnPropTypes.EditRender, |
|||
params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.RenderEditParams, |
|||
) { |
|||
const { props = {} } = renderOpts; |
|||
const { row, column } = params; |
|||
let cellValue = XEUtils.get(row, column.field); |
|||
if (cellValue) { |
|||
cellValue = XEUtils.map(cellValue, (date: any) => |
|||
date.format(props.format || 'YYYY-MM-DD'), |
|||
).join(' ~ '); |
|||
} |
|||
return cellValue; |
|||
} |
|||
|
|||
function getTreeSelectCellValue( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.RenderEditParams, |
|||
) { |
|||
const { props = {} } = renderOpts; |
|||
const { treeData, treeCheckable } = props; |
|||
const { row, column } = params; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
if (!isEmptyValue(cellValue)) { |
|||
return XEUtils.map(treeCheckable ? cellValue : [cellValue], (value) => { |
|||
const matchObj = XEUtils.findTree(treeData, (item: any) => item.value === value, { |
|||
children: 'children', |
|||
}); |
|||
return matchObj ? matchObj.item.title : value; |
|||
}).join(', '); |
|||
} |
|||
return cellValue; |
|||
} |
|||
|
|||
function getDatePickerCellValue( |
|||
renderOpts: VxeGlobalRendererHandles.RenderOptions, |
|||
params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.ExportMethodParams, |
|||
defaultFormat: string, |
|||
) { |
|||
const { props = {} } = renderOpts; |
|||
const { row, column } = params; |
|||
let cellValue = XEUtils.get(row, column.field); |
|||
if (isString(cellValue)) { |
|||
return cellValue; |
|||
} |
|||
if (cellValue) { |
|||
let format = defaultFormat; |
|||
if (props.tableFormat) { |
|||
format = props.tableFormat; |
|||
} else if (props.format) { |
|||
if (isArray(props.format) && props.format.length > 0) { |
|||
format = props.format[0]; |
|||
} else { |
|||
format = props.format as string; |
|||
} |
|||
} |
|||
cellValue = cellValue.format(format); |
|||
} |
|||
return cellValue; |
|||
} |
|||
|
|||
function createEditRender(defaultProps?: { [key: string]: any }) { |
|||
return function ( |
|||
renderOpts: VxeColumnPropTypes.EditRender & { name: string }, |
|||
params: VxeGlobalRendererHandles.RenderEditParams, |
|||
) { |
|||
const { row, column } = params; |
|||
const { name, attrs } = renderOpts; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
return [ |
|||
h(resolveComponent(name), { |
|||
...attrs, |
|||
...getCellEditFilterProps(renderOpts, params, cellValue, defaultProps), |
|||
...getEditOns(renderOpts, params), |
|||
}), |
|||
]; |
|||
}; |
|||
} |
|||
|
|||
function defaultButtonEditRender( |
|||
renderOpts: VxeColumnPropTypes.EditRender, |
|||
params: VxeGlobalRendererHandles.RenderEditParams, |
|||
) { |
|||
const { attrs } = renderOpts; |
|||
return [ |
|||
h( |
|||
resolveComponent('a-button'), |
|||
{ |
|||
...attrs, |
|||
...getCellEditFilterProps(renderOpts, params, null), |
|||
...getOns(renderOpts, params), |
|||
}, |
|||
cellText(renderOpts.content), |
|||
), |
|||
]; |
|||
} |
|||
|
|||
function defaultButtonsEditRender( |
|||
renderOpts: VxeColumnPropTypes.EditRender, |
|||
params: VxeGlobalRendererHandles.RenderEditParams, |
|||
) { |
|||
const { children } = renderOpts; |
|||
if (children) { |
|||
return children.map( |
|||
(childRenderOpts: VxeColumnPropTypes.EditRender) => |
|||
defaultButtonEditRender(childRenderOpts, params)[0], |
|||
); |
|||
} |
|||
return []; |
|||
} |
|||
|
|||
function createFilterRender(defaultProps?: { [key: string]: any }) { |
|||
return function ( |
|||
renderOpts: VxeColumnPropTypes.FilterRender & { name: string }, |
|||
params: VxeGlobalRendererHandles.RenderFilterParams, |
|||
) { |
|||
const { column } = params; |
|||
const { name, attrs } = renderOpts; |
|||
return [ |
|||
h( |
|||
'div', |
|||
{ |
|||
class: 'vxe-table--filter-antd-wrapper', |
|||
}, |
|||
column.filters.map((option, oIndex) => { |
|||
const optionValue = option.data; |
|||
return h(resolveComponent(name), { |
|||
key: oIndex, |
|||
...attrs, |
|||
...getCellEditFilterProps(renderOpts, params, optionValue, defaultProps), |
|||
...getFilterOns(renderOpts, params, option, () => { |
|||
// 处理 change 事件相关逻辑
|
|||
handleConfirmFilter(params, !!option.data, option); |
|||
}), |
|||
}); |
|||
}), |
|||
), |
|||
]; |
|||
}; |
|||
} |
|||
|
|||
function handleConfirmFilter( |
|||
params: VxeGlobalRendererHandles.RenderFilterParams, |
|||
checked: boolean, |
|||
option: VxeTableDefines.FilterOption, |
|||
) { |
|||
const { $panel } = params; |
|||
$panel.changeOption(null, checked, option); |
|||
} |
|||
|
|||
/** |
|||
* 模糊匹配 |
|||
* @param params |
|||
*/ |
|||
function defaultFuzzyFilterMethod(params: VxeGlobalRendererHandles.FilterMethodParams) { |
|||
const { option, row, column } = params; |
|||
const { data } = option; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
return XEUtils.toValueString(cellValue).indexOf(data) > -1; |
|||
} |
|||
|
|||
/** |
|||
* 精确匹配 |
|||
* @param params |
|||
*/ |
|||
function defaultExactFilterMethod(params: VxeGlobalRendererHandles.FilterMethodParams) { |
|||
const { option, row, column } = params; |
|||
const { data } = option; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
/* eslint-disable eqeqeq */ |
|||
return cellValue === data; |
|||
} |
|||
|
|||
function cellText(cellValue: any): string[] { |
|||
return [formatText(cellValue)]; |
|||
} |
|||
|
|||
function renderOptions(options: any[], optionProps: VxeGlobalRendererHandles.RenderOptionProps) { |
|||
const labelProp = optionProps.label || 'label'; |
|||
const valueProp = optionProps.value || 'value'; |
|||
return XEUtils.map(options, (item, oIndex) => { |
|||
return h( |
|||
resolveComponent('a-select-option') as ComponentOptions, |
|||
{ |
|||
key: oIndex, |
|||
value: item[valueProp], |
|||
disabled: item.disabled, |
|||
}, |
|||
{ |
|||
default: () => cellText(item[labelProp]), |
|||
}, |
|||
); |
|||
}); |
|||
} |
|||
|
|||
function createFormItemRender(defaultProps?: { [key: string]: any }) { |
|||
return function ( |
|||
renderOpts: FormItemRenderOptions & { name: string }, |
|||
params: FormItemContentRenderParams, |
|||
) { |
|||
const { data, field } = params; |
|||
const { name } = renderOpts; |
|||
const { attrs } = renderOpts; |
|||
const itemValue = XEUtils.get(data, field); |
|||
return [ |
|||
h(resolveComponent(name), { |
|||
...attrs, |
|||
...getItemProps(renderOpts, params, itemValue, defaultProps), |
|||
...getItemOns(renderOpts, params), |
|||
}), |
|||
]; |
|||
}; |
|||
} |
|||
|
|||
function defaultButtonItemRender( |
|||
renderOpts: FormItemRenderOptions, |
|||
params: FormItemContentRenderParams, |
|||
) { |
|||
const { attrs } = renderOpts; |
|||
const props = getItemProps(renderOpts, params, null); |
|||
return [ |
|||
h( |
|||
resolveComponent('a-button') as ComponentOptions, |
|||
{ |
|||
...attrs, |
|||
...props, |
|||
...getItemOns(renderOpts, params), |
|||
}, |
|||
{ |
|||
default: () => cellText(renderOpts.content || props.content), |
|||
}, |
|||
), |
|||
]; |
|||
} |
|||
|
|||
function defaultButtonsItemRender( |
|||
renderOpts: FormItemRenderOptions, |
|||
params: FormItemContentRenderParams, |
|||
) { |
|||
const { children } = renderOpts; |
|||
if (children) { |
|||
return children.map( |
|||
(childRenderOpts: FormItemRenderOptions) => |
|||
defaultButtonItemRender(childRenderOpts, params)[0], |
|||
); |
|||
} |
|||
return []; |
|||
} |
|||
|
|||
function createDatePickerExportMethod(defaultFormat: string) { |
|||
return function (params: VxeGlobalRendererHandles.ExportMethodParams) { |
|||
const { row, column, options } = params; |
|||
return options && options.original |
|||
? XEUtils.get(row, column.field) |
|||
: getDatePickerCellValue(column.editRender || column.cellRender, params, defaultFormat); |
|||
}; |
|||
} |
|||
|
|||
function createExportMethod(getExportCellValue: Function) { |
|||
return function (params: VxeGlobalRendererHandles.ExportMethodParams) { |
|||
const { row, column, options } = params; |
|||
return options && options.original |
|||
? XEUtils.get(row, column.field) |
|||
: getExportCellValue(column.editRender || column.cellRender, params); |
|||
}; |
|||
} |
|||
|
|||
function createFormItemRadioAndCheckboxRender() { |
|||
return function ( |
|||
renderOpts: FormItemRenderOptions & { name: string }, |
|||
params: FormItemContentRenderParams, |
|||
) { |
|||
const { name, options = [], optionProps = {} } = renderOpts; |
|||
const { data, field } = params; |
|||
const { attrs } = renderOpts; |
|||
const labelProp = optionProps.label || 'label'; |
|||
const valueProp = optionProps.value || 'value'; |
|||
const itemValue = XEUtils.get(data, field); |
|||
return [ |
|||
h( |
|||
resolveComponent(`${name}Group`) as ComponentOptions, |
|||
{ |
|||
...attrs, |
|||
...getItemProps(renderOpts, params, itemValue), |
|||
...getItemOns(renderOpts, params), |
|||
}, |
|||
{ |
|||
default: () => { |
|||
return options.map((option, oIndex) => { |
|||
return h( |
|||
resolveComponent(name) as ComponentOptions, |
|||
{ |
|||
key: oIndex, |
|||
value: option[valueProp], |
|||
disabled: option.disabled, |
|||
}, |
|||
{ |
|||
default: () => cellText(option[labelProp]), |
|||
}, |
|||
); |
|||
}); |
|||
}, |
|||
}, |
|||
), |
|||
]; |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* 检查触发源是否属于目标节点 |
|||
*/ |
|||
function getEventTargetNode(evnt: any, container: HTMLElement, className: string) { |
|||
let targetElem; |
|||
let target = evnt.target; |
|||
while (target && target.nodeType && target !== document) { |
|||
if ( |
|||
className && |
|||
target.className && |
|||
target.className.split && |
|||
target.className.split(' ').indexOf(className) > -1 |
|||
) { |
|||
targetElem = target; |
|||
} else if (target === container) { |
|||
return { flag: className ? !!targetElem : true, container, targetElem: targetElem }; |
|||
} |
|||
target = target.parentNode; |
|||
} |
|||
return { flag: false }; |
|||
} |
|||
|
|||
/** |
|||
* 事件兼容性处理 |
|||
*/ |
|||
function handleClearEvent( |
|||
params: |
|||
| VxeGlobalInterceptorHandles.InterceptorClearFilterParams |
|||
| VxeGlobalInterceptorHandles.InterceptorClearActivedParams |
|||
| VxeGlobalInterceptorHandles.InterceptorClearAreasParams, |
|||
) { |
|||
const { $event } = params; |
|||
const bodyElem = document.body; |
|||
if ( |
|||
// 下拉框
|
|||
getEventTargetNode($event, bodyElem, 'ant-select-dropdown').flag || |
|||
// 级联
|
|||
getEventTargetNode($event, bodyElem, 'ant-cascader-menus').flag || |
|||
// 日期
|
|||
getEventTargetNode($event, bodyElem, 'ant-picker-dropdown').flag || |
|||
getEventTargetNode($event, bodyElem, 'ant-calendar-picker-container').flag || |
|||
// 时间选择
|
|||
getEventTargetNode($event, bodyElem, 'ant-time-picker-panel').flag |
|||
) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 基于 vxe-table 的表格适配插件,用于兼容 ant-design-vue 组件库 |
|||
*/ |
|||
export const VXETablePluginAntd = { |
|||
install(vxetable: VXETableCore) { |
|||
// 检查版本
|
|||
if (!/^(4)\./.test(vxetable.version)) { |
|||
console.error('[vxe-table-plugin-antd] Version vxe-table 4.x is required'); |
|||
} |
|||
|
|||
vxetable.renderer.mixin({ |
|||
AAutoComplete: { |
|||
autofocus: 'input.ant-input', |
|||
renderDefault: createEditRender(), |
|||
renderEdit: createEditRender(), |
|||
renderFilter: createFilterRender(), |
|||
defaultFilterMethod: defaultExactFilterMethod, |
|||
renderItemContent: createFormItemRender(), |
|||
}, |
|||
AInput: { |
|||
autofocus: 'input.ant-input', |
|||
renderDefault: createEditRender(), |
|||
renderEdit: createEditRender(), |
|||
renderFilter: createFilterRender(), |
|||
defaultFilterMethod: defaultFuzzyFilterMethod, |
|||
renderItemContent: createFormItemRender(), |
|||
}, |
|||
AInputNumber: { |
|||
autofocus: 'input.ant-input-number-input', |
|||
renderDefault: createEditRender(), |
|||
renderEdit: createEditRender(), |
|||
renderFilter: createFilterRender(), |
|||
defaultFilterMethod: defaultFuzzyFilterMethod, |
|||
renderItemContent: createFormItemRender(), |
|||
}, |
|||
ASelect: { |
|||
renderEdit(renderOpts, params) { |
|||
const { |
|||
options = [], |
|||
optionGroups, |
|||
optionProps = {}, |
|||
optionGroupProps = {}, |
|||
} = renderOpts; |
|||
const { row, column } = params; |
|||
const { attrs } = renderOpts; |
|||
const cellValue = XEUtils.get(row, column.field); |
|||
const props = getCellEditFilterProps(renderOpts, params, cellValue); |
|||
const ons = getEditOns(renderOpts, params); |
|||
if (optionGroups) { |
|||
const groupOptions = optionGroupProps.options || 'options'; |
|||
const groupLabel = optionGroupProps.label || 'label'; |
|||
return [ |
|||
h( |
|||
resolveComponent('a-select') as ComponentOptions, |
|||
{ |
|||
...props, |
|||
...attrs, |
|||
...ons, |
|||
}, |
|||
{ |
|||
default: () => { |
|||
return XEUtils.map(optionGroups, (group, gIndex) => { |
|||
return h( |
|||
resolveComponent('a-select-opt-group') as ComponentOptions, |
|||
{ |
|||
key: gIndex, |
|||
}, |
|||
{ |
|||
label: () => { |
|||
return h('span', {}, group[groupLabel]); |
|||
}, |
|||
default: () => renderOptions(group[groupOptions], optionProps), |
|||
}, |
|||
); |
|||
}); |
|||
}, |
|||
}, |
|||
), |
|||
]; |
|||
} |
|||
return [ |
|||
h( |
|||
resolveComponent('a-select') as ComponentOptions, |
|||
{ |
|||
...props, |
|||
...attrs, |
|||
...ons, |
|||
}, |
|||
{ |
|||
default: () => renderOptions(options, optionProps), |
|||
}, |
|||
), |
|||
]; |
|||
}, |
|||
renderCell(renderOpts, params) { |
|||
return getCellLabelVNs(renderOpts, params, getSelectCellValue(renderOpts, params)); |
|||
}, |
|||
renderFilter(renderOpts, params) { |
|||
const { |
|||
options = [], |
|||
optionGroups, |
|||
optionProps = {}, |
|||
optionGroupProps = {}, |
|||
} = renderOpts; |
|||
const groupOptions = optionGroupProps.options || 'options'; |
|||
const groupLabel = optionGroupProps.label || 'label'; |
|||
const { column } = params; |
|||
const { attrs } = renderOpts; |
|||
return [ |
|||
h( |
|||
'div', |
|||
{ |
|||
class: 'vxe-table--filter-antd-wrapper', |
|||
}, |
|||
optionGroups |
|||
? column.filters.map((option, oIndex) => { |
|||
const optionValue = option.data; |
|||
const props = getCellEditFilterProps(renderOpts, params, optionValue); |
|||
return h( |
|||
resolveComponent('a-select') as ComponentOptions, |
|||
{ |
|||
key: oIndex, |
|||
...attrs, |
|||
...props, |
|||
...getFilterOns(renderOpts, params, option, () => { |
|||
// 处理 change 事件相关逻辑
|
|||
handleConfirmFilter( |
|||
params, |
|||
props.mode === 'multiple' |
|||
? option.data && option.data.length > 0 |
|||
: !XEUtils.eqNull(option.data), |
|||
option, |
|||
); |
|||
}), |
|||
}, |
|||
{ |
|||
default: () => { |
|||
return XEUtils.map(optionGroups, (group, gIndex) => { |
|||
return h( |
|||
resolveComponent('a-select-opt-group') as ComponentOptions, |
|||
{ |
|||
key: gIndex, |
|||
}, |
|||
{ |
|||
label: () => { |
|||
return h('span', {}, group[groupLabel]); |
|||
}, |
|||
default: () => renderOptions(group[groupOptions], optionProps), |
|||
}, |
|||
); |
|||
}); |
|||
}, |
|||
}, |
|||
); |
|||
}) |
|||
: column.filters.map((option, oIndex) => { |
|||
const optionValue = option.data; |
|||
const props = getCellEditFilterProps(renderOpts, params, optionValue); |
|||
return h( |
|||
resolveComponent('a-select') as ComponentOptions, |
|||
{ |
|||
key: oIndex, |
|||
...attrs, |
|||
...props, |
|||
...getFilterOns(renderOpts, params, option, () => { |
|||
// 处理 change 事件相关逻辑
|
|||
handleConfirmFilter( |
|||
params, |
|||
props.mode === 'multiple' |
|||
? option.data && option.data.length > 0 |
|||
: !XEUtils.eqNull(option.data), |
|||
option, |
|||
); |
|||
}), |
|||
}, |
|||
{ |
|||
default: () => renderOptions(options, optionProps), |
|||
}, |
|||
); |
|||
}), |
|||
), |
|||
]; |
|||
}, |
|||
defaultFilterMethod(params) { |
|||
const { option, row, column } = params; |
|||
const { data } = option; |
|||
const { field, filterRender: renderOpts } = column; |
|||
const { props = {} } = renderOpts; |
|||
const cellValue = XEUtils.get(row, field); |
|||
if (props.mode === 'multiple') { |
|||
if (XEUtils.isArray(cellValue)) { |
|||
return XEUtils.includeArrays(cellValue, data); |
|||
} |
|||
return data.indexOf(cellValue) > -1; |
|||
} |
|||
/* eslint-disable eqeqeq */ |
|||
return cellValue == data; |
|||
}, |
|||
renderItemContent(renderOpts, params) { |
|||
const { |
|||
options = [], |
|||
optionGroups, |
|||
optionProps = {}, |
|||
optionGroupProps = {}, |
|||
} = renderOpts; |
|||
const { data, field } = params; |
|||
const { attrs } = renderOpts; |
|||
const itemValue = XEUtils.get(data, field); |
|||
const props = getItemProps(renderOpts, params, itemValue); |
|||
const ons = getItemOns(renderOpts, params); |
|||
if (optionGroups) { |
|||
const groupOptions = optionGroupProps.options || 'options'; |
|||
const groupLabel = optionGroupProps.label || 'label'; |
|||
return [ |
|||
h( |
|||
resolveComponent('a-select') as ComponentOptions, |
|||
{ |
|||
...attrs, |
|||
...props, |
|||
...ons, |
|||
}, |
|||
{ |
|||
default: () => { |
|||
return XEUtils.map(optionGroups, (group, gIndex) => { |
|||
return h( |
|||
resolveComponent('a-select-opt-group') as ComponentOptions, |
|||
{ |
|||
key: gIndex, |
|||
}, |
|||
{ |
|||
label: () => { |
|||
return h('span', {}, group[groupLabel]); |
|||
}, |
|||
default: () => renderOptions(group[groupOptions], optionProps), |
|||
}, |
|||
); |
|||
}); |
|||
}, |
|||
}, |
|||
), |
|||
]; |
|||
} |
|||
return [ |
|||
h( |
|||
resolveComponent('a-select') as ComponentOptions, |
|||
{ |
|||
...attrs, |
|||
...props, |
|||
...ons, |
|||
}, |
|||
{ |
|||
default: () => renderOptions(options, optionProps), |
|||
}, |
|||
), |
|||
]; |
|||
}, |
|||
exportMethod: createExportMethod(getSelectCellValue), |
|||
}, |
|||
ACascader: { |
|||
renderEdit: createEditRender(), |
|||
renderCell(renderOpts, params) { |
|||
return getCellLabelVNs(renderOpts, params, getCascaderCellValue(renderOpts, params)); |
|||
}, |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createExportMethod(getCascaderCellValue), |
|||
}, |
|||
ADatePicker: { |
|||
renderEdit: createEditRender(), |
|||
renderCell: formatDatePicker('YYYY-MM-DD'), |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createDatePickerExportMethod('YYYY-MM-DD'), |
|||
}, |
|||
AMonthPicker: { |
|||
renderEdit: createEditRender(), |
|||
renderCell: formatDatePicker('YYYY-MM'), |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createDatePickerExportMethod('YYYY-MM'), |
|||
}, |
|||
ARangePicker: { |
|||
renderEdit: createEditRender(), |
|||
renderCell(renderOpts, params) { |
|||
return getCellLabelVNs(renderOpts, params, getRangePickerCellValue(renderOpts, params)); |
|||
}, |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createExportMethod(getRangePickerCellValue), |
|||
}, |
|||
AWeekPicker: { |
|||
renderEdit: createEditRender(), |
|||
renderCell: formatDatePicker('YYYY-WW周'), |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createDatePickerExportMethod('YYYY-WW周'), |
|||
}, |
|||
ATimePicker: { |
|||
renderEdit: createEditRender(), |
|||
renderCell: formatDatePicker('HH:mm:ss'), |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createDatePickerExportMethod('HH:mm:ss'), |
|||
}, |
|||
ATreeSelect: { |
|||
renderEdit: createEditRender(), |
|||
renderCell(renderOpts, params) { |
|||
return getCellLabelVNs(renderOpts, params, getTreeSelectCellValue(renderOpts, params)); |
|||
}, |
|||
renderItemContent: createFormItemRender(), |
|||
exportMethod: createExportMethod(getTreeSelectCellValue), |
|||
}, |
|||
ARate: { |
|||
renderDefault: createEditRender(), |
|||
renderEdit: createEditRender(), |
|||
renderFilter: createFilterRender(), |
|||
defaultFilterMethod: defaultExactFilterMethod, |
|||
renderItemContent: createFormItemRender(), |
|||
}, |
|||
ASwitch: { |
|||
autofocus: 'button.ant-switch', |
|||
renderDefault: createEditRender(), |
|||
renderEdit: createEditRender(), |
|||
renderFilter(renderOpts, params) { |
|||
const { column } = params; |
|||
const { name, attrs } = renderOpts; |
|||
return [ |
|||
h( |
|||
'div', |
|||
{ |
|||
class: 'vxe-table--filter-antd-wrapper', |
|||
}, |
|||
column.filters.map((option, oIndex) => { |
|||
const optionValue = option.data; |
|||
return h(name as string, { |
|||
key: oIndex, |
|||
...attrs, |
|||
...getCellEditFilterProps(renderOpts, params, optionValue), |
|||
...getFilterOns(renderOpts, params, option, () => { |
|||
// 处理 change 事件相关逻辑
|
|||
handleConfirmFilter(params, XEUtils.isBoolean(option.data), option); |
|||
}), |
|||
}); |
|||
}), |
|||
), |
|||
]; |
|||
}, |
|||
defaultFilterMethod: defaultExactFilterMethod, |
|||
renderItemContent: createFormItemRender(), |
|||
}, |
|||
ARadio: { |
|||
renderItemContent: createFormItemRadioAndCheckboxRender(), |
|||
}, |
|||
ACheckbox: { |
|||
renderItemContent: createFormItemRadioAndCheckboxRender(), |
|||
}, |
|||
AButton: { |
|||
renderEdit: defaultButtonEditRender, |
|||
renderDefault: defaultButtonEditRender, |
|||
renderItemContent: defaultButtonItemRender, |
|||
}, |
|||
AButtons: { |
|||
renderEdit: defaultButtonsEditRender, |
|||
renderDefault: defaultButtonsEditRender, |
|||
renderItemContent: defaultButtonsItemRender, |
|||
}, |
|||
}); |
|||
|
|||
vxetable.interceptor.add('event.clearFilter', handleClearEvent); |
|||
vxetable.interceptor.add('event.clearActived', handleClearEvent); |
|||
vxetable.interceptor.add('event.clearAreas', handleClearEvent); |
|||
}, |
|||
}; |
|||
|
|||
if (typeof window !== 'undefined' && window.VXETable && window.VXETable.use) { |
|||
window.VXETable.use(VXETablePluginAntd); |
|||
} |
|||
|
|||
export default VXETablePluginAntd; |
|||
@ -0,0 +1,99 @@ |
|||
import VXETable, { VxeGlobalRendererHandles } from 'vxe-table'; |
|||
import { SmartTableButton } from '@/components/SmartTable'; |
|||
import { omit } from 'lodash-es'; |
|||
import { unref, h } from 'vue'; |
|||
import { isString } from '@/utils/is'; |
|||
import type { ButtonProps } from '@/components/Button'; |
|||
import { hasPermission } from '@/utils/auth'; |
|||
|
|||
export type VxeTableRenderer = |
|||
| 'VxeTableToolButtonCustomRenderer' |
|||
| 'VxeTableToolButtonSlotRenderer' |
|||
| 'VxeTableToolAntRenderer' |
|||
| 'VxeTableToolComponentRenderer'; |
|||
|
|||
export const VxeTableToolButtonCustomRenderer: VxeTableRenderer = |
|||
'VxeTableToolButtonCustomRenderer'; |
|||
|
|||
export const VxeTableToolAntRenderer: VxeTableRenderer = 'VxeTableToolAntRenderer'; |
|||
|
|||
export const VxeTableToolButtonSlotRenderer: VxeTableRenderer = 'VxeTableToolButtonSlotRenderer'; |
|||
|
|||
export const VxeTableToolComponentRenderer: VxeTableRenderer = 'VxeTableToolComponentRenderer'; |
|||
|
|||
VXETable.renderer.add(VxeTableToolButtonCustomRenderer, { |
|||
renderToolbarButton( |
|||
_: VxeGlobalRendererHandles.RenderButtonOptions, |
|||
params: VxeGlobalRendererHandles.RenderButtonParams, |
|||
): VxeGlobalRendererHandles.RenderResult { |
|||
const button = params.button as SmartTableButton; |
|||
let buttonPros = { |
|||
...button, |
|||
...(unref(button.props) || {}), |
|||
}; |
|||
buttonPros = omit(buttonPros, ['props', 'buttonRender']); |
|||
// 权限处理
|
|||
const hasAuth = hasPermission(button?.auth); |
|||
if (!hasAuth) { |
|||
const auth = button.auth; |
|||
if (!isString(auth)) { |
|||
if (auth?.displayMode === 'hide') { |
|||
return ''; |
|||
} |
|||
} |
|||
buttonPros.disabled = true; |
|||
} |
|||
if (button.customRender === 'element') { |
|||
return <el-button {...buttonPros}>{buttonPros.name}</el-button>; |
|||
} else { |
|||
return <a-button {...buttonPros}>{buttonPros.name}</a-button>; |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
VXETable.renderer.add(VxeTableToolAntRenderer, { |
|||
renderToolbarTool( |
|||
_: VxeGlobalRendererHandles.RenderToolOptions, |
|||
params: VxeGlobalRendererHandles.RenderToolParams, |
|||
): VxeGlobalRendererHandles.RenderResult { |
|||
const { tool, $grid } = params; |
|||
const props = unref((tool as any).props); |
|||
const handleClick = (event) => { |
|||
($grid as any)?.triggerToolbarTolEvent(tool, event); |
|||
}; |
|||
return <vxe-button onClick={(event) => handleClick(event)} {...props} />; |
|||
// return (
|
|||
// <a-tooltip title="显示搜索">
|
|||
// <a-button {...props} onClick={(event) => handleClick(event)} />
|
|||
// </a-tooltip>
|
|||
// )
|
|||
}, |
|||
}); |
|||
|
|||
/** |
|||
* vxe-table 插槽渲染器 |
|||
*/ |
|||
VXETable.renderer.add(VxeTableToolButtonSlotRenderer, { |
|||
renderToolbarButton( |
|||
_: VxeGlobalRendererHandles.RenderButtonOptions, |
|||
params: VxeGlobalRendererHandles.RenderButtonParams, |
|||
): VxeGlobalRendererHandles.RenderResult { |
|||
const button = params.button as SmartTableButton & ButtonProps; |
|||
if (!button.slot || isString(button.slot)) { |
|||
return ''; |
|||
} |
|||
return button.slot(button); |
|||
}, |
|||
}); |
|||
|
|||
/** |
|||
* 自定义组件渲染 |
|||
*/ |
|||
VXETable.renderer.add(VxeTableToolComponentRenderer, { |
|||
renderToolbarTool( |
|||
{ props }: VxeGlobalRendererHandles.RenderToolOptions, |
|||
{ tool }: VxeGlobalRendererHandles.RenderToolParams<any>, |
|||
): VxeGlobalRendererHandles.RenderResult { |
|||
return h((tool as any).component, props); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,39 @@ |
|||
.smart-table-column-config-fixed-option { |
|||
.fixed-span { |
|||
padding: 5px; |
|||
cursor: pointer; |
|||
|
|||
&:hover { |
|||
color: var(--vxe-primary-color); |
|||
} |
|||
} |
|||
|
|||
.is--checked { |
|||
color: var(--vxe-primary-color); |
|||
} |
|||
} |
|||
|
|||
.smart-table-column-config { |
|||
.vxe-pulldown--wrapper { |
|||
border: 1px solid var(--vxe-table-border-color); |
|||
border-radius: var(--vxe-border-radius); |
|||
box-shadow: 0 1px 6px rgb(0 0 0 / 20%); |
|||
} |
|||
|
|||
.bottom-button { |
|||
height: 30px; |
|||
line-height: 30px; |
|||
|
|||
.vxe-button { |
|||
width: 50%; |
|||
} |
|||
} |
|||
|
|||
.smart-table-column-config--drag { |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.smart-table-column-config--drag-fixed { |
|||
cursor: no-drop; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
import type { FormActionType, FormProps, FormSchema } from '@/components/Form'; |
|||
|
|||
export type SearchSymbol = |
|||
| '=' |
|||
| 'like' |
|||
| '>' |
|||
| '>=' |
|||
| '<>' |
|||
| '<' |
|||
| '<=' |
|||
| 'in' |
|||
| 'notLike' |
|||
| 'likeLeft' |
|||
| 'likeRight' |
|||
| 'notIn' |
|||
| 'groupBy' |
|||
| 'between'; |
|||
|
|||
export type SmartSearchFormSchema = { |
|||
searchSymbol?: SearchSymbol; |
|||
customSymbol?: ({ |
|||
schema, |
|||
value, |
|||
model, |
|||
}: { |
|||
schema: SmartSearchFormSchema; |
|||
value: any; |
|||
model: Recordable; |
|||
}) => Recordable; |
|||
} & FormSchema; |
|||
|
|||
export interface SmartSearchFormProps extends FormProps { |
|||
schemas?: SmartSearchFormSchema[]; |
|||
|
|||
searchWithSymbol?: boolean; |
|||
defaultVisible?: boolean; |
|||
} |
|||
|
|||
export interface SmartSearchFormParameter { |
|||
searchSymbolForm?: Recordable; |
|||
noSymbolForm?: Recordable; |
|||
searchForm?: Recordable; |
|||
searchWithSymbol?: boolean; |
|||
} |
|||
|
|||
export interface SmartSearchFormActionType extends FormActionType { |
|||
getSearchFormParameter: () => SmartSearchFormParameter; |
|||
/** |
|||
* 设置搜索表单的显示状态 |
|||
* @param visible |
|||
*/ |
|||
setSearchFormVisible: (visible?: boolean) => void; |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
import type { ButtonProps } from '@/components/Button'; |
|||
import type { TooltipProps } from 'ant-design-vue'; |
|||
import type { SmartAuth } from '#/utils'; |
|||
|
|||
export interface ActionItem extends ButtonProps { |
|||
onClick?: Fn; |
|||
label?: string; |
|||
danger?: boolean; |
|||
// color?: 'success' | 'error' | 'warning';
|
|||
icon?: string; |
|||
popConfirm?: PopConfirm; |
|||
disabled?: boolean; |
|||
divider?: boolean; |
|||
// 权限编码控制是否显示
|
|||
auth?: SmartAuth | string; |
|||
// 业务控制是否显示
|
|||
ifShow?: boolean | ((action: ActionItem) => boolean); |
|||
tooltip?: string | TooltipProps; |
|||
// 自定义类名
|
|||
class?: string | Record<string, boolean> | any[]; |
|||
} |
|||
|
|||
export interface PopConfirm { |
|||
title: string; |
|||
okText?: string; |
|||
cancelText?: string; |
|||
confirm: Fn; |
|||
cancel?: Fn; |
|||
icon?: string; |
|||
placement?: string; |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
import type { ModalProps } from '@/components/Modal'; |
|||
import type { FormProps } from '@/components/Form'; |
|||
|
|||
/** |
|||
* 添加编辑表单配置 |
|||
*/ |
|||
export interface SmartTableAddEditConfig<T = any> { |
|||
/** |
|||
* modal配置 |
|||
*/ |
|||
modalConfig?: Partial<ModalProps>; |
|||
/** |
|||
* 表单配置项 |
|||
*/ |
|||
formConfig?: Partial<FormProps>; |
|||
// 编辑加载完数据执行,返回false或Promise<false>停止后续执行
|
|||
afterLoadData?: (data: T) => Promise<boolean | undefined> | boolean | undefined; |
|||
// 保存之前对数据进行处理
|
|||
beforeSave?: (data: T) => T | Promise<T>; |
|||
// 保存之后操作,默认reload
|
|||
afterSave?: (saveResult?) => boolean | Promise<boolean> | void; |
|||
// 自定义弹窗事件
|
|||
openModalHandler?: (row: any, formData?: Recordable) => void; |
|||
} |
|||
|
|||
export interface SmartAddEditModalCallbackData<T = any> { |
|||
getFunction?: (T) => Promise<T>; |
|||
validateFunction?: |
|||
| ((data: T, customData: any) => Promise<boolean | undefined> | boolean | undefined) |
|||
| undefined; |
|||
isAdd: boolean; |
|||
selectData?: Recordable<T>; |
|||
formData?: Recordable; |
|||
afterSave?: () => void; |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
import { SmartAuth, SmartAuthDisplayMode } from '#/utils'; |
|||
|
|||
type SmartTableAuth = string | SmartAuth; |
|||
|
|||
/** |
|||
* smart-table auth配置 |
|||
*/ |
|||
export interface SmartTableAuthConfig { |
|||
toolbar?: SmartTableAuthToolbarConfig; |
|||
/** |
|||
* 判断权限函数 |
|||
* @param auth |
|||
*/ |
|||
authHandler: (auth?: SmartAuth | string) => boolean; |
|||
/** |
|||
* 默认的显示模式 |
|||
*/ |
|||
displayMode?: SmartAuthDisplayMode; |
|||
} |
|||
|
|||
export interface SmartTableAuthToolbarConfig { |
|||
query?: SmartTableAuth; |
|||
insert?: SmartTableAuth; |
|||
delete?: SmartTableAuth; |
|||
remove?: SmartTableAuth; |
|||
import?: SmartTableAuth; |
|||
export?: SmartTableAuth; |
|||
ModalAdd?: SmartTableAuth; |
|||
ModalEdit?: SmartTableAuth; |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
import type { ButtonProps } from '@/components/Button'; |
|||
import type { Component, ComputedRef, Ref, VNode } from 'vue'; |
|||
import type { SmartAuth } from '#/utils'; |
|||
import type { VxeToolbarPropTypes } from 'vxe-table'; |
|||
|
|||
export type SmartTableButtonType = 'text' | 'submit' | 'reset' | 'button'; |
|||
|
|||
export type SmartTableButtonStatus = 'primary' | 'success' | 'info' | 'warning' | 'danger'; |
|||
|
|||
export type SmartTableButtonCode = |
|||
| 'reload' |
|||
| 'query' |
|||
| 'insert' |
|||
| 'insert_actived' |
|||
| 'mark_cancel' |
|||
| 'delete' |
|||
| 'remove' |
|||
| 'save' |
|||
| 'import' |
|||
| 'open_import' |
|||
| 'export' |
|||
| 'open_export' |
|||
| 'reset_custom' |
|||
| 'ModalAdd' |
|||
| 'ModalEdit' |
|||
| 'useYnTrue' |
|||
| 'useYnFalse'; |
|||
|
|||
export type SmartTableToolCode = 'show_search'; |
|||
|
|||
export type SmartTableButtonCustomRender = 'ant' | 'element'; |
|||
|
|||
export interface SmartTableButtonRender { |
|||
name?: string; |
|||
props?: any; |
|||
events?: any; |
|||
} |
|||
|
|||
export interface SmartTableBasicButtonDropdowns { |
|||
name?: string | 'smart-auto'; |
|||
// 按钮类型
|
|||
type?: SmartTableButtonType; |
|||
// 按钮状态
|
|||
status?: SmartTableButtonStatus; |
|||
code?: SmartTableButtonCode; |
|||
visible?: boolean; |
|||
disabled?: boolean | ComputedRef<boolean>; |
|||
icon?: string; |
|||
round?: boolean; |
|||
circle?: boolean; |
|||
auth?: string | SmartAuth; |
|||
} |
|||
|
|||
export interface SmartTableButton extends SmartTableBasicButtonDropdowns { |
|||
placement?: string; |
|||
'destroy-on-close'?: boolean; |
|||
transfer?: boolean; |
|||
dropdowns?: SmartTableBasicButtonDropdowns[]; |
|||
buttonRender?: SmartTableButtonRender; |
|||
props?: ButtonProps | Ref<ButtonProps> | ComputedRef<ButtonProps>; |
|||
// 是否是ant-design按钮,false:使用vxe-table原有的按钮,true使用VxeTableToolButtonRenderer进行渲染
|
|||
customRender?: SmartTableButtonCustomRender; |
|||
// 点击事件是否触发加载状态
|
|||
clickLoading?: boolean; |
|||
// 是否使用插槽
|
|||
slot?: (button: SmartTableButton & ButtonProps) => VNode | string; |
|||
} |
|||
|
|||
export interface SmartTableToolbarTool extends VxeToolbarPropTypes.ToolConfig { |
|||
code?: SmartTableToolCode | string; |
|||
|
|||
props?: Recordable; |
|||
|
|||
component?: Component; |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
import type { VxeTableDefines, VxeColumnSlotTypes, VxeColumnPropTypes } from 'vxe-table'; |
|||
import { VxeTableConstructor, VxeTablePrivateMethods } from 'vxe-table/types/table'; |
|||
|
|||
export type SmartTableColumnComponent = 'switch' | 'tag' | 'button' | 'booleanTag' | 'useYnTag'; |
|||
|
|||
export type SmartEditRenderName = |
|||
| 'AInput' |
|||
| 'AAutocomplete' |
|||
| 'AInputNumber' |
|||
| 'ASelect' |
|||
| 'ACascader' |
|||
| 'ADatePicker' |
|||
| 'AMonthPicker' |
|||
| 'ARangePicker' |
|||
| 'AWeekPicker' |
|||
| 'ATimePicker' |
|||
| 'ATreeSelect' |
|||
| 'ASwitch' |
|||
| 'ARate' |
|||
| 'AButton' |
|||
| 'AButtons' |
|||
| 'input' |
|||
| 'textarea' |
|||
| 'select' |
|||
| '$input' |
|||
| '$select' |
|||
| '$switch'; |
|||
|
|||
// @ts-ignore
|
|||
export interface SmartEditRender extends VxeColumnPropTypes.EditRender { |
|||
autofocus?: boolean | string; |
|||
name?: SmartEditRenderName; |
|||
// 是否阻止回车键事件冒泡,组织后回车不会调到下一行,默认true
|
|||
stopEnterBubbling?: boolean; |
|||
// 是否必填
|
|||
required?: boolean; |
|||
// 校验规则
|
|||
rules?: VxeTableDefines.ValidatorRule[]; |
|||
} |
|||
|
|||
export type SmartColumnDynamicClass = |
|||
| string |
|||
| ((params: { |
|||
$table: VxeTableConstructor & VxeTablePrivateMethods; |
|||
row: any; |
|||
rowIndex: number; |
|||
$rowIndex: number; |
|||
_rowIndex: number; |
|||
column: VxeTableDefines.ColumnInfo; |
|||
columnIndex: number; |
|||
$columnIndex: number; |
|||
_columnIndex: number; |
|||
}) => void | null | string | { [key: string]: boolean }); |
|||
|
|||
export type SmartColumnDynamicStyle = |
|||
| any |
|||
| ((params: { |
|||
$table: VxeTableConstructor & VxeTablePrivateMethods; |
|||
row: any; |
|||
rowIndex: number; |
|||
$rowIndex: number; |
|||
_rowIndex: number; |
|||
column: VxeTableDefines.ColumnInfo; |
|||
columnIndex: number; |
|||
$columnIndex: number; |
|||
_columnIndex: number; |
|||
}) => void | null | string | { [key: string]: boolean }); |
|||
|
|||
export type SmartColumnAutoClass = 'Boolean'; |
|||
|
|||
// @ts-ignore
|
|||
export interface SmartColumn extends VxeTableDefines.ColumnOptions { |
|||
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION'; |
|||
component?: SmartTableColumnComponent; |
|||
componentProps?: Recordable | ((params: VxeColumnSlotTypes.DefaultSlotParams) => Recordable); |
|||
editRender?: SmartEditRender; |
|||
// 动态CLASS
|
|||
dynamicClass?: SmartColumnDynamicClass; |
|||
// 动态style
|
|||
dynamicStyle?: SmartColumnDynamicStyle; |
|||
// 自动class
|
|||
autoClass?: SmartColumnAutoClass; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
import type { VxeButtonProps } from 'vxe-table'; |
|||
|
|||
/** |
|||
* toolbar column config |
|||
*/ |
|||
export interface SmartTableToolbarColumnConfig { |
|||
trigger?: 'manual' | 'click' | 'hover'; |
|||
buttonProps?: VxeButtonProps; |
|||
// 是否支持列排序
|
|||
columnOrder?: boolean; |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
import type { VxeTableDefines, VxeGridProps, VxeGridPropTypes, VxeGridInstance } from 'vxe-table'; |
|||
import type { SmartSearchFormProps } from './SmartSearchFormType'; |
|||
import type { SmartTableButton, SmartTableToolbarTool } from './SmartTableButton'; |
|||
import type { SmartTableAddEditConfig } from './SmartTableAddEditType'; |
|||
import type { NamePath } from 'ant-design-vue/es/form/interface'; |
|||
import type { VxeTablePropTypes } from 'vxe-table/types/table'; |
|||
import type { Options as SortableOptions } from 'sortablejs'; |
|||
import type { SmartColumn } from './SmartTableColumnType'; |
|||
import type { SmartTableAuthConfig } from './SmartTableAuthType'; |
|||
import type { SmartTableToolbarColumnConfig } from '@/components/SmartTable'; |
|||
|
|||
/** |
|||
* 表格高度 |
|||
*/ |
|||
export type TableHeightType = 'auto' | number | string; |
|||
|
|||
export interface FetchParams { |
|||
searchInfo?: Recordable; |
|||
page?: Partial<VxeGridPropTypes.ProxyAjaxQueryPageParams>; |
|||
sorts?: Partial<VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams>[]; |
|||
filters?: Partial<VxeTableDefines.FilterCheckedParams>[]; |
|||
} |
|||
|
|||
// @ts-ignore
|
|||
export interface SmartTableToolbarConfig extends VxeGridPropTypes.ToolbarConfig { |
|||
buttons?: SmartTableButton[]; |
|||
tools?: SmartTableToolbarTool[]; |
|||
// 是否显示搜索
|
|||
showSearch?: boolean | SmartTableToolbarTool; |
|||
column?: SmartTableToolbarColumnConfig | boolean; |
|||
} |
|||
|
|||
export interface SmartTableAjaxQueryParams extends VxeGridPropTypes.ProxyAjaxQueryParams { |
|||
searchInfo?: Recordable; |
|||
searchFormSymbol?: Recordable; |
|||
searchForm?: Recordable; |
|||
ajaxParameter?: Recordable; |
|||
} |
|||
|
|||
export interface SmartTableProxyConfig<T = any> extends VxeGridPropTypes.ProxyConfig { |
|||
ajax?: { |
|||
query?(params: SmartTableAjaxQueryParams, ...args: any[]): Promise<any>; |
|||
queryAll?(params: VxeGridPropTypes.ProxyAjaxQueryAllParams, ...args: any[]): Promise<any>; |
|||
delete?(params: VxeGridPropTypes.ProxyAjaxDeleteParams, ...args: any[]): Promise<any>; |
|||
save?(params: VxeGridPropTypes.ProxyAjaxSaveParams, ...args: any[]): Promise<any>; |
|||
getById?(params: T): Promise<T>; |
|||
useYn?(rows: T[], useYn: boolean): Promise<any>; |
|||
}; |
|||
// 删除回调
|
|||
afterDelete?: (result?: any) => void; |
|||
afterLoad?: (result: any) => any; |
|||
afterUserYn?: (result: any) => void; |
|||
} |
|||
|
|||
/** |
|||
* 行拖拽配置 |
|||
*/ |
|||
export interface SmartTableRowDragConfig extends SortableOptions { |
|||
icon?: string; |
|||
// 是否自定义
|
|||
custom?: boolean; |
|||
} |
|||
|
|||
export interface SmartTableRowConfig extends VxeTablePropTypes.RowConfig { |
|||
dragConfig?: boolean | SmartTableRowDragConfig; |
|||
} |
|||
|
|||
/** |
|||
* 行触发模式,单选/多选 |
|||
*/ |
|||
export type RowTriggerMode = 'single' | 'multiple'; |
|||
|
|||
/** |
|||
* 表格 checkbox配置 |
|||
*/ |
|||
export interface SmartCheckboxConfig extends VxeTablePropTypes.CheckboxConfig { |
|||
// 是否支持行触发选中
|
|||
rowTrigger?: RowTriggerMode; |
|||
// 是否支持ctrl选中,rowTrigger必须为single
|
|||
rowCtrl?: boolean; |
|||
// 是否支持shift多选,rowTrigger必须为single
|
|||
rowShift?: boolean; |
|||
} |
|||
|
|||
// @ts-ignore
|
|||
export interface SmartTableProps<T = any> extends VxeGridProps<T> { |
|||
columns?: SmartColumn[]; |
|||
// 默认的排序参数
|
|||
// 请求接口配置
|
|||
height?: TableHeightType; |
|||
// 表格加载状态
|
|||
loading?: boolean; |
|||
// checkbox配置
|
|||
checkboxConfig?: SmartCheckboxConfig; |
|||
// 搜索表单配置
|
|||
searchFormConfig?: Partial<SmartSearchFormProps>; |
|||
// 是否使用搜索表单
|
|||
useSearchForm?: boolean; |
|||
// 搜索是否带有符号
|
|||
toolbarConfig?: SmartTableToolbarConfig; |
|||
// 分页配置
|
|||
pagerConfig?: boolean | VxeGridPropTypes.PagerConfig; |
|||
proxyConfig?: SmartTableProxyConfig; |
|||
// 添加修改配置
|
|||
addEditConfig?: SmartTableAddEditConfig; |
|||
rowConfig?: SmartTableRowConfig; |
|||
authConfig?: SmartTableAuthConfig; |
|||
} |
|||
|
|||
/** |
|||
* 表格操作函数 |
|||
*/ |
|||
export interface TableActionType { |
|||
reload: (opt?: FetchParams) => Promise<void>; |
|||
query: (opt?: FetchParams) => Promise<void>; |
|||
/** |
|||
* 设置props |
|||
* @param props |
|||
*/ |
|||
setProps: (props: Partial<SmartTableProps>) => void; |
|||
// 设置加载状态
|
|||
setLoading: (loading: boolean) => void; |
|||
setPagination: (info: Partial<VxeGridPropTypes.PagerConfig>) => void; |
|||
getPagination: () => VxeGridPropTypes.PagerConfig | boolean | undefined; |
|||
commitVxeProxy: (code: string, ...args) => void; |
|||
|
|||
setShowPagination: (show: boolean) => Promise<void>; |
|||
getShowPagination: () => boolean; |
|||
|
|||
deleteByCheckbox: () => void; |
|||
getCheckboxRecords: (isFull: boolean) => Array<any>; |
|||
getRadioRecord: (isFull: boolean) => any; |
|||
|
|||
// openAddEditModal: <T = any>(props?: boolean, data?: T, openOnSet?: boolean) => void
|
|||
showAddModal: (formData?: Recordable, selectData?: Recordable) => void; |
|||
editByCheckbox: () => Promise<boolean> | boolean; |
|||
editByRowModal: <T = any>(data: T, formData?: Recordable) => Promise<boolean> | boolean; |
|||
getAddEditFieldsValue: () => Recordable; |
|||
resetAddEditFields: () => Promise<void>; |
|||
setAddEditFieldsValue: (data: any) => Promise<void>; |
|||
deleteByRow: (rows: any | any[]) => Promise<any>; |
|||
/** |
|||
* 用于 type=radio,设置某一行为选中状态 |
|||
* @param row 指定行 |
|||
*/ |
|||
setRadioRow(row: any): Promise<any>; |
|||
/** |
|||
* 用于 type=checkbox,设置行为选中状态,第二个参数为选中与否 |
|||
* @param rows 指定行 |
|||
* @param checked 是否选中 |
|||
*/ |
|||
setCheckboxRow(rows: any | any[], checked: boolean): Promise<any>; |
|||
validateAddEditFields: (nameList?: NamePath[]) => Promise<any>; |
|||
validateAddEdit: (nameList?: NamePath[]) => Promise<any>; |
|||
getTableInstance: () => VxeGridInstance; |
|||
getData: (rowIndex?: number) => any[]; |
|||
useYnByCheckbox: (useYn: boolean) => Promise<boolean | undefined>; |
|||
useYnByRow: (row: any | any[], useYn: boolean) => Promise<boolean | undefined>; |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
import type { VxeTablePropTypes } from 'vxe-table'; |
|||
|
|||
const FormSizeMap: Record<string, 'default' | 'small' | 'large'> = { |
|||
midum: 'default', |
|||
small: 'small', |
|||
mini: 'small', |
|||
tiny: 'small', |
|||
}; |
|||
|
|||
/** |
|||
* 获取form尺寸 |
|||
* @param size |
|||
*/ |
|||
export const getFormSize = ( |
|||
size: VxeTablePropTypes.Size | undefined, |
|||
): 'default' | 'small' | 'large' | undefined => { |
|||
if (size) { |
|||
return FormSizeMap[size]; |
|||
} |
|||
return undefined; |
|||
}; |
|||
@ -1,8 +1,25 @@ |
|||
import type { App } from 'vue'; |
|||
import { Button } from './Button'; |
|||
import { Input, Layout } from 'ant-design-vue'; |
|||
import { Input, Layout, Radio, Tag } from 'ant-design-vue'; |
|||
import VXETable from 'vxe-table'; |
|||
|
|||
import { i18n } from '@/locales/setupI18n'; |
|||
|
|||
export function registerGlobComp(app: App) { |
|||
app.use(Input).use(Button).use(Layout).use(VXETable); |
|||
VXETable.setup({ |
|||
// @ts-ignore
|
|||
i18n: (key, args) => { |
|||
// @ts-ignore
|
|||
return i18n.global.t(key, args); |
|||
}, |
|||
translate(key: string, args?: any): string { |
|||
if (key.startsWith('{') && key.endsWith('}')) { |
|||
const i18nKey = key.replace('{', '').replace('}', ''); |
|||
// @ts-ignore
|
|||
return i18n.global.t(i18nKey, args); |
|||
} |
|||
return key; |
|||
}, |
|||
}); |
|||
app.use(Input).use(Button).use(Layout).use(Radio).use(Tag).use(VXETable); |
|||
} |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
import { useAppStore } from '@/store/modules/app'; |
|||
import { computed } from 'vue'; |
|||
|
|||
const tableButtonSizeMap: { [index: string]: string } = { |
|||
medium: 'middle', |
|||
small: 'small', |
|||
mini: 'small', |
|||
}; |
|||
|
|||
export const useSizeSetting = () => { |
|||
const appStore = useAppStore(); |
|||
|
|||
const getButtonSize = computed(() => appStore.getSizeSetting.button); |
|||
const getTableSize = computed(() => appStore.getSizeSetting.table); |
|||
const getFormSize = computed(() => appStore.getSizeSetting.form); |
|||
|
|||
const tableButtonSizeConfig = computed(() => { |
|||
// @ts-ignore
|
|||
return tableButtonSizeMap[getTableSize.value] || 'small'; |
|||
}); |
|||
|
|||
return { |
|||
getButtonSize, |
|||
getTableSize, |
|||
getFormSize, |
|||
tableSizeConfig: getTableSize, |
|||
buttonSizeConfig: getButtonSize, |
|||
formSizeConfig: getFormSize, |
|||
tableButtonSizeConfig, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,106 @@ |
|||
export default { |
|||
common: { |
|||
button: { |
|||
add: 'Add', |
|||
edit: 'Edit', |
|||
delete: 'Delete', |
|||
search: 'Search', |
|||
reset: 'Reset', |
|||
confirm: 'Confirm', |
|||
use: 'Use', |
|||
noUse: 'No use', |
|||
save: 'Save', |
|||
look: 'Look', |
|||
download: 'Download', |
|||
cancel: 'Cancel', |
|||
submit: 'Submit', |
|||
reload: 'Reload', |
|||
choose: 'Choose', |
|||
publish: 'Publish', |
|||
revoke: 'Revoke', |
|||
}, |
|||
title: { |
|||
details: 'Details', |
|||
close: 'Close', |
|||
add: 'Add', |
|||
edit: 'Edit', |
|||
useYn: 'Use Yn', |
|||
}, |
|||
form: { |
|||
use: 'Use', |
|||
noUse: 'No use', |
|||
delete: 'Y', |
|||
noDelete: 'N', |
|||
yes: 'Y', |
|||
no: 'N', |
|||
}, |
|||
formValidate: { |
|||
seq: 'Please enter serial number', |
|||
remark: 'Please enter remark', |
|||
enter: 'Please enter message', |
|||
}, |
|||
table: { |
|||
createTime: 'Creation time', |
|||
createUser: 'Creation user', |
|||
updateTime: 'Update time', |
|||
updateUser: 'Update user', |
|||
useYn: 'Enabled', |
|||
deleteYn: 'Delete', |
|||
operation: 'Operation', |
|||
remark: 'Remark', |
|||
seq: 'Seq', |
|||
}, |
|||
notice: { |
|||
confirm: 'Confirm', |
|||
confirmSave: 'Are you sure you want to save?', |
|||
deleteConfirm: 'Are you sure you want to delete?', |
|||
deleteChoose: 'Please select the data to delete', |
|||
select: 'Please select', |
|||
choseOne: 'Please select a row of data', |
|||
useYnTrueConfirm: 'Are you sure you want to perform the enable operation?', |
|||
useYnFalseConfirm: 'Are you sure you want to perform the deactivation operation?', |
|||
}, |
|||
message: { |
|||
saveSuccess: 'Saved successfully', |
|||
editSuccess: 'Edit success', |
|||
deleteSuccess: 'Delete successfully', |
|||
deleteFail: 'Deletion failed', |
|||
submitSuccess: 'Submit successfully', |
|||
use: 'enable', |
|||
noUse: 'deactivate', |
|||
OperationSucceeded: 'Operation succeeded', |
|||
noPermission: 'No permission', |
|||
captchaValidateFail: 'Captcha validate fail', |
|||
}, |
|||
}, |
|||
app: { |
|||
multiTab: { |
|||
dropdownMenu: { |
|||
location: 'Locate the current tab', |
|||
closeAll: 'Close all tabs', |
|||
closeOther: 'Close other tabs', |
|||
refreshCurrent: 'Refresh current', |
|||
}, |
|||
closeOperation: 'Action', |
|||
}, |
|||
title: { |
|||
'404': 'The page you visited does not exist', |
|||
'403': 'You do not have permission to access this page', |
|||
backHome: 'Back Home', |
|||
}, |
|||
common: { |
|||
notice: { |
|||
logout: 'Are you sure you want to logout?', |
|||
}, |
|||
}, |
|||
message: { |
|||
changeTheme: 'Switching topics', |
|||
}, |
|||
changePassword: { |
|||
oldPassword: 'Old password', |
|||
newPassword: 'New password', |
|||
newPasswordConfirm: 'Confirm', |
|||
successMessage: 'Password modified successfully, please login again!', |
|||
}, |
|||
}, |
|||
}; |
|||
@ -0,0 +1,106 @@ |
|||
export default { |
|||
common: { |
|||
button: { |
|||
add: '添加', |
|||
edit: '修改', |
|||
delete: '删除', |
|||
search: '搜索', |
|||
reset: '重置', |
|||
confirm: '确认', |
|||
use: '启用', |
|||
noUse: '停用', |
|||
save: '保存', |
|||
look: '查看', |
|||
download: '下载', |
|||
cancel: '取消', |
|||
submit: '提交', |
|||
reload: '重新加载', |
|||
choose: '选择', |
|||
publish: '发布', |
|||
revoke: '撤销', |
|||
}, |
|||
title: { |
|||
details: '详情', |
|||
close: '关闭', |
|||
add: '添加', |
|||
edit: '修改', |
|||
useYn: '启用状态', |
|||
}, |
|||
form: { |
|||
use: '启用', |
|||
noUse: '停用', |
|||
delete: 'Y', |
|||
noDelete: 'N', |
|||
yes: '是', |
|||
no: '否', |
|||
}, |
|||
formValidate: { |
|||
seq: '请输入序号', |
|||
remark: '请输入备注', |
|||
enter: '请输入内容', |
|||
}, |
|||
table: { |
|||
createTime: '创建时间', |
|||
createUser: '创建人', |
|||
updateTime: '更新时间', |
|||
updateUser: '更新人', |
|||
useYn: '启用状态', |
|||
deleteYn: '是否删除', |
|||
operation: '操作', |
|||
remark: '备注', |
|||
seq: '序号', |
|||
}, |
|||
notice: { |
|||
confirm: '确认', |
|||
saveConfirm: '确定要保存吗?', |
|||
deleteConfirm: '确定要删除吗?', |
|||
deleteChoose: '请选择要删除的数据', |
|||
select: '请选择', |
|||
choseOne: '请选择一行数据', |
|||
useYnTrueConfirm: '确认要执行启用操作吗?', |
|||
useYnFalseConfirm: '确认要执行停用操作吗?', |
|||
}, |
|||
message: { |
|||
saveSuccess: '保存成功', |
|||
editSuccess: '修改成功', |
|||
deleteSuccess: '删除成功', |
|||
deleteFail: '删除失败', |
|||
submitSuccess: '提交成功', |
|||
use: '启用', |
|||
noUse: '停用', |
|||
OperationSucceeded: '操作成功', |
|||
noPermission: '无权限', |
|||
captchaValidateFail: '验证码验证失败', |
|||
}, |
|||
}, |
|||
app: { |
|||
multiTab: { |
|||
dropdownMenu: { |
|||
location: '定位当前选项卡', |
|||
closeAll: '关闭全部选项卡', |
|||
closeOther: '关闭其他选项卡', |
|||
refreshCurrent: '刷新页面', |
|||
}, |
|||
closeOperation: '关闭操作', |
|||
}, |
|||
title: { |
|||
'404': '您访问的页面不存在', |
|||
'403': '您无权限访问此页面', |
|||
backHome: '返回主页', |
|||
}, |
|||
common: { |
|||
notice: { |
|||
logout: '确定要登出吗?', |
|||
}, |
|||
}, |
|||
message: { |
|||
changeTheme: '正在切换主题', |
|||
}, |
|||
changePassword: { |
|||
oldPassword: '原密码', |
|||
newPassword: '新密码', |
|||
newPasswordConfirm: '确认新密码', |
|||
successMessage: '密码修改成功,请重新登录!', |
|||
}, |
|||
}, |
|||
}; |
|||
@ -0,0 +1,57 @@ |
|||
/** |
|||
* 用户的权限信息 |
|||
*/ |
|||
export const SystemPermissions = { |
|||
user: { |
|||
add: 'sys:user:save', |
|||
delete: 'sys:user:delete', |
|||
update: 'sys:user:update', |
|||
setRole: 'sys:user:setRole', |
|||
useYn: 'sys:user:setUseYn', |
|||
createAccount: 'sys:account:add', |
|||
unlockUserAccount: 'sys:user:unlockUserAccount', |
|||
unlockPassword: 'sys:user:resetPassword', |
|||
}, |
|||
role: { |
|||
add: 'sys:role:save', |
|||
delete: 'sys:role:delete', |
|||
update: 'sys:role:update', |
|||
setRoleUser: 'sys:role:setRoleUser', |
|||
setFunction: 'sys:role:setFunction', |
|||
}, |
|||
userGroup: { |
|||
add: 'sys:userGroup:save', |
|||
delete: 'sys:userGroup:delete', |
|||
update: 'sys:userGroup:update', |
|||
setUser: 'sys:userGroup:setUser', |
|||
}, |
|||
function: { |
|||
add: 'sys:function:save', |
|||
delete: 'sys:function:delete', |
|||
update: 'sys:function:update', |
|||
}, |
|||
i18n: { |
|||
add: 'sys:i18n:save', |
|||
delete: 'sys:i18n:delete', |
|||
update: 'sys:i18n:update', |
|||
reload: 'sys:i18n:reload', |
|||
}, |
|||
auth: { |
|||
offline: 'sys:auth.offline', |
|||
}, |
|||
}; |
|||
|
|||
/** |
|||
* 系统用户标识 |
|||
*/ |
|||
export const SYS_USER_TYPE = 'SYSTEM_USER'; |
|||
|
|||
/** |
|||
* 数据权限 |
|||
*/ |
|||
export const DATA_SCOPE = [ |
|||
{ key: 'DATA_ALL', value: 'system.views.user.title.dataAll' }, |
|||
{ key: 'DATA_DEPT', value: 'system.views.user.title.dataDept' }, |
|||
{ key: 'DATA_DEPT_AND_CHILD', value: 'system.views.user.title.dataDeptAll' }, |
|||
{ key: 'DATA_PERSONAL', value: 'system.views.user.title.dataPersonal' }, |
|||
]; |
|||
@ -0,0 +1,81 @@ |
|||
import { ApiServiceEnum, defHttp } from '@/utils/http/axios'; |
|||
import TreeUtils from '@/utils/TreeUtils'; |
|||
import { deepMerge } from '@/utils'; |
|||
|
|||
enum Api { |
|||
list = 'sys/function/list', |
|||
getById = 'sys/function/getById', |
|||
save = 'sys/function/saveUpdate', |
|||
delete = 'sys/function/batchDeleteById', |
|||
} |
|||
|
|||
export const listTree = async (params) => { |
|||
const parameter = { |
|||
sortName: 'seq', |
|||
...deepMerge(params, { |
|||
parameter: { |
|||
QUERY_CREATE_UPDATE_USER: true, |
|||
}, |
|||
}), |
|||
}; |
|||
const result = await defHttp.post({ |
|||
service: ApiServiceEnum.SMART_SYSTEM, |
|||
url: Api.list, |
|||
data: parameter, |
|||
}); |
|||
return ( |
|||
TreeUtils.convertList2Tree( |
|||
result, |
|||
(item) => item.functionId, |
|||
(item) => item.parentId, |
|||
0, |
|||
) || [] |
|||
); |
|||
}; |
|||
|
|||
export const listApi = (params) => { |
|||
const parameter = { |
|||
sortName: 'seq', |
|||
...params, |
|||
}; |
|||
return defHttp.post({ |
|||
service: ApiServiceEnum.SMART_SYSTEM, |
|||
url: Api.list, |
|||
data: parameter, |
|||
}); |
|||
}; |
|||
|
|||
export const getByIdApi = async (data) => { |
|||
const { |
|||
function: functionData, |
|||
createUser, |
|||
parent, |
|||
updateUser, |
|||
} = await defHttp.post({ |
|||
service: ApiServiceEnum.SMART_SYSTEM, |
|||
url: Api.getById, |
|||
data: data.functionId, |
|||
}); |
|||
return { |
|||
...functionData, |
|||
createUser: createUser && createUser.fullName, |
|||
updateUser: updateUser && updateUser.fullName, |
|||
parentName: (parent && parent.functionName) || '根目录', |
|||
}; |
|||
}; |
|||
|
|||
export const saveApi = ({ body: { insertRecords, updateRecords } }) => { |
|||
return defHttp.post({ |
|||
service: ApiServiceEnum.SMART_SYSTEM, |
|||
url: Api.save, |
|||
data: [...insertRecords, ...updateRecords][0], |
|||
}); |
|||
}; |
|||
|
|||
export const deleteApi = ({ body: { removeRecords } }) => { |
|||
return defHttp.post({ |
|||
service: ApiServiceEnum.SMART_SYSTEM, |
|||
url: Api.delete, |
|||
data: removeRecords.map((item) => item.functionId), |
|||
}); |
|||
}; |
|||
@ -0,0 +1,303 @@ |
|||
import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable'; |
|||
import type { FormSchema } from '@/components/Form'; |
|||
|
|||
export const tableColumns: SmartColumn[] = [ |
|||
{ |
|||
type: 'checkbox', |
|||
width: 60, |
|||
align: 'center', |
|||
fixed: 'left', |
|||
}, |
|||
{ |
|||
title: '{system.views.function.table.functionName}', |
|||
field: 'functionName', |
|||
width: 220, |
|||
fixed: 'left', |
|||
treeNode: true, |
|||
}, |
|||
{ |
|||
title: '{system.views.function.table.functionType}', |
|||
field: 'functionType', |
|||
width: 110, |
|||
align: 'center', |
|||
headerAlign: 'left', |
|||
slots: { |
|||
default: 'table-functionType', |
|||
}, |
|||
}, |
|||
{ |
|||
title: '{system.views.function.table.icon}', |
|||
field: 'icon', |
|||
width: 80, |
|||
align: 'center', |
|||
headerAlign: 'left', |
|||
slots: { |
|||
default: 'table-icon', |
|||
}, |
|||
}, |
|||
{ |
|||
title: 'URL', |
|||
field: 'url', |
|||
minWidth: 200, |
|||
}, |
|||
{ |
|||
title: '{system.views.function.table.permission}', |
|||
field: 'permission', |
|||
width: 160, |
|||
}, |
|||
{ |
|||
title: '{system.views.function.table.httpMethod}', |
|||
field: 'httpMethod', |
|||
width: 120, |
|||
}, |
|||
{ |
|||
title: '{common.table.seq}', |
|||
field: 'seq', |
|||
width: 100, |
|||
sortable: true, |
|||
}, |
|||
{ |
|||
title: '{common.table.createTime}', |
|||
field: 'createTime', |
|||
width: 165, |
|||
sortable: true, |
|||
}, |
|||
{ |
|||
title: '{common.table.createUser}', |
|||
field: 'createUserId', |
|||
width: 120, |
|||
formatter: ({ row }: any) => { |
|||
if (row.createUser) { |
|||
return row.createUser.fullName; |
|||
} |
|||
return ''; |
|||
}, |
|||
}, |
|||
{ |
|||
title: '{common.table.updateTime}', |
|||
field: 'updateTime', |
|||
width: 165, |
|||
sortable: true, |
|||
}, |
|||
{ |
|||
title: '{common.table.updateUser}', |
|||
field: 'updateUserId', |
|||
width: 120, |
|||
formatter: ({ row }: any) => { |
|||
if (row.updateUser) { |
|||
return row.updateUser.fullName; |
|||
} |
|||
return ''; |
|||
}, |
|||
}, |
|||
{ |
|||
title: '{common.table.operation}', |
|||
field: 'operation', |
|||
width: 200, |
|||
fixed: 'right', |
|||
slots: { |
|||
default: 'table-operation', |
|||
}, |
|||
}, |
|||
]; |
|||
|
|||
export const getAddEditForm = (t: Function): FormSchema[] => { |
|||
return [ |
|||
{ |
|||
field: 'functionId', |
|||
label: '', |
|||
component: 'Input', |
|||
show: false, |
|||
}, |
|||
{ |
|||
field: 'parentName', |
|||
label: '上级', |
|||
component: 'Input', |
|||
componentProps: { |
|||
disabled: true, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'parentId', |
|||
label: '', |
|||
component: 'Input', |
|||
show: false, |
|||
}, |
|||
{ |
|||
field: 'functionName', |
|||
label: t('system.views.function.table.functionName'), |
|||
component: 'Input', |
|||
required: true, |
|||
}, |
|||
{ |
|||
field: 'functionType', |
|||
label: t('system.views.function.table.functionType'), |
|||
slot: 'addEditForm-functionType', |
|||
required: true, |
|||
}, |
|||
{ |
|||
field: 'i18nCode', |
|||
label: t('system.views.function.table.i18nCode'), |
|||
component: 'Input', |
|||
}, |
|||
{ |
|||
field: 'icon', |
|||
label: t('system.views.function.table.icon'), |
|||
component: 'IconPicker', |
|||
}, |
|||
{ |
|||
field: 'seq', |
|||
required: true, |
|||
label: t('common.table.seq'), |
|||
component: 'InputNumber', |
|||
defaultValue: 1, |
|||
}, |
|||
{ |
|||
field: 'componentName', |
|||
label: t('system.views.function.table.componentName'), |
|||
component: 'Input', |
|||
dynamicRules: ({ model }) => { |
|||
return [ |
|||
{ |
|||
required: model.functionType === 'MENU', |
|||
trigger: 'blur', |
|||
message: t('system.views.function.validate.componentName'), |
|||
}, |
|||
]; |
|||
}, |
|||
show: ({ model }) => { |
|||
return model.functionType !== 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'component', |
|||
label: t('system.views.function.table.component'), |
|||
component: 'Input', |
|||
dynamicRules: ({ model }) => { |
|||
return [ |
|||
{ |
|||
required: model.functionType === 'MENU', |
|||
trigger: 'blur', |
|||
message: t('system.views.function.validate.component'), |
|||
}, |
|||
]; |
|||
}, |
|||
show: ({ model }) => { |
|||
return model.functionType !== 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'url', |
|||
label: 'URL', |
|||
component: 'Input', |
|||
dynamicRules: ({ model }) => { |
|||
return [ |
|||
{ |
|||
required: model.functionType === 'MENU', |
|||
trigger: 'blur', |
|||
message: t('system.views.function.validate.url'), |
|||
}, |
|||
]; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'redirect', |
|||
label: 'Redirect', |
|||
component: 'Input', |
|||
show: ({ model }) => { |
|||
return model.functionType !== 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'httpMethod', |
|||
label: t('system.views.function.table.httpMethod'), |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: ['GET', 'POST', 'PUT', 'DELETE'].map((item) => ({ label: item, value: item })), |
|||
}, |
|||
show: ({ model }) => { |
|||
return model.functionType === 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'permission', |
|||
label: t('system.views.function.table.permission'), |
|||
component: 'Input', |
|||
dynamicRules: ({ model }) => { |
|||
return [ |
|||
{ |
|||
required: model.functionType === 'FUNCTION', |
|||
message: t('system.views.function.validate.permission'), |
|||
trigger: 'blur', |
|||
}, |
|||
]; |
|||
}, |
|||
show: ({ model }) => { |
|||
return model.functionType === 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'isMenu', |
|||
label: t('system.views.function.table.menuIs'), |
|||
component: 'Switch', |
|||
defaultValue: true, |
|||
show: ({ model }) => { |
|||
return model.functionType !== 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'internalOrExternal', |
|||
label: t('system.views.function.table.internalOrExternal'), |
|||
component: 'Switch', |
|||
defaultValue: false, |
|||
show: ({ model }) => { |
|||
return model.functionType === 'MENU'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'dataRule', |
|||
label: t('system.views.function.table.dataRule'), |
|||
component: 'Switch', |
|||
defaultValue: false, |
|||
show: ({ model }) => { |
|||
return model.functionType === 'FUNCTION'; |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'cached', |
|||
label: t('system.views.function.title.cached'), |
|||
component: 'Switch', |
|||
defaultValue: true, |
|||
show: ({ model }) => { |
|||
return model.functionType === 'MENU'; |
|||
}, |
|||
}, |
|||
]; |
|||
}; |
|||
|
|||
export const getSearchSchemas = (t: Function): SmartSearchFormSchema[] => { |
|||
return [ |
|||
{ |
|||
label: t('common.title.useYn'), |
|||
field: 'useYn', |
|||
component: 'Select', |
|||
defaultValue: 1, |
|||
searchSymbol: '=', |
|||
componentProps: { |
|||
style: { |
|||
width: '100px', |
|||
}, |
|||
options: [ |
|||
{ |
|||
label: t('common.form.use'), |
|||
value: 1, |
|||
}, |
|||
{ |
|||
label: t('common.form.noUse'), |
|||
value: 0, |
|||
}, |
|||
], |
|||
}, |
|||
}, |
|||
]; |
|||
}; |
|||
@ -0,0 +1,284 @@ |
|||
<template> |
|||
<div class="full-height page-container"> |
|||
<SmartTable @register="registerTable" :size="getTableSize"> |
|||
<template #table-icon="{ row }"> |
|||
<Icon v-if="row.icon" :icon="getIcon(row.icon)" /> |
|||
<div v-else></div> |
|||
</template> |
|||
<template #table-functionType="{ row }"> |
|||
<a-tag :color="getTagData(row.functionType).color"> |
|||
{{ getTagData(row.functionType).text }} |
|||
</a-tag> |
|||
</template> |
|||
<template #table-operation="{ row }"> |
|||
<SmartVxeTableAction :actions="getTableActions(row)" /> |
|||
</template> |
|||
<template #addEditForm-functionType="{ model }"> |
|||
<a-radio-group v-model:value="model.functionType"> |
|||
<a-radio |
|||
v-for="(type, key) in functionTypes" |
|||
:key="key" |
|||
:disabled="type.disabled" |
|||
:value="type.value" |
|||
> |
|||
{{ type.label }} |
|||
</a-radio> |
|||
</a-radio-group> |
|||
</template> |
|||
</SmartTable> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { reactive, ref, unref } from 'vue'; |
|||
|
|||
import { |
|||
ActionItem, |
|||
SmartTable, |
|||
useSmartTable, |
|||
SmartVxeTableAction, |
|||
} from '@/components/SmartTable'; |
|||
import { Icon } from '@/components/Icon'; |
|||
import { tableColumns, getAddEditForm, getSearchSchemas } from './FunctionListView.config'; |
|||
import { useSizeSetting } from '@/hooks/setting/UseSizeSetting'; |
|||
import { listApi, deleteApi, getByIdApi, saveApi } from './FunctionListView.api'; |
|||
import { useI18n } from '@/hooks/web/useI18n'; |
|||
import StringUtils from '@/utils/StringUtils'; |
|||
import { SystemPermissions } from '@/modules/system/constants/SystemConstants'; |
|||
import { hasPermission } from '@/utils/auth'; |
|||
|
|||
const permissions = SystemPermissions.function; |
|||
const { t } = useI18n(); |
|||
const { getTableSize } = useSizeSetting(); |
|||
|
|||
const currentRowRef = ref<any>({}); |
|||
const getTableActions = (row: Recordable): ActionItem[] => { |
|||
return [ |
|||
{ |
|||
label: t('common.button.add'), |
|||
icon: 'ant-design:plus-outlined', |
|||
disabled: row.functionType === 'FUNCTION' || !hasPermission(permissions.add), |
|||
onClick: () => { |
|||
currentRowRef.value = row; |
|||
const data: Recordable = { |
|||
parentId: row.functionId, |
|||
parentName: row.functionName, |
|||
}; |
|||
const functionType = row.functionType; |
|||
switch (functionType) { |
|||
case 'CATALOG': { |
|||
setTypeDisabled(['function']); |
|||
break; |
|||
} |
|||
case 'MENU': { |
|||
setTypeDisabled(['catalogue', 'menu']); |
|||
data.functionType = 'FUNCTION'; |
|||
break; |
|||
} |
|||
case 'FUNCTION': { |
|||
setTypeDisabled(['catalogue', 'menu', 'function']); |
|||
break; |
|||
} |
|||
} |
|||
showAddModal(data, row); |
|||
}, |
|||
}, |
|||
{ |
|||
label: t('common.button.edit'), |
|||
icon: 'ant-design:edit-outlined', |
|||
color: 'warning', |
|||
auth: permissions.update, |
|||
onClick: () => { |
|||
currentRowRef.value = row; |
|||
setTypeDisabled(['catalogue', 'menu', 'function']); |
|||
editByRowModal(row); |
|||
}, |
|||
}, |
|||
{ |
|||
label: t('common.button.delete'), |
|||
icon: 'ant-design:delete-outlined', |
|||
danger: true, |
|||
auth: permissions.delete, |
|||
onClick: () => { |
|||
currentRowRef.value = row; |
|||
deleteByRow(row); |
|||
}, |
|||
}, |
|||
]; |
|||
}; |
|||
|
|||
const [ |
|||
registerTable, |
|||
{ showAddModal, editByRowModal, getSearchForm, getTableInstance, deleteByRow }, |
|||
] = useSmartTable({ |
|||
id: 'FunctionListView', |
|||
columns: tableColumns, |
|||
resizableConfig: {}, |
|||
border: true, |
|||
align: 'left', |
|||
height: 'auto', |
|||
useSearchForm: true, |
|||
searchFormConfig: { |
|||
compact: true, |
|||
layout: 'inline', |
|||
colon: true, |
|||
schemas: getSearchSchemas(t), |
|||
searchWithSymbol: true, |
|||
actionColOptions: { |
|||
span: undefined, |
|||
}, |
|||
}, |
|||
columnConfig: { |
|||
resizable: true, |
|||
isHover: true, |
|||
}, |
|||
authConfig: { |
|||
authHandler: hasPermission, |
|||
toolbar: { |
|||
ModalAdd: permissions.add, |
|||
}, |
|||
}, |
|||
treeConfig: { |
|||
lazy: true, |
|||
loadMethod: ({ row }) => { |
|||
const { searchSymbolForm } = getSearchForm().getSearchFormParameter(); |
|||
const parameter = { |
|||
parameter: { |
|||
...searchSymbolForm, |
|||
'parentId@=': row.functionId, |
|||
}, |
|||
}; |
|||
return listApi(parameter); |
|||
}, |
|||
}, |
|||
rowConfig: { |
|||
keyField: 'functionId', |
|||
}, |
|||
sortConfig: { |
|||
remote: true, |
|||
defaultSort: { |
|||
field: 'seq', |
|||
order: 'asc', |
|||
}, |
|||
}, |
|||
addEditConfig: { |
|||
modalConfig: { |
|||
width: 860, |
|||
}, |
|||
formConfig: { |
|||
colon: true, |
|||
baseColProps: { |
|||
span: 12, |
|||
}, |
|||
labelCol: { span: 7 }, |
|||
wrapperCol: { span: 16 }, |
|||
schemas: getAddEditForm(t), |
|||
}, |
|||
afterSave: () => { |
|||
// 保存完成之后重新加载节点 |
|||
return getTableInstance().reloadTreeExpand(unref(currentRowRef)); |
|||
}, |
|||
}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: ({ ajaxParameter }) => { |
|||
const parameter = { |
|||
...ajaxParameter, |
|||
parameter: { |
|||
...ajaxParameter?.parameter, |
|||
'parentId@=': 0, |
|||
}, |
|||
}; |
|||
return listApi(parameter); |
|||
}, |
|||
delete: (params) => deleteApi(params), |
|||
save: saveApi, |
|||
getById: getByIdApi, |
|||
}, |
|||
}, |
|||
toolbarConfig: { |
|||
refresh: true, |
|||
buttons: [ |
|||
{ |
|||
code: 'ModalAdd', |
|||
props: { |
|||
onClick: () => { |
|||
setTypeDisabled(['function']); |
|||
showAddModal({ |
|||
parentId: '0', |
|||
parentName: '根目录', |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
], |
|||
}, |
|||
}); |
|||
const getTagData = (functionType: string) => { |
|||
switch (functionType) { |
|||
case 'CATALOG': { |
|||
return { |
|||
color: '#f50', |
|||
text: t('system.views.function.common.catalogue'), |
|||
}; |
|||
} |
|||
case 'MENU': { |
|||
return { |
|||
color: '#2db7f5', |
|||
text: t('system.views.function.common.menu'), |
|||
}; |
|||
} |
|||
case 'FUNCTION': { |
|||
return { |
|||
color: '#108ee9', |
|||
text: t('system.views.function.common.function'), |
|||
}; |
|||
} |
|||
default: { |
|||
return { |
|||
text: '', |
|||
}; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const getIcon = (compatibleIcon: string) => { |
|||
if (compatibleIcon && compatibleIcon.indexOf(':') === -1) { |
|||
compatibleIcon = StringUtils.humpToLine(compatibleIcon); |
|||
compatibleIcon = 'ant-design:' + compatibleIcon; |
|||
} |
|||
return compatibleIcon; |
|||
}; |
|||
|
|||
const defaultFunctionTypes = (t: Function) => { |
|||
return { |
|||
catalogue: { |
|||
value: 'CATALOG', |
|||
label: t('system.views.function.common.catalogue'), |
|||
disabled: false, |
|||
}, |
|||
menu: { |
|||
value: 'MENU', |
|||
label: t('system.views.function.common.menu'), |
|||
disabled: false, |
|||
}, |
|||
function: { |
|||
value: 'FUNCTION', |
|||
label: t('system.views.function.common.function'), |
|||
disabled: false, |
|||
}, |
|||
}; |
|||
}; |
|||
/** |
|||
* 设置不可用类型 |
|||
* @param keys |
|||
*/ |
|||
const setTypeDisabled = (keys: Array<string>) => { |
|||
Object.keys(functionTypes).forEach((key) => { |
|||
functionTypes[key].disabled = keys.includes(key); |
|||
}); |
|||
}; |
|||
const functionTypes: any = reactive(defaultFunctionTypes(t)); |
|||
</script> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,75 @@ |
|||
export default { |
|||
trans: true, |
|||
key: 'system.views.function', |
|||
data: { |
|||
common: { |
|||
catalogue: 'Catalogue', |
|||
menu: 'Menu', |
|||
function: 'Function', |
|||
rootCatalogue: 'Root catalogue', |
|||
}, |
|||
table: { |
|||
functionName: 'Function name', |
|||
functionType: 'Function type', |
|||
i18nCode: 'I18n code', |
|||
icon: 'Icon', |
|||
permission: 'Permission', |
|||
httpMethod: 'Http method', |
|||
menuIs: 'Is menu', |
|||
internalOrExternal: 'Open externally', |
|||
dataRule: 'Data permissions', |
|||
component: 'Component path', |
|||
componentName: 'Component name', |
|||
}, |
|||
validate: { |
|||
httpMethod: 'Please select the request method', |
|||
functionName: 'Please enter the function name', |
|||
functionType: 'Please enter the function type', |
|||
i18nCode: 'Please enter the i18n code', |
|||
url: 'Please enter the url', |
|||
permission: 'Please enter the permission', |
|||
icon: 'Please enter the icon', |
|||
component: 'Please enter component path', |
|||
componentName: 'Please enter component name', |
|||
redirect: 'Please enter redirect', |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
/** |
|||
* Function I18N |
|||
*/ |
|||
// export default {
|
|||
// system: {
|
|||
// views: {
|
|||
// function: {
|
|||
// common: {
|
|||
// catalogue: 'Catalogue',
|
|||
// menu: 'Menu',
|
|||
// function: 'Function',
|
|||
// rootCatalogue: 'Root catalogue'
|
|||
// },
|
|||
// table: {
|
|||
// functionName: 'Function name',
|
|||
// functionType: 'Function type',
|
|||
// i18nCode: 'I18n code',
|
|||
// icon: 'Icon',
|
|||
// permission: 'Permission',
|
|||
// httpMethod: 'Http method',
|
|||
// menuIs: 'Is menu',
|
|||
// internalOrExternal: 'Open externally',
|
|||
// dataRule: 'Data permissions'
|
|||
// },
|
|||
// validate: {
|
|||
// httpMethod: 'Please select the request method',
|
|||
// functionName: 'Please enter the function name',
|
|||
// functionType: 'Please enter the function type',
|
|||
// i18nCode: 'Please enter the i18n code',
|
|||
// url: 'Please enter the url',
|
|||
// permission: 'Please enter the permission',
|
|||
// icon: 'Please enter the icon'
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Function I18N |
|||
*/ |
|||
export default { |
|||
system: { |
|||
views: { |
|||
function: { |
|||
common: { |
|||
catalogue: '目录', |
|||
menu: '菜单', |
|||
function: '功能', |
|||
rootCatalogue: '跟目录', |
|||
}, |
|||
table: { |
|||
functionName: '功能名称', |
|||
functionType: '类型', |
|||
i18nCode: '国际化编码', |
|||
icon: '图标', |
|||
permission: '权限标识', |
|||
httpMethod: '请求方式', |
|||
menuIs: '是否菜单', |
|||
internalOrExternal: '是否外部打开', |
|||
dataRule: '数据权限', |
|||
component: '组件路径', |
|||
componentName: '组件名称', |
|||
}, |
|||
validate: { |
|||
httpMethod: '请选择请求方式', |
|||
functionName: '请输入功能名称', |
|||
functionType: '请选择功能类型', |
|||
i18nCode: '请输入国际化编码', |
|||
url: '请输入URL', |
|||
permission: '请输入权限标识', |
|||
icon: '请输入图标', |
|||
component: '请输入组件路径', |
|||
componentName: '请输入组件名称', |
|||
redirect: '请输入redirect', |
|||
}, |
|||
title: { |
|||
cached: '缓存路由', |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
Loading…
Reference in new issue