feat: support for importing attachments (#1466)
This commit is contained in:
parent
01663da7ec
commit
5421686504
@ -10,6 +10,7 @@
|
|||||||
"async-mutex": "^0.3.2",
|
"async-mutex": "^0.3.2",
|
||||||
"cron-parser": "4.4.0",
|
"cron-parser": "4.4.0",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
|
"excel-date-to-js": "^1.1.5",
|
||||||
"flat": "^5.0.2",
|
"flat": "^5.0.2",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"mathjs": "^10.6.1",
|
"mathjs": "^10.6.1",
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { BaseValueParser as ValueParser } from '../../value-parsers';
|
||||||
|
|
||||||
|
describe('number value parser', () => {
|
||||||
|
let parser: ValueParser;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parser = new ValueParser({}, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be converted to an array', () => {
|
||||||
|
expect(parser.toArr('A/B', '/')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A,B')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A, B')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A, B')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A, B ')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A, B ')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A、 B')).toEqual(['A', 'B']);
|
||||||
|
expect(parser.toArr('A ,, B')).toEqual(['A', 'B']);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,67 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { Database, mockDatabase } from '../..';
|
||||||
|
import { DateValueParser } from '../../value-parsers';
|
||||||
|
|
||||||
|
describe('number value parser', () => {
|
||||||
|
let parser: DateValueParser;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = mockDatabase();
|
||||||
|
db.collection({
|
||||||
|
name: 'tests',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dateOnly',
|
||||||
|
type: 'date',
|
||||||
|
uiSchema: {
|
||||||
|
['x-component-props']: {
|
||||||
|
showTime: false,
|
||||||
|
gmt: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dateTime',
|
||||||
|
type: 'date',
|
||||||
|
uiSchema: {
|
||||||
|
['x-component-props']: {
|
||||||
|
showTime: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dateTimeGmt',
|
||||||
|
type: 'date',
|
||||||
|
uiSchema: {
|
||||||
|
['x-component-props']: {
|
||||||
|
showTime: true,
|
||||||
|
gmt: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
parser = new DateValueParser({}, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectValue = (value, field = 'date') => {
|
||||||
|
const collection = db.getCollection('tests');
|
||||||
|
parser = new DateValueParser(collection.getField(field), {});
|
||||||
|
parser.setValue(value);
|
||||||
|
return expect(parser.getValue());
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be correct', () => {
|
||||||
|
expectValue(42510).toBe('2016-05-20T00:00:00.000Z');
|
||||||
|
expectValue('42510').toBe('2016-05-20T00:00:00.000Z');
|
||||||
|
expectValue('2016-05-20T00:00:00.000Z').toBe('2016-05-20T00:00:00.000Z');
|
||||||
|
expectValue('2016-05-20 04:22:22', 'dateOnly').toBe('2016-05-20T00:00:00.000Z');
|
||||||
|
expectValue('2016-05-20 01:00:00', 'dateTime').toBe(moment('2016-05-20 01:00:00').toISOString());
|
||||||
|
expectValue('2016-05-20 01:00:00', 'dateTimeGmt').toBe('2016-05-20T01:00:00.000Z');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,206 @@
|
|||||||
|
import { Database, mockDatabase } from '../..';
|
||||||
|
import { ToManyValueParser } from '../../value-parsers';
|
||||||
|
|
||||||
|
describe('number value parser', () => {
|
||||||
|
let parser: ToManyValueParser;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'tags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'attachments',
|
||||||
|
interface: 'attachment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
db.collection({
|
||||||
|
name: 'attachments',
|
||||||
|
fields: [],
|
||||||
|
});
|
||||||
|
db.collection({
|
||||||
|
name: 'tags',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const tag = db.getRepository('tags');
|
||||||
|
await tag.create({
|
||||||
|
values: { name: 'tag1' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = async (value) => {
|
||||||
|
const post = db.getCollection('posts');
|
||||||
|
parser = new ToManyValueParser(post.getField('tags'), {
|
||||||
|
column: {
|
||||||
|
dataIndex: ['tags', 'name'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await parser.setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAttachment = async (value) => {
|
||||||
|
const post = db.getCollection('posts');
|
||||||
|
parser = new ToManyValueParser(post.getField('attachments'), {});
|
||||||
|
await parser.setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be [1]', async () => {
|
||||||
|
await setValue('tag1');
|
||||||
|
expect(parser.errors.length).toBe(0);
|
||||||
|
expect(parser.getValue()).toEqual([1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be null', async () => {
|
||||||
|
await setValue('tag2');
|
||||||
|
expect(parser.errors.length).toBe(1);
|
||||||
|
expect(parser.getValue()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be attachment', async () => {
|
||||||
|
await setAttachment('https://www.nocobase.com/images/logo.png');
|
||||||
|
expect(parser.errors.length).toBe(0);
|
||||||
|
expect(parser.getValue()).toMatchObject([
|
||||||
|
{
|
||||||
|
title: 'logo.png',
|
||||||
|
extname: '.png',
|
||||||
|
filename: 'logo.png',
|
||||||
|
url: 'https://www.nocobase.com/images/logo.png',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.only('china region', () => {
|
||||||
|
let parser: ToManyValueParser;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'chinaRegion',
|
||||||
|
target: 'chinaRegions',
|
||||||
|
interface: 'chinaRegion',
|
||||||
|
targetKey: 'code',
|
||||||
|
sortBy: 'level',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
db.collection({
|
||||||
|
name: 'chinaRegions',
|
||||||
|
autoGenId: false,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
type: 'string',
|
||||||
|
// unique: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parent',
|
||||||
|
type: 'belongsTo',
|
||||||
|
target: 'chinaRegions',
|
||||||
|
targetKey: 'code',
|
||||||
|
foreignKey: 'parentCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'children',
|
||||||
|
type: 'hasMany',
|
||||||
|
target: 'chinaRegions',
|
||||||
|
sourceKey: 'code',
|
||||||
|
foreignKey: 'parentCode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'level',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const areas = require('china-division/dist/areas.json');
|
||||||
|
const cities = require('china-division/dist/cities.json');
|
||||||
|
const provinces = require('china-division/dist/provinces.json');
|
||||||
|
const ChinaRegion = db.getModel('chinaRegions');
|
||||||
|
await ChinaRegion.bulkCreate(
|
||||||
|
provinces.map((item) => ({
|
||||||
|
code: item.code,
|
||||||
|
name: item.name,
|
||||||
|
level: 1,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
await ChinaRegion.bulkCreate(
|
||||||
|
cities.map((item) => ({
|
||||||
|
code: item.code,
|
||||||
|
name: item.name,
|
||||||
|
level: 2,
|
||||||
|
parentCode: item.provinceCode,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
await ChinaRegion.bulkCreate(
|
||||||
|
areas.map((item) => ({
|
||||||
|
code: item.code,
|
||||||
|
name: item.name,
|
||||||
|
level: 3,
|
||||||
|
parentCode: item.cityCode,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = async (value) => {
|
||||||
|
const r = db.getCollection('users');
|
||||||
|
parser = new ToManyValueParser(r.getField('chinaRegion'), {});
|
||||||
|
await parser.setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be correct', async () => {
|
||||||
|
await setValue('北京市/市辖区');
|
||||||
|
expect(parser.errors.length).toBe(0);
|
||||||
|
expect(parser.getValue()).toEqual(['11', '1101']);
|
||||||
|
|
||||||
|
await setValue('北京市 / 市辖区');
|
||||||
|
expect(parser.errors.length).toBe(0);
|
||||||
|
expect(parser.getValue()).toEqual(['11', '1101']);
|
||||||
|
|
||||||
|
await setValue('天津市 / 市辖区');
|
||||||
|
expect(parser.errors.length).toBe(0);
|
||||||
|
expect(parser.getValue()).toEqual(['12', '1201']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be null', async () => {
|
||||||
|
await setValue('北京市2 / 市辖区');
|
||||||
|
expect(parser.errors.length).toBe(1);
|
||||||
|
expect(parser.getValue()).toBeNull();
|
||||||
|
|
||||||
|
await setValue('北京市 / 市辖区 2');
|
||||||
|
expect(parser.errors.length).toBe(1);
|
||||||
|
expect(parser.getValue()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,60 @@
|
|||||||
|
import { Database, mockDatabase } from '../..';
|
||||||
|
import { ToManyValueParser } from '../../value-parsers';
|
||||||
|
|
||||||
|
describe('number value parser', () => {
|
||||||
|
let parser: ToManyValueParser;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'user',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const r = db.getRepository('users');
|
||||||
|
await r.create({
|
||||||
|
values: { name: 'user1' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = async (value) => {
|
||||||
|
const post = db.getCollection('posts');
|
||||||
|
parser = new ToManyValueParser(post.getField('user'), {
|
||||||
|
column: {
|
||||||
|
dataIndex: ['user', 'name'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await parser.setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be correct', async () => {
|
||||||
|
await setValue('user1');
|
||||||
|
expect(parser.errors.length).toBe(0);
|
||||||
|
expect(parser.getValue()).toEqual([1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be null', async () => {
|
||||||
|
await setValue('user2');
|
||||||
|
expect(parser.errors.length).toBe(1);
|
||||||
|
expect(parser.getValue()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
@ -27,16 +27,4 @@ export class ArrayValueParser extends BaseValueParser {
|
|||||||
}
|
}
|
||||||
return { map, set };
|
return { map, set };
|
||||||
}
|
}
|
||||||
|
|
||||||
toArr(value) {
|
|
||||||
let values: string[] = [];
|
|
||||||
if (!value) {
|
|
||||||
values = [];
|
|
||||||
} else if (typeof value === 'string') {
|
|
||||||
values = value.split(',');
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
values = value;
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,22 @@ export class BaseValueParser {
|
|||||||
this.value = null;
|
this.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trim(value: any) {
|
||||||
|
return typeof value === 'string' ? value.trim() : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArr(value: any, splitter?: string) {
|
||||||
|
let values: string[] = [];
|
||||||
|
if (!value) {
|
||||||
|
values = [];
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
values = value.split(splitter || /,|,|、/);
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
values = value;
|
||||||
|
}
|
||||||
|
return values.map((v) => this.trim(v)).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return this.value;
|
return this.value;
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ export class BooleanValueParser extends BaseValueParser {
|
|||||||
} else if (['0', 'n', 'no', 'false', '否'].includes(value.toLowerCase())) {
|
} else if (['0', 'n', 'no', 'false', '否'].includes(value.toLowerCase())) {
|
||||||
this.value = false;
|
this.value = false;
|
||||||
} else {
|
} else {
|
||||||
this.errors.push(`Value invalid - ${JSON.stringify(this.value)}`);
|
this.errors.push(`Invalid value - ${JSON.stringify(this.value)}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.errors.push(`Value invalid - ${JSON.stringify(this.value)}`);
|
this.errors.push(`Invalid value - ${JSON.stringify(this.value)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import { moment2str } from '@nocobase/utils';
|
import { moment2str } from '@nocobase/utils';
|
||||||
|
import { getJsDateFromExcel } from 'excel-date-to-js';
|
||||||
import moment, { isDate, isMoment } from 'moment';
|
import moment, { isDate, isMoment } from 'moment';
|
||||||
import { BaseValueParser } from "./base-value-parser";
|
import { BaseValueParser } from './base-value-parser';
|
||||||
|
|
||||||
|
function isNumeric(str: any) {
|
||||||
|
if (typeof str === 'number') return true;
|
||||||
|
if (typeof str != 'string') return false;
|
||||||
|
return !isNaN(str as any) && !isNaN(parseFloat(str));
|
||||||
|
}
|
||||||
|
|
||||||
export class DateValueParser extends BaseValueParser {
|
export class DateValueParser extends BaseValueParser {
|
||||||
async setValue(value: any) {
|
async setValue(value: any) {
|
||||||
@ -8,6 +15,12 @@ export class DateValueParser extends BaseValueParser {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
} else if (isDate(value)) {
|
} else if (isDate(value)) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
} else if (isNumeric(value)) {
|
||||||
|
try {
|
||||||
|
this.value = getJsDateFromExcel(value).toISOString();
|
||||||
|
} catch (error) {
|
||||||
|
this.errors.push(`Invalid date - ${error.message}`);
|
||||||
|
}
|
||||||
} else if (typeof value === 'string') {
|
} else if (typeof value === 'string') {
|
||||||
const props = this.getProps();
|
const props = this.getProps();
|
||||||
const m = moment(value);
|
const m = moment(value);
|
||||||
|
@ -2,6 +2,18 @@ import { BaseValueParser } from './base-value-parser';
|
|||||||
|
|
||||||
export class JsonValueParser extends BaseValueParser {
|
export class JsonValueParser extends BaseValueParser {
|
||||||
async setValue(value: any) {
|
async setValue(value: any) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (value.trim() === '') {
|
||||||
|
this.value = null;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this.value = JSON.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
this.errors.push(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,13 @@ export class NumberValueParser extends BaseValueParser {
|
|||||||
value = +value;
|
value = +value;
|
||||||
}
|
}
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
this.errors.push(`Value invalid - "${value}"`);
|
this.errors.push(`Invalid value - "${value}"`);
|
||||||
} else {
|
} else {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.errors.push(`Value invalid - ${JSON.stringify(value)}`);
|
this.errors.push(`Invalid value - ${JSON.stringify(value)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,85 @@
|
|||||||
|
import { basename, extname } from 'path';
|
||||||
import { Repository } from '../repository';
|
import { Repository } from '../repository';
|
||||||
import { BaseValueParser } from './base-value-parser';
|
import { BaseValueParser } from './base-value-parser';
|
||||||
|
|
||||||
export class ToManyValueParser extends BaseValueParser {
|
export class ToManyValueParser extends BaseValueParser {
|
||||||
async setValue(value: any) {
|
setAccessors = {
|
||||||
const fieldNames = this.getFileNames();
|
attachment: 'setAttachments',
|
||||||
if (this.isInterface('chinaRegion')) {
|
chinaRegion: 'setChinaRegion',
|
||||||
const repository = this.field.database.getRepository(this.field.target) as Repository;
|
};
|
||||||
try {
|
|
||||||
this.value = await Promise.all(
|
async setAttachments(value: any) {
|
||||||
value.split('/').map(async (v) => {
|
this.value = this.toArr(value).map((url: string) => {
|
||||||
const instance = await repository.findOne({ filter: { [fieldNames.label]: v.trim() } });
|
return {
|
||||||
if (!instance) {
|
title: basename(url),
|
||||||
throw new Error(`"${v}" does not exist`);
|
extname: extname(url),
|
||||||
}
|
filename: basename(url),
|
||||||
return instance.get(fieldNames.value);
|
url,
|
||||||
}),
|
};
|
||||||
);
|
});
|
||||||
} catch (error) {
|
}
|
||||||
this.errors.push(error.message);
|
|
||||||
|
async setChinaRegion(value: any) {
|
||||||
|
const repository = this.field.database.getRepository(this.field.target) as Repository;
|
||||||
|
try {
|
||||||
|
const values = [];
|
||||||
|
const names = this.toArr(value, '/');
|
||||||
|
let parentCode = null;
|
||||||
|
for (const name of names) {
|
||||||
|
const instance = await repository.findOne({
|
||||||
|
filter: {
|
||||||
|
name: name.trim(),
|
||||||
|
parentCode,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!instance) {
|
||||||
|
throw new Error(`"${value}" does not exist`);
|
||||||
|
}
|
||||||
|
parentCode = instance.get('code');
|
||||||
|
values.push(parentCode);
|
||||||
}
|
}
|
||||||
} else {
|
if (values.length !== names.length) {
|
||||||
const dataIndex = this.ctx?.column?.dataIndex || [];
|
throw new Error(`"${value}" does not exist`);
|
||||||
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
|
|
||||||
this.errors.push(`data index invalid`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const key = this.ctx.column.dataIndex[1];
|
|
||||||
const repository = this.field.database.getRepository(this.field.target) as Repository;
|
|
||||||
try {
|
|
||||||
this.value = await Promise.all(
|
|
||||||
value.split(',').map(async (v) => {
|
|
||||||
const instance = await repository.findOne({ filter: { [key]: v.trim() } });
|
|
||||||
if (!instance) {
|
|
||||||
throw new Error(`"${v}" does not exist`);
|
|
||||||
}
|
|
||||||
return instance.get(fieldNames.value);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
this.errors.push(error.message);
|
|
||||||
}
|
}
|
||||||
|
this.value = values;
|
||||||
|
} catch (error) {
|
||||||
|
this.errors.push(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileNames() {
|
async setAssociations(value: any) {
|
||||||
const fieldNames = this.field.options?.uiSchema?.['x-component-props']?.['fieldNames'] || {};
|
const dataIndex = this.ctx?.column?.dataIndex || [];
|
||||||
return { label: 'id', value: 'id', ...fieldNames };
|
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
|
||||||
|
this.errors.push(`data index invalid`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = this.ctx.column.dataIndex[1];
|
||||||
|
const repository = this.field.database.getRepository(this.field.target) as Repository;
|
||||||
|
try {
|
||||||
|
this.value = await Promise.all(
|
||||||
|
this.toArr(value).map(async (v) => {
|
||||||
|
const instance = await repository.findOne({ filter: { [key]: v } });
|
||||||
|
if (!instance) {
|
||||||
|
throw new Error(`"${v}" does not exist`);
|
||||||
|
}
|
||||||
|
return instance.get(this.field.targetKey || 'id');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.errors.push(error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isInterface(name) {
|
async setValue(value: any) {
|
||||||
return this.field.options.interface === name;
|
const setAccessor = this.setAccessors[this.getInterface()] || 'setAssociations';
|
||||||
|
await this[setAccessor](value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInterface() {
|
||||||
|
return this.field?.options?.interface as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInterface(name: string) {
|
||||||
|
return this.getInterface() === name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,27 +3,18 @@ import { BaseValueParser } from './base-value-parser';
|
|||||||
|
|
||||||
export class ToOneValueParser extends BaseValueParser {
|
export class ToOneValueParser extends BaseValueParser {
|
||||||
async setValue(value: any) {
|
async setValue(value: any) {
|
||||||
const fieldNames = this.getFileNames();
|
|
||||||
const dataIndex = this.ctx?.column?.dataIndex || [];
|
const dataIndex = this.ctx?.column?.dataIndex || [];
|
||||||
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
|
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
|
||||||
|
this.errors.push(`data index invalid`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const field = this.ctx.column.dataIndex[1];
|
const key = this.ctx.column.dataIndex[1];
|
||||||
const repository = this.field.database.getRepository(this.field.target) as Repository;
|
const repository = this.field.database.getRepository(this.field.target) as Repository;
|
||||||
const instance = await repository.findOne({ filter: { [field]: value.trim() } });
|
const instance = await repository.findOne({ filter: { [key]: this.trim(value) } });
|
||||||
if (instance) {
|
if (instance) {
|
||||||
this.value = instance.get(fieldNames.value);
|
this.value = instance.get(this.field.targetKey || 'id');
|
||||||
} else {
|
} else {
|
||||||
this.errors.push(`"${value}" does not exist`);
|
this.errors.push(`"${value}" does not exist`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileNames() {
|
|
||||||
const fieldNames = this.field.options?.uiSchema?.['x-component-props']?.['fieldNames'] || {};
|
|
||||||
return { label: 'id', value: 'id', ...fieldNames };
|
|
||||||
}
|
|
||||||
|
|
||||||
isInterface(name) {
|
|
||||||
return this.field.options.interface === name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,4 +8,215 @@ Excel 数据导入插件。
|
|||||||
|
|
||||||
内置插件无需手动安装激活。
|
内置插件无需手动安装激活。
|
||||||
|
|
||||||
## 使用方法
|
## 导入说明
|
||||||
|
|
||||||
|
### 数字类型字段
|
||||||
|
|
||||||
|
支持数字和百分比,`N/A` 或 `-` 的文案会被过滤掉
|
||||||
|
|
||||||
|
| 数字1 | 百分比 | 数字2 | 数字3 |
|
||||||
|
| -- | -- | -- | -- |
|
||||||
|
| 123 | 25% | N/A | - |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"数字1": 123,
|
||||||
|
"百分比": 0.25,
|
||||||
|
"数字2": null,
|
||||||
|
"数字3": null,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 布尔类型字段
|
||||||
|
|
||||||
|
输入文案支持(英文不区分大小写):
|
||||||
|
|
||||||
|
- `Yes` `Y` `True` `1` `是`
|
||||||
|
- `No` `N` `False` `0` `否`
|
||||||
|
|
||||||
|
| 字段1 | 字段2 | 字段3 | 字段4 | 字段4 |
|
||||||
|
| -- | -- | -- | -- | -- |
|
||||||
|
| 否 | 是 | Y | true | 0 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"字段1": false,
|
||||||
|
"字段2": true,
|
||||||
|
"字段3": true,
|
||||||
|
"字段4": true,
|
||||||
|
"字段5": false,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日期类型字段
|
||||||
|
|
||||||
|
| DateOnly | Local(+08:00) | GMT |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| 2023-01-18 22:22:22 | 2023-01-18 22:22:22 | 2023-01-18 22:22:22 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"DateOnly": "2023-01-18T00:00:00.000Z",
|
||||||
|
"Local(+08:00)": "2023-01-18T14:22:22.000Z",
|
||||||
|
"GMT": "2023-01-18T22:22:22.000Z",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选择类型字段
|
||||||
|
|
||||||
|
选项值和选项标签都可作为导入文案,多个选项之间以以逗号(`,` `,`)或顿号(`、`)区分
|
||||||
|
|
||||||
|
如字段 `优先级` 的可选项包括:
|
||||||
|
|
||||||
|
| 选项值 | 选项标签 |
|
||||||
|
| -- | -- |
|
||||||
|
| low | 低 |
|
||||||
|
| medium | 中 |
|
||||||
|
| high | 低 |
|
||||||
|
|
||||||
|
选项值和选项标签都可作为导入文案
|
||||||
|
|
||||||
|
| 优先级 |
|
||||||
|
| -- |
|
||||||
|
| 高 |
|
||||||
|
| low |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
[
|
||||||
|
{ "优先级": "high" },
|
||||||
|
{ "优先级": "low" },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 中国行政区字段
|
||||||
|
|
||||||
|
| 地区1 | 地区2 |
|
||||||
|
| -- | -- |
|
||||||
|
| 北京市/市辖区 | 天津市/市辖区 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"地区1": ["11","1101"],
|
||||||
|
"地区2": ["12","1201"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 附件字段
|
||||||
|
|
||||||
|
| 附件 |
|
||||||
|
| --|
|
||||||
|
| https://www.nocobase.com/images/logo.png |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"附件": [
|
||||||
|
{
|
||||||
|
"filename": "logo.png",
|
||||||
|
"title": "logo.png",
|
||||||
|
"extname": ".png",
|
||||||
|
"url": "https://www.nocobase.com/images/logo.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关系类型字段
|
||||||
|
|
||||||
|
多条数据以逗号(`,` `,`)或顿号(`、`)区分
|
||||||
|
|
||||||
|
| 部门/名称 | 分类/标题 |
|
||||||
|
| -- | -- |
|
||||||
|
| 开发组 | 分类1、分类2 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"部门": [1], // 1 为部门名称为「开发组」的记录 ID
|
||||||
|
"分类": [1,2], // 1,2 为分类标题为「分类1」和「分类2」的记录 ID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON 类型字段
|
||||||
|
|
||||||
|
| JSON1 |
|
||||||
|
| -- |
|
||||||
|
| {"key":"value"} |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"JSON": {"key":"value"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 地图几何图形类型
|
||||||
|
|
||||||
|
| Point | Line | Polygon | Circle |
|
||||||
|
| -- | -- | -- | -- |
|
||||||
|
| 1,2 | (1,2),(3,4) | (1,2),(3,4),(1,2) | 1,2,3 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"Point": [1,2],
|
||||||
|
"Line": [[1,2], [3,4]],
|
||||||
|
"Polygon": [[1,2], [3,4], [1,2]],
|
||||||
|
"Circle": [1,2,3]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义导入格式
|
||||||
|
|
||||||
|
通过 `db.registerFieldValueParsers()` 方法注册自定义的 `ValueParser`,如:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { BaseValueParser } from '@nocobase/database';
|
||||||
|
|
||||||
|
class PointValueParser extends BaseValueParser {
|
||||||
|
async setValue(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this.value = value;
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
this.value = value.split(',');
|
||||||
|
} else {
|
||||||
|
this.errors.push('Value invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new Database();
|
||||||
|
|
||||||
|
// type=point 的字段导入时,将通过 PointValueParser 解析数据
|
||||||
|
db.registerFieldValueParsers({
|
||||||
|
point: PointValueParser,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
导入示例
|
||||||
|
|
||||||
|
| Point |
|
||||||
|
| --|
|
||||||
|
| 1,2 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"Point": [1,2]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -8,4 +8,215 @@ Excel 数据导入插件。
|
|||||||
|
|
||||||
内置插件无需手动安装激活。
|
内置插件无需手动安装激活。
|
||||||
|
|
||||||
## 使用方法
|
## 导入说明
|
||||||
|
|
||||||
|
### 数字类型字段
|
||||||
|
|
||||||
|
支持数字和百分比,`N/A` 或 `-` 的文案会被过滤掉
|
||||||
|
|
||||||
|
| 数字1 | 百分比 | 数字2 | 数字3 |
|
||||||
|
| -- | -- | -- | -- |
|
||||||
|
| 123 | 25% | N/A | - |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"数字1": 123,
|
||||||
|
"百分比": 0.25,
|
||||||
|
"数字2": null,
|
||||||
|
"数字3": null,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 布尔类型字段
|
||||||
|
|
||||||
|
输入文案支持(英文不区分大小写):
|
||||||
|
|
||||||
|
- `Yes` `Y` `True` `1` `是`
|
||||||
|
- `No` `N` `False` `0` `否`
|
||||||
|
|
||||||
|
| 字段1 | 字段2 | 字段3 | 字段4 | 字段4 |
|
||||||
|
| -- | -- | -- | -- | -- |
|
||||||
|
| 否 | 是 | Y | true | 0 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"字段1": false,
|
||||||
|
"字段2": true,
|
||||||
|
"字段3": true,
|
||||||
|
"字段4": true,
|
||||||
|
"字段5": false,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日期类型字段
|
||||||
|
|
||||||
|
| DateOnly | Local(+08:00) | GMT |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| 2023-01-18 22:22:22 | 2023-01-18 22:22:22 | 2023-01-18 22:22:22 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"DateOnly": "2023-01-18T00:00:00.000Z",
|
||||||
|
"Local(+08:00)": "2023-01-18T14:22:22.000Z",
|
||||||
|
"GMT": "2023-01-18T22:22:22.000Z",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选择类型字段
|
||||||
|
|
||||||
|
选项值和选项标签都可作为导入文案,多个选项之间以以逗号(`,` `,`)或顿号(`、`)区分
|
||||||
|
|
||||||
|
如字段 `优先级` 的可选项包括:
|
||||||
|
|
||||||
|
| 选项值 | 选项标签 |
|
||||||
|
| -- | -- |
|
||||||
|
| low | 低 |
|
||||||
|
| medium | 中 |
|
||||||
|
| high | 低 |
|
||||||
|
|
||||||
|
选项值和选项标签都可作为导入文案
|
||||||
|
|
||||||
|
| 优先级 |
|
||||||
|
| -- |
|
||||||
|
| 高 |
|
||||||
|
| low |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
[
|
||||||
|
{ "优先级": "high" },
|
||||||
|
{ "优先级": "low" },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 中国行政区字段
|
||||||
|
|
||||||
|
| 地区1 | 地区2 |
|
||||||
|
| -- | -- |
|
||||||
|
| 北京市/市辖区 | 天津市/市辖区 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"地区1": ["11","1101"],
|
||||||
|
"地区2": ["12","1201"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 附件字段
|
||||||
|
|
||||||
|
| 附件 |
|
||||||
|
| --|
|
||||||
|
| https://www.nocobase.com/images/logo.png |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"附件": [
|
||||||
|
{
|
||||||
|
"filename": "logo.png",
|
||||||
|
"title": "logo.png",
|
||||||
|
"extname": ".png",
|
||||||
|
"url": "https://www.nocobase.com/images/logo.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关系类型字段
|
||||||
|
|
||||||
|
多条数据以逗号(`,` `,`)或顿号(`、`)区分
|
||||||
|
|
||||||
|
| 部门/名称 | 分类/标题 |
|
||||||
|
| -- | -- |
|
||||||
|
| 开发组 | 分类1、分类2 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"部门": [1], // 1 为部门名称为「开发组」的记录 ID
|
||||||
|
"分类": [1,2], // 1,2 为分类标题为「分类1」和「分类2」的记录 ID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON 类型字段
|
||||||
|
|
||||||
|
| JSON1 |
|
||||||
|
| -- |
|
||||||
|
| {"key":"value"} |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"JSON": {"key":"value"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 地图几何图形类型
|
||||||
|
|
||||||
|
| Point | Line | Polygon | Circle |
|
||||||
|
| -- | -- | -- | -- |
|
||||||
|
| 1,2 | (1,2),(3,4) | (1,2),(3,4),(1,2) | 1,2,3 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"Point": [1,2],
|
||||||
|
"Line": [[1,2], [3,4]],
|
||||||
|
"Polygon": [[1,2], [3,4], [1,2]],
|
||||||
|
"Circle": [1,2,3]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义导入格式
|
||||||
|
|
||||||
|
通过 `db.registerFieldValueParsers()` 方法注册自定义的 `ValueParser`,如:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { BaseValueParser } from '@nocobase/database';
|
||||||
|
|
||||||
|
class PointValueParser extends BaseValueParser {
|
||||||
|
async setValue(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this.value = value;
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
this.value = value.split(',');
|
||||||
|
} else {
|
||||||
|
this.errors.push('Value invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new Database();
|
||||||
|
|
||||||
|
// type=point 的字段导入时,将通过 PointValueParser 解析数据
|
||||||
|
db.registerFieldValueParsers({
|
||||||
|
point: PointValueParser,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
导入示例
|
||||||
|
|
||||||
|
| Point |
|
||||||
|
| --|
|
||||||
|
| 1,2 |
|
||||||
|
|
||||||
|
转 JSON 之后为
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
"Point": [1,2]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { useCollectionManager } from '@nocobase/client';
|
import { useCollectionManager } from '@nocobase/client';
|
||||||
|
|
||||||
const EXCLUDE_INTERFACES = [
|
const EXCLUDE_INTERFACES = [
|
||||||
'icon',
|
// 'icon',
|
||||||
'formula',
|
// 'formula',
|
||||||
'attachment',
|
// 'attachment',
|
||||||
'markdown',
|
// 'markdown',
|
||||||
'richText',
|
// 'richText',
|
||||||
'id',
|
'id',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'createdBy',
|
'createdBy',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
'updatedBy',
|
'updatedBy',
|
||||||
'sequence',
|
// 'sequence',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const useFields = (collectionName: string) => {
|
export const useFields = (collectionName: string) => {
|
||||||
|
@ -22,6 +22,13 @@ const useImportSchema = (s: Schema) => {
|
|||||||
return { schema };
|
return { schema };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toArr = (v: any) => {
|
||||||
|
if (!v || !Array.isArray(v)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
export const useDownloadXlsxTemplateAction = () => {
|
export const useDownloadXlsxTemplateAction = () => {
|
||||||
const { service, resource } = useBlockRequestContext();
|
const { service, resource } = useBlockRequestContext();
|
||||||
const apiClient = useAPIClient();
|
const apiClient = useAPIClient();
|
||||||
@ -34,7 +41,7 @@ export const useDownloadXlsxTemplateAction = () => {
|
|||||||
return {
|
return {
|
||||||
async run() {
|
async run() {
|
||||||
const { importColumns, explain } = cloneDeep(importSchema?.['x-action-settings']?.['importSettings'] ?? {});
|
const { importColumns, explain } = cloneDeep(importSchema?.['x-action-settings']?.['importSettings'] ?? {});
|
||||||
const columns = importColumns
|
const columns = toArr(importColumns)
|
||||||
.map((column) => {
|
.map((column) => {
|
||||||
const field = getCollectionField(`${name}.${column.dataIndex[0]}`);
|
const field = getCollectionField(`${name}.${column.dataIndex[0]}`);
|
||||||
if (!field) {
|
if (!field) {
|
||||||
@ -88,7 +95,7 @@ export const useImportStartAction = () => {
|
|||||||
return {
|
return {
|
||||||
async run() {
|
async run() {
|
||||||
const { importColumns, explain } = cloneDeep(importSchema?.['x-action-settings']?.['importSettings'] ?? {});
|
const { importColumns, explain } = cloneDeep(importSchema?.['x-action-settings']?.['importSettings'] ?? {});
|
||||||
const columns = importColumns
|
const columns = toArr(importColumns)
|
||||||
.map((column) => {
|
.map((column) => {
|
||||||
const field = getCollectionField(`${name}.${column.dataIndex[0]}`);
|
const field = getCollectionField(`${name}.${column.dataIndex[0]}`);
|
||||||
if (!field) {
|
if (!field) {
|
||||||
@ -110,7 +117,6 @@ export const useImportStartAction = () => {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
const uploadFiles = form.values.upload.map((f) => f.originFileObj);
|
const uploadFiles = form.values.upload.map((f) => f.originFileObj);
|
||||||
console.log(form, uploadFiles);
|
|
||||||
formData.append('file', uploadFiles[0]);
|
formData.append('file', uploadFiles[0]);
|
||||||
formData.append('columns', JSON.stringify(columns));
|
formData.append('columns', JSON.stringify(columns));
|
||||||
formData.append('explain', explain);
|
formData.append('explain', explain);
|
||||||
|
Loading…
Reference in New Issue
Block a user