|
|
@@ -0,0 +1,188 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <el-button type="primary" @click="dialogVisible = true">选择文件</el-button>
|
|
|
+ <div v-if="selectedFiles.length > 0" class="selected-files-list">
|
|
|
+ <el-tag v-for="(file, idx) in selectedFiles" :key="file.id" closable @close="removeFile(idx)" style="margin: 2px">
|
|
|
+ {{ file.name }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <el-dialog title="选择媒资文件" v-model="dialogVisible" width="900px" append-to-body @close="restoreSelection">
|
|
|
+ <el-form :inline="true" :model="queryParams" class="mb-2">
|
|
|
+ <el-form-item label="名称">
|
|
|
+ <el-input v-model="queryParams.originalName" placeholder="文件名" clearable style="width: 180px"
|
|
|
+ @keyup.enter="getFileList" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="类型">
|
|
|
+ <el-select v-model="queryParams.type" clearable placeholder="全部" style="width: 120px">
|
|
|
+ <el-option label="全部" :value="''" />
|
|
|
+ <el-option label="图片" :value="1" />
|
|
|
+ <el-option label="视频" :value="2" />
|
|
|
+ <el-option label="音频" :value="3" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="getFileList">搜索</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <el-table v-loading="dialogLoading" ref="fileTable" :data="fileList" reserve-selection row-key="id"
|
|
|
+ @selection-change="handleSelectionFile" @select="handleSelect" @select-all="handleSelectAll">
|
|
|
+ <el-table-column type="selection" width="55" header-align="center" />
|
|
|
+ <el-table-column label="类型" header-align="center" prop="type" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="smsb_source_type" :value="scope.row.type" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="原名" header-align="left" prop="originalName" width="150" :show-overflow-tooltip="true" />
|
|
|
+ <el-table-column label="大小" header-align="center" prop="size" />
|
|
|
+ <el-table-column label="时长" header-align="center" prop="duration" />
|
|
|
+ <el-table-column label="截图" header-align="center" prop="screenshot">
|
|
|
+ <template #default="scope">
|
|
|
+ <image-preview :src="scope.row.screenshot" style="width: 40px; height: 40px; cursor: pointer" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <pagination v-show="fileTotal > 0" :total="fileTotal" v-model:page="queryParams.pageNum"
|
|
|
+ v-model:limit="queryParams.pageSize" @pagination="getFileList" />
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmSelect">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
+const { smsb_source_type } = toRefs<any>(proxy?.useDict('smsb_source_type'));
|
|
|
+import { ref, watch, defineProps, defineEmits, nextTick } from 'vue';
|
|
|
+import { listMinioData } from '@/api/smsb/source/minioData';
|
|
|
+import type { MinioDataVO, MinioDataQuery } from '@/api/smsb/source/minioData_type';
|
|
|
+
|
|
|
+const props = defineProps<{ modelValue: string }>();
|
|
|
+const emit = defineEmits(['update:modelValue']);
|
|
|
+
|
|
|
+const dialogVisible = ref(false);
|
|
|
+const dialogLoading = ref(false);
|
|
|
+const fileList = ref<MinioDataVO[]>([]);
|
|
|
+const fileTotal = ref(0);
|
|
|
+const selectedFiles = ref<any[]>([]);
|
|
|
+const fileTable = ref();
|
|
|
+
|
|
|
+const queryParams = ref<MinioDataQuery>({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ originalName: '',
|
|
|
+ type: ''
|
|
|
+});
|
|
|
+
|
|
|
+// 初始化时,如果有值,反序列化
|
|
|
+watch(
|
|
|
+ () => props.modelValue,
|
|
|
+ (val) => {
|
|
|
+ if (val) {
|
|
|
+ try {
|
|
|
+ selectedFiles.value = JSON.parse(val);
|
|
|
+ } catch {
|
|
|
+ selectedFiles.value = [];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ selectedFiles.value = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+// 查询文件资源列表
|
|
|
+const getFileList = async () => {
|
|
|
+ dialogLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await listMinioData(queryParams.value);
|
|
|
+ console.log('[getFileList] raw:', res);
|
|
|
+ console.log('[getFileList] rows:', res.rows);
|
|
|
+ const mapped = (res.rows || []).map((item: any, idx: number) => {
|
|
|
+ const sizeNum = Number(item.size);
|
|
|
+ const sizeStr = !isNaN(sizeNum) && sizeNum > 0 ? (sizeNum / 1024).toFixed(3) + 'MB' : '0MB';
|
|
|
+ if (isNaN(sizeNum)) {
|
|
|
+ console.warn(`[getFileList] row[${idx}] 非法size:`, item.size, item);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ size: sizeStr
|
|
|
+ };
|
|
|
+ });
|
|
|
+ fileList.value = mapped;
|
|
|
+ console.log('[getFileList] mapped fileList:', mapped);
|
|
|
+ console.log('[getFileList] fileList.value:', fileList.value, 'isArray:', Array.isArray(fileList.value), 'length:', fileList.value.length);
|
|
|
+
|
|
|
+ fileTotal.value = res.total || 0;
|
|
|
+ await nextTick();
|
|
|
+ restoreSelection();
|
|
|
+ } finally {
|
|
|
+ dialogLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 多选框选中文件数据
|
|
|
+function handleSelectionFile(selection: MinioDataVO[]) {
|
|
|
+ // 只做新增
|
|
|
+ selection.forEach((item) => {
|
|
|
+ if (!selectedFiles.value.some((f) => String(f.id) === String(item.id))) {
|
|
|
+ selectedFiles.value.push({
|
|
|
+ id: item.id,
|
|
|
+ name: item.originalName,
|
|
|
+ type: item.type,
|
|
|
+ duration: item.type === 1 ? 10 : item.duration,
|
|
|
+ order: 0
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ selectedFiles.value = selectedFiles.value.map((f, idx) => ({ ...f, order: idx + 1 }));
|
|
|
+}
|
|
|
+// 取消单个选中
|
|
|
+function handleSelect(selection: MinioDataVO[], row: MinioDataVO) {
|
|
|
+ if (!selection.some((item) => String(item.id) === String(row.id))) {
|
|
|
+ selectedFiles.value = selectedFiles.value.filter((f) => String(f.id) !== String(row.id));
|
|
|
+ selectedFiles.value = selectedFiles.value.map((f, idx) => ({ ...f, order: idx + 1 }));
|
|
|
+ }
|
|
|
+}
|
|
|
+// 取消全选
|
|
|
+function handleSelectAll(selection: MinioDataVO[]) {
|
|
|
+ const currentPageIds = new Set(fileList.value.map((item) => String(item.id)));
|
|
|
+ const selectedIds = new Set(selection.map((item) => String(item.id)));
|
|
|
+ selectedFiles.value = selectedFiles.value.filter((f) => !currentPageIds.has(String(f.id)) || selectedIds.has(String(f.id)));
|
|
|
+ selectedFiles.value = selectedFiles.value.map((f, idx) => ({ ...f, order: idx + 1 }));
|
|
|
+}
|
|
|
+
|
|
|
+function removeFile(idx: number) {
|
|
|
+ selectedFiles.value.splice(idx, 1);
|
|
|
+ emitChange();
|
|
|
+}
|
|
|
+function confirmSelect() {
|
|
|
+ dialogVisible.value = false;
|
|
|
+ emitChange();
|
|
|
+}
|
|
|
+function emitChange() {
|
|
|
+ emit('update:modelValue', JSON.stringify(selectedFiles.value));
|
|
|
+}
|
|
|
+// 弹窗关闭时还原选中状态
|
|
|
+function restoreSelection() {
|
|
|
+ nextTick(() => {
|
|
|
+ if (!fileTable.value) return;
|
|
|
+ const selectedIds = new Set(selectedFiles.value.map((f) => String(f.id)));
|
|
|
+ fileList.value.forEach((row) => {
|
|
|
+ fileTable.value.toggleRowSelection(row, selectedIds.has(String(row.id)));
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 弹窗首次打开时自动加载
|
|
|
+watch(dialogVisible, (val) => {
|
|
|
+ if (val) getFileList();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.selected-files-list {
|
|
|
+ margin: 8px 0;
|
|
|
+}
|
|
|
+</style>
|