feat(db): field value parser

This commit is contained in:
chenos 2023-02-16 23:56:00 +08:00
parent 0ac351bbd2
commit 5805b69455
27 changed files with 422 additions and 309 deletions

View File

@ -15,7 +15,7 @@ import {
Sequelize,
SyncOptions,
Transactionable,
Utils,
Utils
} from 'sequelize';
import { SequelizeStorage, Umzug } from 'umzug';
import { Collection, CollectionOptions, RepositoryType } from './collection';
@ -58,11 +58,12 @@ import {
SyncListener,
UpdateListener,
UpdateWithAssociationsListener,
ValidateListener,
ValidateListener
} from './types';
import { patchSequelizeQueryInterface, snakeCase } from './utils';
import DatabaseUtils from './database-utils';
import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
export interface MergeOptions extends merge.Options {}
@ -150,6 +151,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
migrator: Umzug;
migrations: Migrations;
fieldTypes = new Map();
fieldValueParsers = new Map();
options: IDatabaseOptions;
models = new Map<string, ModelStatic<Model>>();
repositories = new Map<string, RepositoryType>();
@ -227,6 +229,8 @@ export class Database extends EventEmitter implements AsyncEmitter {
});
}
registerFieldValueParsers(this);
this.initOperators();
const migratorOptions: any = this.options.migrator || {};
@ -473,6 +477,20 @@ export class Database extends EventEmitter implements AsyncEmitter {
}
}
registerFieldValueParsers(parsers: MapOf<any>) {
for (const [type, parser] of Object.entries(parsers)) {
this.fieldValueParsers.set(type, parser);
}
}
buildFieldValueParser<T extends BaseValueParser>(field: Field, ctx: any) {
const Parser = this.fieldValueParsers.has(field.type)
? this.fieldValueParsers.get(field.type)
: this.fieldValueParsers.get('default');
const parser = new Parser(field, ctx);
return parser as T;
}
registerModels(models: MapOf<ModelStatic<any>>) {
for (const [type, schemaType] of Object.entries(models)) {
this.models.set(type, schemaType);

View File

@ -1,5 +1,5 @@
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field, ValueParser } from './field';
import { BaseColumnFieldOptions, Field } from './field';
export class ArrayField extends Field {
get dataType() {
@ -28,51 +28,6 @@ export class ArrayField extends Field {
super.unbind();
this.off('beforeSave', this.sortValue);
}
buildValueParser(ctx: any) {
return new ArrayValueParser(this, ctx);
}
}
export class ArrayValueParser extends ValueParser {
async setValue(value: any) {
const { map, set } = this.getOptions();
const values = this.toArr(value);
if (set.size > 0) {
const filtered = values.map((v) => (map.has(v) ? map.get(v) : v)).filter((v) => set.has(v));
if (values.length === filtered.length) {
this.value = filtered;
} else {
this.errors.push('No matching option found');
}
} else {
this.value = values;
}
}
getOptions() {
const options = this.field.options?.['uiSchema']?.enum || [];
const map = new Map();
const set = new Set();
for (const option of options) {
set.add(option.value);
set.add(option.label);
map.set(option.label, option.value);
}
return { map, set };
}
toArr(value) {
let values = [];
if (!value) {
values = [];
} else if (typeof value === 'string') {
values = value.split(',');
} else if (Array.isArray(value)) {
values = value;
}
return values;
}
}
export interface ArrayFieldOptions extends BaseColumnFieldOptions {

View File

@ -2,7 +2,6 @@ import { omit } from 'lodash';
import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
import { Reference } from '../features/ReferencesMap';
import { checkIdentifier } from '../utils';
import { ToOneValueParser } from './has-one-field';
import { BaseRelationFieldOptions, RelationField } from './relation-field';
export class BelongsToField extends RelationField {
@ -116,10 +115,6 @@ export class BelongsToField extends RelationField {
collection.model.refreshAttributes();
// this.collection.removeIndex([this.options.foreignKey]);
}
buildValueParser(ctx: any) {
return new ToOneValueParser(this, ctx);
}
}
export interface BelongsToFieldOptions extends BaseRelationFieldOptions, SequelizeBelongsToOptions {

View File

@ -2,10 +2,8 @@ import { omit } from 'lodash';
import { BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize';
import { Collection } from '../collection';
import { Reference } from '../features/ReferencesMap';
import { Repository } from '../repository';
import { checkIdentifier } from '../utils';
import { BelongsToField } from './belongs-to-field';
import { ValueParser } from './field';
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
export class BelongsToManyField extends RelationField {
@ -125,48 +123,6 @@ export class BelongsToManyField extends RelationField {
this.clearAccessors();
delete collection.model.associations[this.name];
}
buildValueParser(ctx: any) {
return new ToManyValueParser(this, ctx);
}
}
export class ToManyValueParser extends ValueParser {
async setValue(value: any) {
const fieldNames = this.getFileNames();
console.log('fieldNames', fieldNames, this.ctx.column);
if (this.isInterface('chinaRegion')) {
const repository = this.field.database.getRepository(this.field.target) as Repository;
this.value = await Promise.all(
value.split('/').map(async (v) => {
const instance = await repository.findOne({ filter: { [fieldNames.label]: v.trim() } });
return instance ? instance.get(fieldNames.value) : v;
}),
);
} else {
const dataIndex = this.ctx?.column?.dataIndex || [];
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
return;
}
const field = this.ctx.column.dataIndex[1];
const repository = this.field.database.getRepository(this.field.target) as Repository;
this.value = await Promise.all(
value.split(',').map(async (v) => {
const instance = await repository.findOne({ filter: { [field]: v.trim() } });
return instance ? instance.get(fieldNames.value) : v;
}),
);
}
}
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;
}
}
export interface BelongsToManyFieldOptions

View File

@ -1,42 +1,10 @@
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field, ValueParser } from './field';
import { BaseColumnFieldOptions, Field } from './field';
export class BooleanField extends Field {
get dataType() {
return DataTypes.BOOLEAN;
}
buildValueParser(ctx: any) {
return new BooleanValueParser(this, ctx);
}
}
export class BooleanValueParser extends ValueParser {
async setValue(value: any) {
// Boolean
if (typeof value === 'boolean') {
this.value = value;
}
// Number
else if (typeof value === 'number' && [0, 1].includes(value)) {
this.value = value === 1;
}
// String
else if (typeof value === 'string') {
if (!value) {
this.value = null;
}
if (['1', 'y', 'yes', 'true', '是'].includes(value.toLowerCase())) {
this.value = true;
} else if (['0', 'n', 'no', 'false', '否'].includes(value.toLowerCase())) {
this.value = false;
} else {
this.errors.push(`${JSON.stringify(this.value)} value invalid`);
}
} else {
this.errors.push(`${JSON.stringify(this.value)} value invalid`);
}
}
}
export interface BooleanFieldOptions extends BaseColumnFieldOptions {

View File

@ -1,38 +1,10 @@
import { moment2str } from '@nocobase/utils';
import moment, { isDate, isMoment } from 'moment';
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field, ValueParser } from './field';
import { BaseColumnFieldOptions, Field } from './field';
export class DateField extends Field {
get dataType() {
return DataTypes.DATE(3);
}
buildValueParser(ctx: any) {
return new DateValueParser(this, ctx);
}
}
export class DateValueParser extends ValueParser {
async setValue(value: any) {
if (isMoment(value)) {
this.value = value;
} else if (isDate(value)) {
this.value = value;
} else if (typeof value === 'string') {
const props = this.getProps();
const m = moment(value);
if (m.isValid()) {
this.value = moment2str(m, props);
} else {
this.errors.push('Invalid date');
}
}
}
getProps() {
return this.field.options?.uiSchema?.['x-component-props'] || {};
}
}
export interface DateFieldOptions extends BaseColumnFieldOptions {

View File

@ -238,33 +238,4 @@ export abstract class Field {
typeToString() {
return this.dataType.toString();
}
buildValueParser(ctx: any) {
return new ValueParser(this, ctx);
}
}
export class ValueParser {
ctx: any;
field: any;
value: any;
errors = [];
constructor(field: any, ctx: any) {
this.field = field;
this.ctx = ctx;
}
toString() {
return this.value;
}
getValue() {
return this.value;
}
async setValue(value: any) {
console.log(this.field.name, value);
this.value = value;
}
}

View File

@ -10,7 +10,6 @@ import {
import { Collection } from '../collection';
import { Reference } from '../features/ReferencesMap';
import { checkIdentifier } from '../utils';
import { ToManyValueParser } from './belongs-to-many-field';
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
export interface HasManyFieldOptions extends HasManyOptions {
@ -186,10 +185,6 @@ export class HasManyField extends RelationField {
// @ts-ignore
collection.model.refreshAttributes();
}
buildValueParser(ctx: any) {
return new ToManyValueParser(this, ctx);
}
}
export interface HasManyFieldOptions extends MultipleRelationFieldOptions, SequelizeHasManyOptions {

View File

@ -9,9 +9,7 @@ import {
} from 'sequelize';
import { Collection } from '../collection';
import { Reference } from '../features/ReferencesMap';
import { Repository } from '../repository';
import { checkIdentifier } from '../utils';
import { ValueParser } from './field';
import { BaseRelationFieldOptions, RelationField } from './relation-field';
export interface HasOneFieldOptions extends HasOneOptions {
@ -193,36 +191,6 @@ export class HasOneField extends RelationField {
// @ts-ignore
collection.model.refreshAttributes();
}
buildValueParser(ctx: any) {
return new ToOneValueParser(this, ctx);
}
}
export class ToOneValueParser extends ValueParser {
async setValue(value: any) {
const fieldNames = this.getFileNames();
console.log('fieldNames', fieldNames, this.ctx.column);
const dataIndex = this.ctx?.column?.dataIndex || [];
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
return;
}
const field = this.ctx.column.dataIndex[1];
const repository = this.field.database.getRepository(this.field.target) as Repository;
const instance = await repository.findOne({ filter: { [field]: value.trim() } });
if (instance) {
this.value = instance.get(fieldNames.value);
}
}
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;
}
}
export interface HasOneFieldOptions extends BaseRelationFieldOptions, SequelizeHasOneOptions {

View File

@ -1,41 +1,8 @@
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field, ValueParser } from './field';
import { BaseColumnFieldOptions, Field } from './field';
abstract class NumberField extends Field {
buildValueParser(ctx: any) {
return new NumberValueParser(this, ctx);
}
}
function percent2float(value: string) {
const index = value.indexOf('.');
if (index === -1) {
return parseFloat(value) / 100;
}
const repeat = value.length - index - 2;
const v = parseInt('1' + '0'.repeat(repeat));
return (parseFloat(value) * v) / (100 * v);
}
export class NumberValueParser extends ValueParser {
async setValue(value: any) {
if (value === null || value === undefined || typeof value === 'number') {
this.value = value;
}
if (typeof value === 'string') {
if (!value) {
this.value = null;
} else if (['n/a', '-'].includes(value.toLowerCase())) {
this.value = null;
} else if (value.endsWith('%')) {
this.value = percent2float(value);
console.log(value, this.value);
} else {
const val = +value;
this.value = isNaN(val) ? null : val;
}
}
}
}
export class IntegerField extends NumberField {

View File

@ -1,44 +1,10 @@
import { DataTypes } from 'sequelize';
import { BaseColumnFieldOptions, Field, ValueParser } from './field';
import { BaseColumnFieldOptions, Field } from './field';
export class StringField extends Field {
get dataType() {
return DataTypes.STRING;
}
buildValueParser(ctx: any) {
return new StringValueParser(this, ctx);
}
}
export class StringValueParser extends ValueParser {
async setValue(value: any) {
const { map, set } = this.getOptions();
if (set.size > 0) {
if (map.has(value)) {
value = map.get(value);
}
if (set.has(value)) {
this.value = value;
} else {
this.errors.push('No matching option found');
}
} else {
this.value = value;
}
}
getOptions() {
const options = this.field.options?.['uiSchema']?.enum || [];
const map = new Map();
const set = new Set();
for (const option of options) {
set.add(option.value);
set.add(option.label);
map.set(option.label, option.value);
}
return { map, set };
}
}
export interface StringFieldOptions extends BaseColumnFieldOptions {

View File

@ -1,9 +1,12 @@
export { DataTypes, ModelStatic, Op, SyncOptions } from 'sequelize';
export * from './collection';
export * from './inherited-collection';
export * from './collection-importer';
export * from './database';
export { Database as default } from './database';
export * from './field-repository/array-field-repository';
export * from './fields';
export * from './filter-match';
export * from './inherited-collection';
export * from './magic-attribute-model';
export * from './migration';
export * from './mock-database';
@ -15,7 +18,6 @@ export * from './relation-repository/multiple-relation-repository';
export * from './relation-repository/single-relation-repository';
export * from './repository';
export * from './update-associations';
export * from './collection-importer';
export * from './filter-match';
export * from './field-repository/array-field-repository';
export { snakeCase } from './utils';
export * from './value-parsers';

View File

@ -1,8 +1,7 @@
import crypto from 'crypto';
import Database from './database';
import { IdentifierError } from './errors/identifier-error';
import { Model } from './model';
import lodash from 'lodash';
import Database from './database';
type HandleAppendsQueryOptions = {
templateModel: any;
@ -116,3 +115,13 @@ export function patchSequelizeQueryInterface(db: Database) {
};
}
}
export function percent2float(value: string) {
const index = value.indexOf('.');
if (index === -1) {
return parseFloat(value) / 100;
}
const repeat = value.length - index - 2;
const v = parseInt('1' + '0'.repeat(repeat));
return (parseFloat(value) * v) / (100 * v);
}

View File

@ -0,0 +1,42 @@
import { BaseValueParser } from './base-value-parser';
export class ArrayValueParser extends BaseValueParser {
async setValue(value: any) {
const { map, set } = this.getOptions();
const values = this.toArr(value);
if (set.size > 0) {
const filtered = values.map((v) => (map.has(v) ? map.get(v) : v)).filter((v) => set.has(v));
if (values.length === filtered.length) {
this.value = filtered;
} else {
this.errors.push(`No matching option found - ${JSON.stringify(value)}`);
}
} else {
this.value = values;
}
}
getOptions() {
const options = this.field.options?.['uiSchema']?.enum || [];
const map = new Map();
const set = new Set();
for (const option of options) {
set.add(option.value);
set.add(option.label);
map.set(option.label, option.value);
}
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;
}
}

View File

@ -0,0 +1,23 @@
export class BaseValueParser {
ctx: any;
field: any;
value: any;
errors: string[] = [];
constructor(field: any, ctx: any) {
this.field = field;
this.ctx = ctx;
}
toString() {
return this.value;
}
getValue() {
return this.value;
}
async setValue(value: any) {
this.value = value;
}
}

View File

@ -0,0 +1,29 @@
import { BaseValueParser } from './base-value-parser';
export class BooleanValueParser extends BaseValueParser {
async setValue(value: any) {
// Boolean
if (typeof value === 'boolean') {
this.value = value;
}
// Number
else if (typeof value === 'number' && [0, 1].includes(value)) {
this.value = value === 1;
}
// String
else if (typeof value === 'string') {
if (!value) {
this.value = null;
}
if (['1', 'y', 'yes', 'true', '是'].includes(value.toLowerCase())) {
this.value = true;
} else if (['0', 'n', 'no', 'false', '否'].includes(value.toLowerCase())) {
this.value = false;
} else {
this.errors.push(`Value invalid - ${JSON.stringify(this.value)}`);
}
} else {
this.errors.push(`Value invalid - ${JSON.stringify(this.value)}`);
}
}
}

View File

@ -0,0 +1,25 @@
import { moment2str } from '@nocobase/utils';
import moment, { isDate, isMoment } from 'moment';
import { BaseValueParser } from "./base-value-parser";
export class DateValueParser extends BaseValueParser {
async setValue(value: any) {
if (isMoment(value)) {
this.value = value;
} else if (isDate(value)) {
this.value = value;
} else if (typeof value === 'string') {
const props = this.getProps();
const m = moment(value);
if (m.isValid()) {
this.value = moment2str(m, props);
} else {
this.errors.push('Invalid date');
}
}
}
getProps() {
return this.field.options?.uiSchema?.['x-component-props'] || {};
}
}

View File

@ -0,0 +1,46 @@
import { Database } from '../database';
import { ArrayValueParser } from './array-value-parser';
import { BaseValueParser } from './base-value-parser';
import { BooleanValueParser } from './boolean-value-parser';
import { DateValueParser } from './date-value-parser';
import { JsonValueParser } from './json-value-parser';
import { NumberValueParser } from './number-value-parser';
import { StringValueParser } from './string-value-parser';
import { ToManyValueParser } from './to-many-value-parser';
import { ToOneValueParser } from './to-one-value-parser';
export function registerFieldValueParsers(db: Database) {
db.registerFieldValueParsers({
default: BaseValueParser,
array: ArrayValueParser,
set: ArrayValueParser,
boolean: BooleanValueParser,
date: DateValueParser,
json: JsonValueParser,
jsonb: JsonValueParser,
number: NumberValueParser,
integer: NumberValueParser,
bigInt: NumberValueParser,
float: NumberValueParser,
double: NumberValueParser,
real: NumberValueParser,
decimal: NumberValueParser,
string: StringValueParser,
hasOne: ToOneValueParser,
hasMany: ToManyValueParser,
belongsTo: ToOneValueParser,
belongsToMany: ToManyValueParser,
});
}
export {
ArrayValueParser,
BaseValueParser,
BooleanValueParser,
DateValueParser,
JsonValueParser,
NumberValueParser,
StringValueParser,
ToManyValueParser,
ToOneValueParser,
};

View File

@ -0,0 +1,7 @@
import { BaseValueParser } from './base-value-parser';
export class JsonValueParser extends BaseValueParser {
async setValue(value: any) {
}
}

View File

@ -0,0 +1,23 @@
import { percent2float } from '../utils';
import { BaseValueParser } from './base-value-parser';
export class NumberValueParser extends BaseValueParser {
async setValue(value: any) {
if (value === null || value === undefined || typeof value === 'number') {
this.value = value;
}
if (typeof value === 'string') {
if (!value) {
this.value = null;
} else if (['n/a', '-'].includes(value.toLowerCase())) {
this.value = null;
} else if (value.endsWith('%')) {
this.value = percent2float(value);
console.log(value, this.value);
} else {
const val = +value;
this.value = isNaN(val) ? null : val;
}
}
}
}

View File

@ -0,0 +1,31 @@
import { BaseValueParser } from './base-value-parser';
export class StringValueParser extends BaseValueParser {
async setValue(value: any) {
const { map, set } = this.getOptions();
if (set.size > 0) {
if (map.has(value)) {
value = map.get(value);
}
if (set.has(value)) {
this.value = value;
} else {
this.errors.push(`No matching option found - ${JSON.stringify(value)}`);
}
} else {
this.value = value;
}
}
getOptions() {
const options = this.field.options?.['uiSchema']?.enum || [];
const map = new Map();
const set = new Set();
for (const option of options) {
set.add(option.value);
set.add(option.label);
map.set(option.label, option.value);
}
return { map, set };
}
}

View File

@ -0,0 +1,54 @@
import { Repository } from '../repository';
import { BaseValueParser } from './base-value-parser';
export class ToManyValueParser extends BaseValueParser {
async setValue(value: any) {
const fieldNames = this.getFileNames();
if (this.isInterface('chinaRegion')) {
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: { [fieldNames.label]: v.trim() } });
if (!instance) {
throw new Error(`"${v}" does not exist`);
}
return instance.get(fieldNames.value);
}),
);
} catch (error) {
this.errors.push(error.message);
}
} else {
const dataIndex = this.ctx?.column?.dataIndex || [];
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);
}
}
}
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;
}
}

