Feature: plugin-china-region (#66)
* feat: add core function for china region cascade select * fix: test case * refactor: use belongsToMany for region field and fix component * fix: data import * fix: remove sort field * fix: clear on changeOnSelect is false
This commit is contained in:
parent
ce5690e25c
commit
662619b91c
@ -30,6 +30,7 @@
|
||||
"@nocobase/database": "^0.3.0-alpha.0",
|
||||
"@nocobase/father-build": "^0.3.0-alpha.0",
|
||||
"@nocobase/plugin-action-logs": "^0.3.0-alpha.0",
|
||||
"@nocobase/plugin-china-region": "^0.3.0-alpha.0",
|
||||
"@nocobase/plugin-collections": "^0.3.0-alpha.0",
|
||||
"@nocobase/plugin-pages": "^0.3.0-alpha.0",
|
||||
"@nocobase/server": "^0.3.0-alpha.0",
|
||||
|
@ -68,5 +68,6 @@ api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-use
|
||||
api.registerPlugin('plugin-file-manager', [path.resolve(__dirname, '../../../plugin-file-manager'), {}]);
|
||||
api.registerPlugin('plugin-permissions', [path.resolve(__dirname, '../../../plugin-permissions'), {}]);
|
||||
api.registerPlugin('plugin-automations', [path.resolve(__dirname, '../../../plugin-automations'), {}]);
|
||||
api.registerPlugin('plugin-china-region', [path.resolve(__dirname, '../../../plugin-china-region'), {}]);
|
||||
|
||||
export default api;
|
||||
|
13
packages/app/src/api/migrations/add-china-region-data.ts
Normal file
13
packages/app/src/api/migrations/add-china-region-data.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { init } from '@nocobase/plugin-china-region/src/db/seeders';
|
||||
import api from '../app';
|
||||
|
||||
(async () => {
|
||||
await api.loadPlugins();
|
||||
await api.database.sync();
|
||||
await init(api);
|
||||
})().then(() => {
|
||||
process.exit(0);
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
@ -6,8 +6,9 @@ global.sync = {
|
||||
},
|
||||
};
|
||||
|
||||
import api from '../app';
|
||||
import Database from '@nocobase/database';
|
||||
import { init as chinaRegionSeederInit } from '@nocobase/plugin-china-region/src/db/seeders';
|
||||
import api from '../app';
|
||||
|
||||
const data = [
|
||||
{
|
||||
@ -234,6 +235,9 @@ const data = [
|
||||
await Action.bulkCreate([
|
||||
]);
|
||||
|
||||
// 导入地域数据
|
||||
await chinaRegionSeederInit(api);
|
||||
|
||||
await database.getModel('collections').import(require('./collections/example').default);
|
||||
await database.getModel('collections').import(require('./collections/authors').default);
|
||||
await database.getModel('collections').import(require('./collections/books').default);
|
||||
|
125
packages/app/src/components/form.fields/cascader/index.tsx
Normal file
125
packages/app/src/components/form.fields/cascader/index.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from '@formily/react-schema-renderer'
|
||||
import { Cascader as AntdCascader } from 'antd'
|
||||
import { useRequest } from 'umi';
|
||||
import api from '@/api-client';
|
||||
import {
|
||||
transformDataSourceKey,
|
||||
mapStyledProps,
|
||||
mapTextComponent
|
||||
} from '../shared'
|
||||
|
||||
function findTreeNode(tree, values, { value = 'value', children = 'children' }) {
|
||||
let node, i;
|
||||
for (node = tree, i = 0; node && values[i]; i++ ) {
|
||||
node = (node[children] || []).find(item => item[value] === values[i][value]);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export const Cascader = connect({
|
||||
getProps: mapStyledProps,
|
||||
getComponent: mapTextComponent,
|
||||
})(function (props) {
|
||||
const {
|
||||
disabled,
|
||||
target,
|
||||
labelField,
|
||||
valueField = 'id',
|
||||
parentField,
|
||||
maxLevel,
|
||||
changeOnSelect,
|
||||
value = [],
|
||||
onChange,
|
||||
// TODO(feature): 增加静态数据支持
|
||||
// dataSource: []
|
||||
} = props;
|
||||
const fieldNames = {
|
||||
label: labelField,
|
||||
value: valueField,
|
||||
children: 'children'
|
||||
};
|
||||
const [options, setOptions] = useState([]);
|
||||
|
||||
const { loading, run } = useRequest(async (selectedOptions = []) => {
|
||||
if (maxLevel != null && selectedOptions.length >= maxLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const last = selectedOptions[selectedOptions.length - 1] || null;
|
||||
if (last) {
|
||||
if (last.isLeaf) {
|
||||
return;
|
||||
}
|
||||
last.loading = true;
|
||||
}
|
||||
|
||||
return api.resource(target).list({
|
||||
filter: {
|
||||
[parentField]: last && last[valueField]
|
||||
},
|
||||
perPage: -1,
|
||||
sort: [valueField]
|
||||
});
|
||||
// TODO(bug): 关联资源加载问题较多,暂时先用 filter 解决
|
||||
// return api.resource(`${target}.${target}`).list({
|
||||
// associatedKey: last,
|
||||
// perPage: -1
|
||||
// });
|
||||
}, {
|
||||
manual: true,
|
||||
onSuccess(result, [selectedOptions = []]) {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = result.map(item => ({
|
||||
...item,
|
||||
isLeaf: maxLevel != null && item.level >= maxLevel
|
||||
}));
|
||||
// 找到已有值指向的 options 节点
|
||||
const root = { [fieldNames.children]: options };
|
||||
const node = findTreeNode(root, selectedOptions, fieldNames);
|
||||
|
||||
if (node && node !== root) {
|
||||
node.children = data;
|
||||
node.loading = false;
|
||||
// use spread array to avoid popup to be collapsed
|
||||
setOptions([...options]);
|
||||
} else {
|
||||
setOptions(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 根据 value 的值,按需预加载相应的数据
|
||||
useEffect(() => {
|
||||
if (value.length) {
|
||||
value.reduce((promise, option, i) => promise.then(() => run(value.slice(0, i))), Promise.resolve());
|
||||
} else {
|
||||
run([]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AntdCascader
|
||||
disabled={disabled}
|
||||
options={options}
|
||||
value={value.map(item => item[valueField])}
|
||||
onChange={(v, selected) => {
|
||||
if (maxLevel != null && v.length < maxLevel) {
|
||||
run(selected);
|
||||
}
|
||||
if (changeOnSelect || !v.length || v.length >= maxLevel) {
|
||||
onChange(selected);
|
||||
}
|
||||
}}
|
||||
loadData={run}
|
||||
changeOnSelect={changeOnSelect}
|
||||
fieldNames={fieldNames}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
export default Cascader
|
@ -0,0 +1 @@
|
||||
import 'antd/lib/cascader/style/index'
|
@ -17,6 +17,7 @@ import { Filter } from './filter'
|
||||
import { RemoteSelect } from './remote-select'
|
||||
import { DrawerSelect } from './drawer-select'
|
||||
import { SubTable } from './sub-table'
|
||||
import { Cascader } from './cascader'
|
||||
import { Icon } from './icons'
|
||||
import { ColorSelect } from './color-select'
|
||||
import { Permissions } from './permissions'
|
||||
@ -29,6 +30,7 @@ export const setup = () => {
|
||||
time: TimePicker,
|
||||
timerange: TimePicker.RangePicker,
|
||||
transfer: Transfer,
|
||||
cascader: Cascader,
|
||||
boolean: Checkbox,
|
||||
switch: Switch,
|
||||
checkbox: Checkbox,
|
||||
|
@ -44,12 +44,12 @@ const createEnum = (enums: any) => {
|
||||
}
|
||||
|
||||
export const Select: React.FC<SelectProps> = styled((props: SelectProps) => {
|
||||
const { dataSource = [], onChange, value, ...others } = props
|
||||
const { dataSource = [], onChange, value, ...others } = props;
|
||||
const children = createEnum(dataSource).map(item => {
|
||||
const { label, value, children = [], ...others } = item
|
||||
const { label, value, key, children = [], ...others } = item;
|
||||
if (children.length) {
|
||||
return (
|
||||
<AntSelect.OptGroup label={label}>
|
||||
<AntSelect.OptGroup key={key} label={label}>
|
||||
{children.map(({value, label, ...others}: any) => (
|
||||
<AntSelect.Option
|
||||
key={value}
|
||||
|
@ -256,13 +256,13 @@ export function LinkToFieldLink(props) {
|
||||
e.stopPropagation();
|
||||
setVisible(true);
|
||||
}}>{data[labelField]}</a>
|
||||
<Drawer
|
||||
<Drawer
|
||||
// @ts-ignore
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={'noco-drawer'}
|
||||
bodyStyle={{padding: 0}}
|
||||
bodyStyle={{padding: 0}}
|
||||
width={'40%'}
|
||||
title={`查看${title}详情`}
|
||||
visible={visible}
|
||||
@ -361,20 +361,34 @@ export function AttachmentFieldItem(props: any) {
|
||||
);
|
||||
}
|
||||
|
||||
function LogField(props) {
|
||||
export function LogField(props) {
|
||||
const { value = {} } = props;
|
||||
return (
|
||||
<div>{value.title||value.name}</div>
|
||||
)
|
||||
}
|
||||
|
||||
function LogFieldValue(props) {
|
||||
export function LogFieldValue(props) {
|
||||
const { value, schema, data } = props;
|
||||
return (
|
||||
<div>{JSON.stringify(value)}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ChinaRegion(props: any) {
|
||||
const { schema, value } = props;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const values = (Array.isArray(value) ? value : [value])
|
||||
.sort((a, b) => a.level !== b.level ? a.level - b.level : a.sort - b.sort);
|
||||
return (
|
||||
<div className={'china-region-field'}>
|
||||
{values.map(item => item.name).join('/')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
registerFieldComponents({
|
||||
string: StringField,
|
||||
textarea: TextareaField,
|
||||
@ -396,6 +410,7 @@ registerFieldComponents({
|
||||
attachment: AttachmentField,
|
||||
'logs.field': LogField,
|
||||
'logs.fieldValue': LogFieldValue,
|
||||
chinaRegion: ChinaRegion,
|
||||
});
|
||||
|
||||
export default function Field(props: any) {
|
||||
|
10
packages/plugin-china-region/package.json
Normal file
10
packages/plugin-china-region/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-china-region",
|
||||
"version": "0.3.0-alpha.0",
|
||||
"main": "lib/index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nocobase/database": "^0.3.0-alpha.0",
|
||||
"china-division": "2.3.1"
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import { TableOptions } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'china_regions',
|
||||
title: '中国行政区划',
|
||||
internal: true,
|
||||
developerMode: true,
|
||||
fields: [
|
||||
// 如使用代码作为 id 可能更节省,但由于代码数字最长为 12 字节,除非使用 bigint(64) 才够放置
|
||||
{
|
||||
name: 'code',
|
||||
title: '代码',
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
title: '名称',
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'parent',
|
||||
title: '从属',
|
||||
interface: 'linkTo',
|
||||
type: 'belongsTo',
|
||||
target: 'china_regions',
|
||||
targetKey: 'code',
|
||||
foreignKey: 'parent_code',
|
||||
},
|
||||
{
|
||||
name: 'children',
|
||||
title: '下辖',
|
||||
interface: 'linkTo',
|
||||
type: 'hasMany',
|
||||
target: 'china_regions',
|
||||
sourceKey: 'code',
|
||||
foreignKey: 'parent_code'
|
||||
},
|
||||
{
|
||||
name: 'level',
|
||||
title: '层级',
|
||||
type: 'integer'
|
||||
}
|
||||
]
|
||||
} as TableOptions;
|
1
packages/plugin-china-region/src/db/seeders/index.ts
Normal file
1
packages/plugin-china-region/src/db/seeders/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as init } from './init';
|
47
packages/plugin-china-region/src/db/seeders/init.ts
Normal file
47
packages/plugin-china-region/src/db/seeders/init.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { provinces, cities, areas, streets, villages } from 'china-division';
|
||||
|
||||
export default async function({ database }) {
|
||||
const Model = database.getModel('china_regions');
|
||||
// TODO(feature): 可以考虑再加一级大区,编码规则应该是支持的
|
||||
// 华北、东北、华东、华南、西南、西北、华中
|
||||
const timer = Date.now();
|
||||
|
||||
await Model.bulkCreate(provinces.map(item => ({
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
level: 1
|
||||
})));
|
||||
|
||||
await Model.bulkCreate(cities.map(item => ({
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
level: 2,
|
||||
parent_code: item.provinceCode
|
||||
})));
|
||||
|
||||
await Model.bulkCreate(areas.map(item => ({
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
level: 3,
|
||||
parent_code: item.cityCode
|
||||
})));
|
||||
|
||||
// 乡级数据 2856 条
|
||||
await Model.bulkCreate(streets.map(item => ({
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
level: 4,
|
||||
parent_code: item.areaCode
|
||||
})));
|
||||
|
||||
// 村级数据 658001 条
|
||||
await Model.bulkCreate(villages.map(item => ({
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
level: 5,
|
||||
parent_code: item.streetCode
|
||||
})));
|
||||
|
||||
const count = await Model.count();
|
||||
console.log(`${count} rows of region data imported in ${(Date.now() - timer) / 1000}s`);
|
||||
};
|
18
packages/plugin-china-region/src/server.ts
Normal file
18
packages/plugin-china-region/src/server.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import path from 'path';
|
||||
// import actions from '@nocobase/actions';
|
||||
|
||||
// 不从主入口导出,考虑到加载的数据量太大比较占内存,只在迁移时处理
|
||||
// export * as seeders from './db/seeders';
|
||||
|
||||
export default async function (options = {}) {
|
||||
const { database, resourcer } = this;
|
||||
|
||||
database.import({
|
||||
directory: path.resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
// resourcer.define({
|
||||
// name: 'china_regions.china_regions',
|
||||
// actions: actions.common
|
||||
// });
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { Agent, getAgent, getApp } from '../';
|
||||
import { Application } from '@nocobase/server';
|
||||
import { options, types } from '../../interfaces';
|
||||
import { FieldModel } from '../../models';
|
||||
import { types } from '../../interfaces';
|
||||
|
||||
describe('models.field', () => {
|
||||
let app: Application;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TableOptions } from '@nocobase/database';
|
||||
import { options } from '../interfaces';
|
||||
import { types, getOptions } from '../interfaces';
|
||||
|
||||
export default {
|
||||
name: 'fields',
|
||||
@ -57,7 +57,7 @@ export default {
|
||||
name: 'interface',
|
||||
title: '字段类型',
|
||||
required: true,
|
||||
dataSource: options,
|
||||
dataSource: getOptions(),
|
||||
createOnly: true,
|
||||
component: {
|
||||
type: 'select',
|
||||
@ -65,6 +65,17 @@ export default {
|
||||
showInDetail: true,
|
||||
showInForm: true,
|
||||
"x-linkages": [
|
||||
// TODO(draft): 统一解决字段类型和配置参数联动的一种方式
|
||||
// {
|
||||
// type: 'value:schema',
|
||||
// target: 'options',
|
||||
// schema: {
|
||||
// 'x-component-props': {
|
||||
// fields: '{{ $self.values[1].fields || [] }}'
|
||||
// }
|
||||
// },
|
||||
// condition: '{{ !!$self.value }}'
|
||||
// },
|
||||
{
|
||||
"type": "value:visible",
|
||||
"target": "precision",
|
||||
@ -130,9 +141,31 @@ export default {
|
||||
"target": "required",
|
||||
"condition": "{{ ['createdAt', 'updatedAt', 'createdBy', 'updatedBy'].indexOf($self.value) === -1 }}"
|
||||
},
|
||||
{
|
||||
"type": "value:visible",
|
||||
"target": "maxLevel",
|
||||
"condition": "{{ ['chinaRegion'].includes($self.value) }}"
|
||||
},
|
||||
{
|
||||
"type": "value:visible",
|
||||
"target": "incompletely",
|
||||
"condition": "{{ ['chinaRegion'].includes($self.value) }}"
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// TODO(draft): 将 options 作为集合字段开放出来,可以动态的解决字段参数的配置表单联动问题
|
||||
// {
|
||||
// interface: 'json',
|
||||
// type: 'json',
|
||||
// name: 'options',
|
||||
// title: '配置信息',
|
||||
// defaultValue: {},
|
||||
// component: {
|
||||
// type: 'subFields',
|
||||
// showInForm: true,
|
||||
// },
|
||||
// },
|
||||
{
|
||||
interface: 'subTable',
|
||||
type: 'virtual',
|
||||
@ -250,6 +283,33 @@ export default {
|
||||
default: 'HH:mm:ss',
|
||||
},
|
||||
},
|
||||
// TODO(refactor): 此部分类型相关的参数,后期应拆分出去
|
||||
{
|
||||
name: 'maxLevel',
|
||||
title: '可选层级',
|
||||
interface: 'radio',
|
||||
type: 'virtual',
|
||||
dataSource: [
|
||||
{ value: 1, label: '省' },
|
||||
{ value: 2, label: '市' },
|
||||
{ value: 3, label: '区/县' },
|
||||
{ value: 4, label: '乡镇/街道' },
|
||||
{ value: 5, label: '村/居委会' },
|
||||
],
|
||||
component: {
|
||||
showInForm: true,
|
||||
default: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'incompletely',
|
||||
title: '可部分选择',
|
||||
interface: 'boolean',
|
||||
type: 'virtual',
|
||||
component: {
|
||||
showInForm: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
interface: 'linkTo',
|
||||
multiple: false,
|
||||
|
@ -4,90 +4,44 @@
|
||||
import * as types from './types';
|
||||
export * as types from './types';
|
||||
|
||||
export const options = [
|
||||
{
|
||||
key: 'basic',
|
||||
title: '基本类型',
|
||||
children: [
|
||||
types.string,
|
||||
types.textarea,
|
||||
types.phone,
|
||||
types.email,
|
||||
types.number,
|
||||
types.percent,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'media',
|
||||
title: '多媒体类型',
|
||||
children: [
|
||||
types.wysiwyg,
|
||||
types.attachment,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'choices',
|
||||
title: '选择类型',
|
||||
children: [
|
||||
types.boolean,
|
||||
types.select,
|
||||
types.multipleSelect,
|
||||
types.radio,
|
||||
types.checkboxes,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'datetime',
|
||||
title: '日期和时间',
|
||||
children: [
|
||||
types.datetime,
|
||||
types.time,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'relation',
|
||||
title: '关系类型',
|
||||
children: [
|
||||
types.subTable,
|
||||
types.linkTo,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'systemInfo',
|
||||
title: '系统信息',
|
||||
children: [
|
||||
types.createdAt,
|
||||
types.updatedAt,
|
||||
types.createdBy,
|
||||
types.updatedBy,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'developerMode',
|
||||
title: '开发者模式',
|
||||
children: [
|
||||
types.primaryKey,
|
||||
types.sort,
|
||||
types.password,
|
||||
types.json,
|
||||
types.icon,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'others',
|
||||
title: '其他',
|
||||
children: [
|
||||
types.description,
|
||||
],
|
||||
}
|
||||
].map(({key, title, children}: any) => ({
|
||||
key,
|
||||
label: title,
|
||||
children: children.map(child => ({
|
||||
label: child.title,
|
||||
value: child.options.interface,
|
||||
disabled: child.disabled,
|
||||
})),
|
||||
}));
|
||||
export const groupLabelMap = {
|
||||
basic: '基本类型',
|
||||
media: '多媒体类型',
|
||||
choices: '选择类型',
|
||||
datetime: '日期和时间',
|
||||
relation: '关系类型',
|
||||
systemInfo: '关系类型',
|
||||
developerMode: '开发者模式',
|
||||
others: '其他'
|
||||
};
|
||||
|
||||
export default options;
|
||||
export function getOptions() {
|
||||
return Object.keys(groupLabelMap).map(key => ({
|
||||
key,
|
||||
label: groupLabelMap[key],
|
||||
children: Object.values(types)
|
||||
.filter(type => type['group'] === key)
|
||||
.map(type => ({
|
||||
label: type.title,
|
||||
value: type.options.interface,
|
||||
// TODO(draft): 配置信息一并存到数据库方便字段配置时取出参与联动计算
|
||||
// properties: type.properties,
|
||||
disabled: type['disabled'],
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
export type interfaceType = {
|
||||
title: string,
|
||||
group?: string,
|
||||
options: {
|
||||
[key: string]: any
|
||||
},
|
||||
disabled?: boolean
|
||||
};
|
||||
|
||||
// TODO(draft)
|
||||
// 目前仅在内存中注册,应用启动时需要解决扩展字段读取并注册到内存
|
||||
export function register(type: interfaceType) {
|
||||
types[type.options.interface] = type;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
// TODO: interface 的修改
|
||||
export const string = {
|
||||
title: '单行文本',
|
||||
group: 'basic',
|
||||
options: {
|
||||
interface: 'string',
|
||||
type: 'string',
|
||||
@ -19,6 +20,7 @@ export const string = {
|
||||
|
||||
export const textarea = {
|
||||
title: '多行文本',
|
||||
group: 'basic',
|
||||
options: {
|
||||
interface: 'textarea',
|
||||
type: 'text',
|
||||
@ -31,6 +33,7 @@ export const textarea = {
|
||||
|
||||
export const phone = {
|
||||
title: '手机号码',
|
||||
group: 'basic',
|
||||
options: {
|
||||
interface: 'phone',
|
||||
type: 'string',
|
||||
@ -45,6 +48,7 @@ export const phone = {
|
||||
|
||||
export const email = {
|
||||
title: '邮箱',
|
||||
group: 'basic',
|
||||
options: {
|
||||
interface: 'email',
|
||||
type: 'string',
|
||||
@ -62,6 +66,7 @@ export const email = {
|
||||
*/
|
||||
export const number = {
|
||||
title: '数字',
|
||||
group: 'basic',
|
||||
options: {
|
||||
interface: 'number',
|
||||
type: 'float',
|
||||
@ -80,6 +85,7 @@ export const number = {
|
||||
*/
|
||||
export const percent = {
|
||||
title: '百分比',
|
||||
group: 'basic',
|
||||
options: {
|
||||
interface: 'percent',
|
||||
type: 'float',
|
||||
@ -94,6 +100,7 @@ export const percent = {
|
||||
|
||||
export const wysiwyg = {
|
||||
title: '可视化编辑器',
|
||||
group: 'media',
|
||||
disabled: true,
|
||||
options: {
|
||||
interface: 'wysiwyg',
|
||||
@ -109,6 +116,7 @@ export const wysiwyg = {
|
||||
*/
|
||||
export const attachment = {
|
||||
title: '附件',
|
||||
group: 'media',
|
||||
// disabled: true,
|
||||
options: {
|
||||
interface: 'attachment',
|
||||
@ -129,6 +137,7 @@ export const attachment = {
|
||||
*/
|
||||
export const select = {
|
||||
title: '下拉选择(单选)',
|
||||
group: 'choices',
|
||||
options: {
|
||||
interface: 'select',
|
||||
type: 'string',
|
||||
@ -152,6 +161,7 @@ export const select = {
|
||||
*/
|
||||
export const multipleSelect = {
|
||||
title: '下拉选择(多选)',
|
||||
group: 'choices',
|
||||
options: {
|
||||
interface: 'multipleSelect',
|
||||
type: 'json', // json 过滤
|
||||
@ -167,6 +177,7 @@ export const multipleSelect = {
|
||||
|
||||
export const radio = {
|
||||
title: '单选框',
|
||||
group: 'choices',
|
||||
options: {
|
||||
interface: 'radio',
|
||||
type: 'string',
|
||||
@ -180,6 +191,7 @@ export const radio = {
|
||||
|
||||
export const checkboxes = {
|
||||
title: '多选框',
|
||||
group: 'choices',
|
||||
options: {
|
||||
interface: 'checkboxes',
|
||||
type: 'json',
|
||||
@ -194,6 +206,7 @@ export const checkboxes = {
|
||||
|
||||
export const boolean = {
|
||||
title: '是/否',
|
||||
group: 'choices',
|
||||
options: {
|
||||
interface: 'boolean',
|
||||
type: 'boolean',
|
||||
@ -210,6 +223,7 @@ export const boolean = {
|
||||
*/
|
||||
export const datetime = {
|
||||
title: '日期',
|
||||
group: 'datetime',
|
||||
options: {
|
||||
interface: 'datetime',
|
||||
type: 'date',
|
||||
@ -222,10 +236,34 @@ export const datetime = {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
// TODO(draft): 配置参数的一种描述方式
|
||||
// properties: [
|
||||
// {
|
||||
// name: 'showTime',
|
||||
// title: '显示时间',
|
||||
// interface: 'boolean',
|
||||
// component: {
|
||||
// 'x-linkages': [
|
||||
// {
|
||||
// type: 'value:visible',
|
||||
// target: 'timeFormat',
|
||||
// condition: '{{ $value }}'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// interface: 'string',
|
||||
// name: 'timeFormat',
|
||||
// title: '时间格式',
|
||||
// filterable: false
|
||||
// }
|
||||
// ]
|
||||
};
|
||||
|
||||
export const time = {
|
||||
title: '时间',
|
||||
group: 'datetime',
|
||||
options: {
|
||||
interface: 'time',
|
||||
type: 'time',
|
||||
@ -275,6 +313,7 @@ export const time = {
|
||||
// });
|
||||
export const subTable = {
|
||||
title: '子表格',
|
||||
group: 'relation',
|
||||
// disabled: true,
|
||||
options: {
|
||||
interface: 'subTable',
|
||||
@ -329,6 +368,7 @@ export const subTable = {
|
||||
|
||||
export const linkTo = {
|
||||
title: '关联数据',
|
||||
group: 'relation',
|
||||
// disabled: true,
|
||||
options: {
|
||||
interface: 'linkTo',
|
||||
@ -346,6 +386,7 @@ export const linkTo = {
|
||||
|
||||
export const createdAt = {
|
||||
title: '创建时间',
|
||||
group: 'systemInfo',
|
||||
options: {
|
||||
interface: 'createdAt',
|
||||
type: 'date',
|
||||
@ -365,6 +406,7 @@ export const createdAt = {
|
||||
|
||||
export const updatedAt = {
|
||||
title: '修改时间',
|
||||
group: 'systemInfo',
|
||||
options: {
|
||||
interface: 'updatedAt',
|
||||
type: 'date',
|
||||
@ -384,6 +426,7 @@ export const updatedAt = {
|
||||
|
||||
export const createdBy = {
|
||||
title: '创建人',
|
||||
group: 'systemInfo',
|
||||
// disabled: true,
|
||||
options: {
|
||||
interface: 'createdBy',
|
||||
@ -402,6 +445,7 @@ export const createdBy = {
|
||||
|
||||
export const updatedBy = {
|
||||
title: '修改人',
|
||||
group: 'systemInfo',
|
||||
// disabled: true,
|
||||
options: {
|
||||
interface: 'updatedBy',
|
||||
@ -438,6 +482,7 @@ export const group = {
|
||||
|
||||
export const description = {
|
||||
title: '说明文字',
|
||||
group: 'others',
|
||||
options: {
|
||||
interface: 'description',
|
||||
// name: 'id',
|
||||
@ -453,6 +498,7 @@ export const description = {
|
||||
*/
|
||||
export const primaryKey = {
|
||||
title: '主键',
|
||||
group: 'developerMode',
|
||||
options: {
|
||||
interface: 'primaryKey',
|
||||
name: 'id',
|
||||
@ -474,6 +520,7 @@ export const primaryKey = {
|
||||
*/
|
||||
export const sort = {
|
||||
title: '排序',
|
||||
group: 'developerMode',
|
||||
options: {
|
||||
interface: 'sort',
|
||||
type: 'integer',
|
||||
@ -487,6 +534,7 @@ export const sort = {
|
||||
|
||||
export const password = {
|
||||
title: '密码',
|
||||
group: 'developerMode',
|
||||
options: {
|
||||
interface: 'password',
|
||||
type: 'password',
|
||||
@ -499,6 +547,7 @@ export const password = {
|
||||
|
||||
export const json = {
|
||||
title: 'JSON',
|
||||
group: 'developerMode',
|
||||
options: {
|
||||
interface: 'json',
|
||||
type: 'json',
|
||||
@ -512,6 +561,7 @@ export const json = {
|
||||
|
||||
export const icon = {
|
||||
title: '图标',
|
||||
group: 'developerMode',
|
||||
options: {
|
||||
interface: 'icon',
|
||||
type: 'string',
|
||||
@ -520,3 +570,32 @@ export const icon = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const chinaRegion = {
|
||||
title: '中国行政区划',
|
||||
group: 'choices',
|
||||
options: {
|
||||
interface: 'chinaRegion',
|
||||
type: 'belongsToMany',
|
||||
// 数据来源的数据表,与 dataSource 不同,需要从表数据加载后转化成 dataSource
|
||||
target: 'china_regions',
|
||||
targetKey: 'code',
|
||||
// 值字段
|
||||
// valueField: 'code',
|
||||
// 名称字段
|
||||
labelField: 'name',
|
||||
// TODO(refactor): 等 toWhere 重构完成后要改成 parent
|
||||
// 上级字段名
|
||||
parentField: 'parent_code',
|
||||
// 深度限制,默认:-1(代表不控制,即如果是数据表,则无限加载)
|
||||
// limit: -1,
|
||||
// 可选层级,默认:-1(代表可选的最深层级)
|
||||
// maxLevel: null,
|
||||
// 是否可以不选择到最深一级
|
||||
// 'x-component-props': { changeOnSelect: true }
|
||||
incompletely: false,
|
||||
component: {
|
||||
type: 'cascader',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -152,6 +152,14 @@ const transforms = {
|
||||
if (['radio', 'select', 'multipleSelect', 'checkboxes'].includes(interfaceType)) {
|
||||
prop.enum = field.get('dataSource');
|
||||
}
|
||||
if (interfaceType === 'chinaRegion') {
|
||||
set(prop, 'x-component-props.target', field.get('target'));
|
||||
set(prop, 'x-component-props.labelField', field.get('labelField'));
|
||||
set(prop, 'x-component-props.valueField', field.get('targetKey'));
|
||||
set(prop, 'x-component-props.parentField', field.get('parentField'));
|
||||
set(prop, 'x-component-props.maxLevel', field.get('maxLevel'));
|
||||
set(prop, 'x-component-props.changeOnSelect', field.get('incompletely'));
|
||||
}
|
||||
schema[field.name] = {
|
||||
id: field.id,
|
||||
...prop,
|
||||
@ -362,7 +370,7 @@ export default async (ctx, next) => {
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
if (!['subTable', 'linkTo', 'attachment', 'createdBy', 'updatedBy'].includes(field.get('interface'))) {
|
||||
if (!['subTable', 'linkTo', 'attachment', 'createdBy', 'updatedBy', 'chinaRegion'].includes(field.get('interface'))) {
|
||||
continue;
|
||||
}
|
||||
let showInKey;
|
||||
|
Loading…
Reference in New Issue
Block a user