业务背景
我们有一个 pc 端系统,运营人员需要发布策略,这个策略可以理解为一个 json,为了可以让运营更方便的编辑 json。我们采用的技术方案是使用 monaco-editor 体验可以类似 vscode。除此之外还有一个校验问题,为了避免运营人员发布错误的 json。我们需要配置一个校验规则的 schema,可以提示输入也可以提示错误,我们采用了 ajv 来实现。
为什么使用全局的脚本引入的方式引入
由于第三方库不稳定,并且第三方库对于定制的 schema 校验等能力并不完善
以下是业务代码
import React, { useState, useRef, useEffect } from 'react';
import {
Form, Button, message, notification,
} from 'antd';
import PropTypes from 'prop-types';
import { useSize } from 'ahooks';
import Ajv from 'ajv';
import { renderForm } from '@/utils/renderFormItem';
import { ajaxAndLoading } from '@/utils/util';
import { createPolicy } from '@/service/policy';
import { getFormArr } from './FormConfig';
import schema from './schema';
import './index.scss';
let monacoInited = false;
const loadScriptResultMap = {};
function importScript(src) {
if (loadScriptResultMap[src]) {
return loadScriptResultMap[src];
}
loadScriptResultMap[src] = new Promise((resolve, reject) => {
const newScript = document.createElement('script');
newScript.src = src;
document.body.append(newScript);
newScript.addEventListener('load', () => {
resolve();
});
newScript.onerror = () => {
monacoInited = false;
document.body.removeChild(newScript);
reject();
};
});
return loadScriptResultMap[src];
}
function initMocaco() {
if (monacoInited === true) {
return Promise.resolve();
}
return importScript('https://g.alicdn.com/code/lib/monaco-editor/0.20.0/min/vs/loader.js').then(() => {
window.require.config({ paths: { vs: '//g.alicdn.com/code/lib/monaco-editor/0.20.0/min/vs' } });
monacoInited = true;
}).catch(() => {
monacoInited = false;
});
}
function FormWrap(props) {
const [loading, setLoading] = useState(false);
const { form, history, userId } = props;
const actionFunc = () => {
history.push('/strategy');
message.success('成功创建策略');
};
const handleCancel = () => history.push('/strategy');
const EditorRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
const { validateFieldsAndScroll } = form;
validateFieldsAndScroll((err, values) => {
const ajv = new Ajv({ allErrors: true });
const validate = ajv.compile(schema.schema);
const valid = validate(JSON.parse(EditorRef.current.getValue()));
if (!valid) {
const errorTip = validate?.errors?.map(v => (
<React.Fragment key={v}>{v.dataPath} {v.message} <br /></React.Fragment>
));
notification.warning({
description: errorTip,
message: '错误',
placement: 'topRight',
});
}
if (!err && valid) {
const { name, description, type } = values;
const params = {
name,
description,
creator: userId,
content: JSON.stringify(JSON.parse(EditorRef.current.getValue())),
plugin: type,
};
ajaxAndLoading(createPolicy, params, setLoading, null, actionFunc);
}
});
};
const formSetter = getFormArr(form);
const needKeys = ['name', 'description', 'type'];
const filterControl = formSetter.filter(v => needKeys.includes(v.key));
const ref = useRef(null);
const size = useSize(ref);
const container = useRef(null);
useEffect(() => {
initMocaco().then(() => {
window.require(['vs/editor/editor.main'], () => {
if (container.current) {
EditorRef.current = window.monaco.editor.create(container.current, {
value: '{}',
language: 'json',
theme: localStorage.getItem('themeEditor') || 'hc-black',
});
window.monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [schema],
});
}
});
});
return () => {
if (EditorRef.current) {
EditorRef.current.dispose();
const model = EditorRef.current.getModel();
if (model) {
model.dispose();
}
}
};
}, []);
return (
<Form onSubmit={handleSubmit}>
<div className="ant-advanced-search-form">
{renderForm(filterControl).map(v => v)}
</div>
<div ref={ref}>
<div style={{ height: 500, width: size.width }} ref={container} />
</div>
<div id="test" />
<Form.Item className="m-t-12">
<Button type="primary" style={{ marginRight: 8 }} loading={loading} htmlType="submit">
提交
</Button>
<Button onClick={handleCancel}>
取消
</Button>
</Form.Item>
</Form>
);
}
FormWrap.propTypes = {
form: PropTypes.any.isRequired,
history: PropTypes.object.isRequired,
userId: PropTypes.string,
};
FormWrap.defaultProps = {
userId: '',
};
export default Form.create({ name: 'form-wrap' })(FormWrap);