View File

@ -0,0 +1,29 @@
import { Repository } from '../repository';
import { BaseValueParser } from './base-value-parser';
export class ToOneValueParser extends BaseValueParser {
async setValue(value: any) {
const fieldNames = this.getFileNames();
const dataIndex = this.ctx?.column?.dataIndex || [];
if (Array.isArray(dataIndex) && dataIndex.length < 2) {
return;
}
const field = this.ctx.column.dataIndex[1];
const repository = this.field.database.getRepository(this.field.target) as Repository;
const instance = await repository.findOne({ filter: { [field]: value.trim() } });
if (instance) {
this.value = instance.get(fieldNames.value);
} else {
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;
}
}

View File

@ -40,9 +40,6 @@ export const useDownloadXlsxTemplateAction = () => {
if (!field) {
return;
}
if (field.interface === 'chinaRegion') {
column.dataIndex.push('name');
}
column.defaultTitle = compile(field?.uiSchema?.title) || field.name;
if (column.dataIndex.length > 1) {
const subField = getCollectionJoinField(`${name}.${column.dataIndex.join('.')}`);
@ -51,6 +48,9 @@ export const useDownloadXlsxTemplateAction = () => {
}
column.defaultTitle = column.defaultTitle + '/' + compile(subField?.uiSchema?.title) || subField.name;
}
if (field.interface === 'chinaRegion') {
column.dataIndex.push('name');
}
return column;
})
.filter(Boolean);
@ -94,9 +94,6 @@ export const useImportStartAction = () => {
if (!field) {
return;
}
if (field.interface === 'chinaRegion') {
column.dataIndex.push('name');
}
column.defaultTitle = compile(field?.uiSchema?.title) || field.name;
if (column.dataIndex.length > 1) {
const subField = getCollectionJoinField(`${name}.${column.dataIndex.join('.')}`);
@ -105,6 +102,9 @@ export const useImportStartAction = () => {
}
column.defaultTitle = column.defaultTitle + '/' + compile(subField?.uiSchema?.title) || subField.name;
}
if (field.interface === 'chinaRegion') {
column.dataIndex.push('name');
}
return column;
})
.filter(Boolean);

