@done-coding/admin-core 0.24.1-alpha.0 → 0.25.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/components/app-layout/AppPage.vue.mjs +1 -1
- package/es/components/app-layout/AppPage.vue2.mjs +42 -23
- package/es/components/app-layout/use-content-box.mjs +17 -0
- package/es/components/data-view/DataGridView.vue.mjs +1 -1
- package/es/components/data-view/DataGridView.vue2.mjs +34 -14
- package/es/components/data-view/DataListView.vue.mjs +1 -1
- package/es/components/data-view/DataListView.vue2.mjs +27 -7
- package/es/components/data-view/DataListViewItem.vue.mjs +1 -1
- package/es/components/data-view/DataListViewItem.vue2.mjs +3 -2
- package/es/components/list-layout/ListLayout.vue.mjs +1 -1
- package/es/components/list-layout/ListLayout.vue2.mjs +34 -11
- package/es/components/modal/ModalConfirm.vue.mjs +1 -1
- package/es/components/modal/ModalConfirm.vue2.mjs +67 -49
- package/es/components/page-layout/AppPageDetail.vue.mjs +7 -0
- package/es/components/page-layout/AppPageDetail.vue2.mjs +89 -0
- package/es/components/page-layout/AppPageListDetailLayout.vue.mjs +153 -0
- package/es/components/page-layout/AppPageListDetailLayout.vue2.mjs +4 -0
- package/es/components/page-layout/AppPageListDetailSheet.vue.mjs +96 -0
- package/es/components/page-layout/AppPageListDetailSheet.vue2.mjs +4 -0
- package/es/components/page-layout/AppPageListDetailSplit.vue.mjs +7 -0
- package/es/components/page-layout/AppPageListDetailSplit.vue2.mjs +83 -0
- package/es/components/page-layout/use-list-detail.mjs +110 -0
- package/es/components/panel/PanelEditSwitch.vue.mjs +1 -1
- package/es/components/table/TableMain.vue.mjs +1 -1
- package/es/components/table/TableMain.vue2.mjs +55 -5
- package/es/components/view-layout/ViewLayout.vue.mjs +1 -1
- package/es/components/view-layout/ViewLayout.vue2.mjs +34 -4
- package/es/components/view-layout/ViewLayoutNavigation.vue.mjs +7 -0
- package/es/components/view-layout/ViewLayoutNavigation.vue2.mjs +34 -0
- package/es/hooks/use-active-record.mjs +100 -0
- package/es/index.mjs +146 -141
- package/es/inject/key.mjs +4 -0
- package/es/style.css +129 -96
- package/package.json +2 -2
- package/src/components/app-layout/README.md +8 -1
- package/src/components/app-layout/docs/README-AppPage.md +48 -2
- package/src/components/data-view/docs/README-DataGridView.md +2 -1
- package/src/components/data-view/docs/README-DataListView.md +13 -4
- package/src/components/list-layout/docs/README-ListLayout.md +1 -1
- package/src/components/modal/docs/README-ModalConfirm.md +2 -0
- package/src/components/page-layout/README.md +47 -0
- package/src/components/page-layout/docs/README-AppPageDetail.md +57 -0
- package/src/components/page-layout/docs/README-AppPageListDetailLayout.md +142 -0
- package/src/components/table/docs/README-TableMain.md +1 -1
- package/src/components/view-layout/docs/README-ViewLayout.md +14 -0
- package/types/components/app-layout/AppPage.vue.d.ts +14 -0
- package/types/components/app-layout/use-content-box.d.ts +31 -0
- package/types/components/data-view/DataGridView.vue.d.ts +9 -2
- package/types/components/data-view/DataListView.vue.d.ts +10 -2
- package/types/components/data-view/DataListViewItem.vue.d.ts +4 -2
- package/types/components/data-view/types.d.ts +5 -1
- package/types/components/list-layout/ListLayout.vue.d.ts +7 -2
- package/types/components/modal/ModalConfirm.vue.d.ts +18 -0
- package/types/components/page-layout/AppPageDetail.vue.d.ts +40 -0
- package/types/components/page-layout/AppPageListDetailLayout.vue.d.ts +39 -0
- package/types/components/page-layout/AppPageListDetailSheet.vue.d.ts +44 -0
- package/types/components/page-layout/AppPageListDetailSplit.vue.d.ts +47 -0
- package/types/components/page-layout/index.d.ts +4 -0
- package/types/components/page-layout/types.d.ts +102 -0
- package/types/components/page-layout/use-list-detail.d.ts +58 -0
- package/types/components/panel/PanelEditSwitch.vue.d.ts +30 -0
- package/types/components/table/TableMain.vue.d.ts +9 -2
- package/types/components/view-layout/ViewLayoutNavigation.vue.d.ts +13 -0
- package/types/components/view-layout/types.d.ts +46 -0
- package/types/hooks/use-active-record.d.ts +64 -0
- package/types/index.d.ts +1 -0
- package/types/inject/key.d.ts +29 -0
- package/types/injectInfo.json.d.ts +1 -1
- package/types/types/utility-types.d.ts +17 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AppPageListDetailDetailScope, AppPageListDetailEmptyScope, AppPageListDetailListProps, AppPageListDetailProps } from './types';
|
|
2
|
+
declare const _default: <T extends Record<string, any>, R = any>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
3
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, never> & {
|
|
4
|
+
listProps: AppPageListDetailListProps<T>;
|
|
5
|
+
ctx: Omit<AppPageListDetailDetailScope<T, R>, "data" | "loading">;
|
|
6
|
+
listEmpty: boolean;
|
|
7
|
+
listWidth: string;
|
|
8
|
+
/**
|
|
9
|
+
* 本形态不消费,但 [MUST] 声明 —— 门面用 `<component :is>` 给两个形态下发**同一组绑定**,
|
|
10
|
+
* 某形态不声明的键会退化成 fallthrough attr、经 $attrs 泄漏成 AppPage 上的 DOM 属性
|
|
11
|
+
* (实测漏出 `sheetsize="70%"`)。
|
|
12
|
+
*/
|
|
13
|
+
sheetSize?: string | number;
|
|
14
|
+
/** 同 `sheetSize`:本形态不消费,[MUST] 声明以防 attr 泄漏成 DOM 属性 */
|
|
15
|
+
sheetLayerType?: "drawer" | "modal";
|
|
16
|
+
detailApi?: AppPageListDetailProps<R>["detailApi"];
|
|
17
|
+
detailTitle?: AppPageListDetailProps<R>["detailTitle"];
|
|
18
|
+
} & Partial<{}>> & import('vue').PublicProps;
|
|
19
|
+
expose(exposed: import('vue').ShallowUnwrapRef<{}>): void;
|
|
20
|
+
attrs: any;
|
|
21
|
+
slots: Readonly<{
|
|
22
|
+
list?: (scope: {
|
|
23
|
+
listProps: AppPageListDetailListProps<T>;
|
|
24
|
+
}) => unknown;
|
|
25
|
+
detail?: (scope: AppPageListDetailDetailScope<T, R>) => unknown;
|
|
26
|
+
empty?: (scope: AppPageListDetailEmptyScope) => unknown;
|
|
27
|
+
top?: () => unknown;
|
|
28
|
+
bottom?: () => unknown;
|
|
29
|
+
right?: () => unknown;
|
|
30
|
+
}> & {
|
|
31
|
+
list?: (scope: {
|
|
32
|
+
listProps: AppPageListDetailListProps<T>;
|
|
33
|
+
}) => unknown;
|
|
34
|
+
detail?: (scope: AppPageListDetailDetailScope<T, R>) => unknown;
|
|
35
|
+
empty?: (scope: AppPageListDetailEmptyScope) => unknown;
|
|
36
|
+
top?: () => unknown;
|
|
37
|
+
bottom?: () => unknown;
|
|
38
|
+
right?: () => unknown;
|
|
39
|
+
};
|
|
40
|
+
emit: {};
|
|
41
|
+
}>) => import('vue').VNode & {
|
|
42
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
43
|
+
};
|
|
44
|
+
export default _default;
|
|
45
|
+
type __VLS_PrettifyLocal<T> = {
|
|
46
|
+
[K in keyof T]: T[K];
|
|
47
|
+
} & {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ActiveRecordKey } from '../../hooks/use-active-record';
|
|
2
|
+
import { NoInferType } from '../../types';
|
|
3
|
+
/** 形态:`auto` 按视口宽定档(默认)|`split` 强制宽屏并排|`sheet` 强制窄屏抽屉 */
|
|
4
|
+
export type AppPageListDetailMode = "auto" | "split" | "sheet";
|
|
5
|
+
/**
|
|
6
|
+
* `#list` 插槽 scope 携带的「列表接线包」——业务 [MUST] 整体 `v-bind` 到列表组件上:
|
|
7
|
+
*
|
|
8
|
+
* ```vue
|
|
9
|
+
* <template #list="{ listProps }">
|
|
10
|
+
* <ListLayout v-bind="listProps" :api="api" :columns="columns" row-key="id" />
|
|
11
|
+
* </template>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* 它同时接了三条线:两个「当前项」model(双向)+ `dataChange`(门面据此算序列 / 自动激活首条)
|
|
15
|
+
* + `ref`(门面据此调 `refresh()` 实现 `refreshList`)。少接一条对应能力即静默失效。
|
|
16
|
+
*/
|
|
17
|
+
export interface AppPageListDetailListProps<T = any> {
|
|
18
|
+
activeId: ActiveRecordKey | undefined;
|
|
19
|
+
"onUpdate:activeId": (v: ActiveRecordKey | undefined) => void;
|
|
20
|
+
activeData: T | undefined;
|
|
21
|
+
"onUpdate:activeData": (v: T | undefined) => void;
|
|
22
|
+
onDataChange: (rows: T[]) => void;
|
|
23
|
+
ref: (el: unknown) => void;
|
|
24
|
+
}
|
|
25
|
+
/** `#detail` 插槽 scope(`data` / `loading` 仅在传了 `detailApi` 时有值) */
|
|
26
|
+
export interface AppPageListDetailDetailScope<T = any, R = any> {
|
|
27
|
+
/** 当前项唯一键 */
|
|
28
|
+
id: ActiveRecordKey | undefined;
|
|
29
|
+
/** 当前项对应的**列表 item**(恒为列表接口原样,[MUST NOT] 被详情回写) */
|
|
30
|
+
record: T | undefined;
|
|
31
|
+
/** 当前项在序列中的位置(1 基;不在序列内 = 0) */
|
|
32
|
+
index: number;
|
|
33
|
+
/** 序列总条数(= 列表当前页条数,本期不跨页) */
|
|
34
|
+
total: number;
|
|
35
|
+
hasPrev: boolean;
|
|
36
|
+
hasNext: boolean;
|
|
37
|
+
goPrev: () => void;
|
|
38
|
+
goNext: () => void;
|
|
39
|
+
/** 触发列表整体刷新(REQ-06 唯一通路,[MUST NOT] 反向回写 record) */
|
|
40
|
+
refreshList: () => void;
|
|
41
|
+
/** 详情接口数据(未传 `detailApi` 时恒为 undefined——此时详情数据由业务自持) */
|
|
42
|
+
data: R | undefined;
|
|
43
|
+
/** 详情接口拉取中(未传 `detailApi` 时恒为 false) */
|
|
44
|
+
loading: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** `#empty` 插槽 scope */
|
|
47
|
+
export interface AppPageListDetailEmptyScope {
|
|
48
|
+
/** true = 列表本身无数据;false = 有数据但未选中。两种空态文案不同,由业务分流 */
|
|
49
|
+
listEmpty: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** `AppPageListDetailLayout` props(不含两个 v-model,见 README) */
|
|
52
|
+
export interface AppPageListDetailProps<R = any> {
|
|
53
|
+
/** 形态;默认 `auto`(按视口宽 `splitAt` 定档) */
|
|
54
|
+
mode?: AppPageListDetailMode;
|
|
55
|
+
/** `auto` 档的切档阈值(px,视口宽),默认 1200 */
|
|
56
|
+
splitAt?: number;
|
|
57
|
+
/** 分栏态列表列宽(CSS 长度)。[MUST NOT] 写死 px,默认 `clamp(280px, 28%, 420px)` */
|
|
58
|
+
listWidth?: string;
|
|
59
|
+
/**
|
|
60
|
+
* 列表出数后自动激活第一条。**两形态默认值不同**:`split` 默认 `true`(宽屏右半屏不该空着)、
|
|
61
|
+
* `sheet` 默认 `false`(窄屏自动弹抽屉是打扰)。显式传值优先。
|
|
62
|
+
* ⚠️ 详情若有副作用(如「打开即标记已读」)[MUST] 显式关掉。
|
|
63
|
+
*/
|
|
64
|
+
autoActiveFirst?: boolean;
|
|
65
|
+
/** 传了才把 `activeId` 同步进 URL query(`replace` 不 `push`)。复原仍走不变式校验 */
|
|
66
|
+
queryKey?: string;
|
|
67
|
+
/**
|
|
68
|
+
* 详情接口。传了则内置 `ViewLayout`(零配置拿到导航 + `refreshToken` 接线);
|
|
69
|
+
* 不传则 `#detail` 原样渲染、门面不发任何详情请求(覆盖「详情即列表数据」形态)。
|
|
70
|
+
* 入参为 `{ id: activeId }`。
|
|
71
|
+
*/
|
|
72
|
+
detailApi?: (params?: {
|
|
73
|
+
id: ActiveRecordKey;
|
|
74
|
+
}) => Promise<R>;
|
|
75
|
+
/**
|
|
76
|
+
* 详情区标题。字符串 = 固定标题;函数 = 按数据算标题。
|
|
77
|
+
*
|
|
78
|
+
* 函数签名 `(detailData, record) => string`:
|
|
79
|
+
* - `detailData` = 详情接口数据(**未传 `detailApi` 时恒 `undefined`**)
|
|
80
|
+
* - `record` = 当前项对应的列表 item(两种形态下都有),**「详情即列表数据」形态靠它出标题**
|
|
81
|
+
*
|
|
82
|
+
* 渲染位置:**详情区顶部**(两形态一致),而非 sheet 弹层的 header —— 详情数据只在
|
|
83
|
+
* 详情区内部(`ViewLayout` 插槽作用域)可得,弹层那一层拿不到。缺省不传则不渲染标题条,
|
|
84
|
+
* 业务照旧可在 `#detail` 里自绘。
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ 首参用 `NoInferType<R>`(本仓自带、非 TS 5.4 内置 `NoInfer` —— 内置的会原样落进
|
|
87
|
+
* 发布的 d.ts,下游 TS < 5.4 会报找不到):`R` [MUST] 只由 `detailApi` 的返回类型定。否则本回调的参数标注
|
|
88
|
+
* 也会参与 `R` 推断,与 `detailApi` 推出的候选打架(实测:`detailApi` 返
|
|
89
|
+
* `Promise<X | undefined>`、回调标 `(d?: X)` 时报 TS2322,把别扭甩给消费方)。
|
|
90
|
+
*/
|
|
91
|
+
detailTitle?: string | ((detailData: NoInferType<R> | undefined, record: any) => string);
|
|
92
|
+
/**
|
|
93
|
+
* sheet 形态的弹层形态:`drawer` 抽屉(默认)|`modal` 居中弹窗。透传 `ModalConfirm.layerType`。
|
|
94
|
+
* 详情区两种承载都支持;窄屏下抽屉更常见,故默认抽屉。
|
|
95
|
+
*/
|
|
96
|
+
sheetLayerType?: "drawer" | "modal";
|
|
97
|
+
/**
|
|
98
|
+
* sheet 形态的弹层尺寸(透传 `ModalConfirm.width`):`drawer` 下是抽屉尺寸、
|
|
99
|
+
* `modal` 下是弹窗宽度,两形态同一个口子,默认 `"70%"`。
|
|
100
|
+
*/
|
|
101
|
+
sheetSize?: string | number;
|
|
102
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ComputedRef, Ref, ShallowRef } from 'vue';
|
|
2
|
+
import { ActiveRecordKey } from '../../hooks/use-active-record';
|
|
3
|
+
import { AppPageListDetailMode } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* 形态定档(`AppPageListDetailLayout` 内部组合函数,**[MUST NOT] 出桶导出**)。
|
|
6
|
+
*
|
|
7
|
+
* 判据 [MUST] 用**视口宽**(matchMedia),[MUST NOT] 用容器宽(ResizeObserver)——
|
|
8
|
+
* 设备宽固定 ≠ 容器宽固定:侧边栏折叠是同设备内的高频操作、会改内容区容器宽,
|
|
9
|
+
* 用容器断点会折一下侧栏就跨阈值触发形态重建(spec D4 / Ⓒ)。
|
|
10
|
+
*
|
|
11
|
+
* 不复用 `useBreakpoint`:那是固定五档梯子(xs…xl),承不住任意 `splitAt` 阈值。
|
|
12
|
+
*/
|
|
13
|
+
export declare function useAppPageListDetailMode(options: {
|
|
14
|
+
mode: () => AppPageListDetailMode;
|
|
15
|
+
splitAt: () => number;
|
|
16
|
+
}): {
|
|
17
|
+
isSplit: ComputedRef<boolean>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 序列 + 导航(`AppPageListDetailLayout` 内部组合函数,**[MUST NOT] 出桶导出**)。
|
|
21
|
+
*
|
|
22
|
+
* 序列来源 = 列表组件的 `dataChange`(门面经 `#list` 插槽 scope 的 `listProps` 把
|
|
23
|
+
* `onDataChange` 交给业务 `v-bind` 上去)——门面不持有列表数据、也不知道 `rowKey`。
|
|
24
|
+
*
|
|
25
|
+
* 定位当前项用**对象身份**(`rows.indexOf(activeData)`)而非键比对:`activeData` 恒为
|
|
26
|
+
* 列表数据原样(同一数组里的那个对象,由 `useActiveRecord` 保证),故身份必命中;
|
|
27
|
+
* 命中失败(当前项不在序列内)→ `index=0` / 两端皆 false,正是 spec 要的边界行为。
|
|
28
|
+
*/
|
|
29
|
+
export declare function useAppPageListDetailSequence<T extends Record<string, any>>(options: {
|
|
30
|
+
activeId: Ref<ActiveRecordKey | undefined>;
|
|
31
|
+
activeData: Ref<T | undefined>;
|
|
32
|
+
autoActiveFirst: () => boolean;
|
|
33
|
+
}): {
|
|
34
|
+
rows: ShallowRef<T[]>;
|
|
35
|
+
onDataChange: (rows: T[]) => void;
|
|
36
|
+
index: ComputedRef<number>;
|
|
37
|
+
total: ComputedRef<number>;
|
|
38
|
+
hasPrev: ComputedRef<boolean>;
|
|
39
|
+
hasNext: ComputedRef<boolean>;
|
|
40
|
+
goPrev: () => void;
|
|
41
|
+
goNext: () => void;
|
|
42
|
+
/** 深链复原用:把 query 里的 id 挂起,等列表首次出数再写(写入合法性交给列表校验) */
|
|
43
|
+
pendingId: Ref<ActiveRecordKey | undefined>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* `activeId` ↔ URL query 同步(`AppPageListDetailLayout` 内部组合函数,**[MUST NOT] 出桶导出**)。
|
|
47
|
+
*
|
|
48
|
+
* - **[MUST] 用 `replace` 而非 `push`**:连点「下一条」10 次会攒 10 条 history,
|
|
49
|
+
* 用户要按 10 次后退才能离开页面(spec D9;TabsMain queryKey 期踩过同族坑)。
|
|
50
|
+
* - 复原**不绕过不变式**:读到的 id 先挂起(`pendingId`),等列表首次出数再写入,
|
|
51
|
+
* 合法性由列表族的不变式内核校验(查不到 → 忽略 + warn,不激活)。
|
|
52
|
+
* - 无 router 环境(单测 / 脱离路由)静默降级,[MUST NOT] 抛错。
|
|
53
|
+
*/
|
|
54
|
+
export declare function useAppPageListDetailQuerySync(options: {
|
|
55
|
+
queryKey: () => string | undefined;
|
|
56
|
+
activeId: Ref<ActiveRecordKey | undefined>;
|
|
57
|
+
pendingId: Ref<ActiveRecordKey | undefined>;
|
|
58
|
+
}): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PanelEditSwitchProps } from './types';
|
|
2
|
+
import { ExcludeNeverProperties, ExtractFormStringifyRawFromObject, FormDataInfo } from '../form';
|
|
3
|
+
declare const _default: <PO extends Record<string, any>, SO extends Record<string, any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
4
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
5
|
+
readonly onSubmitSuccess?: ((data: ExcludeNeverProperties<ExtractFormStringifyRawFromObject<PO, SO>>) => any) | undefined;
|
|
6
|
+
readonly "onUpdate:editing"?: ((value: boolean) => any) | undefined;
|
|
7
|
+
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onSubmitSuccess" | "onUpdate:editing"> & ({
|
|
8
|
+
editing?: boolean;
|
|
9
|
+
} & PanelEditSwitchProps<PO, SO>) & Partial<{}>> & import('vue').PublicProps;
|
|
10
|
+
expose(exposed: import('vue').ShallowUnwrapRef<{}>): void;
|
|
11
|
+
attrs: any;
|
|
12
|
+
slots: {
|
|
13
|
+
default?(_: {
|
|
14
|
+
stringifyDraftData: ExcludeNeverProperties<ExtractFormStringifyRawFromObject<PO, SO>>;
|
|
15
|
+
}): any;
|
|
16
|
+
default?(_: {
|
|
17
|
+
stringifyDraftData: ExcludeNeverProperties<ExtractFormStringifyRawFromObject<PO, SO>>;
|
|
18
|
+
}): any;
|
|
19
|
+
editor?(_: {
|
|
20
|
+
stringifyDraftData: ExcludeNeverProperties<ExtractFormStringifyRawFromObject<PO, SO>>;
|
|
21
|
+
}): any;
|
|
22
|
+
};
|
|
23
|
+
emit: ((e: "submitSuccess", data: FormDataInfo<PO, SO>["stringifyData"]) => void) & ((evt: "update:editing", value: boolean) => void);
|
|
24
|
+
}>) => import('vue').VNode & {
|
|
25
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
26
|
+
};
|
|
27
|
+
export default _default;
|
|
28
|
+
type __VLS_PrettifyLocal<T> = {
|
|
29
|
+
[K in keyof T]: T[K];
|
|
30
|
+
} & {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActiveRecordKey } from '../../hooks/use-active-record';
|
|
1
2
|
import { TableMainProps, TableMainInstance, ElTableColumnProps, TablePageInfo } from './types';
|
|
2
3
|
declare const _default: <T extends Record<string, any>, SQ extends Record<string, any>, F extends Record<string, any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
3
4
|
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
@@ -5,11 +6,15 @@ declare const _default: <T extends Record<string, any>, SQ extends Record<string
|
|
|
5
6
|
readonly onLoadingChange?: ((value: boolean) => any) | undefined;
|
|
6
7
|
readonly "onUpdate:refreshInterval"?: ((value: number) => any) | undefined;
|
|
7
8
|
readonly "onUpdate:customView"?: ((value: boolean) => any) | undefined;
|
|
9
|
+
readonly "onUpdate:activeId"?: ((value: ActiveRecordKey | undefined) => any) | undefined;
|
|
10
|
+
readonly "onUpdate:activeData"?: ((value: T | undefined) => any) | undefined;
|
|
8
11
|
readonly onPageChange?: ((value: number) => any) | undefined;
|
|
9
12
|
readonly onPageSizeChange?: ((value: number) => any) | undefined;
|
|
10
13
|
readonly onDataChange?: ((data: T[]) => any) | undefined;
|
|
11
14
|
readonly onPageInfoChange?: ((info: TablePageInfo) => any) | undefined;
|
|
12
|
-
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onUpdate:isAutoRefresh" | "onLoadingChange" | "onUpdate:refreshInterval" | "onUpdate:customView" | "onPageChange" | "onPageSizeChange" | "onDataChange" | "onPageInfoChange"> & ({
|
|
15
|
+
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onUpdate:isAutoRefresh" | "onLoadingChange" | "onUpdate:refreshInterval" | "onUpdate:customView" | "onUpdate:activeId" | "onUpdate:activeData" | "onPageChange" | "onPageSizeChange" | "onDataChange" | "onPageInfoChange"> & ({
|
|
16
|
+
activeId?: ActiveRecordKey | undefined;
|
|
17
|
+
activeData?: T | undefined;
|
|
13
18
|
isAutoRefresh?: boolean;
|
|
14
19
|
refreshInterval?: number;
|
|
15
20
|
customView?: boolean;
|
|
@@ -34,6 +39,8 @@ declare const _default: <T extends Record<string, any>, SQ extends Record<string
|
|
|
34
39
|
index: number;
|
|
35
40
|
selected: boolean;
|
|
36
41
|
toggleSelect: () => void;
|
|
42
|
+
active: boolean;
|
|
43
|
+
setActive: () => void;
|
|
37
44
|
}): any;
|
|
38
45
|
};
|
|
39
46
|
emit: {
|
|
@@ -41,7 +48,7 @@ declare const _default: <T extends Record<string, any>, SQ extends Record<string
|
|
|
41
48
|
(e: "dataChange", data: T[]): void;
|
|
42
49
|
(e: "loadingChange", value: boolean): void;
|
|
43
50
|
(e: "pageInfoChange", info: TablePageInfo): void;
|
|
44
|
-
} & (((evt: "update:isAutoRefresh", value: boolean) => void) & ((evt: "update:refreshInterval", value: number) => void) & ((evt: "update:customView", value: boolean) => void));
|
|
51
|
+
} & (((evt: "update:isAutoRefresh", value: boolean) => void) & ((evt: "update:refreshInterval", value: number) => void) & ((evt: "update:customView", value: boolean) => void) & ((evt: "update:activeId", value: ActiveRecordKey | undefined) => void) & ((evt: "update:activeData", value: T | undefined) => void));
|
|
45
52
|
}>) => import('vue').VNode & {
|
|
46
53
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
47
54
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AppPageListDetailNavigationContext } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* ViewLayout toolbar 内置导航(view-layout 族内部件,**不出桶导出**)。
|
|
4
|
+
*
|
|
5
|
+
* 独立成组件的理由:ViewLayout 的 toolbar 在精细流 / 自然流两个分支各写了一遍,
|
|
6
|
+
* 内联会把同一段导航标记重复两处;抽出后两处各一行引用(且 ViewLayout 主干不涨过程代码)。
|
|
7
|
+
*/
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
/** 由 AppPageListDetailLayout provide 的上下文(ViewLayout 侧已判定非空才渲染本组件) */
|
|
10
|
+
ctx: AppPageListDetailNavigationContext;
|
|
11
|
+
};
|
|
12
|
+
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
export default _default;
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
1
2
|
import { ActionBtnConfigItem } from '../display';
|
|
3
|
+
/**
|
|
4
|
+
* 列表 / 详情同页范式的导航上下文(`APP_PAGE_LIST_DETAIL_CONTEXT` 的值形状)。
|
|
5
|
+
* 在此重声明一份**结构等价**的类型,是为了让 view-layout 族不反向 import inject 桶
|
|
6
|
+
* (inject/key.ts 已 import 了 app-layout 的类型,反向引会成环)。
|
|
7
|
+
*/
|
|
8
|
+
export interface AppPageListDetailNavigationContext {
|
|
9
|
+
hasPrev: Readonly<Ref<boolean>>;
|
|
10
|
+
hasNext: Readonly<Ref<boolean>>;
|
|
11
|
+
index: Readonly<Ref<number>>;
|
|
12
|
+
total: Readonly<Ref<number>>;
|
|
13
|
+
goPrev: () => void;
|
|
14
|
+
goNext: () => void;
|
|
15
|
+
refreshList: () => void;
|
|
16
|
+
}
|
|
2
17
|
/**
|
|
3
18
|
* ViewLayout 默认插槽 scope(注入给插槽内容的数据与操作句柄)
|
|
4
19
|
*/
|
|
@@ -20,6 +35,19 @@ export interface ViewLayoutButtonContext {
|
|
|
20
35
|
loading: boolean;
|
|
21
36
|
/** 手动刷新(与右上角刷新按钮同源,等价插槽 scope.refresh) */
|
|
22
37
|
refresh: () => Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* 以下导航句柄**仅在 `AppPageListDetailLayout` 内**有值(门面 provide 上下文时注入);
|
|
40
|
+
* 单独使用 ViewLayout 时恒为 `undefined`。业务自定义按钮可据此做「保存并下一条」等组合动作。
|
|
41
|
+
*/
|
|
42
|
+
hasPrev?: boolean;
|
|
43
|
+
hasNext?: boolean;
|
|
44
|
+
/** 当前项在序列中的位置(1 基;不在序列内 = 0) */
|
|
45
|
+
index?: number;
|
|
46
|
+
total?: number;
|
|
47
|
+
goPrev?: () => void;
|
|
48
|
+
goNext?: () => void;
|
|
49
|
+
/** 触发列表整体刷新(REQ-06 唯一通路) */
|
|
50
|
+
refreshList?: () => void;
|
|
23
51
|
}
|
|
24
52
|
/** ViewLayout toolbar 额外按钮配置(ActionBtnGroup 单项,default-size=small 对齐 table-toolbar 视觉惯例) */
|
|
25
53
|
export type ViewLayoutButtonConfig = ActionBtnConfigItem<ViewLayoutButtonContext>;
|
|
@@ -31,6 +59,17 @@ export interface ViewLayoutProps<P = Record<string, any>, R = any> {
|
|
|
31
59
|
api: (params?: P) => Promise<R>;
|
|
32
60
|
/** 拉取参数;响应式变化不自动重拉(需要时调 scope.refresh()) */
|
|
33
61
|
params?: P;
|
|
62
|
+
/**
|
|
63
|
+
* 外部重拉触发口:**值变化即触发一次 `api` 重拉**,且 [MUST NOT] 进 `params`
|
|
64
|
+
* (不污染请求参数、不发给后端)。默认 `undefined` = 不触发,老行为逐字节不变。
|
|
65
|
+
*
|
|
66
|
+
* - 语义是「令牌」而非参数:把「什么时候该重拉」与「拉什么」解耦——需要新参数时改
|
|
67
|
+
* `params`(不自动重拉,防请求风暴的既有设计),需要重拉时动 `refreshToken`。
|
|
68
|
+
* - 变为 `undefined` 时**不触发**(清空令牌 ≠ 要求刷新),避免无谓请求。
|
|
69
|
+
* - 典型用途:列表 / 详情同页范式里承接 `activeId` 变化(`:refreshToken="activeId"`)。
|
|
70
|
+
* [MUST NOT] 改用 `:key="activeId"` 重建 —— 那会连 toolbar / 导航一起重建,连点时闪烁。
|
|
71
|
+
*/
|
|
72
|
+
refreshToken?: string | number;
|
|
34
73
|
/** 精细流:默认 false(自然流:无高度预算,内容按自身尺寸);true=包 HeightProvider 精确扣 toolbar reserve,内容区可用高(slot scope viewportHeight + 容器 max-height) */
|
|
35
74
|
refine?: boolean;
|
|
36
75
|
/** 显式视口高,优先级最高;未传降级 inject SCOPE_VIEWPORT_MAX_HEIGHT scope 链 */
|
|
@@ -45,6 +84,13 @@ export interface ViewLayoutProps<P = Record<string, any>, R = any> {
|
|
|
45
84
|
showAutoRefresh?: boolean;
|
|
46
85
|
/** 是否显示手动刷新按钮(默认 true) */
|
|
47
86
|
showRefresh?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* 是否显示内置「上一条 / 下一条」导航(默认 true)。
|
|
89
|
+
* ⚠️ **真正的显隐判据是可选 inject 探测**:只有置于 `AppPageListDetailLayout` 内
|
|
90
|
+
* (注入到导航上下文)才渲染;单独使用 ViewLayout 时无论本值为何都不渲染 ——
|
|
91
|
+
* 故老用法一行不改、零变化。本 prop 用于在门面内**显式关掉**导航。
|
|
92
|
+
*/
|
|
93
|
+
showNavigation?: boolean;
|
|
48
94
|
/** 左侧额外按钮(ActionBtnGroup 配置,ctx = { loading, refresh },default-size=small 对齐 table-toolbar) */
|
|
49
95
|
leftExtraButtons?: ViewLayoutButtonConfig[];
|
|
50
96
|
/** 右侧额外按钮(置于 AutoRefreshGroup 之前) */
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* 当前项唯一键:`string | number` **两种都是一等公民**——出口值保持业务数据里的原始类型
|
|
4
|
+
* (`rowKey` 是字段名时直接取 `row[rowKey]`,数字 id 出数字、字符串 id 出字符串),
|
|
5
|
+
* 仅**比对**时内部统一 `String()` 归一,故外部传另一种类型也照样命中。
|
|
6
|
+
*/
|
|
7
|
+
export type ActiveRecordKey = string | number;
|
|
8
|
+
/**
|
|
9
|
+
* 行 → 当前项键值(保原始类型)。`rowKey` 为字段名 → 直取 `row[rowKey]`(number 保持 number);
|
|
10
|
+
* 为取键函数 → 用其返回值(契约上是 string)。
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ [MUST NOT] 在此 `String()` 归一 —— 那会把业务的数字 id 强制变成字符串吐回去
|
|
13
|
+
* (归一只发生在比对处,见 `normalizeActiveKey`)。
|
|
14
|
+
*/
|
|
15
|
+
export declare const activeRecordValueOf: <T extends Record<string, any>>(row: T, rowKey: string | ((row: T) => string)) => ActiveRecordKey;
|
|
16
|
+
/**
|
|
17
|
+
* 「当前项」(active)双 model 同步内核(core 内部组合函数,**[MUST NOT] 出桶导出**——
|
|
18
|
+
* 见 hooks/index.ts 锁面注释;使用者可枚举:TableMain / DataListView / DataGridView)。
|
|
19
|
+
*
|
|
20
|
+
* `activeId`(唯一键)与 `activeData`(列表 item)**同生同灭**,由本 hook 守以下不变式
|
|
21
|
+
* (spec D2「先有列表,才有详情」):
|
|
22
|
+
*
|
|
23
|
+
* > `activeId` 有值 ⇒ `activeData` 必有值,且 `activeData` 恒为**列表数据原样**(非详情数据)。
|
|
24
|
+
*
|
|
25
|
+
* | 触发 | 行为 |
|
|
26
|
+
* | --- | --- |
|
|
27
|
+
* | 用户点击某项(`setActive(row)`) | 同时写两者(data 即被点行对象,天然一致) |
|
|
28
|
+
* | 外部写入 `activeData`(如序列导航切上/下一条) | **对等通道**:按行反查键 → 同步 `activeId`;行不在列表内 → 忽略 + `console.warn` + 回滚 |
|
|
29
|
+
* | 列表数据刷新 | 按当前 id 在新数据内重查 → 刷新 `activeData`;**查不到 → 两者一并清空** |
|
|
30
|
+
* | 外部写入不在列表内的 id | **忽略 + `console.warn`**(回滚到上一个合法值),[MUST NOT] 擅自清空用户既有选中 |
|
|
31
|
+
* | 外部写入 `undefined` | 合法清空(两者一并归零) |
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ 外部写入的校验与同步走 `watch`(值变化语义):**同一 tick 内把 `activeId` 改回原值**
|
|
34
|
+
* (如 `undefined → "1" → undefined`)净值未变、watch 不触发,`activeData` 会停在中间态。
|
|
35
|
+
* 实际写入路径(点击走 `setActive` 同步写两者、外部写入天然跨 tick)不会遇到;
|
|
36
|
+
* [MUST NOT] 在同一 tick 内对 `activeId` 做往返赋值。
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ `activeId` **出口值保原始类型**(`rowKey` 为字段名时即 `row[rowKey]`,数字 id 出数字);
|
|
39
|
+
* 外部写入用另一种类型(如字符串 `"1"` 对数字 id)同样命中——比对统一 `String()` 归一,
|
|
40
|
+
* 且**保留调用方写入的原值**,不回写覆盖。
|
|
41
|
+
*
|
|
42
|
+
* @param options.data 当前列表数据(getter,数组整体重赋值时触发重查)
|
|
43
|
+
* @param options.keyOf 行 → 唯一键**原始值**(各消费方用自己的 rowKey 解析,
|
|
44
|
+
* [MUST] 走 {@link activeRecordValueOf} 保类型,[MUST NOT] 自行 `String()` 归一)
|
|
45
|
+
* @param options.activeId `v-model:activeId`
|
|
46
|
+
* @param options.activeData `v-model:activeData`
|
|
47
|
+
* @param options.name 消费方组件名(仅用于 warn 前缀)
|
|
48
|
+
* @param options.enabled 本实例是否接管不变式(默认 true)。**唯一用途**:TableMain 在
|
|
49
|
+
* `customView` 态把渲染与点击交给内层 `DataListView`(后者自带本 hook),此时 TableMain 侧
|
|
50
|
+
* [MUST] 关掉,否则同一对 model 上跑两份状态机 —— 双重 warn、两个 `lastValidId` 各自为政。
|
|
51
|
+
*/
|
|
52
|
+
export declare function useActiveRecord<T extends Record<string, any>>(options: {
|
|
53
|
+
data: () => T[];
|
|
54
|
+
keyOf: (row: T) => ActiveRecordKey;
|
|
55
|
+
activeId: Ref<ActiveRecordKey | undefined>;
|
|
56
|
+
activeData: Ref<T | undefined>;
|
|
57
|
+
name: string;
|
|
58
|
+
enabled?: () => boolean;
|
|
59
|
+
}): {
|
|
60
|
+
/** 该行是否为当前项 */
|
|
61
|
+
isActive: (row: T) => boolean;
|
|
62
|
+
/** 设为当前项(传 undefined = 清空);内部写入路径,不触发非法值告警 */
|
|
63
|
+
setActive: (row: T | undefined) => void;
|
|
64
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './components/table';
|
|
|
9
9
|
export * from './components/list-layout';
|
|
10
10
|
export * from './components/panel';
|
|
11
11
|
export * from './components/app-layout';
|
|
12
|
+
export * from './components/page-layout';
|
|
12
13
|
export * from './components/slot-layout';
|
|
13
14
|
export * from './components/view-layout';
|
|
14
15
|
export * from './router';
|
package/types/inject/key.d.ts
CHANGED
|
@@ -200,3 +200,32 @@ export declare const FORM_NEST_REGISTRY_KEY: InjectionKey<NestFormExposeMap>;
|
|
|
200
200
|
* Symbol.for 保证跨包/跨 chunk/跨 ESM 实例唯一(规则 8,同上)。
|
|
201
201
|
*/
|
|
202
202
|
export declare const NEST_LAYOUT_SCALE_KEY: InjectionKey<Readonly<Ref<number>>>;
|
|
203
|
+
/**
|
|
204
|
+
* 列表 / 详情同页范式上下文 inject key(`AppPageListDetailLayout` 唯一 provide、后代按需 inject)。
|
|
205
|
+
* ---
|
|
206
|
+
* 承载两件事,**刻意合成同一个上下文、[MUST NOT] 另开第二个接缝**:
|
|
207
|
+
* - **序列导航**(`hasPrev` / `hasNext` / `goPrev` / `goNext` / `index` / `total`)——
|
|
208
|
+
* `ViewLayout` 据此**可选探测**是否渲染内置上/下一条按钮:注入到才渲染,
|
|
209
|
+
* 单独使用 `ViewLayout` 时完全不渲染(老用法一行不改)。
|
|
210
|
+
* - **`refreshList`**(REQ-06 单向同步的唯一通路):详情侧判定与列表 item 不同步时触发
|
|
211
|
+
* **列表整体刷新**,由列表接口出新数据;[MUST NOT] 由详情反向回写 `activeData`。
|
|
212
|
+
* ---
|
|
213
|
+
* `index` 为 **1 基**(无当前项 = 0),直接可作 “第 index / total 条” 展示。
|
|
214
|
+
* Symbol.for 保证跨包/跨 chunk/跨 ESM 实例唯一(规则 8,同上)。
|
|
215
|
+
*/
|
|
216
|
+
export declare const APP_PAGE_LIST_DETAIL_CONTEXT: InjectionKey<{
|
|
217
|
+
/** 是否有上一条(当前项不在序列内时为 false) */
|
|
218
|
+
hasPrev: Readonly<Ref<boolean>>;
|
|
219
|
+
/** 是否有下一条(当前项不在序列内时为 false) */
|
|
220
|
+
hasNext: Readonly<Ref<boolean>>;
|
|
221
|
+
/** 当前项在序列中的位置(1 基;无当前项 / 不在序列内 = 0) */
|
|
222
|
+
index: Readonly<Ref<number>>;
|
|
223
|
+
/** 当前序列总条数(= 列表当前页数据条数,本期不跨页) */
|
|
224
|
+
total: Readonly<Ref<number>>;
|
|
225
|
+
/** 切到上一条(无上一条时空操作) */
|
|
226
|
+
goPrev: () => void;
|
|
227
|
+
/** 切到下一条(无下一条时空操作) */
|
|
228
|
+
goNext: () => void;
|
|
229
|
+
/** 触发列表整体刷新(REQ-06 唯一通路) */
|
|
230
|
+
refreshList: () => void;
|
|
231
|
+
}>;
|
|
@@ -6,3 +6,20 @@
|
|
|
6
6
|
* -> { page: { page: number; pageSize: number } }
|
|
7
7
|
*/
|
|
8
8
|
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
9
|
+
/**
|
|
10
|
+
* 阻断类型推断(等价 TS 5.4 内置的 `NoInfer<T>`,自带一份不依赖下游 TS 版本)。
|
|
11
|
+
* ---
|
|
12
|
+
* 用于「同一个泛型参数被多处标注、但只应由其中一处决定」的场景:把不该参与推断的那处
|
|
13
|
+
* 包上本类型,该位置便只做校验、不产候选。
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ [MUST NOT] 直接用 TS 内置 `NoInfer<T>`:core 是发布包,它会原样落进 `.d.ts`,
|
|
16
|
+
* 下游 TS < 5.4 编译时报 `Cannot find name 'NoInfer'`(本包 devDep 声明的还是 `^5.2.2`)。
|
|
17
|
+
*
|
|
18
|
+
* 实现:`[T]` 包一层再按 `T extends any` 取下标——延迟解析使该位置成为非推断位。
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // R 只由 api 的返回类型定;title 回调的标注不参与推断
|
|
22
|
+
* api: () => Promise<R>;
|
|
23
|
+
* title?: (data: NoInferType<R>) => string;
|
|
24
|
+
*/
|
|
25
|
+
export type NoInferType<T> = [T][T extends any ? 0 : never];
|