editor.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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. count: 1, // 最多可以选择的图片张数,默认9
  271. // sizeType: ["compressed"], // original 原图,compressed 压缩图,默认二者都有
  272. sourceType: ["album", "camera"], // album 从相册选图,camera 使用相机,默认二者都有
  273. async success(res) {
  274. console.log("选择图片成功");
  275. console.log(res);
  276. const tempFiles = res.tempFiles;
  277. const data = await that.uploadFileData(tempFiles[0]);
  278. that.editorCtx.insertImage({
  279. width: "50%",
  280. height: "20%",
  281. src: data,
  282. });
  283. },
  284. });
  285. },
  286. async uploadFileData(files) {
  287. uni.showLoading({
  288. title: "上传中",
  289. mask: true,
  290. });
  291. const uploadUrl = `${baseURL}/mer/upload/image/0/file`;
  292. return new Promise((resolve, reject) => {
  293. uni.uploadFile({
  294. url: uploadUrl,
  295. file: files,
  296. name: "file",
  297. header: {
  298. "X-Token": phpToken,
  299. // 'Content-Type': "application/json;charset=UTF-8",
  300. },
  301. success: (res) => {
  302. const data = JSON.parse(res.data);
  303. if (data.status === 200) {
  304. uni.showToast({
  305. title: "上传成功",
  306. icon: "success",
  307. });
  308. this.paths = data.data.src || "";
  309. } else {
  310. uni.showToast({
  311. title: "上传失败",
  312. icon: "error",
  313. });
  314. this.paths = "";
  315. }
  316. resolve(this.paths);
  317. console.log("上传成功", JSON.parse(res.data));
  318. uni.hideLoading();
  319. uni.hideToast();
  320. },
  321. fail: (err) => {
  322. console.error("上传失败", err);
  323. this.paths = "";
  324. resolve(this.paths);
  325. uni.hideLoading();
  326. uni.hideToast();
  327. },
  328. complete: () => {
  329. uni.hideLoading();
  330. uni.hideToast();
  331. },
  332. });
  333. });
  334. },
  335. },
  336. };
  337. </script>
  338. <style>
  339. @import "./editor-icon.css";
  340. .page-body {
  341. height: calc(100vh - var(--window-top) - var(--status-bar-height));
  342. }
  343. .wrapper {
  344. height: 100%;
  345. }
  346. .editor-wrapper {
  347. height: calc(100vh - var(--window-top) - var(--status-bar-height) - 140px);
  348. background: #eee;
  349. }
  350. .iconfont {
  351. display: inline-block;
  352. padding: 8px 8px;
  353. width: 24px;
  354. height: 24px;
  355. cursor: pointer;
  356. font-size: 20px;
  357. }
  358. .toolbar {
  359. box-sizing: border-box;
  360. border-bottom: 0;
  361. font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  362. }
  363. .ql-container {
  364. box-sizing: border-box;
  365. padding: 12px 15px;
  366. width: 100%;
  367. min-height: 30vh;
  368. height: 100%;
  369. margin-top: 20px;
  370. font-size: 16px;
  371. line-height: 1.5;
  372. }
  373. .ql-active {
  374. color: #06c;
  375. }
  376. </style>