ソースを参照

💫feat(split): add loading skeleton for file list and improve UX

Shinohara Haruna 6 ヶ月 前
コミット
20cc5fda1d
1 ファイル変更114 行追加57 行削除
  1. 114 57
      smsb-plus-ui/src/views/smsb/item/split.vue

+ 114 - 57
smsb-plus-ui/src/views/smsb/item/split.vue

@@ -13,11 +13,13 @@
           <el-radio-button label="第三屏" :key="'screen3'" value="3" v-if="splitScreen === 3" />
           <el-radio-button label="播放框" :key="'screen4'" value="4" v-if="splitScreen === 4" />
         </el-radio-group>
-        <el-table :data="minioDataList" ref="minioTable"
-          @selection-change="handleSelectionFile"
-          @select="handleSelect"
-          @select-all="handleSelectAll"
-          style="margin-top: 20px">
+        <el-skeleton :rows="5" animated style="margin-top: 20px" v-show="loadingFileList">
+          <template #template>
+            <el-skeleton-item variant="text" style="height: 40px; margin-bottom: 10px" v-for="i in 5" :key="i" />
+          </template>
+        </el-skeleton>
+        <el-table :data="minioDataList" ref="minioTable" @selection-change="handleSelectionFile" @select="handleSelect"
+          @select-all="handleSelectAll" style="margin-top: 20px" v-show="!loadingFileList">
           <el-table-column type="selection" width="55" align="center" />
           <el-table-column label="原名" align="left" prop="originalName" width="150" :show-overflow-tooltip="true" />
           <el-table-column label="类型" align="center" prop="type" width="80">
@@ -41,68 +43,116 @@
       <!-- 右侧内容 -->
       <div class="w-1/3 p-2">
         <div v-if="screenNum === '1'" class="table-container">
-          <div class="draggable-header" style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
+          <div class="draggable-header"
+            style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
             <span style="width: 80px; text-align: center">排序</span>
             <span style="flex: 1">文件名</span>
             <span style="width: 120px; margin-left: 8px">播放时长</span>
           </div>
-          <draggable v-model="selectedFiles1" item-key="id" @end="onSelectedFilesDragEnd('1')" :animation="200" tag="div">
+          <draggable v-model="selectedFiles1" item-key="id" @end="onSelectedFilesDragEnd('1')" :animation="200"
+            tag="div">
             <template #item="{ element, index }">
-              <div class="draggable-row" :draggable="true" :key="String(element.id)" style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
+              <div class="draggable-row" :draggable="true" :key="String(element.id)"
+                style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
                 <span class="order-number" style="width: 80px; text-align: center">{{ index + 1 }}</span>
-                <span style="flex: 1; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;">{{ element.name }}</span>
-                <el-input-number v-model="element.duration" :min="1" :max="300" style="width: 120px; margin-left: 8px" />
+                <span style="
+                    flex: 1;
+                    max-width: 240px;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    display: inline-block;
+                    vertical-align: middle;
+                  ">{{ element.name }}</span>
+                <el-input-number v-model="element.duration" :min="1" :max="300"
+                  style="width: 120px; margin-left: 8px" />
               </div>
             </template>
           </draggable>
           <div v-if="selectedFiles1.length === 0" style="text-align: center; color: #999; padding: 16px 0">暂无数据</div>
         </div>
         <div v-if="screenNum === '2'" class="table-container">
-          <div class="draggable-header" style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
+          <div class="draggable-header"
+            style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
             <span style="width: 80px; text-align: center">排序</span>
             <span style="flex: 1">文件名</span>
             <span style="width: 120px; margin-left: 8px">播放时长</span>
           </div>
-          <draggable v-model="selectedFiles2" item-key="id" @end="onSelectedFilesDragEnd('2')" :animation="200" tag="div">
+          <draggable v-model="selectedFiles2" item-key="id" @end="onSelectedFilesDragEnd('2')" :animation="200"
+            tag="div">
             <template #item="{ element, index }">
-              <div class="draggable-row" :draggable="true" style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
+              <div class="draggable-row" :draggable="true"
+                style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
                 <span class="order-number" style="width: 80px; text-align: center">{{ index + 1 }}</span>
