FileManage/web/src/components/textEditing/txtexl.vue

216 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="text-viewer" :class="{ 'full-screen': isFullscreen }">
<!-- 在顶部添加控制按钮 -->
<div class="controls">
<!-- 在controls区块添加 -->
<button @click="saveChanges" style="margin-right: 8px;">
保存修改
</button>
<button @click="toggleFullscreen">
{{ isFullscreen ? '退出全屏' : '全屏' }}
</button>
</div>
<div v-if="error" class="error">{{ error }}</div>
<div v-else>
<div v-if="isLoading" class="loading">加载进度:{{ loadedPercent }}%</div>
<div class="scroll-container" ref="scrollContainer" @scroll="handleScroll"
:style="{ height: containerHeight + 'px' }">
<div class="phantom" :style="{ height: totalHeight + 'px' }">
<div v-for="row in visibleRows" :key="row.index" class="row" :style="row.style">
<div v-for="(cell, i) in row.data" :key="i" class="cell">
<input :value="rawData[row.index][i]" @blur="(e) => handleEdit(row.index, i, e)"
class="edit-input">
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount } from 'vue';
import { apicontent, batchModify } from "@/api/datamanagement";
import { ElMessage } from "element-plus";
const props = defineProps({
fileUrl: {
type: String,
required: true
},
rowId: {
type: String,
default: false
},
});
// 配置项
const CHUNK_SIZE = 1024 * 1024; // 1MB分块处理
const ROW_HEIGHT = 40; // 行高
const VISIBLE_BUFFER = 200; // 可视区外缓冲行数
// 响应式状态
const isLoading = ref(false);
const error = ref(null);
const rawData = reactive([]); // 原始数据存储
const scrollTop = ref(0);
const totalRows = ref(0);
const reader = ref(null); // 保持reader引用
const loadedPercent = ref(0);
// 新增响应式状态
const isFullscreen = ref(false);
// 新增方法
const toggleFullscreen = () => {
isFullscreen.value = !isFullscreen.value;
// 可选使用浏览器全屏API根据需求选择其中一种方式
// if (isFullscreen.value && document.documentElement.requestFullscreen) {
// document.documentElement.requestFullscreen();
// } else if (document.exitFullscreen) {
// document.exitFullscreen();
// }
};
// 修改容器高度计算逻辑
const containerHeight = computed(() => {
return isFullscreen.value ? window.innerHeight : window.innerHeight * 0.8;
});
const startIndex = computed(() =>
Math.max(0, Math.floor(scrollTop.value / ROW_HEIGHT) - VISIBLE_BUFFER)
);
const endIndex = computed(() =>
Math.min(totalRows.value, startIndex.value + Math.ceil(containerHeight.value / ROW_HEIGHT) + VISIBLE_BUFFER * 2)
);
const visibleRows = computed(() => {
// console.log(rawData)
return rawData.slice(startIndex.value, endIndex.value).map((row, index) => ({
index: startIndex.value + index,
data: row,
style: {
transform: `translateY(${(startIndex.value + index) * ROW_HEIGHT}px)`
}
}));
});
const totalHeight = computed(() => totalRows.value * ROW_HEIGHT);
// 方法
const handleScroll = (e) => {
scrollTop.value = e.target.scrollTop;
};
const abortLoading = () => {
if (reader.value) {
reader.value.cancel();
reader.value = null;
}
};
const processChunk = (chunk, isFinal) => {
const lines = chunk.split('\n');
// const lines = chunk.split('\n').map(line => line.replace(/\t/g, ' ')); // 新增替换逻辑
if (!isFinal) {
// 保留未完成的行
const lastLine = lines.pop() || '';
rawData.push(...lines.filter(l => l).map(line => line.split('\t')));
return lastLine;
}
rawData.push(...lines.filter(l => l).map(line => line.split('\t')));
return '';
};
const loadStream = async () => {
// debugger
try {
isLoading.value = true;
error.value = null;
rawData.length = 0;
totalRows.value = 0;
const response = await fetch(props.fileUrl);
if (!response.body) throw new Error('不支持流式读取');
const contentLength = response.headers.get('content-length');
let receivedLength = 0;
let leftover = '';
reader.value = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.value.read();
if (done) break;
receivedLength += value.length;
if (contentLength) {
loadedPercent.value = Math.round((receivedLength / contentLength) * 100);
}
const chunk = decoder.decode(value, { stream: true });
// debugger
leftover = processChunk(leftover + chunk, false);
}
// 处理剩余数据
if (leftover) processChunk(leftover, true);
totalRows.value = rawData.length;
} catch (err) {
if (err.name !== 'AbortError') {
error.value = `加载失败:${err.message}`;
}
} finally {
isLoading.value = false;
reader.value = null;
}
};
// 监听URL变化
watch(() => props.fileUrl, (newVal) => {
if (newVal) {
abortLoading();
loadStream();
}
});
// 生命周期
onBeforeUnmount(() => {
abortLoading();
});
// 初始化加载
onMounted(() => {
if (props.fileUrl) loadStream();
});
const tabledata = ref([])
// 修改后的处理方法
const handleEdit = (rowIndex, colIndex, event) => {
const oldValue = rawData[rowIndex][colIndex];
const newValue1 = event.target.value.trim();
if (newValue1 !== oldValue) {
console.log(`修改了第${rowIndex + 1}行第${colIndex + 1}`, `旧值: ${oldValue} => 新值: ${newValue1}`);
rawData[rowIndex][colIndex] = newValue1; // 手动更新保证数据同步
tabledata.value.push({ lineNum: rowIndex + 1, colNum: colIndex + 1, newValue: newValue1 })
// debugger
}
};
// 在script中添加
const saveChanges = () => {
// 构造表格数据(示例使用\t分隔
// const csvData = rawData.map(row => row.join('\t')).join('\n');
batchModify({ id: props.rowId, modifications: tabledata.value }).then((res) => {
ElMessage.success('保存成功')
if (props.fileUrl) {
abortLoading();
loadStream()
tabledata.value.length = 0
}
})
};
</script>
<style>
</style>