Browse Source
* chore: wip vxe-table * feat: add table demo * chore: follow ci recommendations to adjust details * chore: add custom-cell demo * feat: add custom-cell table demo * feat: add table from demopull/4566/head
committed by
GitHub
80 changed files with 2426 additions and 80 deletions
@ -0,0 +1,48 @@ |
|||
import { faker } from '@faker-js/faker'; |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { unAuthorizedResponse } from '~/utils/response'; |
|||
|
|||
function generateMockDataList(count: number) { |
|||
const dataList = []; |
|||
|
|||
for (let i = 0; i < count; i++) { |
|||
const dataItem = { |
|||
id: faker.string.uuid(), |
|||
imageUrl: faker.image.avatar(), |
|||
imageUrl2: faker.image.avatar(), |
|||
open: faker.datatype.boolean(), |
|||
status: faker.helpers.arrayElement(['success', 'error', 'warning']), |
|||
productName: faker.commerce.productName(), |
|||
price: faker.commerce.price(), |
|||
currency: faker.finance.currencyCode(), |
|||
quantity: faker.number.int({ min: 1, max: 100 }), |
|||
available: faker.datatype.boolean(), |
|||
category: faker.commerce.department(), |
|||
releaseDate: faker.date.past(), |
|||
rating: faker.number.float({ min: 1, max: 5 }), |
|||
description: faker.commerce.productDescription(), |
|||
weight: faker.number.float({ min: 0.1, max: 10 }), |
|||
color: faker.color.human(), |
|||
inProduction: faker.datatype.boolean(), |
|||
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()), |
|||
}; |
|||
|
|||
dataList.push(dataItem); |
|||
} |
|||
|
|||
return dataList; |
|||
} |
|||
|
|||
const mockData = generateMockDataList(100); |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
|
|||
await sleep(600); |
|||
|
|||
const { page, pageSize } = getQuery(event); |
|||
return usePageResponseSuccess(page as string, pageSize as string, mockData); |
|||
}); |
|||
@ -1 +1,2 @@ |
|||
export * from './form'; |
|||
export * from './vxe-table'; |
|||
|
|||
@ -0,0 +1,59 @@ |
|||
import { h } from 'vue'; |
|||
|
|||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; |
|||
|
|||
import { Button, Image } from 'ant-design-vue'; |
|||
|
|||
import { useVbenForm } from './form'; |
|||
|
|||
setupVbenVxeTable({ |
|||
configVxeTable: (vxeUI) => { |
|||
vxeUI.setConfig({ |
|||
grid: { |
|||
align: 'center', |
|||
border: true, |
|||
minHeight: 180, |
|||
proxyConfig: { |
|||
autoLoad: true, |
|||
response: { |
|||
result: 'items', |
|||
total: 'total', |
|||
list: 'items', |
|||
}, |
|||
showActiveMsg: true, |
|||
showResponseMsg: false, |
|||
}, |
|||
round: true, |
|||
size: 'small', |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|||
vxeUI.renderer.add('CellImage', { |
|||
renderDefault(_renderOpts, params) { |
|||
const { column, row } = params; |
|||
return h(Image, { src: row[column.field] }); |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|||
vxeUI.renderer.add('CellLink', { |
|||
renderDefault(renderOpts) { |
|||
const { props } = renderOpts; |
|||
return h( |
|||
Button, |
|||
{ size: 'small', type: 'link' }, |
|||
{ default: () => props?.text }, |
|||
); |
|||
}, |
|||
}); |
|||
|
|||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|||
// vxeUI.formats.add
|
|||
}, |
|||
useVbenForm, |
|||
}); |
|||
|
|||
export { useVbenVxeGrid }; |
|||
|
|||
export type * from '@vben/plugins/vxe-table'; |
|||
@ -1 +1,2 @@ |
|||
export * from './form'; |
|||
export * from './vxe-table'; |
|||
|
|||
@ -0,0 +1,60 @@ |
|||
import { h } from 'vue'; |
|||
|
|||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; |
|||
|
|||
import { ElButton, ElImage } from 'element-plus'; |
|||
|
|||
import { useVbenForm } from './form'; |
|||
|
|||
setupVbenVxeTable({ |
|||
configVxeTable: (vxeUI) => { |
|||
vxeUI.setConfig({ |
|||
grid: { |
|||
align: 'center', |
|||
border: true, |
|||
minHeight: 180, |
|||
proxyConfig: { |
|||
autoLoad: true, |
|||
response: { |
|||
result: 'items', |
|||
total: 'total', |
|||
list: 'items', |
|||
}, |
|||
showActiveMsg: true, |
|||
showResponseMsg: false, |
|||
}, |
|||
round: true, |
|||
size: 'small', |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|||
vxeUI.renderer.add('CellImage', { |
|||
renderDefault(_renderOpts, params) { |
|||
const { column, row } = params; |
|||
const src = row[column.field]; |
|||
return h(ElImage, { src, previewSrcList: [src] }); |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|||
vxeUI.renderer.add('CellLink', { |
|||
renderDefault(renderOpts) { |
|||
const { props } = renderOpts; |
|||
return h( |
|||
ElButton, |
|||
{ size: 'small', link: true }, |
|||
{ default: () => props?.text }, |
|||
); |
|||
}, |
|||
}); |
|||
|
|||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|||
// vxeUI.formats.add
|
|||
}, |
|||
useVbenForm, |
|||
}); |
|||
|
|||
export { useVbenVxeGrid }; |
|||
|
|||
export type * from '@vben/plugins/vxe-table'; |
|||
@ -1,2 +1,3 @@ |
|||
export * from './form'; |
|||
export * from './naive'; |
|||
export * from './vxe-table'; |
|||
|
|||
@ -0,0 +1,59 @@ |
|||
import { h } from 'vue'; |
|||
|
|||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; |
|||
|
|||
import { NButton, NImage } from 'naive-ui'; |
|||
|
|||
import { useVbenForm } from './form'; |
|||
|
|||
setupVbenVxeTable({ |
|||
configVxeTable: (vxeUI) => { |
|||
vxeUI.setConfig({ |
|||
grid: { |
|||
align: 'center', |
|||
border: true, |
|||
minHeight: 180, |
|||
proxyConfig: { |
|||
autoLoad: true, |
|||
response: { |
|||
result: 'items', |
|||
total: 'total', |
|||
list: 'items', |
|||
}, |
|||
showActiveMsg: true, |
|||
showResponseMsg: false, |
|||
}, |
|||
round: true, |
|||
size: 'small', |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|||
vxeUI.renderer.add('CellImage', { |
|||
renderDefault(_renderOpts, params) { |
|||
const { column, row } = params; |
|||
return h(NImage, { src: row[column.field] }); |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|||
vxeUI.renderer.add('CellLink', { |
|||
renderDefault(renderOpts) { |
|||
const { props } = renderOpts; |
|||
return h( |
|||
NButton, |
|||
{ size: 'small', type: 'primary', quaternary: true }, |
|||
{ default: () => props?.text }, |
|||
); |
|||
}, |
|||
}); |
|||
|
|||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|||
// vxeUI.formats.add
|
|||
}, |
|||
useVbenForm, |
|||
}); |
|||
|
|||
export { useVbenVxeGrid }; |
|||
|
|||
export type * from '@vben/plugins/vxe-table'; |
|||
@ -0,0 +1,20 @@ |
|||
import type { PluginOption } from 'vite'; |
|||
|
|||
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'; |
|||
|
|||
async function viteVxeTableImportsPlugin(): Promise<PluginOption> { |
|||
return [ |
|||
lazyImport({ |
|||
resolvers: [ |
|||
VxeResolver({ |
|||
libraryName: 'vxe-table', |
|||
}), |
|||
VxeResolver({ |
|||
libraryName: 'vxe-pc-ui', |
|||
}), |
|||
], |
|||
}), |
|||
]; |
|||
} |
|||
|
|||
export { viteVxeTableImportsPlugin }; |
|||
@ -0,0 +1,27 @@ |
|||
<template> |
|||
<svg |
|||
height="41" |
|||
viewBox="0 0 64 41" |
|||
width="64" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
> |
|||
<g fill="none" fill-rule="evenodd" transform="translate(0 1)"> |
|||
<ellipse |
|||
cx="32" |
|||
cy="33" |
|||
fill="hsl(var(--background-deep))" |
|||
rx="32" |
|||
ry="7" |
|||
/> |
|||
<g fill-rule="nonzero" stroke="hsl(var(--heavy))"> |
|||
<path |
|||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z" |
|||
/> |
|||
<path |
|||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" |
|||
fill="hsl(var(--accent))" |
|||
/> |
|||
</g> |
|||
</g> |
|||
</svg> |
|||
</template> |
|||
@ -1,4 +1,5 @@ |
|||
export { default as EmptyIcon } from './components/empty.vue'; |
|||
export * from './create-icon'; |
|||
export * from './lucide'; |
|||
|
|||
export * from './lucide'; |
|||
export * from '@iconify/vue'; |
|||
|
|||
@ -0,0 +1 @@ |
|||
export { default } from '@vben/tailwind-config/postcss'; |
|||
@ -0,0 +1,111 @@ |
|||
import type { VxeGridInstance } from 'vxe-table'; |
|||
|
|||
import type { VxeGridProps } from './types'; |
|||
|
|||
import { toRaw } from 'vue'; |
|||
|
|||
import { Store } from '@vben-core/shared/store'; |
|||
import { |
|||
bindMethods, |
|||
isFunction, |
|||
mergeWithArrayOverride, |
|||
StateHandler, |
|||
} from '@vben-core/shared/utils'; |
|||
|
|||
function getDefaultState(): VxeGridProps { |
|||
return { |
|||
class: '', |
|||
gridClass: '', |
|||
gridOptions: {}, |
|||
gridEvents: {}, |
|||
formOptions: undefined, |
|||
}; |
|||
} |
|||
|
|||
export class VxeGridApi { |
|||
// private prevState: null | VxeGridProps = null;
|
|||
public grid = {} as VxeGridInstance; |
|||
|
|||
isMounted = false; |
|||
public state: null | VxeGridProps = null; |
|||
|
|||
stateHandler: StateHandler; |
|||
|
|||
public store: Store<VxeGridProps>; |
|||
|
|||
constructor(options: VxeGridProps = {}) { |
|||
const storeState = { ...options }; |
|||
|
|||
const defaultState = getDefaultState(); |
|||
this.store = new Store<VxeGridProps>( |
|||
mergeWithArrayOverride(storeState, defaultState), |
|||
{ |
|||
onUpdate: () => { |
|||
// this.prevState = this.state;
|
|||
this.state = this.store.state; |
|||
}, |
|||
}, |
|||
); |
|||
|
|||
this.state = this.store.state; |
|||
this.stateHandler = new StateHandler(); |
|||
bindMethods(this); |
|||
} |
|||
|
|||
mount(instance: null | VxeGridInstance) { |
|||
if (!this.isMounted && instance) { |
|||
this.grid = instance; |
|||
this.stateHandler.setConditionTrue(); |
|||
this.isMounted = true; |
|||
} |
|||
} |
|||
|
|||
async query(params: Record<string, any> = {}) { |
|||
try { |
|||
await this.grid.commitProxy('query', toRaw(params)); |
|||
} catch (error) { |
|||
console.error('Error occurred while querying:', error); |
|||
} |
|||
} |
|||
|
|||
async reload(params: Record<string, any> = {}) { |
|||
try { |
|||
await this.grid.commitProxy('reload', toRaw(params)); |
|||
} catch (error) { |
|||
console.error('Error occurred while reloading:', error); |
|||
} |
|||
} |
|||
|
|||
setGridOptions(options: Partial<VxeGridProps['gridOptions']>) { |
|||
this.setState({ |
|||
gridOptions: options, |
|||
}); |
|||
} |
|||
|
|||
setLoading(isLoading: boolean) { |
|||
this.setState({ |
|||
gridOptions: { |
|||
loading: isLoading, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
setState( |
|||
stateOrFn: |
|||
| ((prev: VxeGridProps) => Partial<VxeGridProps>) |
|||
| Partial<VxeGridProps>, |
|||
) { |
|||
if (isFunction(stateOrFn)) { |
|||
this.store.setState((prev) => { |
|||
return mergeWithArrayOverride(stateOrFn(prev), prev); |
|||
}); |
|||
} else { |
|||
this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev)); |
|||
} |
|||
} |
|||
|
|||
unmount() { |
|||
this.isMounted = false; |
|||
this.stateHandler.reset(); |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
export { setupVbenVxeTable } from './init'; |
|||
export * from './use-vxe-grid'; |
|||
export { default as VbenVxeGrid } from './use-vxe-grid.vue'; |
|||
export type { VxeGridListeners, VxeGridProps } from 'vxe-table'; |
|||
@ -0,0 +1,122 @@ |
|||
import type { SetupVxeTable } from './types'; |
|||
|
|||
import { defineComponent, watch } from 'vue'; |
|||
|
|||
import { usePreferences } from '@vben/preferences'; |
|||
import { useVbenForm } from '@vben-core/form-ui'; |
|||
|
|||
import { |
|||
VxeButton, |
|||
VxeButtonGroup, |
|||
// VxeFormGather,
|
|||
// VxeForm,
|
|||
// VxeFormItem,
|
|||
VxeIcon, |
|||
VxeInput, |
|||
VxeLoading, |
|||
VxePager, |
|||
// VxeList,
|
|||
// VxeModal,
|
|||
// VxeOptgroup,
|
|||
// VxeOption,
|
|||
// VxePulldown,
|
|||
// VxeRadio,
|
|||
// VxeRadioButton,
|
|||
// VxeRadioGroup,
|
|||
VxeSelect, |
|||
VxeTooltip, |
|||
VxeUI, |
|||
// VxeSwitch,
|
|||
// VxeTextarea,
|
|||
} from 'vxe-pc-ui'; |
|||
import enUS from 'vxe-pc-ui/lib/language/en-US'; |
|||
|
|||
// 导入默认的语言
|
|||
import zhCN from 'vxe-pc-ui/lib/language/zh-CN'; |
|||
import { |
|||
VxeColgroup, |
|||
VxeColumn, |
|||
VxeGrid, |
|||
VxeTable, |
|||
VxeToolbar, |
|||
} from 'vxe-table'; |
|||
|
|||
// 是否加载过
|
|||
let isInit = false; |
|||
|
|||
// eslint-disable-next-line import/no-mutable-exports
|
|||
export let useTableForm: typeof useVbenForm; |
|||
|
|||
// 部分组件,如果没注册,vxe-table 会报错,这里实际没用组件,只是为了不报错,同时可以减少打包体积
|
|||
const createVirtualComponent = (name = '') => { |
|||
return defineComponent({ |
|||
name, |
|||
}); |
|||
}; |
|||
|
|||
export function initVxeTable() { |
|||
if (isInit) { |
|||
return; |
|||
} |
|||
|
|||
VxeUI.component(VxeTable); |
|||
VxeUI.component(VxeColumn); |
|||
VxeUI.component(VxeColgroup); |
|||
VxeUI.component(VxeLoading); |
|||
VxeUI.component(VxeGrid); |
|||
VxeUI.component(VxeToolbar); |
|||
|
|||
VxeUI.component(VxeButton); |
|||
VxeUI.component(VxeButtonGroup); |
|||
// VxeUI.component(VxeCheckbox);
|
|||
// VxeUI.component(VxeCheckboxGroup);
|
|||
VxeUI.component(createVirtualComponent('VxeForm')); |
|||
// VxeUI.component(VxeFormGather);
|
|||
// VxeUI.component(VxeFormItem);
|
|||
VxeUI.component(VxeIcon); |
|||
VxeUI.component(VxeInput); |
|||
// VxeUI.component(VxeList);
|
|||
VxeUI.component(VxeLoading); |
|||
// VxeUI.component(VxeModal);
|
|||
// VxeUI.component(VxeOptgroup);
|
|||
// VxeUI.component(VxeOption);
|
|||
VxeUI.component(VxePager); |
|||
// VxeUI.component(VxePulldown);
|
|||
// VxeUI.component(VxeRadio);
|
|||
// VxeUI.component(VxeRadioButton);
|
|||
// VxeUI.component(VxeRadioGroup);
|
|||
VxeUI.component(VxeSelect); |
|||
// VxeUI.component(VxeSwitch);
|
|||
// VxeUI.component(VxeTextarea);
|
|||
VxeUI.component(VxeTooltip); |
|||
|
|||
isInit = true; |
|||
} |
|||
|
|||
export function setupVbenVxeTable(setupOptions: SetupVxeTable) { |
|||
const { configVxeTable, useVbenForm } = setupOptions; |
|||
|
|||
initVxeTable(); |
|||
useTableForm = useVbenForm; |
|||
|
|||
const preference = usePreferences(); |
|||
|
|||
const localMap = { |
|||
'zh-CN': zhCN, |
|||
'en-US': enUS, |
|||
}; |
|||
|
|||
watch( |
|||
[() => preference.theme.value, () => preference.locale.value], |
|||
([theme, locale]) => { |
|||
VxeUI.setTheme(theme === 'dark' ? 'dark' : 'light'); |
|||
VxeUI.setI18n(locale, localMap[locale]); |
|||
VxeUI.setLanguage(locale); |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
}, |
|||
); |
|||
|
|||
configVxeTable(VxeUI); |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
:root { |
|||
--vxe-ui-font-color: hsl(var(--foreground)); |
|||
--vxe-ui-font-primary-color: hsl(var(--primary)); |
|||
|
|||
/* --vxe-ui-font-lighten-color: #babdc0; |
|||
--vxe-ui-font-darken-color: #86898e; */ |
|||
--vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%); |
|||
|
|||
/* base */ |
|||
--vxe-ui-base-popup-border-color: hsl(var(--border)); |
|||
|
|||
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */ |
|||
|
|||
/* layout */ |
|||
--vxe-ui-layout-background-color: hsl(var(--background)); |
|||
--vxe-ui-table-resizable-line-color: hsl(var(--border)); |
|||
|
|||
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent)); |
|||
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */ |
|||
|
|||
/* input */ |
|||
--vxe-ui-input-border-color: hsl(var(--border)); |
|||
|
|||
/* --vxe-ui-input-placeholder-color: #8d9095; */ |
|||
|
|||
/* --vxe-ui-input-disabled-background-color: #262727; */ |
|||
|
|||
/* loading */ |
|||
--vxe-ui-loading-background-color: hsl(var(--overlay-content)); |
|||
|
|||
/* table */ |
|||
--vxe-ui-table-header-background-color: hsl(var(--accent)); |
|||
--vxe-ui-table-border-color: hsl(var(--border)); |
|||
--vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover)); |
|||
--vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%); |
|||
--vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent)); |
|||
--vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent)); |
|||
--vxe-ui-table-row-hover-radio-checked-background-color: hsl( |
|||
var(--accent-hover) |
|||
); |
|||
--vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent)); |
|||
--vxe-ui-table-row-hover-checkbox-checked-background-color: hsl( |
|||
var(--accent-hover) |
|||
); |
|||
--vxe-ui-table-row-current-background-color: hsl(var(--accent)); |
|||
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover)); |
|||
|
|||
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */ |
|||
} |
|||
|
|||
.vxe-pager { |
|||
.vxe-pager--prev-btn:not(.is--disabled):active, |
|||
.vxe-pager--next-btn:not(.is--disabled):active, |
|||
.vxe-pager--num-btn:not(.is--disabled):active, |
|||
.vxe-pager--jump-prev:not(.is--disabled):active, |
|||
.vxe-pager--jump-next:not(.is--disabled):active, |
|||
.vxe-pager--prev-btn:not(.is--disabled):focus, |
|||
.vxe-pager--next-btn:not(.is--disabled):focus, |
|||
.vxe-pager--num-btn:not(.is--disabled):focus, |
|||
.vxe-pager--jump-prev:not(.is--disabled):focus, |
|||
.vxe-pager--jump-next:not(.is--disabled):focus { |
|||
color: hsl(var(--accent-foreground)); |
|||
background-color: hsl(var(--accent)); |
|||
border: 1px solid hsl(var(--border)); |
|||
box-shadow: 0 0 0 1px hsl(var(--border)); |
|||
} |
|||
|
|||
.vxe-pager { |
|||
&--wrapper { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
&--sizes { |
|||
margin-right: auto; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
import type { DeepPartial } from '@vben/types'; |
|||
import type { VbenFormProps } from '@vben-core/form-ui'; |
|||
import type { |
|||
VxeGridListeners, |
|||
VxeGridProps as VxeTableGridProps, |
|||
VxeUIExport, |
|||
} from 'vxe-table'; |
|||
|
|||
import type { VxeGridApi } from './api'; |
|||
|
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { useVbenForm } from '@vben-core/form-ui'; |
|||
|
|||
export interface VxePaginationInfo { |
|||
currentPage: number; |
|||
pageSize: number; |
|||
total: number; |
|||
} |
|||
|
|||
export interface VxeGridProps { |
|||
/** |
|||
* 组件class |
|||
*/ |
|||
class?: any; |
|||
/** |
|||
* vxe-grid class |
|||
*/ |
|||
gridClass?: any; |
|||
/** |
|||
* vxe-grid 配置 |
|||
*/ |
|||
gridOptions?: DeepPartial<VxeTableGridProps>; |
|||
/** |
|||
* vxe-grid 事件 |
|||
*/ |
|||
gridEvents?: DeepPartial<VxeGridListeners>; |
|||
/** |
|||
* 表单配置 |
|||
*/ |
|||
formOptions?: VbenFormProps; |
|||
} |
|||
|
|||
export type ExtendedVxeGridApi = { |
|||
useStore: <T = NoInfer<VxeGridProps>>( |
|||
selector?: (state: NoInfer<VxeGridProps>) => T, |
|||
) => Readonly<Ref<T>>; |
|||
} & VxeGridApi; |
|||
|
|||
export interface SetupVxeTable { |
|||
configVxeTable: (ui: VxeUIExport) => void; |
|||
useVbenForm: typeof useVbenForm; |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
import type { ExtendedVxeGridApi, VxeGridProps } from './types'; |
|||
|
|||
import { defineComponent, h, onBeforeUnmount } from 'vue'; |
|||
|
|||
import { useStore } from '@vben-core/shared/store'; |
|||
|
|||
import { VxeGridApi } from './api'; |
|||
import VxeGrid from './use-vxe-grid.vue'; |
|||
|
|||
export function useVbenVxeGrid(options: VxeGridProps) { |
|||
// const IS_REACTIVE = isReactive(options);
|
|||
const api = new VxeGridApi(options); |
|||
const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi; |
|||
extendedApi.useStore = (selector) => { |
|||
return useStore(api.store, selector); |
|||
}; |
|||
|
|||
const Grid = defineComponent( |
|||
(props: VxeGridProps, { attrs, slots }) => { |
|||
onBeforeUnmount(() => { |
|||
api.unmount(); |
|||
}); |
|||
return () => h(VxeGrid, { ...props, ...attrs, api: extendedApi }, slots); |
|||
}, |
|||
{ |
|||
inheritAttrs: false, |
|||
name: 'VbenVxeGrid', |
|||
}, |
|||
); |
|||
// Add reactivity support
|
|||
// if (IS_REACTIVE) {
|
|||
// watch(
|
|||
// () => options,
|
|||
// () => {
|
|||
// api.setState(options);
|
|||
// },
|
|||
// { immediate: true },
|
|||
// );
|
|||
// }
|
|||
|
|||
return [Grid, extendedApi] as const; |
|||
} |
|||
@ -0,0 +1,264 @@ |
|||
<script lang="ts" setup> |
|||
import type { VbenFormProps } from '@vben-core/form-ui'; |
|||
import type { |
|||
VxeGridInstance, |
|||
VxeGridProps as VxeTableGridProps, |
|||
} from 'vxe-table'; |
|||
|
|||
import type { ExtendedVxeGridApi, VxeGridProps } from './types'; |
|||
|
|||
import { |
|||
computed, |
|||
nextTick, |
|||
onMounted, |
|||
toRaw, |
|||
useSlots, |
|||
useTemplateRef, |
|||
} from 'vue'; |
|||
|
|||
import { usePriorityValues } from '@vben/hooks'; |
|||
import { EmptyIcon } from '@vben/icons'; |
|||
import { $t } from '@vben/locales'; |
|||
import { cloneDeep, cn, mergeWithArrayOverride } from '@vben/utils'; |
|||
import { VbenLoading } from '@vben-core/shadcn-ui'; |
|||
|
|||
import { VxeGrid, VxeUI } from 'vxe-table'; |
|||
|
|||
import { useTableForm } from './init'; |
|||
|
|||
import 'vxe-table/styles/cssvar.scss'; |
|||
import 'vxe-pc-ui/styles/cssvar.scss'; |
|||
import './theme.css'; |
|||
|
|||
interface Props extends VxeGridProps { |
|||
api: ExtendedVxeGridApi; |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), {}); |
|||
|
|||
const gridRef = useTemplateRef<VxeGridInstance>('gridRef'); |
|||
|
|||
const state = props.api?.useStore?.(); |
|||
|
|||
const { |
|||
gridOptions, |
|||
class: className, |
|||
gridClass, |
|||
gridEvents, |
|||
formOptions, |
|||
} = usePriorityValues(props, state); |
|||
|
|||
const slots = useSlots(); |
|||
|
|||
const [Form, formApi] = useTableForm({}); |
|||
|
|||
const showToolbar = computed(() => { |
|||
return !!slots['toolbar-actions']?.() || !!slots['toolbar-tools']?.(); |
|||
}); |
|||
|
|||
const options = computed(() => { |
|||
const slotActions = slots['toolbar-actions']?.(); |
|||
const slotTools = slots['toolbar-tools']?.(); |
|||
|
|||
const forceUseToolbarOptions = showToolbar.value |
|||
? { |
|||
toolbarConfig: { |
|||
slots: { |
|||
...(slotActions ? { buttons: 'toolbar-actions' } : {}), |
|||
...(slotTools ? { tools: 'toolbar-tools' } : {}), |
|||
}, |
|||
}, |
|||
} |
|||
: {}; |
|||
|
|||
const mergedOptions: VxeTableGridProps = cloneDeep( |
|||
mergeWithArrayOverride( |
|||
{}, |
|||
forceUseToolbarOptions, |
|||
toRaw(gridOptions.value), |
|||
), |
|||
); |
|||
|
|||
if (mergedOptions.proxyConfig) { |
|||
const { ajax } = mergedOptions.proxyConfig; |
|||
mergedOptions.proxyConfig.enabled = !!ajax; |
|||
// 不自动加载数据, 由组件控制 |
|||
mergedOptions.proxyConfig.autoLoad = false; |
|||
} |
|||
|
|||
if (!showToolbar.value && mergedOptions.toolbarConfig) { |
|||
mergedOptions.toolbarConfig.enabled = false; |
|||
} |
|||
|
|||
if (mergedOptions.pagerConfig) { |
|||
mergedOptions.pagerConfig = mergeWithArrayOverride( |
|||
{}, |
|||
mergedOptions.pagerConfig, |
|||
{ |
|||
pageSize: 20, |
|||
background: true, |
|||
pageSizes: [10, 20, 30, 50, 100, 200], |
|||
className: 'mt-2 w-full', |
|||
layouts: [ |
|||
'Total', |
|||
'Sizes', |
|||
'Home', |
|||
'PrevJump', |
|||
'PrevPage', |
|||
'Number', |
|||
'NextPage', |
|||
'NextJump', |
|||
'End', |
|||
// 'FullJump', |
|||
] as any[], |
|||
size: 'mini' as const, |
|||
}, |
|||
); |
|||
} |
|||
if (mergedOptions.formConfig) { |
|||
mergedOptions.formConfig.enabled = false; |
|||
} |
|||
return mergedOptions; |
|||
}); |
|||
|
|||
const events = computed(() => { |
|||
return { |
|||
...gridEvents.value, |
|||
}; |
|||
}); |
|||
|
|||
const vbenFormOptions = computed(() => { |
|||
const defaultFormProps: VbenFormProps = { |
|||
handleSubmit: async () => { |
|||
const formValues = formApi.form.values; |
|||
props.api.reload(formValues); |
|||
}, |
|||
handleReset: async () => { |
|||
formApi.resetForm(); |
|||
const formValues = formApi.form.values; |
|||
props.api.reload(formValues); |
|||
}, |
|||
collapseTriggerResize: true, |
|||
commonConfig: { |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
}, |
|||
showCollapseButton: true, |
|||
submitButtonOptions: { |
|||
text: $t('common.query'), |
|||
}, |
|||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', |
|||
}; |
|||
return { |
|||
...mergeWithArrayOverride({}, formOptions.value, defaultFormProps), |
|||
}; |
|||
}); |
|||
|
|||
const delegatedSlots = computed(() => { |
|||
const resultSlots: string[] = []; |
|||
|
|||
for (const key of Object.keys(slots)) { |
|||
if (!['empty', 'form', 'loading'].includes(key)) { |
|||
resultSlots.push(key); |
|||
} |
|||
} |
|||
return resultSlots; |
|||
}); |
|||
|
|||
const delegatedFormSlots = computed(() => { |
|||
const resultSlots: string[] = []; |
|||
|
|||
for (const key of Object.keys(slots)) { |
|||
if (key.startsWith('form-')) { |
|||
resultSlots.push(key); |
|||
} |
|||
} |
|||
return resultSlots; |
|||
}); |
|||
|
|||
async function init() { |
|||
await nextTick(); |
|||
const globalGridConfig = VxeUI?.getConfig()?.grid ?? {}; |
|||
const defaultGridOptions: VxeTableGridProps = mergeWithArrayOverride( |
|||
{}, |
|||
toRaw(gridOptions.value), |
|||
toRaw(globalGridConfig), |
|||
); |
|||
// 内部主动加载数据,防止form的默认值影响 |
|||
const autoLoad = defaultGridOptions.proxyConfig?.autoLoad; |
|||
const enableProxyConfig = options.value.proxyConfig?.enabled; |
|||
if (enableProxyConfig && autoLoad) { |
|||
props.api.reload(formApi.form.values); |
|||
} |
|||
|
|||
// form 由 vben-form代替,所以不适配formConfig,这里给出警告 |
|||
const formConfig = defaultGridOptions.formConfig; |
|||
if (formConfig?.enabled) { |
|||
console.warn( |
|||
'[Vben Vxe Table]: The formConfig in the grid is not supported, please use the `formOptions` props', |
|||
); |
|||
} |
|||
} |
|||
|
|||
onMounted(() => { |
|||
props.api?.mount?.(gridRef.value); |
|||
init(); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div :class="cn('bg-card h-full rounded-md', className)"> |
|||
<VxeGrid |
|||
ref="gridRef" |
|||
:class=" |
|||
cn( |
|||
'p-2', |
|||
{ |
|||
'pt-0': showToolbar && !formOptions, |
|||
}, |
|||
gridClass, |
|||
) |
|||
" |
|||
v-bind="options" |
|||
v-on="events" |
|||
> |
|||
<template |
|||
v-for="slotName in delegatedSlots" |
|||
:key="slotName" |
|||
#[slotName]="slotProps" |
|||
> |
|||
<slot :name="slotName" v-bind="slotProps"></slot> |
|||
</template> |
|||
<template #form> |
|||
<div v-if="formOptions" class="relative rounded py-3 pb-6"> |
|||
<slot name="form"> |
|||
<Form v-bind="vbenFormOptions"> |
|||
<template |
|||
v-for="slotName in delegatedFormSlots" |
|||
:key="slotName" |
|||
#[slotName]="slotProps" |
|||
> |
|||
<slot :name="slotName" v-bind="slotProps"></slot> |
|||
</template> |
|||
</Form> |
|||
</slot> |
|||
<div |
|||
class="bg-background-deep z-100 absolute -left-2 bottom-2 h-4 w-[calc(100%+1rem)] overflow-hidden" |
|||
></div> |
|||
</div> |
|||
</template> |
|||
<template #loading> |
|||
<slot name="loading"> |
|||
<VbenLoading :spinning="true" /> |
|||
</slot> |
|||
</template> |
|||
<template #empty> |
|||
<slot name="empty"> |
|||
<EmptyIcon class="mx-auto" /> |
|||
<div class="mt-2">{{ $t('common.noData') }}</div> |
|||
</slot> |
|||
</template> |
|||
</VxeGrid> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1 @@ |
|||
export { default } from '@vben/tailwind-config'; |
|||
@ -1 +1,2 @@ |
|||
export * from './form'; |
|||
export * from './vxe-table'; |
|||
|
|||
@ -0,0 +1,59 @@ |
|||
import { h } from 'vue'; |
|||
|
|||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; |
|||
|
|||
import { Button, Image } from 'ant-design-vue'; |
|||
|
|||
import { useVbenForm } from './form'; |
|||
|
|||
setupVbenVxeTable({ |
|||
configVxeTable: (vxeUI) => { |
|||
vxeUI.setConfig({ |
|||
grid: { |
|||
align: 'center', |
|||
border: true, |
|||
minHeight: 180, |
|||
proxyConfig: { |
|||
autoLoad: true, |
|||
response: { |
|||
result: 'items', |
|||
total: 'total', |
|||
list: 'items', |
|||
}, |
|||
showActiveMsg: true, |
|||
showResponseMsg: false, |
|||
}, |
|||
round: true, |
|||
size: 'small', |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|||
vxeUI.renderer.add('CellImage', { |
|||
renderDefault(_renderOpts, params) { |
|||
const { column, row } = params; |
|||
return h(Image, { src: row[column.field] }); |
|||
}, |
|||
}); |
|||
|
|||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|||
vxeUI.renderer.add('CellLink', { |
|||
renderDefault(renderOpts) { |
|||
const { props } = renderOpts; |
|||
return h( |
|||
Button, |
|||
{ size: 'small', type: 'link' }, |
|||
{ default: () => props?.text }, |
|||
); |
|||
}, |
|||
}); |
|||
|
|||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|||
// vxeUI.formats.add
|
|||
}, |
|||
useVbenForm, |
|||
}); |
|||
|
|||
export { useVbenVxeGrid }; |
|||
|
|||
export type * from '@vben/plugins/vxe-table'; |
|||
@ -1 +1,2 @@ |
|||
export * from './status'; |
|||
export * from './table'; |
|||
@ -0,0 +1,18 @@ |
|||
import { requestClient } from '#/api/request'; |
|||
|
|||
export namespace DemoTableApi { |
|||
export interface PageFetchParams { |
|||
[key: string]: any; |
|||
page: number; |
|||
pageSize: number; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取示例表格数据 |
|||
*/ |
|||
async function getExampleTableApi(params: DemoTableApi.PageFetchParams) { |
|||
return requestClient.get('/table/list', { params }); |
|||
} |
|||
|
|||
export { getExampleTableApi }; |
|||
@ -1,2 +1,2 @@ |
|||
export * from './core'; |
|||
export * from './demos'; |
|||
export * from './examples'; |
|||
|
|||
@ -0,0 +1,93 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridListeners, VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { Button, message } from 'ant-design-vue'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
|
|||
import DocButton from '../doc-button.vue'; |
|||
import { MOCK_TABLE_DATA } from './table-data'; |
|||
|
|||
interface RowType { |
|||
address: string; |
|||
age: number; |
|||
id: number; |
|||
name: string; |
|||
nickname: string; |
|||
role: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
columns: [ |
|||
{ title: '序号', type: 'seq', width: 50 }, |
|||
{ field: 'name', title: 'Name' }, |
|||
{ field: 'age', sortable: true, title: 'Age' }, |
|||
{ field: 'nickname', title: 'Nickname' }, |
|||
{ field: 'role', title: 'Role' }, |
|||
{ field: 'address', showOverflow: true, title: 'Address' }, |
|||
], |
|||
data: MOCK_TABLE_DATA, |
|||
sortConfig: { |
|||
multiple: true, |
|||
}, |
|||
}; |
|||
|
|||
const gridEvents: VxeGridListeners<RowType> = { |
|||
cellClick: ({ row }) => { |
|||
message.info(`cell-click: ${row.name}`); |
|||
}, |
|||
}; |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions }); |
|||
|
|||
const showBorder = gridApi.useStore((state) => state.gridOptions?.border); |
|||
const showStripe = gridApi.useStore((state) => state.gridOptions?.stripe); |
|||
|
|||
function changeBorder() { |
|||
gridApi.setGridOptions({ |
|||
border: !showBorder.value, |
|||
}); |
|||
} |
|||
|
|||
function changeStripe() { |
|||
gridApi.setGridOptions({ |
|||
stripe: !showStripe.value, |
|||
}); |
|||
} |
|||
|
|||
function changeLoading() { |
|||
gridApi.setLoading(true); |
|||
setTimeout(() => { |
|||
gridApi.setLoading(false); |
|||
}, 2000); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Page |
|||
description="表格组件常用于快速开发数据展示与交互界面,示例数据为静态数据。该组件是对vxe-table进行简单的二次封装,大部分属性与方法与vxe-table保持一致。" |
|||
title="表格基础示例" |
|||
> |
|||
<template #extra> |
|||
<DocButton path="/components/common-ui/vben-vxe-table" /> |
|||
</template> |
|||
<Grid> |
|||
<template #toolbar-actions> |
|||
<Button class="mr-2" type="primary">左右按钮插槽</Button> |
|||
</template> |
|||
<template #toolbar-tools> |
|||
<Button class="mr-2" type="primary" @click="changeBorder"> |
|||
{{ showBorder ? '隐藏' : '显示' }}边框 |
|||
</Button> |
|||
<Button class="mr-2" type="primary" @click="changeLoading"> |
|||
显示loading |
|||
</Button> |
|||
<Button class="mr-2" type="primary" @click="changeStripe"> |
|||
{{ showStripe ? '隐藏' : '显示' }}斑马纹 |
|||
</Button> |
|||
</template> |
|||
</Grid> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,110 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { Button, Image, Switch, Tag } from 'ant-design-vue'; |
|||
import dayjs from 'dayjs'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
import { getExampleTableApi } from '#/api'; |
|||
|
|||
interface RowType { |
|||
category: string; |
|||
color: string; |
|||
id: string; |
|||
imageUrl: string; |
|||
open: boolean; |
|||
price: string; |
|||
productName: string; |
|||
releaseDate: string; |
|||
status: 'error' | 'success' | 'warning'; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
checkboxConfig: { |
|||
highlight: true, |
|||
labelField: 'name', |
|||
}, |
|||
columns: [ |
|||
{ title: '序号', type: 'seq', width: 50 }, |
|||
{ field: 'category', title: 'Category', width: 100 }, |
|||
{ |
|||
field: 'imageUrl', |
|||
slots: { default: 'image-url' }, |
|||
title: 'Image', |
|||
width: 100, |
|||
}, |
|||
{ |
|||
cellRender: { name: 'CellImage' }, |
|||
field: 'imageUrl2', |
|||
title: 'Render Image', |
|||
width: 130, |
|||
}, |
|||
{ |
|||
field: 'open', |
|||
slots: { default: 'open' }, |
|||
title: 'Open', |
|||
width: 100, |
|||
}, |
|||
{ |
|||
field: 'status', |
|||
slots: { default: 'status' }, |
|||
title: 'Status', |
|||
width: 100, |
|||
}, |
|||
{ field: 'color', title: 'Color', width: 100 }, |
|||
{ field: 'productName', title: 'Product Name', width: 200 }, |
|||
{ field: 'price', title: 'Price', width: 100 }, |
|||
{ |
|||
field: 'releaseDate', |
|||
formatter: ({ cellValue }) => { |
|||
return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss'); |
|||
}, |
|||
title: 'Date', |
|||
width: 200, |
|||
}, |
|||
{ |
|||
cellRender: { name: 'CellLink', props: { text: '编辑' } }, |
|||
field: 'action', |
|||
fixed: 'right', |
|||
title: '操作', |
|||
width: 120, |
|||
}, |
|||
], |
|||
height: 'auto', |
|||
keepSource: true, |
|||
pagerConfig: {}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }) => { |
|||
return await getExampleTableApi({ |
|||
page: page.currentPage, |
|||
pageSize: page.pageSize, |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
const [Grid] = useVbenVxeGrid({ gridOptions }); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid> |
|||
<template #image-url="{ row }"> |
|||
<Image :src="row.imageUrl" height="30" width="30" /> |
|||
</template> |
|||
<template #open="{ row }"> |
|||
<Switch v-model:checked="row.open" /> |
|||
</template> |
|||
<template #status="{ row }"> |
|||
<Tag :color="row.color">{{ row.status }}</Tag> |
|||
</template> |
|||
<template #action> |
|||
<Button type="link">编辑</Button> |
|||
</template> |
|||
</Grid> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,57 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
import { getExampleTableApi } from '#/api'; |
|||
|
|||
interface RowType { |
|||
category: string; |
|||
color: string; |
|||
id: string; |
|||
price: string; |
|||
productName: string; |
|||
releaseDate: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
columns: [ |
|||
{ title: '序号', type: 'seq', width: 50 }, |
|||
{ editRender: { name: 'input' }, field: 'category', title: 'Category' }, |
|||
{ editRender: { name: 'input' }, field: 'color', title: 'Color' }, |
|||
{ |
|||
editRender: { name: 'input' }, |
|||
field: 'productName', |
|||
title: 'Product Name', |
|||
}, |
|||
{ field: 'price', title: 'Price' }, |
|||
{ field: 'releaseDate', title: 'Date' }, |
|||
], |
|||
editConfig: { |
|||
mode: 'cell', |
|||
trigger: 'click', |
|||
}, |
|||
height: 'auto', |
|||
pagerConfig: {}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }) => { |
|||
return await getExampleTableApi({ |
|||
page: page.currentPage, |
|||
pageSize: page.pageSize, |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
showOverflow: true, |
|||
}; |
|||
|
|||
const [Grid] = useVbenVxeGrid({ gridOptions }); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid /> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,94 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { Button, message } from 'ant-design-vue'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
import { getExampleTableApi } from '#/api'; |
|||
|
|||
interface RowType { |
|||
category: string; |
|||
color: string; |
|||
id: string; |
|||
price: string; |
|||
productName: string; |
|||
releaseDate: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
columns: [ |
|||
{ title: '序号', type: 'seq', width: 50 }, |
|||
{ editRender: { name: 'input' }, field: 'category', title: 'Category' }, |
|||
{ editRender: { name: 'input' }, field: 'color', title: 'Color' }, |
|||
{ |
|||
editRender: { name: 'input' }, |
|||
field: 'productName', |
|||
title: 'Product Name', |
|||
}, |
|||
{ field: 'price', title: 'Price' }, |
|||
{ field: 'releaseDate', title: 'Date' }, |
|||
{ slots: { default: 'action' }, title: '操作' }, |
|||
], |
|||
editConfig: { |
|||
mode: 'row', |
|||
trigger: 'click', |
|||
}, |
|||
height: 'auto', |
|||
pagerConfig: {}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }) => { |
|||
return await getExampleTableApi({ |
|||
page: page.currentPage, |
|||
pageSize: page.pageSize, |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
showOverflow: true, |
|||
}; |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions }); |
|||
|
|||
function hasEditStatus(row: RowType) { |
|||
return gridApi.grid?.isEditByRow(row); |
|||
} |
|||
|
|||
function editRowEvent(row: RowType) { |
|||
gridApi.grid?.setEditRow(row); |
|||
} |
|||
|
|||
async function saveRowEvent(row: RowType) { |
|||
await gridApi.grid?.clearEdit(); |
|||
|
|||
gridApi.setLoading(true); |
|||
setTimeout(() => { |
|||
gridApi.setLoading(false); |
|||
message.success({ |
|||
content: `保存成功!category=${row.category}`, |
|||
}); |
|||
}, 600); |
|||
} |
|||
|
|||
const cancelRowEvent = (_row: RowType) => { |
|||
gridApi.grid?.clearEdit(); |
|||
}; |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid> |
|||
<template #action="{ row }"> |
|||
<template v-if="hasEditStatus(row)"> |
|||
<Button type="link" @click="saveRowEvent(row)">保存</Button> |
|||
<Button type="link" @click="cancelRowEvent(row)">取消</Button> |
|||
</template> |
|||
<template v-else> |
|||
<Button type="link" @click="editRowEvent(row)">编辑</Button> |
|||
</template> |
|||
</template> |
|||
</Grid> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,64 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { Button } from 'ant-design-vue'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
import { getExampleTableApi } from '#/api'; |
|||
|
|||
interface RowType { |
|||
category: string; |
|||
color: string; |
|||
id: string; |
|||
price: string; |
|||
productName: string; |
|||
releaseDate: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
columns: [ |
|||
{ fixed: 'left', title: '序号', type: 'seq', width: 50 }, |
|||
{ field: 'category', title: 'Category', width: 300 }, |
|||
{ field: 'color', title: 'Color', width: 300 }, |
|||
{ field: 'productName', title: 'Product Name', width: 300 }, |
|||
{ field: 'price', title: 'Price', width: 300 }, |
|||
{ field: 'releaseDate', title: 'Date', width: 500 }, |
|||
{ |
|||
field: 'action', |
|||
fixed: 'right', |
|||
slots: { default: 'action' }, |
|||
title: '操作', |
|||
width: 120, |
|||
}, |
|||
], |
|||
height: 'auto', |
|||
pagerConfig: {}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }) => { |
|||
return await getExampleTableApi({ |
|||
page: page.currentPage, |
|||
pageSize: page.pageSize, |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
rowConfig: { |
|||
isHover: true, |
|||
}, |
|||
}; |
|||
|
|||
const [Grid] = useVbenVxeGrid({ gridOptions }); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid> |
|||
<template #action> |
|||
<Button type="link">编辑</Button> |
|||
</template> |
|||
</Grid> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,102 @@ |
|||
<script lang="ts" setup> |
|||
import type { VbenFormProps, VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
import { getExampleTableApi } from '#/api'; |
|||
|
|||
interface RowType { |
|||
category: string; |
|||
color: string; |
|||
id: string; |
|||
price: string; |
|||
productName: string; |
|||
releaseDate: string; |
|||
} |
|||
|
|||
const formOptions: VbenFormProps = { |
|||
schema: [ |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'category', |
|||
label: 'Category', |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'productName', |
|||
label: 'ProductName', |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'price', |
|||
label: 'Price', |
|||
}, |
|||
{ |
|||
component: 'Select', |
|||
componentProps: { |
|||
allowClear: true, |
|||
options: [ |
|||
{ |
|||
label: 'Color1', |
|||
value: '1', |
|||
}, |
|||
{ |
|||
label: 'Color2', |
|||
value: '2', |
|||
}, |
|||
], |
|||
placeholder: '请选择', |
|||
}, |
|||
fieldName: 'color', |
|||
label: 'Color', |
|||
}, |
|||
{ |
|||
component: 'DatePicker', |
|||
fieldName: 'datePicker', |
|||
label: 'Date', |
|||
}, |
|||
], |
|||
}; |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
checkboxConfig: { |
|||
highlight: true, |
|||
labelField: 'name', |
|||
}, |
|||
columns: [ |
|||
{ title: '序号', type: 'seq', width: 50 }, |
|||
{ align: 'left', title: 'Name', type: 'checkbox', width: 100 }, |
|||
{ field: 'category', title: 'Category' }, |
|||
{ field: 'color', title: 'Color' }, |
|||
{ field: 'productName', title: 'Product Name' }, |
|||
{ field: 'price', title: 'Price' }, |
|||
{ field: 'releaseDate', title: 'Date' }, |
|||
], |
|||
height: 'auto', |
|||
keepSource: true, |
|||
pagerConfig: {}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }, formValues) => { |
|||
message.success(`Query params: ${JSON.stringify(formValues)}`); |
|||
return await getExampleTableApi({ |
|||
page: page.currentPage, |
|||
pageSize: page.pageSize, |
|||
...formValues, |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions }); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid /> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,65 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { Button } from 'ant-design-vue'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
import { getExampleTableApi } from '#/api'; |
|||
|
|||
interface RowType { |
|||
category: string; |
|||
color: string; |
|||
id: string; |
|||
price: string; |
|||
productName: string; |
|||
releaseDate: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
checkboxConfig: { |
|||
highlight: true, |
|||
labelField: 'name', |
|||
}, |
|||
columns: [ |
|||
{ title: '序号', type: 'seq', width: 50 }, |
|||
{ align: 'left', title: 'Name', type: 'checkbox', width: 100 }, |
|||
{ field: 'category', title: 'Category' }, |
|||
{ field: 'color', title: 'Color' }, |
|||
{ field: 'productName', title: 'Product Name' }, |
|||
{ field: 'price', title: 'Price' }, |
|||
{ field: 'releaseDate', title: 'Date' }, |
|||
], |
|||
height: 'auto', |
|||
keepSource: true, |
|||
pagerConfig: {}, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }) => { |
|||
return await getExampleTableApi({ |
|||
page: page.currentPage, |
|||
pageSize: page.pageSize, |
|||
}); |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions }); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid> |
|||
<template #toolbar-tools> |
|||
<Button class="mr-2" type="primary" @click="() => gridApi.query()"> |
|||
刷新当前页面 |
|||
</Button> |
|||
<Button type="primary" @click="() => gridApi.reload()"> |
|||
刷新并返回第一页 |
|||
</Button> |
|||
</template> |
|||
</Grid> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,172 @@ |
|||
interface TableRowData { |
|||
address: string; |
|||
age: number; |
|||
id: number; |
|||
name: string; |
|||
nickname: string; |
|||
role: string; |
|||
} |
|||
|
|||
const roles = ['User', 'Admin', 'Manager', 'Guest']; |
|||
|
|||
export const MOCK_TABLE_DATA: TableRowData[] = (() => { |
|||
const data: TableRowData[] = []; |
|||
for (let i = 0; i < 40; i++) { |
|||
data.push({ |
|||
address: `New York${i}`, |
|||
age: i + 1, |
|||
id: i, |
|||
name: `Test${i}`, |
|||
nickname: `Test${i}`, |
|||
role: roles[Math.floor(Math.random() * roles.length)] as string, |
|||
}); |
|||
} |
|||
return data; |
|||
})(); |
|||
|
|||
export const MOCK_TREE_TABLE_DATA = [ |
|||
{ |
|||
date: '2020-08-01', |
|||
id: 10_000, |
|||
name: 'Test1', |
|||
parentId: null, |
|||
size: 1024, |
|||
type: 'mp3', |
|||
}, |
|||
{ |
|||
date: '2021-04-01', |
|||
id: 10_050, |
|||
name: 'Test2', |
|||
parentId: null, |
|||
size: 0, |
|||
type: 'mp4', |
|||
}, |
|||
{ |
|||
date: '2020-03-01', |
|||
id: 24_300, |
|||
name: 'Test3', |
|||
parentId: 10_050, |
|||
size: 1024, |
|||
type: 'avi', |
|||
}, |
|||
{ |
|||
date: '2021-04-01', |
|||
id: 20_045, |
|||
name: 'Test4', |
|||
parentId: 24_300, |
|||
size: 600, |
|||
type: 'html', |
|||
}, |
|||
{ |
|||
date: '2021-04-01', |
|||
id: 10_053, |
|||
name: 'Test5', |
|||
parentId: 24_300, |
|||
size: 0, |
|||
type: 'avi', |
|||
}, |
|||
{ |
|||
date: '2021-10-01', |
|||
id: 24_330, |
|||
name: 'Test6', |
|||
parentId: 10_053, |
|||
size: 25, |
|||
type: 'txt', |
|||
}, |
|||
{ |
|||
date: '2020-01-01', |
|||
id: 21_011, |
|||
name: 'Test7', |
|||
parentId: 10_053, |
|||
size: 512, |
|||
type: 'pdf', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 22_200, |
|||
name: 'Test8', |
|||
parentId: 10_053, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2020-11-01', |
|||
id: 23_666, |
|||
name: 'Test9', |
|||
parentId: null, |
|||
size: 2048, |
|||
type: 'xlsx', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 23_677, |
|||
name: 'Test10', |
|||
parentId: 23_666, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 23_671, |
|||
name: 'Test11', |
|||
parentId: 23_677, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 23_672, |
|||
name: 'Test12', |
|||
parentId: 23_677, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 23_688, |
|||
name: 'Test13', |
|||
parentId: 23_666, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 23_681, |
|||
name: 'Test14', |
|||
parentId: 23_688, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 23_682, |
|||
name: 'Test15', |
|||
parentId: 23_688, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2020-10-01', |
|||
id: 24_555, |
|||
name: 'Test16', |
|||
parentId: null, |
|||
size: 224, |
|||
type: 'avi', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 24_566, |
|||
name: 'Test17', |
|||
parentId: 24_555, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
{ |
|||
date: '2021-06-01', |
|||
id: 24_577, |
|||
name: 'Test18', |
|||
parentId: 24_555, |
|||
size: 1024, |
|||
type: 'js', |
|||
}, |
|||
]; |
|||
@ -0,0 +1,59 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { Button } from 'ant-design-vue'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
|
|||
import { MOCK_TREE_TABLE_DATA } from './table-data'; |
|||
|
|||
interface RowType { |
|||
date: string; |
|||
id: number; |
|||
name: string; |
|||
parentId: null | number; |
|||
size: number; |
|||
type: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
columns: [ |
|||
{ type: 'seq', width: 70 }, |
|||
{ field: 'name', minWidth: 300, title: 'Name', treeNode: true }, |
|||
{ field: 'size', title: 'Size' }, |
|||
{ field: 'type', title: 'Type' }, |
|||
{ field: 'date', title: 'Date' }, |
|||
], |
|||
data: MOCK_TREE_TABLE_DATA, |
|||
treeConfig: { |
|||
parentField: 'parentId', |
|||
rowField: 'id', |
|||
transform: true, |
|||
}, |
|||
}; |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions }); |
|||
|
|||
const expandAll = () => { |
|||
gridApi.grid?.setAllTreeExpand(true); |
|||
}; |
|||
|
|||
const collapseAll = () => { |
|||
gridApi.grid?.setAllTreeExpand(false); |
|||
}; |
|||
</script> |
|||
|
|||
<template> |
|||
<Page> |
|||
<Grid> |
|||
<template #toolbar-tools> |
|||
<Button class="mr-2" type="primary" @click="expandAll"> |
|||
展开全部 |
|||
</Button> |
|||
<Button type="primary" @click="collapseAll"> 折叠全部 </Button> |
|||
</template> |
|||
</Grid> |
|||
</Page> |
|||
</template> |
|||
@ -0,0 +1,63 @@ |
|||
<script lang="ts" setup> |
|||
import type { VxeGridProps } from '#/adapter'; |
|||
|
|||
import { onMounted } from 'vue'; |
|||
|
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { useVbenVxeGrid } from '#/adapter'; |
|||
|
|||
interface RowType { |
|||
id: number; |
|||
name: string; |
|||
role: string; |
|||
sex: string; |
|||
} |
|||
|
|||
const gridOptions: VxeGridProps<RowType> = { |
|||
columns: [ |
|||
{ type: 'seq', width: 70 }, |
|||
{ field: 'name', title: 'Name' }, |
|||
{ field: 'role', title: 'Role' }, |
|||
{ field: 'sex', title: 'Sex' }, |
|||
], |
|||
data: [], |
|||
height: 'auto', |
|||
scrollY: { |
|||
enabled: true, |
|||
gt: 0, |
|||
}, |
|||
showOverflow: true, |
|||
}; |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions }); |
|||
|
|||
// 模拟行数据 |
|||
const loadList = (size = 200) => { |
|||
try { |
|||
const dataList: RowType[] = []; |
|||
for (let i = 0; i < size; i++) { |
|||
dataList.push({ |
|||
id: 10_000 + i, |
|||
name: `Test${i}`, |
|||
role: 'Developer', |
|||
sex: '男', |
|||
}); |
|||
} |
|||
gridApi.setGridOptions({ data: dataList }); |
|||
} catch (error) { |
|||
console.error('Failed to load data:', error); |
|||
// Implement user-friendly error handling |
|||
} |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
loadList(1000); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page auto-content-height> |
|||
<Grid /> |
|||
</Page> |
|||
</template> |
|||
Loading…
Reference in new issue