index.vue 15 KB

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