feat: plugin export (#479)
* feat: init export plugin * feat: add client export * fix: fix the word spell * feat: export plugin done * feat: init export plugin * feat: add client export * fix: fix the word spell * feat: export plugin done * ci: change plugin-export version * refactor: renders add ctx params * fix: fix select and multipleSelect export * fix: array convert string * refactor: move SchemaInitializerPluginProvider * fix: build error * fix: change umijs config * fix: update SchemaInitializerPluginProvider * fix: import server * fix: fix some bug * fix: fix some bug * refactor: export plugin refactor * refactor: create all export fields by default * fix: fix export plugin bug * fix(plugin-collection-manager): uiSchema toJSON * fix: update yarn.lock * fix: fix init fields bug * refactor: enum params pass by client * fix: fix export table header title * refactor: refactor dataIndex * fix: fix dataIndex maybe complex object * fix: add checkboxGroup in export plugin * fix: add checkbox and i18n * feat: improve code Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
e69257e346
commit
d831a9b889
@ -3,7 +3,8 @@
|
||||
"version": "0.7.0-alpha.83",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "0.7.0-alpha.83"
|
||||
"@nocobase/client": "0.7.0-alpha.83",
|
||||
"@nocobase/plugin-export": "0.7.0-alpha.83"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
WorkflowShortcut
|
||||
} from '@nocobase/client';
|
||||
import { AuditLogsProvider } from '@nocobase/plugin-audit-logs/client';
|
||||
import { ExportPluginProvider } from '@nocobase/plugin-export/client';
|
||||
import { notification } from 'antd';
|
||||
import 'antd/dist/antd.css';
|
||||
import React from 'react';
|
||||
@ -105,6 +106,7 @@ const providers = [
|
||||
},
|
||||
},
|
||||
],
|
||||
ExportPluginProvider,
|
||||
AuditLogsProvider,
|
||||
BlockSchemaComponentProvider,
|
||||
AntdSchemaComponentProvider,
|
||||
|
@ -18,22 +18,6 @@
|
||||
},
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"mock/**/*",
|
||||
"src/**/*",
|
||||
"config/**/*",
|
||||
".umirc.ts",
|
||||
"typings.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
"es",
|
||||
"dist",
|
||||
"typings",
|
||||
"**/__test__",
|
||||
"test",
|
||||
"docs",
|
||||
"tests"
|
||||
]
|
||||
"include": ["mock/**/*", "src/**/*", "config/**/*", ".umirc.ts", "typings.d.ts"],
|
||||
"exclude": ["node_modules", "lib", "es", "dist", "typings", "**/__test__", "test", "docs", "tests"]
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ export default [
|
||||
'@nocobase/plugin-china-region',
|
||||
'@nocobase/plugin-workflow',
|
||||
'@nocobase/plugin-client',
|
||||
'@nocobase/plugin-export',
|
||||
'@nocobase/plugin-audit-logs',
|
||||
] as PluginsConfigurations;
|
||||
|
@ -57,7 +57,6 @@ async function listWithNonPaged(ctx: Context) {
|
||||
|
||||
export async function list(ctx: Context, next) {
|
||||
const { paginate } = ctx.action.params;
|
||||
|
||||
if (paginate === false || paginate === 'false') {
|
||||
await listWithNonPaged(ctx);
|
||||
} else {
|
||||
|
@ -16,7 +16,7 @@ export const useBlockResource = () => {
|
||||
return useContext(BlockResourceContext);
|
||||
};
|
||||
|
||||
interface UseReousrceProps {
|
||||
interface UseResourceProps {
|
||||
resource: any;
|
||||
association?: any;
|
||||
useSourceId?: any;
|
||||
@ -33,7 +33,7 @@ const useAssociation = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const useReousrce = (props: UseReousrceProps) => {
|
||||
const useResource = (props: UseResourceProps) => {
|
||||
const { block, resource, useSourceId } = props;
|
||||
const record = useRecord();
|
||||
const api = useAPIClient();
|
||||
@ -121,7 +121,7 @@ export const useBlockRequestContext = () => {
|
||||
|
||||
export const BlockProvider = (props) => {
|
||||
const { collection, association } = props;
|
||||
const resource = useReousrce(props);
|
||||
const resource = useResource(props);
|
||||
return (
|
||||
<MaybeCollectionProvider collection={collection}>
|
||||
<BlockAssociationContext.Provider value={association}>
|
||||
|
@ -4,6 +4,17 @@ import { CollectionManagerContext } from '../context';
|
||||
|
||||
export const useCollectionManager = () => {
|
||||
const { refreshCM, service, interfaces, collections } = useContext(CollectionManagerContext);
|
||||
const getCollectionField = (name: string) => {
|
||||
const [collectionName, fieldName] = name.split('.');
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
const collection = collections?.find((collection) => collection.name === collectionName);
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
return collection?.fields?.find((field) => field.name === fieldName);
|
||||
};
|
||||
return {
|
||||
service,
|
||||
interfaces,
|
||||
@ -22,16 +33,19 @@ export const useCollectionManager = () => {
|
||||
const collection = collections?.find((collection) => collection.name === name);
|
||||
return collection?.fields || [];
|
||||
},
|
||||
getCollectionField(name: string) {
|
||||
const [collectionName, fieldName] = name.split('.');
|
||||
if (!fieldName) {
|
||||
getCollectionField,
|
||||
getCollectionJoinField(name: string) {
|
||||
const [collectionName, ...fieldNames] = name.split('.');
|
||||
if (!fieldNames?.length) {
|
||||
return;
|
||||
}
|
||||
const collection = collections?.find((collection) => collection.name === collectionName);
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
return collection?.fields?.find((field) => field.name === fieldName);
|
||||
let cName = collectionName;
|
||||
return fieldNames.reduce((result, curFieldName) => {
|
||||
const collectionField = getCollectionField(`${cName}.${curFieldName}`);
|
||||
cName = collectionField.target;
|
||||
|
||||
return collectionField;
|
||||
}, null);
|
||||
},
|
||||
getInterface(name: string) {
|
||||
return interfaces[name] ? clone(interfaces[name]) : null;
|
||||
|
@ -26,4 +26,3 @@ export * from './slate';
|
||||
export * from './system-settings';
|
||||
export * from './user';
|
||||
export * from './workflow';
|
||||
|
||||
|
@ -251,7 +251,7 @@ export default {
|
||||
"Title": "Title",
|
||||
"Select view": "Select view",
|
||||
"Reset": "Reset",
|
||||
"Export fields": "Export fields",
|
||||
"Exportable fields": "Exportable fields",
|
||||
"Saved successfully": "Saved successfully",
|
||||
"Nickname": "Nickname",
|
||||
"Sign in": "Sign in",
|
||||
@ -532,8 +532,7 @@ export default {
|
||||
"Request success": "Request success",
|
||||
"Invalid JSON format": "Invalid JSON format",
|
||||
"After successful request": "After successful request",
|
||||
"Add exported field": "Add exported field",
|
||||
"Exported table header name": "Exported table header name",
|
||||
"Add exportable field": "Add exportable field",
|
||||
"Audit logs": "Audit logs",
|
||||
"Record ID": "Record ID",
|
||||
"User": "User",
|
||||
|
@ -262,7 +262,7 @@ export default {
|
||||
"Title": "标题",
|
||||
"Select view": "切换视图",
|
||||
"Reset": "重置",
|
||||
"Export fields": "导出字段",
|
||||
"Exportable fields": "可导出字段",
|
||||
"Saved successfully": "保存成功",
|
||||
"Nickname": "昵称",
|
||||
"Sign in": "登录",
|
||||
@ -584,6 +584,8 @@ export default {
|
||||
'Request success': '请求成功',
|
||||
'Invalid JSON format': '非法JSON格式',
|
||||
'After successful request': '请求成功之后',
|
||||
'Add exportable field': '添加可导出字段',
|
||||
// 'Custom column title': '自定义列标题',
|
||||
"Audit logs": "操作记录",
|
||||
"Record ID": "数据 ID",
|
||||
"User": "用户",
|
||||
|
@ -73,7 +73,7 @@ export const Cascader = connect(
|
||||
fieldNames={fieldNames}
|
||||
displayRender={displayRender}
|
||||
onChange={(value, selectedOptions) => {
|
||||
if (labelInValue) {
|
||||
if (value && labelInValue) {
|
||||
onChange(selectedOptions.map((option) => omit(option, [fieldNames.children])));
|
||||
} else {
|
||||
onChange(value);
|
||||
|
@ -15,7 +15,6 @@ export interface SchemaInitializerProviderProps {
|
||||
|
||||
export const useSchemaInitializer = (name: string) => {
|
||||
const initializers = useContext(SchemaInitializerContext);
|
||||
|
||||
const render = (component?: any, props?: any) => {
|
||||
return component && React.createElement(component, props);
|
||||
};
|
||||
@ -46,6 +45,8 @@ export const useSchemaInitializer = (name: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const SchemaInitializerPluginContext = createContext(null);
|
||||
|
||||
export const SchemaInitializerProvider: React.FC<SchemaInitializerProviderProps> = (props) => {
|
||||
const { initializers, components, children } = props;
|
||||
return (
|
||||
|
@ -476,10 +476,12 @@ SchemaSettings.ActionModalItem = React.memo((props: any) => {
|
||||
const { dn } = useSchemaSettings();
|
||||
const compile = useCompile();
|
||||
const api = useAPIClient();
|
||||
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
initialValues: cloneDeep(initialValues),
|
||||
values: cloneDeep(initialValues),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@ -508,7 +510,7 @@ SchemaSettings.ActionModalItem = React.memo((props: any) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SchemaSettings.Item {...others} onClick={openAssignedFieldValueHandler}>
|
||||
<SchemaSettings.Item {...others} onClick={openAssignedFieldValueHandler} onKeyDown={(e) => e.stopPropagation()}>
|
||||
{props.children || props.title}
|
||||
</SchemaSettings.Item>
|
||||
{createPortal(
|
||||
|
6
packages/core/devtools/umiConfig.d.ts
vendored
6
packages/core/devtools/umiConfig.d.ts
vendored
@ -3,10 +3,12 @@ export declare function getUmiConfig(): {
|
||||
'process.env.API_BASE_URL': string;
|
||||
};
|
||||
proxy: {
|
||||
[x: string]: {
|
||||
[x: string]:
|
||||
| {
|
||||
target: string;
|
||||
changeOrigin: boolean;
|
||||
} | {
|
||||
}
|
||||
| {
|
||||
target: string;
|
||||
changeOrigin: boolean;
|
||||
pathRewrite: {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import Resourcer from './resourcer';
|
||||
import Middleware, { MiddlewareType } from './middleware';
|
||||
import Action, { ActionName, ActionType } from './action';
|
||||
import Middleware, { MiddlewareType } from './middleware';
|
||||
import Resourcer from './resourcer';
|
||||
|
||||
export type ResourceType = 'single' | 'hasOne' | 'hasMany' | 'belongsTo' | 'belongsToMany';
|
||||
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './plugin';
|
||||
export { default } from './server';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { mockServer } from '@nocobase/test';
|
||||
import CollectionManagerPlugin from '../plugin';
|
||||
import CollectionManagerPlugin from '../server';
|
||||
|
||||
describe('collections repository', () => {
|
||||
it('case 1', async () => {
|
||||
|
@ -1,3 +1,3 @@
|
||||
export { default } from './plugin';
|
||||
export * from './repositories';
|
||||
export { default } from './server';
|
||||
|
||||
|
@ -28,7 +28,7 @@ export class FieldModel extends MagicAttributeModel {
|
||||
const uiSchema = await UISchema.findByPk(options.uiSchemaUid, {
|
||||
transaction: loadOptions.transaction,
|
||||
});
|
||||
return collection.setField(name, { ...options, uiSchema });
|
||||
return collection.setField(name, { ...options, uiSchema: uiSchema.get() });
|
||||
} else {
|
||||
return collection.setField(name, options);
|
||||
}
|
||||
|
2
packages/plugins/export/client.d.ts
vendored
Normal file
2
packages/plugins/export/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/client';
|
12
packages/plugins/export/client.js
Normal file
12
packages/plugins/export/client.js
Normal file
@ -0,0 +1,12 @@
|
||||
var _useExportClient = require("./lib/client");
|
||||
|
||||
Object.keys(_useExportClient).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (key in exports && exports[key] === _useExportClient[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _useExportClient[key];
|
||||
}
|
||||
});
|
||||
});
|
18
packages/plugins/export/package.json
Normal file
18
packages/plugins/export/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-export",
|
||||
"version": "0.7.0-alpha.83",
|
||||
"main": "lib/server/index.js",
|
||||
"dependencies": {
|
||||
"node-xlsx": "^0.16.1",
|
||||
"@nocobase/client": "0.7.0-alpha.83",
|
||||
"@nocobase/server": "0.7.0-alpha.83"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node-xlsx": "^0.15.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/server": "*",
|
||||
"@nocobase/client": "*",
|
||||
"@nocobase/test": "*"
|
||||
}
|
||||
}
|
2
packages/plugins/export/server.d.ts
vendored
Normal file
2
packages/plugins/export/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/server';
|
12
packages/plugins/export/server.js
Normal file
12
packages/plugins/export/server.js
Normal file
@ -0,0 +1,12 @@
|
||||
var _useExportServer = require("./lib/server");
|
||||
|
||||
Object.keys(_useExportServer).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (key in exports && exports[key] === _useExportServer[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _useExportServer[key];
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,94 @@
|
||||
import { Schema, useFieldSchema } from '@formily/react';
|
||||
import { merge } from '@formily/shared';
|
||||
import { SchemaInitializer, useCollection, useCompile, useDesignable } from '@nocobase/client';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useFields } from './useFields';
|
||||
|
||||
const findSchema = (schema: Schema, key: string, action: string) => {
|
||||
return schema.reduceProperties((buf, s) => {
|
||||
if (s[key] === action) {
|
||||
return s;
|
||||
}
|
||||
const c = findSchema(s, key, action);
|
||||
if (c) {
|
||||
return c;
|
||||
}
|
||||
return buf;
|
||||
});
|
||||
};
|
||||
const removeSchema = (schema, cb) => {
|
||||
return cb(schema);
|
||||
};
|
||||
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { remove } = useDesignable();
|
||||
const schema = find(fieldSchema, key, action);
|
||||
return {
|
||||
schema,
|
||||
exists: !!schema,
|
||||
remove() {
|
||||
schema && rm(schema, remove);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const initExportSettings = (fields) => {
|
||||
const exportSettings = [];
|
||||
const generateDataIndex = (di, preFields, fNodes: any[]) => {
|
||||
let child = cloneDeep(preFields);
|
||||
fNodes.reduce((buf, cur) => {
|
||||
if (cur.children) {
|
||||
const childDI = [];
|
||||
preFields.dataIndex.push(cur.name);
|
||||
generateDataIndex(childDI, cloneDeep(preFields), cur.children);
|
||||
preFields.dataIndex.pop();
|
||||
di.push(...childDI);
|
||||
} else {
|
||||
child.dataIndex.push(cur.name);
|
||||
di.push(child);
|
||||
child = cloneDeep(preFields);
|
||||
}
|
||||
}, []);
|
||||
};
|
||||
generateDataIndex(exportSettings, { dataIndex: [] }, fields);
|
||||
return exportSettings;
|
||||
};
|
||||
|
||||
export const ExportActionInitializer = (props) => {
|
||||
const { item, insert } = props;
|
||||
const { exists, remove } = useCurrentSchema('export', 'x-action', item.find, item.remove);
|
||||
const compile = useCompile();
|
||||
const { name } = useCollection();
|
||||
const fields = useFields(name);
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
title: '{{ t("Export") }}',
|
||||
'x-action': 'export',
|
||||
'x-action-settings': {
|
||||
exportSettings: [],
|
||||
},
|
||||
'x-designer': 'ExportDesigner',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
icon: 'clouddownloadoutlined',
|
||||
useProps: '{{ useExportAction }}',
|
||||
},
|
||||
};
|
||||
return (
|
||||
<SchemaInitializer.SwitchItem
|
||||
checked={exists}
|
||||
title={item.title}
|
||||
onClick={() => {
|
||||
if (exists) {
|
||||
return remove();
|
||||
}
|
||||
schema['x-action-settings']['exportSettings'] = initExportSettings(fields);
|
||||
const s = merge(schema || {}, item.schema || {});
|
||||
item?.schemaInitialize?.(s);
|
||||
insert(s);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
122
packages/plugins/export/src/client/ExportDesigner.tsx
Normal file
122
packages/plugins/export/src/client/ExportDesigner.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { ArrayItems } from '@formily/antd';
|
||||
import type { ISchema } from '@formily/react';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import { GeneralSchemaDesigner, SchemaSettings, useDesignable } from '@nocobase/client';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useShared } from './useShared';
|
||||
|
||||
export const ExportDesigner = () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { t } = useTranslation();
|
||||
const { dn } = useDesignable();
|
||||
const [schema, setSchema] = useState<ISchema>();
|
||||
const { schema: pageSchema } = useShared();
|
||||
|
||||
useEffect(() => {
|
||||
setSchema(pageSchema);
|
||||
}, [field.address, fieldSchema?.['x-action-settings']?.['exportSettings']]);
|
||||
|
||||
return (
|
||||
<GeneralSchemaDesigner disableInitializer>
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Edit button')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Edit button'),
|
||||
properties: {
|
||||
title: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
title: t('Button title'),
|
||||
default: fieldSchema.title,
|
||||
'x-component-props': {},
|
||||
// description: `原字段标题:${collectionField?.uiSchema?.title}`,
|
||||
},
|
||||
icon: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'IconPicker',
|
||||
title: t('Button icon'),
|
||||
default: fieldSchema?.['x-component-props']?.icon,
|
||||
'x-component-props': {},
|
||||
// description: `原字段标题:${collectionField?.uiSchema?.title}`,
|
||||
},
|
||||
type: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
title: t('Button background color'),
|
||||
default: fieldSchema?.['x-component-props']?.danger
|
||||
? 'danger'
|
||||
: fieldSchema?.['x-component-props']?.type === 'primary'
|
||||
? 'primary'
|
||||
: 'default',
|
||||
enum: [
|
||||
{ value: 'default', label: '{{t("Default")}}' },
|
||||
{ value: 'primary', label: '{{t("Highlight")}}' },
|
||||
{ value: 'danger', label: '{{t("Danger red")}}' },
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ title, icon, type }) => {
|
||||
if (title) {
|
||||
fieldSchema.title = title;
|
||||
field.title = title;
|
||||
field.componentProps.icon = icon;
|
||||
field.componentProps.danger = type === 'danger';
|
||||
field.componentProps.type = type;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props'].icon = icon;
|
||||
fieldSchema['x-component-props'].danger = type === 'danger';
|
||||
fieldSchema['x-component-props'].type = type;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
title,
|
||||
'x-component-props': {
|
||||
...fieldSchema['x-component-props'],
|
||||
},
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.ActionModalItem
|
||||
title={t('Exportable fields')}
|
||||
schema={schema}
|
||||
initialValues={{ exportSettings: fieldSchema?.['x-action-settings']?.exportSettings }}
|
||||
components={{ ArrayItems }}
|
||||
onSubmit={({ exportSettings }) => {
|
||||
fieldSchema['x-action-settings']['exportSettings'] = exportSettings
|
||||
?.filter((fieldItem) => fieldItem?.dataIndex?.length)
|
||||
.map((item) => ({
|
||||
dataIndex: item.dataIndex.map((di) => di.name ?? di),
|
||||
title: item.title,
|
||||
}));
|
||||
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-action-settings': fieldSchema['x-action-settings'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
breakRemoveOn={(s) => {
|
||||
return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar');
|
||||
}}
|
||||
confirm={{
|
||||
title: t('Delete action'),
|
||||
}}
|
||||
/>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { SchemaInitializerContext } from '@nocobase/client';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const ExportInitializerProvider = (props: any) => {
|
||||
const initializes = useContext(SchemaInitializerContext);
|
||||
const hasExportAction = initializes.TableActionInitializers.items[0].children.some(
|
||||
(initialize) => initialize.component === 'ExportActionInitializer',
|
||||
);
|
||||
!hasExportAction &&
|
||||
initializes.TableActionInitializers.items[0].children.push({
|
||||
type: 'item',
|
||||
title: "{{t('Export')}}",
|
||||
component: 'ExportActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-acl-action-props': {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return props.children;
|
||||
};
|
11
packages/plugins/export/src/client/ExportPluginProvider.tsx
Normal file
11
packages/plugins/export/src/client/ExportPluginProvider.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { SchemaComponentOptions } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { ExportActionInitializer, ExportDesigner, ExportInitializerProvider, useExportAction } from './';
|
||||
|
||||
export const ExportPluginProvider = (props: any) => {
|
||||
return (
|
||||
<SchemaComponentOptions components={{ ExportActionInitializer, ExportDesigner }} scope={{ useExportAction }}>
|
||||
<ExportInitializerProvider>{props.children}</ExportInitializerProvider>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
};
|
6
packages/plugins/export/src/client/index.ts
Normal file
6
packages/plugins/export/src/client/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './ExportActionInitializer';
|
||||
export * from './ExportDesigner';
|
||||
export * from './ExportInitializerProvider';
|
||||
export * from './ExportPluginProvider';
|
||||
export * from './useExportAction';
|
||||
|
56
packages/plugins/export/src/client/useExportAction.ts
Normal file
56
packages/plugins/export/src/client/useExportAction.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
useAPIClient,
|
||||
useBlockRequestContext,
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useCompile,
|
||||
} from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useExportAction = () => {
|
||||
const { service } = useBlockRequestContext();
|
||||
const apiClient = useAPIClient();
|
||||
const actionSchema = useFieldSchema();
|
||||
const compile = useCompile();
|
||||
const { getCollectionJoinField } = useCollectionManager();
|
||||
const { name, title, getField } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
async onClick() {
|
||||
const { exportSettings } = actionSchema?.['x-action-settings'] ?? {};
|
||||
exportSettings.forEach((es) => {
|
||||
const { uiSchema } = getCollectionJoinField(`${name}.${es.dataIndex.join('.')}`) ?? {};
|
||||
es.enum = uiSchema?.enum?.map((e) => ({ value: e.value, label: e.label }));
|
||||
if (!es.enum && uiSchema.type === 'boolean') {
|
||||
es.enum = [
|
||||
{ value: true, label: t('Yes') },
|
||||
{ value: false, label: t('No') },
|
||||
];
|
||||
}
|
||||
es.defaultTitle = uiSchema?.title;
|
||||
});
|
||||
const { data } = await apiClient.request({
|
||||
url: `/${name}:exportXlsx`,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
params: {
|
||||
title: compile(title),
|
||||
columns: JSON.stringify(compile(exportSettings)),
|
||||
appends: service.params[0]?.appends?.join(),
|
||||
filter: JSON.stringify(service.params[0]?.filter),
|
||||
},
|
||||
});
|
||||
|
||||
let blob = new Blob([data], { type: 'application/x-xls' });
|
||||
const a = document.createElement('a');
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
a.download = `${compile(title)}.xlsx`;
|
||||
a.href = blobUrl;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
document.body.removeChild(a);
|
||||
},
|
||||
};
|
||||
};
|
58
packages/plugins/export/src/client/useFields.ts
Normal file
58
packages/plugins/export/src/client/useFields.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useCollectionManager } from '@nocobase/client';
|
||||
|
||||
export const useFields = (collectionName: string) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
||||
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||
const fields = getCollectionFields(collectionName);
|
||||
const field2option = (field, depth) => {
|
||||
if (nonfilterable.length && depth === 1 && nonfilterable.includes(field.name)) {
|
||||
return;
|
||||
}
|
||||
if (!field.interface) {
|
||||
return;
|
||||
}
|
||||
const fieldInterface = getInterface(field.interface);
|
||||
if (!fieldInterface.filterable) {
|
||||
return;
|
||||
}
|
||||
const { nested, children, operators } = fieldInterface.filterable;
|
||||
const option = {
|
||||
name: field.name,
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
schema: field?.uiSchema,
|
||||
operators:
|
||||
operators?.filter?.((operator) => {
|
||||
return !operator?.visible || operator.visible(field);
|
||||
}) || [],
|
||||
};
|
||||
if (field.target && depth > 2) {
|
||||
return;
|
||||
}
|
||||
if (depth > 2) {
|
||||
return option;
|
||||
}
|
||||
if (children?.length) {
|
||||
option['children'] = children;
|
||||
}
|
||||
if (nested) {
|
||||
const targetFields = getCollectionFields(field.target);
|
||||
const options = getOptions(targetFields, depth + 1).filter(Boolean);
|
||||
option['children'] = option['children'] || [];
|
||||
option['children'].push(...options);
|
||||
}
|
||||
return option;
|
||||
};
|
||||
const getOptions = (fields, depth) => {
|
||||
const options = [];
|
||||
fields.forEach((field) => {
|
||||
const option = field2option(field, depth);
|
||||
if (option) {
|
||||
options.push(option);
|
||||
}
|
||||
});
|
||||
return options;
|
||||
};
|
||||
return getOptions(fields, 1);
|
||||
};
|
92
packages/plugins/export/src/client/useShared.ts
Normal file
92
packages/plugins/export/src/client/useShared.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useCollection } from '@nocobase/client';
|
||||
import { useFields } from './useFields';
|
||||
|
||||
export const useShared = () => {
|
||||
const { name } = useCollection();
|
||||
const fields = useFields(name);
|
||||
return {
|
||||
schema: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
exportSettings: {
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
space: {
|
||||
type: 'void',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
width: 100%;
|
||||
& .ant-space-item:nth-child(2),
|
||||
& .ant-space-item:nth-child(3) {
|
||||
flex: 1;
|
||||
}
|
||||
`,
|
||||
},
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.SortHandle',
|
||||
},
|
||||
dataIndex: {
|
||||
type: 'array',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Cascader',
|
||||
required: true,
|
||||
enum: fields,
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
label: 'title',
|
||||
value: 'name',
|
||||
children: 'children',
|
||||
},
|
||||
// labelInValue: true,
|
||||
changeOnSelect: false,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: '{{ t("Custom column title") }}',
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.Remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '{{ t("Add exportable field") }}',
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
border-color: rgb(241, 139, 98);
|
||||
color: rgb(241, 139, 98);
|
||||
&.ant-btn-dashed:hover {
|
||||
border-color: rgb(241, 139, 98);
|
||||
color: rgb(241, 139, 98);
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
1
packages/plugins/export/src/index.ts
Normal file
1
packages/plugins/export/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
0
packages/plugins/export/src/server/actions/.gitkeep
Normal file
0
packages/plugins/export/src/server/actions/.gitkeep
Normal file
51
packages/plugins/export/src/server/actions/export-xlsx.ts
Normal file
51
packages/plugins/export/src/server/actions/export-xlsx.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
import { Collection, Repository } from '@nocobase/database';
|
||||
import xlsx from 'node-xlsx';
|
||||
import render from '../renders';
|
||||
|
||||
export async function exportXlsx(ctx: Context, next: Next) {
|
||||
let { title, columns, associatedName, associatedIndex, resourceName, filter, fields, appends, except } =
|
||||
ctx.action.params;
|
||||
if (typeof columns === 'string') {
|
||||
columns = JSON.parse(columns);
|
||||
}
|
||||
columns = columns?.filter((col) => col?.dataIndex?.length > 0);
|
||||
|
||||
let repository: Repository;
|
||||
let collection: Collection;
|
||||
|
||||
if (associatedName && associatedIndex) {
|
||||
const associated = ctx.db.getCollection(associatedName);
|
||||
const resourceField = associated.getField(resourceName);
|
||||
collection = ctx.db.getCollection(resourceField.target);
|
||||
repository = associated.repository.relation(resourceName).of(associatedIndex) as any;
|
||||
} else {
|
||||
collection = ctx.db.getCollection(resourceName);
|
||||
repository = collection.repository;
|
||||
}
|
||||
const data = await repository.find({
|
||||
filter,
|
||||
fields,
|
||||
appends,
|
||||
except,
|
||||
});
|
||||
const collectionFields = columns.map((col) => collection.fields.get(col.dataIndex[0]));
|
||||
const { rows, ranges } = await render({ columns, fields: collectionFields, data }, ctx);
|
||||
ctx.body = xlsx.build([
|
||||
{
|
||||
name: title,
|
||||
data: rows,
|
||||
options: {
|
||||
'!merges': ranges,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
ctx.set({
|
||||
'Content-Type': 'application/octet-stream',
|
||||
// to avoid "invalid character" error in header (RFC)
|
||||
'Content-Disposition': `attachment; filename=${encodeURI(title)}.xlsx`,
|
||||
});
|
||||
|
||||
await next();
|
||||
}
|
1
packages/plugins/export/src/server/actions/index.ts
Normal file
1
packages/plugins/export/src/server/actions/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './export-xlsx';
|
26
packages/plugins/export/src/server/index.ts
Normal file
26
packages/plugins/export/src/server/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
import { exportXlsx } from './actions';
|
||||
|
||||
export class ExportPlugin extends Plugin {
|
||||
getName(): string {
|
||||
return this.getPackageName(__dirname);
|
||||
}
|
||||
|
||||
beforeLoad() {}
|
||||
|
||||
async load() {
|
||||
// Visit: http://localhost:13000/api/xxx:exportXlsx
|
||||
this.app.resourcer.registerActionHandler('exportXlsx', exportXlsx);
|
||||
// this.app.resource({
|
||||
// name: 'export',
|
||||
// actions: {
|
||||
// xlsx: exportXlsx,
|
||||
// },
|
||||
// });
|
||||
this.app.acl.allow('*', 'exportXlsx');
|
||||
}
|
||||
|
||||
async install(options: InstallOptions) {}
|
||||
}
|
||||
|
||||
export default ExportPlugin;
|
0
packages/plugins/export/src/server/models/.gitkeep
Normal file
0
packages/plugins/export/src/server/models/.gitkeep
Normal file
173
packages/plugins/export/src/server/renders/index.ts
Normal file
173
packages/plugins/export/src/server/renders/index.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import * as renders from './renders';
|
||||
|
||||
function getInterfaceRender(name: string): Function {
|
||||
return renders[name] || renders._;
|
||||
}
|
||||
|
||||
function renderHeader(params, ctx) {
|
||||
const { columns, fields, headers = [], rowIndex = 0 } = params;
|
||||
|
||||
let { colIndex = 0 } = params;
|
||||
|
||||
if (!headers[rowIndex]) {
|
||||
headers.push([]);
|
||||
}
|
||||
const row = headers[rowIndex];
|
||||
fields.forEach((field, i) => {
|
||||
const nextColIndex = colIndex + i;
|
||||
row.push({
|
||||
column: columns[i],
|
||||
field,
|
||||
rowIndex,
|
||||
colIndex: nextColIndex,
|
||||
});
|
||||
// if (field.interface === 'subTable') {
|
||||
// const subTable = ctx.db.getTable(field.target);
|
||||
// const subFields = subTable.getOptions().fields.filter((field) => Boolean(field.__index));
|
||||
// renderHeader(
|
||||
// {
|
||||
// fields: subFields,
|
||||
// headers,
|
||||
// rowIndex: rowIndex + 1,
|
||||
// colIndex: nextColIndex,
|
||||
// },
|
||||
// ctx,
|
||||
// );
|
||||
// colIndex += subFields.length;
|
||||
// }
|
||||
});
|
||||
|
||||
Object.assign(params, { headers });
|
||||
}
|
||||
|
||||
async function renderRows({ columns, fields, data }, ctx) {
|
||||
return await data.reduce(async (preResult, row) => {
|
||||
const result = await preResult;
|
||||
const thisRow = [];
|
||||
const rowIndex = 0;
|
||||
let colOffset = 0;
|
||||
for (let i = 0, iLen = fields.length; i < iLen; i++) {
|
||||
const field = fields[i];
|
||||
|
||||
if (!thisRow[rowIndex]) {
|
||||
thisRow.push([]);
|
||||
}
|
||||
const cells = thisRow[rowIndex];
|
||||
if (field.options.interface !== 'subTable') {
|
||||
const render = getInterfaceRender(field.options.interface);
|
||||
const value = await render(field, row, ctx, columns[i]);
|
||||
cells.push({
|
||||
value,
|
||||
rowIndex: result.length + rowIndex,
|
||||
colIndex: i + colOffset,
|
||||
});
|
||||
} else {
|
||||
const subTable = ctx.db.getTable(field.target);
|
||||
const subFields = subTable.getOptions().fields.filter((item) => Boolean(item.__index));
|
||||
//TODO: must provide sub-table columns
|
||||
const subTableColumns = [];
|
||||
const subRows = await renderRows(
|
||||
{ columns: subTableColumns, fields: subFields, data: row.get(field.name) || [] },
|
||||
ctx,
|
||||
);
|
||||
|
||||
// const { rows: subRowGroups } = subTableRows;
|
||||
subRows.forEach((cells, j) => {
|
||||
const subRowIndex = rowIndex + j;
|
||||
if (!thisRow[subRowIndex]) {
|
||||
thisRow.push([]);
|
||||
}
|
||||
const subCells = thisRow[subRowIndex];
|
||||
subCells.push(
|
||||
...cells.map((cell) => ({
|
||||
...cell,
|
||||
rowIndex: result.length + subRowIndex,
|
||||
colIndex: cell.colIndex + i,
|
||||
})),
|
||||
);
|
||||
});
|
||||
colOffset += subFields.length;
|
||||
}
|
||||
}
|
||||
thisRow.forEach((cells) => {
|
||||
cells.forEach((cell) => {
|
||||
const relRowIndex = cell.rowIndex - result.length;
|
||||
Object.assign(cell, {
|
||||
rowSpan:
|
||||
relRowIndex >= thisRow.length - 1 ||
|
||||
thisRow[relRowIndex + 1].find((item) => item.colIndex === cell.colIndex)
|
||||
? 1
|
||||
: thisRow.length - relRowIndex,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result.concat(thisRow);
|
||||
}, Promise.resolve([]));
|
||||
}
|
||||
|
||||
export default async function ({ columns, fields, data }, ctx) {
|
||||
const headers = [];
|
||||
renderHeader({ columns, fields, headers }, ctx);
|
||||
const ranges = [];
|
||||
// 计算全表最大的列索引(由于无论如何最大列都是单个单元格,所以等价于长度)
|
||||
const maxColIndex = Math.max(...headers.map((row) => row[row.length - 1].colIndex));
|
||||
// 遍历所有单元格,计算需要合并的坐标范围
|
||||
headers.forEach((row, rowIndex) => {
|
||||
row.forEach((cell, cellIndex) => {
|
||||
// 跨行合并的行数为
|
||||
cell.rowSpan =
|
||||
cell.rowIndex >= headers.length - 1 ||
|
||||
headers[cell.rowIndex + 1].find((item) => item.colIndex === cell.colIndex)
|
||||
? 1
|
||||
: headers.length - cell.rowIndex;
|
||||
|
||||
const nextCell = headers
|
||||
.slice(0, rowIndex + 1)
|
||||
.map((r) => r.find((item) => item.colIndex > cell.colIndex))
|
||||
.filter((c) => Boolean(c))
|
||||
.reduce((min, c) => (min && Math.min(min.colIndex, c.colIndex) === min.colIndex ? min : c), null);
|
||||
cell.colSpan = nextCell ? nextCell.colIndex - cell.colIndex : maxColIndex - cell.colIndex + 1;
|
||||
|
||||
if (cell.rowSpan > 1 || cell.colSpan > 1) {
|
||||
ranges.push({
|
||||
s: { c: cell.colIndex, r: cell.rowIndex },
|
||||
e: { c: cell.colIndex + cell.colSpan - 1, r: cell.rowIndex + cell.rowSpan - 1 },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const rows = (await renderRows({ columns, fields, data }, ctx)).map((row) => {
|
||||
const cells = Array(maxColIndex).fill(null);
|
||||
row.forEach((cell) => {
|
||||
cells.splice(cell.colIndex, 1, cell.value);
|
||||
if (cell.rowSpan > 1) {
|
||||
ranges.push({
|
||||
s: { c: cell.colIndex, r: cell.rowIndex + headers.length },
|
||||
e: { c: cell.colIndex, r: cell.rowIndex + cell.rowSpan - 1 + headers.length },
|
||||
});
|
||||
}
|
||||
});
|
||||
return cells;
|
||||
});
|
||||
|
||||
return {
|
||||
rows: [
|
||||
...headers.map((row) => {
|
||||
// 补齐无数据单元格,以供合并
|
||||
const cells = Array(maxColIndex).fill(null);
|
||||
row.forEach((cell) =>
|
||||
cells.splice(
|
||||
cell.colIndex,
|
||||
1,
|
||||
cell.column.title ?? cell.column.defaultTitle ?? cell.column.dataIndex[cell.column.dataIndex.length - 1],
|
||||
),
|
||||
);
|
||||
return cells;
|
||||
}),
|
||||
...rows,
|
||||
],
|
||||
ranges,
|
||||
};
|
||||
}
|
98
packages/plugins/export/src/server/renders/renders.ts
Normal file
98
packages/plugins/export/src/server/renders/renders.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import moment from 'moment';
|
||||
|
||||
export async function _(field, row, ctx, column?: any) {
|
||||
if (column?.dataIndex.length > 1) {
|
||||
return column.dataIndex.reduce((result, col) => {
|
||||
return result?.[col];
|
||||
}, row);
|
||||
} else {
|
||||
return row.get(field.name);
|
||||
}
|
||||
}
|
||||
|
||||
export async function datetime(field, row, ctx) {
|
||||
const value = row.get(field.name);
|
||||
return moment(value).format(field.showTime ? `${field.dateFormat} ${field.timeFormat}` : field.dateFormat);
|
||||
}
|
||||
|
||||
export async function percent(field, row, ctx) {
|
||||
const value = row.get(field.name);
|
||||
return value && `${value}%`;
|
||||
}
|
||||
|
||||
export async function boolean(field, row, ctx, column?: any) {
|
||||
const value = row.get(field.name);
|
||||
let { enum: enumData } = column ?? {};
|
||||
if (enumData?.length > 0) {
|
||||
const option = enumData.find((item) => item.value === value);
|
||||
return option?.label;
|
||||
} else {
|
||||
// FIXME: i18n
|
||||
return value ? '是' : value === null || value === undefined ? '' : '否';
|
||||
}
|
||||
}
|
||||
|
||||
export const checkbox = boolean;
|
||||
|
||||
export async function select(field, row, ctx, column?: any) {
|
||||
const value = row.get(field.name);
|
||||
let { enum: enumData } = column ?? {};
|
||||
if (!enumData) {
|
||||
const repository = ctx.db.getCollection('uiSchemas').repository;
|
||||
const model = await repository.findById(field.options.uiSchemaUid);
|
||||
enumData = model.get('enum');
|
||||
}
|
||||
const option = enumData.find((item) => item.value === value);
|
||||
return option?.label;
|
||||
}
|
||||
|
||||
export async function multipleSelect(field, row, ctx, column?: any) {
|
||||
const values = row.get(field.name);
|
||||
let { enum: enumData } = column ?? {};
|
||||
if (!enumData) {
|
||||
const repository = ctx.db.getCollection('uiSchemas').repository;
|
||||
const model = await repository.findById(field.options.uiSchemaUid);
|
||||
enumData = model.get('enum');
|
||||
}
|
||||
return values
|
||||
?.map((value) => {
|
||||
const option = enumData.find((item) => item.value === value);
|
||||
return option?.label;
|
||||
})
|
||||
?.join();
|
||||
}
|
||||
|
||||
export const radio = select;
|
||||
|
||||
export const radioGroup = select;
|
||||
|
||||
export const checkboxes = multipleSelect;
|
||||
|
||||
export const checkboxGroup = multipleSelect;
|
||||
|
||||
export async function subTable(field, row, ctx) {
|
||||
// TODO: need title field to be defined
|
||||
return (row.get(field.name) || []).map((item) => item[field.sourceKey]);
|
||||
}
|
||||
|
||||
export async function linkTo(field, row, ctx, column?: any) {
|
||||
return (row.get(field.name) || []).map((item) => {
|
||||
return column.dataIndex.reduce((buf, cur) => {
|
||||
buf = item[cur];
|
||||
return buf;
|
||||
});
|
||||
});
|
||||
// return (row.get(field.name) || []).map((item) => item[field.labelField]);
|
||||
}
|
||||
|
||||
export async function attachment(field, row, ctx) {
|
||||
return (row.get(field.name) || []).map((item) => item[field.url]).join(' ');
|
||||
}
|
||||
|
||||
export async function chinaRegion(field, row, ctx) {
|
||||
const value = row.get(field.name);
|
||||
const values = (Array.isArray(value) ? value : [value]).sort((a, b) =>
|
||||
a.level !== b.level ? a.level - b.level : a.sort - b.sort,
|
||||
);
|
||||
return values.map((item) => item.name).join('/');
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './plugin';
|
||||
export { default } from './server';
|
||||
|
@ -1,105 +1 @@
|
||||
import { skip } from '@nocobase/acl';
|
||||
import { MagicAttributeModel } from '@nocobase/database';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import { resolve } from 'path';
|
||||
import { getAccessible } from './actions/getAccessible';
|
||||
|
||||
export class UiRoutesStoragePlugin extends Plugin {
|
||||
getName(): string {
|
||||
return this.getPackageName(__dirname);
|
||||
}
|
||||
|
||||
async install() {
|
||||
const repository = this.app.db.getRepository('uiRoutes');
|
||||
const routes = [
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/',
|
||||
to: '/admin',
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
uiSchema: {
|
||||
type: 'void',
|
||||
'x-component': 'Menu',
|
||||
'x-designer': 'Menu.Designer',
|
||||
'x-initializer': 'MenuItemInitializers',
|
||||
'x-component-props': {
|
||||
mode: 'mix',
|
||||
theme: 'dark',
|
||||
// defaultSelectedUid: 'u8',
|
||||
onSelect: '{{ onSelect }}',
|
||||
sideMenuRefScopeKey: 'sideMenuRef',
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'AdminLayout',
|
||||
title: 'NocoBase Admin',
|
||||
routes: [
|
||||
// test...
|
||||
// {
|
||||
// type: 'route',
|
||||
// path: '/admin/workflows/:id',
|
||||
// component: 'WorkflowPage',
|
||||
// },
|
||||
// {
|
||||
// type: 'route',
|
||||
// path: '/admin/block-templates/:key',
|
||||
// component: 'BlockTemplateDetails',
|
||||
// },
|
||||
// {
|
||||
// type: 'route',
|
||||
// path: '/admin/block-templates',
|
||||
// component: 'BlockTemplatePage',
|
||||
// },
|
||||
{
|
||||
type: 'route',
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'RouteSchemaComponent',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
component: 'AuthLayout',
|
||||
routes: [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/signin',
|
||||
component: 'SigninPage',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/signup',
|
||||
component: 'SignupPage',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const values of routes) {
|
||||
await repository.create({
|
||||
values,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.app.resourcer.registerActionHandler('uiRoutes:getAccessible', getAccessible);
|
||||
this.app.db.registerModels({ MagicAttributeModel });
|
||||
|
||||
await this.app.db.import({
|
||||
directory: resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
this.app.acl.use(
|
||||
skip({
|
||||
resourceName: 'uiRoutes',
|
||||
actionName: 'getAccessible',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UiRoutesStoragePlugin;
|
||||
export { default } from './server';
|
||||
|
105
packages/plugins/ui-routes-storage/src/server.ts
Normal file
105
packages/plugins/ui-routes-storage/src/server.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { skip } from '@nocobase/acl';
|
||||
import { MagicAttributeModel } from '@nocobase/database';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import { resolve } from 'path';
|
||||
import { getAccessible } from './actions/getAccessible';
|
||||
|
||||
export class UiRoutesStoragePlugin extends Plugin {
|
||||
getName(): string {
|
||||
return this.getPackageName(__dirname);
|
||||
}
|
||||
|
||||
async install() {
|
||||
const repository = this.app.db.getRepository('uiRoutes');
|
||||
const routes = [
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/',
|
||||
to: '/admin',
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
uiSchema: {
|
||||
type: 'void',
|
||||
'x-component': 'Menu',
|
||||
'x-designer': 'Menu.Designer',
|
||||
'x-initializer': 'MenuItemInitializers',
|
||||
'x-component-props': {
|
||||
mode: 'mix',
|
||||
theme: 'dark',
|
||||
// defaultSelectedUid: 'u8',
|
||||
onSelect: '{{ onSelect }}',
|
||||
sideMenuRefScopeKey: 'sideMenuRef',
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'AdminLayout',
|
||||
title: 'NocoBase Admin',
|
||||
routes: [
|
||||
// test...
|
||||
// {
|
||||
// type: 'route',
|
||||
// path: '/admin/workflows/:id',
|
||||
// component: 'WorkflowPage',
|
||||
// },
|
||||
// {
|
||||
// type: 'route',
|
||||
// path: '/admin/block-templates/:key',
|
||||
// component: 'BlockTemplateDetails',
|
||||
// },
|
||||
// {
|
||||
// type: 'route',
|
||||
// path: '/admin/block-templates',
|
||||
// component: 'BlockTemplatePage',
|
||||
// },
|
||||
{
|
||||
type: 'route',
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'RouteSchemaComponent',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
component: 'AuthLayout',
|
||||
routes: [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/signin',
|
||||
component: 'SigninPage',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/signup',
|
||||
component: 'SignupPage',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const values of routes) {
|
||||
await repository.create({
|
||||
values,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.app.resourcer.registerActionHandler('uiRoutes:getAccessible', getAccessible);
|
||||
this.app.db.registerModels({ MagicAttributeModel });
|
||||
|
||||
await this.app.db.import({
|
||||
directory: resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
this.app.acl.use(
|
||||
skip({
|
||||
resourceName: 'uiRoutes',
|
||||
actionName: 'getAccessible',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UiRoutesStoragePlugin;
|
@ -1,6 +1,7 @@
|
||||
export * from './constants';
|
||||
export * from './calculators';
|
||||
export * from './triggers';
|
||||
export * from './constants';
|
||||
export * from './instructions';
|
||||
|
||||
export { default } from './server';
|
||||
export * from './triggers';
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
"@nocobase/plugin-ui-schema-storage": "0.7.0-alpha.83",
|
||||
"@nocobase/plugin-users": "0.7.0-alpha.83",
|
||||
"@nocobase/plugin-workflow": "0.7.0-alpha.83",
|
||||
"@nocobase/plugin-export": "0.7.0-alpha.83",
|
||||
"@nocobase/server": "0.7.0-alpha.83"
|
||||
},
|
||||
"repository": {
|
||||
|
106
yarn.lock
106
yarn.lock
@ -2544,6 +2544,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.14.6", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3":
|
||||
version "7.18.3"
|
||||
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4"
|
||||
integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
|
||||
@ -2551,13 +2558,6 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3":
|
||||
version "7.18.3"
|
||||
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4"
|
||||
integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.10.4", "@babel/template@^7.16.7", "@babel/template@^7.4.0", "@babel/template@^7.4.4":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
|
||||
@ -5170,6 +5170,11 @@
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/node-xlsx@^0.15.1":
|
||||
version "0.15.3"
|
||||
resolved "https://registry.npmmirror.com/@types/node-xlsx/-/node-xlsx-0.15.3.tgz#675e5f91868b6c76c0bd3234fb84e4b55b807b1c"
|
||||
integrity sha512-6UAa+t9eR3AZzRanZWGNUV8MoXwVpXQHMqCMdK8PNPwTy+G3kYHP+2KkTI+39vXETNb6krRijL+zWzmFrlfLjw==
|
||||
|
||||
"@types/node@*":
|
||||
version "16.11.7"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42"
|
||||
@ -5871,6 +5876,19 @@ address@>=0.0.1, address@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
|
||||
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
|
||||
|
||||
adler-32@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/adler-32/-/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25"
|
||||
integrity sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==
|
||||
dependencies:
|
||||
exit-on-epipe "~1.0.1"
|
||||
printj "~1.1.0"
|
||||
|
||||
adler-32@~1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2"
|
||||
integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
|
||||
|
||||
agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
@ -7187,7 +7205,7 @@ buffer-equal@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
|
||||
integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74=
|
||||
|
||||
buffer-from@1.x, buffer-from@^1.0.0:
|
||||
buffer-from@1.x, buffer-from@^1.0.0, buffer-from@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
@ -7466,6 +7484,14 @@ center-align@^0.1.1:
|
||||
align-text "^0.1.3"
|
||||
lazy-cache "^1.0.3"
|
||||
|
||||
cfb@^1.1.4:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44"
|
||||
integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
|
||||
dependencies:
|
||||
adler-32 "~1.3.0"
|
||||
crc-32 "~1.2.0"
|
||||
|
||||
chalk@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
||||
@ -7849,6 +7875,11 @@ code-point-at@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
||||
|
||||
codepage@~1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab"
|
||||
integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
|
||||
|
||||
collect-v8-coverage@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59"
|
||||
@ -8388,6 +8419,11 @@ cosmiconfig@^7, cosmiconfig@^7.0.0:
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.10.0"
|
||||
|
||||
crc-32@~1.2.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
|
||||
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||
|
||||
create-ecdh@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
|
||||
@ -10202,6 +10238,11 @@ exenv@^1.2.0:
|
||||
resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
|
||||
integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
|
||||
|
||||
exit-on-epipe@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
|
||||
integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==
|
||||
|
||||
exit@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||
@ -10676,6 +10717,11 @@ formstream@^1.1.0:
|
||||
mime "^2.5.2"
|
||||
pause-stream "~0.0.11"
|
||||
|
||||
frac@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b"
|
||||
integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
|
||||
|
||||
fraction.js@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
@ -15697,6 +15743,15 @@ node-releases@^2.0.3:
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
|
||||
integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
|
||||
|
||||
node-xlsx@^0.16.1:
|
||||
version "0.16.2"
|
||||
resolved "https://registry.npmmirror.com/node-xlsx/-/node-xlsx-0.16.2.tgz#40f580187eae0e032cac96e958e97cb6ceca09f6"
|
||||
integrity sha512-ZT3Y4Zg2BFC2UWdp9B/6x3GqrFL0Bf0cXKy9IyhcwlKbcDAf5GuPAPSqrWFQK68NIpfTNA1Kr/NNjpwYxUgHTA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
buffer-from "^1.1.1"
|
||||
xlsx "^0.17.0"
|
||||
|
||||
nodemailer-mock@^1.5.11:
|
||||
version "1.5.11"
|
||||
resolved "https://registry.npmjs.org/nodemailer-mock/-/nodemailer-mock-1.5.11.tgz#1bb6b9af44e9007380191d32e33555ea136a819f"
|
||||
@ -17785,6 +17840,11 @@ pretty-quick@^3.1.0:
|
||||
mri "^1.1.5"
|
||||
multimatch "^4.0.0"
|
||||
|
||||
printj@~1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmmirror.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
|
||||
integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
|
||||
|
||||
prism-react-renderer@^1.1.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz#392460acf63540960e5e3caa699d851264e99b89"
|
||||
@ -20599,6 +20659,13 @@ sqlstring@^2.3.2:
|
||||
resolved "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514"
|
||||
integrity sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==
|
||||
|
||||
ssf@~0.11.2:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c"
|
||||
integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
|
||||
dependencies:
|
||||
frac "~1.1.2"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
@ -22779,11 +22846,21 @@ wkx@^0.5.0:
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
wmf@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da"
|
||||
integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
|
||||
|
||||
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
word@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.npmmirror.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
|
||||
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
|
||||
|
||||
wordwrap@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
@ -22914,6 +22991,19 @@ xdg-basedir@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
|
||||
|
||||
xlsx@^0.17.0:
|
||||
version "0.17.5"
|
||||
resolved "https://registry.npmmirror.com/xlsx/-/xlsx-0.17.5.tgz#78b788fcfc0773d126cdcd7ea069cb7527c1ce81"
|
||||
integrity sha512-lXNU0TuYsvElzvtI6O7WIVb9Zar1XYw7Xb3VAx2wn8N/n0whBYrCnHMxtFyIiUU1Wjf09WzmLALDfBO5PqTb1g==
|
||||
dependencies:
|
||||
adler-32 "~1.2.0"
|
||||
cfb "^1.1.4"
|
||||
codepage "~1.15.0"
|
||||
crc-32 "~1.2.0"
|
||||
ssf "~0.11.2"
|
||||
wmf "~1.0.1"
|
||||
word "~0.3.0"
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
|
Loading…
Reference in New Issue
Block a user