当前位置:   article > 正文

小程序富文本编辑器组件

小程序富文本编辑器组件

 效果图只是功能的一部分

话不多说,先上代码

html部分:

  1. <page-container show="{{showPage}}" z-index="9999999" round="{{round}}" overlay="{{overlay}}" duration="{{duration}}" position="{{position}}" close-on-slide-down="{{false}}" bindbeforeenter="onBeforeEnter" bindenter="onEnter" bindafterenter="onAfterEnter" bindbeforeleave="onBeforeLeave" bindleave="onLeave" bindafterleave="onAfterLeave" bindclickoverlay="onClickOverlay" custom-style="{{customStyle}}" overlay-style="{{overlayStyle}}">
  2. <view class="detail-page">
  3. <mp-navigation-bar back="{{false}}" title="文本编辑" ext-class="navigation" background="{{background}}">
  4. <view slot="left">
  5. <view bindtap="exit" style="width: 100rpx;height:60rpx;display: flex;justify-content: left;align-items: center;">
  6. <van-icon name="arrow-left'" color="#333" size="40rpx" />
  7. </view>
  8. </view>
  9. </mp-navigation-bar>
  10. <view class="editor_toolbox">
  11. <i class="iconfont icon-undo" data-method="undo" bindtap="edit" />
  12. <i class="iconfont icon-redo" data-method="redo" bindtap="edit" />
  13. <i class="iconfont icon-img" data-method="insertImg" bindtap="edit" />
  14. <i class="iconfont icon-video" data-method="insertVideo" bindtap="edit" />
  15. <i class="iconfont icon-link" data-method="insertLink" bindtap="edit" />
  16. <i class="iconfont icon-text" data-method="insertText" bindtap="edit" />
  17. <!-- <i class="iconfont icon-text" data-method="insertTable" bindtap="edit" /> -->
  18. <i class="iconfont icon-clear" bindtap="clear" />
  19. <i class="iconfont icon-save" bindtap="save" />
  20. </view>
  21. <view>
  22. <mp-html id="article" container-style="padding:20px" tag-style="{{tagStyle}}" content="{{html}}" domain="https://mp-html.oss-cn-hangzhou.aliyuncs.com" editable="{{editable}}" bindremove="remove"></mp-html>
  23. </view>
  24. <block wx:if="{{modal}}">
  25. <view class="mask" />
  26. <view class="modal">
  27. <view class="modal_title">{{modal.title}}</view>
  28. <input class="modal_input" value="{{modal.value}}" maxlength="-1" auto-focus bindinput="modalInput" />
  29. <view class="modal_foot">
  30. <view class="modal_button" bindtap="modalCancel">取消</view>
  31. <view class="modal_button" style="color:#576b95;border-left:1px solid rgba(0,0,0,.1)" bindtap="modalConfirm">确定</view>
  32. </view>
  33. </view>
  34. </block>
  35. </view>
  36. <van-dialog use-slot title="插入表格" show="{{ show }}" show-cancel-button bind:close="onClose" bind:confirm="getUserInfo">
  37. <view style="display: flex;justify-content: center;flex-direction: column;align-items: center;">
  38. <view class="between" style="margin-top: 24rpx;">
  39. 行:{{ row }}
  40. <van-stepper model:value="{{ row }}" mark:type="row" bind:change="onChange" />
  41. </view>
  42. <view class="between" style="margin-top: 24rpx;">
  43. 列:{{ column }}
  44. <van-stepper model:value="{{ column }}" mark:type="column" bind:change="onChange" />
  45. </view>
  46. </view>
  47. </van-dialog>
  48. </page-container>

