@kine-design/crud 0.0.1-beta.2 → 0.0.1-beta.21

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.
Files changed (118) hide show
  1. package/.vlaude/last-session-id +1 -0
  2. package/components/crudPage/KCrudPage.tsx +178 -0
  3. package/components/crudPage/crudPage.css +64 -0
  4. package/components/crudPage/index.ts +10 -0
  5. package/components/editableTable/KEditableTable.tsx +281 -0
  6. package/components/editableTable/editableTable.css +268 -0
  7. package/components/editableTable/index.ts +10 -0
  8. package/components/formPage/KApprovalDialog.tsx +142 -0
  9. package/components/formPage/KFormCard.tsx +65 -0
  10. package/components/formPage/KFormPage.tsx +128 -0
  11. package/components/formPage/KMasterDetailPage.tsx +205 -0
  12. package/components/formPage/KStickyActionBar.tsx +33 -0
  13. package/components/formPage/formPage.css +629 -0
  14. package/components/formPage/index.ts +14 -0
  15. package/components/layout/KContent.tsx +20 -0
  16. package/components/layout/KHeader.tsx +37 -0
  17. package/components/layout/KLayout.tsx +82 -0
  18. package/components/layout/KSider.tsx +80 -0
  19. package/components/layout/index.ts +18 -0
  20. package/components/layout/layout.css +262 -0
  21. package/components/login/KLoginPage.tsx +129 -0
  22. package/components/login/index.ts +10 -0
  23. package/components/login/login.css +118 -0
  24. package/components/navMenu/KNavMenu.tsx +175 -0
  25. package/components/navMenu/index.ts +2 -0
  26. package/components/navMenu/navMenu.css +197 -0
  27. package/components/pageHeader/KPageHeader.tsx +85 -0
  28. package/components/pageHeader/index.ts +9 -0
  29. package/components/pageHeader/pageHeader.css +93 -0
  30. package/components/searchTable/KSearchTable.tsx +138 -0
  31. package/components/searchTable/index.ts +10 -0
  32. package/components/searchTable/searchTable.css +121 -0
  33. package/components/upload/KFileList.tsx +95 -0
  34. package/components/upload/KImageUpload.tsx +286 -0
  35. package/components/upload/KUpload.tsx +206 -0
  36. package/components/upload/index.ts +13 -0
  37. package/components/upload/types.ts +26 -0
  38. package/components/upload/upload.css +345 -0
  39. package/composables/auth/authGuard.ts +128 -0
  40. package/composables/auth/index.ts +23 -0
  41. package/composables/auth/types.ts +109 -0
  42. package/composables/auth/useAuth.ts +278 -0
  43. package/composables/auth/vCan.ts +95 -0
  44. package/composables/defineRepository.ts +224 -0
  45. package/composables/error/createErrorHandler.ts +46 -0
  46. package/composables/error/defaultFeedbackHandler.ts +76 -0
  47. package/composables/error/dispatchError.ts +70 -0
  48. package/composables/error/index.ts +32 -0
  49. package/composables/error/types.ts +57 -0
  50. package/composables/error/useErrorHandler.ts +41 -0
  51. package/composables/form/index.ts +18 -0
  52. package/composables/form/renderFormField.tsx +119 -0
  53. package/composables/form/types.ts +129 -0
  54. package/composables/form/useFormPage.ts +183 -0
  55. package/composables/index.ts +62 -0
  56. package/composables/page/index.ts +11 -0
  57. package/composables/page/types.ts +62 -0
  58. package/composables/page/useCrudPage.ts +88 -0
  59. package/composables/request/composables.ts +206 -0
  60. package/composables/request/controlGate.ts +143 -0
  61. package/composables/request/createRequest.ts +173 -0
  62. package/composables/request/index.ts +71 -0
  63. package/composables/request/orchestrator.ts +145 -0
  64. package/composables/request/requestBuilder.ts +418 -0
  65. package/composables/request/transport/fetchTransport.ts +79 -0
  66. package/composables/request/transport/xhrTransport.ts +100 -0
  67. package/composables/request/types.ts +226 -0
  68. package/composables/request/upload.ts +146 -0
  69. package/composables/router/createRouterGuard.ts +134 -0
  70. package/composables/router/defineCrudRoutes.ts +116 -0
  71. package/composables/router/index.ts +22 -0
  72. package/composables/router/types.ts +128 -0
  73. package/composables/router/useMenuFromRoutes.ts +109 -0
  74. package/composables/router/useTabStore.ts +183 -0
  75. package/composables/search/index.ts +11 -0
  76. package/composables/search/useAutoCompleteSearch.ts +161 -0
  77. package/composables/setupCrud.ts +43 -0
  78. package/composables/storage/createStorageAdapter.ts +72 -0
  79. package/composables/storage/index.ts +13 -0
  80. package/composables/storage/types.ts +30 -0
  81. package/composables/storage/useStorage.ts +108 -0
  82. package/composables/store/defineUserStore.ts +122 -0
  83. package/composables/store/index.ts +11 -0
  84. package/composables/types.ts +118 -0
  85. package/dist/components/crudPage/KCrudPage.d.ts +14 -0
  86. package/dist/components/crudPage/index.d.ts +9 -0
  87. package/dist/components/editableTable/KEditableTable.d.ts +146 -0
  88. package/dist/components/editableTable/index.d.ts +10 -0
  89. package/dist/components/formPage/KApprovalDialog.d.ts +99 -0
  90. package/dist/components/formPage/KFormCard.d.ts +49 -0
  91. package/dist/components/formPage/KFormPage.d.ts +14 -0
  92. package/dist/components/formPage/KMasterDetailPage.d.ts +14 -0
  93. package/dist/components/formPage/KStickyActionBar.d.ts +16 -0
  94. package/dist/components/formPage/index.d.ts +14 -0
  95. package/dist/components/layout/KLayout.d.ts +7 -4
  96. package/dist/composables/auth/useAuth.d.ts +5 -5
  97. package/dist/composables/error/types.d.ts +2 -1
  98. package/dist/composables/form/index.d.ts +12 -0
  99. package/dist/composables/form/renderFormField.d.ts +11 -0
  100. package/dist/composables/form/types.d.ts +104 -0
  101. package/dist/composables/form/useFormPage.d.ts +38 -0
  102. package/dist/composables/index.d.ts +2 -0
  103. package/dist/composables/page/index.d.ts +10 -0
  104. package/dist/composables/page/types.d.ts +61 -0
  105. package/dist/composables/page/useCrudPage.d.ts +14 -0
  106. package/dist/composables/request/createRequest.d.ts +2 -0
  107. package/dist/composables/request/requestBuilder.d.ts +2 -0
  108. package/dist/composables/search/index.d.ts +10 -0
  109. package/dist/composables/search/useAutoCompleteSearch.d.ts +50 -0
  110. package/dist/crud.css +2499 -663
  111. package/dist/crud.js +11512 -2910
  112. package/dist/index.d.ts +11 -0
  113. package/dist/setup.d.ts +2 -2
  114. package/index.ts +144 -0
  115. package/package.json +20 -19
  116. package/setup.ts +288 -0
  117. package/tsconfig.json +12 -0
  118. package/vite.config.build.ts +52 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @description KNavMenu — 路由联动导航菜单(crud 高阶组件)
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ *
9
+ * 基于 useMenu + KMenuItem + vue-router 封装。
10
+ * 自动同步路由 active 状态,点击叶子节点跳转路由。
11
+ */
12
+ import { defineComponent, watch, onMounted, PropType, h } from 'vue';
13
+ import { useRouter, useRoute } from 'vue-router';
14
+ import { useMenu } from '@kine-design/core/components/template/menu/useMenu';
15
+ import { MenuNodeData } from '@kine-design/core/components/template/menu/props';
16
+ import KMenuItem from 'kine-ui/components/menu/KMenuItem.tsx';
17
+ import './navMenu.css';
18
+
19
+ /**
20
+ * 导航菜单数据项(扩展 MenuData,叶子节点带 path/icon)
21
+ */
22
+ export interface NavMenuData {
23
+ key: string | number;
24
+ label: string;
25
+ path?: string;
26
+ icon?: string;
27
+ children?: NavMenuData[];
28
+ disabled?: boolean;
29
+ [k: string]: unknown;
30
+ }
31
+
32
+ /**
33
+ * 递归查找 path 匹配的节点 key
34
+ */
35
+ function findKeyByPath(data: NavMenuData[], path: string): string | number | null {
36
+ for (const item of data) {
37
+ if (item.path === path) return item.key;
38
+ if (item.children) {
39
+ const found = findKeyByPath(item.children, path);
40
+ if (found !== null) return found;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+
46
+ export default defineComponent({
47
+ name: 'KNavMenu',
48
+ props: {
49
+ data: { type: Array as PropType<NavMenuData[]>, default: () => [] },
50
+ },
51
+ setup(props, { slots }) {
52
+ const router = useRouter();
53
+ const route = useRoute();
54
+
55
+ const {
56
+ nodesRef,
57
+ nodeMap,
58
+ initNodes,
59
+ toggleExpand,
60
+ setActive,
61
+ getNodesByKeys,
62
+ config,
63
+ } = useMenu({ data: props.data });
64
+
65
+ // data 变化时重新初始化
66
+ watch(() => props.data, newData => {
67
+ initNodes(newData ?? []);
68
+ syncActiveByRoute(route.path);
69
+ }, { deep: true });
70
+
71
+ /**
72
+ * 根据路由路径同步 active 状态 + 展开父链
73
+ */
74
+ const syncActiveByRoute = (path: string) => {
75
+ const key = findKeyByPath(props.data, path);
76
+ if (key === null) return;
77
+
78
+ const node = nodeMap.get(key);
79
+ if (!node) return;
80
+
81
+ setActive(node);
82
+
83
+ // 展开所有祖先节点
84
+ let parent = node.parent;
85
+ while (parent) {
86
+ if (!parent.expand) {
87
+ parent.expand = true;
88
+ }
89
+ parent = parent.parent;
90
+ }
91
+ };
92
+
93
+ // 监听路由变化
94
+ watch(() => route.path, syncActiveByRoute);
95
+
96
+ // 首次挂载时同步
97
+ onMounted(() => {
98
+ syncActiveByRoute(route.path);
99
+ });
100
+
101
+ /** 点击节点 */
102
+ const handleItemClick = (node: MenuNodeData, e: MouseEvent) => {
103
+ if (node.disabled) return;
104
+
105
+ const navNode = node as MenuNodeData & { path?: string };
106
+
107
+ if (navNode.path) {
108
+ // 叶子节点:跳转路由
109
+ router.push(navNode.path);
110
+ setActive(node);
111
+ } else {
112
+ // 分类节点:切换展开
113
+ toggleExpand(node);
114
+ }
115
+
116
+ e.stopPropagation();
117
+ };
118
+
119
+ /**
120
+ * label 点击(KMenuItem 内部 label span 绑的是 handleExpand)
121
+ * 对叶子节点也需要处理路由跳转
122
+ */
123
+ const handleExpand = (node: MenuNodeData, e: MouseEvent) => {
124
+ if (node.disabled) return;
125
+
126
+ const navNode = node as MenuNodeData & { path?: string };
127
+ const hasChildren = node.children && node.children.length > 0;
128
+
129
+ if (hasChildren) {
130
+ // 分类节点:切换展开
131
+ toggleExpand(node);
132
+ } else if (navNode.path) {
133
+ // 叶子节点:跳转路由
134
+ router.push(navNode.path);
135
+ setActive(node);
136
+ }
137
+
138
+ e.stopPropagation();
139
+ };
140
+
141
+ /** checkbox(不启用,占位) */
142
+ const handleCheck = () => {};
143
+
144
+ /**
145
+ * 默认 slot:渲染 icon + label
146
+ * 外部可通过 slot 自定义,slot 参数 { data: label, node: MenuNodeData }
147
+ */
148
+ const defaultSlot = slots.default
149
+ ? slots.default
150
+ : (slotProps: { data: string; node?: MenuNodeData }) => {
151
+ const label = slotProps.data;
152
+ const icon = slotProps.node ? (slotProps.node as NavMenuData & MenuNodeData).icon : undefined;
153
+ return [
154
+ icon ? h('span', { class: 'k-nav-menu-icon' }, icon as string) : null,
155
+ h('span', { class: 'k-nav-menu-label' }, label),
156
+ ];
157
+ };
158
+
159
+ return () => (
160
+ <div class="k-nav-menu">
161
+ <KMenuItem
162
+ data={[...nodesRef.value]}
163
+ config={config}
164
+ checkbox={false}
165
+ root={true}
166
+ getNodesByKeys={getNodesByKeys}
167
+ handleExpand={handleExpand}
168
+ handleCheck={handleCheck}
169
+ handleItemClick={handleItemClick}
170
+ v-slots={{ default: defaultSlot }}
171
+ />
172
+ </div>
173
+ );
174
+ },
175
+ });
@@ -0,0 +1,2 @@
1
+ export { default as KNavMenu } from './KNavMenu';
2
+ export type { NavMenuData } from './KNavMenu';
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @description KNavMenu 导航菜单样式 — Phosphor 主题
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v1.0.0
6
+ */
7
+
8
+ /* ===== 容器:撑满侧边栏,去掉 KMenu 默认边框 ===== */
9
+ .k-nav-menu {
10
+ display: flex;
11
+ flex-direction: column;
12
+ width: 100%;
13
+ padding: var(--kine-spacing-2) 0;
14
+ }
15
+
16
+ /* 根级 li:无缩进 */
17
+ .k-nav-menu > .k-menu-item-root {
18
+ list-style: none;
19
+ }
20
+
21
+ /* ===== 内容行通用 ===== */
22
+ .k-nav-menu .k-menu-item-content {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: var(--kine-spacing-5);
26
+ padding: 11px 18px; /* 11px/18px 无精确 token,保留 */
27
+ color: var(--kine-color-text-secondary);
28
+ font-size: 15px; /* 15px 无精确 token,保留 */
29
+ cursor: pointer;
30
+ transition:
31
+ background var(--kine-motion-duration-fast) var(--kine-motion-easing-default),
32
+ color var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
33
+ border-left: 2px solid transparent;
34
+ }
35
+
36
+ .k-nav-menu .k-menu-item-content:hover {
37
+ background: var(--kine-color-bg-hover);
38
+ color: var(--kine-color-text-primary);
39
+ }
40
+
41
+ /* ===== 分类父节点:小号标题风格 ===== */
42
+ .k-nav-menu > .k-menu-item-root > .k-menu-item-content {
43
+ font-size: var(--kine-font-size-sm);
44
+ font-weight: var(--kine-font-weight-semibold);
45
+ color: var(--kine-color-text-muted);
46
+ text-transform: uppercase;
47
+ letter-spacing: 0.8px;
48
+ padding: var(--kine-spacing-7) var(--kine-spacing-8) var(--kine-spacing-3);
49
+ border-left: none;
50
+ }
51
+
52
+ .k-nav-menu > .k-menu-item-root > .k-menu-item-content:hover {
53
+ background: transparent;
54
+ color: var(--kine-color-text-secondary);
55
+ }
56
+
57
+ /* 分类节点不显示 active 样式 */
58
+ .k-nav-menu > .k-menu-item-root.k-menu-item-active > .k-menu-item-content {
59
+ color: var(--kine-color-text-muted);
60
+ background: transparent;
61
+ border-left: none;
62
+ padding-left: var(--kine-spacing-8);
63
+ }
64
+
65
+ /* ===== 子节点(组件条目)===== */
66
+ .k-nav-menu .k-menu-item-children {
67
+ padding-left: 0;
68
+ border-left: none;
69
+ margin-left: 0;
70
+ }
71
+
72
+ .k-nav-menu .k-menu-item-children .k-menu-item-content {
73
+ padding: 11px 18px 11px 26px; /* 11px/18px/26px 无精确 token,保留 */
74
+ font-size: 15px; /* 15px 无精确 token,保留 */
75
+ }
76
+
77
+ /* ===== active 叶子节点 ===== */
78
+ .k-nav-menu .k-menu-item-children .k-menu-item-active > .k-menu-item-content {
79
+ background: color-mix(in srgb, var(--kine-color-accent-default) 12%, transparent);
80
+ color: var(--kine-color-accent-default);
81
+ font-weight: var(--kine-font-weight-medium);
82
+ border-left: 2px solid var(--kine-color-accent-default);
83
+ padding-left: 22px; /* 24 - 2(border) */
84
+ }
85
+
86
+ .k-nav-menu .k-menu-item-children .k-menu-item-active > .k-menu-item-content:hover {
87
+ background: color-mix(in srgb, var(--kine-color-accent-default) 18%, transparent);
88
+ }
89
+
90
+ /* ===== 展开箭头 ===== */
91
+ .k-nav-menu .k-menu-item-arrow {
92
+ flex-shrink: 0;
93
+ width: 0;
94
+ height: 0;
95
+ border-top: 3px solid transparent;
96
+ border-bottom: 3px solid transparent;
97
+ border-left: 4px solid currentColor;
98
+ opacity: 0.5;
99
+ transition: transform var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
100
+ }
101
+
102
+ .k-nav-menu .k-menu-item-arrow-expand {
103
+ transform: rotate(90deg);
104
+ }
105
+
106
+ /* ===== 禁用状态 ===== */
107
+ .k-nav-menu .k-menu-item-disabled {
108
+ opacity: 0.4;
109
+ cursor: not-allowed;
110
+ pointer-events: none;
111
+ }
112
+
113
+ /* ===== 折叠状态:只显示图标 ===== */
114
+ .k-crud-sider--collapsed .k-nav-menu-label {
115
+ display: none;
116
+ }
117
+
118
+ .k-crud-sider--collapsed .k-nav-menu .k-menu-item-content {
119
+ justify-content: center;
120
+ padding: var(--kine-spacing-6) 0;
121
+ border-left: none;
122
+ gap: 0;
123
+ }
124
+
125
+ .k-crud-sider--collapsed .k-nav-menu-icon {
126
+ font-size: var(--kine-font-size-3xl);
127
+ width: 64px;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ }
132
+
133
+ /* 折叠时根节点也用统一的居中样式(覆盖分类标题的特殊样式) */
134
+ .k-crud-sider--collapsed .k-nav-menu > .k-menu-item-root > .k-menu-item-content {
135
+ font-size: inherit;
136
+ font-weight: normal;
137
+ text-transform: none;
138
+ letter-spacing: normal;
139
+ padding: var(--kine-spacing-6) 0;
140
+ justify-content: center;
141
+ color: var(--kine-color-text-secondary);
142
+ border-left: none;
143
+ }
144
+
145
+ /* 折叠时隐藏展开箭头 */
146
+ .k-crud-sider--collapsed .k-menu-item-arrow {
147
+ display: none;
148
+ }
149
+
150
+ /* 折叠时子菜单也对齐 */
151
+ .k-crud-sider--collapsed .k-nav-menu .k-menu-item-children .k-menu-item-content {
152
+ padding: var(--kine-spacing-6) 0;
153
+ justify-content: center;
154
+ }
155
+
156
+ /* 折叠时子菜单列表无缩进 */
157
+ .k-crud-sider--collapsed .k-nav-menu .k-menu-item-children {
158
+ padding-left: 0;
159
+ }
160
+
161
+ /* 折叠时 active 样式调整 */
162
+ .k-crud-sider--collapsed .k-nav-menu .k-menu-item-children .k-menu-item-active > .k-menu-item-content {
163
+ border-left: none;
164
+ padding-left: 0;
165
+ border-radius: var(--kine-radius-sm);
166
+ margin: 0 var(--kine-spacing-4);
167
+ }
168
+
169
+ /* 折叠时 label 区域也隐藏 */
170
+ .k-crud-sider--collapsed .k-menu-item-label {
171
+ display: none;
172
+ }
173
+
174
+ /* ===== label 溢出省略 ===== */
175
+ .k-nav-menu .k-menu-item-label {
176
+ flex: 1;
177
+ white-space: nowrap;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ display: flex;
181
+ align-items: center;
182
+ gap: var(--kine-spacing-4);
183
+ }
184
+
185
+ /* ===== icon ===== */
186
+ .k-nav-menu-icon {
187
+ flex-shrink: 0;
188
+ font-size: 18px; /* 18px 无对应 token,保留 */
189
+ width: 22px;
190
+ text-align: center;
191
+ }
192
+
193
+ .k-nav-menu-label {
194
+ white-space: nowrap;
195
+ overflow: hidden;
196
+ text-overflow: ellipsis;
197
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @description KPageHeader 页面头部组件
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { defineComponent } from 'vue';
10
+ import KButton from 'kine-ui/components/button/KButton.tsx';
11
+ import './pageHeader.css';
12
+
13
+ export default defineComponent({
14
+ name: 'KPageHeader',
15
+ props: {
16
+ /** 页面标题 */
17
+ title: {
18
+ type: String,
19
+ default: '',
20
+ },
21
+ /** 副标题 */
22
+ subtitle: {
23
+ type: String,
24
+ default: '',
25
+ },
26
+ /** 是否显示返回按钮 */
27
+ backable: {
28
+ type: Boolean,
29
+ default: false,
30
+ },
31
+ /** 返回回调 */
32
+ onBack: {
33
+ type: Function as unknown as () => () => void,
34
+ default: undefined,
35
+ },
36
+ },
37
+ setup(props, ctx) {
38
+ const handleBack = () => {
39
+ props.onBack?.();
40
+ };
41
+
42
+ return () => (
43
+ <div class="k-page-header">
44
+ {/* 面包屑插槽 */}
45
+ {ctx.slots.breadcrumb && (
46
+ <div class="k-page-header-breadcrumb">
47
+ {ctx.slots.breadcrumb()}
48
+ </div>
49
+ )}
50
+
51
+ {/* 主行:左侧标题 + 右侧操作 */}
52
+ <div class="k-page-header-main">
53
+ {/* 左侧:返回 + 标题区 */}
54
+ <div class="k-page-header-left">
55
+ {props.backable && (
56
+ <KButton text="←" onClick={handleBack} />
57
+ )}
58
+ <div class="k-page-header-heading">
59
+ {props.title && (
60
+ <h2 class="k-page-header-title">{props.title}</h2>
61
+ )}
62
+ {props.subtitle && (
63
+ <p class="k-page-header-subtitle">{props.subtitle}</p>
64
+ )}
65
+ </div>
66
+ </div>
67
+
68
+ {/* 右侧操作区插槽 */}
69
+ {ctx.slots.extra && (
70
+ <div class="k-page-header-extra">
71
+ {ctx.slots.extra()}
72
+ </div>
73
+ )}
74
+ </div>
75
+
76
+ {/* 标题下方内容插槽 */}
77
+ {ctx.slots.default && (
78
+ <div class="k-page-header-content">
79
+ {ctx.slots.default()}
80
+ </div>
81
+ )}
82
+ </div>
83
+ );
84
+ },
85
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @description pageHeader 模块导出
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export { default as KPageHeader } from './KPageHeader';
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @description KPageHeader 页面头部样式 — Phosphor 主题
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.2
6
+ */
7
+
8
+ /* ===== 容器 ===== */
9
+ .k-page-header {
10
+ display: flex;
11
+ flex-direction: column;
12
+ width: 100%;
13
+ box-sizing: border-box;
14
+ padding: var(--kine-spacing-8) 0 var(--kine-spacing-6);
15
+ border-bottom: 1px solid var(--kine-color-border-default);
16
+ background-color: transparent;
17
+ margin-bottom: var(--kine-spacing-8);
18
+ }
19
+
20
+ /* ===== 面包屑区域 ===== */
21
+ .k-page-header-breadcrumb {
22
+ margin-bottom: var(--kine-spacing-4);
23
+ font-size: var(--kine-font-size-lg);
24
+ color: var(--kine-color-text-muted);
25
+ font-family: var(--kine-font-family-system);
26
+ min-height: 20px;
27
+ }
28
+
29
+ /* ===== 主行:标题 + 操作 ===== */
30
+ .k-page-header-main {
31
+ display: flex;
32
+ flex-direction: row;
33
+ align-items: center;
34
+ justify-content: space-between;
35
+ gap: var(--kine-spacing-8);
36
+ min-height: 32px;
37
+ }
38
+
39
+ /* ===== 左侧:返回 + 标题区 ===== */
40
+ .k-page-header-left {
41
+ display: flex;
42
+ flex-direction: row;
43
+ align-items: center;
44
+ gap: var(--kine-spacing-4);
45
+ min-width: 0;
46
+ flex: 1;
47
+ }
48
+
49
+ /* 标题文字区 */
50
+ .k-page-header-heading {
51
+ display: flex;
52
+ flex-direction: column;
53
+ min-width: 0;
54
+ }
55
+
56
+ /* 主标题 */
57
+ .k-page-header-title {
58
+ font-size: 18px; /* 18px 无对应 token,保留 */
59
+ font-weight: var(--kine-font-weight-semibold);
60
+ color: var(--kine-color-text-primary);
61
+ margin: 0;
62
+ line-height: 1.4;
63
+ white-space: nowrap;
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ font-family: var(--kine-font-family-system);
67
+ }
68
+
69
+ /* 副标题 */
70
+ .k-page-header-subtitle {
71
+ font-size: var(--kine-font-size-lg);
72
+ color: var(--kine-color-text-muted);
73
+ margin: var(--kine-spacing-1) 0 0;
74
+ line-height: 1.4;
75
+ white-space: nowrap;
76
+ overflow: hidden;
77
+ text-overflow: ellipsis;
78
+ font-family: var(--kine-font-family-system);
79
+ }
80
+
81
+ /* ===== 右侧操作区 ===== */
82
+ .k-page-header-extra {
83
+ display: flex;
84
+ flex-direction: row;
85
+ align-items: center;
86
+ flex-shrink: 0;
87
+ gap: var(--kine-spacing-4);
88
+ }
89
+
90
+ /* ===== 标题下方内容插槽 ===== */
91
+ .k-page-header-content {
92
+ margin-top: var(--kine-spacing-6);
93
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @description KSearchTable 搜索表格组合组件
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { defineComponent, PropType } from 'vue';
10
+ import KTable from 'kine-ui/components/table/KTable.tsx';
11
+ import KPagination from 'kine-ui/components/pagination/KPagination.tsx';
12
+ import KButton from 'kine-ui/components/button/KButton.tsx';
13
+ import './searchTable.css';
14
+
15
+ export default defineComponent({
16
+ name: 'KSearchTable',
17
+
18
+ props: {
19
+ /** 加载状态 */
20
+ loading: {
21
+ type: Boolean,
22
+ default: false,
23
+ },
24
+ /** 表格数据 */
25
+ data: {
26
+ type: Array as PropType<Record<string, unknown>[]>,
27
+ default: () => [],
28
+ },
29
+ /** 总条数 */
30
+ total: {
31
+ type: Number,
32
+ default: 0,
33
+ },
34
+ /** 当前页(受控,配合 v-model:page) */
35
+ page: {
36
+ type: Number,
37
+ default: 1,
38
+ },
39
+ /** 每页条数(受控,配合 v-model:pageSize) */
40
+ pageSize: {
41
+ type: Number,
42
+ default: 20,
43
+ },
44
+ /** 是否显示搜索区 */
45
+ searchable: {
46
+ type: Boolean,
47
+ default: true,
48
+ },
49
+ /** 是否显示分页 */
50
+ pageable: {
51
+ type: Boolean,
52
+ default: true,
53
+ },
54
+ /** 透传给 KTable 的额外 props */
55
+ tableProps: {
56
+ type: Object as PropType<Record<string, unknown>>,
57
+ default: () => ({}),
58
+ },
59
+ },
60
+
61
+ emits: [
62
+ 'update:page',
63
+ 'update:pageSize',
64
+ /** 点击搜索按钮 */
65
+ 'search',
66
+ /** 点击重置按钮 */
67
+ 'reset',
68
+ ],
69
+
70
+ setup(props, ctx) {
71
+ /** 点击搜索按钮 */
72
+ const onSearch = () => {
73
+ ctx.emit('search');
74
+ };
75
+
76
+ /** 点击重置按钮 */
77
+ const onReset = () => {
78
+ ctx.emit('reset');
79
+ };
80
+
81
+ /** 分页 currentPage 变更 */
82
+ const onPageChange = (page: number) => {
83
+ ctx.emit('update:page', page);
84
+ };
85
+
86
+ return () => (
87
+ <div class={['k-search-table', props.loading ? 'k-search-table--loading' : '']}>
88
+
89
+ {/* 搜索区:searchable 为 true 时展示 */}
90
+ {props.searchable && (
91
+ <div class="k-search-table-search">
92
+ {/* 搜索表单插槽:业务方放 KForm + KFormItem */}
93
+ <div class="k-search-table-search-form">
94
+ {ctx.slots.search?.()}
95
+ </div>
96
+ {/* 搜索 / 重置按钮区 */}
97
+ <div class="k-search-table-search-actions">
98
+ <KButton type="primary" text="搜索" onClick={onSearch} />
99
+ <KButton text="重置" onClick={onReset} />
100
+ </div>
101
+ </div>
102
+ )}
103
+
104
+ {/* 工具栏:搜索区与表格之间,放操作按钮等 */}
105
+ {ctx.slots.toolbar && (
106
+ <div class="k-search-table-toolbar">
107
+ {ctx.slots.toolbar()}
108
+ </div>
109
+ )}
110
+
111
+ {/* 表格区:flex: 1 撑满剩余空间 */}
112
+ <div class="k-search-table-body">
113
+ <KTable
114
+ data={props.data}
115
+ {...props.tableProps}
116
+ >
117
+ {{
118
+ default: ctx.slots.default,
119
+ empty: ctx.slots.empty,
120
+ }}
121
+ </KTable>
122
+ </div>
123
+
124
+ {/* 分页区:固定底部 */}
125
+ {props.pageable && (
126
+ <div class="k-search-table-pagination">
127
+ <KPagination
128
+ modelValue={props.page}
129
+ total={props.total}
130
+ pageSize={props.pageSize}
131
+ onUpdate:modelValue={onPageChange}
132
+ />
133
+ </div>
134
+ )}
135
+ </div>
136
+ );
137
+ },
138
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @description KSearchTable barrel export
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ export { default as KSearchTable } from './KSearchTable';