diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts
index ad52d654..aa6133e1 100644
--- a/packages/form/src/index.ts
+++ b/packages/form/src/index.ts
@@ -24,7 +24,6 @@ import GroupList from './containers/GroupList.vue';
import Panel from './containers/Panel.vue';
import Row from './containers/Row.vue';
import MStep from './containers/Step.vue';
-import Table from './containers/Table.vue';
import Tabs from './containers/Tabs.vue';
import Cascader from './fields/Cascader.vue';
import Checkbox from './fields/Checkbox.vue';
@@ -46,6 +45,7 @@ import Text from './fields/Text.vue';
import Textarea from './fields/Textarea.vue';
import Time from './fields/Time.vue';
import Timerange from './fields/Timerange.vue';
+import Table from './table/Table.vue';
import { setConfig } from './utils/config';
import Form from './Form.vue';
import FormDialog from './FormDialog.vue';
@@ -66,7 +66,7 @@ export { default as MFieldset } from './containers/Fieldset.vue';
export { default as MPanel } from './containers/Panel.vue';
export { default as MRow } from './containers/Row.vue';
export { default as MTabs } from './containers/Tabs.vue';
-export { default as MTable } from './containers/Table.vue';
+export { default as MTable } from './table/Table.vue';
export { default as MGroupList } from './containers/GroupList.vue';
export { default as MText } from './fields/Text.vue';
export { default as MNumber } from './fields/Number.vue';
diff --git a/packages/form/src/table/ActionsColumn.vue b/packages/form/src/table/ActionsColumn.vue
new file mode 100644
index 00000000..94a72bdc
--- /dev/null
+++ b/packages/form/src/table/ActionsColumn.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
diff --git a/packages/form/src/table/SortColumn.vue b/packages/form/src/table/SortColumn.vue
new file mode 100644
index 00000000..4776438e
--- /dev/null
+++ b/packages/form/src/table/SortColumn.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/form/src/table/Table.vue b/packages/form/src/table/Table.vue
new file mode 100644
index 00000000..2c17a880
--- /dev/null
+++ b/packages/form/src/table/Table.vue
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
新增一行
+
+
+ 展开配置
+
+ {{ isFullscreen ? '退出全屏' : '全屏编辑' }}
+
+
+ 导入EXCEL
+
+ 清空
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/form/src/table/type.ts b/packages/form/src/table/type.ts
new file mode 100644
index 00000000..a870dc69
--- /dev/null
+++ b/packages/form/src/table/type.ts
@@ -0,0 +1,18 @@
+import type { TableConfig } from '@tmagic/form-schema';
+
+export interface TableProps {
+ model: any;
+ lastValues?: any;
+ isCompare?: boolean;
+ config: TableConfig;
+ name: string;
+ prop?: string;
+ labelWidth?: string;
+ sort?: boolean;
+ disabled?: boolean;
+ sortKey?: string;
+ text?: string;
+ size?: string;
+ enableToggleMode?: boolean;
+ showIndex?: boolean;
+}
diff --git a/packages/form/src/table/useAdd.ts b/packages/form/src/table/useAdd.ts
new file mode 100644
index 00000000..059647bc
--- /dev/null
+++ b/packages/form/src/table/useAdd.ts
@@ -0,0 +1,111 @@
+import { computed, inject } from 'vue';
+
+import { tMagicMessage } from '@tmagic/design';
+import type { FormState } from '@tmagic/form-schema';
+
+import { initValue } from '../utils/form';
+
+import type { TableProps } from './type';
+
+export const useAdd = (
+ props: TableProps,
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
+) => {
+ const mForm = inject
('mForm');
+
+ const addable = computed(() => {
+ const modelName = props.name || props.config.name || '';
+ if (!props.model[modelName].length) {
+ return true;
+ }
+ if (typeof props.config.addable === 'function') {
+ return props.config.addable(mForm, {
+ model: props.model[modelName],
+ formValue: mForm?.values,
+ prop: props.prop,
+ });
+ }
+ return typeof props.config.addable === 'undefined' ? true : props.config.addable;
+ });
+
+ const newHandler = async (row?: any) => {
+ const modelName = props.name || props.config.name || '';
+
+ if (props.config.max && props.model[modelName].length >= props.config.max) {
+ tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
+ return;
+ }
+
+ if (typeof props.config.beforeAddRow === 'function') {
+ const beforeCheckRes = props.config.beforeAddRow(mForm, {
+ model: props.model[modelName],
+ formValue: mForm?.values,
+ prop: props.prop,
+ });
+ if (!beforeCheckRes) return;
+ }
+
+ const columns = props.config.items;
+ const enumValues = props.config.enum || [];
+ let enumV = [];
+ const { length } = props.model[modelName];
+ const key = props.config.key || 'id';
+ let inputs: any = {};
+
+ if (enumValues.length) {
+ if (length >= enumValues.length) {
+ return;
+ }
+ enumV = enumValues.filter((item) => {
+ let i = 0;
+ for (; i < length; i++) {
+ if (item[key] === props.model[modelName][i][key]) {
+ break;
+ }
+ }
+ return i === length;
+ });
+
+ if (enumV.length > 0) {
+ // eslint-disable-next-line prefer-destructuring
+ inputs = enumV[0];
+ }
+ } else if (Array.isArray(row)) {
+ columns.forEach((column, index) => {
+ column.name && (inputs[column.name] = row[index]);
+ });
+ } else {
+ if (typeof props.config.defaultAdd === 'function') {
+ inputs = await props.config.defaultAdd(mForm, {
+ model: props.model[modelName],
+ formValue: mForm?.values,
+ });
+ } else if (props.config.defaultAdd) {
+ inputs = props.config.defaultAdd;
+ }
+
+ inputs = await initValue(mForm, {
+ config: columns,
+ initValues: inputs,
+ });
+ }
+
+ if (props.sortKey && length) {
+ inputs[props.sortKey] = props.model[modelName][length - 1][props.sortKey] - 1;
+ }
+
+ emit('change', [...props.model[modelName], inputs], {
+ changeRecords: [
+ {
+ propPath: `${props.prop}.${props.model[modelName].length}`,
+ value: inputs,
+ },
+ ],
+ });
+ };
+
+ return {
+ addable,
+ newHandler,
+ };
+};
diff --git a/packages/form/src/table/useFullscreen.ts b/packages/form/src/table/useFullscreen.ts
new file mode 100644
index 00000000..4bebe190
--- /dev/null
+++ b/packages/form/src/table/useFullscreen.ts
@@ -0,0 +1,28 @@
+import { ref, useTemplateRef } from 'vue';
+
+import { useZIndex } from '@tmagic/design';
+
+export const useFullscreen = () => {
+ const isFullscreen = ref(false);
+ const mTableEl = useTemplateRef('mTable');
+
+ const { nextZIndex } = useZIndex();
+
+ const toggleFullscreen = () => {
+ if (!mTableEl.value) return;
+
+ if (isFullscreen.value) {
+ mTableEl.value.classList.remove('fixed');
+ isFullscreen.value = false;
+ } else {
+ mTableEl.value.classList.add('fixed');
+ mTableEl.value.style.zIndex = `${nextZIndex()}`;
+ isFullscreen.value = true;
+ }
+ };
+
+ return {
+ isFullscreen,
+ toggleFullscreen,
+ };
+};
diff --git a/packages/form/src/table/useImport.ts b/packages/form/src/table/useImport.ts
new file mode 100644
index 00000000..35987e49
--- /dev/null
+++ b/packages/form/src/table/useImport.ts
@@ -0,0 +1,68 @@
+import { computed, inject, useTemplateRef } from 'vue';
+
+import type { TMagicUpload } from '@tmagic/design';
+import type { FormState } from '@tmagic/form-schema';
+import { asyncLoadJs } from '@tmagic/utils';
+
+import type { TableProps } from './type';
+
+export const useImport = (
+ props: TableProps,
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
+ newHandler: (row: any) => void,
+) => {
+ const mForm = inject('mForm');
+ const modelName = computed(() => props.name || props.config.name || '');
+ const importable = computed(() => {
+ if (typeof props.config.importable === 'function') {
+ return props.config.importable(mForm, {
+ formValue: mForm?.values,
+ model: props.model[modelName.value],
+ });
+ }
+ return typeof props.config.importable === 'undefined' ? false : props.config.importable;
+ });
+
+ const excelBtn = useTemplateRef>('excelBtn');
+
+ const excelHandler = async (file: any) => {
+ if (!file?.raw) {
+ return false;
+ }
+
+ if (!(globalThis as any).XLSX) {
+ await asyncLoadJs('https://cdn.bootcdn.net/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
+ }
+
+ const reader = new FileReader();
+ reader.onload = () => {
+ const data = reader.result;
+ const pdata = (globalThis as any).XLSX.read(data, { type: 'array' });
+ pdata.SheetNames.forEach((sheetName: string) => {
+ const arr = (globalThis as any).XLSX.utils.sheet_to_json(pdata.Sheets[sheetName], { header: 1 });
+ if (arr?.[0]) {
+ arr.forEach((row: any) => {
+ newHandler(row);
+ });
+ }
+ setTimeout(() => {
+ excelBtn.value?.clearFiles();
+ }, 300);
+ });
+ };
+ reader.readAsArrayBuffer(file.raw);
+
+ return false;
+ };
+
+ const clearHandler = () => {
+ emit('change', []);
+ mForm?.$emit('field-change', props.prop, props.model[modelName.value]);
+ };
+
+ return {
+ importable,
+ excelHandler,
+ clearHandler,
+ };
+};
diff --git a/packages/form/src/table/usePagination.ts b/packages/form/src/table/usePagination.ts
new file mode 100644
index 00000000..39d9619b
--- /dev/null
+++ b/packages/form/src/table/usePagination.ts
@@ -0,0 +1,30 @@
+import { computed, type Ref, ref } from 'vue';
+
+import { getDataByPage } from '../utils/form';
+
+import type { TableProps } from './type';
+
+export const usePagination = (props: TableProps, modelName: Ref) => {
+ const pageSize = ref(10);
+ /**
+ * 当前页码
+ */
+ const currentPage = ref(0);
+
+ const paginationData = computed(() => getDataByPage(props.model[modelName.value], currentPage.value, pageSize.value));
+ const handleSizeChange = (val: number) => {
+ pageSize.value = val;
+ };
+
+ const handleCurrentChange = (val: number) => {
+ currentPage.value = val - 1;
+ };
+
+ return {
+ pageSize,
+ currentPage,
+ paginationData,
+ handleSizeChange,
+ handleCurrentChange,
+ };
+};
diff --git a/packages/form/src/table/useSelection.ts b/packages/form/src/table/useSelection.ts
new file mode 100644
index 00000000..b820fe1a
--- /dev/null
+++ b/packages/form/src/table/useSelection.ts
@@ -0,0 +1,34 @@
+import { inject, type ShallowRef } from 'vue';
+
+import type { TMagicTable } from '@tmagic/design';
+import type { FormState } from '@tmagic/form-schema';
+
+import type { TableProps } from './type';
+
+export const useSelection = (
+ props: TableProps,
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
+ tMagicTableRef: ShallowRef | null>,
+) => {
+ const mForm = inject('mForm');
+
+ const selectHandle = (selection: any, row: any) => {
+ if (typeof props.config.selection === 'string' && props.config.selection === 'single') {
+ tMagicTableRef.value?.clearSelection();
+ tMagicTableRef.value?.toggleRowSelection(row, true);
+ }
+ emit('select', selection, row);
+ if (typeof props.config.onSelect === 'function') {
+ props.config.onSelect(mForm, { selection, row, config: props.config });
+ }
+ };
+
+ const toggleRowSelection = (row: any, selected: boolean) => {
+ tMagicTableRef.value?.toggleRowSelection.call(tMagicTableRef.value?.getTableRef(), row, selected);
+ };
+
+ return {
+ selectHandle,
+ toggleRowSelection,
+ };
+};
diff --git a/packages/form/src/table/useSortable.ts b/packages/form/src/table/useSortable.ts
new file mode 100644
index 00000000..10aa5761
--- /dev/null
+++ b/packages/form/src/table/useSortable.ts
@@ -0,0 +1,48 @@
+import { inject, type Ref, type ShallowRef, watchEffect } from 'vue';
+import Sortable, { type SortableEvent } from 'sortablejs';
+
+import { type TMagicTable } from '@tmagic/design';
+import type { FormState } from '@tmagic/form-schema';
+
+import { sortArray } from '../utils/form';
+
+import type { TableProps } from './type';
+
+export const useSortable = (
+ props: TableProps,
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
+ tMagicTableRef: ShallowRef | null>,
+ modelName: Ref,
+) => {
+ const mForm = inject('mForm');
+
+ let sortable: Sortable | undefined;
+ const rowDrop = () => {
+ sortable?.destroy();
+ const tableEl = tMagicTableRef.value?.getEl();
+ const tBodyEl = tableEl?.querySelector('.el-table__body > tbody');
+ if (!tBodyEl) {
+ return;
+ }
+ sortable = Sortable.create(tBodyEl, {
+ draggable: '.tmagic-design-table-row',
+ filter: 'input', // 表单组件选字操作和触发拖拽会冲突,优先保证选字操作
+ preventOnFilter: false, // 允许选字
+ direction: 'vertical',
+ onEnd: ({ newIndex, oldIndex }: SortableEvent) => {
+ if (typeof newIndex === 'undefined') return;
+ if (typeof oldIndex === 'undefined') return;
+ const newData = sortArray(props.model[modelName.value], newIndex, oldIndex, props.sortKey);
+
+ emit('change', newData);
+ mForm?.$emit('field-change', newData);
+ },
+ });
+ };
+
+ watchEffect(() => {
+ if (props.config.dropSort) {
+ rowDrop();
+ }
+ });
+};
diff --git a/packages/form/src/table/useTableColumns.ts b/packages/form/src/table/useTableColumns.ts
new file mode 100644
index 00000000..d3b63880
--- /dev/null
+++ b/packages/form/src/table/useTableColumns.ts
@@ -0,0 +1,193 @@
+import { computed, h, inject, type Ref } from 'vue';
+import { cloneDeep } from 'lodash-es';
+
+import type { TableColumnOptions } from '@tmagic/design';
+import type { FormState, TableColumnConfig } from '@tmagic/form-schema';
+
+import Container from '../containers/Container.vue';
+import type { ContainerChangeEventData } from '../schema';
+import { display as displayFunc, getDataByPage, sortArray } from '../utils/form';
+
+import ActionsColumn from './ActionsColumn.vue';
+import SortColumn from './SortColumn.vue';
+import type { TableProps } from './type';
+
+export const useTableColumns = (
+ props: TableProps,
+ emit: (event: 'select' | 'change' | 'addDiffCount', ...args: any[]) => void,
+ currentPage: Ref,
+ pageSize: Ref,
+ modelName: Ref,
+) => {
+ const mForm = inject('mForm');
+
+ const display = (fuc: any) => displayFunc(mForm, fuc, props);
+
+ const lastData = computed(() =>
+ props.config.pagination
+ ? getDataByPage(props.lastValues[modelName.value], currentPage.value, pageSize.value)
+ : props.lastValues[modelName.value] || [],
+ );
+
+ const itemExtra = (fuc: any, index: number) => {
+ if (typeof fuc === 'function') {
+ return fuc(mForm, {
+ values: mForm?.initValues,
+ model: props.model,
+ formValue: mForm ? mForm.values : props.model,
+ prop: props.prop,
+ index,
+ });
+ }
+
+ return fuc;
+ };
+
+ const selection = computed(() => {
+ if (typeof props.config.selection === 'function') {
+ return props.config.selection(mForm, { model: props.model[modelName.value] });
+ }
+ return props.config.selection;
+ });
+
+ const getProp = (index: number) => {
+ return `${props.prop}${props.prop ? '.' : ''}${index + 1 + currentPage.value * pageSize.value - 1}`;
+ };
+
+ const makeConfig = (config: TableColumnConfig, row: any) => {
+ const newConfig = cloneDeep(config);
+ if (typeof config.itemsFunction === 'function') {
+ newConfig.items = config.itemsFunction(row);
+ }
+ delete newConfig.display;
+ return newConfig;
+ };
+
+ const changeHandler = (v: any, eventData: ContainerChangeEventData) => {
+ emit('change', props.model, eventData);
+ };
+
+ const onAddDiffCount = () => emit('addDiffCount');
+
+ const columns = computed(() => {
+ const columns: TableColumnOptions[] = [];
+
+ if (props.config.itemExtra && !props.config.dropSort) {
+ columns.push({
+ props: {
+ fixed: 'left',
+ width: 30,
+ type: 'expand',
+ },
+ cell: ({ $index }: any) =>
+ h('span', {
+ innerHTML: itemExtra(props.config.itemExtra, $index),
+ class: 'm-form-tip',
+ }),
+ });
+ }
+
+ columns.push({
+ props: {
+ label: '操作',
+ fixed: props.config.fixed === false ? undefined : 'left',
+ width: props.config.operateColWidth || 100,
+ align: 'center',
+ },
+ cell: ({ row, $index }: any) =>
+ h(ActionsColumn, {
+ row,
+ index: $index,
+ model: props.model,
+ config: props.config,
+ prop: props.prop,
+ disabled: props.disabled,
+ name: modelName.value,
+ currentPage: currentPage.value,
+ pageSize: pageSize.value,
+ onChange: (v: any) => {
+ emit('change', v);
+ },
+ }),
+ });
+
+ if (props.sort && props.model[modelName.value] && props.model[modelName.value].length > 1) {
+ columns.push({
+ props: {
+ label: '排序',
+ width: 80,
+ },
+ cell: ({ $index }: any) =>
+ h(SortColumn, {
+ index: $index,
+ model: props.model,
+ disabled: props.disabled,
+ name: modelName.value,
+ currentPage: currentPage.value,
+ pageSize: pageSize.value,
+ onSwap: (index1: number, index2: number) => {
+ const newData = sortArray(props.model[modelName.value], index1, index2, props.sortKey);
+ emit('change', newData);
+ mForm?.$emit('field-change', newData);
+ },
+ }),
+ });
+ }
+
+ if (selection.value) {
+ columns.push({
+ props: {
+ align: 'center',
+ headerAlign: 'center',
+ type: 'selection',
+ width: 45,
+ },
+ });
+ }
+
+ if (props.showIndex && props.config.showIndex) {
+ columns.push({
+ props: {
+ label: '序号',
+ width: 60,
+ },
+ cell: ({ $index }: any) => h('span', $index + 1 + currentPage.value * pageSize.value),
+ });
+ }
+
+ for (const column of props.config.items) {
+ if (column.type !== 'hidden' && display(column.display)) {
+ columns.push({
+ props: {
+ prop: column.name,
+ label: column.label,
+ width: column.width,
+ sortable: column.sortable,
+ sortOrders: ['ascending', 'descending'],
+ class: props.config.dropSort === true ? 'el-table__column--dropable' : '',
+ },
+ cell: ({ row, $index }: any) =>
+ h(Container, {
+ labelWidth: '0',
+ disabled: props.disabled,
+ prop: getProp($index),
+ rules: column.rules,
+ config: makeConfig(column, row),
+ model: row,
+ lastValues: lastData.value[$index],
+ isCompare: props.isCompare,
+ size: props.size,
+ onChange: changeHandler,
+ onAddDiffCount,
+ }),
+ });
+ }
+ }
+
+ return columns;
+ });
+
+ return {
+ columns,
+ };
+};
diff --git a/packages/form/src/theme/form.scss b/packages/form/src/theme/form.scss
index eade271b..a456a44d 100644
--- a/packages/form/src/theme/form.scss
+++ b/packages/form/src/theme/form.scss
@@ -19,17 +19,21 @@
height: 100%;
}
- .el-table {
+ .tmagic-design-table {
.cell > div.m-form-container {
display: block;
+
+ &.has-tip {
+ display: flex;
+ }
}
}
- .el-tabs {
+ .tmagic-design-tabs {
margin-bottom: 10px;
}
- .el-form-item.tmagic-form-hidden {
+ .tmagic-design-form-item.tmagic-form-hidden {
> .el-form-item__label {
display: none;
}
@@ -39,5 +43,9 @@
> .t-form__label {
display: none;
}
+
+ > .t-form__controls {
+ margin-left: 0 !important;
+ }
}
}
diff --git a/packages/form/src/utils/form.ts b/packages/form/src/utils/form.ts
index c971953b..67b29b6c 100644
--- a/packages/form/src/utils/form.ts
+++ b/packages/form/src/utils/form.ts
@@ -31,6 +31,7 @@ import {
FormValue,
HtmlField,
Rule,
+ SortProp,
TabPaneConfig,
TypeFunction,
} from '../schema';
@@ -136,6 +137,18 @@ const initValueItem = function (
setValue(mForm, value, initValue, item);
+ if (type === 'table') {
+ if (item.defautSort) {
+ sortChange(value[name], item.defautSort);
+ } else if (item.defaultSort) {
+ sortChange(value[name], item.defaultSort);
+ }
+
+ if (item.sort && item.sortKey) {
+ value[name].sort((a: any, b: any) => b[item.sortKey] - a[item.sortKey]);
+ }
+ }
+
return value;
};
@@ -297,3 +310,36 @@ export const datetimeFormatter = (
}
return defaultValue;
};
+
+export const getDataByPage = (data: any[] = [], pagecontext: number, pagesize: number) =>
+ data.filter(
+ (item: any, index: number) => index >= pagecontext * pagesize && index + 1 <= (pagecontext + 1) * pagesize,
+ );
+
+export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortKey?: string) => {
+ if (newIndex === oldIndex) {
+ return data;
+ }
+
+ if (newIndex < 0 || newIndex >= data.length || oldIndex < 0 || oldIndex >= data.length) {
+ return data;
+ }
+
+ const newData = data.toSpliced(newIndex, 0, ...data.splice(oldIndex, 1));
+
+ if (sortKey) {
+ for (let i = newData.length - 1, v = 0; i >= 0; i--, v++) {
+ newData[v][sortKey] = i;
+ }
+ }
+
+ return newData;
+};
+
+export const sortChange = (data: any[], { prop, order }: SortProp) => {
+ if (order === 'ascending') {
+ data = data.sort((a: any, b: any) => a[prop] - b[prop]);
+ } else if (order === 'descending') {
+ data = data.sort((a: any, b: any) => b[prop] - a[prop]);
+ }
+};
diff --git a/packages/form/tests/unit/utils/form.spec.ts b/packages/form/tests/unit/utils/form.spec.ts
index 8e747f12..2e6d47b5 100644
--- a/packages/form/tests/unit/utils/form.spec.ts
+++ b/packages/form/tests/unit/utils/form.spec.ts
@@ -18,7 +18,7 @@
import { describe, expect, test } from 'vitest';
import type { FormState } from '@form/index';
-import { datetimeFormatter, display, filterFunction, getRules, initValue } from '@form/utils/form';
+import { datetimeFormatter, display, filterFunction, getRules, initValue, sortArray } from '@form/utils/form';
// form state mock 数据
const mForm: FormState = {
@@ -32,6 +32,19 @@ const mForm: FormState = {
setField: (prop: string, field: any) => field,
getField: (prop: string) => prop,
deleteField: (prop: string) => prop,
+ $messageBox: {
+ alert: () => Promise.resolve(),
+ confirm: () => Promise.resolve(),
+ prompt: () => Promise.resolve(),
+ close: () => undefined,
+ },
+ $message: {
+ success: () => undefined,
+ warning: () => undefined,
+ info: () => undefined,
+ error: () => undefined,
+ closeAll: () => undefined,
+ },
};
describe('filterFunction', () => {
@@ -339,3 +352,71 @@ describe('datetimeFormatter', () => {
expect(datetimeFormatter(date.toISOString(), defaultValue, 'timestamp')).toBe(date.getTime());
});
});
+
+describe('sortArray', () => {
+ test('索引相同时不执行任何操作', () => {
+ const data = [1, 2, 3, 4, 5];
+
+ expect(sortArray(data, 2, 2)).toEqual(data);
+ });
+
+ test('正常交换两个元素的位置', () => {
+ const data = [1, 2, 3, 4, 5];
+
+ expect(sortArray(data, 0, 3)).toEqual([4, 1, 2, 3, 5]);
+ });
+
+ test('从后往前移动元素', () => {
+ const data = [1, 2, 3, 4, 5];
+
+ expect(sortArray(data, 3, 1)).toEqual([1, 3, 4, 2, 5]);
+ });
+
+ test('使用sortKey参数重新排序', () => {
+ const data = [
+ { id: 1, order: 0 },
+ { id: 2, order: 1 },
+ { id: 3, order: 2 },
+ { id: 4, order: 3 },
+ ];
+
+ expect(sortArray(data, 0, 2, 'order')).toEqual([
+ { id: 3, order: 3 },
+ { id: 1, order: 2 },
+ { id: 2, order: 1 },
+ { id: 4, order: 0 },
+ ]);
+ });
+
+ test('移动第一个元素到最后', () => {
+ const data = [1, 2, 3, 4, 5];
+
+ expect(sortArray(data, 4, 0)).toEqual([2, 3, 4, 5, 1]);
+ });
+
+ test('移动最后一个元素到第一个', () => {
+ const data = [1, 2, 3, 4, 5];
+
+ expect(sortArray(data, 0, 4)).toEqual([5, 1, 2, 3, 4]);
+ });
+
+ test('空数组不执行任何操作', () => {
+ const data: any[] = [];
+
+ expect(sortArray(data, 0, 1)).toEqual([]);
+ });
+
+ test('只有一个元素的数组不执行任何操作', () => {
+ const data = [1];
+
+ expect(sortArray(data, 0, 0)).toEqual([1]);
+ });
+
+ test('索引超出范围时正常处理', () => {
+ const data = [1, 2, 3];
+
+ // 索引超出范围应该由调用方处理,这里测试函数的行为
+ expect(sortArray(data, 5, 1)).toEqual(data);
+ expect(sortArray(data, 1, 5)).toEqual(data);
+ });
+});
diff --git a/packages/table/src/ActionsColumn.vue b/packages/table/src/ActionsColumn.vue
index 421fd1d8..7d834cc2 100644
--- a/packages/table/src/ActionsColumn.vue
+++ b/packages/table/src/ActionsColumn.vue
@@ -1,49 +1,46 @@
-
-
-
-
-
- 保存
- 取消
-
-
+
+
+
+
+ 保存
+ 取消
diff --git a/packages/tdesign-vue-next-adapter/src/DatePicker.vue b/packages/tdesign-vue-next-adapter/src/DatePicker.vue
index c95ea7e8..9af71ec5 100644
--- a/packages/tdesign-vue-next-adapter/src/DatePicker.vue
+++ b/packages/tdesign-vue-next-adapter/src/DatePicker.vue
@@ -8,7 +8,7 @@
:size="size === 'default' ? 'medium' : size"
:separator="rangeSeparator"
:format="format"
- :valueType="valueFormat === 's' ? 'time-stamp' : valueFormat"
+ :valueType="valueType"
@change="changeHandler"
@update:modelValue="updateModelValue"
/>
@@ -21,7 +21,7 @@
:size="size === 'default' ? 'medium' : size"
:format="format"
:enableTimePicker="type.includes('time')"
- :valueType="valueFormat === 's' ? 'time-stamp' : valueFormat"
+ :valueType="valueType"
@change="changeHandler"
@update:modelValue="updateModelValue"
/>
@@ -54,6 +54,8 @@ const mode = computed(() => {
return map[props.type] || props.type;
});
+const valueType = computed(() => (props.valueFormat === 's' ? 'time-stamp' : props.valueFormat.replace(/\//g, '-')));
+
const emit = defineEmits(['change', 'update:modelValue']);
const changeHandler = (v: any) => {
diff --git a/packages/tdesign-vue-next-adapter/src/Dialog.vue b/packages/tdesign-vue-next-adapter/src/Dialog.vue
new file mode 100644
index 00000000..0d9c0fe0
--- /dev/null
+++ b/packages/tdesign-vue-next-adapter/src/Dialog.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/tdesign-vue-next-adapter/src/Icon.vue b/packages/tdesign-vue-next-adapter/src/Icon.vue
index e8b390c6..8d50cf0f 100644
--- a/packages/tdesign-vue-next-adapter/src/Icon.vue
+++ b/packages/tdesign-vue-next-adapter/src/Icon.vue
@@ -1,7 +1,11 @@
-
+
-
+
diff --git a/packages/tdesign-vue-next-adapter/src/Radio.vue b/packages/tdesign-vue-next-adapter/src/Radio.vue
new file mode 100644
index 00000000..d25ae559
--- /dev/null
+++ b/packages/tdesign-vue-next-adapter/src/Radio.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/packages/tdesign-vue-next-adapter/src/RadioButton.vue b/packages/tdesign-vue-next-adapter/src/RadioButton.vue
new file mode 100644
index 00000000..85f9146a
--- /dev/null
+++ b/packages/tdesign-vue-next-adapter/src/RadioButton.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/packages/tdesign-vue-next-adapter/src/Table.vue b/packages/tdesign-vue-next-adapter/src/Table.vue
new file mode 100644
index 00000000..d7544cae
--- /dev/null
+++ b/packages/tdesign-vue-next-adapter/src/Table.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
diff --git a/packages/tdesign-vue-next-adapter/src/TableColumn.vue b/packages/tdesign-vue-next-adapter/src/TableColumn.vue
deleted file mode 100644
index 8f01dcfc..00000000
--- a/packages/tdesign-vue-next-adapter/src/TableColumn.vue
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/packages/tdesign-vue-next-adapter/src/index.ts b/packages/tdesign-vue-next-adapter/src/index.ts
index e3df5e99..8882e15a 100644
--- a/packages/tdesign-vue-next-adapter/src/index.ts
+++ b/packages/tdesign-vue-next-adapter/src/index.ts
@@ -4,13 +4,11 @@ import {
Button as TButton,
Card as TCard,
Cascader as TCascader,
- Checkbox as TCheckbox,
CheckboxGroup as TCheckboxGroup,
Col as TCol,
Collapse as TCollapse,
CollapsePanel as TCollapsePanel,
ColorPicker as TColorPicker,
- Dialog as TDialog,
DialogPlugin,
Divider as TDivider,
Drawer as TDrawer,
@@ -23,15 +21,12 @@ import {
Option as TOption,
OptionGroup as TOptionGroup,
Pagination as TPagination,
- Radio as TRadio,
- RadioButton as TRadioButton,
RadioGroup as TRadioGroup,
Row as TRow,
Select as TSelect,
StepItem as TStepItem,
Steps as TSteps,
Switch as TSwitch,
- Table as TTable,
TabPanel as TTabPanel,
Tabs as TTabs,
Tag as TTag,
@@ -71,7 +66,6 @@ import type {
StepProps,
StepsProps,
SwitchProps,
- TableColumnProps,
TableProps,
TabPaneProps,
TabsProps,
@@ -81,11 +75,15 @@ import type {
UploadProps,
} from '@tmagic/design';
+import Checkbox from './Checkbox.vue';
import DatePicker from './DatePicker.vue';
+import Dialog from './Dialog.vue';
import Icon from './Icon.vue';
import Input from './Input.vue';
+import Radio from './Radio.vue';
+import RadioButton from './RadioButton.vue';
import Scrollbar from './Scrollbar.vue';
-import TableColumn from './TableColumn.vue';
+import Table from './Table.vue';
const adapter: any = {
message: MessagePlugin,
@@ -119,7 +117,7 @@ const adapter: any = {
props: (props: ButtonProps) => ({
theme: props.type,
size: props.size === 'default' ? 'medium' : props.size,
- icon: () => (props.icon ? h(props.icon) : null),
+ icon: () => (props.icon ? h(Icon, null, { default: () => h(props.icon) }) : null),
variant: props.link || props.text ? 'text' : 'base',
shape: props.circle ? 'circle' : 'rectangle',
}),
@@ -153,13 +151,8 @@ const adapter: any = {
},
checkbox: {
- component: TCheckbox,
- props: (props: CheckboxProps) => ({
- modelValue: props.modelValue,
- label: props.label,
- value: props.value,
- disabled: props.disabled,
- }),
+ component: Checkbox,
+ props: (props: CheckboxProps) => props,
},
checkboxGroup: {
@@ -174,14 +167,14 @@ const adapter: any = {
col: {
component: TCol,
props: (props: ColProps) => ({
- span: props.span,
+ span: props.span ? props.span / 2 : 12,
}),
},
collapse: {
component: TCollapse,
props: (props: CollapseProps) => ({
- value: props.modelValue,
+ modelValue: props.modelValue,
expandIconPlacement: 'right',
}),
},
@@ -212,15 +205,8 @@ const adapter: any = {
},
dialog: {
- component: TDialog,
- props: (props: DialogProps) => ({
- visible: props.modelValue,
- attach: props.appendToBody ? 'body' : '',
- header: props.title,
- width: props.width,
- mode: props.fullscreen ? 'full-screen' : 'modal',
- closeOnOverlayClick: props.closeOnClickModal,
- }),
+ component: Dialog,
+ props: (props: DialogProps) => props,
},
divider: {
@@ -295,6 +281,7 @@ const adapter: any = {
labelWidth: props.labelWidth,
name: props.prop,
rules: props.rules,
+ help: props.extra,
}),
},
@@ -347,18 +334,13 @@ const adapter: any = {
},
radio: {
- component: TRadio,
- props: (props: RadioProps) => ({
- label: props.label,
- value: props.value,
- }),
+ component: Radio,
+ props: (props: RadioProps) => props,
},
radioButton: {
- component: TRadioButton,
- props: (props: RadioButtonProps) => ({
- label: props.label,
- }),
+ component: RadioButton,
+ props: (props: RadioButtonProps) => props,
},
radioGroup: {
@@ -424,15 +406,10 @@ const adapter: any = {
},
table: {
- component: TTable,
+ component: Table,
props: (props: TableProps) => props,
},
- tableColumn: {
- component: TableColumn,
- props: (props: TableColumnProps) => props,
- },
-
tabPane: {
component: TTabPanel,
props: (props: TabPaneProps) => ({
diff --git a/playground/src/components/NavMenu.vue b/playground/src/components/NavMenu.vue
index 08c16286..068d0506 100644
--- a/playground/src/components/NavMenu.vue
+++ b/playground/src/components/NavMenu.vue
@@ -1,15 +1,10 @@
diff --git a/playground/vite.config.ts b/playground/vite.config.ts
index 302ab1fd..18e0b761 100644
--- a/playground/vite.config.ts
+++ b/playground/vite.config.ts
@@ -85,6 +85,10 @@ export default defineConfig({
find: /^@tmagic\/element-plus-adapter/,
replacement: path.join(__dirname, '../packages/element-plus-adapter/src/index.ts'),
},
+ {
+ find: /^@tmagic\/tdesign-vue-next-adapter/,
+ replacement: path.join(__dirname, '../packages/tdesign-vue-next-adapter/src/index.ts'),
+ },
] : [],
},