Skip to content

上节我们实现了试卷编辑器:

2024-08-26 23.08.21.gif

但没有和对应 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\":\"答案解析\"}]"
}

然后刷新页面:

2024-08-27 07.46.37.gif

这样,回显就完成了。

然后再做下保存:

在 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 || '系统繁忙,请稍后再试');
    }
}

测试下:

2024-08-27 07.56.35.gif

保存成功。

最后我们做下预览,这个也是递归渲染 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;
    }
}

看下效果:

2024-08-27 08.13.16.gif

没啥问题。

这样,试卷编辑器的回显、预览、保存就完成了。

案例代码在小册仓库:

前端代码

后端代码

总结

这节我们实现了试卷的回显、预览、保存。

回显就是查询 id 对应的 content,parse 为 json 渲染。

保存就是把 content 保存到数据库。

预览则是递归渲染 json,但和编辑时渲染的内容不同。

至此,试卷编辑器就完成了。