support resize

This commit is contained in:
Julian Freeman
2026-03-26 20:40:34 -04:00
parent 5f14a0dd10
commit 3fe7f013dc

View File

@@ -23,9 +23,61 @@ const selectedTags = ref<number[]>(mainTags.value.map(t => t.id));
// 表格数据
const dateList = ref<string[]>([]);
const previewData = ref<Record<string, Record<number, string>>>({});
const isDesc = ref(false);
const isDesc = ref(true);
const sortedDateList = computed(() => isDesc.value ? [...dateList.value].reverse() : dateList.value);
// 拖拽调整宽高状态
const colWidths = ref<Record<string | number, number>>({});
const rowHeights = ref<Record<string, number>>({});
const isDragging = ref(false);
let dragType: 'col' | 'row' | null = null;
let dragKey: string | number | null = null;
let startPos = 0;
let startSize = 0;
const MIN_COL_WIDTH = 120;
const MIN_ROW_HEIGHT = 48;
const startResizeCol = (e: MouseEvent, key: string | number) => {
isDragging.value = true;
dragType = 'col';
dragKey = key;
startPos = e.clientX;
startSize = colWidths.value[key] || MIN_COL_WIDTH;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const startResizeRow = (e: MouseEvent, key: string) => {
isDragging.value = true;
dragType = 'row';
dragKey = key;
startPos = e.clientY;
startSize = rowHeights.value[key] || (e.target as HTMLElement).closest('td')?.offsetHeight || MIN_ROW_HEIGHT;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const onMouseMove = (e: MouseEvent) => {
if (!isDragging.value || dragKey === null) return;
if (dragType === 'col') {
const delta = e.clientX - startPos;
colWidths.value[dragKey] = Math.max(MIN_COL_WIDTH, startSize + delta);
} else if (dragType === 'row') {
const delta = e.clientY - startPos;
rowHeights.value[dragKey as string] = Math.max(MIN_ROW_HEIGHT, startSize + delta);
}
};
const onMouseUp = () => {
isDragging.value = false;
dragType = null;
dragKey = null;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
const toggleTag = (id: number) => {
if (selectedTags.value.includes(id)) {
selectedTags.value = selectedTags.value.filter(tId => tId !== id);
@@ -50,6 +102,8 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
});
const exportStartCalendarDays = computed(() => {
@@ -107,6 +161,17 @@ const handlePreview = async () => {
}
dateList.value = dates;
// 初始化列宽
colWidths.value = { date: 150 };
selectedTags.value.forEach(tagId => {
colWidths.value[tagId] = 200;
});
// 初始化统一行高
rowHeights.value = {};
dates.forEach(d => {
rowHeights.value[d] = 100;
});
// 过滤选中的主标签并构建矩阵
const matrix: Record<string, Record<number, string>> = {};
dates.forEach(d => matrix[d] = {});
@@ -146,7 +211,6 @@ const copyColumn = async (tagId: number | 'date') => {
lines.push(date);
} else {
let cellStr = previewData.value[date][tagId] || "";
// Excel/Sheets 处理带换行符的单元格,需要用双引号包围
if (cellStr.includes('\n') || cellStr.includes('\t') || cellStr.includes('"')) {
cellStr = `"${cellStr.replace(/"/g, '""')}"`;
}
@@ -161,6 +225,30 @@ const copyColumn = async (tagId: number | 'date') => {
showToast("复制失败", "error");
}
};
const copyToClipboard = async () => {
const header = ["日期", ...selectedTags.value.map(id => getTagName(id))];
let tsv = header.join("\t") + "\n";
for (const date of sortedDateList.value) {
let row = [date];
for (const tagId of selectedTags.value) {
let cellStr = previewData.value[date][tagId] || "";
if (cellStr.includes('\n') || cellStr.includes('\t') || cellStr.includes('"')) {
cellStr = `"${cellStr.replace(/"/g, '""')}"`;
}
row.push(cellStr);
}
tsv += row.join("\t") + "\n";
}
try {
await navigator.clipboard.writeText(tsv);
showToast("表格已复制,可直接粘贴到 Excel");
} catch(err) {
showToast("复制失败", "error");
}
};
</script>
<template>
@@ -251,33 +339,44 @@ const copyColumn = async (tagId: number | 'date') => {
{{ isDesc ? '倒序 (由近及远)' : '正序 (由远及近)' }}
</button>
</div>
<button @click="copyToClipboard" class="flex items-center gap-1.5 px-4 py-2 bg-[#007AFF] hover:bg-[#007AFF]/90 text-white rounded-xl text-xs font-bold transition-all shadow-lg shadow-[#007AFF]/20 active:scale-95">
<Copy :size="14" /> 一键复制完整表格
</button>
</div>
<div class="flex-1 overflow-auto border border-border-main rounded-2xl bg-bg-input/30 relative select-text">
<table class="w-full text-left border-collapse text-xs">
<thead class="bg-bg-card sticky top-0 z-10 shadow-sm">
<div class="flex-1 overflow-auto border border-border-main rounded-2xl bg-bg-input/30 relative" :class="isDragging ? 'select-none' : 'select-text'">
<table class="w-max min-w-full text-left border-collapse text-xs table-fixed">
<thead class="bg-bg-card sticky top-0 z-30 shadow-sm">
<tr>
<th class="p-4 border-b border-border-main font-bold whitespace-nowrap text-text-sec w-32 group">
<div class="flex items-center gap-2">
<th class="relative p-4 border-b border-border-main font-bold whitespace-nowrap text-text-sec bg-bg-card group" :style="{ width: colWidths['date'] + 'px' }">
<div class="flex items-center gap-2 overflow-hidden w-full">
日期
<button @click="copyColumn('date')" title="复制此列" class="opacity-0 group-hover:opacity-100 p-1 hover:bg-bg-input rounded transition-all text-text-sec"><Copy :size="12"/></button>
<button @click="copyColumn('date')" title="复制此列" class="opacity-0 group-hover:opacity-100 p-1 hover:bg-bg-input rounded transition-all text-text-sec flex-shrink-0"><Copy :size="12"/></button>
</div>
<div class="absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-[#007AFF] z-40 group-hover:bg-border-main" @mousedown.prevent="startResizeCol($event, 'date')"></div>
</th>
<th v-for="tagId in selectedTags" :key="tagId" class="p-4 border-b border-l border-border-main/50 font-bold whitespace-nowrap group" :style="{ color: mainTags.find(t => t.id === tagId)?.color || 'inherit' }">
<div class="flex items-center gap-2">
<th v-for="tagId in selectedTags" :key="tagId" class="relative p-4 border-b border-l border-border-main/50 font-bold whitespace-nowrap bg-bg-card group" :style="{ color: mainTags.find(t => t.id === tagId)?.color || 'inherit', width: colWidths[tagId] + 'px' }">
<div class="flex items-center gap-2 overflow-hidden w-full">
{{ getTagName(tagId) }}
<button @click="copyColumn(tagId)" title="复制此列" class="opacity-0 group-hover:opacity-100 p-1 hover:bg-bg-input rounded transition-all text-text-sec"><Copy :size="12"/></button>
<button @click="copyColumn(tagId)" title="复制此列" class="opacity-0 group-hover:opacity-100 p-1 hover:bg-bg-input rounded transition-all text-text-sec flex-shrink-0"><Copy :size="12"/></button>
</div>
<div class="absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-[#007AFF] z-40 group-hover:bg-border-main" @mousedown.prevent="startResizeCol($event, tagId)"></div>
</th>
</tr>
</thead>
<tbody class="bg-bg-card">
<tr v-for="date in sortedDateList" :key="date" class="border-b border-border-main/30 hover:bg-bg-input/50 transition-colors">
<td class="p-4 font-bold whitespace-nowrap text-text-main align-top">
{{ date }}
<td class="relative p-4 font-bold whitespace-nowrap text-text-main align-top group">
<div class="flex items-start overflow-hidden w-full" :style="{ height: rowHeights[date] ? Math.max(0, rowHeights[date] - 32) + 'px' : 'auto', minHeight: '16px' }">
{{ date }}
</div>
<div class="absolute bottom-0 left-0 right-0 h-1.5 cursor-row-resize hover:bg-[#007AFF] z-20 group-hover:bg-border-main" @mousedown.prevent="startResizeRow($event, date)"></div>
</td>
<td v-for="tagId in selectedTags" :key="tagId" class="p-4 border-l border-border-main/30 align-top whitespace-pre-wrap leading-relaxed text-text-sec min-w-[150px]">
{{ previewData[date]?.[tagId] || '' }}
<td v-for="tagId in selectedTags" :key="tagId" class="relative p-4 border-l border-border-main/30 align-top whitespace-pre-wrap leading-relaxed text-text-sec group">
<div class="w-full overflow-y-auto no-scrollbar break-words" :style="{ height: rowHeights[date] ? Math.max(0, rowHeights[date] - 32) + 'px' : 'auto', minHeight: '16px' }">
{{ previewData[date]?.[tagId] || '' }}
</div>
<div class="absolute bottom-0 left-0 right-0 h-1.5 cursor-row-resize hover:bg-[#007AFF] z-20 group-hover:bg-border-main" @mousedown.prevent="startResizeRow($event, date)"></div>
</td>
</tr>
</tbody>
@@ -288,4 +387,4 @@ const copyColumn = async (tagId: number | 'date') => {
</div>
</div>
</div>
</template>
</template>