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 dateList = ref<string[]>([]);
|
||||||
const previewData = ref<Record<string, Record<number, 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 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) => {
|
const toggleTag = (id: number) => {
|
||||||
if (selectedTags.value.includes(id)) {
|
if (selectedTags.value.includes(id)) {
|
||||||
selectedTags.value = selectedTags.value.filter(tId => tId !== id);
|
selectedTags.value = selectedTags.value.filter(tId => tId !== id);
|
||||||
@@ -50,6 +102,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('mousedown', handleClickOutside);
|
window.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
|
document.removeEventListener('mouseup', onMouseUp);
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportStartCalendarDays = computed(() => {
|
const exportStartCalendarDays = computed(() => {
|
||||||
@@ -107,6 +161,17 @@ const handlePreview = async () => {
|
|||||||
}
|
}
|
||||||
dateList.value = dates;
|
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>> = {};
|
const matrix: Record<string, Record<number, string>> = {};
|
||||||
dates.forEach(d => matrix[d] = {});
|
dates.forEach(d => matrix[d] = {});
|
||||||
@@ -146,7 +211,6 @@ const copyColumn = async (tagId: number | 'date') => {
|
|||||||
lines.push(date);
|
lines.push(date);
|
||||||
} else {
|
} else {
|
||||||
let cellStr = previewData.value[date][tagId] || "";
|
let cellStr = previewData.value[date][tagId] || "";
|
||||||
// Excel/Sheets 处理带换行符的单元格,需要用双引号包围
|
|
||||||
if (cellStr.includes('\n') || cellStr.includes('\t') || cellStr.includes('"')) {
|
if (cellStr.includes('\n') || cellStr.includes('\t') || cellStr.includes('"')) {
|
||||||
cellStr = `"${cellStr.replace(/"/g, '""')}"`;
|
cellStr = `"${cellStr.replace(/"/g, '""')}"`;
|
||||||
}
|
}
|
||||||
@@ -161,6 +225,30 @@ const copyColumn = async (tagId: number | 'date') => {
|
|||||||
showToast("复制失败", "error");
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -251,33 +339,44 @@ const copyColumn = async (tagId: number | 'date') => {
|
|||||||
{{ isDesc ? '倒序 (由近及远)' : '正序 (由远及近)' }}
|
{{ isDesc ? '倒序 (由近及远)' : '正序 (由远及近)' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="flex-1 overflow-auto border border-border-main rounded-2xl bg-bg-input/30 relative select-text">
|
<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-full text-left border-collapse text-xs">
|
<table class="w-max min-w-full text-left border-collapse text-xs table-fixed">
|
||||||
<thead class="bg-bg-card sticky top-0 z-10 shadow-sm">
|
<thead class="bg-bg-card sticky top-0 z-30 shadow-sm">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="p-4 border-b border-border-main font-bold whitespace-nowrap text-text-sec w-32 group">
|
<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">
|
<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>
|
||||||
|
<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>
|
||||||
<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' }">
|
<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">
|
<div class="flex items-center gap-2 overflow-hidden w-full">
|
||||||
{{ getTagName(tagId) }}
|
{{ 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>
|
||||||
|
<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>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-bg-card">
|
<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">
|
<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">
|
<td class="relative p-4 font-bold whitespace-nowrap text-text-main align-top group">
|
||||||
{{ date }}
|
<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>
|
||||||
<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]">
|
<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">
|
||||||
{{ previewData[date]?.[tagId] || '' }}
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user