public.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  1. import $api from '@/api'
  2. import store from '@/store'
  3. import SparkMD5 from "spark-md5";
  4. import FileSaver from "file-saver";
  5. import JSZip from "jszip";
  6. import QRCode from 'qrcodejs2'
  7. import JsPDF from 'jspdf'
  8. import 'jspdf-autotable'
  9. import domtoimage from '@/utils/dom_to_image';
  10. import excel from '@/utils/excel.js'
  11. import {
  12. app
  13. } from '@/boot-app.js'
  14. import {
  15. Message
  16. } from 'view-design'
  17. /**
  18. * 校验blob授权是否过期
  19. * */
  20. function checkSas(sasTs) {
  21. let curTimestamp = (new Date()).getTime()
  22. if (curTimestamp >= sasTs) {
  23. return true
  24. } else {
  25. return false
  26. }
  27. }
  28. export default {
  29. /* vueScroll全局配置工具 */
  30. vueScrollOpt: {
  31. mode: 'native',
  32. // 设置 vuescroll的大小类型, 可选的有percent, number.
  33. // 设置为percent会把 vuescroll 的 height 和 width 设置成100%,
  34. // 设置成number的话 vuescroll 会自动计算父元素的大小,并将height和width设置成对应的数值。
  35. // 提示:如果父元素的尺寸为百分比大小时建议设置成number,如果父元素大小为一个固定的px的值,那么设置为百分比比较合适一些。
  36. sizeStrategy: 'percent',
  37. // 是否开启监听 dom resize
  38. detectResize: false,
  39. // 下拉刷新相关(slide mode)
  40. pullRefresh: {
  41. enable: false,
  42. // 下拉刷新的提示
  43. tips: {
  44. deactive: 'Pull to Refresh',
  45. active: 'Release to Refresh',
  46. start: 'Refreshing...',
  47. beforeDeactive: 'Refresh Successfully!'
  48. }
  49. },
  50. // 上推加载相关
  51. pushLoad: {
  52. enable: false,
  53. tips: {
  54. deactive: 'Push to Load',
  55. active: 'Release to Load',
  56. start: 'Loading...',
  57. beforeDeactive: 'Load Successfully!'
  58. },
  59. auto: false,
  60. autoLoadDistance: 0
  61. },
  62. paging: false,
  63. zooming: true,
  64. // 快照
  65. snapping: {
  66. enable: false,
  67. width: 100,
  68. height: 100
  69. },
  70. /* shipped scroll options */
  71. scroller: {
  72. /*
  73. 允许滚动出边界
  74. true 或者 false 或者一个数组指定哪个方向可以超出边界,可选项分别是:
  75. ['top','bottom','left','right']
  76. */
  77. bouncing: true,
  78. /** Enable locking to the main axis if user moves only slightly on one of them at start */
  79. locking: true,
  80. /** 最小缩放级别 */
  81. minZoom: 0.5,
  82. /** 最大缩放级别 */
  83. maxZoom: 3,
  84. /** 滚动速度的倍速 **/
  85. speedMultiplier: 1,
  86. /** 到达边界时应用于减速的改变量 **/
  87. penetrationDeceleration: 0.03,
  88. /** 到达边界时应用于加速的改变量 **/
  89. penetrationAcceleration: 0.08,
  90. /** Whether call e.preventDefault event when sliding the content or not */
  91. preventDefault: true,
  92. /** Whether call preventDefault when (mouse/touch)move */
  93. preventDefaultOnMove: true
  94. },
  95. scrollPanel: {
  96. // 组件加载完后的初始滚动量
  97. initialScrollY: false,
  98. initialScrollX: false,
  99. // 是否禁止x或y方向上的滚动
  100. scrollingX: false,
  101. scrollingY: true,
  102. speed: 300,
  103. // 滚动动画
  104. easing: undefined,
  105. // 是否有一个padding样式,样式的大小应该和rail/bar的大小是一样。可以用来阻止内容被滚动条遮住一部分
  106. padding: false,
  107. // 有时候原声滚动条可能在左侧,
  108. // 请查看 https://github.com/YvesCoding/vuescroll/issues/64
  109. verticalNativeBarPos: 'right'
  110. },
  111. // 滚动条滚动的地方
  112. rail: {
  113. background: '#000',
  114. opacity: 0,
  115. border: 'none',
  116. /** Rail's size(Height/Width) , default -> 6px */
  117. size: '6px',
  118. /** Specify rail's border-radius, or the border-radius of rail and bar will be equal to the rail's size. default -> false **/
  119. specifyBorderRadius: false,
  120. /** Rail the distance from the two ends of the X axis and Y axis. **/
  121. gutterOfEnds: null,
  122. /** Rail the distance from the side of container. **/
  123. gutterOfSide: '0px',
  124. /** Whether to keep rail show or not, default -> false, event content height is not enough */
  125. keepShow: false
  126. },
  127. bar: {
  128. /** 当不做任何操作时滚动条自动消失的时间 */
  129. showDelay: 1000,
  130. /** Specify bar's border-radius, or the border-radius of rail and bar will be equal to the rail's size. default -> false **/
  131. specifyBorderRadius: false,
  132. /** 是否只在滚动的时候现实滚动条 */
  133. onlyShowBarOnScroll: false,
  134. /** 是否保持显示 */
  135. keepShow: false,
  136. /** 滚动条颜色, default -> #00a650 */
  137. background: 'rgb(197, 197, 197)',
  138. /** 滚动条透明度, default -> 1 */
  139. opacity: 1,
  140. /** Styles when you hover scrollbar, it will merge into the current style */
  141. hoverStyle: false
  142. },
  143. scrollButton: {
  144. enable: false,
  145. background: 'rgb(3, 185, 118)',
  146. opacity: 1,
  147. step: 180,
  148. mousedownStep: 30
  149. }
  150. },
  151. /* wangEditor菜单配置 */
  152. wangEditorMenu: [
  153. 'head', // 标题
  154. 'bold', // 粗体
  155. 'fontSize', // 字号
  156. 'fontName', // 字体
  157. 'italic', // 斜体
  158. 'underline', // 下划线
  159. 'strikeThrough', // 删除线
  160. 'foreColor', // 文字颜色
  161. 'link', // 插入链接
  162. 'video', //插入视频
  163. 'justify', // 对齐方式
  164. 'image', // 插入图片
  165. 'table', // 表格
  166. 'undo', // 撤销
  167. ],
  168. /* 简易版菜单 */
  169. wangEditorMenuSimple: [
  170. 'head', // 标题
  171. 'bold', // 粗体
  172. 'fontSize', // 字号
  173. 'fontName', // 字体
  174. 'italic', // 斜体
  175. 'underline', // 下划线
  176. 'strikeThrough', // 删除线
  177. 'foreColor', // 文字颜色
  178. 'justify', // 对齐方式
  179. 'table', // 表格
  180. ],
  181. /* 全屏 */
  182. fullScreen(element) {
  183. // var element = document.documentElement;
  184. if (element.requestFullscreen) {
  185. element.requestFullscreen();
  186. } else if (element.msRequestFullscreen) {
  187. element.msRequestFullscreen();
  188. } else if (element.mozRequestFullScreen) {
  189. element.mozRequestFullScreen();
  190. } else if (element.webkitRequestFullscreen) {
  191. element.webkitRequestFullscreen();
  192. }
  193. },
  194. /* 退出全屏 */
  195. exitFullscreen() {
  196. if (document.exitFullscreen) {
  197. document.exitFullscreen();
  198. } else if (document.msExitFullscreen) {
  199. document.msExitFullscreen();
  200. } else if (document.mozCancelFullScreen) {
  201. document.mozCancelFullScreen();
  202. } else if (document.webkitExitFullscreen) {
  203. document.webkitExitFullscreen();
  204. }
  205. },
  206. getFileThum(type, fileName) {
  207. const docType = ['doc', 'docx']
  208. const excelType = ['xls', 'csv', 'xlsx']
  209. const pptType = ['ppt', 'pptx']
  210. let thumPath = ''
  211. if (type === 'doc' && docType.includes(this.getSuffix(fileName))) {
  212. thumPath = require('@/assets/source/word.png')
  213. } else if (type === 'doc' && excelType.includes(this.getSuffix(fileName))) {
  214. thumPath = require('@/assets/source/excel.png')
  215. } else if (type === 'doc' && pptType.includes(this.getSuffix(fileName))) {
  216. thumPath = require('@/assets/source/ppt.png')
  217. } else if (type === 'image') {
  218. thumPath = require('@/assets/source/image.png')
  219. } else if (type === 'video') {
  220. thumPath = require('@/assets/source/video.png')
  221. } else if (type === 'audio') {
  222. thumPath = require('@/assets/source/audio.png')
  223. } else if (type === 'link') {
  224. thumPath = require('@/assets/source/link.png')
  225. } else if (type === 'res') {
  226. thumPath = require('@/assets/source/zip.png')
  227. } else if (type === 'thum') {
  228. thumPath = require('@/assets/source/image.png')
  229. } else {
  230. thumPath = require('@/assets/source/link.png')
  231. }
  232. return thumPath
  233. },
  234. /* 获取文件后缀名 */
  235. getSuffix(name) {
  236. return name.substr(name.lastIndexOf(".") + 1)
  237. },
  238. /* 导出表格 */
  239. exportTable(params) {
  240. excel.export_array_to_excel(params)
  241. },
  242. /* 检查音视频格式是否符合格式要求 */
  243. checkMediaFile(file) {
  244. return new Promise((r, j) => {
  245. const getSize = () => file.size
  246. const readChunk = (chunkSize, offset) =>
  247. new Promise((resolve, reject) => {
  248. const reader = new FileReader()
  249. reader.onload = (event) => {
  250. if (event.target.error) {
  251. reject(event.target.error)
  252. }
  253. resolve(new Uint8Array(event.target.result))
  254. }
  255. reader.readAsArrayBuffer(file.slice(offset, offset + chunkSize))
  256. })
  257. window.MediaInfo().then((media) => {
  258. media.analyzeData(getSize, readChunk).then((result) => {
  259. console.log(result)
  260. if (result['media']) {
  261. let tracks = result['media']['track']
  262. // 判断是否是视频
  263. let videoTrack = tracks.find(track => track['@type'] === 'Video')
  264. let audioTrack = tracks.find(track => track['@type'] === 'Audio')
  265. // 如果视频文件满足MP4(H264+AAC),WAV('vp8','vp9') 则代表是可以正常播放的视频文件,返回视频的编码级别
  266. if (videoTrack && audioTrack) {
  267. let videoFormat = videoTrack.Format.toLowerCase()
  268. let audioFormat = audioTrack.Format.toLowerCase()
  269. if((videoFormat === 'avc' && audioFormat === 'aac') || (['vp8','vp9'].includes(videoFormat) && audioFormat === 'vorbis')) {
  270. r(videoTrack.Format_Profile || true)
  271. }else{
  272. r(false)
  273. }
  274. }
  275. // 如果是音频文件则需要满足 mp3(MPEG Audio),ogg(Vorbis),wav(PCM) 任意一种编码格式
  276. else if (!videoTrack && audioTrack && ['mpeg audio','vorbis','pcm'].includes(audioTrack.Format.toLowerCase())) {
  277. r(true)
  278. } else {
  279. r(false)
  280. }
  281. } else {
  282. r(false)
  283. }
  284. }).catch((error) => {
  285. j(error)
  286. })
  287. }).catch((error) => {
  288. j(error)
  289. })
  290. })
  291. },
  292. /* 根据最新服务器时间获取当前所在学期的起止时间戳 */
  293. getSemesterTimeRange() {
  294. let curServerTime = localStorage.getItem('serverTime') // 服务器时间
  295. let curPeriod = store.state.user.curPeriod
  296. // 如果没有记录到服务器时间或者学期数据,则默认查全部
  297. if (!curServerTime || !curPeriod || !curPeriod.semesterCount) {
  298. return {
  299. st: 0,
  300. et: 0
  301. }
  302. } else {
  303. let settingSemesters = curPeriod.semesters // 基础设置的学期数据
  304. let settingRange = this.getSettingTermRange(settingSemesters) // 获取设置学期对应的月和日
  305. let serverTimeArr = this.formatTime(curServerTime, 'MM-dd').split('-') // 获取当前服务器时间的月和日
  306. let curSemeterIndex = settingSemesters.length > 1 ? this.getCurSemesterIndex(settingSemesters, settingRange,
  307. serverTimeArr) : 0 // 获取当前学期的序号
  308. let curYear = new Date(Number(curServerTime)).getFullYear() // 获取服务器时间的年份
  309. let curMonth = new Date(Number(curServerTime)).getMonth() + 1 // 获取服务器时间的月份
  310. let startMonth = settingSemesters[curSemeterIndex].month // 当前所在学期的开始月
  311. let startDay = settingSemesters[curSemeterIndex].day // 当前所在学期的开始日
  312. let endMonth = settingSemesters[curSemeterIndex === settingSemesters.length - 1 ? 0 : (curSemeterIndex + 1)]
  313. .month
  314. let endDay = settingSemesters[curSemeterIndex === settingSemesters.length - 1 ? 0 : (curSemeterIndex + 1)]
  315. .day
  316. let startTime = ''
  317. let endTime = ''
  318. // 如果当前学校只设置了一个学期
  319. if (settingSemesters.length === 1) {
  320. startMonth = settingSemesters[0].month
  321. startDay = settingSemesters[0].day
  322. startTime = new Date(
  323. `${curYear}-${startMonth > 9 ? startMonth : '0' + startMonth}-${startDay > 9 ? startDay : '0' + startDay} 00:00:00`
  324. ).getTime()
  325. endTime = new Date(
  326. `${curYear + 1}-${startMonth > 9 ? startMonth : '0' + startMonth}-${startDay > 9 ? startDay : '0' + startDay} 00:00:00`
  327. ).getTime()
  328. } else {
  329. // 如果当前月份小于开始月份 则代表开始时间是在去年
  330. if (curMonth >= startMonth && curMonth <= 12) {
  331. startTime = new Date(
  332. `${curYear}-${startMonth > 9 ? startMonth : '0' + startMonth}-${startDay > 9 ? startDay : '0' + startDay} 00:00:00`
  333. ).getTime()
  334. } else {
  335. startTime = new Date(
  336. `${curYear - 1}-${startMonth > 9 ? startMonth : '0' + startMonth}-${startDay > 9 ? startDay : '0' + startDay} 00:00:00`
  337. ).getTime()
  338. }
  339. // 如果当前月份比结束月份大 则代表是明年结束 Year要加1
  340. if (curMonth > endMonth) {
  341. endTime = new Date(
  342. `${curYear + 1}-${endMonth > 9 ? endMonth : '0' + endMonth}-${endDay > 9 ? endDay : '0' + endDay} 00:00:00`
  343. ).getTime()
  344. } else {
  345. endTime = new Date(
  346. `${curYear}-${endMonth > 9 ? endMonth : '0' + endMonth}-${endDay > 9 ? endDay : '0' + endDay} 00:00:00`
  347. ).getTime()
  348. }
  349. }
  350. return {
  351. st: startTime,
  352. et: endTime,
  353. name: settingSemesters[curSemeterIndex].name
  354. }
  355. }
  356. },
  357. /* 获取当前学段学期时间范围 */
  358. getSettingTermRange(semesters) {
  359. let range = []
  360. for (var i = 0; i < semesters.length; i++) {
  361. range.push([
  362. this.tranNum([+semesters[i].month, +semesters[i].day]),
  363. i === semesters.length - 1 ? this.tranNum([+semesters[0].month, +semesters[0].day]) : this
  364. .tranNum([+semesters[i + 1].month, +semesters[i + 1].day])
  365. ])
  366. }
  367. return range
  368. },
  369. /* 对比评测时间和学期分界点时间 */
  370. getCurSemesterIndex(semesters, range, val) {
  371. console.log(...arguments);
  372. let rangeArr = []
  373. let s = this.tranNum(val)
  374. for (let i = 0; i < range.length; i++) {
  375. let item = range[i]
  376. if (item[0] === s) {
  377. return i
  378. } else {
  379. // 代表跨年了
  380. if (item[0] > item[1]) {
  381. rangeArr = [...this.getNumsArr([item[0], 1231]), ...this.getNumsArr([0, item[1]])]
  382. } else {
  383. rangeArr = this.getNumsArr(item)
  384. }
  385. if (rangeArr.includes(s)) {
  386. return i
  387. }
  388. }
  389. }
  390. },
  391. /* 获取范围内数字数组 */
  392. getNumsArr(range) {
  393. let diff = range[1] - range[0]
  394. let arr = []
  395. for (let i = 0; i < diff; i++) {
  396. arr.push(range[0] + i)
  397. }
  398. return arr
  399. },
  400. /* 数字转换 */
  401. tranNum(val) {
  402. let month = +val[0]
  403. let day = +val[1]
  404. return Number(month + '' + (day > 9 ? day : ('0' + day)))
  405. },
  406. /* 数字与中文转换 */
  407. getChineseByNum(num) {
  408. num = Number(num)
  409. var upperCaseNumber = [
  410. app.$t('learnActivity.score.zero'),
  411. app.$t('learnActivity.score.one'),
  412. app.$t('learnActivity.score.two'),
  413. app.$t('learnActivity.score.three'),
  414. app.$t('learnActivity.score.four'),
  415. app.$t('learnActivity.score.five'),
  416. app.$t('learnActivity.score.six'),
  417. app.$t('learnActivity.score.seven'),
  418. app.$t('learnActivity.score.eight'),
  419. app.$t('learnActivity.score.nine'),
  420. app.$t('learnActivity.score.ten'),
  421. app.$t('learnActivity.score.hundred'),
  422. app.$t('learnActivity.score.thousand'),
  423. app.$t('learnActivity.score.tenThd'),
  424. app.$t('learnActivity.score.hMillion')
  425. ]
  426. var length = String(num).length
  427. if (length === 1) {
  428. return upperCaseNumber[num]
  429. } else if (length === 2) {
  430. if (num === 10) {
  431. return upperCaseNumber[num]
  432. } else if (num > 10 && num < 20) {
  433. return app.$t('learnActivity.score.ten') + upperCaseNumber[String(num).charAt(1)]
  434. } else {
  435. return upperCaseNumber[String(num).charAt(0)] + app.$t('learnActivity.score.ten') + upperCaseNumber[
  436. String(num).charAt(1)].replace(app.$t('learnActivity.score.zero'), '')
  437. }
  438. }
  439. },
  440. /* 生成随机UUID工具 */
  441. randomId: function () {
  442. return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
  443. },
  444. /* 生成随机UUID工具 */
  445. guid: function () {
  446. return (this.randomId() + this.randomId() + '-' + this.randomId() + '-' + this.randomId() + '-' + this
  447. .randomId() +
  448. '-' + this.randomId() + this.randomId() + this.randomId())
  449. },
  450. /* 生成从minNum到maxNum的随机数 */
  451. randomNum(minNum, maxNum) {
  452. switch (arguments.length) {
  453. case 1:
  454. return parseInt(Math.random() * minNum + 1, 10);
  455. break;
  456. case 2:
  457. return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
  458. break;
  459. default:
  460. return 0;
  461. break;
  462. }
  463. },
  464. /* 获取mm与px的转换 */
  465. getOneMmsPx() {
  466. // 创建一个1mm宽的元素插入到页面,然后坐等出结果
  467. let div = document.createElement("div");
  468. div.id = "mm";
  469. div.style.width = "1mm";
  470. document.querySelector("body").appendChild(div);
  471. // 原生方法获取浏览器对元素的计算值
  472. let mm1 = document.getElementById("mm").getBoundingClientRect();
  473. console.log(mm1);
  474. return mm1.width;
  475. },
  476. // 根据总分和数量求平均结果
  477. doAverage(total, count, step) {
  478. let oneMaxScore = (Math.floor((total / (count * step))) * step);
  479. let maxIndex = Math.floor((total - oneMaxScore * count) / step);
  480. let tempArr = new Array(count).fill(oneMaxScore).map((item, index) => index < maxIndex ? (item + step) : item);
  481. return tempArr
  482. },
  483. /* 根据x,y,w,h获取四点坐标 */
  484. getBoxPos(x, y, w, h) {
  485. return [{
  486. x: x,
  487. y: y
  488. },
  489. {
  490. x: x + w,
  491. y: y
  492. },
  493. {
  494. x: x + w,
  495. y: y + h
  496. },
  497. {
  498. x: x,
  499. y: y + h
  500. }
  501. ]
  502. },
  503. /* 匹配URL里面的HOST */
  504. matchHost(url) {
  505. var pattern = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/
  506. return 'https://' + url.match(pattern)[0]
  507. },
  508. /* 正则判断是否为标准的URL格式 */
  509. isURL(url) {
  510. const strRegex = '^((https|http|ftp)://)?' //(https或http或ftp):// 可有可无
  511. +
  512. '(([\\w_!~*\'()\\.&=+$%-]+: )?[\\w_!~*\'()\\.&=+$%-]+@)?' //ftp的user@ 可有可无
  513. +
  514. '(([0-9]{1,3}\\.){3}[0-9]{1,3}' // IP形式的URL- 3位数字.3位数字.3位数字.3位数字
  515. +
  516. '|' // 允许IP和DOMAIN(域名)
  517. +
  518. '(localhost)|' //匹配localhost
  519. +
  520. '([\\w_!~*\'()-]+\\.)*' // 域名- 至少一个[英文或数字_!~*\'()-]加上.
  521. +
  522. '\\w+\\.' // 一级域名 -英文或数字 加上.
  523. +
  524. '[a-zA-Z]{1,6})' // 顶级域名- 1-6位英文
  525. +
  526. '(:[0-9]{1,5})?' // 端口- :80 ,1-5位数字
  527. +
  528. '((/?)|' // url无参数结尾 - 斜杆或这没有
  529. +
  530. '(/[\\w_!~*\'()\\.;?:@&=+$,%#-]+)+/?)$'; //请求参数结尾- 英文或数字和[]内的各种字符
  531. const re = new RegExp(strRegex, 'i'); // 大小写不敏感
  532. if (re.test(encodeURI(url))) {
  533. return true;
  534. }
  535. return false;
  536. },
  537. /* 数据结构转换工具 */
  538. jsonTransform(obj) {
  539. let arr = []
  540. console.log(obj)
  541. if (obj.datas.length) {
  542. let item = {}
  543. let attrLength = obj.keys.length
  544. for (let i = 0; i < obj.datas.length; i++) {
  545. for (let j = 0; j < attrLength; j++) {
  546. item[obj.keys[j]] = obj.datas[i][j]
  547. }
  548. arr.push(item)
  549. item = {}
  550. }
  551. }
  552. return arr || null
  553. },
  554. /* 时间格式化 */
  555. formatTime(timestamp, fmt = 'yyyy-MM-dd hh:mm:ss') {
  556. let d = new Date()
  557. d.setTime(timestamp)
  558. var o = {
  559. 'M+': d.getMonth() + 1, //月份
  560. 'd+': d.getDate(), //日
  561. 'h+': d.getHours(), //小时
  562. 'm+': d.getMinutes(), //分
  563. 's+': d.getSeconds(), //秒
  564. 'q+': Math.floor((d.getMonth() + 3) / 3), //季度
  565. 'S': d.getMilliseconds() //毫秒
  566. }
  567. if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (d.getFullYear() + '').substr(4 - RegExp.$1.length))
  568. for (var k in o)
  569. if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((
  570. '00' + o[
  571. k]).substr(('' + o[k]).length)))
  572. return fmt
  573. },
  574. /* 字节格式转换 */
  575. bytesToSize(bytes) {
  576. if (bytes === 0) return '0 B';
  577. let k = 1024,
  578. sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  579. i = Math.floor(Math.log(bytes) / Math.log(k));
  580. return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
  581. },
  582. /* 采用原生Xhr来发送HTTP请求 避免携带token访问blob报错 */
  583. getFile(url, type) {
  584. return new Promise((resolve, reject) => {
  585. var xhr = new XMLHttpRequest();
  586. var formData = new FormData();
  587. xhr.open('get', url); //url填写后台的接口地址,如果是post,在formData append参数(参考原文地址)
  588. if (type) xhr.responseType = 'arraybuffer';
  589. xhr.onload = function (e) {
  590. switch (e.currentTarget.status) {
  591. case 200:
  592. resolve(e.currentTarget.response)
  593. break;
  594. case 404:
  595. // Message.error('有资源丢失')
  596. reject(404)
  597. break;
  598. case 403:
  599. Message.error(app.$t('http.error403'))
  600. reject(403)
  601. break;
  602. default:
  603. break;
  604. }
  605. };
  606. xhr.send(formData);
  607. })
  608. },
  609. /* 获取授权码 */
  610. getSas(param) {
  611. return new Promise((r, j) => {
  612. switch (param.type) {
  613. case 'file': //单文件授权
  614. if (!store.state.fileSas || checkSas(store.state.fileSas.timeout)) {
  615. $api.blob.urlSasR({
  616. params: param.data
  617. }).then(
  618. (res) => {
  619. if (res.error == null) {
  620. r(res.result.data)
  621. } else {
  622. j(500)
  623. }
  624. },
  625. (err) => {
  626. j(500)
  627. }
  628. )
  629. } else {
  630. r(store.state.fileSas)
  631. }
  632. break
  633. case 'blobR': //容器只读权限
  634. if (!store.state.blobR || checkSas(store.state.blobR.timeout)) {
  635. $api.blob.blobSasR(param.data).then(
  636. (res) => {
  637. if (res.error == null) {
  638. //store.commit('setBlobR')
  639. r(res.result.data)
  640. } else {
  641. j(500)
  642. }
  643. },
  644. (err) => {
  645. j(500)
  646. }
  647. )
  648. } else {
  649. r(store.state.blobR)
  650. }
  651. break
  652. case 'blobRW':
  653. if (!store.state.blobRW || checkSas(store.state.blobRW.timeout)) {
  654. $api.blob.blobSasRCW(param.data).then(
  655. (res) => {
  656. if (res.error == null) {
  657. store.commit('setBlobRW', res.result.data)
  658. r(res.result.data)
  659. } else {
  660. j(500)
  661. }
  662. },
  663. (err) => {
  664. j(500)
  665. }
  666. )
  667. } else {
  668. r(store.state.blobRW)
  669. }
  670. break
  671. default:
  672. j('getSas() 参数错误:type')
  673. }
  674. })
  675. },
  676. /* 根据登录后的用户信息获取blobHOST域名 */
  677. getBlobHost() {
  678. let s = store.state.user.userProfile.blob_uri || store.state.user.studentProfile.blob_uri
  679. let pattern = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/
  680. return s.split('//')[0] + '//' + s.match(pattern)[0]
  681. },
  682. /* 获取个人容器授权 */
  683. getPrivateSas(code) {
  684. return new Promise((r, j) => {
  685. let ts = new Date().getTime()
  686. if (!store.state.user.userProfile.blob_sas) {
  687. $api.blob.blobSasRCW({
  688. name: store.state.userInfo.TEAMModelId,
  689. role: 'teacher'
  690. }).then(
  691. (res) => {
  692. if (res.error == null) {
  693. res.sas = '?' + res.sas
  694. r(res)
  695. } else {
  696. j(500)
  697. }
  698. },
  699. (err) => {
  700. j(500)
  701. }
  702. )
  703. } else {
  704. r({
  705. sas: '?' + store.state.user.userProfile.blob_sas + '&timestamp=' + ts,
  706. name: code || store.state.userInfo.TEAMModelId,
  707. url: this.getBlobHost()
  708. })
  709. }
  710. })
  711. },
  712. /* 获取学校容器授权 */
  713. getSchoolSas(code) {
  714. return new Promise((r, j) => {
  715. let ts = new Date().getTime()
  716. if (!store.state.user.schoolProfile.blob_sas) {
  717. $api.blob.blobSasRCW({
  718. name: code || store.state.userInfo.schoolCode,
  719. role: 'school'
  720. }).then(
  721. (res) => {
  722. if (res.error == null) {
  723. res.sas = '?' + res.sas
  724. r(res)
  725. } else {
  726. j(500)
  727. }
  728. },
  729. (err) => {
  730. j(500)
  731. }
  732. )
  733. } else {
  734. r({
  735. sas: '?' + store.state.user.schoolProfile.blob_sas + '&timestamp=' + ts,
  736. name: code || store.state.userInfo.schoolCode,
  737. url: this.getBlobHost()
  738. })
  739. }
  740. })
  741. },
  742. /* 获取单个文件授权 */
  743. getFileSas(param) {
  744. console.log('获取单个文件授权', param)
  745. return new Promise((r, j) => {
  746. $api.blob.urlSasR({
  747. url: param
  748. }).then(
  749. (res) => {
  750. if (res.error == null) {
  751. //store.commit('setBlobRW', res.result.data)
  752. r(res)
  753. } else {
  754. j(500)
  755. }
  756. },
  757. (err) => {
  758. j(500)
  759. }
  760. )
  761. })
  762. },
  763. /* 根据容器名称获取完整授权码 */
  764. getBlobSas(code) {
  765. return new Promise((r, j) => {
  766. $api.blob.blobSasR({
  767. name: code,
  768. role: 'teacher'
  769. }).then(res => {
  770. if (!res.error) {
  771. r(res)
  772. }
  773. })
  774. })
  775. },
  776. /* 批量生成二维码 */
  777. batchQrcodes(arr, pdfName) {
  778. return new Promise((resolve, reject) => {
  779. if (!arr.length) {
  780. app.$Message.warning('未获取到学生数据')
  781. resolve(200)
  782. return
  783. }
  784. let promiseArr = []
  785. arr.forEach((i, index) => {
  786. promiseArr.push(new Promise((r, j) => {
  787. let dom = document.createElement('div')
  788. let qrcode = new QRCode(dom, {
  789. width: 320, // 设置宽度,单位像素
  790. height: 320, // 设置高度,单位像素
  791. text: arr[index], // 设置二维码内容或跳转地址
  792. })
  793. setTimeout(() => {
  794. let base64 = dom.getElementsByTagName('img')[0].currentSrc
  795. dom.remove()
  796. r(base64)
  797. }, 500)
  798. }))
  799. })
  800. Promise.all(promiseArr).then(result => {
  801. var PDF = new JsPDF({
  802. orientation: 'p',
  803. unit: 'mm',
  804. format: 'a4',
  805. putOnlyUsedFonts: true
  806. })
  807. PDF.setFontSize(12);
  808. result.forEach((img, imgIndex) => {
  809. let whichRow = parseInt((imgIndex % 30) / 5)
  810. let xArr = [10, 50, 90, 130, 170]
  811. let yArr = [10, 55, 100, 145, 190, 235]
  812. let x = xArr[imgIndex % 5]
  813. let y = yArr[whichRow]
  814. let xMarginLeft = ((13 - arr[imgIndex].length) / 2) * (30 / 13)
  815. PDF.addImage(img, 'JPEG', x, y, 30, 30)
  816. PDF.text(arr[imgIndex], x + xMarginLeft, y + 38);
  817. if ((imgIndex + 1) % 30 === 0 && imgIndex !== result.length - 1) {
  818. PDF.addPage()
  819. }
  820. })
  821. PDF.save(app.$t('stuAccount.qrcode') + '(' + pdfName + ').pdf')
  822. resolve(200)
  823. })
  824. })
  825. },
  826. /* 生成学生清单PDF */
  827. batchStuList(datas, pdfName) {
  828. return new Promise(async (resolve, reject) => {
  829. try {
  830. let namesArr = await this.getNamesBase64(datas.map(i => i.name))
  831. let classNameBase64 = await this.getClassNameBase64(pdfName)
  832. let pageCount = 1
  833. let PDF = new JsPDF({
  834. orientation: 'p',
  835. unit: 'mm',
  836. format: 'a4',
  837. putOnlyUsedFonts: true
  838. })
  839. const a4Height = 297
  840. const a4Width = 210
  841. // 生成学生清单表格
  842. PDF.autoTable({
  843. theme: 'plain',
  844. styles: {
  845. valign: 'middle'
  846. },
  847. margin: {
  848. top: 15,
  849. left: 15,
  850. },
  851. headStyles: {
  852. cellPadding: {
  853. top: 3,
  854. bottom: 3,
  855. left: 5
  856. },
  857. valign: 'middle',
  858. },
  859. bodyStyles: {
  860. cellPadding: 5,
  861. },
  862. columnStyles: {
  863. 0: {
  864. cellWidth: 20,
  865. valign: 'middle',
  866. halign: 'center'
  867. }
  868. },
  869. head: [
  870. ['', 'Name', 'ID', 'IRS', '']
  871. ],
  872. body: datas.map(i => ['', '', i.id, i.irs]),
  873. didDrawPage: (data) => {
  874. PDF.addImage(classNameBase64, 'JPEG', 15, 5, 200, 10);
  875. PDF.setFontSize(12);
  876. PDF.setFont('Times New Roman')
  877. // PDF.text(`${data.pageNumber} / ${data.doc.internal.pages.length}`, a4Width / 2 - 5, a4Height - 4,'center')
  878. },
  879. didDrawCell: (cell) => {
  880. if (cell.column.dataKey === 0 && cell.section === 'body') {
  881. let defaultImg = require('@/assets/image/tmd_logo.png')
  882. let url = datas[cell.row.index].picture || defaultImg
  883. var img = new Image();
  884. img.src = url;
  885. PDF.addImage(img, 'JPEG', cell.cursor.x + 5, cell.cursor.y + 2, 10, 10);
  886. }
  887. if (cell.column.dataKey === 1 && cell.section === 'body') {
  888. let nameBase64 = namesArr[cell.row.index]
  889. PDF.addImage(nameBase64, 'JPEG', cell.cursor.x + 5, cell.cursor.y + 4, 20, 8);
  890. }
  891. if (cell.column.dataKey === 4 && cell.section === 'body') {
  892. PDF.setDrawColor(0);
  893. PDF.setLineWidth(0.6);
  894. PDF.rect(cell.cursor.x + 5, cell.cursor.y + 4, 5, 5);
  895. }
  896. }
  897. })
  898. PDF.save(pdfName + '.pdf')
  899. resolve(200)
  900. } catch (e) {
  901. reject(500)
  902. }
  903. })
  904. },
  905. /* 根据图片生成PDF */
  906. generatePdfByImgs(urls, pdfName) {
  907. return new Promise(async (resolve, reject) => {
  908. try {
  909. const a4Height = 297
  910. const a4Width = 210
  911. let PDF = new JsPDF({
  912. orientation: 'l',
  913. unit: 'mm',
  914. format: 'a4',
  915. putOnlyUsedFonts: true
  916. })
  917. urls.forEach((url, index) => {
  918. var img = new Image();
  919. img.src = url;
  920. PDF.addImage(img, 'JPEG', 0, 0, a4Height, a4Width);
  921. if (index !== urls.length - 1) {
  922. PDF.addPage()
  923. }
  924. })
  925. PDF.save(pdfName + '.pdf')
  926. resolve(200)
  927. } catch (e) {
  928. reject(500)
  929. console.log(e)
  930. }
  931. })
  932. },
  933. /* 生成名字图片 */
  934. getImgsBase64ByUrls(urls) {
  935. return new Promise((resolve, reject) => {
  936. let promiseArr = []
  937. urls.forEach(url => {
  938. promiseArr.push(new Promise((r, j) => {
  939. console.error(url)
  940. var img = new Image();
  941. img.src = url
  942. img.onload = function () {
  943. var canvas = document.createElement("canvas");
  944. canvas.width = img.width;
  945. canvas.height = img.height;
  946. var ctx = canvas.getContext("2d");
  947. ctx.drawImage(img, 0, 0, img.width, img.height);
  948. var dataURL = canvas.toDataURL("image/png");
  949. r(dataURL)
  950. }
  951. }))
  952. })
  953. Promise.all(promiseArr).then(result => {
  954. resolve(result)
  955. }).catch(e => {
  956. reject(e)
  957. })
  958. })
  959. },
  960. /* 生成班级名称图片 */
  961. getClassNameBase64(name) {
  962. return new Promise((resolve, reject) => {
  963. let dom = document.createElement('div')
  964. dom.style.width = '200mm'
  965. dom.style.height = '10mm'
  966. dom.style.fontSize = '20px'
  967. dom.style.fontWeight = 'bold'
  968. dom.innerHTML = name
  969. document.body.appendChild(dom)
  970. setTimeout(() => {
  971. domtoimage.toPng(dom).then((pageData) => {
  972. dom.remove()
  973. resolve(pageData)
  974. })
  975. }, 100)
  976. })
  977. },
  978. /* 生成名字图片 */
  979. getNamesBase64(arr) {
  980. return new Promise((resolve, reject) => {
  981. let promiseArr = []
  982. arr.forEach(name => {
  983. promiseArr.push(new Promise((r, j) => {
  984. let dom = document.createElement('div')
  985. dom.style.width = '20mm'
  986. dom.style.height = '8mm'
  987. dom.style.fontSize = '14px'
  988. dom.style.fontWeight = 'bold'
  989. dom.innerHTML = name
  990. document.body.appendChild(dom)
  991. setTimeout(() => {
  992. domtoimage.toPng(dom).then((pageData) => {
  993. dom.remove()
  994. r(pageData)
  995. })
  996. }, 100)
  997. }))
  998. })
  999. Promise.all(promiseArr).then(result => {
  1000. resolve(result)
  1001. })
  1002. })
  1003. },
  1004. /* 弹窗下载文件操作 */
  1005. doDownloadByUrl(url, fileName) {
  1006. let a = document.createElement('a');
  1007. a.href = url;
  1008. a.download = fileName;
  1009. a.target = '_blank'
  1010. a.click()
  1011. a.remove();
  1012. },
  1013. /* 获取视频第一帧 */
  1014. getVideoBase64(url) {
  1015. return new Promise(function (resolve, reject) {
  1016. let dataURL = '';
  1017. let video = document.createElement("video");
  1018. video.setAttribute('crossOrigin', 'anonymous'); //处理跨域
  1019. video.setAttribute('src', url);
  1020. video.setAttribute('preload', 'auto');
  1021. video.setAttribute('width', 150);
  1022. video.setAttribute('height', 90);
  1023. video.addEventListener('loadeddata', function () {
  1024. let canvas = document.createElement("canvas"),
  1025. width = video.width, //canvas的尺寸和图片一样
  1026. height = video.height;
  1027. canvas.width = width;
  1028. canvas.height = height;
  1029. canvas.getContext("2d").drawImage(video, 0, 0, width, height); //绘制canvas
  1030. dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
  1031. // 画播放按钮
  1032. var img = new Image()
  1033. img.src = require('@/assets/icon/icon_play.png')
  1034. img.setAttribute('crossOrigin', 'anonymous');
  1035. img.onload = function () {
  1036. //画图
  1037. canvas.getContext("2d").drawImage(img, 55, 25, 40, 40);
  1038. var data = canvas.toDataURL('image/jpeg');
  1039. resolve(data);
  1040. }
  1041. });
  1042. })
  1043. },
  1044. /* 获取视频文件的持续时长 */
  1045. getVideoDuration(file, url) {
  1046. return new Promise((resolve, reject) => {
  1047. var vid = document.createElement('video');
  1048. var fileURL = url || URL.createObjectURL(file);
  1049. vid.src = fileURL;
  1050. vid.addEventListener('loadedmetadata', function () {
  1051. resolve(vid.duration);
  1052. });
  1053. vid.remove()
  1054. })
  1055. },
  1056. /* 将文件MD5 Uint8Array格式转换成字符串 */
  1057. convertFileMD5ToString(fileData) {
  1058. var dataString = "";
  1059. for (var i = 0; i < fileData.length; i++) {
  1060. dataString += fileData[i].toString(16);
  1061. }
  1062. return dataString
  1063. },
  1064. /* 将二进制流字符串转换成Uint8Array */
  1065. stringToUint8Array(str) {
  1066. var arr = [];
  1067. for (var i = 0, j = str.length; i < j; ++i) {
  1068. arr.push(str.charCodeAt(i));
  1069. }
  1070. var tmpUint8Array = new Uint8Array(arr);
  1071. return tmpUint8Array
  1072. },
  1073. /* 批量打包下载 */
  1074. async doBatchDownloadZip(urls, zipName) {
  1075. const zip = new JSZip();
  1076. const cache = {};
  1077. const promises = [];
  1078. /* 循环下载所有文件后 加入promise队列 */
  1079. await urls.forEach(item => {
  1080. const promiseItem = this.getFile(item).then(dta => {
  1081. const arr_name = item.split("/"); //处理名称
  1082. var file_name = arr_name[arr_name.length - 1].split('?')[0];
  1083. zip.file(file_name, dta, {
  1084. binary: true
  1085. }); // 逐个添加文件
  1086. cache[file_name] = dta;
  1087. });
  1088. promises.push(promiseItem);
  1089. });
  1090. Promise.all(promises).then(() => {
  1091. zip.generateAsync({
  1092. type: "blob"
  1093. }).then(content => {
  1094. // 生成二进制流
  1095. FileSaver.saveAs(content, zipName + ".zip"); // 利用file-saver保存文件
  1096. })
  1097. .catch(err => {
  1098. console.log(err);
  1099. });
  1100. });
  1101. },
  1102. /* 获取字符串的MD5 */
  1103. getStringMd5(str) {
  1104. let spark = new SparkMD5();
  1105. spark.append(str)
  1106. var md5 = spark.end();
  1107. return md5
  1108. },
  1109. /* 获取文件的MD5 */
  1110. getFileMD5(file) {
  1111. return new Promise((r, j) => {
  1112. console.log(file)
  1113. const CHUNK_SIZE = 4194304; // 大文件获取数前4M大小
  1114. const fileReader = new FileReader();
  1115. const chunkFile = file.slice(0, CHUNK_SIZE);
  1116. fileReader.readAsBinaryString(chunkFile);
  1117. let spark = new SparkMD5();
  1118. console.time('getMd5')
  1119. fileReader.onload = e => {
  1120. spark.appendBinary(e.target.result);
  1121. var md5 = spark.end(true);
  1122. console.log(md5)
  1123. r(this.stringToUint8Array(md5))
  1124. };
  1125. })
  1126. },
  1127. /* 获取时间差异 */
  1128. getDateDiff(dateTimeStamp) {
  1129. var result = ''
  1130. var minute = 1000 * 60;
  1131. var hour = minute * 60;
  1132. var day = hour * 24;
  1133. var halfamonth = day * 15;
  1134. var month = day * 30;
  1135. var now = new Date().getTime();
  1136. var diffValue = now - dateTimeStamp;
  1137. if (diffValue < 0) {
  1138. return;
  1139. }
  1140. var monthC = diffValue / month;
  1141. var weekC = diffValue / (7 * day);
  1142. var dayC = diffValue / day;
  1143. var hourC = diffValue / hour;
  1144. var minC = diffValue / minute;
  1145. if (monthC >= 1) {
  1146. result = "" + parseInt(monthC) + app.$t('unit.diffTip1');
  1147. } else if (weekC >= 1) {
  1148. result = "" + parseInt(weekC) + app.$t('unit.diffTip2');
  1149. } else if (dayC >= 1) {
  1150. result = "" + parseInt(dayC) + app.$t('unit.diffTip3');
  1151. } else if (hourC >= 1) {
  1152. result = "" + parseInt(hourC) + app.$t('unit.diffTip4');
  1153. } else if (minC >= 1) {
  1154. result = "" + parseInt(minC) + app.$t('unit.diffTip5');
  1155. } else
  1156. result = app.$t('unit.diffTip6');
  1157. return result;
  1158. },
  1159. /* 上传视频到BLob */
  1160. async doUploadVideo(vm, file, editor) {
  1161. // 判断试卷编辑试题环境下 则直接取scope
  1162. let editorEnv = sessionStorage.getItem('editorEnv')
  1163. let inPaperEnv = editorEnv && editorEnv !== 'normal'
  1164. let paperName = inPaperEnv ? sessionStorage.getItem('paper_name') : null
  1165. vm.$Spin.show({
  1166. render: (h) => {
  1167. return h('div', {
  1168. 'class': 'my-spin',
  1169. style: {
  1170. background: '#000',
  1171. padding: '20px',
  1172. borderRadius: '5px',
  1173. color: '#fff'
  1174. }
  1175. }, [
  1176. h('Icon', {
  1177. 'class': 'demo-spin-icon-load',
  1178. props: {
  1179. type: 'ios-loading',
  1180. size: 18
  1181. }
  1182. }),
  1183. h('div', app.$t('utils.uploadLoading'))
  1184. ])
  1185. }
  1186. });
  1187. const blobTool = require('./blobTool.js')
  1188. const isSchool = vm.$parent.exerciseScope ? vm.$parent.exerciseScope === 1 : vm.exerciseScope === 1
  1189. const scope = inPaperEnv ? editorEnv : (isSchool ? 'school' : 'private')
  1190. const sasData = scope === 'school' ? await this.getSchoolSas() : await this.getPrivateSas()
  1191. const blobToolClass = blobTool.default
  1192. //初始化Blob
  1193. let containerClient = new blobToolClass(sasData.url, sasData.name, sasData.sas, scope)
  1194. try {
  1195. let id = vm.curId || vm.$parent.curId
  1196. // 上传文件
  1197. let blobFile = await containerClient.upload(file, {
  1198. path: inPaperEnv && paperName ? ('paper/' + paperName) : ('item/' + id)
  1199. })
  1200. // 获取blob链接以及视频封面截图
  1201. const fileSas = await this.getFileSas(blobFile.url)
  1202. // const fileBlobUrl = encodeURI(blobFile.url) + '?' + fileSas.sas
  1203. const fileBlobUrl = encodeURI(blobFile.url) + '?' + fileSas.sas
  1204. // const posterBase64 = await this.getVideoBase64(fileBlobUrl)
  1205. editor.selection.getSelectionStartElem().elems[0].innerHTML += '<span><span>&nbsp;</span><video src=' +
  1206. fileBlobUrl +
  1207. ' class="richText-video" width="300" preload controls="controls"></video><span>&nbsp;</span></span>'
  1208. editor.change.emit()
  1209. // 如果是在试卷环境下上传多媒体 则需要先保留以下 试题的数据 如果同步到题库 再进行上传
  1210. if (inPaperEnv && paperName) {
  1211. let needSaveToItem = sessionStorage.getItem('syncItemBlob') ? JSON.parse(sessionStorage.getItem(
  1212. 'syncItemBlob')) : []
  1213. needSaveToItem.push({
  1214. paperName: paperName,
  1215. target: 'item/' + id + '/' + file.name,
  1216. path: blobFile.blob,
  1217. scope: scope
  1218. })
  1219. sessionStorage.setItem('syncItemBlob', JSON.stringify(needSaveToItem))
  1220. }
  1221. vm.$EventBus.$emit('noSave', {
  1222. path: blobFile.blob,
  1223. size: blobFile.size,
  1224. scope: scope
  1225. })
  1226. vm.$Spin.hide();
  1227. } catch (e) {
  1228. vm.$Message.error(e.spaceError)
  1229. vm.$Spin.hide();
  1230. }
  1231. },
  1232. /* 上传视频到BLob */
  1233. async doUploadAudio(vm, file, editor) {
  1234. console.log(vm)
  1235. // 判断试卷编辑试题环境下 则直接取scope
  1236. let editorEnv = sessionStorage.getItem('editorEnv')
  1237. let inPaperEnv = editorEnv && editorEnv !== 'normal'
  1238. let paperName = inPaperEnv ? sessionStorage.getItem('paper_name') : null
  1239. vm.$Spin.show({
  1240. render: (h) => {
  1241. return h('div', {
  1242. 'class': 'my-spin',
  1243. style: {
  1244. background: '#000',
  1245. padding: '20px',
  1246. borderRadius: '5px',
  1247. color: '#fff'
  1248. }
  1249. }, [
  1250. h('Icon', {
  1251. 'class': 'demo-spin-icon-load',
  1252. props: {
  1253. type: 'ios-loading',
  1254. size: 18
  1255. }
  1256. }),
  1257. h('div', app.$t('utils.uploadLoading'))
  1258. ])
  1259. }
  1260. });
  1261. const blobTool = require('./blobTool.js')
  1262. const isSchool = vm.$parent.exerciseScope ? vm.$parent.exerciseScope === 1 : vm.exerciseScope === 1
  1263. const scope = inPaperEnv ? editorEnv : (isSchool ? 'school' : 'private')
  1264. const sasData = scope === 'school' ? await this.getSchoolSas() : await this.getPrivateSas()
  1265. const blobToolClass = blobTool.default
  1266. console.log(isSchool)
  1267. //初始化Blob
  1268. let containerClient = new blobToolClass(sasData.url, sasData.name, sasData.sas, scope)
  1269. try {
  1270. let id = vm.curId || vm.$parent.curId
  1271. // 上传文件
  1272. let blobFile = await containerClient.upload(file, {
  1273. path: inPaperEnv && paperName ? ('paper/' + paperName) : ('item/' + id)
  1274. })
  1275. console.log(blobFile)
  1276. // 获取blob链接以及视频封面截图
  1277. const fileSas = await this.getFileSas(blobFile.url)
  1278. const fileBlobUrl = fileSas.url
  1279. editor.selection.getSelectionStartElem().elems[0].innerHTML += `<span><span>&nbsp;&nbsp;</span><span class="richText-audio" contenteditable="false" >
  1280. <span class="audio-info">
  1281. <i class="ivu-icon ivu-icon-ios-musical-notes" style="font-size: 24px;margin:0 10px"></i>
  1282. <span class="audio-name">${file.name}</span>
  1283. </span>
  1284. <audio src="${fileBlobUrl}" id="audio" controls="controls" controlsList="nodownload"></audio>
  1285. </span><span>&nbsp;</span></span>`
  1286. editor.change.emit()
  1287. // 如果是在试卷环境下上传多媒体 则需要先保留以下 试题的数据 如果同步到题库 再进行上传
  1288. if (inPaperEnv && paperName) {
  1289. let needSaveToItem = sessionStorage.getItem('syncItemBlob') ? JSON.parse(sessionStorage.getItem(
  1290. 'syncItemBlob')) : []
  1291. needSaveToItem.push({
  1292. paperName: paperName,
  1293. target: 'item/' + id + '/' + file.name,
  1294. path: blobFile.blob,
  1295. scope: scope
  1296. })
  1297. sessionStorage.setItem('syncItemBlob', JSON.stringify(needSaveToItem))
  1298. }
  1299. vm.$EventBus.$emit('noSave', {
  1300. path: blobFile.blob,
  1301. size: blobFile.size,
  1302. scope: scope
  1303. })
  1304. vm.$Spin.hide();
  1305. } catch (e) {
  1306. vm.$Message.error(e.spaceError)
  1307. vm.$Spin.hide();
  1308. }
  1309. },
  1310. /* 删除未保存的BLOB多媒体文件 */
  1311. async deleteNoSave(noSaveArr) {
  1312. const blobTool = require('./blobTool.js')
  1313. const schoolSas = await this.getSchoolSas()
  1314. const privateSas = await this.getPrivateSas()
  1315. const blobToolClass = blobTool.default
  1316. let schoolBlobClient = new blobToolClass(schoolSas.url, schoolSas.name, schoolSas.sas, 'school')
  1317. let privateBlobClient = new blobToolClass(privateSas.url, privateSas.name, privateSas.sas, 'private')
  1318. let promiseArr = []
  1319. for (let i = 0; i < noSaveArr.length; i++) {
  1320. let item = noSaveArr[i]
  1321. promiseArr.push(new Promise((r, j) => {
  1322. let myClient = item.scope === 'school' ? schoolBlobClient : privateBlobClient
  1323. myClient.deleteBlob(item.path, item.size).then(res => {
  1324. r(200)
  1325. }).catch(err => {
  1326. j(err)
  1327. })
  1328. }))
  1329. }
  1330. Promise.all(promiseArr).then(result => {
  1331. localStorage.setItem('noSave', '[]')
  1332. }).catch(err => {
  1333. console.log(err)
  1334. })
  1335. },
  1336. /* 判断是否相等 */
  1337. isEqual: function (x, y) {
  1338. // If both x and y are null or undefined and exactly the same
  1339. if (x === y) {
  1340. return true;
  1341. }
  1342. // If they are not strictly equal, they both need to be Objects
  1343. if (!(x instanceof Object) || !(y instanceof Object)) {
  1344. return false;
  1345. }
  1346. //They must have the exact same prototype chain,the closest we can do is
  1347. //test the constructor.
  1348. if (x.constructor !== y.constructor) {
  1349. return false;
  1350. }
  1351. for (var p in x) {
  1352. //Inherited properties were tested using x.constructor === y.constructor
  1353. if (x.hasOwnProperty(p)) {
  1354. // Allows comparing x[ p ] and y[ p ] when set to undefined
  1355. if (!y.hasOwnProperty(p)) {
  1356. return false;
  1357. }
  1358. // If they have the same strict value or identity then they are equal
  1359. if (x[p] === y[p]) {
  1360. continue;
  1361. }
  1362. // Numbers, Strings, Functions, Booleans must be strictly equal
  1363. if (typeof (x[p]) !== "object") {
  1364. return false;
  1365. }
  1366. // Objects and Arrays must be tested recursively
  1367. if (!Object.equals(x[p], y[p])) {
  1368. return false;
  1369. }
  1370. }
  1371. }
  1372. for (p in y) {
  1373. // allows x[ p ] to be set to undefined
  1374. if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
  1375. return false;
  1376. }
  1377. }
  1378. return true;
  1379. },
  1380. /* 学情认知层次模块数据转换 */
  1381. getLevelStuPercent(val, subjectIndex, index) {
  1382. let result = []
  1383. val.students.forEach(stu => {
  1384. result.push([
  1385. stu.name,
  1386. stu.className,
  1387. stu.no || '-',
  1388. val.fScores[subjectIndex].value[index],
  1389. stu.subjects[subjectIndex].fieldPoint[index],
  1390. val.fScores[subjectIndex].value[index] == 0 ? 0 : (stu.subjects[subjectIndex]
  1391. .fieldPoint[index] / val.fScores[subjectIndex].value[index])
  1392. .toFixed(2)
  1393. ])
  1394. })
  1395. return result
  1396. },
  1397. /* 学情认知层次年级班级得分率数据转换 */
  1398. getLevelClassPercent(val, subjectIndex, index) {
  1399. let result = []
  1400. val.classes.forEach(classItem => {
  1401. result.push(classItem.subjects[subjectIndex].field.map((score, index) => val.fieldwrong[
  1402. subjectIndex].value[index][1] == 0 ? 0 : Number(score / val.fieldwrong[subjectIndex]
  1403. .value[index][1]) * 100)[index])
  1404. })
  1405. return result
  1406. },
  1407. /* 学情认知层次年级班级得分率数据转换 */
  1408. getLevelPercent(val, subjectIndex) {
  1409. let stuResult = {}
  1410. let classResult = {}
  1411. val.fieldName[subjectIndex].value.forEach((item, index) => {
  1412. stuResult[item] = this.getLevelStuPercent(val, subjectIndex, index)
  1413. classResult[item] = this.getLevelClassPercent(val, subjectIndex, index)
  1414. })
  1415. stuResult.grade = val.fieldAllPer[subjectIndex].value.map((score, index) => (val.fieldwrong[subjectIndex].value[
  1416. index][1] == 0 ? 0 : Number(score / val.fieldwrong[subjectIndex].value[index][1]).toFixed(2)) * 100)
  1417. stuResult.keys = val.knowKey
  1418. classResult.keys = val.knowkey
  1419. classResult.className = val.classes.map(i => i.className)
  1420. let obj = {
  1421. stuResult: stuResult,
  1422. classResult: classResult
  1423. }
  1424. return obj
  1425. },
  1426. /* 学情知识点模块数据转换 */
  1427. getKnowStuPercent(val, subjectIndex, index) {
  1428. let result = []
  1429. val.students.forEach(stu => {
  1430. result.push([
  1431. stu.name,
  1432. stu.className,
  1433. stu.no || '-',
  1434. val.kScores[subjectIndex].value[index],
  1435. stu.subjects[subjectIndex].point[index],
  1436. val.kScores[subjectIndex].value[index] == 0 ? 0 : (stu.subjects[subjectIndex].point[
  1437. index] / val.kScores[subjectIndex].value[index])
  1438. .toFixed(2)
  1439. ])
  1440. })
  1441. return result
  1442. },
  1443. /* 学情知识点班级得分率数据转换 */
  1444. getKnowClassPercent(val, subjectIndex, index) {
  1445. let result = []
  1446. val.classes.forEach(classItem => {
  1447. // 取当前班级在每个知识点的得分 除以知识点的总分 得到每个班在该知识点的得分率 index=>知识点下标
  1448. result.push(classItem.subjects[subjectIndex].point.map((score, pointIndex) => val.wrong[
  1449. subjectIndex].value[pointIndex][1] == 0 ? 0 : Number(score / val.wrong[subjectIndex]
  1450. .value[pointIndex][1]) * 100)[index])
  1451. })
  1452. return result
  1453. },
  1454. /* 学情知识点年级班级得分率数据转换 */
  1455. getKnowPercent(val, subjectIndex) {
  1456. let stuResult = {}
  1457. let classResult = {}
  1458. val.knowName[subjectIndex].value.forEach((item, index) => {
  1459. stuResult[item] = this.getKnowStuPercent(val, subjectIndex, index)
  1460. classResult[item] = this.getKnowClassPercent(val, subjectIndex, index)
  1461. })
  1462. // 取当前年级在每个知识点的得分 除以知识点的总分 得到年级在该知识点的得分率
  1463. stuResult.grade = val.knowAllper[subjectIndex].value.map((score, index) => val.wrong[subjectIndex].value[index][
  1464. 1
  1465. ] == 0 ? 0 : Number(score / val.wrong[subjectIndex].value[index][1]) * 100)
  1466. stuResult.keys = val.knowKey
  1467. classResult.keys = val.knowkey
  1468. classResult.className = val.classes.map(i => i.className)
  1469. let obj = {
  1470. stuResult: stuResult,
  1471. classResult: classResult
  1472. }
  1473. return obj
  1474. },
  1475. /* 罗马数字转换 */
  1476. getRomanByNum(number) {
  1477. var Roman = [
  1478. ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"],
  1479. ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"],
  1480. ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"],
  1481. ["", "M", "MM", "MMM", " ", " ", " ", " ", " ", " "]
  1482. ];
  1483. // 只能转换 4000 以下的正整数阿拉伯数字
  1484. function convert(num) {
  1485. if (isNaN(num)) return num;
  1486. var ReverseArr = num.toString().split("").reverse();
  1487. var CorrectArr = [];
  1488. for (var i = 0; i < ReverseArr.length; i++) {
  1489. CorrectArr.unshift(Roman[i][ReverseArr[i]]);
  1490. }
  1491. return CorrectArr.join("");
  1492. }
  1493. return convert(number)
  1494. }
  1495. }