效果图只是功能的一部分
话不多说,先上代码
html部分:
<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}}">
<view class="detail-page">
<mp-navigation-bar back="{{false}}" title="文本编辑" ext-class="navigation" background="{{background}}">
<view slot="left">
<view bindtap="exit" style="width: 100rpx;height:60rpx;display: flex;justify-content: left;align-items: center;">
<van-icon name="arrow-left'" color="#333" size="40rpx" />
</view>
</view>
</mp-navigation-bar>
<view class="editor_toolbox">
<i class="iconfont icon-undo" data-method="undo" bindtap="edit" />
<i class="iconfont icon-redo" data-method="redo" bindtap="edit" />
<i class="iconfont icon-img" data-method="insertImg" bindtap="edit" />
<i class="iconfont icon-video" data-method="insertVideo" bindtap="edit" />
<i class="iconfont icon-link" data-method="insertLink" bindtap="edit" />
<i class="iconfont icon-text" data-method="insertText" bindtap="edit" />
<!-- <i class="iconfont icon-text" data-method="insertTable" bindtap="edit" /> -->
<i class="iconfont icon-clear" bindtap="clear" />
<i class="iconfont icon-save" bindtap="save" />
</view>
<view>
<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>
</view>
<block wx:if="{{modal}}">
<view class="mask" />
<view class="modal">
<view class="modal_title">{{modal.title}}</view>
<input class="modal_input" value="{{modal.value}}" maxlength="-1" auto-focus bindinput="modalInput" />
<view class="modal_foot">
<view class="modal_button" bindtap="modalCancel">取消</view>
<view class="modal_button" style="color:#576b95;border-left:1px solid rgba(0,0,0,.1)" bindtap="modalConfirm">确定</view>
</view>
</view>
</block>
</view>
<van-dialog use-slot title="插入表格" show="{{ show }}" show-cancel-button bind:close="onClose" bind:confirm="getUserInfo">
<view style="display: flex;justify-content: center;flex-direction: column;align-items: center;">
<view class="between" style="margin-top: 24rpx;">
行:{{ row }}
<van-stepper model:value="{{ row }}" mark:type="row" bind:change="onChange" />
</view>
<view class="between" style="margin-top: 24rpx;">
列:{{ column }}
<van-stepper model:value="{{ column }}" mark:type="column" bind:change="onChange" />
</view>
</view>
</van-dialog>
</page-container>
css部分
.editor_toolbox {
background-color: #ededed;
display: flex;
padding: 5px;
box-sizing: border-box;
line-height: 1.6;
}
.detail-page {
width: 100%;
height: 100%;
position: fixed;
z-index: 999999999999;
}
@font-face {
font-family: "iconfont";
src: url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAeYAAsAAAAADlAAAAdLAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCEAgqOYItrATYCJAMkCxQABCAFhG0HcBv/CzOjdoNyEiD7nwnxpstuRCLrGmPTaffv/hnWZJHUNtWZeOD/3t03mks73vmC/3jA8SRom0aatimgfv7d9M8lBEJCIHRBKua0E7EEOqedMBEJm8N7js0MWpgq9PlMkGcTsS8NgH3X/AEbFaFGZNFvH+xR2uYofWlu9NO9ypmTgvNlW8CitMbdT7rHpgAXaKxBOfzWBdQCFgyrMV3kKtMLUFGd7GEC0JSkACq79OKAIMbDAW2W48z1QESSiQsUARGRZFyYAZ0AR8Q1ogsAjs7vhy+UBQIgwhPwR83HdB4F7R6gR25M+CrAUEwAobtM6LuBBBRA1qlLZvYO80KFg8isxmImAH1xHwzc0UPpQ/ph9cNLD+8+cn/9mnAgGQwUGkVj0B1kUhL4EP94IgQIVeCoDc62pg7uyLCRwUMCauARlsQ0ElWAq5EoAV9CogB8FzYieLq7qQM0rM5DYAriAik/Li6hNpHBj2knBBuokIJWymUizhppEknSNKNTIHnPfnR5WRIjFnmB0MgyTh9CFr9W5WgCNNfq00hdfi0KEVVL7I0kQvMYjy8OUbDAQIFwzDiGGzMKX+/PVu1sih2FbLk8PneQ0e84zT9a9GckbXu06x/h1C0hw45Twmlb0BLgUtCw7qqMfsqCGhV6bZl8tzTOhwXIwI1ZQCJLIIvccUoHE88h4XC2eGu4M/1XNB3fedRypMHlpyhbEwA5zn0qQ+4JIFxpmvQRIqHR6kOYxT8yRF6DufwjDl1JEG+8Wqk8ej0Z33StarLSdvb0fhAL+06dIRXWc4EDCLccDJ7nRTRGNUgi5S3sUk8hG4BAIuuqdJzCcXaWtWpZK0O7I0Mqs9SeBk6HJs+pMZ3cqWSo18Up8nh2vcql1drvYnRZfFaWawwlbAjnNuUeD0fz47f2GNsa/fF7o5EiX96JqiMhfWMOZ3c32aoWarUuu1GgZb3ZijxTszPBoebYYVU51WtjNSTXad3ekZzB2YUmmynf7Frv87CjOrjBApsDIHqPD//DhUKGSMSAEKRwBgqH+XDUGI0YIlaOKiYk2EnSqUEWF7XAageI3OVCf/PhsDEaNSKnhrSzKBTiQlVdDbJsPEl0ONSUS4ssTnLO4dOJ8O/y7lwLIsG9w6MxwIZTFYojgSR8c7B6ksJqZ8UOB6W0ubb0SifPuQb42mUpNFyJuQI0OLvELTPRSN5ODs3CBUKIBoDa1xrz1K/SNy3fVPVDMKK2XqdM6a9QLFycnZWRVUJ89QUvJOjZoAOfr7zKhGDrb79CEfP5M8/DTEvPngRh8AWBr9OZ66+89rX3tVtZ8tVf0r2fhur6+fMn0ALooJU7KIuS79+fpFNWX018UNVONCAU6vv7QGWHX5rbxpdvCW5JI9sFt9aUVW1pjXfr1K+5X6dOeOsts8vm/p1Iq+glCpUiObh4EVm2JbClPL5V603DrqjoTQomnlFsolUURBISwDpcroJ7b0oA02NQonc6VOv1iZVzBiYiduCcysTgHXF6OAN6GftRd0b3kZV9q0+AxEQ+IRESEvhEqIl262o2d+ZSUQfnmQcNY2dMXbp06rKxDTkJDJeW6vN3QmnfdO+etSJLmp0tSazIHjphQaZo7r6Dq2MZ3eom36nkLlp5St+X1jcrBkv4xK/79qbGfer3tXOWL0+NzJUNjuev1m2r8TwarGszs8uaVXMJRufavYvjGRWXNvLMw4a6hqnTp+OWddN3rFg+ixl4/IR5XXolag7ZzhxpDtvDmd17wTeo3HzocG+79mhzQ6GYS6SY3AUFNu1aUC5jsE6YcVIdLZk+YIqhI161tOWSVwtt2jUJey9Kv5VI+tWYJCCe8U3j1Nhp6t/ry8zF48oidd2GdLkxpPOAeiG9UXgmkpyh9YWSltIkJvUm3/2XckNk/emn1TPGhuRv5ddp5qpQ5XLRDWCzQqyzQ4tWrMxPsIRfxsInjYr7g9evq5O7w5KQAvAPYk1YW0p4kkSoGpozaS3WThNOfixfE8B+wzrpbqcdWAzzu1akq+jGw+IgeIpNPUYoW30gqPdHzI3c6qeif0AMzVuoE4HK3keDt+KE/PXzUOM3K4anQW5D40VTGYGmWAAeCflDN7ulJpW9TyBKHUAklQgJIYOROQVQQamAKkJbaMrX9fKWFqYDBmUZgJkVDBDGXhBRCAKGcdEJJzg3AKfxAiSYgAHJgFrckJIVVqPJSTBWzCD5AKVcG5mmE7ftHW1TxhL2O+o/lEBlsJjO43U3rFG6OCU87VLVgBGu4Moeh2XJ4IVzTHXqVP1xNjNZp05TrlqTVkfBWC3zDJKPPaVcm611kvj5O9qmjKVgyI/9H0qg7cNiOq/AuKG60pBT6RyedqmUacDoLVzBlTKxFNUMPnu/HFOdugaV/jijqUxVNB2eqs41gtM3LCcta4sQhsQIRxIkRQSSIRLEim8daOMprvsqcXCDRdX2cFgMy8TbcsJh6vBNMY++jmv7bXgQHHmqWy0A")
format("woff2");
}
.iconfont {
flex: 1;
text-align: center;
font-family: "iconfont" !important;
font-size: 22px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-undo:before {
content: "\e607";
}
.icon-redo:before {
content: "\e606";
}
.icon-img:before {
content: "\e6e2";
}
.icon-video:before {
content: "\e798";
}
.icon-link:before {
content: "\e60d";
}
.icon-text:before {
content: "\e6ce";
}
.icon-clear:before {
content: "\e637";
}
.icon-save:before {
content: "\e501";
}
/* 模态框 */
.modal {
position: fixed;
top: 50%;
left: 16px;
right: 16px;
background-color: #fff;
border-radius: 12px;
transform: translateY(-50%);
}
.modal_title {
padding: 32px 24px 16px;
font-size: 17px;
font-weight: 700;
text-align: center;
}
.modal_input {
display: block;
padding: 5px;
margin: 0 24px 32px 24px;
font-size: 14px;
border: 1px solid #dfe2e5;
}
.modal_foot {
display: flex;
line-height: 56px;
font-weight: 700;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.modal_button {
flex: 1;
text-align: center;
}
/* 蒙版 */
.mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: black;
opacity: 0.5;
}
js 部分
const content = require('./content')
const Shop = require("../../libs/api/shop");
// 上传图片方法
function upload(src, type) {
return new Promise((resolve, reject) => {
console.log('上传', type === 'img' ? '图片' : '视频', ':', src)
// 实际使用时,上传到服务器
wx.uploadFile({
url: 'https://zlhb.wifimsl.cn/group/upload/image', // 接口地址
filePath: src,
name: 'image',
success(res) {
const { data } = JSON.parse(res.data)
resolve(data) // 返回线上地址
},
fail: reject
})
})
}
// 删除图片方法
function remove(src) {
console.log('删除图片:', src)
// 实际使用时,删除线上资源
}
Component({
/**
* 组件的属性列表
*/
properties: {
title: String,
showPage: {
type: Boolean,
value: false,
},
html: {
type: String,
value: "",
},
naigation: {
type: String,
value: "文本编辑",
},
},
data: {
duration: 300,
position: 'right',
round: false,
overlay: true,
customStyle: '',
overlayStyle: '',
show: false,
tagStyle: { img: 'width:100% !important;' },
html: '',
content,
editable: true,
column: 1,
row: 1,
},
ready() {
this.ctx = this.selectComponent('#article')
/**
* @description 设置获取链接的方法
* @param {String} type 链接的类型(img/video/audio/link)
* @param {String} value 修改链接时,这里会传入旧值
* @returns {Promise} 返回线上地址(2.2.0 版本起设置了 domain 属性时,可以缺省主域名)
* type 为 audio/video 时,可以返回一个源地址数组
* 2.1.3 版本起 type 为 audio 时,可以返回一个 object,包含 src、name、author、poster 等字段
* 2.2.0 版本起 type 为 img 时,可以返回一个源地址数组,表示插入多张图片(修改链接时仅限一张)
*/
this.ctx.getSrc = (type, value) => {
return new Promise((resolve, reject) => {
if (type === 'img' || type === 'video') {
wx.showActionSheet({
itemList: ['本地选取', '远程链接'],
success: res => {
if (res.tapIndex == 0) {
// 本地选取
if (type === 'img') {
wx.chooseImage({
count: value === undefined ? 9 : 1, // 2.2.0 版本起插入图片时支持多张(修改图片链接时仅限一张)
success: res => {
if (res.tempFilePaths.length == 1 && wx.editImage) {
// 单张图片时进行编辑
wx.editImage({
src: res.tempFilePaths[0],
complete: res2 => {
wx.showLoading({
title: '上传中'
})
upload(res2.tempFilePath || res.tempFilePaths[0], type).then(res => {
wx.hideLoading()
resolve(res)
})
}
})
} else {
// 否则批量上传
wx.showLoading({
title: '上传中'
});
(async () => {
const arr = []
for (let item of res.tempFilePaths) {
// 依次上传
const src = await upload(item, type)
arr.push(src)
}
return arr
})().then(res => {
wx.hideLoading()
resolve(res)
})
}
},
fail: reject
})
} else {
wx.chooseVideo({
success: res => {
wx.showLoading({
title: '上传中'
})
upload(res.tempFilePath, type).then(res => {
wx.hideLoading()
resolve(res)
})
},
fail: reject
})
}
} else {
// 远程链接
this.callback = {
resolve,
reject
}
this.setData({
modal: {
title: (type === 'img' ? '图片' : '视频') + '链接',
value
}
})
}
}
})
} else {
this.callback = {
resolve,
reject
}
let title
if (type === 'audio') {
title = '音频链接'
} else if (type === 'link') {
title = '链接地址'
}
this.setData({
modal: {
title,
value
}
})
}
})
}
},
/**
* 组件的方法列表
*/
methods: {
getUserInfo(event) {
console.log(event);
const text = 'insertTable'
const { row, column } = this.data
this.ctx[text](row, column)
},
onChange(e) {
const { type } = e.mark
this.setData({
[type]: e.detail
})
},
onClose() {
this.setData({ show: false });
},
// 删除图片/视频/音频标签事件
remove(e) {
// 删除线上资源
remove(e.detail.src)
},
// 处理模态框
modalInput(e) {
this.value = e.detail.value
},
modalConfirm() {
this.callback.resolve(this.value || this.data.modal.value || '')
this.setData({
modal: null
})
},
modalCancel() {
this.callback.reject()
this.setData({
modal: null
})
},
// 调用编辑器接口
edit(e) {
const text = e.currentTarget.dataset.method
if (text == 'insertTable') {
this.setData({ show: true });
// wx.showModal({
// title: '',
// content: `<view>测试</ view>`,
// complete: (res) => {
// if (res.cancel) {
// }
// if (res.confirm) {
// this.ctx[text]()
// }
// }
// })
} else {
this.ctx[text]()
}
console.log(this.ctx);
},
// 清空编辑器内容
clear() {
wx.showModal({
title: '确认',
content: '确定清空内容吗?',
success: res => {
if (res.confirm)
this.ctx.clear()
}
})
},
// 保存编辑器内容
save() {
const that = this
// 避免无法获取到正在编辑的文本内容
setTimeout(() => {
var content = this.ctx.getContent()
wx.showModal({
title: '保存',
content,
confirmText: '完成',
success: res => {
if (res.confirm) {
// 实际使用时,这里需要上传到服务器
this.setData({ html: content, showPage: false })
this.triggerEvent('getHtml', this.ctx.getContent())
// that.update()
// 复制到剪贴板
// wx.setClipboardData({
// data: content,
// })
// 结束编辑
// this.setData({
// editable: false
// })
}
}
})
}, 50)
},
exit() {
wx.showModal({
title: '提示',
content: '离开前请保存编辑内容!',
complete: (res) => {
if (res.cancel) {
}
if (res.confirm) {
this.setData({ showPage: false })
}
}
})
return
wx.enableAlertBeforeUnload({ //开启页面退出时的对话框
message: "离开前请保存编辑内容!",
success: function (res) {
// console.log("成功:", res);
return true
},
fail: function (err) {
// console.log("失败:", err);
return false
},
});
// wx.navigateBack()
},
back() {
},
goTo(e) {
wx.navigateTo({ url: `../shareElement/index` })
},
onBeforeEnter(res) {
},
onEnter(res) {
},
onAfterEnter(res) {
},
onBeforeLeave(res) {
},
onLeave(res) {
},
onAfterLeave(res) {
},
onClickOverlay(res) {
}
},
})
json部分
{
"usingComponents": {
"mp-html": "/components/mp-html/index",
"van-stepper": "/components/vant/stepper/index",
"van-dialog": "/components/vant/dialog/index",
"van-nav-bar": "/components/vant/nav-bar/index"
},
"navigationBarTitleText": "文本编辑",
"navigationBarBackgroundColor": "#ffffff"
}
以上是组件的所有代码部分,下边是使用
<editor-jj showPage="{{showPage}}" html="{{html}}" bind:getHtml="getHtml"></editor-jj>
getHtml(e) {
console.log('测试', e.detail);
// 这里是编辑后返回的结果
//参数介绍
// showPage 是组件的显示隐藏
// html 是组件的初始化内容,并不是双向绑定的,仅在初始化的时候使用
},