diff --git a/packages/app/client/package.json b/packages/app/client/package.json index 2b3aaade5..f3fdbfa37 100644 --- a/packages/app/client/package.json +++ b/packages/app/client/package.json @@ -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", diff --git a/packages/app/client/src/pages/index.tsx b/packages/app/client/src/pages/index.tsx index 35da6966f..473aaae58 100644 --- a/packages/app/client/src/pages/index.tsx +++ b/packages/app/client/src/pages/index.tsx @@ -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, diff --git a/packages/app/client/tsconfig.json b/packages/app/client/tsconfig.json index f8870894d..3b95db8d8 100644 --- a/packages/app/client/tsconfig.json +++ b/packages/app/client/tsconfig.json @@ -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"] } diff --git a/packages/app/server/src/config/plugins.ts b/packages/app/server/src/config/plugins.ts index 3966758d1..15cfec3de 100644 --- a/packages/app/server/src/config/plugins.ts +++ b/packages/app/server/src/config/plugins.ts @@ -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; diff --git a/packages/core/actions/src/actions/list.ts b/packages/core/actions/src/actions/list.ts index d193969a2..f9e76b477 100644 --- a/packages/core/actions/src/actions/list.ts +++ b/packages/core/actions/src/actions/list.ts @@ -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 { diff --git a/packages/core/client/src/block-provider/BlockProvider.tsx b/packages/core/client/src/block-provider/BlockProvider.tsx index 30f27f821..082f3d9c2 100644 --- a/packages/core/client/src/block-provider/BlockProvider.tsx +++ b/packages/core/client/src/block-provider/BlockProvider.tsx @@ -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 ( diff --git a/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts b/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts index 2326b2be8..cee20f4d4 100644 --- a/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts +++ b/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts @@ -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; diff --git a/packages/core/client/src/index.tsx b/packages/core/client/src/index.tsx index b43c12fcc..7959329bf 100644 --- a/packages/core/client/src/index.tsx +++ b/packages/core/client/src/index.tsx @@ -26,4 +26,3 @@ export * from './slate'; export * from './system-settings'; export * from './user'; export * from './workflow'; - diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 8949a5952..7e84f7615 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -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", diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index d40533592..969dc6548 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -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": "用户", diff --git a/packages/core/client/src/schema-component/antd/cascader/Cascader.tsx b/packages/core/client/src/schema-component/antd/cascader/Cascader.tsx index 03ff5ab5c..735303b00 100644 --- a/packages/core/client/src/schema-component/antd/cascader/Cascader.tsx +++ b/packages/core/client/src/schema-component/antd/cascader/Cascader.tsx @@ -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); diff --git a/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx b/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx index 12f3e262b..9b90f2918 100644 --- a/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx +++ b/packages/core/client/src/schema-initializer/SchemaInitializerProvider.tsx @@ -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 = (props) => { const { initializers, components, children } = props; return ( diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 844244b18..70de57616 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -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 ( <> - + e.stopPropagation()}> {props.children || props.title} {createPortal( diff --git a/packages/core/devtools/umiConfig.d.ts b/packages/core/devtools/umiConfig.d.ts index 2122ad90c..ec65707e9 100644 --- a/packages/core/devtools/umiConfig.d.ts +++ b/packages/core/devtools/umiConfig.d.ts @@ -1,18 +1,20 @@ export declare function getUmiConfig(): { define: { - 'process.env.API_BASE_URL': string; + 'process.env.API_BASE_URL': string; }; proxy: { - [x: string]: { + [x: string]: + | { target: string; changeOrigin: boolean; - } | { + } + | { target: string; changeOrigin: boolean; pathRewrite: { - [x: string]: string; + [x: string]: string; }; - }; + }; }; }; diff --git a/packages/core/resourcer/src/resource.ts b/packages/core/resourcer/src/resource.ts index 531d2ae94..b53026e95 100644 --- a/packages/core/resourcer/src/resource.ts +++ b/packages/core/resourcer/src/resource.ts @@ -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'; diff --git a/packages/plugins/client/src/index.ts b/packages/plugins/client/src/index.ts index b68aea57f..7ddad5814 100644 --- a/packages/plugins/client/src/index.ts +++ b/packages/plugins/client/src/index.ts @@ -1 +1 @@ -export { default } from './plugin'; +export { default } from './server'; diff --git a/packages/plugins/client/src/plugin.ts b/packages/plugins/client/src/server.ts similarity index 100% rename from packages/plugins/client/src/plugin.ts rename to packages/plugins/client/src/server.ts diff --git a/packages/plugins/collection-manager/src/__tests__/through.test.ts b/packages/plugins/collection-manager/src/__tests__/through.test.ts index 33981d1d8..3df917ade 100644 --- a/packages/plugins/collection-manager/src/__tests__/through.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/through.test.ts @@ -1,5 +1,5 @@ import { mockServer } from '@nocobase/test'; -import CollectionManagerPlugin from '../plugin'; +import CollectionManagerPlugin from '../server'; describe('collections repository', () => { it('case 1', async () => { diff --git a/packages/plugins/collection-manager/src/index.ts b/packages/plugins/collection-manager/src/index.ts index 37dac1725..14bd3d5dd 100644 --- a/packages/plugins/collection-manager/src/index.ts +++ b/packages/plugins/collection-manager/src/index.ts @@ -1,3 +1,3 @@ -export { default } from './plugin'; export * from './repositories'; +export { default } from './server'; diff --git a/packages/plugins/collection-manager/src/models/field.ts b/packages/plugins/collection-manager/src/models/field.ts index f0dd6f485..e6f2b998b 100644 --- a/packages/plugins/collection-manager/src/models/field.ts +++ b/packages/plugins/collection-manager/src/models/field.ts @@ -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); } diff --git a/packages/plugins/collection-manager/src/plugin.ts b/packages/plugins/collection-manager/src/server.ts similarity index 100% rename from packages/plugins/collection-manager/src/plugin.ts rename to packages/plugins/collection-manager/src/server.ts diff --git a/packages/plugins/export/client.d.ts b/packages/plugins/export/client.d.ts new file mode 100644 index 000000000..d472744e5 --- /dev/null +++ b/packages/plugins/export/client.d.ts @@ -0,0 +1,2 @@ +// @ts-nocheck +export * from './lib/client'; diff --git a/packages/plugins/export/client.js b/packages/plugins/export/client.js new file mode 100644 index 000000000..5effcafee --- /dev/null +++ b/packages/plugins/export/client.js @@ -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]; + } + }); +}); diff --git a/packages/plugins/export/package.json b/packages/plugins/export/package.json new file mode 100644 index 000000000..dbf36706d --- /dev/null +++ b/packages/plugins/export/package.json @@ -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": "*" + } +} diff --git a/packages/plugins/export/server.d.ts b/packages/plugins/export/server.d.ts new file mode 100644 index 000000000..d8ed31d87 --- /dev/null +++ b/packages/plugins/export/server.d.ts @@ -0,0 +1,2 @@ +// @ts-nocheck +export * from './lib/server'; diff --git a/packages/plugins/export/server.js b/packages/plugins/export/server.js new file mode 100644 index 000000000..c93247d51 --- /dev/null +++ b/packages/plugins/export/server.js @@ -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]; + } + }); +}); diff --git a/packages/plugins/export/src/client/ExportActionInitializer.tsx b/packages/plugins/export/src/client/ExportActionInitializer.tsx new file mode 100644 index 000000000..e7acc16b2 --- /dev/null +++ b/packages/plugins/export/src/client/ExportActionInitializer.tsx @@ -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 ( + { + if (exists) { + return remove(); + } + schema['x-action-settings']['exportSettings'] = initExportSettings(fields); + const s = merge(schema || {}, item.schema || {}); + item?.schemaInitialize?.(s); + insert(s); + }} + /> + ); +}; diff --git a/packages/plugins/export/src/client/ExportDesigner.tsx b/packages/plugins/export/src/client/ExportDesigner.tsx new file mode 100644 index 000000000..86bee822c --- /dev/null +++ b/packages/plugins/export/src/client/ExportDesigner.tsx @@ -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(); + const { schema: pageSchema } = useShared(); + + useEffect(() => { + setSchema(pageSchema); + }, [field.address, fieldSchema?.['x-action-settings']?.['exportSettings']]); + + return ( + + { + 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(); + } + }} + /> + { + 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(); + }} + /> + + { + return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar'); + }} + confirm={{ + title: t('Delete action'), + }} + /> + + ); +}; diff --git a/packages/plugins/export/src/client/ExportInitializerProvider.tsx b/packages/plugins/export/src/client/ExportInitializerProvider.tsx new file mode 100644 index 000000000..39b9df750 --- /dev/null +++ b/packages/plugins/export/src/client/ExportInitializerProvider.tsx @@ -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; +}; diff --git a/packages/plugins/export/src/client/ExportPluginProvider.tsx b/packages/plugins/export/src/client/ExportPluginProvider.tsx new file mode 100644 index 000000000..901e010af --- /dev/null +++ b/packages/plugins/export/src/client/ExportPluginProvider.tsx @@ -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 ( + + {props.children} + + ); +}; diff --git a/packages/plugins/export/src/client/index.ts b/packages/plugins/export/src/client/index.ts new file mode 100644 index 000000000..45a1aed80 --- /dev/null +++ b/packages/plugins/export/src/client/index.ts @@ -0,0 +1,6 @@ +export * from './ExportActionInitializer'; +export * from './ExportDesigner'; +export * from './ExportInitializerProvider'; +export * from './ExportPluginProvider'; +export * from './useExportAction'; + diff --git a/packages/plugins/export/src/client/useExportAction.ts b/packages/plugins/export/src/client/useExportAction.ts new file mode 100644 index 000000000..729f94f06 --- /dev/null +++ b/packages/plugins/export/src/client/useExportAction.ts @@ -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); + }, + }; +}; diff --git a/packages/plugins/export/src/client/useFields.ts b/packages/plugins/export/src/client/useFields.ts new file mode 100644 index 000000000..82c412ee1 --- /dev/null +++ b/packages/plugins/export/src/client/useFields.ts @@ -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); +}; diff --git a/packages/plugins/export/src/client/useShared.ts b/packages/plugins/export/src/client/useShared.ts new file mode 100644 index 000000000..c62efce37 --- /dev/null +++ b/packages/plugins/export/src/client/useShared.ts @@ -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); + } + `, + }, + }, + }, + }, + }, + }, + }; +}; diff --git a/packages/plugins/export/src/index.ts b/packages/plugins/export/src/index.ts new file mode 100644 index 000000000..7ddad5814 --- /dev/null +++ b/packages/plugins/export/src/index.ts @@ -0,0 +1 @@ +export { default } from './server'; diff --git a/packages/plugins/export/src/server/actions/.gitkeep b/packages/plugins/export/src/server/actions/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/export/src/server/actions/export-xlsx.ts b/packages/plugins/export/src/server/actions/export-xlsx.ts new file mode 100644 index 000000000..ae2011f97 --- /dev/null +++ b/packages/plugins/export/src/server/actions/export-xlsx.ts @@ -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(); +} diff --git a/packages/plugins/export/src/server/actions/index.ts b/packages/plugins/export/src/server/actions/index.ts new file mode 100644 index 000000000..91cdfdbd6 --- /dev/null +++ b/packages/plugins/export/src/server/actions/index.ts @@ -0,0 +1 @@ +export * from './export-xlsx'; diff --git a/packages/plugins/export/src/server/collections/.gitkeep b/packages/plugins/export/src/server/collections/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/export/src/server/index.ts b/packages/plugins/export/src/server/index.ts new file mode 100644 index 000000000..885be9f54 --- /dev/null +++ b/packages/plugins/export/src/server/index.ts @@ -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; diff --git a/packages/plugins/export/src/server/models/.gitkeep b/packages/plugins/export/src/server/models/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/export/src/server/renders/index.ts b/packages/plugins/export/src/server/renders/index.ts new file mode 100644 index 000000000..4ea3a1c58 --- /dev/null +++ b/packages/plugins/export/src/server/renders/index.ts @@ -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, + }; +} diff --git a/packages/plugins/export/src/server/renders/renders.ts b/packages/plugins/export/src/server/renders/renders.ts new file mode 100644 index 000000000..9a16edffa --- /dev/null +++ b/packages/plugins/export/src/server/renders/renders.ts @@ -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('/'); +} diff --git a/packages/plugins/export/src/server/repositories/.gitkeep b/packages/plugins/export/src/server/repositories/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/system-settings/src/index.ts b/packages/plugins/system-settings/src/index.ts index b68aea57f..7ddad5814 100644 --- a/packages/plugins/system-settings/src/index.ts +++ b/packages/plugins/system-settings/src/index.ts @@ -1 +1 @@ -export { default } from './plugin'; +export { default } from './server'; diff --git a/packages/plugins/system-settings/src/plugin.ts b/packages/plugins/system-settings/src/server.ts similarity index 100% rename from packages/plugins/system-settings/src/plugin.ts rename to packages/plugins/system-settings/src/server.ts diff --git a/packages/plugins/ui-routes-storage/src/index.ts b/packages/plugins/ui-routes-storage/src/index.ts index 096d9263f..7ddad5814 100644 --- a/packages/plugins/ui-routes-storage/src/index.ts +++ b/packages/plugins/ui-routes-storage/src/index.ts @@ -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'; diff --git a/packages/plugins/ui-routes-storage/src/server.ts b/packages/plugins/ui-routes-storage/src/server.ts new file mode 100644 index 000000000..096d9263f --- /dev/null +++ b/packages/plugins/ui-routes-storage/src/server.ts @@ -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; diff --git a/packages/plugins/workflow/src/index.ts b/packages/plugins/workflow/src/index.ts index 6074afd19..bc6109ec9 100644 --- a/packages/plugins/workflow/src/index.ts +++ b/packages/plugins/workflow/src/index.ts @@ -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'; + + diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 88f1db02b..149bb67dc 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -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": { diff --git a/yarn.lock b/yarn.lock index 9e523d150..ff33f6e0d 100644 --- a/yarn.lock +++ b/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"