0.8.0: 契约副本刷齐并改为自动同步;未注册字段类型不再静默回落
契约副本(src/IShrimpType.ts)此前漂了一整代:停在旧的 json 类型, 缺 object / array / IFieldConfig.fields / IFieldConfig.item / parse / IPageResult.refs。 而本包自己的组件早就在读这些字段——FieldArray 读 field.item、FieldObject 读 fields、 FieldRelation 与 ShrimpTable 读 refs。等于组件在用、类型说没有。 接入方照这份副本理解 shrimp 能渲染什么,只会得出"结构化数据只有 json"的结论。 现在由 shrimp-server 的 postbuild 单向覆盖过来(它是契约真身), 本仓库另提供 npm run check:contract / sync:contract 手动比对与同步。 注释头不参与同步,它声明的是"我是副本",与契约正文是两回事。 VueAdmin.getFieldComponent:未注册类型仍回落成 string(类型词汇表是开放的, 消费侧放个 Field<X>.vue 就能扩,改成严格校验会当场打死 theseus 的 gesture/goodsList 和 hand 的 dropList/matterPointDropList),但按类型去重告警一次, 打印出现位置、已注册类型和两条修法。上面那个 json 就是这么静默活下来的。 resolveVue:内置组件表为空时报"本包没被 vite 正常转换、消费侧缺 optimizeDeps.exclude", 不再报"找不到 layout.vue"把人引去查文件路径。 README 重写成可照抄的接入模板 + 出错对照表 + "那几行为什么不能删"。 版本与 shrimp-server 归一到 0.8.0,删掉 @syncedFrom 这条手维护的版本标记。
This commit is contained in:
+18
-7
@@ -1,15 +1,18 @@
|
||||
/**
|
||||
* Shrimp 类型契约 — 前后端 wire protocol 定义(前端副本)
|
||||
*
|
||||
* @syncedFrom shrimp-server contract 1.0.0
|
||||
* 这是 shrimp-server/src/IShrimpType.ts 的逐字副本(除本注释块外)。
|
||||
* 不要在这里改 wire protocol,先改 shrimp-server 那份升版本,再整份 cp 过来。
|
||||
*
|
||||
* 这是 shrimp-server/src/IShrimpType.ts 的逐字副本。
|
||||
* 不要在这里改 wire protocol,先改 shrimp-server 那份升版本,再 copy 过来更新 @syncedFrom。
|
||||
* 本文件由 shrimp-server 的 postbuild 自动同步过来,手改会被下次 build 覆盖。
|
||||
* 手动同步: npm run sync:contract 只想看漂没漂: npm run check:contract
|
||||
* 自动同步这件事存在的原因是:曾经靠人工复制,漂了一整代都没人发现——
|
||||
* 副本停在旧的 json 类型、缺 object/array/fields/item/parse/refs,
|
||||
* 而前端对未注册类型静默回落到 string,所以漂移不会报错、只会悄悄渲染错。
|
||||
*
|
||||
* 故意保留副本而不 import 'shrimp-server' 的原因:
|
||||
* - shrimp-server 入口会把 mongoose / express 牵进前端 bundle
|
||||
* - 拆 './types' 子入口会绕开 vite optimize、可读性也差
|
||||
* - 类型契约低频改动,版本号对照即可
|
||||
* - shrimp-server 默认入口会把 mongoose / express 牵进前端 bundle
|
||||
* - 前端项目不该为一份纯类型去装后端包
|
||||
*/
|
||||
|
||||
/** 业务错误码 */
|
||||
@@ -238,8 +241,9 @@ export const ShrimpFieldTypes = {
|
||||
Textarea: "textarea" as ShrimpFieldType,
|
||||
Password: "password" as ShrimpFieldType,
|
||||
Relation: "relation" as ShrimpFieldType,
|
||||
Json: "json" as ShrimpFieldType,
|
||||
Tab: "tab" as ShrimpFieldType,
|
||||
Object: "object" as ShrimpFieldType,
|
||||
Array: "array" as ShrimpFieldType,
|
||||
}
|
||||
|
||||
/** 字段可编辑状态 */
|
||||
@@ -279,6 +283,12 @@ export interface IFieldConfig {
|
||||
rules?: IFieldRule[] // 通用验证规则(前后端共用)
|
||||
/** tab 类型专用:每个 type 对应的子字段配置 */
|
||||
tabs?: { key: any; label: ILabel; fields: IFieldConfig[] }[]
|
||||
/** object 类型专用:嵌套字段组 */
|
||||
fields?: IFieldConfig[]
|
||||
/** array 类型专用:每个元素的字段配置(递归 IFieldConfig;元素无需 key,label 用作每项标题) */
|
||||
item?: IFieldConfig
|
||||
/** tab 类型专用:import 反序列化(把字符串还原成对象,如 xlsx 导入),默认 JSON.parse */
|
||||
parse?: (text: string) => any
|
||||
}
|
||||
|
||||
/** 通用验证规则(前后端共用) */
|
||||
@@ -315,6 +325,7 @@ export interface IPageResult<T> {
|
||||
page: number // 当前页码
|
||||
pageSize: number // 每页数量
|
||||
totalPages: number // 总页数
|
||||
refs?: Record<string, Record<string, any>> // relation 显示名字典: { moduleKey: { id: 显示名 } },前端显示时查名,替代逐行 _label
|
||||
}
|
||||
|
||||
// ============== 数据源查询协议 ==============
|
||||
|
||||
+28
-1
@@ -80,6 +80,15 @@ export function resolveVue(suffix: string): VueLoader {
|
||||
for (const key in internalSources) {
|
||||
if (key.endsWith(tail)) return internalSources[key] as any
|
||||
}
|
||||
// 内置表整个是空的,说明本包被 esbuild 预打包了——import.meta.glob 是 vite 编译期宏,
|
||||
// 预打包不认它,内置组件一个都收不到。报"找不到 layout.vue"会把人引向错误的方向。
|
||||
if (Object.keys(internalSources).length === 0) {
|
||||
throw new Error(
|
||||
`[shrimp-admin] 内置组件表为空,本包没有被 vite 正常转换。\n` +
|
||||
` 消费侧 vite.config 需要: optimizeDeps: { exclude: ["shrimp-admin"] }\n` +
|
||||
` (或直接用 shrimp-admin/vite 提供的插件,它会自动加上)`
|
||||
)
|
||||
}
|
||||
throw new Error(`[shrimp-admin] resolveVue: ${suffix} not found in sources`)
|
||||
}
|
||||
|
||||
@@ -90,6 +99,19 @@ export function resolveVue(suffix: string): VueLoader {
|
||||
|
||||
const fieldRegistry = new Map<string, Component>()
|
||||
|
||||
/** 未注册类型每种只喊一次——表格逐格调 getFieldComponent,不去重会刷屏 */
|
||||
const warnedTypes = new Set<string>()
|
||||
function warnOnce(type: string, moduleKey?: string, fieldKey?: string) {
|
||||
if (warnedTypes.has(type)) return
|
||||
warnedTypes.add(type)
|
||||
console.warn(
|
||||
`[shrimp-admin] 字段类型 "${type}" 没有对应组件,已按 string 渲染` +
|
||||
(moduleKey || fieldKey ? `(出现在 ${moduleKey ?? '?'}.${fieldKey ?? '?'})` : '') +
|
||||
`\n 已注册: ${[...fieldRegistry.keys()].sort().join(', ')}` +
|
||||
`\n 要么后端把 type 改成上面之一,要么消费侧补一个 shrimp/components/fields/Field${type.charAt(0).toUpperCase() + type.slice(1)}.vue`
|
||||
)
|
||||
}
|
||||
|
||||
function deriveFieldType(path: string): string | null {
|
||||
const m = path.match(/\/components\/fields\/Field([A-Z][A-Za-z0-9]*)\.vue$/)
|
||||
if (!m) return null
|
||||
@@ -129,7 +151,12 @@ export class VueAdmin extends ShrimpClient {
|
||||
moduleRoutes = new Map<string, IRouteInfo>()
|
||||
|
||||
getFieldComponent(_moduleKey: string, type: string, _fieldKey?: string): Component {
|
||||
return fieldRegistry.get(type) || fieldRegistry.get('string')!
|
||||
const hit = fieldRegistry.get(type)
|
||||
if (hit) return hit
|
||||
// 类型词汇表是开放的(消费侧放个 Field<X>.vue 就能扩),所以这里不能报错、只能回落。
|
||||
// 但静默回落等于打错字永远发现不了——曾经有个 json 类型这么活了一整代。
|
||||
warnOnce(type, _moduleKey, _fieldKey)
|
||||
return fieldRegistry.get('string')!
|
||||
}
|
||||
|
||||
/** 目标模块能否作为「单项查看」跳转目标:已按权限注册路由 且 非子模块。返回其路由信息或 undefined(不可跳) */
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// 消费侧用法见 README.md。本文件只做 re-export。
|
||||
|
||||
// ===== 类型契约(IShrimpType 本地副本,@syncedFrom shrimp-server contract 1.0.0)=====
|
||||
// ===== 类型契约(IShrimpType 是 shrimp-server 那份的副本,由其 postbuild 自动同步)=====
|
||||
export {
|
||||
ShrimpCode,
|
||||
ShrimpError,
|
||||
|
||||
Reference in New Issue
Block a user