-                <span style="flex: 1; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;">{{ element.name }}</span>
-                <el-input-number v-model="element.duration" :min="1" :max="300" style="width: 120px; margin-left: 8px" />
+                <span style="
+                    flex: 1;
+                    max-width: 240px;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    display: inline-block;
+                    vertical-align: middle;
+                  ">{{ element.name }}</span>
+                <el-input-number v-model="element.duration" :min="1" :max="300"
+                  style="width: 120px; margin-left: 8px" />
               </div>
             </template>
           </draggable>
           <div v-if="selectedFiles2.length === 0" style="text-align: center; color: #999; padding: 16px 0">暂无数据</div>
         </div>
         <div v-if="screenNum === '3'" class="table-container">
-          <div class="draggable-header" style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
+          <div class="draggable-header"
+            style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
             <span style="width: 80px; text-align: center">排序</span>
             <span style="flex: 1">文件名</span>
             <span style="width: 120px; margin-left: 8px">播放时长</span>
           </div>
-          <draggable v-model="selectedFiles3" item-key="id" @end="onSelectedFilesDragEnd('3')" :animation="200" tag="div">
+          <draggable v-model="selectedFiles3" item-key="id" @end="onSelectedFilesDragEnd('3')" :animation="200"
+            tag="div">
             <template #item="{ element, index }">
-              <div class="draggable-row" :draggable="true" style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
+              <div class="draggable-row" :draggable="true"
+                style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
                 <span class="order-number" style="width: 80px; text-align: center">{{ index + 1 }}</span>
-                <span style="flex: 1; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;">{{ element.name }}</span>
-                <el-input-number v-model="element.duration" :min="1" :max="300" style="width: 120px; margin-left: 8px" />
+                <span style="
+                    flex: 1;
+                    max-width: 240px;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    display: inline-block;
+                    vertical-align: middle;
+                  ">{{ element.name }}</span>
+                <el-input-number v-model="element.duration" :min="1" :max="300"
+                  style="width: 120px; margin-left: 8px" />
               </div>
             </template>
           </draggable>
           <div v-if="selectedFiles3.length === 0" style="text-align: center; color: #999; padding: 16px 0">暂无数据</div>
         </div>
         <div v-if="screenNum === '4'" class="table-container">
-          <div class="draggable-header" style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
+          <div class="draggable-header"
+            style="display: flex; align-items: center; background: #fafafa; border-bottom: 1px solid #eee; min-height: 48px; font-weight: bold">
             <span style="width: 80px; text-align: center">排序</span>
             <span style="flex: 1">文件名</span>
             <span style="width: 120px; margin-left: 8px">播放时长</span>
           </div>
-          <draggable v-model="selectedFiles4" item-key="id" @end="onSelectedFilesDragEnd('4')" :animation="200" tag="div">
+          <draggable v-model="selectedFiles4" item-key="id" @end="onSelectedFilesDragEnd('4')" :animation="200"
+            tag="div">
             <template #item="{ element, index }">
-              <div class="draggable-row" :draggable="true" style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
+              <div class="draggable-row" :draggable="true"
+                style="display: flex; align-items: center; border-bottom: 1px solid #eee; min-height: 48px; cursor: move">
                 <span class="order-number" style="width: 80px; text-align: center">{{ index + 1 }}</span>
-                <span style="flex: 1; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;">{{ element.name }}</span>
-                <el-input-number v-model="element.duration" :min="1" :max="300" style="width: 120px; margin-left: 8px" />
+                <span style="
+                    flex: 1;
+                    max-width: 240px;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    display: inline-block;
+                    vertical-align: middle;
+                  ">{{ element.name }}</span>
+                <el-input-number v-model="element.duration" :min="1" :max="300"
+                  style="width: 120px; margin-left: 8px" />
               </div>
             </template>
           </draggable>
@@ -147,6 +197,7 @@ const dialogData = reactive<DialogPageData<MinioDataQuery>>({
   }
 });
 const minioDataList = ref<MinioDataVO[]>([]);
+const loadingFileList = ref(false); // 控制骨架屏显示
 const screenNum = ref<string>();
 const itemSplitVo = ref<ItemSplitScreenVO>();
 const splitScreen = ref(0);
