feat(import): support configurable file row ranges

This commit is contained in:
t8y2 committed 2026-07-12 23:14:23 +08:00
1 parent 9a09bd49cb
commit 4b44ccf86e
5 files changed
+239 -143

No files matched your search

@@ -72,7 +72,9 @@ const errorMessage = ref("");
const wizardStep = ref<TableImportWizardStep>("source");
const fileInput = ref<HTMLInputElement | null>(null);
const delimiter = ref(",");
const hasHeader = ref(true);
const titleRow = ref(1);
const dataStartRow = ref(2);
const lastDataRow = ref(0);
const trimValues = ref(false);
const emptyStringAsNull = ref(true);
const selectedSheet = ref("");
@@ -167,7 +169,9 @@ const createColumnSummaries = computed(() =>
);
const parseOptions = computed<api.TableImportParseOptions>(() => ({
delimiter: sourceFormat.value === "tsv" ? "\\t" : sourceFormat.value === "csv" ? "," : delimiter.value,
hasHeader: hasHeader.value,
titleRow: titleRow.value,
dataStartRow: dataStartRow.value,
lastDataRow: lastDataRow.value,
trimValues: trimValues.value,
emptyStringAsNull: emptyStringAsNull.value,
sheetName: sourceFormat.value === "excel" ? selectedSheet.value || null : null,
@@ -184,7 +188,9 @@ function resetState() {
activeTaskIndex.value = 0;
sourceFormat.value = "csv";
delimiter.value = ",";
hasHeader.value = true;
titleRow.value = 1;
dataStartRow.value = 2;
lastDataRow.value = 0;
trimValues.value = false;
emptyStringAsNull.value = true;
selectedSheet.value = "";
@@ -234,7 +240,9 @@ function uniqueTableName(baseName: string, usedNames: Set<string>): string {
function taskParseOptions(format: api.TableImportSourceFormat, sheetName = ""): api.TableImportParseOptions {
return {
delimiter: format === "tsv" ? "\\t" : format === "csv" ? "," : delimiter.value,
hasHeader: hasHeader.value,
titleRow: titleRow.value,
dataStartRow: dataStartRow.value,
lastDataRow: lastDataRow.value,
trimValues: trimValues.value,
emptyStringAsNull: emptyStringAsNull.value,
sheetName: format === "excel" ? sheetName || null : null,
@@ -716,7 +724,7 @@ watch(
{ immediate: true },
);
watch([sourceFormat, delimiter, hasHeader, trimValues, emptyStringAsNull, selectedSheet, jsonShape, previewLimit], schedulePreviewReload);
watch([sourceFormat, delimiter, titleRow, dataStartRow, lastDataRow, trimValues, emptyStringAsNull, selectedSheet, jsonShape, previewLimit], schedulePreviewReload);
watch([newTableName, columnMapping, columnDataTypes], saveActiveBatchTask, { deep: true });
watch(targetMode, (mode) => {
if (mode === "existing") {
@@ -883,10 +891,18 @@ watch(targetMode, (mode) => {
<Label class="text-xs">{{ t("tableImport.delimiter") }}</Label>
<Input v-model="delimiter" :disabled="sourceFormat !== 'delimited'" class="h-8 text-xs font-mono" />
</div>
<label class="flex items-center gap-2 text-xs">
<input v-model="hasHeader" type="checkbox" class="h-3.5 w-3.5 accent-primary" />
{{ t("tableImport.hasHeader") }}
</label>
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.titleRow") }}</Label>
<Input v-model.number="titleRow" type="number" min="0" class="h-8 text-xs" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.dataStartRow") }}</Label>
<Input v-model.number="dataStartRow" type="number" min="1" class="h-8 text-xs" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.lastDataRow") }}</Label>
<Input v-model.number="lastDataRow" type="number" min="0" class="h-8 text-xs" />
</div>
<label class="flex items-center gap-2 text-xs">
<input v-model="trimValues" type="checkbox" class="h-3.5 w-3.5 accent-primary" />
{{ t("tableImport.trimValues") }}
@@ -913,7 +929,7 @@ watch(targetMode, (mode) => {
</div>
</div>
<div v-else-if="sourceFormat === 'excel'" class="grid grid-cols-3 gap-3 rounded-md border p-3">
<div v-else-if="sourceFormat === 'excel'" class="grid grid-cols-4 gap-3 rounded-md border p-3">
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.sheet") }}</Label>
<Select :model-value="selectedSheet" :disabled="!preview?.sheets?.length" @update:model-value="(value: any) => (selectedSheet = value)">
@@ -925,10 +941,18 @@ watch(targetMode, (mode) => {
</SelectContent>
</Select>
</div>
<label class="flex items-center gap-2 text-xs">
<input v-model="hasHeader" type="checkbox" class="h-3.5 w-3.5 accent-primary" />
{{ t("tableImport.hasHeader") }}
</label>
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.titleRow") }}</Label>
<Input v-model.number="titleRow" type="number" min="0" class="h-8 text-xs" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.dataStartRow") }}</Label>
<Input v-model.number="dataStartRow" type="number" min="1" class="h-8 text-xs" />
</div>
<div class="space-y-1.5">
<Label class="text-xs">{{ t("tableImport.lastDataRow") }}</Label>
<Input v-model.number="lastDataRow" type="number" min="0" class="h-8 text-xs" />
</div>
</div>
<div class="flex items-center gap-2">
+3
View File
@@ -2554,6 +2554,9 @@ export default {
previewRows: "Preview rows",
delimiter: "Delimiter",
hasHeader: "First row is header",
titleRow: "Title row (0 for none)",
dataStartRow: "Data start row",
lastDataRow: "Last data row (0 for end)",
trimValues: "Trim values",
emptyStringAsNull: "Empty string as NULL",
jsonShape: "JSON rows",
+3
View File
@@ -2553,6 +2553,9 @@ export default withEnglishFallback({
previewRows: "预览行数",
delimiter: "分隔符",
hasHeader: "首行为表头",
titleRow: "标题行(0 表示无)",
dataStartRow: "数据开始行",
lastDataRow: "最后数据行(0 表示末尾)",
trimValues: "裁剪空白",
emptyStringAsNull: "空字符串作为 NULL",
jsonShape: "JSON 行结构",
+3
View File
@@ -2094,6 +2094,9 @@ export interface TableImportColumnMapping {
export interface TableImportParseOptions {
delimiter?: string | null;
hasHeader?: boolean | null;
titleRow?: number | null;
dataStartRow?: number | null;
lastDataRow?: number | null;
trimValues?: boolean | null;
emptyStringAsNull?: boolean | null;
sheetName?: string | null;