feat(dynamodb): support querying, editing, and exporting

This commit is contained in:
zipg authored and GitHub committed 2026-08-17 03:27:44 +08:00
1 parent 92ab9bb0dc
commit e97c9ab5d7
73 files changed
+4493 -134

No files matched your search

@@ -44,7 +44,7 @@ async function copy() {
<!-- card: 卡片类报错信息面板 -->
<div v-if="variant === 'card'" class="mx-3 my-2 rounded-lg border border-destructive/30 bg-destructive/10 p-3 shadow-sm shrink-0 select-text flex flex-col gap-2">
<div class="flex items-center justify-between gap-2 border-b border-destructive/20 pb-2">
<div class="flex items-center gap-2 font-semibold text-xs text-destructive">
<div data-native-clipboard class="flex items-center gap-2 font-semibold text-xs text-destructive">
<TriangleAlert class="h-4 w-4 text-destructive shrink-0" aria-hidden="true" />
<span>{{ displayTitle }}</span>
</div>
@@ -58,14 +58,14 @@ async function copy() {
</Button>
</div>
</div>
<div class="max-h-40 min-h-[56px] overflow-y-auto rounded bg-background/80 dark:bg-background/50 border border-destructive/20 p-2.5 text-xs font-mono leading-relaxed text-destructive break-words whitespace-pre-wrap select-text cursor-text" @mousedown.stop @click.stop>
<div data-native-clipboard class="max-h-40 min-h-[56px] overflow-y-auto rounded bg-background/80 dark:bg-background/50 border border-destructive/20 p-2.5 text-xs font-mono leading-relaxed text-destructive break-words whitespace-pre-wrap select-text cursor-text" @mousedown.stop @click.stop>
{{ message }}
</div>
</div>
<!-- banner: 紧凑内联横幅 -->
<div v-else-if="variant === 'banner'" class="flex items-center gap-2 px-3 py-1.5 border-t bg-destructive/10 text-destructive text-xs shrink-0">
<span class="flex-1 min-w-0 break-all">{{ message }}</span>
<span data-native-clipboard class="flex-1 min-w-0 break-all">{{ message }}</span>
<button v-if="copyMode === 'label'" type="button" class="shrink-0 hover:underline" :aria-label="t('grid.copy')" @click.stop="copy">
{{ t("grid.copy") }}
</button>
@@ -78,7 +78,7 @@ async function copy() {
<!-- centered: 居中占满 -->
<div v-else class="flex-1 min-h-0 flex flex-col items-center justify-center gap-2 px-6 py-4 text-center">
<TriangleAlert class="h-8 w-8 text-destructive/50" aria-hidden="true" />
<div class="min-h-0 max-h-48 max-w-lg overflow-auto space-y-1 select-text text-destructive" @mousedown.stop @click.stop>
<div data-native-clipboard class="min-h-0 max-h-48 max-w-lg overflow-auto space-y-1 select-text text-destructive" @mousedown.stop @click.stop>
<div class="text-sm font-medium">{{ displayTitle }}</div>
<div class="text-xs break-all cursor-text text-destructive/80 select-text">{{ message }}</div>
</div>
@@ -0,0 +1,79 @@
// @vitest-environment happy-dom
import { createApp, nextTick } from "vue";
import { createI18n } from "vue-i18n";
import { afterEach, describe, expect, it } from "vitest";
import ErrorBanner from "@/components/ui/ErrorBanner.vue";
import { eventTargetAllowsNativeClipboard } from "@/lib/common/clipboard";
const mountedContainers: HTMLDivElement[] = [];
function findNativeClipboardRegion(container: HTMLElement, text: string): HTMLElement {
const region = Array.from(container.querySelectorAll<HTMLElement>("[data-native-clipboard]")).find((element) => element.textContent?.trim() === text);
if (!region) throw new Error(`Native clipboard region not found: ${text}`);
return region;
}
function selectRegion(region: HTMLElement) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(region);
selection?.removeAllRanges();
selection?.addRange(range);
}
async function mountCardBanner() {
const container = document.createElement("div");
mountedContainers.push(container);
document.body.append(container);
const app = createApp(ErrorBanner, {
variant: "card",
title: "Save failed",
message: "Conditional request failed",
});
app.use(
createI18n({
legacy: false,
locale: "en",
messages: { en: { grid: { copy: "Copy", dismiss: "Dismiss" } } },
missingWarn: false,
fallbackWarn: false,
}),
);
app.mount(container);
await nextTick();
return container;
}
afterEach(() => {
window.getSelection()?.removeAllRanges();
for (const container of mountedContainers.splice(0)) container.remove();
});
describe("ErrorBanner native clipboard selection", () => {
it.each(["Save failed", "Conditional request failed"])("preserves native copy for selected %s text", async (text) => {
const container = await mountCardBanner();
selectRegion(findNativeClipboardRegion(container, text));
expect(
eventTargetAllowsNativeClipboard({
key: "c",
metaKey: true,
target: container,
}),
).toBe(true);
});
it("leaves grid copy shortcuts active without a text selection", async () => {
const container = await mountCardBanner();
expect(
eventTargetAllowsNativeClipboard({
key: "c",
metaKey: true,
target: container,
}),
).toBe(false);
});
});