更新记录
1.0.0(2026-05-09)
第一版
平台兼容性
uni-app(4.0)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | × | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 |
|---|---|---|
| √ | √ | √ |
uni-app @用户 编辑器插件说明
1. 插件简介
本插件提供两个通用组件:
pz-at-editor:支持输入@选择用户,并输出结构化消息数据。pz-at-editor-echo:将结构化消息数据反解成文本回显。
组件目录:
components/
├─ pz-at-editor/
│ └─ pz-at-editor.vue
└─ pz-at-editor-echo/
└─ pz-at-editor-echo.vue
页面只需要负责三件事:
- 准备用户列表。
- 接收
pz-at-editor的change数据。 - 发送或展示时使用
messageList。
2. 功能说明
pz-at-editor
- 输入
@后弹出用户选择面板。 - 选择用户后插入不可编辑的
@昵称节点。 - 支持自定义
@用户文本颜色。 - 实时输出纯文本、HTML、被
@的用户 ID 列表、结构化消息数组。
pz-at-editor-echo
- 接收
messageList。 - 将
text和user类型按顺序回显成文本。 user类型会自动显示为@昵称,并使用传入颜色高亮。
3. 平台兼容
pz-at-editor 使用了:
contenteditablerenderjs- DOM API
Selection / Range
因此当前主要适用于:
- H5
- App-Vue
不建议直接用于小程序端。小程序端如需兼容,建议单独实现 textarea + 高亮层 方案。
pz-at-editor-echo 是普通展示组件,通常可用于 H5、App、小程序。
4. 消息数据格式
最终推荐发送给后端的数据是 messageList:
[
{
"type": "text",
"content": "你好 "
},
{
"type": "user",
"content": "小明",
"id": "u1001"
}
]
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
type |
String |
消息类型,支持 text、user |
content |
String |
文本内容;当 type 为 user 时是用户昵称 |
id |
String |
用户 ID,仅 user 类型有 |
5. pz-at-editor 使用方式
引入组件
import MentionEditor from '@/components/pz-at-editor/pz-at-editor.vue'
基础示例
<template>
<mention-editor
:user-list="userList"
:mention-text-color="mentionTextColor"
@change="handleEditorChange"
/>
</template>
<script>
import MentionEditor from '@/components/pz-at-editor/pz-at-editor.vue'
export default {
components: {
MentionEditor
},
data() {
return {
mentionTextColor: '#ff2442',
userList: [
{ userCode: 'u1001', nickName: '小明' },
{ userCode: 'u1002', nickName: '小红' }
],
editorState: {
html: '',
text: '',
userCodeList: [],
messageList: []
}
}
},
methods: {
handleEditorChange(payload) {
this.editorState = payload
}
}
}
</script>
Props
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
userList |
Array |
[] |
用户列表 |
userIdKey |
String |
'userCode' |
用户 ID 字段名 |
userNameKey |
String |
'nickName' |
用户昵称字段名 |
mentionTextColor |
String |
'#ff2442' |
@用户 文本颜色 |
placeholder |
String |
'请输入内容,输入 @ 选择用户' |
输入框占位文案 |
popupTitle |
String |
'选择要 @ 的用户' |
选人弹窗标题 |
用户列表格式
默认字段:
[
{
userCode: 'u1001',
nickName: '小明'
}
]
如果接口返回字段不同,可以用 user-id-key 和 user-name-key 指定:
<mention-editor
:user-list="userList"
user-id-key="id"
user-name-key="name"
@change="handleEditorChange"
/>
对应数据:
[
{
id: 'u1001',
name: '小明'
}
]
事件
pz-at-editor 只有一个对外事件:change。
输入内容变化、选择用户后都会触发:
handleEditorChange(payload) {
console.log(payload)
}
payload 格式:
{
html: '<span data-mention="true">...</span>',
text: '你好 @小明',
userCodeList: ['u1001'],
messageList: [
{ type: 'text', content: '你好 ' },
{ type: 'user', content: '小明', id: 'u1001' }
]
}
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
html |
String |
编辑器内部 HTML |
text |
String |
纯文本内容 |
userCodeList |
String[] |
被 @ 的用户 ID 列表 |
messageList |
Array |
推荐提交给后端的结构化消息数组 |
6. pz-at-editor-echo 使用方式
引入组件
import MessageEcho from '@/components/pz-at-editor-echo/pz-at-editor-echo.vue'
基础示例
<template>
<message-echo
:message-list="messageList"
:mention-text-color="mentionTextColor"
/>
</template>
<script>
import MessageEcho from '@/components/pz-at-editor-echo/pz-at-editor-echo.vue'
export default {
components: {
MessageEcho
},
data() {
return {
mentionTextColor: '#ff2442',
messageList: [
{ type: 'text', content: '你好 ' },
{ type: 'user', content: '小明', id: 'u1001' }
]
}
}
}
</script>
Props
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
messageList |
Array |
[] |
结构化消息数组 |
mentionTextColor |
String |
'#2563eb' |
回显区 @用户 文本颜色 |
7. 推荐接入流程
- 页面层通过接口获取用户列表。
- 把用户列表传给
pz-at-editor。 - 监听
change,保存最新的editorState。 - 点击发送时,取
editorState.messageList提交给后端。 - 需要回显时,把后端返回的
messageList传给pz-at-editor-echo。
推荐以后端协议使用 messageList 为准,html、text、userCodeList 作为页面辅助字段使用。
8. 完整页面示例
如果插件上传时只上传了 components 目录,可以按下面方式在业务页面接入。
示例文件:pages/index/index.vue
<template>
<view class="page">
<view class="hero-card">
<view class="hero-header">
<text class="hero-title">@ 富文本编辑器</text>
<text class="hero-desc">评论输入框与回显区域已拆成两个通用组件,首页仅负责传参和接收结果。</text>
</view>
<!-- 页面说明区域 -->
<view class="tips">
<text class="tips-label">模拟用户:</text>
<view class="tag-list">
<text v-for="user in userList" :key="user.userCode" class="tag">@{{ user.nickName }}</text>
</view>
</view>
<!-- 模拟用户数据展示 -->
<view class="section-card">
<view class="section-head">
<text class="section-title">评论内容</text>
<text class="section-tip">试试输入:大家好 @</text>
</view>
<mention-editor
:user-list="userList"
:mention-text-color="mentionTextColor"
@change="handleEditorChange"
/>
<!-- 输入区:只负责承载通用输入组件和发送按钮 -->
<view class="action-row">
<button class="action-btn primary" @tap="handleSend">发送内容</button>
</view>
</view>
<!-- 调试信息展示区:便于观察输入组件实时输出的数据 -->
<view class="section-card">
<view class="state-row">
<text class="state-label">纯文本:</text>
<text class="state-value">{{ editorState.text || '暂无内容' }}</text>
</view>
<view class="state-row">
<text class="state-label">被 @ 用户:</text>
<text class="state-value">{{ editorState.userCodeList.length ? editorState.userCodeList.join('、') : '暂无' }}</text>
</view>
<view class="state-row">
<text class="state-label">消息数组:</text>
<text class="state-value state-json">{{ JSON.stringify(editorState.messageList || [], null, 2) }}</text>
</view>
</view>
<!-- 文本回显区:外层负责标题,组件只负责核心文本内容 -->
<view class="section-card">
<text class="section-title block-title">JSON数据文本回显</text>
<message-echo
:message-list="echoMessageList"
:mention-text-color="mentionTextColor"
/>
</view>
<!-- 最终发送结果 JSON 展示 -->
<view class="section-card">
<text class="section-title block-title">发送结果 JSON</text>
<text class="result-json">{{ formattedResult }}</text>
</view>
</view>
</view>
</template>
<script>
import MentionEditor from '@/components/pz-at-editor/pz-at-editor.vue'
import MessageEcho from '@/components/pz-at-editor-echo/pz-at-editor-echo.vue'
const MENTION_TEXT_COLOR = '#ff2442'
// 模拟用户数据,实际接入时可替换为接口返回
const mockUsers = [{
userCode: 'u1001',
nickName: '小明'
}, {
userCode: 'u1002',
nickName: '小红'
}, {
userCode: 'u1003',
nickName: '阿杰'
}, {
userCode: 'u1004',
nickName: '设计喵'
}, {
userCode: 'u1005',
nickName: '前端星'
}]
export default {
components: {
MentionEditor,
MessageEcho
},
data() {
return {
mentionTextColor: MENTION_TEXT_COLOR,
// 页面维护的用户列表
userList: mockUsers,
editorState: {
html: '',
text: '',
userCodeList: [],
messageList: []
},
sendResult: []
}
},
computed: {
// 有发送结果时优先展示发送结果,否则展示实时编辑结果
echoMessageList() {
return this.sendResult.length ? this.sendResult : this.editorState.messageList
},
// 最终 JSON 字符串展示
formattedResult() {
return JSON.stringify(this.echoMessageList || [], null, 2)
}
},
methods: {
// 接收输入组件实时变化
handleEditorChange(payload) {
this.editorState = Object.assign({
html: '',
text: '',
userCodeList: [],
messageList: []
}, payload || {})
},
// 模拟发送:将当前消息数组复制为最终发送结果
handleSend() {
this.sendResult = JSON.parse(JSON.stringify(this.editorState.messageList || []))
uni.showToast({
title: '已生成发送数据',
icon: 'none'
})
}
}
}
</script>
<style>
page {
background: #f4f7fb;
}
.page {
min-height: 100vh;
padding: 32rpx;
box-sizing: border-box;
}
.hero-card {
padding: 32rpx;
background: #ffffff;
border-radius: 24rpx;
box-shadow: 0 18rpx 50rpx rgba(255, 36, 66, 0.10);
}
.hero-header {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.hero-title {
font-size: 40rpx;
font-weight: 700;
color: #2b2b2b;
}
.hero-desc {
font-size: 28rpx;
line-height: 1.6;
color: #7a7a7a;
}
.tips {
margin-top: 28rpx;
padding: 24rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #fff1f3 0%, #fff7f7 100%);
}
.tips-label {
font-size: 26rpx;
color: #8a3040;
}
.section-card {
margin-top: 28rpx;
padding: 28rpx;
background: #ffffff;
border-radius: 24rpx;
box-shadow: 0 18rpx 50rpx rgba(255, 36, 66, 0.10);
}
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #2b2b2b;
}
.block-title {
display: block;
margin-bottom: 16rpx;
}
.section-tip {
font-size: 24rpx;
color: #b06b76;
}
.action-row {
display: flex;
gap: 20rpx;
margin-top: 24rpx;
}
.action-btn {
flex: 1;
height: 84rpx;
line-height: 84rpx;
border-radius: 18rpx;
font-size: 28rpx;
}
.action-btn::after {
border: 0;
}
.action-btn.primary {
background: linear-gradient(135deg, #ff8a9b 0%, #ff6f83 100%);
color: #ffffff;
}
.tag-list {
display: flex;
flex-wrap: wrap;
margin-top: 16rpx;
gap: 16rpx;
}
.tag {
padding: 10rpx 20rpx;
border-radius: 999rpx;
font-size: 24rpx;
color: #ff2442;
background: rgba(255, 36, 66, 0.10);
}
.state-row {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
}
.state-row:last-child {
margin-bottom: 0;
}
.state-label {
width: 160rpx;
font-size: 26rpx;
color: #8f5b64;
}
.state-value {
flex: 1;
font-size: 26rpx;
line-height: 1.6;
color: #2b2b2b;
word-break: break-all;
}
.state-json {
white-space: pre-wrap;
}
.result-json {
display: block;
padding: 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
line-height: 1.7;
color: #7f3340;
background: linear-gradient(135deg, #fff1f3 0%, #ffe4e8 100%);
white-space: pre-wrap;
word-break: break-all;
}
</style>

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 0
赞赏 0
下载 11838582
赞赏 1911
赞赏
京公网安备:11010802035340号