feat(grid): 新增原始数据复制模式并设为默认
* ✨ feat(grid): 新增原始数据复制模式并设为默认 - 原样输出单元格内容,不对引号、制表符和换行进行 DSV 转义 - 支持原始数据复制后的网格粘贴还原 - 补充多语言复制选项文案及前后端提取器实现与测试 * ✨ feat(grid): 优化新增智能复制选项 - 单个单元格复制保留原始单元格文本,多单元格自动使用转义 TSV - 默认复制设置由格式选择升级为复制行为选择,并支持预览 - 限制原始文本格式仅用于单单元格复制 - 更新多语言文案及相关测试
This commit is contained in:
24 files changed
+333
-91
No files matched your search
@@ -1640,10 +1640,10 @@ defineExpose({ focusSearch });
|
||||
</span>
|
||||
</label>
|
||||
<DataGridCopyFormatControl
|
||||
:current-label="dataGridRef?.defaultCopyExtractorLabel ?? '-'"
|
||||
:current-value="dataGridRef?.defaultCopyExtractor ?? ''"
|
||||
:items="dataGridRef?.copyExtractorMenuItems ?? []"
|
||||
@select="dataGridRef?.setDefaultCopyExtractor($event)"
|
||||
:current-label="dataGridRef?.defaultCopyPreferenceLabel ?? '-'"
|
||||
:current-value="dataGridRef?.defaultCopyPreference ?? ''"
|
||||
:items="dataGridRef?.copyPreferenceMenuItems ?? []"
|
||||
@select="dataGridRef?.setDefaultCopyPreference($event)"
|
||||
@configure="openDataGridExtractorConfiguration"
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -189,7 +189,7 @@ import { useDataGridExport, type MongoCopyUpdateTarget } from "@/composables/use
|
||||
import { eventTargetAllowsNativeClipboard, isPlainClipboardShortcut, readTextFromClipboard } from "@/lib/common/clipboard";
|
||||
import { claimDataGridPaste, claimDataGridSelectAll, clearDataGridClipboardCopy, parseDataGridClipboard, planDataGridPaste } from "@/lib/dataGrid/dataGridClipboard";
|
||||
import { beginDataGridNativeSelectionBlock, finishDataGridNativeSelectionBlock } from "@/lib/dataGrid/dataGridNativeSelection";
|
||||
import { DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS, DATA_GRID_COPY_EXTRACTOR_IDS, extractorUnavailableForDatabase, type DataGridCopyExtractorId } from "@/lib/dataGrid/dataGridCopyExtractor";
|
||||
import { DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS, DATA_GRID_COPY_EXTRACTOR_IDS, DATA_GRID_DEFAULT_COPY_PREFERENCES, extractorUnavailableForDatabase, type DataGridCopyExtractorId, type DataGridCopyPreference } from "@/lib/dataGrid/dataGridCopyExtractor";
|
||||
import { columnNamesForCopy } from "@/lib/dataGrid/dataGridColumnNameCopy";
|
||||
import { DATA_GRID_ROW_NUM_WIDTH, dataGridRowNumberColumnWidth, resolveDataGridMaxRowNumber, useDataGridColumnResize } from "@/composables/useDataGridColumnResize";
|
||||
import { createDataGridColumnStructureSignature } from "@/lib/dataGrid/dataGridColumnWidthState";
|
||||
@@ -5497,7 +5497,8 @@ const {
|
||||
canCopyRow,
|
||||
copyAll,
|
||||
copyWithExtractor,
|
||||
previewWithExtractor,
|
||||
copyWithPreference,
|
||||
previewWithPreference,
|
||||
canCopyWithExtractor,
|
||||
exportCsv,
|
||||
exportCurrentPageCsv,
|
||||
@@ -5566,6 +5567,7 @@ const {
|
||||
|
||||
function copyExtractorLabel(extractor: DataGridCopyExtractorId): string {
|
||||
const labels: Record<DataGridCopyExtractorId, string> = {
|
||||
raw: t("grid.copyExtractorRaw"),
|
||||
tsv: "TSV",
|
||||
"tsv-with-headers": t("grid.copyExtractorWithHeaders", { format: "TSV" }),
|
||||
csv: "CSV",
|
||||
@@ -5587,28 +5589,34 @@ function copyExtractorLabel(extractor: DataGridCopyExtractorId): string {
|
||||
return labels[extractor];
|
||||
}
|
||||
|
||||
const selectedCopyExtractor = computed(() => settingsStore.editorSettings.dataGridCopyExtractor);
|
||||
const defaultCopyExtractorLabel = computed(() => {
|
||||
if (selectedCopyExtractor.value !== "sql-inserts") return copyExtractorLabel(selectedCopyExtractor.value);
|
||||
function copyPreferenceLabel(preference: DataGridCopyPreference): string {
|
||||
if (preference === "smart") return t("grid.copyExtractorSmart");
|
||||
if (preference !== "sql-inserts") return copyExtractorLabel(preference);
|
||||
return t(settingsStore.editorSettings.dataGridExtractorOptions.sql.excludePrimaryKeysFromInsert ? "grid.copyExtractorSqlInsertsWithoutPrimaryKeys" : "grid.copyExtractorSqlInsertsWithPrimaryKeys");
|
||||
});
|
||||
}
|
||||
|
||||
const selectedCopyPreference = computed(() => settingsStore.editorSettings.dataGridCopyExtractor);
|
||||
const defaultCopyPreferenceLabel = computed(() => copyPreferenceLabel(selectedCopyPreference.value));
|
||||
const extractorConfigOpen = ref(false);
|
||||
const extractorMenuItems = computed(() =>
|
||||
DATA_GRID_COPY_EXTRACTOR_IDS.map((extractor) => ({
|
||||
value: extractor,
|
||||
label: copyExtractorLabel(extractor),
|
||||
disabled: extractorUnavailableForDatabase(extractor, props.databaseType) || (extractor === "sql-updates" && !props.tableMeta?.primaryKeys.length),
|
||||
separatorBefore: DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS[extractor].separatorBefore,
|
||||
})),
|
||||
const copyPreferenceMenuItems = computed(() =>
|
||||
DATA_GRID_DEFAULT_COPY_PREFERENCES.map((preference) => {
|
||||
const extractor = preference === "smart" ? undefined : preference;
|
||||
return {
|
||||
value: preference,
|
||||
label: copyPreferenceLabel(preference),
|
||||
disabled: extractor ? extractorUnavailableForDatabase(extractor, props.databaseType) || (extractor === "sql-updates" && !props.tableMeta?.primaryKeys.length) : false,
|
||||
separatorBefore: extractor ? DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS[extractor].separatorBefore : false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
function openExtractorConfiguration() {
|
||||
extractorConfigOpen.value = true;
|
||||
}
|
||||
|
||||
function setDefaultCopyExtractor(value: string) {
|
||||
const item = extractorMenuItems.value.find((candidate) => candidate.value === value);
|
||||
function setDefaultCopyPreference(value: string) {
|
||||
const item = copyPreferenceMenuItems.value.find((candidate) => candidate.value === value);
|
||||
if (!item || item.disabled) return;
|
||||
settingsStore.updateEditorSettings({ dataGridCopyExtractor: item.value });
|
||||
settingsStore.updateEditorSettings({ dataGridCopyExtractor: item.value as DataGridCopyPreference });
|
||||
}
|
||||
|
||||
function sqlInsertExtractorOptions(excludePrimaryKeysFromInsert: boolean) {
|
||||
@@ -5619,8 +5627,8 @@ function sqlInsertExtractorOptions(excludePrimaryKeysFromInsert: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
function saveExtractorConfiguration(value: { extractor: DataGridCopyExtractorId; options: typeof settingsStore.editorSettings.dataGridExtractorOptions }) {
|
||||
settingsStore.updateEditorSettings({ dataGridCopyExtractor: value.extractor, dataGridExtractorOptions: value.options });
|
||||
function saveExtractorConfiguration(value: { preference: DataGridCopyPreference; options: typeof settingsStore.editorSettings.dataGridExtractorOptions }) {
|
||||
settingsStore.updateEditorSettings({ dataGridCopyExtractor: value.preference, dataGridExtractorOptions: value.options });
|
||||
}
|
||||
|
||||
const pageSizeMenuItems = computed(() =>
|
||||
@@ -6575,7 +6583,7 @@ async function onGridKeydown(event: KeyboardEvent) {
|
||||
if (clipboardShortcut(event, "c")) {
|
||||
if (!hasCellSelection.value && !hasRowSelection.value) return;
|
||||
event.preventDefault();
|
||||
await copyWithExtractor(selectedCopyExtractor.value);
|
||||
await copyWithPreference(selectedCopyPreference.value);
|
||||
return;
|
||||
}
|
||||
if (clipboardShortcut(event, "a")) {
|
||||
@@ -8187,10 +8195,10 @@ defineExpose({
|
||||
exportSql,
|
||||
exportXlsx,
|
||||
exportTxt,
|
||||
defaultCopyExtractor: selectedCopyExtractor,
|
||||
defaultCopyExtractorLabel,
|
||||
copyExtractorMenuItems: extractorMenuItems,
|
||||
setDefaultCopyExtractor,
|
||||
defaultCopyPreference: selectedCopyPreference,
|
||||
defaultCopyPreferenceLabel,
|
||||
copyPreferenceMenuItems,
|
||||
setDefaultCopyPreference,
|
||||
openExtractorConfiguration,
|
||||
});
|
||||
|
||||
@@ -8228,11 +8236,12 @@ function buildExtractorContextItems(): ContextMenuItem[] {
|
||||
const items: ContextMenuItem[] = [];
|
||||
let separatorPending = false;
|
||||
for (const extractor of DATA_GRID_COPY_EXTRACTOR_IDS) {
|
||||
if (extractor === "raw" && !canCopyWithExtractor(extractor)) continue;
|
||||
const descriptor = DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS[extractor];
|
||||
if (descriptor.separatorBefore) {
|
||||
separatorPending = items.length > 0;
|
||||
}
|
||||
const selected = extractor === selectedCopyExtractor.value;
|
||||
const selected = extractor === selectedCopyPreference.value;
|
||||
const extractorItems: ContextMenuItem[] = [];
|
||||
if (extractor === "sql-inserts") {
|
||||
for (const excludePrimaryKeysFromInsert of [false, true]) {
|
||||
@@ -8265,13 +8274,14 @@ function buildExtractorContextItems(): ContextMenuItem[] {
|
||||
return items;
|
||||
}
|
||||
|
||||
function defaultExtractorContextItem(): ContextMenuItem {
|
||||
const extractor = selectedCopyExtractor.value;
|
||||
const extractorOptions = extractor === "sql-inserts" ? sqlInsertExtractorOptions(settingsStore.editorSettings.dataGridExtractorOptions.sql.excludePrimaryKeysFromInsert) : undefined;
|
||||
function defaultCopyPreferenceContextItem(): ContextMenuItem {
|
||||
const preference = selectedCopyPreference.value;
|
||||
const extractorOptions = preference === "sql-inserts" ? sqlInsertExtractorOptions(settingsStore.editorSettings.dataGridExtractorOptions.sql.excludePrimaryKeysFromInsert) : undefined;
|
||||
const disabled = preference === "smart" ? !canCopyWithExtractor("raw", extractorOptions) && !canCopyWithExtractor("tsv", extractorOptions) : !canCopyWithExtractor(preference, extractorOptions);
|
||||
return {
|
||||
label: defaultCopyExtractorLabel.value,
|
||||
action: () => void copyWithExtractor(extractor, extractorOptions),
|
||||
disabled: !canCopyWithExtractor(extractor, extractorOptions),
|
||||
label: defaultCopyPreferenceLabel.value,
|
||||
action: () => void copyWithPreference(preference, extractorOptions),
|
||||
disabled,
|
||||
checked: true,
|
||||
};
|
||||
}
|
||||
@@ -8283,7 +8293,7 @@ function copySubmenu(): ContextMenuItem {
|
||||
icon: Settings2,
|
||||
action: openExtractorConfiguration,
|
||||
},
|
||||
defaultExtractorContextItem(),
|
||||
defaultCopyPreferenceContextItem(),
|
||||
{ label: "", separator: true },
|
||||
];
|
||||
if (contextColumn.value) {
|
||||
@@ -10047,7 +10057,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
<DataGridBulkEditDialog v-if="bulkEditDialogMounted" v-model:open="bulkEditDialogOpen" v-model:value="bulkEditValue" :selected-cell-count="selectedCellCount" @apply="applyBulkEditValue" />
|
||||
<DataGridInsertRowsDialog v-if="insertRowsDialogMounted" v-model:open="insertRowsDialogOpen" :can-place-at-selection="canPlaceInsertAtSelection" :initial-position="insertPosition" @insert="insertRows" />
|
||||
|
||||
<DataGridExtractorDialog v-model:open="extractorConfigOpen" :extractor="selectedCopyExtractor" :options="settingsStore.editorSettings.dataGridExtractorOptions" :items="extractorMenuItems" :preview="previewWithExtractor" @save="saveExtractorConfiguration" />
|
||||
<DataGridExtractorDialog v-model:open="extractorConfigOpen" :preference="selectedCopyPreference" :options="settingsStore.editorSettings.dataGridExtractorOptions" :items="copyPreferenceMenuItems" :preview="previewWithPreference" @save="saveExtractorConfiguration" />
|
||||
<DataGridCopyColumnNamesDialog v-if="copyColumnNamesDialogMounted" v-model:open="copyColumnNamesDialogOpen" :column-names="copyColumnNamesDialogColumns" :database-type="resolvedDatabaseType" :column-comments="columnCommentMap" @copy="copyText" />
|
||||
|
||||
<Dialog v-model:open="generateIncrementDialogOpen">
|
||||
|
||||
@@ -6,23 +6,32 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS, normalizeDataGridExtractorOptions, validateDataGridExtractorOptions, type DataGridCopyExtractorId, type DataGridExtractorOptions, type DataGridExtractPreview, type DataGridExtractWarningCode } from "@/lib/dataGrid/dataGridCopyExtractor";
|
||||
import {
|
||||
DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS,
|
||||
normalizeDataGridExtractorOptions,
|
||||
validateDataGridExtractorOptions,
|
||||
type DataGridCopyExtractorId,
|
||||
type DataGridCopyPreference,
|
||||
type DataGridExtractorOptions,
|
||||
type DataGridExtractPreview,
|
||||
type DataGridExtractWarningCode,
|
||||
} from "@/lib/dataGrid/dataGridCopyExtractor";
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
extractor: DataGridCopyExtractorId;
|
||||
preference: DataGridCopyPreference;
|
||||
options: DataGridExtractorOptions;
|
||||
items: Array<{ value: DataGridCopyExtractorId; label: string; disabled?: boolean }>;
|
||||
preview: (extractor: DataGridCopyExtractorId, options: DataGridExtractorOptions) => Promise<DataGridExtractPreview>;
|
||||
items: Array<{ value: DataGridCopyPreference; label: string; disabled?: boolean }>;
|
||||
preview: (preference: DataGridCopyPreference, options: DataGridExtractorOptions) => Promise<DataGridExtractPreview>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:open": [value: boolean];
|
||||
save: [value: { extractor: DataGridCopyExtractorId; options: DataGridExtractorOptions }];
|
||||
save: [value: { preference: DataGridCopyPreference; options: DataGridExtractorOptions }];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const draftExtractor = ref<DataGridCopyExtractorId>(props.extractor);
|
||||
const draftPreference = ref<DataGridCopyPreference>(props.preference);
|
||||
const draftOptions = ref<DataGridExtractorOptions>(normalizeDataGridExtractorOptions(props.options));
|
||||
const previewText = ref("");
|
||||
const previewError = ref("");
|
||||
@@ -32,11 +41,13 @@ const previewTruncatedRows = ref(0);
|
||||
let previewSequence = 0;
|
||||
let previewTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const extractorCategory = computed(() => DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS[draftExtractor.value].category);
|
||||
const draftExtractor = computed<DataGridCopyExtractorId | null>(() => (draftPreference.value === "smart" ? null : draftPreference.value));
|
||||
const extractorCategory = computed(() => (draftExtractor.value ? DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS[draftExtractor.value].category : null));
|
||||
const isDsv = computed(() => extractorCategory.value === "delimited");
|
||||
const isSql = computed(() => draftExtractor.value === "sql-inserts" || draftExtractor.value === "sql-updates");
|
||||
const isJson = computed(() => extractorCategory.value === "json");
|
||||
const optionsError = computed(() => {
|
||||
if (!draftExtractor.value) return "";
|
||||
const code = validateDataGridExtractorOptions(draftExtractor.value, draftOptions.value);
|
||||
if (!code) return "";
|
||||
return t(`grid.copyExtractorValidation.${code}`);
|
||||
@@ -70,7 +81,7 @@ async function refreshPreview() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await props.preview(draftExtractor.value, normalizeDataGridExtractorOptions(draftOptions.value));
|
||||
const result = await props.preview(draftPreference.value, normalizeDataGridExtractorOptions(draftOptions.value));
|
||||
if (sequence === previewSequence) {
|
||||
previewText.value = result.text;
|
||||
previewWarnings.value = (result.warnings ?? []).map((warning) => warningText(warning.code, result.omittedColumns));
|
||||
@@ -105,14 +116,14 @@ watch(
|
||||
previewLoading.value = false;
|
||||
return;
|
||||
}
|
||||
draftExtractor.value = props.extractor;
|
||||
draftPreference.value = props.preference;
|
||||
draftOptions.value = normalizeDataGridExtractorOptions(props.options);
|
||||
schedulePreview();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
[draftExtractor, draftOptions],
|
||||
[draftPreference, draftOptions],
|
||||
() => {
|
||||
if (props.open) schedulePreview();
|
||||
},
|
||||
@@ -126,7 +137,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
function save() {
|
||||
if (optionsError.value) return;
|
||||
emit("save", { extractor: draftExtractor.value, options: normalizeDataGridExtractorOptions(draftOptions.value) });
|
||||
emit("save", { preference: draftPreference.value, options: normalizeDataGridExtractorOptions(draftOptions.value) });
|
||||
emit("update:open", false);
|
||||
}
|
||||
</script>
|
||||
@@ -142,7 +153,7 @@ function save() {
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label>{{ t("grid.copyExtractorDefaultFormat") }}</Label>
|
||||
<Select v-model="draftExtractor">
|
||||
<Select v-model="draftPreference">
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in items" :key="item.value" :value="item.value" :disabled="item.disabled">{{ item.label }}</SelectItem>
|
||||
|
||||
@@ -108,10 +108,10 @@ type DataGridHandle = DataGridColumnLayoutHandle & {
|
||||
allNullColumnCount: number;
|
||||
canToggleAllNullColumns: boolean;
|
||||
toggleAllNullColumns: () => void;
|
||||
defaultCopyExtractor: string;
|
||||
defaultCopyExtractorLabel: string;
|
||||
copyExtractorMenuItems: Array<{ value: string; label: string; disabled?: boolean; separatorBefore?: boolean }>;
|
||||
setDefaultCopyExtractor: (value: string) => void;
|
||||
defaultCopyPreference: string;
|
||||
defaultCopyPreferenceLabel: string;
|
||||
copyPreferenceMenuItems: Array<{ value: string; label: string; disabled?: boolean; separatorBefore?: boolean }>;
|
||||
setDefaultCopyPreference: (value: string) => void;
|
||||
openExtractorConfiguration: () => void;
|
||||
showDdl: boolean;
|
||||
toggleDdl: (tab?: TableInfoTab) => void;
|
||||
@@ -1286,10 +1286,10 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
||||
<Switch size="sm" :model-value="!!dataGridRef?.nullColumnsHidden" :disabled="!dataGridRef?.canToggleAllNullColumns" :aria-label="t('grid.hideNullColumns')" @update:model-value="dataGridRef?.toggleAllNullColumns()" />
|
||||
</div>
|
||||
<DataGridCopyFormatControl
|
||||
:current-label="dataGridRef?.defaultCopyExtractorLabel ?? '-'"
|
||||
:current-value="dataGridRef?.defaultCopyExtractor ?? ''"
|
||||
:items="dataGridRef?.copyExtractorMenuItems ?? []"
|
||||
@select="dataGridRef?.setDefaultCopyExtractor($event)"
|
||||
:current-label="dataGridRef?.defaultCopyPreferenceLabel ?? '-'"
|
||||
:current-value="dataGridRef?.defaultCopyPreference ?? ''"
|
||||
:items="dataGridRef?.copyPreferenceMenuItems ?? []"
|
||||
@select="dataGridRef?.setDefaultCopyPreference($event)"
|
||||
@configure="openDataGridExtractorConfiguration"
|
||||
/>
|
||||
</PopoverContent>
|
||||
@@ -1732,10 +1732,10 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
|
||||
<Switch size="sm" :model-value="!!dataGridRef?.nullColumnsHidden" :disabled="!dataGridRef?.canToggleAllNullColumns" :aria-label="t('grid.hideNullColumns')" @update:model-value="dataGridRef?.toggleAllNullColumns()" />
|
||||
</div>
|
||||
<DataGridCopyFormatControl
|
||||
:current-label="dataGridRef?.defaultCopyExtractorLabel ?? '-'"
|
||||
:current-value="dataGridRef?.defaultCopyExtractor ?? ''"
|
||||
:items="dataGridRef?.copyExtractorMenuItems ?? []"
|
||||
@select="dataGridRef?.setDefaultCopyExtractor($event)"
|
||||
:current-label="dataGridRef?.defaultCopyPreferenceLabel ?? '-'"
|
||||
:current-value="dataGridRef?.defaultCopyPreference ?? ''"
|
||||
:items="dataGridRef?.copyPreferenceMenuItems ?? []"
|
||||
@select="dataGridRef?.setDefaultCopyPreference($event)"
|
||||
@configure="openDataGridExtractorConfiguration"
|
||||
/>
|
||||
</PopoverContent>
|
||||
|
||||
Reference in new issue
Block a user