Messages play an important role in social interaction. The message function realized here refers to the way of wechat circle of friends:
Users send a TOPIC, and readers can comment on the TOPIC or the message under the TOPIC. But only two-tier tree comments are always shown.
Of course, it is also possible to show the structure of nested multi-level trees like digging gold. I think the nesting is too deep
The actual results are as follows:
Experience the site at jimmyarea.com.
The front-end implementation
The use of the technical
-
react
-
ant design
-
typescript
In the screenshot above, it is clear that there is a form design, plus a list presentation.
The form is designed using the ant Design framework’s form component:
<Form {... layout} form={form} name="basic"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Form.Item
label="Theme"
name="subject"
rules={[
{ required: true.message:'Please enter your topic'}, {whitespace: true.message:'Input cannot be null'}, {min: 6.message:'The theme cannot be less than6'}, {max: 30.message:'The theme cannot be greater than30Characters'},]} >
<Input maxLength={30} placeholder="Please enter your topic (minimum 6 characters, maximum 30 characters)" />
</Form.Item>
<Form.Item
label="Content"
name="content"
rules={[
{ required: true.message:'Please enter your content'}, {whitespace: true.message:'Input cannot be null'}, {min: 30.message:'The content cannot be less than30Characters'},]} >
<Input.TextArea
placeholder="Please enter your content (min. 30 characters)"
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>
Copy the code
This limits the length of the entered topic name to 6-30; The content is 30-300 characters
For the display of comments, the List and Comment components of Ant Design are used here:
<List
loading={loadingMsg}
itemLayout="horizontal"
pagination={{
size: 'small'.total: count,
showTotal: () = > ` altogether${count}Article `,
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} {/* Submessage */}<div
style={{
fontSize: '12px',
marginTop: '8px',
marginBottom: '16px',
alignItems: 'center',
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between'}} >
<span>The user {item.userId? .username} Published in {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}
{/* The reply form */}
{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:'Please enter your content'}, {whitespace: true.message:'Input cannot be null'}, {min: 2.message:'The content cannot be less than2Characters'},]} >
<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>
)}
/>
Copy the code
Of course, if there are multiple levels of nested tree structures, you can just use the Comment component to make recursive calls
A list is a display of topics, messages, and sub-messages posted by the user. If you look through the code snippet above, you’ll see that there is a Form in it.
Yes, the Form is for messages, and its structure is just to remove the subject field input field from the subject message, but I will still use it in the actual parameter passing.
The complete front-end code can be viewed at the JimmyArea message (front end).
The back-end
Techniques used:
-
Mongodb database, here I use its ODM Mongoose
-
Koa2 A Node framework
-
Pm2 process guard
-
Apidoc is used to generate interface documentation (if you notice the experience site, there is a link to “Documentation” in the upper right corner, which is the content of the generated documentation)
The construction here will not be introduced, you can refer to koA2 official website with Baidu to solve ~
In fact, essentially or add delete change check operation.
First, we define the data structure schema we want to store:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
// Define the message field
let MessageSchema = new Schema({
// Associated field -- user ID
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
type: Number.// 1 is a message, 2 is a reply
subject: String.// Message subject
content: String.// Message content
pid: { / / the parent id
type: String.default: '1'
},
replyTargetId: { Return the target record ID, which is different from the parent PID
type: String.default: '1'
},
meta: {
createAt: {
type: Date.default: Date.now()
},
updateAt: {
type: Date.default: Date.now()
}
}
})
mongoose.model('Message', MessageSchema)
Copy the code
One interesting point here is the userId field, where I directly associate the registered user.
Now that you’ve set the fields, you can add, delete, change and check them.
Detailed CRUD code can be viewed at the JimmyArea message (backend).
The focus of this article is how to convert the topic and comments into a two-tier tree structure.
This is related to the PID field, that is, the ID of the parent node: the PID of the topic is -1, and the PID of the message under the topic is the record value of the topic. The following code:
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 is to exclude the _id
})
.lean(true) // Add lean to JS JSON string
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 is to exclude the _id})}const list = data.map(item= > {
const children = JSON.parse(JSON.stringify(resReply.filter(i= > i.pid === item._id.toString()))) // Reference problems
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: 'Failed to get message! '
}
Copy the code
At this point, you can leave a message with pleasure
The latter
-
More content can be found at Jimmy Github
-
Key codes for messages can be found in the Jimmy message function
-
To experience the message, go to jimmyarea.com