index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. <template>
  2. <div class="editor">
  3. <quill-editor
  4. class="ql-editor"
  5. v-model="forms[keyWord]"
  6. :ref="myAlias"
  7. :options="editorOption"
  8. @change="onEditorChange($event)"
  9. ></quill-editor>
  10. <el-upload
  11. class="ivu-upload"
  12. :show-file-list="false"
  13. :headers="headers"
  14. :on-success="handleSuccess"
  15. accept=".jpg, .jpeg, .png"
  16. :max-size="2048"
  17. multiple
  18. action="/api-web/uploadFile"
  19. >
  20. <Button icon="ios-cloud-upload-outline"></Button>
  21. </el-upload>
  22. <el-dialog
  23. title="插入视频"
  24. width="500px"
  25. :visible.sync="dialogFormVisible"
  26. append-to-body
  27. >
  28. <el-form :model="dialogForm" ref="diologForm" :rules="dialogFormRules">
  29. <el-form-item label="封面图地址" label-width="90px">
  30. <el-upload
  31. class="avatar-uploader"
  32. style="line-height: 0; display: inline-block"
  33. action="/api-web/uploadFile"
  34. :headers="headers"
  35. :show-file-list="false"
  36. v-loading="uploadImgLoading"
  37. accept=".jpg, .jpeg, .png"
  38. :on-success="handleImgSuccess"
  39. :on-error="handleUploadImgError"
  40. :before-upload="beforeImgUpload"
  41. >
  42. <img
  43. width="300px"
  44. v-if="dialogForm.poster"
  45. :src="dialogForm.poster"
  46. class="avatar"
  47. />
  48. <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  49. </el-upload>
  50. </el-form-item>
  51. <el-form-item label="视频类型" label-width="90px">
  52. <el-radio-group v-model="formRadio">
  53. <el-radio :label="1">外部链接</el-radio>
  54. <el-radio :label="2">上传</el-radio>
  55. </el-radio-group>
  56. </el-form-item>
  57. <el-form-item
  58. v-if="formRadio == 1"
  59. label="视频地址"
  60. label-width="90px"
  61. prop="url"
  62. >
  63. <el-input
  64. v-model="dialogForm.url"
  65. style="width: 100%"
  66. autocomplete="off"
  67. ></el-input>
  68. </el-form-item>
  69. <el-form-item
  70. v-if="formRadio == 2"
  71. label="上传视频"
  72. label-width="90px"
  73. prop="videoUrl"
  74. >
  75. <el-upload
  76. class="upload-demo"
  77. style="display: inline-block"
  78. v-loading="uploadLoading"
  79. action="/api-web/uploadFile"
  80. :before-upload="beforeUpload"
  81. :on-success="handleUploadSuccess"
  82. :on-error="handleUploadError"
  83. :show-file-list="false"
  84. accept=".mp4"
  85. :file-list="fileList"
  86. :on-exceed="handleExceed"
  87. >
  88. <video
  89. style="width: 120px; height: 120px"
  90. v-if="dialogForm.videoUrl"
  91. type="video/mp4"
  92. preload="auto"
  93. :src="dialogForm.videoUrl"
  94. ></video>
  95. <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  96. </el-upload>
  97. <p class="imageSize">只能上传mp4文件, 且不超过100M</p>
  98. </el-form-item>
  99. </el-form>
  100. <div slot="footer" class="dialog-footer">
  101. <el-button @click="dialogFormVisible = false">取 消</el-button>
  102. <el-button type="primary" @click="onVideoComfirm('diologForm')"
  103. >确 定</el-button
  104. >
  105. </div>
  106. </el-dialog>
  107. </div>
  108. </template>
  109. <script>
  110. import { getToken } from "@/utils/auth";
  111. import "quill/dist/quill.core.css";
  112. import "quill/dist/quill.snow.css";
  113. import "quill/dist/quill.bubble.css";
  114. import Quill from "quill";
  115. import { quillEditor } from "vue-quill-editor";
  116. // 工具栏配置
  117. const toolbarOptions = [
  118. ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
  119. ["blockquote", "code-block"], // 引用 代码块
  120. [{ header: 1 }, { header: 2 }], // 1、2 级标题
  121. [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
  122. [{ script: "sub" }, { script: "super" }], // 上标/下标
  123. [{ indent: "-1" }, { indent: "+1" }], // 缩进
  124. // [{'direction': 'rtl'}], // 文本方向
  125. [{ size: ["small", false, "large", "huge"] }], // 字体大小
  126. [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
  127. [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
  128. [{ font: [] }], // 字体种类
  129. [{ align: [] }], // 对齐方式
  130. ["clean"], // 清除文本格式 // 链接、图片、视频 , "video"
  131. ["link", "image", "video"], // 链接、图片、视频
  132. ];
  133. // 标题
  134. const titleConfig = {
  135. "ql-bold": "加粗",
  136. "ql-color": "颜色",
  137. "ql-font": "字体",
  138. "ql-code": "插入代码",
  139. "ql-italic": "斜体",
  140. // 'ql-link': '添加链接',
  141. "ql-background": "背景颜色",
  142. "ql-size": "字体大小",
  143. "ql-strike": "删除线",
  144. "ql-script": "上标/下标",
  145. "ql-underline": "下划线",
  146. "ql-blockquote": "引用",
  147. "ql-header": "标题",
  148. "ql-indent": "缩进",
  149. "ql-list": "列表",
  150. "ql-align": "文本对齐",
  151. "ql-direction": "文本方向",
  152. "ql-code-block": "代码块",
  153. "ql-formula": "公式",
  154. "ql-image": "图片",
  155. "ql-video": "视频",
  156. "ql-clean": "清除字体样式",
  157. "ql-upload": "文件",
  158. };
  159. // 这里引入修改过的video模块并注册
  160. import Video from "@/views/quill/video.js";
  161. import dayjs from "dayjs";
  162. Quill.register(Video, true);
  163. let that;
  164. export default {
  165. // props: ["form",'key'],
  166. props: {
  167. keyWord: {
  168. type: String,
  169. default: "content",
  170. },
  171. form: {
  172. type: Object,
  173. },
  174. alias: {
  175. type: String,
  176. default: "myQuillEditor",
  177. },
  178. },
  179. name: "editor",
  180. components: { quillEditor },
  181. data() {
  182. return {
  183. content: null,
  184. headers: {
  185. Authorization: getToken(),
  186. },
  187. dialogFormVisible: false,
  188. dialogForm: {
  189. poster: null,
  190. url: null,
  191. videoUrl: null,
  192. },
  193. uploadLoading: false,
  194. uploadImgLoading: false,
  195. fileList: [],
  196. dialogFormRules: {
  197. url: [{ required: true, message: "请输入视频地址", trigger: "blur" }],
  198. videoUrl: [{ required: true, message: "请上传视频", trigger: "blur" }],
  199. },
  200. formRadio: 1,
  201. editorOption: {
  202. placeholder: "请输入内容",
  203. modules: {
  204. toolbar: {
  205. container: toolbarOptions,
  206. handlers: {
  207. image: function (value) {
  208. if (value) {
  209. // 调用iview图片上传
  210. document.querySelector(".ivu-upload .el-upload").click();
  211. } else {
  212. this.quill.format("image", false);
  213. }
  214. },
  215. video:(value)=> {
  216. if (value) {
  217. this.dialogFormVisible = true;
  218. let editor = this.$refs[this.myAlias].quill;
  219. // 光标所在位置
  220. console.log(editor, editor.getSelection(), this.myAlias);
  221. this.editorIndex = editor.getSelection()?.index || 0;
  222. } else {
  223. this.quill.format("image", false);
  224. }
  225. },
  226. },
  227. },
  228. },
  229. },
  230. };
  231. },
  232. created() {
  233. },
  234. mounted() {
  235. // console.log(this.form);\
  236. },
  237. methods: {
  238. onEditorChange({ quill, html, text }) {
  239. this.form[this.keyWord] = html;
  240. },
  241. onVideoComfirm(formName) {
  242. this.$refs[formName].validate((valid) => {
  243. if (valid) {
  244. let dialogForm = this.dialogForm;
  245. // 获取富文本组件实例
  246. let quill = this.editor;
  247. console.log(quill, this.keyWord);
  248. // 插入图片,res为服务器返回的图片链接地址
  249. const params = {
  250. poster: dialogForm.poster,
  251. url: this.formRadio == 1 ? dialogForm.url : dialogForm.videoUrl,
  252. };
  253. quill.insertEmbed(this.editorIndex, "video", params);
  254. // 调整光标到最后
  255. console.log(this.editorIndex);
  256. quill.setSelection(this.editorIndex + 1, { preload: false });
  257. this.dialogFormVisible = false;
  258. this.dialogForm = {
  259. poster: null,
  260. url: null,
  261. videoUrl: null,
  262. };
  263. } else {
  264. return false;
  265. }
  266. });
  267. },
  268. handleSuccess(res) {
  269. // 获取富文本组件实例
  270. let quill = this.editor;
  271. // 如果上传成功
  272. if (res.code) {
  273. // 获取光标所在位置
  274. let length = quill.getSelection().index;
  275. // 插入图片,res为服务器返回的图片链接地址
  276. quill.insertEmbed(length, "image", res.data.url);
  277. // 调整光标到最后
  278. quill.setSelection(length + 1);
  279. } else {
  280. // 提示信息,需引入Message
  281. this.$message.error("图片插入失败");
  282. }
  283. },
  284. addQuillTitle() {
  285. const oToolBar = document.querySelector(".ql-toolbar"),
  286. aButton = oToolBar.querySelectorAll("button"),
  287. aSelect = oToolBar.querySelectorAll("select");
  288. aButton.forEach(function (item) {
  289. if (item.className === "ql-script") {
  290. item.value === "sub" ? (item.title = "下标") : (item.title = "上标");
  291. } else if (item.className === "ql-indent") {
  292. item.value === "+1"
  293. ? (item.title = "向右缩进")
  294. : (item.title = "向左缩进");
  295. } else {
  296. item.title = titleConfig[item.classList[0]];
  297. }
  298. });
  299. aSelect.forEach(function (item) {
  300. item.parentNode.title = titleConfig[item.classList[0]];
  301. });
  302. },
  303. handleUploadImgError(file) {
  304. this.uploadImgLoading = false;
  305. this.$message.error("上传失败");
  306. },
  307. handleImgSuccess(res, file) {
  308. this.uploadImgLoading = false;
  309. this.dialogForm.poster = res.data.url;
  310. },
  311. beforeImgUpload(file) {
  312. const imageType = {
  313. "image/png": true,
  314. "image/jpeg": true,
  315. };
  316. const isImage = imageType[file.type];
  317. const isLt2M = file.size / 1024 / 1024 < 2;
  318. isImage, isLt2M;
  319. if (!isImage) {
  320. this.$message.error("只能上传图片格式!");
  321. }
  322. if (!isLt2M) {
  323. this.$message.error("上传图片大小不能超过 2MB!");
  324. }
  325. if (isImage && isLt2M) {
  326. this.uploadImgLoading = true;
  327. }
  328. return isImage && isLt2M;
  329. },
  330. handleAvatarSuccess(res, file) {
  331. this.form.coverImage = res.data.url;
  332. },
  333. beforeAvatarUpload(file) {
  334. const imageType = {
  335. "image/png": true,
  336. "image/jpeg": true,
  337. };
  338. const isImage = imageType[file.type];
  339. const isLt2M = file.size / 1024 / 1024 < 2;
  340. if (!isImage) {
  341. this.$message.error("只能上传图片格式!");
  342. }
  343. if (!isLt2M) {
  344. this.$message.error("上传图片大小不能超过 2M!");
  345. }
  346. return isImage && isLt2M;
  347. },
  348. beforeUpload(file) {
  349. // const isJPG = file.type === 'image/jpeg';
  350. const isLt2M = file.size / 1024 / 1024 < 100;
  351. // if (!isJPG) {
  352. // this.$message.error('上传头像图片只能是 JPG 格式!');
  353. // }
  354. if (!isLt2M) {
  355. this.$message.error("上传视频大小不能超过 100MB!");
  356. }
  357. this.uploadLoading = true;
  358. return isLt2M;
  359. },
  360. handleUploadError(file) {
  361. this.uploadLoading = false;
  362. this.$message.error("上传视频失败");
  363. },
  364. handleUploadSuccess(file, fileList) {
  365. this.uploadLoading = false;
  366. this.$message.success("上传视频成功");
  367. this.dialogForm.videoUrl = file.data.url;
  368. },
  369. handleExceed(files, fileList) {
  370. this.$message.error("您已上传过视频");
  371. },
  372. },
  373. computed: {
  374. editor() {
  375. return this.$refs[this.myAlias].quill;
  376. },
  377. forms() {
  378. return this.form;
  379. },
  380. myAlias() {
  381. return this.alias;
  382. },
  383. },
  384. };
  385. </script>
  386. <style lang='scss' scoped>
  387. /deep/.ql-editor {
  388. min-height: 250px;
  389. padding: 0;
  390. }
  391. /deep/.ql-container .ql-editor {
  392. max-height: 500px;
  393. }
  394. </style>