index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import { computed, defineComponent, ref } from 'vue';
  2. import {
  3. NImage,
  4. NDivider,
  5. NButton,
  6. NModal,
  7. useMessage,
  8. ImageRenderToolbarProps
  9. } from 'naive-ui';
  10. import TheNoticeBar from '/src/components/TheNoticeBar';
  11. import styles from './index.module.less';
  12. import { PageEnum } from '/src/enums/pageEnum';
  13. import nodata from '../images/nomore.png';
  14. import CardPreview from '/src/components/card-preview';
  15. import { checkUrlType, iframeDislableKeyboard } from '/src/utils';
  16. import { useUserStore } from '/src/store/modules/users';
  17. import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
  18. import { saveAs } from 'file-saver';
  19. import { modalClickMask } from '/src/state';
  20. export default defineComponent({
  21. name: 'work-item',
  22. props: {
  23. item: {
  24. type: Object,
  25. default: () => ({})
  26. }
  27. },
  28. setup(props) {
  29. const userStore = useUserStore();
  30. const message = useMessage();
  31. const previewShow = ref(false);
  32. const preivewItem = ref({
  33. type: 'MUSIC',
  34. content: props.item.musicId,
  35. title: props.item.musicName,
  36. studentName: props.item.studentName
  37. });
  38. const reportSrc = ref('');
  39. const detailVisiable = ref(false);
  40. // 下载资源
  41. const onDownload = (src: any) => {
  42. if (!src) {
  43. message.error('下载失败');
  44. return;
  45. }
  46. const fileUrl = src;
  47. // props.item.studentName
  48. const title =
  49. props.item.musicName +
  50. (props.item.studentName ? '-' + props.item.studentName : '');
  51. const suffix = src.substring(src.lastIndexOf('.'));
  52. // 发起Fetch请求
  53. fetch(fileUrl)
  54. .then(response => response.blob())
  55. .then(blob => {
  56. saveAs(blob, (title || new Date().getTime() + '') + suffix);
  57. })
  58. .catch(() => {
  59. message.error('下载失败');
  60. });
  61. };
  62. return () => (
  63. <div
  64. class={[
  65. styles.workItem,
  66. (props.item.fileList?.expireFlag ||
  67. props.item.trainingStatus === 'UNSUBMITTED') &&
  68. styles['work-content-disabled']
  69. ]}>
  70. <div
  71. class={[styles['work-content']]}
  72. style={{
  73. cursor:
  74. props.item.trainingStatus === 'UNSUBMITTED'
  75. ? 'default'
  76. : 'pointer'
  77. }}>
  78. {/* ("文件类型:评测:EVALUATION,IMG:图片,SOUND:音频,VIDEO:视频")
  79. private String fileType; */}
  80. {!props.item.fileList?.fileType && (
  81. <NImage
  82. src={nodata}
  83. class={styles.nodata}
  84. previewDisabled
  85. objectFit="contain"
  86. />
  87. )}
  88. {props.item.fileList?.fileType === 'IMG' && (
  89. <NImage
  90. src={props.item.fileList?.filePath}
  91. objectFit="contain"
  92. renderToolbar={({ nodes }: ImageRenderToolbarProps) => {
  93. return [
  94. nodes.prev,
  95. nodes.next,
  96. nodes.rotateCounterclockwise,
  97. nodes.rotateClockwise,
  98. nodes.resizeToOriginalSize,
  99. nodes.zoomOut,
  100. <div
  101. class={'n-base-icon'}
  102. onClick={() => onDownload(props.item.fileList?.filePath)}>
  103. <svg
  104. viewBox="0 0 16 16"
  105. version="1.1"
  106. xmlns="http://www.w3.org/2000/svg">
  107. <g
  108. stroke="none"
  109. stroke-width="1"
  110. fill="none"
  111. fill-rule="evenodd">
  112. <g fill="currentColor" fill-rule="nonzero">
  113. <path d="M3.5,13 L12.5,13 C12.7761424,13 13,13.2238576 13,13.5 C13,13.7454599 12.8231248,13.9496084 12.5898756,13.9919443 L12.5,14 L3.5,14 C3.22385763,14 3,13.7761424 3,13.5 C3,13.2545401 3.17687516,13.0503916 3.41012437,13.0080557 L3.5,13 L12.5,13 L3.5,13 Z M7.91012437,1.00805567 L8,1 C8.24545989,1 8.44960837,1.17687516 8.49194433,1.41012437 L8.5,1.5 L8.5,10.292 L11.1819805,7.6109127 C11.3555469,7.43734635 11.6249713,7.4180612 11.8198394,7.55305725 L11.8890873,7.6109127 C12.0626536,7.78447906 12.0819388,8.05390346 11.9469427,8.2487716 L11.8890873,8.31801948 L8.35355339,11.8535534 C8.17998704,12.0271197 7.91056264,12.0464049 7.7156945,11.9114088 L7.64644661,11.8535534 L4.1109127,8.31801948 C3.91565056,8.12275734 3.91565056,7.80617485 4.1109127,7.6109127 C4.28447906,7.43734635 4.55390346,7.4180612 4.7487716,7.55305725 L4.81801948,7.6109127 L7.5,10.292 L7.5,1.5 C7.5,1.25454011 7.67687516,1.05039163 7.91012437,1.00805567 L8,1 L7.91012437,1.00805567 Z"></path>
  114. </g>
  115. </g>
  116. </svg>
  117. </div>,
  118. nodes.close
  119. ];
  120. }}
  121. />
  122. )}
  123. {props.item.fileList?.fileType === 'SOUND' && (
  124. <div
  125. onClick={() => {
  126. preivewItem.value.content = props.item.fileList?.filePath;
  127. preivewItem.value.title = props.item.musicName;
  128. preivewItem.value.type = 'SONG';
  129. previewShow.value = true;
  130. }}>
  131. <NImage
  132. src={PageEnum.SONG_DEFAULT_COVER}
  133. previewDisabled
  134. objectFit="contain"
  135. />
  136. </div>
  137. )}
  138. {props.item.fileList?.fileType === 'EVALUATION' &&
  139. (checkUrlType(props.item.fileList?.content) === 'video' ? (
  140. <div
  141. class={styles.videoSection}
  142. onClick={() => {
  143. preivewItem.value.content = props.item.fileList?.content;
  144. preivewItem.value.title = props.item.musicName;
  145. preivewItem.value.type = 'VIDEO';
  146. previewShow.value = true;
  147. }}>
  148. <video
  149. style={{ height: '100%' }}
  150. src={props.item.fileList?.content}
  151. />
  152. </div>
  153. ) : (
  154. <div
  155. onClick={() => {
  156. preivewItem.value.content = props.item.fileList?.content;
  157. preivewItem.value.title = props.item.musicName;
  158. preivewItem.value.type = 'SONG';
  159. previewShow.value = true;
  160. }}>
  161. <NImage
  162. src={PageEnum.SONG_DEFAULT_COVER}
  163. previewDisabled
  164. objectFit="contain"
  165. />
  166. </div>
  167. ))}
  168. {/* 'https://oss.dayaedu.com/ktqy/1715586967518b42c4fe5.mp4' */}
  169. {props.item.fileList?.fileType === 'VIDEO' && (
  170. <div
  171. class={styles.videoSection}
  172. onClick={() => {
  173. preivewItem.value.content = props.item.fileList?.filePath;
  174. preivewItem.value.title = props.item.musicName;
  175. preivewItem.value.type = 'VIDEO';
  176. previewShow.value = true;
  177. }}>
  178. <video
  179. style={{ height: '100%' }}
  180. src={props.item.fileList?.filePath}
  181. />
  182. </div>
  183. )}
  184. {/* 判断是否过期 */}
  185. {props.item.fileList?.expireFlag && (
  186. <div class={styles.expireBg}>文件已过期</div>
  187. )}
  188. {props.item.recordId && (
  189. <NButton
  190. color="rgba(0,0,0,0.4)"
  191. textColor="#fff"
  192. disabled={props.item.fileList?.expireFlag}
  193. class={styles.reportBtn}
  194. onClick={() => {
  195. if (!props.item.recordId) {
  196. message.error('暂无评测记录');
  197. return;
  198. }
  199. const tockn = userStore.getToken;
  200. reportSrc.value =
  201. vaildMusicScoreUrl() +
  202. `/instrument/#/evaluat-report?id=${props.item.recordId}&Authorization=${tockn}`;
  203. detailVisiable.value = true;
  204. }}>
  205. 评测报告
  206. </NButton>
  207. )}
  208. </div>
  209. <div class={styles['work-footer']}>
  210. <div class={styles.trainInfo}>
  211. <div class={styles.trainName}>
  212. <span class={[styles.type, styles[props.item.trainingType]]}>
  213. {props.item.trainingType === 'EVALUATION' ? '评测' : '练习'}
  214. </span>
  215. <div class={styles['title-text']}>
  216. <TheNoticeBar text={props.item.musicName} />
  217. </div>
  218. </div>
  219. <div class={styles.tagList}>
  220. {props.item.typeList?.map((type: string, index: number) => (
  221. <>
  222. <span>{type}</span>
  223. {props.item.typeList.length - 1 > index && (
  224. <NDivider vertical />
  225. )}
  226. </>
  227. ))}
  228. </div>
  229. </div>
  230. {props.item.trainingType === 'EVALUATION' ? (
  231. <div class={[styles.scoreGroup, styles.scoreGroupEval]}>
  232. {props.item.trainingStatus !== 'UNSUBMITTED' ? (
  233. <>
  234. {props.item.trainingTimes}
  235. <span>分</span>
  236. </>
  237. ) : (
  238. <span class={styles.noSubmit}>未提交</span>
  239. )}
  240. </div>
  241. ) : (
  242. <div class={[styles.scoreGroup]}>
  243. {props.item.trainingStatus !== 'UNSUBMITTED' ? (
  244. <>
  245. <label>
  246. {props.item.trainingTimes
  247. ? parseInt(props.item.trainingTimes / 60 + '')
  248. : 0}
  249. </label>
  250. <span>分钟</span>
  251. </>
  252. ) : (
  253. <span class={styles.noSubmit}>未提交</span>
  254. )}
  255. </div>
  256. )}
  257. </div>
  258. <CardPreview
  259. v-model:show={previewShow.value}
  260. item={preivewItem.value}
  261. />
  262. <NModal
  263. maskClosable={modalClickMask}
  264. v-model:show={detailVisiable.value}
  265. preset="card"
  266. class={['modalTitle background', styles.reportModel]}
  267. title={'评测报告'}>
  268. <div class={styles.reportContainer} style={{ lineHeight: 0 }}>
  269. <iframe
  270. width={'100%'}
  271. height={'450px'}
  272. frameborder="0"
  273. onLoad={(val: any) => {
  274. iframeDislableKeyboard(val.target);
  275. }}
  276. src={reportSrc.value}></iframe>
  277. </div>
  278. </NModal>
  279. </div>
  280. );
  281. }
  282. });