diff --git a/i18n/locales/en.json b/i18n/locales/en.json index c711580bc..5f2365671 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "This table has no primary key, so adding, updating, or deleting data is not supported yet." } } diff --git a/i18n/locales/es.json b/i18n/locales/es.json index af7977cf2..60908b847 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "Esta tabla no tiene clave primaria; de momento no se pueden añadir, modificar ni eliminar datos." } } diff --git a/i18n/locales/ja.json b/i18n/locales/ja.json index c7fe70eb7..1065944ea 100644 --- a/i18n/locales/ja.json +++ b/i18n/locales/ja.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "このテーブルには主キーが設定されていないため、データの追加・変更・削除は現在サポートされていません。" } } diff --git a/i18n/locales/ko.json b/i18n/locales/ko.json index 215d6bca8..4a4fa6c8c 100644 --- a/i18n/locales/ko.json +++ b/i18n/locales/ko.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "이 테이블에는 기본 키가 없어 지금은 데이터를 추가, 수정 또는 삭제할 수 없습니다." } } diff --git a/i18n/locales/pt_br.json b/i18n/locales/pt_br.json index eae9659f0..b7e73b4a5 100644 --- a/i18n/locales/pt_br.json +++ b/i18n/locales/pt_br.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "Esta tabela não possui chave primária; por enquanto não é possível adicionar, alterar ou excluir dados." } } diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index f59dede98..f0871619a 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "У этой таблицы не задан первичный ключ, поэтому добавление, изменение и удаление данных пока не поддерживается." } } diff --git a/i18n/locales/vi.json b/i18n/locales/vi.json index b79de6ead..62af16a5e 100644 --- a/i18n/locales/vi.json +++ b/i18n/locales/vi.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "Bảng hiện tại chưa có khóa chính, tạm thời không hỗ trợ thêm, sửa hoặc xóa dữ liệu." } } diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index a560ac64e..e5ead467a 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -2255,6 +2255,7 @@ "InvalidTableStructureChanges": "请检查表结构更改。", "InvalidIndexDefinition": "请完整填写索引名称并选择列。", "InvalidCsvFile": "CSV 文件无效或无法读取。", - "CopyOperationFailedDescription": "无法复制所选数据。" + "CopyOperationFailedDescription": "无法复制所选数据。", + "TableWithoutPrimaryKeyNotEditable": "当前表未设置主键,暂不支持新增、修改或删除数据。" } } diff --git a/i18n/locales/zh_hant.json b/i18n/locales/zh_hant.json index 6c49b33aa..10ed13205 100644 --- a/i18n/locales/zh_hant.json +++ b/i18n/locales/zh_hant.json @@ -2252,6 +2252,7 @@ "InvalidTableStructureChanges": "Check the table structure changes.", "InvalidIndexDefinition": "Complete the index name and column selection.", "InvalidCsvFile": "The CSV file is invalid or cannot be read.", - "CopyOperationFailedDescription": "The selected data could not be copied." + "CopyOperationFailedDescription": "The selected data could not be copied.", + "TableWithoutPrimaryKeyNotEditable": "目前資料表未設定主鍵,暫不支援新增、修改或刪除資料。" } } diff --git a/ui/chen/components/DataViewPanel.vue b/ui/chen/components/DataViewPanel.vue index 25d8dfb6d..dfa7c3e5f 100644 --- a/ui/chen/components/DataViewPanel.vue +++ b/ui/chen/components/DataViewPanel.vue @@ -28,6 +28,7 @@ import ChenWorkspaceModal from "~/chen/components/WorkspaceModal.vue"; import { useChenDataViewDerivedMeta } from "~/chen/composables/useChenDataViewDerivedMeta"; import { useChenDataViewEditing } from "~/chen/composables/useChenDataViewEditing"; import { chenGridPreferenceKey } from "~/chen/composables/useChenGridPreferences"; +import { chenDataViewMissingPrimaryKey } from "~/chen/utils/dataViewEditing"; import { buildChenDropIndexSql, chenSupportsIndexDdl } from "~/chen/utils/indexSql"; import { isChenViewRelation } from "~/chen/utils/resourceTree"; @@ -82,6 +83,14 @@ const schemaName = computed(() => String(props.tab.meta?.schema || "").trim()); const isViewRelation = computed(() => isChenViewRelation({ nodeKey: props.tab.nodeKey, kind: props.tab.tableMetadata?.kind }) ); +const missingPrimaryKey = computed( + () => + !editing.editable.value && + chenDataViewMissingPrimaryKey(props.tab.data, { + isView: isViewRelation.value, + tableMetadata: props.tab.tableMetadata + }) +); const indexDdlSupported = computed( () => !isViewRelation.value && @@ -294,6 +303,12 @@ function importCsvRows(rows: Array>) {
+
+ {{ t("Chen.TableWithoutPrimaryKeyNotEditable") }} +
{ return props.resultTabs.find((item) => item.id === props.activeResultTabId) || null; }); const activeResultEditable = computed(() => isChenDataViewEditable(activeResult.value?.data)); +const activeResultMissingPrimaryKey = computed( + () => + props.dataViewEditing && + !activeResultEditable.value && + chenDataViewMissingPrimaryKey(activeResult.value?.data) +); const activeResultDirty = computed(() => Boolean(activeResult.value && chenDataViewHasDirty(activeResult.value.editState)) ); @@ -167,6 +174,12 @@ function cancelActiveResultChanges() {
+
+ {{ t("Chen.TableWithoutPrimaryKeyNotEditable") }} +
{{ activeResult.title }} diff --git a/ui/chen/utils/dataViewEditing.noPk.test.ts b/ui/chen/utils/dataViewEditing.noPk.test.ts new file mode 100644 index 000000000..aedff19f8 --- /dev/null +++ b/ui/chen/utils/dataViewEditing.noPk.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; + +import { + chenDataViewMissingPrimaryKey, + CHEN_EDIT_REASON_NO_PRIMARY_KEY +} from "~/chen/utils/dataViewEditing"; + +describe("chenDataViewMissingPrimaryKey", () => { + it("detects the backend no-primary-key edit reason", () => { + expect( + chenDataViewMissingPrimaryKey({ + fields: [{ name: "name", editReason: CHEN_EDIT_REASON_NO_PRIMARY_KEY }], + data: [] + }) + ).toBe(true); + }); + + it("detects loaded table metadata without a primary key", () => { + expect( + chenDataViewMissingPrimaryKey( + { fields: [{ name: "name" }], data: [] }, + { tableMetadata: { loadedSections: ["primaryKey"], primaryKey: null } } + ) + ).toBe(true); + }); + + it("does not flag views or incomplete metadata", () => { + expect( + chenDataViewMissingPrimaryKey( + { fields: [{ name: "name" }], data: [] }, + { isView: true, tableMetadata: { loadedSections: ["primaryKey"], primaryKey: null } } + ) + ).toBe(false); + expect( + chenDataViewMissingPrimaryKey( + { fields: [{ name: "name" }], data: [] }, + { tableMetadata: { loadedSections: ["columns"], primaryKey: null } } + ) + ).toBe(false); + }); +}); diff --git a/ui/chen/utils/dataViewEditing.ts b/ui/chen/utils/dataViewEditing.ts index bb3e02a35..1374f9e08 100644 --- a/ui/chen/utils/dataViewEditing.ts +++ b/ui/chen/utils/dataViewEditing.ts @@ -214,10 +214,30 @@ export function acceptChenSaveChangesResult(state: ChenDataViewEditState, result return "applied" as const; } +export const CHEN_EDIT_REASON_NO_PRIMARY_KEY = "NO_PRIMARY_KEY"; + export function isChenDataViewEditable(dataset: ChenDataViewDataset | null | undefined) { return dataset?.editable === true && dataset.fields.some((field) => field.editable === true); } +export function chenDataViewMissingPrimaryKey( + dataset?: ChenDataViewDataset | null, + options?: { + isView?: boolean; + tableMetadata?: { loadedSections?: string[]; primaryKey?: { columns?: string[] } | null } | null; + } +) { + if (options?.isView) return false; + const fields = dataset?.fields || []; + if (fields.some((field) => field.editReason === CHEN_EDIT_REASON_NO_PRIMARY_KEY)) { + return true; + } + const metadata = options?.tableMetadata; + if (!metadata?.loadedSections?.includes("primaryKey")) return false; + const columns = metadata.primaryKey?.columns || []; + return metadata.primaryKey === null || columns.length === 0; +} + export function isChenDataViewInsertable(dataset: ChenDataViewDataset | null | undefined) { return isChenDataViewEditable(dataset) && dataset?.fields.some((field) => field.insertable === true) === true; }