From f57bf5d61dfdfcac44a32894bc18d1b4c8165d47 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Sat, 19 Sep 2026 21:20:41 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix=F0=9F=90=9B:=20query-less=20table's=20Q?= =?UTF-8?q?uery=20type=20broke=20FK=20dropdown=20fetches=20(P0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3625ce8 fixed the no-empty-object-type lint error by switching a zero-IsQuery table's {ClassName}Query to `Record`, the same default useTable.ts's own `TQuery extends object = Record` uses. That default is safe there only because useTable.ts's one internal `TQuery & PageQuery` goes through an `as` cast rather than a structural check. Code that builds the object literal directly does not get that protection - and vue.go.template's foreign-key dropdown fetch does exactly that: `list{FkClass}({ pageIndex: 1, pageSize: 100 })`. Record is a mapped type over every string key, each mapped to never, so `Record & PageQuery` does not leave PageQuery's own properties alone: pageIndex becomes `never & number`, i.e. never, and no value can be passed for it - a straight type error, not a lint warning, so the previous fix's pnpm lint pass did not catch it. Trips on any query-less table referenced by a foreign key - a plain lookup/dict table used as a dropdown source, an ordinary shape, not a rare one. Switched to Record: a mapped type over the empty key set, which behaves as the empty object type `{}` under intersection (PageQuery's properties come through unchanged) while still satisfying `TQuery extends object` and not tripping no-empty-object-type (it is a generic instantiation, not a literal `{}` type annotation) - confirmed all three separately before touching the template. Verified with node 24.11.0: generated a real zero-IsQuery table, copied its .ts into go-admin-ui alongside a throwaway file reproducing vue.go.template's exact FK call site (`await list{Class}({ pageIndex: 1, pageSize: 100 })`), and ran both eslint and vue-tsc --noEmit - the type-check step lint alone cannot cover, which is what let this through the first time. Confirmed red first (swapped the generated file's Record back to Record): vue-tsc reported the exact "Property 'pageIndex' is incompatible with index signature" error. Restored the fix - both clean. --- template/v4/ts.go.template | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/template/v4/ts.go.template b/template/v4/ts.go.template index 4387eff1..d5e70a0f 100644 --- a/template/v4/ts.go.template +++ b/template/v4/ts.go.template @@ -58,12 +58,31 @@ export interface {{.ClassName}}Query { `export interface {ClassName}Query {}` is what naturally falls out of the range above finding nothing to iterate, but an empty interface trips @typescript-eslint/no-empty-object-type and fails pnpm lint. - Record is go-admin-ui's own answer to the same shape - - see composables/useTable.ts's `TQuery extends object = Record` - - and it intersects with PageQuery the same way an empty interface would - have, so callers do not have to special-case a query-less table. + + Record (this file's first attempt, and the type + useTable.ts's own `TQuery extends object = Record` default + uses) looks like the obvious match but is wrong here: it is a mapped type + over *every* string key, each mapped to never, so intersecting it with + PageQuery does not leave PageQuery alone - `pageIndex` becomes + `never & number`, i.e. never, and no value can be passed for it at all. + useTable.ts itself never hits this because its one internal use of + `TQuery & PageQuery` goes through an `as` cast rather than a structural + check (composables/useTable.ts ~line 160); code that builds the object + literal directly - such as a foreign-key column's + `list{FkClass}({ pageIndex: 1, pageSize: 100 })` call in vue.go.template - + is not casting anything and hits the real error, only when the referenced + table happens to have no query columns of its own (a plain lookup/dict + table used as a dropdown source, not a rare shape). + + Record is the type with the same intent - "no query + columns" - but the mapped-type domain is `never`, so it has no keys at + all rather than "every key, mapped to never": it behaves as the empty + object type `{}` under intersection, leaving PageQuery's own pageIndex/ + pageSize untouched, and confirmed separately not to trip + no-empty-object-type either (it is a generic instantiation, not a + literal `{}` type annotation). */ -}} -export type {{.ClassName}}Query = Record +export type {{.ClassName}}Query = Record {{- end}} export function list{{.ClassName}}(query: {{.ClassName}}Query & PageQuery) { From 08f789737f55ba3a73803c2d07cb40b506f2f002 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Sat, 19 Sep 2026 21:21:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix=F0=9F=90=9B:=20escape=20the=20literal?= =?UTF-8?q?=20T=20in=20the=20datetime=20value-format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit value-format="YYYY-MM-DDTHH:mm:ssZ" works today only because dayjs does not currently give T a format-token meaning, so it passes through as a literal character -- an accident of the current token table, not something this string declares. Escaped it to YYYY-MM-DD[T]HH:mm:ssZ, dayjs's own syntax for "this character, verbatim, not a token": produces byte-for-byte the same output today (confirmed below) and stops depending on T staying meaningless in a future dayjs version. Re-verified both directions against the escaped string, and did so against the real el-date-picker component this time rather than dayjs alone: mounted element-plus's actual ElDatePicker with value-format="YYYY-MM-DD[T]HH:mm:ssZ" (copied from a real rendering of this fixed template, not retyped) and confirmed it renders a real, non-blank date -- not "Invalid Date" -- when its modelValue is set to either shape Go's encoding/json actually sends (a numeric offset or a literal Z for UTC), and that both render identically since they are the same instant. Submission was re-checked through the same dayjs call the component itself makes to format a picked value. A fourth check formats the same instant with both the old and the escaped string and asserts they are equal, so this suite would have caught the difference if the escape had changed anything instead of just hardening it. --- template/v4/vue.go.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/v4/vue.go.template b/template/v4/vue.go.template index f40e3b10..2b1e43f1 100644 --- a/template/v4/vue.go.template +++ b/template/v4/vue.go.template @@ -99,7 +99,7 @@ {{- else}} @@ -223,7 +223,7 @@ {{- else if eq .HtmlType "textarea"}}