• vue2 quill 视频上传 ,基于ruoyi vue,oss


    包含两种上传方式,第一种点开弹新页面可选url和点击上传。本文中是第二种,自己拼的。像点击上传图片一样,直接传video文件,原创不易,纯纯踩坑;

    因为现阶段能搜索到的内容,99.5%都是一样的内容,无法满足需求。

    我的需求是在ruoyi-vue里面用quill富文本,但里面一部分功能没有。

    但作者给了自定义插件通道。

    如果根据我的代码去写,上传完视频 就拿不到光标,只能拿到最后一个字的长度,因此上传视频被提成bug。

    自猜:点击完tag的上传视频 是打开个子页面,父子页面传值可能存在问题。

    无奈之下只能自己手拼。看代码吧。如有问题请及时指正。

    所有代码在同级目录下

    贴代码

    index.vue

    1. <template>
    2. <div>
    3. <el-upload
    4. :action="uploadUrl"
    5. :before-upload="handleBeforeUpload"
    6. :http-request="uploadURL"
    7. name="file"
    8. :show-file-list="false"
    9. :headers="headers"
    10. style="display: none"
    11. ref="upload"
    12. v-if="this.type == 'url'"
    13. >
    14. </el-upload>
    15. <div class="editor" ref="editor" :style="styles"></div>
    16. <!--视频上传 begin -->
    17. <!-- <el-tab-pane label="添加视频链接" name="second">
    18. <el-input v-model="videoDialog.videoLink" placeholder="请输入视频链接" clearable></el-input>
    19. <el-button type="primary" size="small" style="margin: 20px 0px 0px 0px" @click="addVideoLink(videoDialog.videoLink)">添加 </el-button>
    20. </el-tab-pane> -->
    21. <!--<div>
    22. <el-dialog :close-on-click-modal="false" width="50%" style="margin-top: 1px" title="视频上传" :visible.sync="videoDialog.show" append-to-body ref="dialogRef">
    23. <el-tabs v-model="videoDialog.activeName">
    24. <el-tab-pane label="本地视频上传" >
    25. <el-upload
    26. v-loading="uploading"
    27. style="text-align: center"
    28. drag
    29. action=""
    30. accept="video/*"
    31. :http-request="uploadVideoURL"
    32. :multiple="false"
    33. :before-upload="videoHandleBeforeUpload"
    34. v-if="this.type == 'video'"
    35. >
    36. <i class="el-icon-upload"></i>
    37. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    38. </el-upload>
    39. </el-tab-pane>
    40. </el-tabs>
    41. </el-dialog>
    42. </div> -->
    43. <!-- :http-request="uploadVideoURL"
    44. :multiple="false"
    45. :before-upload="videoHandleBeforeUpload"
    46. style="display: none" -->
    47. <el-upload
    48. name="file"
    49. :show-file-list="false"
    50. :headers="headers"
    51. style="display: none"
    52. ref="uploadVideo"
    53. :http-request="uploadVideoURL"
    54. :before-upload="videoHandleBeforeUpload"
    55. action="uploadVideoURL"
    56. :multiple="false"
    57. v-if="this.video == 'r'"
    58. >
    59. <i class="el-icon-upload"></i>
    60. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    61. </el-upload>
    62. <!--视频上传 end -->
    63. </div>
    64. </template>
    65. <script>
    66. import Quill from "quill";
    67. import "quill/dist/quill.core.css";
    68. import "quill/dist/quill.snow.css";
    69. import "quill/dist/quill.bubble.css";
    70. import { getToken } from "@/utils/auth";
    71. import {client, getFileNameUUID} from '@/utils/alioss';
    72. let fontFamily = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'Times-New-Roman', 'sans-serif'];
    73. Quill.imports['formats/font'].whitelist = fontFamily;
    74. Quill.register(Quill.imports['formats/font']);
    75. let fontSize = ['10px', '11px','12px', '14px', '15px', '16px', '17px', '18px', '20px', '24px', '36px']
    76. Quill.imports['attributors/style/size'].whitelist = fontSize;
    77. Quill.register(Quill.imports['attributors/style/size']);
    78. import Video from "./video.js";
    79. // import Buffer from "vue-buffer";
    80. Quill.register(Video, true);
    81. // 行高
    82. import { lineHeightStyle } from './lineHeight.js';
    83. Quill.register({ 'formats/lineHeight': lineHeightStyle }, true);
    84. // 行间距
    85. import { letterSpacingStyle } from './letterSpacing.js';
    86. Quill.register({ 'formats/letterSpacing': letterSpacingStyle }, true);
    87. Quill.register({ 'formats/lineHeight': lineHeightStyle }, true);
    88. export default {
    89. name: "Editor",
    90. props: {
    91. /* 编辑器的内容 */
    92. value: {
    93. type: String,
    94. default: "",
    95. },
    96. /* 高度 */
    97. height: {
    98. type: Number,
    99. default: null,
    100. },
    101. /* 最小高度 */
    102. minHeight: {
    103. type: Number,
    104. default: null,
    105. },
    106. /* 只读 */
    107. readOnly: {
    108. type: Boolean,
    109. default: false,
    110. },
    111. // 上传文件大小限制(MB)
    112. fileSize: {
    113. type: Number,
    114. default: 5,
    115. },
    116. /* 类型(base64格式、url格式) */
    117. type: {
    118. type: String,
    119. default: "url",
    120. },
    121. video: {
    122. type: String,
    123. default: "r",
    124. }
    125. },
    126. data() {
    127. return {
    128. aliyun :{},
    129. uploading : false,
    130. videoDialog: {show: false,activeName: null,videoLink:null},
    131. uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
    132. headers: {
    133. Authorization: "Bearer " + getToken()
    134. },
    135. Quill: null,
    136. currentValue: "",
    137. options: {
    138. theme: "snow",
    139. bounds: document.body,
    140. debug: "warn",
    141. modules: {
    142. // 工具栏配置
    143. toolbar: {
    144. handlers: {
    145. // lineHeight: (value) => {
    146. // if (value) {
    147. // let quill = this.$refs.myQuillEditor.quill;
    148. // quill.format("lineHeight", value);
    149. // }
    150. // },
    151. // lineHeight :['initial', '1', '1.5', '1.75', '2', '3', '4', '5'],
    152. video: (value) => {
    153. this.videoDialog.show = true;
    154. },
    155. },
    156. container :
    157. [
    158. ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
    159. ["blockquote", "code-block"], // 引用 代码块
    160. [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
    161. [{ indent: "-1" }, { indent: "+1" }], // 缩进
    162. // [{ size: ["small", false, "large", "huge"] }], // 字体大小
    163. [{ size:fontSize }], // 字体大小
    164. // [{ size: [ "ten",false,"eleven", "twelve", "thirteen",
    165. // "fourteen", "fifteen", "sixteen", "seventeen",
    166. // "eighteen", "nineteen", "twenty"]}],
    167. [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
    168. [{ color: ['aqua', 'black', 'blue', 'brown', 'cyan', 'gold', 'gray', 'green', 'indigo', 'lavender', 'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'pink', 'purple', 'red', 'silver', 'teal', 'violet', 'white', 'yellow'] },
    169. { background: ['aqua', 'black', 'blue', 'brown', 'cyan', 'gold', 'gray', 'green', 'indigo', 'lavender', 'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'pink', 'purple', 'red', 'silver', 'teal', 'violet', 'white', 'yellow']
    170. }], // 字体颜色、字体背景颜色
    171. [{ align: [] }], // 对齐方式
    172. ["clean"], // 清除文本格式
    173. ["image", "video"], // 链接、图片、视频
    174. [{lineheight: ['initial', '1', '2', '3', '4', '5','20'] }],
    175. [{ letterSpacing: [ 'initial','2px','4px', '6px','8px','10px', '12px', '14px', '16px'],},], //行间距
    176. // ["link", "image", "video"] // 链接、图片、视频
    177. ],
    178. }
    179. },
    180. placeholder: "请输入内容",
    181. readOnly: this.readOnly,
    182. },
    183. };
    184. },
    185. computed: {
    186. styles() {
    187. let style = {};
    188. if (this.minHeight) {
    189. style.minHeight = `${this.minHeight}px`;
    190. }
    191. if (this.height) {
    192. style.height = `${this.height}px`;
    193. }
    194. return style;
    195. },
    196. },
    197. watch: {
    198. value: {
    199. handler(val) {
    200. if (val !== this.currentValue) {
    201. this.currentValue = val === null ? "" : val;
    202. if (this.Quill) {
    203. this.Quill.pasteHTML(this.currentValue);
    204. }
    205. }
    206. },
    207. immediate: true,
    208. },
    209. },
    210. mounted() {
    211. this.init();
    212. },
    213. methods: {
    214. // 上传文件之前
    215. videoHandleBeforeUpload(file) {
    216. const isJpgOrPng =
    217. file.type === 'video/ogg' ||
    218. file.type === 'video/flv' ||
    219. file.type === 'video/avi' ||
    220. file.type === 'video/wmv' ||
    221. file.type === 'video/mov' ||
    222. file.type === 'video/mp4'
    223. if (!isJpgOrPng) {
    224. this.$message.error('只能上传图片/视频!')
    225. return
    226. }
    227. },
    228. uploadURL(file) {
    229. //注意哦,这里指定文件夹'image/',尝试过写在配置文件,但是各种不行,写在这里就可以
    230. var fileName = 'imgs/'+'editor' + getFileNameUUID() + "." + file.file.name.replace(/.+\./, "");//'.jpg';
    231. client().multipartUpload(fileName, file.file,{
    232. progress: function(percentage, cpt) {
    233. console.log('打印进度',percentage)
    234. }
    235. }).then((res)=>{
    236. //此处赋值,是相当于上传成功之后,手动拼接服务器地址和文件名
    237. let quill = this.Quill;
    238. let length = quill.getSelection().index;
    239. console.log("quill.getSelection().index===",length);
    240. console.log(quill.getSelection());
    241. // 插入图片 res.url为服务器返回的图片地址
    242. quill.insertEmbed(length, "image", 'https://oss-cn-beijing.aliyuncs.com/' + fileName);
    243. // 调整光标到最后
    244. quill.setSelection(length + 1);
    245. })
    246. },
    247. init() {
    248. const editor = this.$refs.editor;
    249. this.Quill = new Quill(editor, this.options);
    250. // 如果设置了上传地址则自定义图片上传事件
    251. if (this.type == 'url') {
    252. let toolbar = this.Quill.getModule("toolbar");
    253. toolbar.addHandler("image", (value) => {
    254. this.uploadType = "image";
    255. if (value) {
    256. this.$refs.upload.$children[0].$refs.input.click();
    257. } else {
    258. this.quill.format("image", false);
    259. }
    260. });
    261. }
    262. //todo begin
    263. console.log("this.video",this.video);
    264. if (this.video == 'r') {
    265. let toolbar = this.Quill.getModule("toolbar");
    266. toolbar.addHandler("video", (value) => {
    267. this.uploadType = "video";
    268. console.log("value",value);
    269. if (value) {
    270. this.$refs.uploadVideo.$children[0].$refs.input.click();
    271. } else {
    272. this.quill.format("video", false);
    273. }
    274. });
    275. }
    276. //todo end
    277. this.Quill.pasteHTML(this.currentValue);
    278. this.Quill.on("text-change", (delta, oldDelta, source) => {
    279. const html = this.$refs.editor.children[0].innerHTML;
    280. const text = this.Quill.getText();
    281. const quill = this.Quill;
    282. this.currentValue = html;
    283. this.$emit("input", html);
    284. this.$emit("on-change", { html, text, quill });
    285. });
    286. this.Quill.on("text-change", (delta, oldDelta, source) => {
    287. this.$emit("on-text-change", delta, oldDelta, source);
    288. });
    289. this.Quill.on("selection-change", (range, oldRange, source) => {
    290. this.$emit("on-selection-change", range, oldRange, source);
    291. });
    292. this.Quill.on("editor-change", (eventName, ...args) => {
    293. this.$emit("on-editor-change", eventName, ...args);
    294. });
    295. },
    296. handleBeforeUpload(file) {
    297. const isJPEG = file.name.split('.')[1] === 'jpeg';
    298. const isJPG = file.name.split('.')[1] === 'jpg';
    299. const isPNG = file.name.split('.')[1] === 'png';
    300. const isWEBP = file.name.split('.')[1] === 'webp';
    301. const isGIF = file.name.split('.')[1] === 'gif';
    302. const isLt500K = file.size / 1024 / 1024 / 1024 / 1024 < 4;
    303. if (!isJPG && !isJPEG && !isPNG && !isWEBP && !isGIF) {
    304. this.$message.error('上传图片只能是 JPEG/JPG/PNG 格式!');
    305. }
    306. if (!isLt500K) {
    307. this.$message.error('单张图片大小不能超过 4mb!');
    308. }
    309. return (isJPEG || isJPG || isPNG || isWEBP || isGIF) && isLt500K;
    310. },
    311. uploadVideoURL(file) {
    312. //注意哦,这里指定文件夹'image/',尝试过写在配置文件,但是各种不行,写在这里就可以
    313. var fileName = 'imgs/'+'editor' + getFileNameUUID() + "." + file.file.name.replace(/.+\./, "");//'.jpg';
    314. client().multipartUpload(fileName, file.file,{
    315. progress: function(percentage, cpt) {
    316. // console.log('打印进度',percentage)
    317. }
    318. }).then((res)=>{
    319. //此处赋值,是相当于上传成功之后,手动拼接服务器地址和文件名
    320. this.videoDialog.show = false;
    321. // console.log("this.$parent.editor",this.$parent.editor);
    322. // let editor = this.$refs['editor'];
    323. // this.init();
    324. let quill = this.Quill;
    325. let lengthSelection = quill.getSelection().index;
    326. console.log("quill.getSelection().index===",lengthSelection);
    327. // console.log("this.$refs.editer.quill.selection.savedRange.index",this.$refs.editer.quill.selection.savedRange.index);
    328. console.log("quill.getLength()",quill.getLength());
    329. // 获取富文本
    330. // let range = quill.getSelection().index;
    331. // 获取光标位置:当编辑器中没有输入文本时,这里获取到的 range 为 null
    332. // let index = range ? range.index : 0
    333. // let quill = this.Quill;
    334. var length = quill.getLength();
    335. if(quill.getSelection()){
    336. length = quill.getSelection().index;
    337. // console.log("quill.getSelection().index===",length);
    338. }
    339. // console.log(quill.getSelection());
    340. let resp = 'https://oss-cn-beijing.aliyuncs.com/' + fileName;
    341. // 插入
    342. quill.insertEmbed(length, "video", resp);
    343. // 调整光标到最后
    344. quill.setSelection(length + 1);
    345. this.$forceUpdate();
    346. })
    347. },
    348. },
    349. };
    350. </script>
    351. <style>
    352. .editor, .ql-toolbar {
    353. white-space: pre-wrap !important;
    354. line-height: normal !important;
    355. }
    356. .quill-img {
    357. display: none;
    358. }
    359. .ql-snow .ql-tooltip[data-mode="link"]::before {
    360. content: "请输入链接地址:";
    361. }
    362. .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
    363. border-right: 0px;
    364. content: "保存";
    365. padding-right: 0px;
    366. }
    367. .ql-snow .ql-tooltip[data-mode="video"]::before {
    368. content: "请输入视频地址:";
    369. }
    370. .ql-snow .ql-picker.ql-size .ql-picker-label::before,
    371. .ql-snow .ql-picker.ql-size .ql-picker-item::before {
    372. content: "14px";
    373. }
    374. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
    375. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
    376. content: "10px";
    377. }
    378. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
    379. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
    380. content: "18px";
    381. }
    382. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
    383. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
    384. content: "32px";
    385. }
    386. /*todo begin*/
    387. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='10px']::before,
    388. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='10px']::before {
    389. content: '10px';
    390. }
    391. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='11px']::before,
    392. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='11px']::before {
    393. content: '11px';
    394. }
    395. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='12px']::before,
    396. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='12px']::before {
    397. content: '12px';
    398. }
    399. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='14px']::before,
    400. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='14px']::before {
    401. content: '14px';
    402. }
    403. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='15px']::before,
    404. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='15px']::before {
    405. content: '15px';
    406. }
    407. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='16px']::before,
    408. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='16px']::before {
    409. content: '16px';
    410. }
    411. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='17px']::before,
    412. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='17px']::before {
    413. content: '17px';
    414. }
    415. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='18px']::before,
    416. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='18px']::before {
    417. content: '18px';
    418. }
    419. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='20px']::before,
    420. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='20px']::before {
    421. content: '20px';
    422. }
    423. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='24px']::before,
    424. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='24px']::before {
    425. content: '24px';
    426. }
    427. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='36px']::before,
    428. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='36px']::before {
    429. content: '36px';
    430. }
    431. /*todo end*/
    432. .ql-snow .ql-picker.ql-header .ql-picker-label::before,
    433. .ql-snow .ql-picker.ql-header .ql-picker-item::before {
    434. content: "文本";
    435. }
    436. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
    437. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
    438. content: "标题1";
    439. }
    440. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
    441. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
    442. content: "标题2";
    443. }
    444. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
    445. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
    446. content: "标题3";
    447. }
    448. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
    449. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
    450. content: "标题4";
    451. }
    452. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
    453. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
    454. content: "标题5";
    455. }
    456. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
    457. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
    458. content: "标题6";
    459. }
    460. .ql-snow .ql-picker.ql-font .ql-picker-label::before,
    461. .ql-snow .ql-picker.ql-font .ql-picker-item::before {
    462. content: "标准字体";
    463. }
    464. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
    465. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
    466. content: "衬线字体";
    467. }
    468. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
    469. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
    470. content: "等宽字体";
    471. }
    472. </style>
    473. <style>
    474. /* .ql-snow .ql-picker.ql-lineheight .ql-picker-label::before,
    475. .ql-snow .ql-picker.ql-lineheight .ql-picker-item::before {
    476. content: '行高';
    477. }
    478. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="行高"]::before,
    479. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='initial']::before {
    480. content: '默认';
    481. }
    482. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1"]::before,
    483. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1']::before {
    484. content: '1px';
    485. }
    486. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1.5"]::before,
    487. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.5']::before {
    488. content: '1.5px';
    489. }
    490. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1.75"]::before,
    491. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='1.75']::before {
    492. content: '1.75px';
    493. }
    494. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="2"]::before,
    495. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='2']::before {
    496. content: '2px';
    497. }
    498. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="3"]::before,
    499. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='3']::before {
    500. content: '3px';
    501. }
    502. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="4"]::before,
    503. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='4']::before {
    504. content: '4px';
    505. }
    506. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="5"]::before,
    507. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value='5']::before {
    508. content: '5px';
    509. }
    510. .ql-snow .ql-picker.ql-lineheight {
    511. width: 70px;
    512. }*/
    513. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label::before,
    514. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item::before {
    515. content: '字符间距';
    516. }
    517. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="字符间距"]::before,
    518. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='initial']::before {
    519. content: '默认';
    520. }
    521. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="2px"]::before,
    522. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='2px']::before {
    523. content: '2px';
    524. }
    525. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="4px"]::before,
    526. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='4px']::before {
    527. content: '4px';
    528. }
    529. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="6px"]::before,
    530. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='6px']::before {
    531. content: '6px';
    532. }
    533. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="8px"]::before,
    534. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='8px']::before {
    535. content: '8px';
    536. }
    537. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="10px"]::before,
    538. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='10px']::before {
    539. content: '10px';
    540. }
    541. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="12px"]::before,
    542. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='12px']::before {
    543. content: '12px';
    544. }
    545. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="14px"]::before,
    546. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='14px']::before {
    547. content: '14px';
    548. }
    549. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-label[data-value="16px"]::before,
    550. .ql-snow .ql-picker.ql-letterSpacing .ql-picker-item[data-value='16px']::before {
    551. content: '16px';
    552. }
    553. .ql-snow .ql-picker.ql-letterSpacing {
    554. width: 90px;
    555. }
    556. </style>
    557. <style>
    558. .ql-snow .ql-picker.ql-lineheight .ql-picker-label::before,
    559. .ql-snow .ql-picker.ql-lineheight .ql-picker-item::before {
    560. content: '行高';
    561. }
    562. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="1"]::before,
    563. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="1"]::before {
    564. content: "1";
    565. }
    566. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="2"]::before,
    567. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="2"]::before {
    568. content: "2";
    569. }
    570. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="3"]::before,
    571. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="3"]::before {
    572. content: "3";
    573. }
    574. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="4"]::before,
    575. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="4"]::before {
    576. content: "4";
    577. }
    578. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="5"]::before,
    579. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="5"]::before {
    580. content: "5";
    581. }
    582. .ql-snow .ql-picker.ql-lineheight .ql-picker-label[data-value="20"]::before,
    583. .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="20"]::before {
    584. content: "20";
    585. }
    586. .ql-snow .ql-picker.ql-lineheight {
    587. width: 70px;
    588. }
    589. </style>

    letterSpacing.js

    1. import Quill from 'quill'
    2. let Parchment = Quill.import('parchment')
    3. // 字符间距
    4. class letterSpacingAttributor extends Parchment.Attributor.Style {}
    5. const letterSpacingStyle = new letterSpacingAttributor(
    6. 'letter-spacing',
    7. 'letterSpacing',
    8. {
    9. scope: Parchment.Scope.INLINE,
    10. whitelist: [
    11. 'initial',
    12. '2px',
    13. '4px',
    14. '6px',
    15. '8px',
    16. '10px',
    17. '12px',
    18. '14px',
    19. '16px',
    20. ],
    21. }
    22. )
    23. export { letterSpacingStyle }

    lineHeight.js

    1. import Quill from 'quill'
    2. let Parchment = Quill.import('parchment')
    3. // 行高
    4. class lineHeightAttributor extends Parchment.Attributor.Style {}
    5. const lineHeightStyle = new lineHeightAttributor(
    6. // 'line-height', 'lineHeight',
    7. // 'lineheight', 'ql-lineheight',
    8. 'lineheight', 'line-height',
    9. // 'line-height','lineheight',
    10. {
    11. scope: Parchment.Scope.INLINE,
    12. whitelist: ['initial', '1', '2', '3', '4', '5','20'],
    13. })
    14. export { lineHeightStyle }

    quill-title.js

    1. const titleConfig = {
    2. 'ql-bold': '加粗',
    3. 'ql-color': '颜色',
    4. 'ql-font': '字体',
    5. 'ql-code': '插入代码',
    6. 'ql-italic': '斜体',
    7. 'ql-link': '添加链接',
    8. 'ql-background': '背景颜色',
    9. 'ql-size': '字体大小',
    10. 'ql-strike': '删除线',
    11. 'ql-script': '上标/下标',
    12. 'ql-underline': '下划线',
    13. 'ql-blockquote': '引用',
    14. 'ql-header': '标题',
    15. 'ql-indent': '缩进',
    16. 'ql-list': '列表',
    17. 'ql-align': '文本对齐',
    18. 'ql-direction': '文本方向',
    19. 'ql-code-block': '代码块',
    20. 'ql-formula': '公式',
    21. 'ql-image': '图片',
    22. 'ql-video': '视频',
    23. 'ql-clean': '清除字体样式',
    24. 'ql-lineheight': '行高',
    25. 'ql-letterSpacing': '字符间距',
    26. }
    27. export function addQuillTitle() {
    28. const oToolBar = document.querySelector('.ql-toolbar'),
    29. aButton = oToolBar.querySelectorAll('button'),
    30. aSelect = oToolBar.querySelectorAll('select')
    31. aButton.forEach(function (item) {
    32. if (item.className === 'ql-script') {
    33. item.value === 'sub' ? (item.title = '下标') : (item.title = '上标')
    34. } else if (item.className === 'ql-indent') {
    35. item.value === '+1'
    36. ? (item.title = '向右缩进')
    37. : (item.title = '向左缩进')
    38. } else {
    39. item.title = titleConfig[item.classList[0]]
    40. }
    41. })
    42. aSelect.forEach(function (item) {
    43. item.parentNode.title = titleConfig[item.classList[0]]
    44. })
    45. }

    video.js

    1. import { Quill } from "vue-quill-editor";
    2. // 源码中是import直接导入,这里要用Quill.import引入
    3. const BlockEmbed = Quill.import("blots/block/embed");
    4. const Link = Quill.import("formats/link");
    5. const ATTRIBUTES = ["height", "width"];
    6. class Video extends BlockEmbed {
    7. static create(value) {
    8. const node = super.create(value);
    9. // 添加video标签所需的属性
    10. node.setAttribute("controls", "controls");
    11. node.setAttribute("type", "video/mp4");
    12. node.setAttribute("src", this.sanitize(value));
    13. //为了兼容 iOS 设备上,显示海报图(视频封面)
    14. node.setAttribute("preload", "metadata");
    15. // node.setAttribute("poster", value.poster);
    16. node.setAttribute("webkit-playsinline", "true");
    17. return node;
    18. }
    19. static formats(domNode) {
    20. return ATTRIBUTES.reduce((formats, attribute) => {
    21. if (domNode.hasAttribute(attribute)) {
    22. formats[attribute] = domNode.getAttribute(attribute);
    23. }
    24. return formats;
    25. }, {});
    26. }
    27. static sanitize(url) {
    28. return Link.sanitize(url); // eslint-disable-line import/no-named-as-default-member
    29. }
    30. static value(domNode) {
    31. return domNode.getAttribute("src");
    32. // return {
    33. // url: domNode.getAttribute('src'),
    34. // poster: domNode.getAttribute('poster')
    35. // }
    36. }
    37. format(name, value) {
    38. if (ATTRIBUTES.indexOf(name) > -1) {
    39. if (value) {
    40. this.domNode.setAttribute(name, value);
    41. } else {
    42. this.domNode.removeAttribute(name);
    43. }
    44. } else {
    45. super.format(name, value);
    46. }
    47. }
    48. html() {
    49. const { video } = this.value();
    50. return `<a href="${video}">${video}</a>`;
    51. }
    52. }
    53. Video.blotName = "video";
    54. Video.className = "ql-video";
    55. Video.tagName = "video"; // 用video标签替换iframe
    56. export default Video;

  • 相关阅读:
    vue3 如何国际化
    【编程题】【Scratch三级】2021.09 计算平均分
    java70-GUL图形用户界面初识
    从阿里云容器攻防矩阵&API安全生命周期,看如何构建金融安全云原生平台
    springboot整合xxl-job分布式定时任务【图文完整版】
    255-261BFC,媒体的类型,媒体的特性,浏览器前缀,媒体查询,逻辑操作符,
    49 多个 classloader 加载的同类限定名的Class 在 jhat 中显示不全
    部署kubernetes-v1.25.3(k8s)- 基于containerd容器运行时
    nginx配置http负载均衡转发
    CSS calc() 使用指南
  • 原文地址:https://blog.csdn.net/yangziqi098/article/details/134046247