sign.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <template>
  2. <ui-group-option
  3. class="record-sign"
  4. :class="{ sign: record.status === RecordStatus.SUCCESS }"
  5. >
  6. <div class="content">
  7. <span class="cover">
  8. <img :src="getResource(getFileUrl(record.cover))" alt="" v-if="record.cover" />
  9. <ui-icon
  10. type="preview"
  11. ctrl
  12. class="preview"
  13. @click="actions.play()"
  14. v-if="record.status === RecordStatus.SUCCESS"
  15. />
  16. </span>
  17. <ui-input
  18. type="text"
  19. :modelValue="record.title"
  20. @update:modelValue="(title: string) => $emit('updateTitle', title.trim())"
  21. v-show="isEditTitle"
  22. ref="inputRef"
  23. height="28px"
  24. :maxlength="15"
  25. />
  26. <div class="title" v-show="!isEditTitle">
  27. <p>{{ record.title }}</p>
  28. <span v-if="record.status === RecordStatus.RUN">后台正在处理</span>
  29. </div>
  30. </div>
  31. <div class="action" v-if="edit && record.status === RecordStatus.SUCCESS">
  32. <ui-icon type="order" ctrl />
  33. <ui-more
  34. :options="menus"
  35. style="margin-left: 20px"
  36. @click="(action: keyof typeof actions) => actions[action]()"
  37. />
  38. </div>
  39. <Shot
  40. v-if="isShot"
  41. @close="closeHandler"
  42. @append="appendFragment"
  43. @updateCover="(cover: string) => $emit('updateCover', cover)"
  44. @deleteRecord="$emit('delete')"
  45. :record="record"
  46. />
  47. <Preview
  48. v-if="isPlayVideo"
  49. :items="[{ type: MetaType.video, url: record.url! }]"
  50. @close="isPlayVideo = false"
  51. />
  52. </ui-group-option>
  53. </template>
  54. <script lang="ts">
  55. import type { PropType } from "vue";
  56. import { computed, defineComponent, ref, watchEffect } from "vue";
  57. import { getExtname, getFileUrl, loadPack, MetaType, saveAs } from "@/utils";
  58. import { useFocus } from "bill/hook/useFocus";
  59. import {
  60. createRecordFragment,
  61. getRecordFragmentBlobs,
  62. isTemploraryID,
  63. recordFragments,
  64. RecordStatus,
  65. } from "@/store";
  66. import { Preview } from "@/components/static-preview/index.vue";
  67. import { getResource } from "@/env";
  68. import Shot from "./shot.vue";
  69. import type { RecordProcess } from "./help";
  70. import { Message } from "bill/index";
  71. export default defineComponent({
  72. props: {
  73. record: {
  74. type: Object as PropType<RecordProcess>,
  75. required: true,
  76. },
  77. edit: {
  78. type: Boolean,
  79. required: false,
  80. default: true,
  81. },
  82. },
  83. emits: {
  84. updateCover: (cover: string) => true,
  85. updateTitle: (title: string) => true,
  86. delete: () => true,
  87. },
  88. setup(props, { emit }) {
  89. const menus = computed(() => {
  90. const base = [];
  91. if ([RecordStatus.SUCCESS, RecordStatus.UN].includes(props.record.status)) {
  92. base.push(
  93. { label: "重命名", value: "rename" },
  94. { label: "继续录制", value: "continue" }
  95. );
  96. if (props.record.status === RecordStatus.SUCCESS) {
  97. base.push({ label: "下载", value: "download" });
  98. }
  99. }
  100. base.push({ label: "删除", value: "delete" });
  101. return base;
  102. });
  103. const isShot = ref<boolean>(false);
  104. const inputRef = ref();
  105. const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root));
  106. watchEffect(() => {
  107. if (!isEditTitle.value && !props.record.title.length) {
  108. isEditTitle.value = true;
  109. Message.warning("视频名称不可为空");
  110. }
  111. });
  112. const isPlayVideo = ref(false);
  113. const actions = {
  114. continue: () => (isShot.value = true),
  115. delete: () => emit("delete"),
  116. rename: () => (isEditTitle.value = true),
  117. play: () => (isPlayVideo.value = true),
  118. download() {
  119. const url = getResource(props.record.url!);
  120. const ext = getExtname(url) || "mp4";
  121. loadPack(saveAs(url, `${props.record.title}.${ext}`));
  122. },
  123. };
  124. props.record.immediately && actions.continue();
  125. const closeHandler = () => {
  126. if (
  127. getRecordFragmentBlobs(props.record).length === 0 &&
  128. isTemploraryID(props.record.id)
  129. ) {
  130. emit("delete");
  131. }
  132. isShot.value = false;
  133. };
  134. const appendFragment = (blobs: Blob[]) => {
  135. recordFragments.value.push(
  136. ...blobs.map((blob) =>
  137. createRecordFragment({ url: blob, recordId: props.record.id })
  138. )
  139. );
  140. props.record.status = RecordStatus.UN;
  141. };
  142. return {
  143. menus,
  144. actions,
  145. isShot,
  146. isEditTitle,
  147. closeHandler,
  148. inputRef,
  149. RecordStatus,
  150. MetaType,
  151. isPlayVideo,
  152. getResource,
  153. getFileUrl,
  154. appendFragment,
  155. };
  156. },
  157. components: {
  158. Shot,
  159. Preview,
  160. },
  161. });
  162. </script>
  163. <style lang="scss" src="./style.scss" scoped></style>
  164. <style>
  165. .record-sign .ui-input .text.suffix input {
  166. padding-right: 60px;
  167. }
  168. </style>