const schema = { type: 'page', body: [ { type: 'form', name: 'cg', body: [ { type: 'service', name: 'service', api: { method: 'get', url: 'rest/exam_question_categories/${tree}', sendOn: 'this.tree', }, messages: {}, body: [ { type: 'formula', name: 'filter', formula: "'(category.in.(' + data.tree + '),category.is.null)'", condition: 'data.tree && data.no_cate', id: 'u:20719f5b3c61', }, { type: 'formula', name: 'filter', condition: 'data.tree && !data.no_cate', formula: "'(category.in.(' + data.tree + '))'", id: 'u:da3404346ffb', }, { type: 'formula', name: 'filter', condition: '!data.tree && data.no_cate', formula: "'(category.is.null)'", id: 'u:c7c6c0aa04b6', }, { type: 'formula', name: 'filter', formula: "'(id.neq.0)'", condition: '!data.tree && !data.no_cate', id: 'u:d2517bd7c8ee', }, ], id: 'u:87d7f2fe2416', dsType: 'api', }, { type: 'tree-select', label: '分类', name: 'tree', mode: 'horizontal', source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n\r\n return nodes.map(item => {\r\n let temp = recursive(item.id)\r\n\r\n if (temp.length > 0) {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: temp\r\n }\r\n } else {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`\r\n }\r\n }\r\n })\r\n}\r\n\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\n\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\n', }, initiallyOpen: false, searchable: true, multiple: true, joinValues: true, onlyChildren: false, withChildren: true, cascade: true, heightAuto: true, virtualThreshold: false, id: 'u:cde3f7416895', }, { type: 'checkbox', label: '', name: 'no_cate', mode: 'inline', option: '显示无分类题目', id: 'u:50f0cbebc61b', }, { type: 'switch', name: 'is_show', mode: 'inline', value: true, option: '缩略显示', optionAtLeft: false, trueValue: true, falseValue: false, id: 'u:bfaf3772c9d4', }, ], title: '表单', wrapWithPanel: false, reload: 'stu?parent=${tree}&tree=${tree}&org_name=${name}&filter=${filter}&is_show=${is_show}', submitOnInit: true, submitOnChange: true, target: '', initApi: '', data: { parentId: 1, }, md: 3, resetAfterSubmit: false, id: 'u:48ceddfb7e02', feat: 'View', }, { type: 'divider', lineStyle: 'solid', id: 'u:7a199eac8602', }, { type: 'crud', messages: {}, api: { method: 'get', url: 'rest/exam_questions?select=id,type,content,analysis,tag,score,organization_id,answer,difficulty,is_objective,category,exam_question_categories(*),dicts(dictkey,dictvalue)&organization_id=eq.$centre_id&order=category,id', data: { page: '${page}', perPage: '${perPage}', or: '${filter}', }, adaptor: "return {\r\n ...payload,\r\n data: {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n let pattern = /

|<\\/p>||<\\/strong>|
/g\r\n let image_pattern = //g\r\n let raw_options = ''\r\n item.content.options && item.content.options.forEach(option => {\r\n raw_options = raw_options.concat(option.label.replace(/^

/, '

'.concat(option.key, '. ')).replace(/^<%= this.dicts && this.dicts.dictvalue %>\n

<%= this.content && this.content.title %>
\n<% this.content && this.content.options && this.content.options.forEach(item => { %>\n
<%= item.label.replace(/^

/, '

'.concat(item.key, '. ')).replace(/^

\n<% }) %>\n
<%= this.answer && '答案'.concat(this.answer) %>
\n
<%= this.analysis && this.analysis.replace(/^

/, '

'.concat('答案解析')).replace(/^答案解析

", inline: false, }, ], size: 'lg', actions: [], closeOnEsc: true, }, width: 500, innerClassName: '', className: 'question', block: false, level: 'link', remark: '', tooltip: '$raw_title', tooltipPlacement: 'bottom', id: 'u:84ea04af2db4', }, { name: 'content.title', type: 'tpl', placeholder: '-', tpl: '<% if(data.content) { %><%= data.content.title %><% } %>', label: '题干', quickEdit: { mode: 'popOver', type: 'image-upload', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { content: '$content', }, requestAdaptor: 'let pattern = new RegExp("(\\\\.\\\\./){1,6}")\r\n\r\nif (api.data.content.options.length) {\r\n api.data.content = {\r\n title: api.data.content.title.replace(pattern, "/"),\r\n options: api.data.content.options.map(item => {\r\n return {\r\n ...item,\r\n label: item.label.replace(pattern, "/")\r\n }\r\n })\r\n }\r\n} else {\r\n api.data.content = {\r\n title: api.data.content.title.replace(pattern, "/")\r\n }\r\n}\r\n\r\nreturn api', }, }, name: 'content.title', }, inline: false, popOver: false, copyable: false, visibleOn: '!this.is_show', id: 'u:16e82384ee3c', }, { type: 'tpl', name: 'raw_options', placeholder: '-', visibleOn: '!this.is_show', label: '选项', tpl: '<%= this.raw_options %>', inline: false, quickEdit: { type: 'custom-option', name: 'content.options', label: '选项配置', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { content: '$content', }, requestAdaptor: 'let options = JSON.parse(JSON.stringify(api.data.content.options))\r\nlet index = options\r\n .findIndex(item => !Object.keys(item).length)\r\nwhile (index != -1) {\r\n options.splice(index, 1)\r\n index = options\r\n .findIndex(item => !Object.keys(item).length)\r\n}\r\n\r\napi.data.content = {\r\n ...api.data.content,\r\n options: options\r\n}\r\n\r\nlet pattern = new RegExp("(\\\\.\\\\./){1,6}")\r\n\r\nif (api.data.content.options.length) {\r\n api.data.content = {\r\n title: api.data.content.title.replace(pattern, "/"),\r\n options: api.data.content.options.map(item => {\r\n return {\r\n ...item,\r\n label: item.label.replace(pattern, "/")\r\n }\r\n })\r\n }\r\n} else {\r\n api.data.content = {\r\n title: api.data.content.title.replace(pattern, "/")\r\n }\r\n}\r\n\r\nreturn api', }, }, mode: 'popOver', form: { title: '修改选项', controls: [ { type: 'text', label: '选项', name: 'key', disabledOn: 'true', }, { name: 'label', label: '内容', type: 'input-rich-text', vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, mode: 'normal', }, { type: 'switch', name: 'is_fixed', label: '', option: '是否固定', optionAtLeft: false, trueValue: true, falseValue: false, }, ], size: 'lg', }, multiple: true, btnLabel: '设置选项', }, popOver: false, copyable: false, quickEditEnabledOn: "this.type === 'question_single_choice' || this.type === 'question_multi_choice'", id: 'u:aee56529edd2', }, { type: 'tpl', placeholder: '-', name: 'answer', label: '答案', quickEdit: { type: 'input-text', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { answer: '$answer', }, }, saveImmediately: true, }, }, tpl: '<%= data.answer %>', inline: false, visibleOn: '!this.is_show', id: 'u:01aba281f46f', }, { type: 'tpl', name: 'analysis', label: '答案解析', placeholder: '-', tpl: '<%= data.analysis %>', inline: false, quickEdit: { type: 'input-rich-text', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { analysis: '$analysis', }, requestAdaptor: 'let pattern = new RegExp("(\\\\.\\\\./){1,6}")\r\n\r\napi.data.analysis = api.data.analysis.replace(pattern, "/")\r\n\r\nreturn api', }, }, mode: 'popOver', name: 'analysis', vendor: 'tinymce', options: { menubar: 'true', relative_urls: false, remove_script_host: true, height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, }, visibleOn: '!this.is_show', id: 'u:c7737bb550ad', }, { type: 'text', label: '题型', name: 'dicts.dictvalue', placeholder: '-', quickEdit: { type: 'select', options: [ { label: '单选题', value: 'question_single_choice', }, { label: '多选题', value: 'question_multi_choice', }, { label: '判断题', value: 'question_true_or_false', }, ], saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { type: '${dicts.dictkey}', }, requestAdaptor: "if(api.data.type === 'question_single_choice' || api.data.type === 'question_multi_choice' || api.data.type === 'question_true_or_false') {\r\n api.data.is_objective = true;\r\n} else {\r\n api.data.is_objective = false;\r\n}\r\nreturn api;", }, }, name: 'dicts.dictkey', mode: 'popOver', }, id: 'u:cb1a31cb64c5', }, { type: 'text', label: '难易程度', placeholder: '-', name: 'difficulty', quickEdit: { type: 'select', options: [ { label: '简单', value: '简单', }, { label: '中等', value: '中等', }, { label: '困难', value: '困难', }, ], saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { difficulty: '$difficulty', }, }, }, }, id: 'u:f802eab00c6d', }, { type: 'text', label: '分数', name: 'score', placeholder: '-', quickEdit: { type: 'input-number', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { score: '$score', }, }, }, }, id: 'u:b3daeb51d76d', }, { type: 'tpl', label: '类别', name: 'exam_question_categories.name', placeholder: '-', quickEdit: { type: 'tree-select', source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', }, saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { category: '${exam_question_categories.id}', }, }, }, name: 'exam_question_categories.id', initiallyOpen: false, heightAuto: true, virtualThreshold: false, mode: 'popOver', }, id: 'u:9c16b39eb764', }, { type: 'operation', buttons: [ { type: 'button-group', buttons: [ { type: 'button', actionType: 'dialog', dialog: { type: 'dialog', title: '修改', body: [ { type: 'form', title: '表单', api: { method: 'patch', url: 'rest/exam_questions/$id', data: { content: '$content', category: '$category', answer: '$answer', analysis: '$analysis', difficulty: '$difficulty', is_objective: '$is_objective', type: '$type', }, dataType: 'json', requestAdaptor: 'if (api && api.data && api.data.content && api.data.content.options) {\r\n let options = JSON.parse(JSON.stringify(api.data.content.options))\r\n let index = options.findIndex(item => !Object.keys(item).length)\r\n while (index != -1) {\r\n options.splice(index, 1)\r\n index = options.findIndex(item => !Object.keys(item).length)\r\n }\r\n\r\n api.data.content = {\r\n ...api.data.content,\r\n options: options\r\n }\r\n} else if (api && api.data && api.data.type === \'question_true_or_false\') {\r\n api.data.content = {\r\n ...api.data.content,\r\n options: [\r\n { "key": "正确", "label": "正确", "is_fixed": false },\r\n { "key": "错误", "label": "错误", "is_fixed": false }\r\n ]\r\n }\r\n}\r\n\r\nlet pattern = new RegExp("(\\\\.\\\\./){1,6}")\r\napi.data.content.title = api.data.content.title.replace(pattern, "/")\r\n\r\nif (api.data.content.options.length) {\r\n api.data.content.options = api.data.content.options.map(item => {\r\n return {\r\n ...item,\r\n label: item.label.replace(pattern, "/")\r\n }\r\n })\r\n}\r\n\r\napi.data.analysis = api.data.analysis.replace(pattern, "/")\r\n\r\nreturn api', }, body: [ { label: '', type: 'combo', name: 'content', multiple: false, multiLine: true, joinValues: true, messages: {}, typeSwitchable: false, strictMode: true, mode: 'normal', clearValueOnHidden: true, visibleOn: "this.type === 'question_single_choice' || this.type === 'question_multi_choice'", items: [ { type: 'input-rich-text', label: '题干', name: 'title', mode: 'normal', reciever: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, }, { label: '选项配置', name: 'options', type: 'custom-option', form: { title: '修改选项', controls: [ { type: 'text', label: '选项', name: 'key', }, { name: 'label', label: '内容', type: 'input-rich-text', vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, mode: 'normal', }, { type: 'switch', name: 'is_fixed', label: '', option: '是否固定', optionAtLeft: false, trueValue: true, falseValue: false, }, ], size: 'lg', }, multiple: true, btnLabel: '设置选项', }, ], }, { type: 'combo', label: '', name: 'content', mode: 'normal', value: '', multiple: false, multiLine: true, joinValues: true, messages: {}, typeSwitchable: false, strictMode: true, visibleOn: "this.type === 'question_true_or_false' || this.type === 'question_filling' || this.type === 'question_short_answer'", clearValueOnHidden: true, items: [ { type: 'input-rich-text', label: '题干', name: 'title', vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, mode: 'normal', }, ], }, { type: 'input-text', label: '答案', name: 'answer', mode: 'normal', }, { type: 'input-rich-text', label: '答案解析', name: 'analysis', mode: 'normal', receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, vendor: 'tinymce', options: { menubar: 'true', relative_urls: false, remove_script_host: true, height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, }, { type: 'input-text', label: '难易程度', name: 'difficulty', mode: 'normal', }, { type: 'select', name: 'type', label: '题型', mode: 'normal', options: [], checkAll: false, source: { method: 'get', url: 'rest/dicts?typecode=eq.019', adaptor: 'return {\r\n ...payload.data,\r\n items: payload.data.items.map(item => {\r\n return {\r\n label: item.dictvalue,\r\n value: item.dictkey\r\n }\r\n })\r\n}', }, }, { type: 'formula', name: 'is_objective', formula: "data.type === 'question_single_choice' || data.type === 'question_multi_choice' || data.type === 'question_true_or_false'", }, { type: 'hidden', name: 'is_objective', }, { type: 'tree-select', name: 'category', label: '类别', options: [], source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', }, mode: 'normal', heightAuto: true, virtualThreshold: false, }, ], }, ], closeOnEsc: true, showCloseButton: true, size: 'lg', }, level: 'link', size: 'md', icon: 'fa fa-pencil text-info', tooltip: '修改', tooltipPlacement: 'top', className: 'p-r-none p-l-none', iconClassName: 'pull-left', id: 'u:467671f595fd', }, { type: 'button', actionType: 'ajax', confirmText: '确认删除?', api: { method: 'delete', url: 'rest/exam_questions/$id', }, level: 'link', icon: 'fa fa-times text-danger', size: 'md', tooltip: '删除', tooltipPlacement: 'top', iconClassName: 'pull-left', className: 'p-r-none p-l-none', id: 'u:9b3cceb15b58', }, ], label: '操作', id: 'u:1c467b0d0cdb', }, ], label: '操作', placeholder: '-', width: 100, id: 'u:2850d5909876', }, ], visibleOn: '', perPageAvailable: ['10', '20', 30, 40, 50], className: '', headerToolbar: [ { type: 'bulk-actions', }, { type: 'wrapper', title: '', body: [ { type: 'tpl', tpl: '${org_name}', inline: true, name: 'titletpl', visibleOn: "this.tree && this.tree.split(',').length === 1", id: 'u:3f915fa4c5c9', }, { type: 'button', label: '', actionType: 'dialog', dialog: { type: 'dialog', title: '题目信息', body: [ { type: 'form', title: '表单', api: { method: 'patch', url: 'rest/exam_question_categories/$tree', data: { name: '$name', parent: '$parent', }, }, initApi: 'rest/exam_question_categories/${tree}', body: [ { label: '名称', type: 'input-text', name: 'name', mode: 'normal', id: 'u:d00a7b4108d3', }, { type: 'tree-select', name: 'parent', label: '上级目录', mode: 'normal', onlyChildren: true, heightAuto: true, virtualThreshold: false, source: { url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', method: 'get', }, id: 'u:28516ae265d1', }, ], id: 'u:f314267b4b38', actions: [ { type: 'submit', label: 'Submit', primary: true, }, ], feat: 'Edit', dsType: 'api', }, ], closeOnEsc: true, showCloseButton: true, id: 'u:8a446118216b', actions: [ { type: 'button', actionType: 'cancel', label: 'Cancel', id: 'u:63e0b9a383ab', }, { type: 'button', actionType: 'confirm', label: 'confirm', primary: true, id: 'u:3df731dbb334', }, ], closeOnOutside: false, showErrorMsg: true, showLoading: true, draggable: false, }, level: 'link', icon: 'fa fa-edit', iconClassName: '', visibleOn: "this.tree && this.tree.split(',').length === 1", reload: 'cg.tree', id: 'u:0b1f6849e465', }, { type: 'button', label: '', actionType: 'ajax', icon: 'fa fa-trash', level: 'link', iconClassName: 'text-danger', confirmText: '确认删除?', api: { method: 'delete', url: 'rest/exam_question_categories/$tree', }, reload: 'cg.tree', visibleOn: "this.tree && this.tree.split(',').length === 1", id: 'u:e972a2b364b2', }, ], name: 'header', id: 'u:373ca0cc2ca7', }, { type: 'button', label: '添加分类', actionType: 'dialog', align: 'right', icon: '', tpl: '内容', level: 'primary', dialog: { type: 'dialog', title: '添加分类', body: [ { type: 'form', title: '表单', api: { method: 'post', url: 'rest/exam_question_categories', requestAdaptor: 'return {\r\n ...api,\r\n data: {\r\n ...api.data,\r\n parent: api.data.parent ? api.data.parent : null\r\n }\r\n}', headers: { Prefer: 'params=single-object', }, adaptor: '', }, body: [ { label: '名称', type: 'input-text', name: 'name', required: true, mode: 'normal', }, { type: 'tree-select', label: '上级目录', name: 'parent', mode: 'normal', source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', }, onlyChildren: true, heightAuto: true, virtualThreshold: false, disabledOn: 'true', }, ], }, ], closeOnEsc: true, showCloseButton: true, }, reload: 'cg.tree', id: 'u:88fc9975bcf8', }, { type: 'button', label: '题库导入', level: 'primary', actionType: 'dialog', dialog: { type: 'dialog', title: '题库导入', body: [ { type: 'form', title: '表单', controls: [ { type: 'tree-select', label: '分类', name: 'tree', mode: 'inline', options: [], source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', }, size: 'sm', initiallyOpen: false, heightAuto: true, virtualThreshold: false, }, { type: 'examimport', name: 'import', mode: 'inline', label: '请选择要导入的Word', visibleOn: 'this.tree', }, { type: 'table', name: 'import', mode: 'normal', label: '', columns: [ { label: '题干', name: 'title', quickEdit: { type: 'input-rich-text', name: 'title', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { content: '$content', }, }, }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, mode: 'popOver', }, type: 'tpl', width: 400, placeholder: '-', }, { label: '选项', name: 'options', type: 'each', items: { type: 'tpl', tpl: '<%= data.key %>. <%= data.label %>', inline: false, }, quickEdit: { type: 'custom-option', form: { title: '修改选项', controls: [ { type: 'text', label: '选项', name: 'key', disabledOn: 'true', }, { name: 'label', label: '内容', type: 'input-rich-text', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { content: '$content', }, }, }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, }, { type: 'switch', name: 'is_fixed', label: '', option: '是否固定', optionAtLeft: false, trueValue: true, falseValue: false, }, ], size: 'lg', }, multiple: true, btnLabel: '设置选项', name: 'options', mode: 'popOver', }, quickEditEnabledOn: "this.type === '单选题' || this.type === '多选题'", placeholder: '-', }, { label: '答案', name: 'answer', type: 'tpl', quickEdit: { type: 'text', }, }, { label: '答案解析', name: 'analysis', quickEdit: { type: 'input-rich-text', name: 'analysis', saveImmediately: { api: { method: 'patch', url: 'rest/exam_questions/$id', data: { content: '$content', }, }, }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, vendor: 'tinymce', options: { menubar: 'true', relative_urls: false, remove_script_host: true, height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, mode: 'popOver', }, type: 'tpl', placeholder: '-', }, { label: '难度', name: 'difficulty', quickEdit: { type: 'select', options: [ { label: '简单', value: '简单', }, { label: '中等', value: '中等', }, { label: '困难', value: '困难', }, ], }, type: 'tpl', width: 10, }, { label: '题型', name: 'type', quickEdit: { type: 'select', options: [ { label: '单选题', value: '单选题', }, { label: '多选题', value: '多选题', }, { label: '判断题', value: '判断题', }, ], }, type: 'tpl', width: 50, }, ], }, ], api: { method: 'post', url: 'rest/exam_questions', data: { '&': '$import', }, requestAdaptor: "return {\r\n ...api,\r\n data: api.data.map(item => {\r\n let content = {}\r\n if (item.options) {\r\n let options = JSON.parse(JSON.stringify(item.options))\r\n let index = options\r\n .findIndex(item => !Object.keys(item).length)\r\n\r\n while (index != -1) {\r\n options.splice(index, 1)\r\n index = options\r\n .findIndex(item => !Object.keys(item).length)\r\n }\r\n\r\n content = {\r\n title: item.title ? item.title : '',\r\n options: options\r\n }\r\n } else {\r\n content = {\r\n title: item.title ? item.title : ''\r\n }\r\n }\r\n if (!item.type) {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_unknow',\r\n organization_id: item.organization_id,\r\n is_objective: true\r\n }\r\n } else if(item.type === '单选题') {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_single_choice',\r\n organization_id: item.organization_id,\r\n is_objective: true\r\n }\r\n } else if(item.type === '多选题') {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_multi_choice',\r\n organization_id: item.organization_id,\r\n is_objective: true\r\n }\r\n } else if(item.type === '判断题') {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_true_or_false',\r\n organization_id: item.organization_id,\r\n is_objective: true\r\n }\r\n } else if(item.type === '填空题') {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_filling',\r\n organization_id: item.organization_id,\r\n is_objective: false\r\n }\r\n } else if(item.type === '简答题') {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_short_answer',\r\n organization_id: item.organization_id,\r\n is_objective: false\r\n }\r\n } else {\r\n return {\r\n content: content,\r\n answer: item.answer ? item.answer : '',\r\n analysis: item.analysis ? item.analysis : '',\r\n difficulty: item.difficulty ? item.difficulty : '',\r\n category: item.category,\r\n type: 'question_unknow',\r\n organization_id: item.organization_id,\r\n is_objective: false\r\n }\r\n }\r\n })\r\n}", }, target: '', name: 'form', actions: [ { actionType: 'reload', target: 'recognize', }, ], }, ], closeOnEsc: true, showCloseButton: true, size: 'xl', }, className: '', align: 'right', visibleOn: 'this.tree', id: 'u:9208bc84a232', }, { type: 'button', label: '添加试题', level: 'primary', actionType: 'dialog', dialog: { type: 'dialog', title: '添加试题', body: [ { type: 'form', title: '表单', api: { method: 'post', url: 'rest/exam_questions', data: { organization_id: '${centre_id}', type: '$type', content: '$content', answer: '$answer', analysis: '$analysis', score: '$score', difficulty: '$difficulty', category: '$tree', is_objective: '$is_objective', }, requestAdaptor: 'if (api && api.data && api.data.content && api.data.content.options) {\r\n let options = JSON.parse(JSON.stringify(api.data.content.options))\r\n let index = options.findIndex(item => !Object.keys(item).length)\r\n while (index != -1) {\r\n options.splice(index, 1)\r\n index = options.findIndex(item => !Object.keys(item).length)\r\n }\r\n\r\n api.data.content = {\r\n ...api.data.content,\r\n options: options\r\n }\r\n} else if (api && api.data && api.data.type === \'question_true_or_false\') {\r\n api.data.content = {\r\n ...api.data.content,\r\n options: [\r\n { "key": "正确", "label": "正确", "is_fixed": false },\r\n { "key": "错误", "label": "错误", "is_fixed": false }\r\n ]\r\n }\r\n}\r\n\r\nlet pattern = new RegExp("(\\\\.\\\\./){1,6}")\r\napi.data.content.title = api.data.content.title.replace(pattern, "/")\r\n\r\nif (api.data.content.options.length) {\r\n api.data.content.options = api.data.content.options.map(item => {\r\n return {\r\n ...item,\r\n label: item.label.replace(pattern, "/")\r\n }\r\n })\r\n}\r\n\r\napi.data.analysis = api.data.analysis.replace(pattern, "/")\r\n\r\nreturn api', }, body: [ { label: '题型', type: 'select', name: 'type', mode: 'normal', options: [], checkAll: false, source: { method: 'get', url: 'rest/dicts?select=value:dictkey,label:dictvalue&typecode=eq.019&dictkey=neq.question_unknow', }, }, { type: 'formula', name: 'is_objective', formula: "data.type === 'question_single_choice' || data.type === 'question_multi_choice' || data.type === 'question_true_or_false'", }, { type: 'hidden', name: 'is_objective', }, { type: 'combo', label: '', name: 'content', mode: 'normal', multiple: false, multiLine: true, joinValues: true, messages: {}, typeSwitchable: false, strictMode: true, value: { options: [ { key: 'A', label: '', is_fixed: false, }, { key: 'B', label: '', is_fixed: false, }, { key: 'C', label: '', is_fixed: false, }, { key: 'D', label: '', is_fixed: false, }, ], }, visibleOn: "this.type === 'question_single_choice' || this.type === 'question_multi_choice'", clearValueOnHidden: true, items: [ { type: 'input-rich-text', label: '题干', name: 'title', vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, mode: 'normal', }, { label: '选项配置', name: 'options', type: 'custom-option', form: { title: '修改选项', controls: [ { type: 'text', label: '选项', name: 'key', disabledOn: 'true', }, { name: 'label', label: '内容', type: 'input-rich-text', vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, mode: 'normal', }, { type: 'switch', name: 'is_fixed', label: '', option: '是否固定', optionAtLeft: false, trueValue: true, falseValue: false, }, ], size: 'lg', }, multiple: true, btnLabel: '设置选项', }, ], }, { type: 'combo', name: 'content', label: '', mode: 'normal', value: '', multiple: false, multiLine: true, joinValues: true, messages: {}, typeSwitchable: false, strictMode: true, visibleOn: "this.type === 'question_true_or_false' || this.type === 'question_filling' || this.type === 'question_short_answer'", clearValueOnHidden: true, items: [ { type: 'input-rich-text', label: '题干', name: 'title', vendor: 'tinymce', options: { menubar: 'true', height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, mode: 'normal', }, ], }, { type: 'input-text', label: '答案', name: 'answer', mode: 'normal', }, { type: 'input-rich-text', label: '答案解析', name: 'analysis', mode: 'normal', receiver: { method: 'post', url: 'storage/v1/object/exam', adaptor: '', data: null, requestAdaptor: '', }, vendor: 'tinymce', options: { menubar: 'true', relative_urls: false, remove_script_host: true, height: 400, plugins: 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking table emoticons template paste help', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help', }, }, { type: 'input-number', label: '分数', name: 'score', mode: 'normal', value: 1, }, { type: 'select', label: '难易程度', name: 'difficulty', mode: 'normal', options: [ { label: '简单', value: '简单', }, { label: '中等', value: '中等', }, { label: '困难', value: '困难', }, ], checkAll: false, }, { type: 'tree-select', label: '类别', name: 'tree', options: [], mode: 'normal', source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', }, initiallyOpen: false, heightAuto: true, virtualThreshold: false, }, ], }, ], closeOnEsc: true, showCloseButton: true, size: 'lg', }, visibleOn: 'this.tree', align: 'right', id: 'u:79a6cf04b4d0', }, { type: 'button', label: '题库模板示例', onEvent: { click: { actions: [ { args: { url: '/download/题库模板参考.docx', }, actionType: 'url', }, ], weight: 0, }, }, level: 'success', icon: 'fa fa-download', align: 'right', id: 'u:06075e8d295a', }, { type: 'button', label: '题库模板说明', onEvent: { click: { actions: [ { args: { url: '/download/题库模板说明.docx', }, actionType: 'url', }, ], weight: 0, }, }, level: 'success', icon: 'fa fa-download', align: 'right', id: 'u:2f4c3be56c4f', }, ], footerToolbar: [ { type: 'pagination', }, { type: 'switch-per-page', }, { type: 'statistics', }, ], bulkActions: [ { label: '批量移动', type: 'button', actionType: 'dialog', dialog: { type: 'dialog', title: '移动到分类', body: [ { type: 'form', title: '表单', api: { method: 'patch', url: 'rest/exam_questions?id=in.(${ids})', data: { category: '$category', }, requestAdaptor: 'return {\r\n ...api,\r\n data: {\r\n category: api.data.category ? api.data.category : null\r\n }\r\n}', }, body: [ { type: 'tree-select', label: '分类', name: 'category', options: [], source: { method: 'get', url: 'rest/exam_question_categories?path=cd.root&order=name', adaptor: 'const recursive = id => {\r\n let nodes = payload.data.items.filter(x => {\r\n return x.parent === id\r\n })\r\n return nodes.map(item => {\r\n return {\r\n ...item,\r\n label: item.name,\r\n value: `${item.id}`,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}\r\nlet tops = payload.data.items.filter(x => x.parent === null)\r\nreturn {\r\n ...payload,\r\n data: tops.map(item => {\r\n return {\r\n label: item.name,\r\n value: item.id,\r\n children: recursive(item.id)\r\n }\r\n })\r\n}', }, mode: 'normal', heightAuto: true, virtualThreshold: false, }, ], }, ], closeOnEsc: true, showCloseButton: true, }, id: 'u:1900a8775c6a', }, { label: '批量删除', actionType: 'ajax', api: { method: 'delete', url: 'rest/exam_questions?id=in.(${ids})', data: null, }, confirmText: '确认删除?', type: 'button', id: 'u:b24dc3837adb', }, ], md: 9, columnsTogglable: false, filter: null, quickSaveItemApi: '', hideQuickSaveBtn: true, id: 'u:d5ccedbd35f2', }, ], title: '', messages: {}, bodyClassName: '', definitions: { options: { type: 'combo', multiple: true, multiLine: true, controls: [ { type: 'group', controls: [ { label: '选项', name: 'key', type: 'text', }, { label: '内容', name: 'label', type: 'text', }, { type: 'switch', option: '开关', name: 'is_fixed', label: '是否固定', }, ], label: false, }, ], }, }, id: 'u:757690c834b6', asideResizor: false, pullRefresh: { disabled: true, }, }; export { schema };