View File

@ -77,7 +77,7 @@ class Importer {
if (value === undefined || value === null) {
continue;
}
const parser = field.buildValueParser({ ...this.context, column });
const parser = this.context.db.buildFieldValueParser(field, { ...this.context, column });
await parser.setValue(typeof value === 'string' ? value.trim() : value);
value = parser.getValue();
if (parser.errors.length > 0) {
@ -127,6 +127,7 @@ class Importer {
result[0].push(instance);
} catch (error) {
this.context.log.error(error, row);
row.push(error.message);
result[1].push(row);
}
}

View File

@ -2,6 +2,7 @@ import { InstallOptions, Plugin } from '@nocobase/server';
import { resolve } from 'path';
import { getConfiguration, setConfiguration } from './actions';
import { CircleField, LineStringField, PointField, PolygonField } from './fields';
import { CircleValueParser, LineStringValueParser, PointValueParser, PolygonValueParser } from './value-parsers';
export class MapPlugin extends Plugin {
afterAdd() {}
@ -13,8 +14,13 @@ export class MapPlugin extends Plugin {
lineString: LineStringField,
circle: CircleField,
};
this.db.registerFieldTypes(fields);
this.db.registerFieldValueParsers({
point: PointValueParser,
polygon: PolygonValueParser,
lineString: LineStringValueParser,
circle: CircleValueParser,
});
}
async load() {

View File

@ -0,0 +1,55 @@
import { BaseValueParser } from '@nocobase/database';
export 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');
}
}
}
export class PolygonValueParser extends BaseValueParser {
async setValue(value) {
if (Array.isArray(value)) {
this.value = value;
} else if (typeof value === 'string') {
this.value = value
.substring(1, value.length - 1)
.split('),(')
.map((v) => v.split(','));
} else {
this.errors.push('Value invalid');
}
}
}
export class LineStringValueParser extends BaseValueParser {
async setValue(value) {
if (Array.isArray(value)) {
this.value = value;
} else if (typeof value === 'string') {
this.value = value
.substring(1, value.length - 1)
.split('),(')
.map((v) => v.split(','));
} else {
this.errors.push('Value invalid');
}
}
}
export class CircleValueParser 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');
}
}
}