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? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in 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: