cu-editor.vue 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. <template>
  2. <view class="editor-container">
  3. <view id="fixed-top" class="fixed-top" :class="{ 'isFixed': isFixed }">
  4. <view style="background-color: #fff;display: flex;justify-content: space-between;">
  5. <view class="center" style="width: 80%;justify-content: flex-start;">
  6. <u-form style="width: 100%;">
  7. <u-form-item label="标题">
  8. <u-input style="width: 95%;" v-model="title" placeholder="请输入标题"/>
  9. </u-form-item>
  10. </u-form>
  11. </view>
  12. <view class="center">
  13. <view @click="save" class="cu-btn radius bg-base">
  14. 保存
  15. </view>
  16. </view>
  17. </view>
  18. </view>
  19. <view class="fixed-top__place"></view>
  20. <scroll-view scroll-y :style="{ height: scrollViewHeight + 'px' }">
  21. <editor id="editor" class="cu-editor" :placeholder="placeholder" :read-only="readOnly"
  22. :show-img-size="showImgSize" :show-img-toolbar="showImgToolbar" :show-img-resize="showImgResize"
  23. @statuschange="onStatusChange" @ready="onEditorReady" @input="onEditorInput" @focus="onEditorFocus"
  24. @blur="onEditorBlur"></editor>
  25. </scroll-view>
  26. <view class="fixed-bottom" :hidden="!toolbarShow" :style="{ bottom: fixedBottom + 'px' }" @touchstart.stop="">
  27. <view class="toolbar selector" :style="{height: toolBarHeight + 'rpx'}">
  28. <view class="toolbar-item-header" @touchend.stop="changeKeyBoard"><i class="iconfont icon-keyboard"></i>
  29. </view>
  30. <view v-for="(icon, index) in ['icon-add', 'icon-textformat', 'icon-align-left']" :key="index"
  31. class="toolbar-item" @touchend.stop="changeSwiper(index)">
  32. <i class="iconfont" :class="[icon, { active: toolBarContentShow && swiperCurrent == index }]"></i>
  33. </view>
  34. <view class="toolbar-item" @touchend.stop="formatformat('check')"><i
  35. class="iconfont icon-list-check"></i></view>
  36. <view class="toolbar-item" @touchend.stop="formatformat('undo')"><i class="iconfont icon-undo"></i>
  37. </view>
  38. <view class="toolbar-item" @touchend.stop="formatformat('redo')"><i class="iconfont icon-redo"></i>
  39. </view>
  40. <view class="toolbar-item-footer" @touchend.stop="hideToolbar"><i class="iconfont icon-check"></i>
  41. </view>
  42. </view>
  43. <swiper :hidden="!toolBarContentShow" class="toolbar-content swiper-box"
  44. :style="{height: toolBarContentHeight + 'rpx'}" :current="swiperCurrent" duration="300">
  45. <!-- @touchstart.stop="" 禁用手动滑动 -->
  46. <swiper-item v-for="(page, i) in formatArray" :key="i" class="swiper-item" @touchstart.stop="">
  47. <template v-if="page.type === 'feature'">
  48. <view class="feature-items flex">
  49. <view v-for="(pitem, pindex) in page.array" :key="pindex" class="feature"
  50. @touchend.stop="formatformat(pitem.name)">
  51. <view class="icon"><text class="iconfont" :class="'icon-' + pitem.icon"></text></view>
  52. </view>
  53. </view>
  54. </template>
  55. <template v-else-if="page.type === 'tool'">
  56. <view v-for="(pitem, pindex) in page.array" :key="pindex" class="tool-items flex">
  57. <view v-for="(item, index) in pitem.items" :key="index" class="tool-item"
  58. @touchend.stop="formatformat('format', item, pitem)"
  59. :class="{ 'ql-active': isActive(item, pitem), noBgColor: pitem.name == 'color' }">
  60. <view v-if="pitem.name == 'color'" class="color-circle"
  61. :style="{ 'background-color': item.value }"></view>
  62. <i v-else-if="pitem.label == 'icon'" class="iconfont" :class="'icon-' + item.icon"></i>
  63. <text v-else class="txt"
  64. :style="[{fontSize : pitem.name == 'fontSize' ? item.value : ''}, item.style]">{{ item.title || item.value }}</text>
  65. </view>
  66. </view>
  67. </template>
  68. </swiper-item>
  69. </swiper>
  70. </view>
  71. </view>
  72. </template>
  73. <script>
  74. import {
  75. handleHtmlImage
  76. } from './util'
  77. export default {
  78. name: 'cuEditor',
  79. props: {
  80. //editor属性,提示信息
  81. placeholder: {
  82. type: String,
  83. default: '请输入内容'
  84. },
  85. //editor属性,点击图片时显示图片大小控件
  86. showImgSize: {
  87. type: Boolean,
  88. default: true
  89. },
  90. //editor属性,点击图片时显示工具栏控件
  91. showImgToolbar: {
  92. type: Boolean,
  93. default: true
  94. },
  95. //editor属性,点击图片时显示修改尺寸控件
  96. showImgResize: {
  97. type: Boolean,
  98. default: true
  99. },
  100. //编辑器内容,必填
  101. content: {
  102. type: String,
  103. default: ''
  104. },
  105. //chooseImage参数,最大文件大小,上传文件前校验是否符合规则,单位MB
  106. maxSize: {
  107. type: Number,
  108. default: 5
  109. },
  110. //chooseImage参数,最多可以选择的图片张数
  111. count: {
  112. type: Number,
  113. default: 5
  114. },
  115. //chooseImage参数,所选的图片的尺寸
  116. sizeType: {
  117. type: Array,
  118. default () {
  119. return ['original', 'compressed'] //['original', 'compressed']
  120. }
  121. },
  122. //chooseImage参数,选择图片的来源
  123. sourceType: {
  124. type: Array,
  125. default () {
  126. return ['album', 'camera']
  127. }
  128. },
  129. //不允许上传的图片类型
  130. noAllowType: {
  131. type: Array,
  132. default () {
  133. return [] //['gif']
  134. }
  135. },
  136. //uploadFile参数,必填
  137. url: {
  138. type: String,
  139. default: ''
  140. },
  141. //uploadFile参数
  142. header: {
  143. type: Object,
  144. default () {
  145. return {}
  146. }
  147. },
  148. //uploadFile参数
  149. formData: {
  150. type: Object,
  151. default () {
  152. return {}
  153. }
  154. },
  155. //uploadFile参数
  156. name: {
  157. type: String,
  158. default: 'file'
  159. }
  160. },
  161. data() {
  162. return {
  163. //标题
  164. title:'',
  165. isFixed: true,
  166. iphoneXBottomH: 0,
  167. scrollHeightDefault: 0,
  168. keyboardHeight: 0,
  169. readOnly: true,
  170. isDefaultFormat: true, // 首次聚集时设置默认格式
  171. isIos: false,
  172. inputFocus: false,
  173. formats: {},
  174. formatArray: [{
  175. type: 'feature',
  176. array: [{
  177. name: 'chooseImage',
  178. icon: 'image'
  179. },
  180. {
  181. name: 'chooseImagebyCamera',
  182. icon: 'photo'
  183. },
  184. {
  185. name: 'insertDivider',
  186. icon: 'line'
  187. }
  188. ]
  189. },
  190. {
  191. type: 'tool',
  192. array: [{
  193. name: 'text',
  194. label: 'icon',
  195. items: [{
  196. name: 'bold',
  197. icon: 'bold'
  198. },
  199. {
  200. name: 'italic',
  201. icon: 'italic'
  202. },
  203. {
  204. name: 'underline',
  205. icon: 'underline'
  206. },
  207. {
  208. name: 'strike',
  209. icon: 'strikethrough'
  210. },
  211. {
  212. name: 'backgroundColor',
  213. value: 'yellow',
  214. icon: 'fontbgcolor'
  215. }
  216. ]
  217. },
  218. {
  219. name: 'defaultFormat',
  220. items: [{
  221. title: '标题',
  222. format: {
  223. fontSize: '18px',
  224. bold: 'strong'
  225. },
  226. style: {
  227. fontSize: '18px',
  228. fontWeight: 'bold'
  229. }
  230. },
  231. {
  232. title: '小标题',
  233. format: {
  234. fontSize: '16px',
  235. bold: 'strong'
  236. },
  237. style: {
  238. fontSize: '16px',
  239. fontWeight: 'bold'
  240. }
  241. },
  242. {
  243. title: '正文',
  244. format: {
  245. fontSize: '14px'
  246. },
  247. style: {
  248. fontSize: '14px'
  249. }
  250. },
  251. {
  252. title: '注释',
  253. format: {
  254. fontSize: '12px',
  255. color: '#888888'
  256. },
  257. style: {
  258. fontSize: '12px',
  259. color: '#888888',
  260. }
  261. }
  262. ]
  263. },
  264. {
  265. name: 'fontSize',
  266. items: [{
  267. title: '18',
  268. value: '18px'
  269. },
  270. {
  271. title: '16',
  272. value: '16px'
  273. },
  274. {
  275. title: '14',
  276. value: '14px'
  277. },
  278. {
  279. title: '12',
  280. value: '12px'
  281. },
  282. {
  283. title: '11',
  284. value: '11px'
  285. },
  286. {
  287. title: '10',
  288. value: '10px'
  289. }
  290. ]
  291. },
  292. {
  293. name: 'color',
  294. items: [{
  295. value: '#000000'
  296. },
  297. {
  298. value: '#888888'
  299. },
  300. {
  301. value: '#ffffff'
  302. },
  303. {
  304. value: '#f6de41'
  305. },
  306. {
  307. value: '#f68c41'
  308. },
  309. {
  310. value: '#fd3136'
  311. },
  312. {
  313. value: '#5ad8a6'
  314. }
  315. ]
  316. }
  317. ]
  318. },
  319. {
  320. type: 'tool',
  321. array: [{
  322. name: 'align',
  323. label: 'icon',
  324. items: [{
  325. value: 'left',
  326. icon: 'align-left'
  327. },
  328. {
  329. value: 'center',
  330. icon: 'align-center'
  331. },
  332. {
  333. value: 'right',
  334. icon: 'align-right'
  335. }
  336. ]
  337. },
  338. {
  339. name: 'text',
  340. label: 'icon',
  341. items: [{
  342. name: 'list',
  343. value: 'ordered',
  344. icon: 'orderedlist'
  345. },
  346. {
  347. name: 'list',
  348. value: 'bullet',
  349. icon: 'unorderedlist'
  350. },
  351. {
  352. name: 'indent',
  353. icon: 'outdent',
  354. value: '+1'
  355. },
  356. {
  357. name: 'indent',
  358. icon: 'indent',
  359. value: '-1'
  360. }
  361. ]
  362. },
  363. {
  364. name: 'lineHeight',
  365. items: [{
  366. value: 1
  367. },
  368. {
  369. value: 1.3
  370. },
  371. {
  372. value: 1.5
  373. },
  374. {
  375. value: 2
  376. },
  377. {
  378. value: 3
  379. }
  380. ]
  381. }
  382. ]
  383. }
  384. ],
  385. curLength: 0,
  386. swiperCurrent: 0,
  387. toolbarShow: false,
  388. toolBarContentShow: false,
  389. fixedTopHeight: 0, // 顶部工具栏高度
  390. toolBarHeight: 100, // 工具栏高度
  391. toolBarContentHeight: 530, // 工具栏内容高度
  392. progress: true //判断是否监听上传进度变化
  393. };
  394. },
  395. computed: {
  396. fullToolBarHeight() {
  397. let height = 0
  398. this.toolbarShow ? height += this.toolBarHeight : ''
  399. this.toolBarContentShow ? height += this.toolBarContentHeight : ''
  400. return uni.upx2px(height)
  401. },
  402. scrollHeight() {
  403. return this.scrollHeightDefault - this.fixedTopHeight - this.fullToolBarHeight;
  404. },
  405. scrollViewHeight() {
  406. let scrollViewHeight = this.scrollHeight - this.keyboardHeight;
  407. return this.keyboardHeight > 0 ? scrollViewHeight + this.iphoneXBottomH : scrollViewHeight;
  408. },
  409. fixedBottom() {
  410. return this.isIos || this.iphoneXBottomH > 0 ? (this.keyboardHeight > 0 ? this.keyboardHeight :
  411. this.iphoneXBottomH) : 0
  412. }
  413. },
  414. watch: {
  415. keyboardHeight(newVal, oldVal) {
  416. if (newVal > 0) {
  417. this.toolBarContentShow = false
  418. }
  419. // this.updatePosition(newVal)
  420. },
  421. toolbarShow(val) {
  422. if (!val) this.toolBarContentShow = val
  423. }
  424. },
  425. created() {
  426. this.index = 0
  427. this.createdAt = Date.now()
  428. this.getUid = () => `wux-upload--${this.createdAt}-${++this.index}`
  429. this.uploadTask = {}
  430. this.tempFilePaths = []
  431. },
  432. mounted() {
  433. const query = wx.createSelectorQuery().in(this)
  434. query.select('#fixed-top').boundingClientRect(res => {
  435. this.fixedTopHeight = res.height
  436. }).exec()
  437. const system = uni.getSystemInfo({
  438. success: e => {
  439. this.isIos = e.platform == 'ios'
  440. let isIphoneX = (e.platform == 'devtools' || this.isIos) && e.safeArea.top == 44
  441. this.iphoneXBottomH = isIphoneX ? 34 : 0
  442. this.scrollHeightDefault = e.windowHeight - 34
  443. }
  444. })
  445. uni.onKeyboardHeightChange(res => {
  446. let keyboardHeight = this.keyboardHeight
  447. if (res.height === keyboardHeight) return
  448. this.keyboardHeight = res.height;
  449. const duration = res.height > 0 ? res.duration * 1000 : 0
  450. keyboardHeight = res.height;
  451. setTimeout(() => {
  452. uni.pageScrollTo({
  453. scrollTop: 0,
  454. success: () => {
  455. this.updatePosition(keyboardHeight)
  456. this.editorCtx.scrollIntoView() //使得编辑器光标处滚动到窗口可视区域内
  457. }
  458. })
  459. }, duration)
  460. })
  461. },
  462. beforeDestroy() {
  463. console.log("editor beforeDestroy")
  464. },
  465. methods: {
  466. initTitle(title){
  467. this.title=title
  468. },
  469. isActive(item, pitem) {
  470. let {
  471. name,
  472. value,
  473. format
  474. } = item
  475. !name ? name = pitem.name : ''
  476. if (format) {
  477. for (let name in format) {
  478. if (this.formats[name] !== format[name]) {
  479. return false
  480. }
  481. }
  482. return true
  483. } else {
  484. return value ? this.formats[name] === value : this.formats[name]
  485. }
  486. },
  487. hideKeyboard() {
  488. // uni.hideKeyboard() //uni-app提供了隐藏软键盘的api,但是没有生效
  489. this.editorCtx.blur()
  490. },
  491. changeSwiper(current) {
  492. this.toolBarContentShow = true
  493. this.swiperCurrent = current
  494. this.hideKeyboard()
  495. },
  496. updatePosition(keyboardHeight) {
  497. this.keyboardHeight = keyboardHeight
  498. },
  499. onEditorReady() {
  500. const that = this
  501. uni.createSelectorQuery()
  502. .in(this)
  503. .select('#editor')
  504. .context(function(res) {
  505. that.editorCtx = res.context
  506. that.setValue(that.content)
  507. // //设置默认格式
  508. // // that.editorCtx.format('header', '4')
  509. // that.editorCtx.format('fontSize', '14px')
  510. // that.editorCtx.format('align', 'left')
  511. // that.editorCtx.format('lineHeight', '1.3')
  512. //setContents设置内容后editor会自动聚焦,解决:先设置read_only为true,赋值后再把read_only属性设置为false
  513. that.readOnly = false
  514. })
  515. .exec()
  516. },
  517. onEditorInput(e) {
  518. let {
  519. html,
  520. text
  521. } = e.detail
  522. this.curLength = text.length - 1
  523. },
  524. onEditorFocus(e) {
  525. this.toolbarShow = true
  526. this.inputFocus = true
  527. if (this.isDefaultFormat) {
  528. //设置默认格式
  529. this.editorCtx.format('fontSize', '14px')
  530. this.editorCtx.format('align', 'left')
  531. this.isDefaultFormat = false
  532. }
  533. },
  534. onEditorBlur() {
  535. this.editorCtx.blur()
  536. this.updatePosition(0)
  537. this.inputFocus = false
  538. },
  539. changeKeyBoard() {
  540. this.toolBarContentShow = false
  541. this.hideKeyboard()
  542. },
  543. hideToolbar() {
  544. this.hideKeyboard()
  545. this.toolbarShow = false
  546. },
  547. // 修改默认样式
  548. formatDefault(format) {
  549. for (let name in format) {
  550. this.editorCtx.format(name, format[name])
  551. }
  552. if (format.bold) {
  553. this.editorCtx.format('bold', true)
  554. } else if (this.formats.bold) {
  555. this.editorCtx.format('bold', '')
  556. }
  557. this.editorCtx.format('lineHeight', '') //选择默认样式时,取消当前行高的选择
  558. },
  559. formatformat(bind, item = {}, pitem = {}) {
  560. item.name = item.name || pitem.name || ''
  561. let {
  562. name,
  563. value
  564. } = item
  565. switch (bind) {
  566. case 'format': //改变文本样式
  567. if (!name) return
  568. if (name == 'defaultFormat') { //选择标题样式时,取消当前字号的选择
  569. this.formatDefault(item.format)
  570. } else {
  571. this.editorCtx.format(name, value)
  572. }
  573. break
  574. case 'removeFormat': //删除字体样式
  575. this.editorCtx.removeFormat()
  576. break
  577. case 'insertDate': //插入时间
  578. var date = new Date()
  579. var formatDate = `${date.getFullYear()} 年${date.getMonth() + 1} 月${date.getDate()} 日`;
  580. this.editorCtx.insertText({
  581. text: formatDate
  582. })
  583. break
  584. case 'check': //设置当前行为待办列表格式
  585. this.editorCtx.format('list', 'check')
  586. break
  587. case 'undo': //撤销操作
  588. this.editorCtx.undo()
  589. break
  590. case 'redo': //恢复操作
  591. this.editorCtx.redo()
  592. break
  593. case 'insertDivider': //添加分割线
  594. this.editorCtx.insertDivider()
  595. break
  596. case 'clear': //清除内容
  597. this.editorCtx.clear()
  598. break
  599. case 'chooseImage': //插入相册图片
  600. this.chooseImage()
  601. break;
  602. case 'chooseImagebyCamera': //拍摄
  603. this.chooseImage(true)
  604. break
  605. }
  606. },
  607. onStatusChange(e) {
  608. this.formats = e.detail
  609. console.log(this.formats)
  610. },
  611. chooseImage(onlyCamera) {
  612. const success = res => {
  613. this.tempFilePaths = res.tempFiles.map(item => ({
  614. url: item.path,
  615. size: item.size,
  616. type: item.path.substring(item.path.lastIndexOf('.') + 1, item.path.length),
  617. uid: this.getUid()
  618. }))
  619. // 当前插入图片src地址直接使用临时路径,如果对接接口上传,更改为使用【上传文件】代码片段:
  620. /* 直接插入临时图片地址 start */
  621. // this.tempFilePaths.forEach(file => {
  622. // this.insertImage(file.url, file)
  623. // })
  624. /* 直接插入临时图片地址 end */
  625. /* 上传文件 start */
  626. this.$emit('before', res)
  627. this.verifyFile()
  628. this.$nextTick(() => {
  629. this.uploadFile(this.tempFilePaths.length)
  630. })
  631. /* 上传文件 end */
  632. }
  633. const {
  634. count,
  635. sizeType
  636. } = this
  637. setTimeout(() => {
  638. uni.chooseImage({
  639. count,
  640. sizeType,
  641. sourceType: onlyCamera ? ['camera'] : this.sourceType,
  642. success
  643. })
  644. }, 100)
  645. },
  646. insertImage(src, file) {
  647. var that = this
  648. that.editorCtx.insertImage({
  649. src,
  650. data: {
  651. id: file.uid
  652. },
  653. // extClass:'editor-img',
  654. extClass: 'editor--editor-img', //添加到图片 img标签上的类名为editor-img,设置前缀editor--才生效。部分机型点击图片右边的光标时不灵敏,需将样式editor-img宽度调小 max-width:98%;从而在图片右侧中留出部分位置供用户点击聚集。
  655. success(e) {
  656. //真机会自动插入一行空格
  657. }
  658. })
  659. },
  660. /**
  661. * 上传文件,支持多图递归上传
  662. */
  663. uploadFile(uploadCount, curIndex) {
  664. if (!this.tempFilePaths.length) return
  665. const {
  666. url,
  667. name,
  668. header,
  669. formData,
  670. progress
  671. } = this
  672. const file = this.tempFilePaths.shift()
  673. curIndex ? (file.index = curIndex + 1) : (file.index = 1)
  674. let {
  675. uid,
  676. url: filePath
  677. } = file
  678. if (!url || !filePath) return
  679. this.uploadTask[uid] = uni.uploadFile({
  680. url,
  681. filePath,
  682. name,
  683. header,
  684. formData,
  685. success: res => this.onSuccess(file, res),
  686. fail: res => this.onFail(file, res),
  687. complete: res => {
  688. delete this.uploadTask[uid]
  689. this.$emit('complete', res)
  690. //同时选择多图上传时,只校验第一张图片大小,多图递归上传需逐一校验
  691. if (!this.tempFilePaths.length) return
  692. this.verifyFile()
  693. this.uploadFile(uploadCount, file.index)
  694. }
  695. })
  696. // 判断是否监听上传进度变化
  697. if (progress) {
  698. this.uploadTask[uid].onProgressUpdate(res => this.onProgress(file, res, uploadCount))
  699. }
  700. },
  701. /**校验图片格式和大小是否符合规则 */
  702. verifyFile() {
  703. var {
  704. size: tempFilesSize,
  705. type
  706. } = this.tempFilePaths[0] //获取图片的大小,单位B
  707. this.noAllowType.map(item => {
  708. if (type == item) {
  709. uni.showToast({
  710. title: `不支持上传${item}图片`,
  711. icon: 'none'
  712. })
  713. this.tempFilePaths.shift()
  714. }
  715. })
  716. if (tempFilesSize / 1024 > this.maxSize * 1024) {
  717. uni.showToast({
  718. title: '上传图片不能大于' + this.bytesToSize(this.maxSize * 1024 * 1024) + '!',
  719. icon: 'none'
  720. })
  721. this.tempFilePaths.shift()
  722. }
  723. },
  724. onSuccess(file, res) {
  725. //按照接口自行处理数据,insertImage的src参数为接口返回的图片地址
  726. /**
  727. * 示例数据:
  728. res = {
  729. data: '{"code":0,"msg":"上传成功","data":{"path":"https://xxx.com/images/upload/1.png"}}'
  730. }
  731. */
  732. let json = JSON.parse(res.data)
  733. if (json.success) {
  734. this.insertImage(json.data.link, file)
  735. } else {
  736. uni.showToast({
  737. title: '图片上传失败',
  738. icon: 'none'
  739. })
  740. }
  741. },
  742. onFail(file, res) {
  743. console.log(res);
  744. uni.showToast({
  745. title: '图片上传失败!',
  746. icon: 'none'
  747. })
  748. },
  749. /**
  750. * 监听上传进度变化的回调函数
  751. * @param {Object} file 文件对象
  752. * @param {Object} res 请求响应对象
  753. * @param {Number} uploadCount 选择图片总数量
  754. */
  755. onProgress(file, res, uploadCount) {
  756. if (res.progress != 100) {
  757. uni.showToast({
  758. title: `正在上传图片${file.index}/${uploadCount}`,
  759. icon: 'none'
  760. })
  761. }
  762. const targetItem = {
  763. ...file,
  764. progress: res.progress,
  765. res
  766. }
  767. const info = {
  768. file: targetItem
  769. }
  770. this.$emit('progress', info)
  771. },
  772. bytesToSize: function(bytes) {
  773. if (bytes === 0) return '0 B'
  774. var k = 1024,
  775. sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  776. i = Math.floor(Math.log(bytes) / Math.log(k))
  777. return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
  778. },
  779. setValue(value) {
  780. if (this.editorCtx) {
  781. this.editorCtx.setContents({
  782. html: value,
  783. success: () => {
  784. this.getContents(res => {
  785. this.onEditorInput({
  786. detail: {
  787. html: res.html,
  788. text: res.text
  789. }
  790. })
  791. this.$emit('update', res)
  792. })
  793. }
  794. })
  795. }
  796. },
  797. getContents(callback) {
  798. //由于获取编辑器内容getContents为异步,因此需要使用callback回调
  799. this.editorCtx.getContents({
  800. success: res => {
  801. callback(res)
  802. }
  803. })
  804. },
  805. save() {
  806. if (!this.title) {
  807. uni.showToast({
  808. title:"请输入标题",
  809. icon:"none"
  810. })
  811. return
  812. }
  813. if (this.curLength==0) {
  814. uni.showToast({
  815. title:"请输入内容",
  816. icon:"none"
  817. })
  818. return
  819. }
  820. this.editorCtx.getContents({
  821. success: res => {
  822. console.log(res)
  823. res.html = handleHtmlImage(res.html, true)
  824. let data={
  825. title:this.title,
  826. content:res
  827. }
  828. this.$emit('save', data)
  829. },
  830. complete: res => {
  831. console.log('getContents complete')
  832. }
  833. })
  834. }
  835. }
  836. }
  837. </script>
  838. <style lang="scss" scoped>
  839. @import 'iconfont.wxss';
  840. $bg-color: #f7f7f7;
  841. $bg-color-hover: #eaeaea;
  842. .center{
  843. display: flex;
  844. justify-content: center;
  845. align-items: center;
  846. }
  847. .fixed-top {
  848. position: fixed;
  849. top: -88rpx;
  850. line-height: 88rpx;
  851. padding: 0 30rpx;
  852. box-sizing: border-box;
  853. transition: all 0.3s ease;
  854. background-color: #FFFFFF;
  855. z-index: 999;
  856. &.isFixed {
  857. top: 0;
  858. }
  859. .btn {
  860. width: 100%;
  861. position: relative;
  862. border: 0 !important;
  863. border-radius: 6rpx;
  864. padding-left: 0;
  865. padding-right: 0;
  866. overflow: visible;
  867. float: right;
  868. width: 100rpx;
  869. height: 60rpx;
  870. line-height: 60rpx;
  871. font-size: 24rpx;
  872. margin: 14rpx 0;
  873. text-align: center;
  874. }
  875. .btn-primary {
  876. background: $color !important;
  877. color: #fff;
  878. }
  879. .btn-primary:hover {
  880. opacity: 0.8;
  881. }
  882. }
  883. .fixed-top,
  884. .fixed-top__place {
  885. width: 100%;
  886. height: 88rpx;
  887. }
  888. .flex {
  889. display: flex;
  890. }
  891. .editor-container {}
  892. .cu-editor {
  893. box-sizing: border-box;
  894. width: 100%;
  895. height: 100%;
  896. font-size: 28rpx;
  897. line-height: 1.5;
  898. overflow: auto;
  899. padding: 40rpx 35rpx;
  900. min-height: unset !important;
  901. }
  902. .editor-img {
  903. max-width: 98% !important;
  904. }
  905. .ql-active {
  906. background-color: $bg-color-hover;
  907. .color-circle {
  908. border: solid 1px;
  909. }
  910. }
  911. .noBgColor {
  912. background-color: none !important;
  913. }
  914. .fixed-bottom {
  915. position: fixed;
  916. left: 0;
  917. width: 100%;
  918. right: 100%;
  919. bottom: 0;
  920. z-index: 99999;
  921. }
  922. .toolbar {
  923. box-sizing: border-box;
  924. display: flex;
  925. align-items: center;
  926. justify-content: space-between;
  927. border: 1rpx solid #e5e5e5;
  928. border-left: none;
  929. border-right: none;
  930. background: #fff;
  931. .iconfont {
  932. display: inline-block;
  933. cursor: pointer;
  934. font-size: 40rpx;
  935. text-align: center;
  936. position: relative;
  937. &.active::after {
  938. content: '';
  939. left: 0;
  940. bottom: 0;
  941. width: 100%;
  942. position: absolute;
  943. height: 6rpx;
  944. border-bottom: solid 6rpx #000000;
  945. }
  946. }
  947. .toolbar-item {
  948. height: 100rpx;
  949. line-height: 100rpx;
  950. flex: 1;
  951. text-align: center;
  952. &:active {
  953. opacity: 0.4;
  954. }
  955. }
  956. .toolbar-item-header,
  957. .toolbar-item-footer {
  958. width: 108rpx;
  959. text-align: center;
  960. }
  961. .toolbar-item-header {
  962. border-right: solid 1rpx $uni-border-color;
  963. }
  964. .toolbar-item-footer {
  965. border-left: solid 1rpx $uni-border-color;
  966. color: $color;
  967. font-weight: bold;
  968. }
  969. }
  970. .toolbar-content {
  971. background-color: #ffffff;
  972. }
  973. .swiper-item {
  974. box-sizing: border-box;
  975. padding: 0 30rpx;
  976. }
  977. .tool-items {
  978. background-color: $bg-color;
  979. color: #323232;
  980. height: 80rpx;
  981. line-height: 80rpx;
  982. margin: 32rpx 0;
  983. border-radius: 16rpx;
  984. overflow: hidden;
  985. .tool-item {
  986. flex: 1;
  987. display: flex;
  988. align-items: center;
  989. justify-content: center;
  990. &:active {
  991. background-color: $bg-color-hover;
  992. }
  993. }
  994. .iconfont {
  995. display: inline-block;
  996. width: 80rpx;
  997. height: 80rpx;
  998. line-height: 80rpx;
  999. cursor: pointer;
  1000. font-size: 40rpx;
  1001. text-align: center;
  1002. }
  1003. .color-circle {
  1004. width: 40rpx;
  1005. height: 40rpx;
  1006. border-radius: 50%;
  1007. }
  1008. .txt {
  1009. font-size: 14px;
  1010. }
  1011. }
  1012. .feature {
  1013. margin: 30rpx;
  1014. text-align: center;
  1015. .icon {
  1016. background-color: $bg-color;
  1017. width: 120rpx;
  1018. height: 120rpx;
  1019. line-height: 120rpx;
  1020. color: #323232;
  1021. margin-bottom: 10rpx;
  1022. &:active {
  1023. background-color: $bg-color-hover;
  1024. }
  1025. .iconfont {
  1026. font-size: 42rpx;
  1027. }
  1028. }
  1029. }
  1030. </style>