support resize
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user