editor.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <template>
  2. <view class="container">
  3. <view class="page-body">
  4. <view class="wrapper">
  5. <view
  6. class="toolbar"
  7. @tap="format"
  8. style="height: 120px; overflow-y: auto"
  9. >
  10. <view
  11. :class="formats.bold ? 'ql-active' : ''"
  12. class="iconfont icon-zitijiacu"
  13. data-name="bold"
  14. >
  15. </view>
  16. <view
  17. :class="formats.italic ? 'ql-active' : ''"
  18. class="iconfont icon-zitixieti"
  19. data-name="italic"
  20. >
  21. </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 class="iconfont icon-charutupian" @tap="insertImage"></view>
  137. <view
  138. :class="formats.header === 1 ? 'ql-active' : ''"
  139. class="iconfont icon-format-header-1"
  140. data-name="header"
  141. :data-value="1"
  142. ></view>
  143. <view
  144. :class="formats.script === 'sub' ? 'ql-active' : ''"
  145. class="iconfont icon-zitixiabiao"
  146. data-name="script"
  147. data-value="sub"
  148. ></view>
  149. <view
  150. :class="formats.script === 'super' ? 'ql-active' : ''"
  151. class="iconfont icon-zitishangbiao"
  152. data-name="script"
  153. data-value="super"
  154. ></view>
  155. <view class="iconfont icon-shanchu" @tap="clear"></view>
  156. <view
  157. :class="formats.direction === 'rtl' ? 'ql-active' : ''"
  158. class="iconfont icon-direction-rtl"
  159. data-name="direction"
  160. data-value="rtl"
  161. ></view>
  162. </view>
  163. <view class="editor-wrapper">
  164. <editor
  165. id="editor"
  166. class="ql-container"
  167. :placeholder="placeholder"
  168. @statuschange="onStatusChange"
  169. :show-img-resize="true"
  170. @ready="onEditorReady"
  171. @input="getCtx"
  172. ></editor>
  173. </view>
  174. </view>
  175. </view>
  176. </view>
  177. </template>
  178. <script>
  179. import { baseURL, phpToken } from "@/commit/requestPhp.js";
  180. export default {
  181. props: {
  182. value: {
  183. type: String,
  184. default: "",
  185. },
  186. placeholder:{
  187. type:String,
  188. default:"开始输入..."
  189. },
  190. readOnly:{
  191. type:Boolean,
  192. default:false
  193. }
  194. },
  195. data() {
  196. return {
  197. richText: "",
  198. formats: {},
  199. };
  200. },
  201. methods: {
  202. readOnlyChange() {
  203. this.readOnly = !this.readOnly;
  204. },
  205. onEditorReady() {
  206. // 富文本节点渲染完成
  207. const query = uni.createSelectorQuery().in(this);
  208. query
  209. .select("#editor")
  210. .context((res) => {
  211. this.editorCtx = res.context;
  212. if (this.value) {
  213. this.editorCtx.setContents({
  214. html: this.value,
  215. });
  216. }
  217. })
  218. .exec(this);
  219. },
  220. // 失去焦点时,获取富文本的内容
  221. getCtx(e) {
  222. this.richText = e.detail.html;
  223. this.$emit("input", e.detail.html);
  224. },
  225. undo() {
  226. this.editorCtx.undo();
  227. },
  228. redo() {
  229. this.editorCtx.redo();
  230. },
  231. format(e) {
  232. let { name, value } = e.target.dataset;
  233. if (!name) return;
  234. // console.log('format', name, value)
  235. this.editorCtx.format(name, value);
  236. },
  237. onStatusChange(e) {
  238. const formats = e.detail;
  239. this.formats = formats;
  240. },
  241. insertDivider() {
  242. this.editorCtx.insertDivider({
  243. success: function () {
  244. console.log("insert divider success");
  245. },
  246. });
  247. },
  248. clear() {
  249. this.editorCtx.clear({
  250. success: function (res) {
  251. console.log("clear success");
  252. },
  253. });
  254. },
  255. removeFormat() {
  256. this.editorCtx.removeFormat();
  257. },
  258. insertDate() {
  259. const date = new Date();
  260. const formatDate = `${date.getFullYear()}/${
  261. date.getMonth() + 1
  262. }/${date.getDate()}`;
  263. this.editorCtx.insertText({
  264. text: formatDate,
  265. });
  266. },
  267. insertImage() {
  268. var that = this;
  269. uni.chooseImage({
  270. async success(res) {
  271. console.log("选择图片成功");
  272. console.log(res);
  273. const tempFiles = res.tempFiles;
  274. const data = await that.uploadFileData(tempFiles[0]);
  275. that.editorCtx.insertImage({
  276. width: "50%",
  277. height: "20%",
  278. src: data,
  279. });
  280. },
  281. });
  282. },
  283. async uploadFileData(files) {
  284. uni.showLoading({
  285. title: "上传中",
  286. mask: true,
  287. });
  288. const uploadUrl = `${baseURL}/mer/upload/image/0/file`;
  289. return new Promise((resolve, reject) => {
  290. uni.uploadFile({
  291. url: uploadUrl,
  292. file: files,
  293. name: "file",
  294. header: {
  295. "X-Token": phpToken,
  296. // 'Content-Type': "application/json;charset=UTF-8",
  297. },
  298. success: (res) => {
  299. const data = JSON.parse(res.data);
  300. if (data.status === 200) {
  301. uni.showToast({
  302. title: "上传成功",
  303. icon: "success",
  304. });
  305. this.paths = data.data.src || "";
  306. } else {
  307. uni.showToast({
  308. title: "上传失败",
  309. icon: "error",
  310. });
  311. this.paths = "";
  312. }
  313. resolve(this.paths);
  314. console.log("上传成功", JSON.parse(res.data));
  315. uni.hideLoading();
  316. uni.hideToast();
  317. },
  318. fail: (err) => {
  319. console.error("上传失败", err);
  320. this.paths = "";
  321. resolve(this.paths);
  322. uni.hideLoading();
  323. uni.hideToast();
  324. },
  325. complete: () => {
  326. uni.hideLoading();
  327. uni.hideToast();
  328. },
  329. });
  330. });
  331. },
  332. },
  333. };
  334. </script>
  335. <style>
  336. @import "./editor-icon.css";
  337. .page-body {
  338. height: calc(100vh - var(--window-top) - var(--status-bar-height));
  339. }
  340. .wrapper {
  341. height: 100%;
  342. }
  343. .editor-wrapper {
  344. height: calc(100vh - var(--window-top) - var(--status-bar-height) - 140px);
  345. background: #eee;
  346. }
  347. .iconfont {
  348. display: inline-block;
  349. padding: 8px 8px;
  350. width: 24px;
  351. height: 24px;
  352. cursor: pointer;
  353. font-size: 20px;
  354. }
  355. .toolbar {
  356. box-sizing: border-box;
  357. border-bottom: 0;
  358. font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  359. }
  360. .ql-container {
  361. box-sizing: border-box;
  362. padding: 12px 15px;
  363. width: 100%;
  364. min-height: 30vh;
  365. height: 100%;
  366. margin-top: 20px;
  367. font-size: 16px;
  368. line-height: 1.5;
  369. }
  370. .ql-active {
  371. color: #06c;
  372. }
  373. </style>