editor.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <template>
  2. <view class="container">
  3. <view class="page-body">
  4. <view class="wrapper">
  5. <view
  6. class="toolbar"
  7. @tap="format"
  8. >
  9. <view
  10. :class="formats.bold ? 'ql-active' : ''"
  11. class="iconfont icon-zitijiacu"
  12. data-name="bold"
  13. >
  14. </view>
  15. <view
  16. :class="formats.italic ? 'ql-active' : ''"
  17. class="iconfont icon-zitixieti"
  18. data-name="italic"
  19. >
  20. </view>
  21. <view class="iconfont icon-charutupian" @tap="insertImage"></view>
  22. <view
  23. :class="formats.underline ? 'ql-active' : ''"
  24. class="iconfont icon-zitixiahuaxian"
  25. data-name="underline"
  26. ></view>
  27. <view
  28. :class="formats.strike ? 'ql-active' : ''"
  29. class="iconfont icon-zitishanchuxian"
  30. data-name="strike"
  31. ></view>
  32. <view
  33. :class="formats.align === 'left' ? 'ql-active' : ''"
  34. class="iconfont icon-zuoduiqi"
  35. data-name="align"
  36. data-value="left"
  37. ></view>
  38. <view
  39. :class="formats.align === 'center' ? 'ql-active' : ''"
  40. class="iconfont icon-juzhongduiqi"
  41. data-name="align"
  42. data-value="center"
  43. ></view>
  44. <view
  45. :class="formats.align === 'right' ? 'ql-active' : ''"
  46. class="iconfont icon-youduiqi"
  47. data-name="align"
  48. data-value="right"
  49. ></view>
  50. <view
  51. :class="formats.align === 'justify' ? 'ql-active' : ''"
  52. class="iconfont icon-zuoyouduiqi"
  53. data-name="align"
  54. data-value="justify"
  55. ></view>
  56. <view
  57. :class="formats.lineHeight ? 'ql-active' : ''"
  58. class="iconfont icon-line-height"
  59. data-name="lineHeight"
  60. data-value="2"
  61. ></view>
  62. <view
  63. :class="formats.letterSpacing ? 'ql-active' : ''"
  64. class="iconfont icon-Character-Spacing"
  65. data-name="letterSpacing"
  66. data-value="2em"
  67. ></view>
  68. <view
  69. :class="formats.marginTop ? 'ql-active' : ''"
  70. class="iconfont icon-722bianjiqi_duanqianju"
  71. data-name="marginTop"
  72. data-value="20px"
  73. ></view>
  74. <view
  75. :class="formats.previewarginBottom ? 'ql-active' : ''"
  76. class="iconfont icon-723bianjiqi_duanhouju"
  77. data-name="marginBottom"
  78. data-value="20px"
  79. ></view>
  80. <view class="iconfont icon-clearedformat" @tap="removeFormat"></view>
  81. <view
  82. :class="formats.fontFamily ? 'ql-active' : ''"
  83. class="iconfont icon-font"
  84. data-name="fontFamily"
  85. data-value="Pacifico"
  86. ></view>
  87. <view
  88. :class="formats.fontSize === '24px' ? 'ql-active' : ''"
  89. class="iconfont icon-fontsize"
  90. data-name="fontSize"
  91. data-value="24px"
  92. ></view>
  93. <view
  94. :class="formats.color === '#0000ff' ? 'ql-active' : ''"
  95. class="iconfont icon-text_color"
  96. data-name="color"
  97. data-value="#0000ff"
  98. ></view>
  99. <view
  100. :class="formats.backgroundColor === '#00ff00' ? 'ql-active' : ''"
  101. class="iconfont icon-fontbgcolor"
  102. data-name="backgroundColor"
  103. data-value="#00ff00"
  104. ></view>
  105. <view class="iconfont icon-date" @tap="insertDate"></view>
  106. <view
  107. class="iconfont icon--checklist"
  108. data-name="list"
  109. data-value="check"
  110. ></view>
  111. <view
  112. :class="formats.list === 'ordered' ? 'ql-active' : ''"
  113. class="iconfont icon-youxupailie"
  114. data-name="list"
  115. data-value="ordered"
  116. ></view>
  117. <view
  118. :class="formats.list === 'bullet' ? 'ql-active' : ''"
  119. class="iconfont icon-wuxupailie"
  120. data-name="list"
  121. data-value="bullet"
  122. ></view>
  123. <view class="iconfont icon-undo" @tap="undo"></view>
  124. <view class="iconfont icon-redo" @tap="redo"></view>
  125. <view
  126. class="iconfont icon-outdent"
  127. data-name="indent"
  128. data-value="-1"
  129. ></view>
  130. <view
  131. class="iconfont icon-indent"
  132. data-name="indent"
  133. data-value="+1"
  134. ></view>
  135. <view class="iconfont icon-fengexian" @tap="insertDivider"></view>
  136. <view
  137. :class="formats.header === 1 ? 'ql-active' : ''"
  138. class="iconfont icon-format-header-1"
  139. data-name="header"
  140. :data-value="1"
  141. ></view>
  142. <view
  143. :class="formats.script === 'sub' ? 'ql-active' : ''"
  144. class="iconfont icon-zitixiabiao"
  145. data-name="script"
  146. data-value="sub"
  147. ></view>
  148. <view
  149. :class="formats.script === 'super' ? 'ql-active' : ''"
  150. class="iconfont icon-zitishangbiao"
  151. data-name="script"
  152. data-value="super"
  153. ></view>
  154. <view class="iconfont icon-shanchu" @tap="clear"></view>
  155. <view
  156. :class="formats.direction === 'rtl' ? 'ql-active' : ''"
  157. class="iconfont icon-direction-rtl"
  158. data-name="direction"
  159. data-value="rtl"
  160. ></view>
  161. </view>
  162. <view class="editor-wrapper">
  163. <editor
  164. id="editor"
  165. class="ql-container"
  166. :placeholder="placeholder"
  167. @statuschange="onStatusChange"
  168. :show-img-resize="true"
  169. @ready="onEditorReady"
  170. @input="getCtx"
  171. ></editor>
  172. </view>
  173. </view>
  174. </view>
  175. </view>
  176. </template>
  177. <script>
  178. import { baseURL, phpToken } from "@/commit/requestPhp.js";
  179. export default {
  180. props: {
  181. value: {
  182. type: String,
  183. default: "",
  184. },
  185. placeholder:{
  186. type:String,
  187. default:"请输入商品详情信息..."
  188. },
  189. readOnly:{
  190. type:Boolean,
  191. default:false
  192. }
  193. },
  194. data() {
  195. return {
  196. richText: "",
  197. formats: {},
  198. };
  199. },
  200. watch: {
  201. value:{
  202. handler(val){
  203. this.richText = val
  204. console.log(val)
  205. if(val){
  206. this.$nextTick(()=>{
  207. // this.onEditorReady()
  208. })
  209. }
  210. },
  211. deep:true,
  212. },
  213. },
  214. mounted(){
  215. this.richText = this.value;
  216. // this.onEditorReady()
  217. },
  218. methods: {
  219. readOnlyChange() {
  220. this.readOnly = !this.readOnly;
  221. },
  222. onEditorReady() {
  223. // 富文本节点渲染完成
  224. const query = uni.createSelectorQuery().in(this);
  225. query
  226. .select("#editor")
  227. .context((res) => {
  228. this.editorCtx = res.context;
  229. if (this.value) {
  230. this.editorCtx.setContents({
  231. html: this.value,
  232. });
  233. }
  234. })
  235. .exec(this);
  236. },
  237. // 失去焦点时,获取富文本的内容
  238. getCtx(e) {
  239. this.richText = e.detail.html;
  240. this.$emit("input", e.detail.html);
  241. },
  242. undo() {
  243. this.editorCtx.undo();
  244. },
  245. redo() {
  246. this.editorCtx.redo();
  247. },
  248. format(e) {
  249. let { name, value } = e.target.dataset;
  250. if (!name) return;
  251. // console.log('format', name, value)
  252. this.editorCtx.format(name, value);
  253. },
  254. onStatusChange(e) {
  255. const formats = e.detail;
  256. this.formats = formats;
  257. },
  258. insertDivider() {
  259. this.editorCtx.insertDivider({
  260. success: function () {
  261. console.log("insert divider success");
  262. },
  263. });
  264. },
  265. clear() {
  266. this.editorCtx.clear({
  267. success: function (res) {
  268. console.log("clear success");
  269. },
  270. });
  271. },
  272. removeFormat() {
  273. this.editorCtx.removeFormat();
  274. },
  275. insertDate() {
  276. const date = new Date();
  277. const formatDate = `${date.getFullYear()}/${
  278. date.getMonth() + 1
  279. }/${date.getDate()}`;
  280. this.editorCtx.insertText({
  281. text: formatDate,
  282. });
  283. },
  284. insertImage() {
  285. var that = this;
  286. uni.chooseImage({
  287. count: 1, // 最多可以选择的图片张数,默认9
  288. // sizeType: ["compressed"], // original 原图,compressed 压缩图,默认二者都有
  289. sourceType: ["album", "camera"], // album 从相册选图,camera 使用相机,默认二者都有
  290. async success(res) {
  291. console.log("选择图片成功");
  292. console.log(res);
  293. const tempFiles = res.tempFiles;
  294. const data = await that.uploadFileData(tempFiles[0]);
  295. that.editorCtx.insertImage({
  296. width: "50%",
  297. height: "20%",
  298. src: data,
  299. });
  300. },
  301. });
  302. },
  303. async uploadFileData(files) {
  304. uni.showLoading({
  305. title: "上传中",
  306. mask: true,
  307. });
  308. const uploadUrl = `${baseURL}/mer/upload/image/0/file`;
  309. return new Promise((resolve, reject) => {
  310. uni.uploadFile({
  311. url: uploadUrl,
  312. file: files,
  313. name: "file",
  314. header: {
  315. "X-Token": phpToken,
  316. // 'Content-Type': "application/json;charset=UTF-8",
  317. },
  318. success: (res) => {
  319. const data = JSON.parse(res.data);
  320. if (data.status === 200) {
  321. uni.showToast({
  322. title: "上传成功",
  323. icon: "success",
  324. });
  325. this.paths = data.data.src || "";
  326. } else {
  327. uni.showToast({
  328. title: "上传失败",
  329. icon: "error",
  330. });
  331. this.paths = "";
  332. }
  333. resolve(this.paths);
  334. console.log("上传成功", JSON.parse(res.data));
  335. uni.hideLoading();
  336. uni.hideToast();
  337. },
  338. fail: (err) => {
  339. console.error("上传失败", err);
  340. this.paths = "";
  341. resolve(this.paths);
  342. uni.hideLoading();
  343. uni.hideToast();
  344. },
  345. complete: () => {
  346. uni.hideLoading();
  347. uni.hideToast();
  348. },
  349. });
  350. });
  351. },
  352. },
  353. };
  354. </script>
  355. <style>
  356. @import "./editor-icon.css";
  357. .page-body {
  358. height: calc(100% - var(--window-top) - var(--status-bar-height));
  359. }
  360. .wrapper {
  361. height: 100%;
  362. }
  363. .editor-wrapper {
  364. height: calc(100vh - var(--window-top) - var(--status-bar-height) - 140px);
  365. background: #eee;
  366. }
  367. .iconfont {
  368. display: inline-block;
  369. padding: 16rpx;
  370. width: 34rpx;
  371. height: 34rpx;
  372. cursor: pointer;
  373. font-size: 40rpx;
  374. }
  375. .toolbar {
  376. box-sizing: border-box;
  377. border-bottom: 0;
  378. font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  379. }
  380. .ql-container {
  381. box-sizing: border-box;
  382. padding: 24rpx 30rpx;
  383. width: 100%;
  384. min-height: 30vh;
  385. height: 100%;
  386. margin-top: 20rpx;
  387. font-size: 32rpx;
  388. line-height: 1.5;
  389. }
  390. .ql-active {
  391. color: #06c;
  392. }
  393. </style>