We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
留言功能在社交中占据很重要的作用。这里实现的留言功能,参考微信朋友圈的方式:
用户发送一个TOPIC话题,读者可以在该话题下面进行评论,也可以对该话题下的留言进行评论。但是始终只会展示两层树的评论。
TOPIC
当然,也可以像掘金这样进行嵌套多层树的结构展示。臣妾觉得嵌套得太深~
实际完成的效果如下:
体验站点请戳 jimmyarea.com 。
使用技术
react
ant design
typescript
在上面的截图中,很明显,就是一个表单的设计,外加一个列表的展示。
表单的设计使用了ant design框架自带的form组件:
form
<Form {...layout} form={form} name="basic" onFinish={onFinish} onFinishFailed={onFinishFailed} > <Form.Item label="主题" name="subject" rules={[ { required: true, message: '请输入你的主题' }, { whitespace: true, message: '输入不能为空' }, { min: 6, message: '主题不能小于6个字符' }, { max: 30, message: '主题不能大于30个字符' }, ]} > <Input maxLength={30} placeholder="请输入你的主题(最少6字符,最多30字符)" /> </Form.Item> <Form.Item label="内容" name="content" rules={[ { required: true, message: '请输入你的内容' }, { whitespace: true, message: '输入不能为空' }, { min: 30, message: '内容不能小于30个字符' }, ]} > <Input.TextArea placeholder="请输入你的内容(最少30字符)" autoSize={{ minRows: 6, maxRows: 12, }} showCount maxLength={300} /> </Form.Item> <Form.Item {...tailLayout}> <Button type="primary" htmlType="submit" style={{ width: '100%' }} loading={loading} disabled={loading} > <CloudUploadOutlined /> Submit </Button> </Form.Item> </Form>
这里限制了输入的主题名称的长度为6-30;内容是30-300字符
针对留言的展示,这里使用的是ant design自带的List和Comment组件:
List
Comment
<List loading={loadingMsg} itemLayout="horizontal" pagination={{ size: 'small', total: count, showTotal: () => `共 ${count} 条`, pageSize, current: activePage, onChange: changePage, }} dataSource={list} renderItem={(item: any, index: any) => ( <List.Item actions={[]} key={index}> <List.Item.Meta avatar={ <Avatar style={{ backgroundColor: '#1890ff' }}> {item.userId?.username?.slice(0, 1)?.toUpperCase()} </Avatar> } title={<b>{item.subject}</b>} description={ <> {item.content} {/* 子留言 */} <div style={{ fontSize: '12px', marginTop: '8px', marginBottom: '16px', alignItems: 'center', display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', }} > <span> 用户 {item.userId?.username} 发表于 {moment(item.meta?.createAt).format('YYYY-MM-DD HH:mm:ss')} </span> <span> {item.canDel ? ( <a style={{ color: 'red', fontSize: '12px', marginRight: '12px' }} onClick={() => removeMsg(item)} > <DeleteOutlined /> Delete </a> ) : null} <a style={{ fontSize: '12px', marginRight: '12px' }} onClick={() => replyMsg(item)} > <MessageOutlined /> Reply </a> </span> </div> {/* 回复的内容 */} {item.children && item.children.length ? ( <> {item.children.map((innerItem: any, innerIndex: any) => ( <Comment key={innerIndex} author={<span>{innerItem.subject}</span>} avatar={ <Avatar style={{ backgroundColor: '#1890ff' }}> {innerItem.userId?.username?.slice(0, 1)?.toUpperCase()} </Avatar> } content={<p>{innerItem.content}</p>} datetime={ <Tooltip title={moment(innerItem.meta?.createAt).format( 'YYYY-MM-DD HH:mm:ss', )} > <span>{moment(innerItem.meta?.createAt).fromNow()}</span> </Tooltip> } actions={[ <> {innerItem.canDel ? ( <a style={{ color: 'red', fontSize: '12px', marginRight: '12px', }} onClick={() => removeMsg(innerItem)} > <DeleteOutlined /> Delete </a> ) : null} </>, <a style={{ fontSize: '12px', marginRight: '12px' }} onClick={() => replyMsg(innerItem)} > <MessageOutlined /> Reply </a>, ]} /> ))} </> ) : null} {/* 回复的表单 */} {replyObj._id === item._id || replyObj.pid === item._id ? ( <div style={{ marginTop: '12px' }} ref={replyArea}> <Form form={replyForm} name="reply" onFinish={onFinishReply} onFinishFailed={onFinishFailed} > <Form.Item name="reply" rules={[ { required: true, message: '请输入你的内容' }, { whitespace: true, message: '输入不能为空' }, { min: 2, message: '内容不能小于2个字符' }, ]} > <Input.TextArea placeholder={replyPlaceholder} autoSize={{ minRows: 6, maxRows: 12, }} showCount maxLength={300} /> </Form.Item> <Form.Item> <div style={{ display: 'flex', justifyContent: 'flex-end' }}> <Button style={{ marginRight: '12px' }} onClick={() => cancelReply()} > Dismiss </Button> <Button type="primary" htmlType="submit" loading={innerLoading} disabled={innerLoading} > Submit </Button> </div> </Form.Item> </Form> </div> ) : null} </> } /> </List.Item> )} />
当然,如果是多级地树结构嵌套,你完全可以只是使用Comment组件进行递归调用
列表是对用户发表的主题,留言以及子留言的展示。如果你纵览上面的代码片段,你会发现里面有一个Form表单。
Form
是的,其Form表单就是给留言使用的,其结构仅仅是剔除了主题留言中的subject字段输入框,但是实际传参我还是会使用到。
subject
完整的前端代码可前往jimmyarea 留言(前端)查看。
使用的技术:
mongodb 数据库,这里我使用到了其ODM mongoose
mongoose
koa2 一个Node框架
Node
pm2 进程守卫
apidoc 用来生成接口文档(如果你留意体验站点,右上角有一个"文档"的链接,链接的内容就是生成的文档内容)
这里的搭建就不进行介绍了,可以参考koa2官网配合百度解决~
其实,本质上还是增删改查的操作。
首先,我们对自己要存储的数据结构schema进行相关的定义:
schema
const mongoose = require('mongoose') const Schema = mongoose.Schema // 定义留言字段 let MessageSchema = new Schema({ // 关联字段 -- 用户的id userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, type: Number, // 1是留言,2是回复 subject: String, // 留言主题 content: String, // 留言内容 pid: { // 父id type: String, default: '-1' }, replyTargetId: { // 回复目标记录id, 和父pid有所不同 type: String, default: '-1' }, meta: { createAt: { type: Date, default: Date.now() }, updateAt: { type: Date, default: Date.now() } } }) mongoose.model('Message', MessageSchema)
这里有个注意的点userId字段,这里我直接关联了注册的用户。
userId
完成了字段的设定之后,下面就可以进行增删改查了。
详细的crud代码可以到jimmyarea 留言(后端) 查看。
crud
本篇的重点是,对评论的话题和留言,如何转换成两层的树型结构呢?
这就是涉及到了pid这个字段,也就是父节点的id: 话题的pid为-1,话题下留言的pid为话题的记录值。如下代码:
pid
父节点的id
-1
let count = await Message.count({pid: '-1'}) let data = await Message.find({pid: '-1'}) .skip((current-1) * pageSize) .limit(pageSize) .sort({ 'meta.createAt': -1}) .populate({ path: 'userId', select: 'username _id' // select: 'username -_id' -_id 是排除_id }) .lean(true) // 添加lean变成js的json字符串 const pids = Array.isArray(data) ? data.map(i => i._id) : []; let resReply = [] if(pids.length) { resReply = await Message.find({pid: {$in: pids}}) .sort({ 'meta.createAt': 1}) .populate({ path: 'userId', select: 'username _id' // select: 'username -_id' -_id 是排除_id }) } const list = data.map(item => { const children = JSON.parse(JSON.stringify(resReply.filter(i => i.pid === item._id.toString()))) // 引用问题 const tranformChildren = children.map(innerItem => ({ ...innerItem, canDel: innerItem.userId && innerItem.userId._id.toString() === (user._id&&user._id.toString()) ? 1 : 0 })) return { ...item, children: tranformChildren, canDel: item.userId && item.userId._id.toString() === (user._id&&user._id.toString()) ? 1 : 0 } }) if(list) { ctx.body = { results: list, current: 1, count } return } ctx.body = { code: 10002, msg: '获取留言失败!' }
至此,可以愉快地进行留言~
更多内容可前往 jimmy github
留言的关键代码可前往 jimmy 留言功能
留言的体验地址可前往 jimmyarea.com
The text was updated successfully, but these errors were encountered:
No branches or pull requests
留言功能在社交中占据很重要的作用。这里实现的留言功能,参考微信朋友圈的方式:
实际完成的效果如下:
体验站点请戳 jimmyarea.com 。
前端实现
使用技术
react
ant design
typescript
在上面的截图中,很明显,就是一个表单的设计,外加一个列表的展示。
表单的设计使用了
ant design
框架自带的form
组件:针对留言的展示,这里使用的是
ant design
自带的List
和Comment
组件:列表是对用户发表的主题,留言以及子留言的展示。如果你纵览上面的代码片段,你会发现里面有一个
Form
表单。是的,其
Form
表单就是给留言使用的,其结构仅仅是剔除了主题留言中的subject
字段输入框,但是实际传参我还是会使用到。完整的前端代码可前往jimmyarea 留言(前端)查看。
后端
使用的技术:
mongodb 数据库,这里我使用到了其ODM
mongoose
koa2 一个
Node
框架pm2 进程守卫
apidoc 用来生成接口文档(如果你留意体验站点,右上角有一个"文档"的链接,链接的内容就是生成的文档内容)
这里的搭建就不进行介绍了,可以参考koa2官网配合百度解决~
其实,本质上还是增删改查的操作。
首先,我们对自己要存储的数据结构
schema
进行相关的定义:这里有个注意的点
userId
字段,这里我直接关联了注册的用户。完成了字段的设定之后,下面就可以进行增删改查了。
详细的
crud
代码可以到jimmyarea 留言(后端) 查看。本篇的重点是,对评论的话题和留言,如何转换成两层的树型结构呢?
这就是涉及到了
pid
这个字段,也就是父节点的id
: 话题的pid
为-1
,话题下留言的pid
为话题的记录值。如下代码:至此,可以愉快地进行留言~
后话
更多内容可前往 jimmy github
留言的关键代码可前往 jimmy 留言功能
留言的体验地址可前往 jimmyarea.com
The text was updated successfully, but these errors were encountered: