上节我们实现了试卷编辑器:
但没有和对应 id 的试卷关联。
这节来做下回显和关联。
回显就是根据 id 查询对应的试卷内容。
我们之前没开发这个接口,加一下:
javascript
@Get('find/:id')
@RequireLogin()
async find(@Param('id') id: string) {
return this.examService.find(+id, userId);
}
在 ExamService 加一下实现:
javascript
async find(id: number) {
return this.prismaService.exam.findUnique({
where: {
id
}
})
}
试一下:
在 interfaces/index.tsx 加一下接口:
javascript
export async function examFind(id: number) {
return await examServiceInstance.get('/exam/find/' + id );
}
调用下:
javascript
async function query() {
if(!id) {
return;
}
try {
const res = await examFind(+id);
if(res.status === 201 || res.status === 200) {
try{
setJson(JSON.parse(res.data.content))
} catch(e) {}
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
useEffect(() => {
query();
}, [])
当 JSON.parse 失败 try catch 就行,不用处理。
调用 save 接口更新下内容:
javascript
{
"id": 2,
"content": "[{\"id\":1724715396822,\"type\":\"checkbox\",\"question\":\"最高的山?\",\"options\":[\"选项1\",\"选项2\"],\"score\":5,\"answer\":\"选项1\",\"answerAnalyse\":\"答案解析\"}]"
}
然后刷新页面:
这样,回显就完成了。
然后再做下保存:
在 interfaces/index.tsx 加一下这个接口:
javascript
export async function examSave(data: { id: number, content: string}) {
return await examServiceInstance.post('/exam/save', data);
}
然后在页面加一个保存按钮,点击的时候调用 save:
javascript
<div>
<Space>
<Button type="default">预览</Button>
<Button type="primary" onClick={saveExam}>保存</Button>
</Space>
</div>
javascript
async function saveExam() {
if(!id) {
return;
}
try {
const res = await examSave({
id: +id,
content: JSON.stringify(json)
});
if(res.status === 201 || res.status === 200) {
message.success('保存成功')
}
} catch(e: any){
message.error(e.response?.data?.message || '系统繁忙,请稍后再试');
}
}
测试下:
保存成功。
最后我们做下预览,这个也是递归渲染 json,只不过内容不同:
创建 Preview 组件:
pages/Edit/PreviewModal.tsx
javascript
import { Checkbox, Form, Input, Modal, Radio, message } from "antd";
import { Question } from ".";
interface PreviewModalProps {
isOpen: boolean;
handleClose: Function;
json: Question[]
}
export function PreviewModal(props: PreviewModalProps) {
function renderPreviewComponents(arr: Array<Question>) {
return arr.map(item => {
let formComponent;
if(item.type === 'radio') {
formComponent = <Radio.Group>
{
item.options?.map(option => <Radio value={option}>{option}</Radio>)
}
</Radio.Group>
} else if(item.type === 'checkbox') {
formComponent = <Checkbox.Group options={item.options} />
} else if(item.type === 'input'){
formComponent = <Input/>
}
return <div className="component-item" key={item.id}>
<p className="question">{item.question}</p>
<div className="options">
{formComponent}
</div>
</div>
})
}
return <Modal
title="预览"
className="preview"
open={props.isOpen}
onOk={() => props.handleClose()}
onCancel={() => props.handleClose()}
okText={'确认'}
cancelText={'取消'}
>
{renderPreviewComponents(props.json)}
</Modal>
}
递归渲染传过来的 json,在 Modal 里显示。
调用下:
javascript
const [isPreviewModalOpen, setPreviewModalOpen] = useState(false);
javascript
<PreviewModal isOpen={isPreviewModalOpen} json={json} handleClose={() =>{
setPreviewModalOpen(false)
}}/>
javascript
<Button type="default" onClick={() => {
setPreviewModalOpen(true)
}}>预览</Button>
然后写下样式:
scss
.preview {
.component-item {
margin: 20px;
line-height: 40px;
font-size: 20px;
}
}
看下效果:
没啥问题。
这样,试卷编辑器的回显、预览、保存就完成了。
案例代码在小册仓库:
总结
这节我们实现了试卷的回显、预览、保存。
回显就是查询 id 对应的 content,parse 为 json 渲染。
保存就是把 content 保存到数据库。
预览则是递归渲染 json,但和编辑时渲染的内容不同。
至此,试卷编辑器就完成了。