Просмотр исходного кода

refactor(frontend): 优化批量上传字幕交互

Signed-off-by: Myon <[email protected]>
Myon 3 лет назад
Родитель
Сommit
cc2bc7f3a5
1 измененных файлов с 131 добавлено и 43 удалено
  1. 131 43
      frontend/src/pages/library/tvs/BtnUploadMultipleForTv.vue

+ 131 - 43
frontend/src/pages/library/tvs/BtnUploadMultipleForTv.vue

@@ -13,14 +13,23 @@
     v-show="false"
     type="file"
     ref="qFile"
-    v-model="uploadFile"
+    v-model="inputFiles"
     @update:model-value="handleFileChange"
     multiple
     accept=".srt,.ass,.ssa,.sbv,.webvtt"
   />
 
-  <q-dialog v-model="show">
-    <q-card style="min-width: 900px">
+  <q-dialog v-model="show" @before-show="handleBeforeShow">
+    <q-card
+      class="relative-position"
+      style="min-width: 900px"
+      @dragover="handleDragenter"
+      @drop="handleDrop"
+      @dragleave="handleDragleave"
+    >
+      <div class="drag-mask row items-center justify-center text-white text-h5" v-if="isDragover">
+        放置字幕文件到此处
+      </div>
       <q-card-section>
         <div class="text-body1">批量上传</div>
       </q-card-section>
@@ -28,31 +37,48 @@
       <q-separator />
 
       <q-card-section style="min-height: 500px">
-        <q-list separator>
-          <q-item v-for="item in uploadFile" :key="item.name">
-            <q-item-section>{{ item.name }}</q-item-section>
-            <q-item-section side>
-              <q-select
-                v-model="mapForm[item.name]"
-                :options="items"
-                dense
-                filled
-                style="width: 200px"
-                label="选择对应的集数"
-                clearable
-              >
-                <template v-slot:selected>
-                  <span v-if="mapForm[item.name]"> 第{{ mapForm[item.name].episode }}集 </span>
-                </template>
-                <template v-slot:option="scope">
-                  <q-item v-bind="scope.itemProps" clickable v-ripple>
-                    <q-item-section>第{{ scope.opt.episode }}集</q-item-section>
-                  </q-item>
-                </template>
-              </q-select>
-            </q-item-section>
-          </q-item>
-        </q-list>
+        <div
+          v-if="uploadFiles.length === 0"
+          class="upload-area column justify-center items-center q-gutter-sm"
+          style="height: 500px"
+        >
+          <div>拖拽文件上传</div>
+          <div>或者</div>
+          <q-btn color="primary" label="选择文件" dense flat @click="() => qFile.$el.click()" />
+        </div>
+        <template v-else>
+          <q-btn color="primary" label="添加字幕文件" @click="() => qFile.$el.click()" class="q-mb-sm" />
+          <q-list separator>
+            <q-item v-for="item in uploadFiles" :key="item.name">
+              <q-item-section>{{ item.name }}</q-item-section>
+              <q-item-section side>
+                <q-select
+                  :disable="submitting"
+                  v-model="mapForm[item.name]"
+                  :options="items"
+                  dense
+                  filled
+                  style="width: 200px"
+                  label="选择对应的集数"
+                  clearable
+                >
+                  <template v-slot:selected>
+                    <span v-if="mapForm[item.name]"> 第{{ mapForm[item.name].episode }}集 </span>
+                  </template>
+                  <template v-slot:option="scope">
+                    <q-item v-bind="scope.itemProps" clickable v-ripple>
+                      <q-item-section>第{{ scope.opt.episode }}集</q-item-section>
+                    </q-item>
+                  </template>
+                </q-select>
+              </q-item-section>
+
+              <q-item-section side>
+                <q-btn color="negative" flat icon="close" dense title="删除" rounded @click="handleRemoveFile(item)" />
+              </q-item-section>
+            </q-item>
+          </q-list>
+        </template>
       </q-card-section>
 
       <q-separator />
@@ -85,14 +111,17 @@ const props = defineProps({
   },
 });
 
-const uploadFile = ref(null);
+const inputFiles = ref(null);
 const qFile = ref(null);
 const show = ref(false);
 const submitting = ref(false);
 const mapForm = reactive({});
+const isDragover = ref(false);
+const uploadFiles = ref([]);
 
 const handleUploadClick = () => {
-  qFile.value.$el.click();
+  show.value = true;
+  // qFile.value.$el.click();
 };
 
 const upload = async () => {
@@ -105,12 +134,15 @@ const upload = async () => {
       formData.append('video_f_path', item.video_f_path);
       formData.append(
         'file',
-        [].find.call(uploadFile.value, (file) => file.name === name)
+        uploadFiles.value.find((file) => file.name === name)
       );
       // eslint-disable-next-line no-await-in-loop
       await LibraryApi.uploadSubtitle(formData);
       // eslint-disable-next-line no-await-in-loop
       await getSubtitleUploadList();
+      // sleep 1s
+      // eslint-disable-next-line no-await-in-loop,no-promise-executor-return
+      await new Promise((resolve) => setTimeout(resolve, 1000));
       eventBus.emit('subtitle-uploaded');
     }
   }
@@ -121,18 +153,74 @@ const upload = async () => {
   show.value = false;
 };
 
-const handleFileChange = (val) => {
-  if (val.length > 0) {
-    show.value = true;
-    [].forEach.call(val, (e) => {
-      const episode = getEpisode(e.name);
-      const item = props.items.find((i) => i.episode === episode);
-      if (item) {
-        mapForm[e.name] = item;
-      } else {
-        mapForm[e.name] = null;
-      }
-    });
+const addMapForm = (file) => {
+  const episode = getEpisode(file.name);
+  const item = props.items.find((i) => i.episode === episode);
+  if (item) {
+    mapForm[file.name] = item;
+  } else {
+    mapForm[file.name] = null;
+  }
+};
+
+const handleNewFileAdded = (file) => {
+  if (/\.srt|\.ass|\.ssa|\.sbv|\.webvtt/.test(file.name)) {
+    if (!uploadFiles.value.some((f) => f.name === file.name)) {
+      uploadFiles.value.push(file);
+      addMapForm(file);
+    }
   }
 };
+
+const handleFileChange = (val) => {
+  [].forEach.call(val, (file) => {
+    handleNewFileAdded(file);
+  });
+};
+
+const handleDragenter = (e) => {
+  e.preventDefault();
+  isDragover.value = true;
+};
+
+const handleDragleave = (e) => {
+  e.preventDefault();
+};
+
+const handleDrop = (e) => {
+  e.preventDefault();
+  const { files } = e.dataTransfer;
+  [].forEach.call(files, (file) => {
+    handleNewFileAdded(file);
+  });
+  handleFileChange(files);
+  isDragover.value = false;
+};
+
+const handleRemoveFile = (file) => {
+  uploadFiles.value = uploadFiles.value.filter((f) => f.name !== file.name);
+  delete mapForm[file.name];
+};
+
+const handleBeforeShow = () => {
+  uploadFiles.value = [];
+  Object.keys(mapForm).forEach((key) => {
+    delete mapForm[key];
+  });
+};
 </script>
+
+<style lang="scss">
+.drag-mask {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  z-index: 999;
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.upload-area {
+  border: 2px dashed #ccc;
+  border-radius: 4px;
+}
+</style>