@@ -163,19 +214,19 @@ const uploadForm = reactive<SplitUploadForm>({
 });
 
 const screenChange = () => {
-  console.log(screenNum.value);
+  getFileList();
   // minioTable.value.clearSelection();
 };
 
 const getParamItemId = () => {
   itemId.value = route.params.itemId as string;
-  console.log(itemId.value);
+
   getItemInfo();
 };
 
 const getItemInfo = async () => {
   const res = await splitItemInfo(itemId.value);
-  console.log('分屏详情:', res.data);
+
   itemSplitVo.value = res.data;
   splitScreen.value = res.data.splitScreen;
   if (splitScreen.value === 4) {
@@ -196,19 +247,20 @@ const restoreSelectedFiles = async () => {
     const selectedFilesRef = [null, selectedFiles1, selectedFiles2, selectedFiles3, selectedFiles4][n];
     if (Array.isArray(fileIdList) && fileIdList.length) {
       const fileRes = await listMinioData({ ids: fileIdList });
-      selectedFilesRef.value = fileIdList.map((id: string | number, idx: number) => {
-        const file = fileRes.rows.find((f: any) => String(f.id) === String(id));
-        return file
-          ? {
+      selectedFilesRef.value = fileIdList
+        .map((id: string | number, idx: number) => {
+          const file = fileRes.rows.find((f: any) => String(f.id) === String(id));
+          return file
+            ? {
               id: file.id,
               name: file.originalName,
               type: file.type,
               duration: file.type === 1 ? 10 : Number(file.duration),
               order: idx + 1
             }
-          : null;
-      }).filter(Boolean);
-      console.log(`fileIdList${n}:`, fileIdList, `selectedFiles${n}:`, selectedFilesRef.value);
+            : null;
+        })
+        .filter(Boolean);
     } else {
       selectedFilesRef.value = [];
     }
@@ -270,28 +322,33 @@ const handleSelectAll = (selection: MinioDataVO[]) => {
 };
 /** 查询文件资源列表 */
 const getFileList = async () => {
-  console.log('分页参数:', dialogQueryParams.value);
-  const res = await listMinioData(dialogQueryParams.value);
-  minioDataList.value = res.rows.map((data) => ({
-    ...data,
-    size: (parseFloat(data.size) / 1024).toFixed(3) + 'MB'
-  }));
-  fileTotal.value = res.total;
-  await nextTick();
-  // 高亮当前分屏
-  let selectedFilesRef;
-  if (screenNum.value === '1') selectedFilesRef = selectedFiles1;
-  if (screenNum.value === '2') selectedFilesRef = selectedFiles2;
-  if (screenNum.value === '3') selectedFilesRef = selectedFiles3;
-  if (screenNum.value === '4') selectedFilesRef = selectedFiles4;
-  if (selectedFilesRef?.value?.length) {
-    const selectedIds = new Set(selectedFilesRef.value.map((f) => f.id));
-    minioDataList.value.forEach((row) => {
-      if (selectedIds.has(row.id)) {
-        minioTable.value?.toggleRowSelection(row, true);
-      }
-    });
-    console.log('本页高亮选中:', [...selectedIds]);
+  loadingFileList.value = true;
+  try {
+    const res = await listMinioData(dialogQueryParams.value);
+    minioDataList.value = res.rows.map((data) => ({
+      ...data,
+      size: (parseFloat(data.size) / 1024).toFixed(3) + 'MB'
+    }));
+    fileTotal.value = res.total;
+  } catch (e) {
+    console.error('文件列表加载失败', e);
+  } finally {
+    loadingFileList.value = false;
+    await nextTick();
+    // 高亮当前分屏
+    let selectedFilesRef;
+    if (screenNum.value === '1') selectedFilesRef = selectedFiles1;
+    if (screenNum.value === '2') selectedFilesRef = selectedFiles2;
+    if (screenNum.value === '3') selectedFilesRef = selectedFiles3;
+    if (screenNum.value === '4') selectedFilesRef = selectedFiles4;
+    if (selectedFilesRef?.value?.length) {
+      const selectedIds = new Set(selectedFilesRef.value.map((f) => f.id));
+      minioDataList.value.forEach((row) => {
+        if (selectedIds.has(row.id)) {
+          minioTable.value?.toggleRowSelection(row, true);
+        }
+      });
+    }
   }
 };