RichTextEditor2.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <template>
  2. <Popup
  3. :show="show"
  4. position="bottom"
  5. size="90vh"
  6. >
  7. <ActionSheetTitle
  8. title="编辑"
  9. cancelText="取消"
  10. confirmText="保存"
  11. @cancel="cancel"
  12. @confirm="save"
  13. />
  14. <sp-editor
  15. :toolbar-config="{
  16. excludeKeys: ['direction', 'date', 'lineHeight', 'letterSpacing', 'listCheck'],
  17. iconSize: '18px'
  18. }"
  19. @init="initEditor"
  20. @input="inputOver"
  21. @upinImage="upinImage"
  22. @overMax="overMax"
  23. ></sp-editor>
  24. </Popup>
  25. <view class="d-flex flex-col">
  26. <view class="richtext-preview-box" @click.stop="edit">
  27. <Parse v-if="modelValue" :content="modelValue" contentStyle="max-height:400px;overflow:hidden;" />
  28. <Text v-else color="text.second">{{placeholder}}</Text>
  29. <div class="richtext-preview-box-fade"></div>
  30. </view>
  31. <view class="d-flex flex-row gap-sss align-center mt-2">
  32. <Button icon="edit" text="编辑" size="small" type="primary" @click.stop="edit" />
  33. </view>
  34. </view>
  35. </template>
  36. <script setup lang="ts">
  37. import Popup from '@/components/dialog/Popup.vue';
  38. import spEditor from '@/uni_modules/sp-editor/components/sp-editor/sp-editor.vue';
  39. import ActionSheetTitle from '@/components/dialog/ActionSheetTitle.vue';
  40. import { ref } from 'vue';
  41. import { confirm, toast } from '@/components/utils/DialogAction';
  42. import CommonContent from '@/api/CommonContent';
  43. import Parse from '@/components/display/parse/Parse.vue';
  44. import Text from '@/components/basic/Text.vue';
  45. import Button from '@/components/basic/Button.vue';
  46. const show = ref(false);
  47. const props = defineProps({
  48. modelValue: {
  49. type: String,
  50. default: null
  51. },
  52. maxLength: {
  53. type: Number,
  54. default: -1,
  55. },
  56. placeholder: {
  57. type: String,
  58. default: '未编写内容,点击编写',
  59. },
  60. })
  61. const emit = defineEmits(['update:modelValue'])
  62. function edit() {
  63. show.value = true;
  64. setTimeout(() => {
  65. }, 200);
  66. }
  67. function cancel() {
  68. confirm({
  69. title: '提示',
  70. content: '是否放弃编辑?',
  71. }).then((res) => {
  72. if (res)
  73. show.value = false;
  74. })
  75. }
  76. function save() {
  77. emit('update:modelValue', currentContent);
  78. show.value = false;
  79. }
  80. let currentContent = '';
  81. let currentEditor: any = null;
  82. /**
  83. * 获取输入内容
  84. */
  85. function inputOver(e: { html: string; text: string; }) {
  86. // 可以在此处获取到编辑器已编辑的内容
  87. currentContent = e.html;
  88. }
  89. /**
  90. * 超出最大内容限制
  91. * @param {Object} e {html,text} 内容的html文本,和text文本
  92. */
  93. function overMax(e: { html: string; text: string; }) {
  94. // 若设置了最大字数限制,可在此处触发超出限制的回调
  95. console.log('==== overMax :', e)
  96. }
  97. function initEditor(editor: any) {
  98. editor.setContents({
  99. html: props.modelValue
  100. })
  101. }
  102. /**
  103. * 直接运行示例工程插入图片无法正常显示的看这里
  104. * 因为插件默认采用云端存储图片的方式
  105. * 以$emit('upinImage', tempFiles, this.editorCtx)的方式回调
  106. * @param {Object} tempFiles
  107. * @param {Object} editorCtx
  108. */
  109. function upinImage(tempFiles: any, editorCtx: any) {
  110. CommonContent.uploadFile(
  111. // #ifdef MP-WEIXIN
  112. // 注意微信小程序的图片路径是在tempFilePath字段中
  113. tempFiles[0].tempFilePath
  114. // #endif
  115. // #ifndef MP-WEIXIN
  116. tempFiles[0].path
  117. // #endif
  118. , 'image', 'file').then((res) => {
  119. editorCtx.insertImage({
  120. src: res.fullurl,
  121. width: '80%', // 默认不建议铺满宽度100%,预留一点空隙以便用户编辑
  122. success: function () {}
  123. })
  124. }).catch((err) => {
  125. toast('上传图片失败')
  126. console.error('uploadFile error', err);
  127. });
  128. return;
  129. }
  130. </script>
  131. <style lang="scss" scoped>
  132. .richtext-preview-box {
  133. position: relative;
  134. flex: 1;
  135. min-height: 400rpx;
  136. .richtext-preview-box-fade {
  137. position: absolute;
  138. bottom: 0;
  139. left: 0;
  140. width: 100%;
  141. height: 100rpx;
  142. background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
  143. }
  144. }
  145. </style>