css部分

  1. .editor_toolbox {
  2. background-color: #ededed;
  3. display: flex;
  4. padding: 5px;
  5. box-sizing: border-box;
  6. line-height: 1.6;
  7. }
  8. .detail-page {
  9. width: 100%;
  10. height: 100%;
  11. position: fixed;
  12. z-index: 999999999999;
  13. }
  14. @font-face {
  15. font-family: "iconfont";
  16. src: url("data:application/x-font-woff2;charset=utf-8;base64,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")
  17. format("woff2");
  18. }
  19. .iconfont {
  20. flex: 1;
  21. text-align: center;
  22. font-family: "iconfont" !important;
  23. font-size: 22px;
  24. font-style: normal;
  25. -webkit-font-smoothing: antialiased;
  26. -moz-osx-font-smoothing: grayscale;
  27. }
  28. .icon-undo:before {
  29. content: "\e607";
  30. }
  31. .icon-redo:before {
  32. content: "\e606";
  33. }
  34. .icon-img:before {
  35. content: "\e6e2";
  36. }
  37. .icon-video:before {
  38. content: "\e798";
  39. }
  40. .icon-link:before {
  41. content: "\e60d";
  42. }
  43. .icon-text:before {
  44. content: "\e6ce";
  45. }
  46. .icon-clear:before {
  47. content: "\e637";
  48. }
  49. .icon-save:before {
  50. content: "\e501";
  51. }
  52. /* 模态框 */
  53. .modal {
  54. position: fixed;
  55. top: 50%;
  56. left: 16px;
  57. right: 16px;
  58. background-color: #fff;
  59. border-radius: 12px;
  60. transform: translateY(-50%);
  61. }
  62. .modal_title {
  63. padding: 32px 24px 16px;
  64. font-size: 17px;
  65. font-weight: 700;
  66. text-align: center;
  67. }
  68. .modal_input {
  69. display: block;
  70. padding: 5px;
  71. margin: 0 24px 32px 24px;
  72. font-size: 14px;
  73. border: 1px solid #dfe2e5;
  74. }
  75. .modal_foot {
  76. display: flex;
  77. line-height: 56px;
  78. font-weight: 700;
  79. border-top: 1px solid rgba(0, 0, 0, 0.1);
  80. }
  81. .modal_button {
  82. flex: 1;
  83. text-align: center;
  84. }
  85. /* 蒙版 */
  86. .mask {
  87. position: fixed;
  88. top: 0;
  89. right: 0;
  90. bottom: 0;
  91. left: 0;
  92. background-color: black;
  93. opacity: 0.5;
  94. }

js 部分

  1. const content = require('./content')
  2. const Shop = require("../../libs/api/shop");
  3. // 上传图片方法
  4. function upload(src, type) {
  5. return new Promise((resolve, reject) => {
  6. console.log('上传', type === 'img' ? '图片' : '视频', ':', src)
  7. // 实际使用时,上传到服务器
  8. wx.uploadFile({
  9. url: 'https://zlhb.wifimsl.cn/group/upload/image', // 接口地址
  10. filePath: src,
  11. name: 'image',
  12. success(res) {
  13. const { data } = JSON.parse(res.data)
  14. resolve(data) // 返回线上地址
  15. },
  16. fail: reject
  17. })
  18. })
  19. }
  20. // 删除图片方法
  21. function remove(src) {
  22. console.log('删除图片:', src)
  23. // 实际使用时,删除线上资源
  24. }
  25. Component({
  26. /**
  27. * 组件的属性列表
  28. */
  29. properties: {
  30. title: String,
  31. showPage: {
  32. type: Boolean,
  33. value: false,
  34. },
  35. html: {
  36. type: String,
  37. value: "",
  38. },
  39. naigation: {
  40. type: String,
  41. value: "文本编辑",
  42. },
  43. },
  44. data: {
  45. duration: 300,
  46. position: 'right',
  47. round: false,
  48. overlay: true,
  49. customStyle: '',
  50. overlayStyle: '',
  51. show: false,
  52. tagStyle: { img: 'width:100% !important;' },
  53. html: '',
  54. content,
  55. editable: true,
  56. column: 1,
  57. row: 1,
  58. },
  59. ready() {
  60. this.ctx = this.selectComponent('#article')
  61. /**
  62. * @description 设置获取链接的方法
  63. * @param {String} type 链接的类型(img/video/audio/link)
  64. * @param {String} value 修改链接时,这里会传入旧值
  65. * @returns {Promise} 返回线上地址(2.2.0 版本起设置了 domain 属性时,可以缺省主域名)
  66. * type 为 audio/video 时,可以返回一个源地址数组
  67. * 2.1.3 版本起 type 为 audio 时,可以返回一个 object,包含 src、name、author、poster 等字段
  68. * 2.2.0 版本起 type 为 img 时,可以返回一个源地址数组,表示插入多张图片(修改链接时仅限一张)
  69. */
  70. this.ctx.getSrc = (type, value) => {
  71. return new Promise((resolve, reject) => {
  72. if (type === 'img' || type === 'video') {
  73. wx.showActionSheet({
  74. itemList: ['本地选取', '远程链接'],
  75. success: res => {
  76. if (res.tapIndex == 0) {
  77. // 本地选取
  78. if (type === 'img') {
  79. wx.chooseImage({
  80. count: value === undefined ? 9 : 1, // 2.2.0 版本起插入图片时支持多张(修改图片链接时仅限一张)
  81. success: res => {
  82. if (res.tempFilePaths.length == 1 && wx.editImage) {
  83. // 单张图片时进行编辑
  84. wx.editImage({
  85. src: res.tempFilePaths[0],
  86. complete: res2 => {
  87. wx.showLoading({
  88. title: '上传中'
  89. })
  90. upload(res2.tempFilePath || res.tempFilePaths[0], type).then(res => {
  91. wx.hideLoading()
  92. resolve(res)
  93. })
  94. }
  95. })
  96. } else {
  97. // 否则批量上传
  98. wx.showLoading({
  99. title: '上传中'
  100. });
  101. (async () => {
  102. const arr = []
  103. for (let item of res.tempFilePaths) {
  104. // 依次上传
  105. const src = await upload(item, type)
  106. arr.push(src)
  107. }
  108. return arr
  109. })().then(res => {
  110. wx.hideLoading()
  111. resolve(res)
  112. })
  113. }
  114. },
  115. fail: reject
  116. })
  117. } else {
  118. wx.chooseVideo({
  119. success: res => {
  120. wx.showLoading({
  121. title: '上传中'
  122. })
  123. upload(res.tempFilePath, type).then(res => {
  124. wx.hideLoading()
  125. resolve(res)
  126. })
  127. },
  128. fail: reject
  129. })
  130. }
  131. } else {
  132. // 远程链接
  133. this.callback = {
  134. resolve,
  135. reject
  136. }
  137. this.setData({
  138. modal: {
  139. title: (type === 'img' ? '图片' : '视频') + '链接',
  140. value
  141. }
  142. })
  143. }
  144. }
  145. })
  146. } else {
  147. this.callback = {
  148. resolve,
  149. reject
  150. }
  151. let title
  152. if (type === 'audio') {
  153. title = '音频链接'
  154. } else if (type === 'link') {
  155. title = '链接地址'
  156. }
  157. this.setData({
  158. modal: {
  159. title,
  160. value
  161. }
  162. })
  163. }
  164. })
  165. }
  166. },
  167. /**
  168. * 组件的方法列表
  169. */
  170. methods: {
  171. getUserInfo(event) {
  172. console.log(event);
  173. const text = 'insertTable'
  174. const { row, column } = this.data
  175. this.ctx[text](row, column)
  176. },
  177. onChange(e) {
  178. const { type } = e.mark
  179. this.setData({
  180. [type]: e.detail
  181. })
  182. },
  183. onClose() {
  184. this.setData({ show: false });
  185. },
  186. // 删除图片/视频/音频标签事件
  187. remove(e) {
  188. // 删除线上资源
  189. remove(e.detail.src)
  190. },
  191. // 处理模态框
  192. modalInput(e) {
  193. this.value = e.detail.value
  194. },
  195. modalConfirm() {
  196. this.callback.resolve(this.value || this.data.modal.value || '')
  197. this.setData({
  198. modal: null
  199. })
  200. },
  201. modalCancel() {
  202. this.callback.reject()
  203. this.setData({
  204. modal: null
  205. })
  206. },
  207. // 调用编辑器接口
  208. edit(e) {
  209. const text = e.currentTarget.dataset.method
  210. if (text == 'insertTable') {
  211. this.setData({ show: true });
  212. // wx.showModal({
  213. // title: '',
  214. // content: `<view>测试</ view>`,
  215. // complete: (res) => {
  216. // if (res.cancel) {
  217. // }
  218. // if (res.confirm) {
  219. // this.ctx[text]()
  220. // }
  221. // }
  222. // })
  223. } else {
  224. this.ctx[text]()
  225. }
  226. console.log(this.ctx);
  227. },
  228. // 清空编辑器内容
  229. clear() {
  230. wx.showModal({
  231. title: '确认',
  232. content: '确定清空内容吗?',
  233. success: res => {
  234. if (res.confirm)
  235. this.ctx.clear()
  236. }
  237. })
  238. },
  239. // 保存编辑器内容
  240. save() {
  241. const that = this
  242. // 避免无法获取到正在编辑的文本内容
  243. setTimeout(() => {
  244. var content = this.ctx.getContent()
  245. wx.showModal({
  246. title: '保存',
  247. content,
  248. confirmText: '完成',
  249. success: res => {
  250. if (res.confirm) {
  251. // 实际使用时,这里需要上传到服务器
  252. this.setData({ html: content, showPage: false })
  253. this.triggerEvent('getHtml', this.ctx.getContent())
  254. // that.update()
  255. // 复制到剪贴板
  256. // wx.setClipboardData({
  257. // data: content,
  258. // })
  259. // 结束编辑
  260. // this.setData({
  261. // editable: false
  262. // })
  263. }
  264. }
  265. })
  266. }, 50)
  267. },
  268. exit() {
  269. wx.showModal({
  270. title: '提示',
  271. content: '离开前请保存编辑内容!',
  272. complete: (res) => {
  273. if (res.cancel) {
  274. }
  275. if (res.confirm) {
  276. this.setData({ showPage: false })
  277. }
  278. }
  279. })
  280. return
  281. wx.enableAlertBeforeUnload({ //开启页面退出时的对话框
  282. message: "离开前请保存编辑内容!",
  283. success: function (res) {
  284. // console.log("成功:", res);
  285. return true
  286. },
  287. fail: function (err) {
  288. // console.log("失败:", err);
  289. return false
  290. },
  291. });
  292. // wx.navigateBack()
  293. },
  294. back() {
  295. },
  296. goTo(e) {
  297. wx.navigateTo({ url: `../shareElement/index` })
  298. },
  299. onBeforeEnter(res) {
  300. },
  301. onEnter(res) {
  302. },
  303. onAfterEnter(res) {
  304. },
  305. onBeforeLeave(res) {
  306. },
  307. onLeave(res) {
  308. },
  309. onAfterLeave(res) {
  310. },
  311. onClickOverlay(res) {
  312. }
  313. },
  314. })

json部分

  1. {
  2. "usingComponents": {
  3. "mp-html": "/components/mp-html/index",
  4. "van-stepper": "/components/vant/stepper/index",
  5. "van-dialog": "/components/vant/dialog/index",
  6. "van-nav-bar": "/components/vant/nav-bar/index"
  7. },
  8. "navigationBarTitleText": "文本编辑",
  9. "navigationBarBackgroundColor": "#ffffff"
  10. }

以上是组件的所有代码部分,下边是使用

<editor-jj showPage="{{showPage}}" html="{{html}}" bind:getHtml="getHtml"></editor-jj>
  1. getHtml(e) {
  2. console.log('测试', e.detail);
  3. // 这里是编辑后返回的结果
  4. //参数介绍
  5. // showPage 是组件的显示隐藏
  6. // html 是组件的初始化内容,并不是双向绑定的,仅在初始化的时候使用
  7. },

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/974710
推荐阅读
相关标签
  

闽ICP备14008679号