@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,82 @@
1
+ /**
2
+ * @description KLayout 布局容器组件,提供 collapsed 状态及宽度配置
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { defineComponent, provide, ref, watch, type InjectionKey, type Ref } from 'vue';
10
+ import './layout.css';
11
+
12
+ export const LAYOUT_COLLAPSED_KEY: InjectionKey<Ref<boolean>> = Symbol('kCrudLayoutCollapsed');
13
+ export const LAYOUT_TOGGLE_KEY: InjectionKey<() => void> = Symbol('kCrudLayoutToggle');
14
+ export const LAYOUT_SIDER_WIDTH_KEY: InjectionKey<string> = Symbol('kCrudLayoutSiderWidth');
15
+ export const LAYOUT_COLLAPSED_WIDTH_KEY: InjectionKey<string> = Symbol('kCrudLayoutCollapsedWidth');
16
+ export const LAYOUT_DRAWER_OPEN_KEY: InjectionKey<Ref<boolean>> = Symbol('kCrudLayoutDrawerOpen');
17
+ export const LAYOUT_TOGGLE_DRAWER_KEY: InjectionKey<() => void> = Symbol('kCrudLayoutToggleDrawer');
18
+
19
+ export default defineComponent({
20
+ name: 'KLayout',
21
+ props: {
22
+ /** 受控模式:外部传入 collapsed 状态 */
23
+ collapsed: {
24
+ type: Boolean,
25
+ default: undefined,
26
+ },
27
+ /** 侧边栏展开宽度 */
28
+ siderWidth: {
29
+ type: String,
30
+ default: '240px',
31
+ },
32
+ /** 侧边栏折叠宽度 */
33
+ collapsedWidth: {
34
+ type: String,
35
+ default: '64px',
36
+ },
37
+ },
38
+ emits: ['update:collapsed'],
39
+ setup(props, ctx) {
40
+ // 内部折叠状态,受控时跟随 props
41
+ const innerCollapsed = ref(props.collapsed ?? false);
42
+
43
+ // 受控模式:同步外部变化
44
+ watch(
45
+ () => props.collapsed,
46
+ val => {
47
+ if (val !== undefined) {
48
+ innerCollapsed.value = val;
49
+ }
50
+ },
51
+ );
52
+
53
+ const toggleCollapsed = () => {
54
+ const next = !innerCollapsed.value;
55
+ innerCollapsed.value = next;
56
+ ctx.emit('update:collapsed', next);
57
+ };
58
+
59
+ // 移动端抽屉开关状态
60
+ const drawerOpen = ref(false);
61
+ const toggleDrawer = () => { drawerOpen.value = !drawerOpen.value; };
62
+
63
+ // 向子组件提供折叠状态、切换方法和宽度配置
64
+ provide(LAYOUT_COLLAPSED_KEY, innerCollapsed);
65
+ provide(LAYOUT_TOGGLE_KEY, toggleCollapsed);
66
+ provide(LAYOUT_SIDER_WIDTH_KEY, props.siderWidth);
67
+ provide(LAYOUT_COLLAPSED_WIDTH_KEY, props.collapsedWidth);
68
+ provide(LAYOUT_DRAWER_OPEN_KEY, drawerOpen);
69
+ provide(LAYOUT_TOGGLE_DRAWER_KEY, toggleDrawer);
70
+
71
+ return () => (
72
+ <div class="k-crud-layout">
73
+ {/* 移动端遮罩层,点击关闭抽屉 */}
74
+ <div
75
+ class={['k-crud-sider-overlay', drawerOpen.value && 'k-crud-sider-overlay--visible']}
76
+ onClick={toggleDrawer}
77
+ />
78
+ {ctx.slots.default?.()}
79
+ </div>
80
+ );
81
+ },
82
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @description KSider 侧边栏组件,支持折叠动画和自定义 trigger
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { computed, defineComponent, inject, ref } from 'vue';
10
+ import {
11
+ LAYOUT_COLLAPSED_KEY,
12
+ LAYOUT_TOGGLE_KEY,
13
+ LAYOUT_SIDER_WIDTH_KEY,
14
+ LAYOUT_COLLAPSED_WIDTH_KEY,
15
+ LAYOUT_DRAWER_OPEN_KEY,
16
+ } from './KLayout';
17
+
18
+ export default defineComponent({
19
+ name: 'KSider',
20
+ setup(_, ctx) {
21
+ // 从 KLayout 注入折叠状态、切换方法和宽度配置
22
+ const collapsed = inject(LAYOUT_COLLAPSED_KEY);
23
+ const toggle = inject(LAYOUT_TOGGLE_KEY);
24
+ const drawerOpen = inject(LAYOUT_DRAWER_OPEN_KEY, ref(false));
25
+ const siderWidth = inject(LAYOUT_SIDER_WIDTH_KEY, '240px');
26
+ const collapsedWidth = inject(LAYOUT_COLLAPSED_WIDTH_KEY, '64px');
27
+
28
+ const currentWidth = computed(() => {
29
+ if (!collapsed) return siderWidth;
30
+ return collapsed.value ? collapsedWidth : siderWidth;
31
+ });
32
+
33
+ const isCollapsed = computed(() => collapsed?.value ?? false);
34
+
35
+ const handleTriggerClick = () => {
36
+ toggle?.();
37
+ };
38
+
39
+ // 默认折叠按钮图标:展开时显示 ←,折叠时显示 →
40
+ const defaultTrigger = () => (
41
+ <button
42
+ class="k-crud-sider-trigger-btn"
43
+ onClick={handleTriggerClick}
44
+ title={isCollapsed.value ? '展开侧边栏' : '折叠侧边栏'}
45
+ >
46
+ <span class={['k-crud-sider-trigger-icon', isCollapsed.value && 'k-crud-sider-trigger-icon--collapsed']}>
47
+ {'‹'}
48
+ </span>
49
+ </button>
50
+ );
51
+
52
+ return () => (
53
+ <aside
54
+ class={[
55
+ 'k-crud-sider',
56
+ isCollapsed.value && 'k-crud-sider--collapsed',
57
+ drawerOpen.value && 'k-crud-sider--drawer-open',
58
+ ]}
59
+ style={{ width: currentWidth.value }}
60
+ >
61
+ {/* Logo 区域 */}
62
+ {ctx.slots.logo && (
63
+ <div class="k-crud-sider-logo">
64
+ {ctx.slots.logo()}
65
+ </div>
66
+ )}
67
+
68
+ {/* 主内容区(菜单等) */}
69
+ <div class="k-crud-sider-body">
70
+ {ctx.slots.default?.()}
71
+ </div>
72
+
73
+ {/* 折叠触发器:优先使用自定义 trigger slot */}
74
+ <div class="k-crud-sider-trigger">
75
+ {ctx.slots.trigger ? ctx.slots.trigger({ collapsed: isCollapsed.value, toggle }) : defaultTrigger()}
76
+ </div>
77
+ </aside>
78
+ );
79
+ },
80
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @description layout 布局组件统一导出
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export { default as KLayout } from './KLayout';
10
+ export { default as KSider } from './KSider';
11
+ export { default as KHeader } from './KHeader';
12
+ export { default as KContent } from './KContent';
13
+ export {
14
+ LAYOUT_COLLAPSED_KEY,
15
+ LAYOUT_TOGGLE_KEY,
16
+ LAYOUT_SIDER_WIDTH_KEY,
17
+ LAYOUT_COLLAPSED_WIDTH_KEY,
18
+ } from './KLayout';
@@ -0,0 +1,262 @@
1
+ /**
2
+ * @description KLayout 布局组件样式 — Phosphor 主题
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.2
6
+ */
7
+
8
+ /* ===== KLayout ===== */
9
+ .k-crud-layout {
10
+ display: flex;
11
+ flex-direction: row;
12
+ width: 100%;
13
+ height: 100%;
14
+ min-height: 0;
15
+ overflow: hidden;
16
+ }
17
+
18
+ /* ===== KSider ===== */
19
+ .k-crud-sider {
20
+ display: flex;
21
+ flex-direction: column;
22
+ flex-shrink: 0;
23
+ height: 100%;
24
+ overflow: hidden;
25
+ background-color: var(--kine-color-bg-secondary);
26
+ color: var(--kine-color-text-primary);
27
+ border-right: 1px solid var(--kine-color-border-default);
28
+ /* 折叠/展开宽度过渡 */
29
+ transition: width 0.3s var(--kine-motion-easing-default);
30
+ will-change: width;
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ /* Logo 区域 */
35
+ .k-crud-sider-logo {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ flex-shrink: 0;
40
+ height: 56px;
41
+ min-height: 56px;
42
+ overflow: hidden;
43
+ border-bottom: 1px solid var(--kine-color-border-default);
44
+ box-sizing: border-box;
45
+ }
46
+
47
+ /* 菜单主体,占满剩余高度 */
48
+ .k-crud-sider-body {
49
+ display: flex;
50
+ flex-direction: column;
51
+ flex: 1;
52
+ min-height: 0;
53
+ overflow-y: auto;
54
+ overflow-x: hidden;
55
+ }
56
+
57
+ /* 折叠触发器(底部) */
58
+ .k-crud-sider-trigger {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ flex-shrink: 0;
63
+ height: 48px;
64
+ border-top: 1px solid var(--kine-color-border-default);
65
+ }
66
+
67
+ /* 默认折叠按钮 */
68
+ .k-crud-sider-trigger-btn {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ width: 100%;
73
+ height: 100%;
74
+ background: none;
75
+ border: none;
76
+ cursor: pointer;
77
+ color: var(--kine-color-text-secondary);
78
+ background-color: var(--kine-color-bg-tertiary);
79
+ transition:
80
+ background-color var(--kine-motion-duration-fast) var(--kine-motion-easing-default),
81
+ color var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
82
+ padding: 0;
83
+ }
84
+
85
+ .k-crud-sider-trigger-btn:hover {
86
+ background-color: var(--kine-color-bg-hover);
87
+ color: var(--kine-color-text-primary);
88
+ }
89
+
90
+ /* 折叠箭头图标:展开时朝左,折叠时朝右 */
91
+ .k-crud-sider-trigger-icon {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ font-size: var(--kine-font-size-3xl);
95
+ line-height: 1;
96
+ font-style: normal;
97
+ transition: transform 0.3s var(--kine-motion-easing-default);
98
+ /* 默认(展开态):‹ 指向左侧,表示可以收起 */
99
+ transform: rotate(0deg);
100
+ }
101
+
102
+ .k-crud-sider-trigger-icon--collapsed {
103
+ /* 折叠态:旋转 180deg,‹ 变成朝右,表示可以展开 */
104
+ transform: rotate(180deg);
105
+ }
106
+
107
+ /* ===== KHeader ===== */
108
+ .k-crud-header {
109
+ display: flex;
110
+ flex-direction: row;
111
+ align-items: center;
112
+ flex-shrink: 0;
113
+ height: 56px;
114
+ min-height: 56px;
115
+ padding: 0 var(--kine-spacing-8);
116
+ box-sizing: border-box;
117
+ background-color: var(--kine-color-bg-primary);
118
+ border-bottom: 1px solid var(--kine-color-border-default);
119
+ }
120
+
121
+ .k-crud-header-main {
122
+ display: flex;
123
+ flex-direction: row;
124
+ align-items: center;
125
+ flex: 1;
126
+ min-width: 0;
127
+ }
128
+
129
+ .k-crud-header-extra {
130
+ display: flex;
131
+ flex-direction: row;
132
+ align-items: center;
133
+ flex-shrink: 0;
134
+ gap: var(--kine-spacing-4);
135
+ }
136
+
137
+ /* ===== KContent ===== */
138
+ .k-crud-content {
139
+ display: flex;
140
+ flex-direction: column;
141
+ flex: 1;
142
+ min-height: 0;
143
+ min-width: 0;
144
+ overflow-y: auto;
145
+ overflow-x: hidden;
146
+ background-color: var(--kine-color-bg-primary);
147
+ padding: var(--kine-spacing-8);
148
+ box-sizing: border-box;
149
+ }
150
+
151
+ /* ===== 遮罩层(桌面端隐藏) ===== */
152
+ .k-crud-sider-overlay {
153
+ display: none;
154
+ position: fixed;
155
+ inset: 0;
156
+ z-index: 999;
157
+ background: rgba(0, 0, 0, 0);
158
+ pointer-events: none;
159
+ transition: background 0.3s var(--kine-motion-easing-default);
160
+ }
161
+
162
+ .k-crud-sider-overlay--visible {
163
+ background: rgba(0, 0, 0, 0.45);
164
+ pointer-events: auto;
165
+ }
166
+
167
+ /* ===== 汉堡按钮 ===== */
168
+ .k-crud-header-hamburger {
169
+ display: none; /* 桌面端隐藏 */
170
+ flex-direction: column;
171
+ justify-content: center;
172
+ align-items: center;
173
+ width: 36px;
174
+ height: 36px;
175
+ flex-shrink: 0;
176
+ margin-right: var(--kine-spacing-4);
177
+ background: none;
178
+ border: none;
179
+ cursor: pointer;
180
+ padding: var(--kine-spacing-2);
181
+ border-radius: var(--kine-radius-xs);
182
+ color: var(--kine-color-text-secondary);
183
+ transition: background var(--kine-motion-duration-fast) var(--kine-motion-easing-default);
184
+ }
185
+
186
+ .k-crud-header-hamburger:hover {
187
+ background: var(--kine-color-bg-hover);
188
+ color: var(--kine-color-text-primary);
189
+ }
190
+
191
+ /* 三条横线图标 */
192
+ .k-crud-header-hamburger-icon,
193
+ .k-crud-header-hamburger-icon::before,
194
+ .k-crud-header-hamburger-icon::after {
195
+ display: block;
196
+ width: 18px;
197
+ height: 2px;
198
+ background: currentColor;
199
+ border-radius: 1px;
200
+ }
201
+
202
+ .k-crud-header-hamburger-icon {
203
+ position: relative;
204
+ }
205
+
206
+ .k-crud-header-hamburger-icon::before,
207
+ .k-crud-header-hamburger-icon::after {
208
+ content: '';
209
+ position: absolute;
210
+ left: 0;
211
+ }
212
+
213
+ .k-crud-header-hamburger-icon::before { top: -5px; }
214
+ .k-crud-header-hamburger-icon::after { top: 5px; }
215
+
216
+ /* ================================================================
217
+ 移动端(<768px)
218
+ ================================================================ */
219
+
220
+ @media (max-width: 767px) {
221
+ /* 汉堡按钮显示 */
222
+ .k-crud-header-hamburger {
223
+ display: flex;
224
+ }
225
+
226
+ /* 侧边栏变为 fixed 抽屉,默认隐藏 */
227
+ .k-crud-sider {
228
+ position: fixed;
229
+ top: 0;
230
+ left: 0;
231
+ height: 100%;
232
+ width: 240px !important; /* 覆盖 inline style */
233
+ z-index: 1000;
234
+ transform: translateX(-100%);
235
+ transition:
236
+ transform 0.3s var(--kine-motion-easing-default),
237
+ box-shadow 0.3s var(--kine-motion-easing-default);
238
+ box-shadow: none;
239
+ border-right: none;
240
+ }
241
+
242
+ /* 抽屉打开 */
243
+ .k-crud-sider--drawer-open {
244
+ transform: translateX(0);
245
+ box-shadow: 4px 0 24px rgba(0, 0, 0, 0.18);
246
+ }
247
+
248
+ /* 遮罩层 */
249
+ .k-crud-sider-overlay {
250
+ display: block;
251
+ }
252
+
253
+ /* 底部折叠 trigger 在移动端隐藏 */
254
+ .k-crud-sider-trigger {
255
+ display: none;
256
+ }
257
+
258
+ /* 内容区 padding 缩减 */
259
+ .k-crud-content {
260
+ padding: var(--kine-spacing-4);
261
+ }
262
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @description KLoginPage 登录页组件
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { defineComponent, ref } from 'vue';
10
+ import KInput from 'kine-ui/components/input/KInput.tsx';
11
+ import KButton from 'kine-ui/components/button/KButton.tsx';
12
+ import './login.css';
13
+
14
+ export type LoginForm = {
15
+ username: string;
16
+ password: string;
17
+ remember: boolean;
18
+ };
19
+
20
+ export default defineComponent({
21
+ name: 'KLoginPage',
22
+ props: {
23
+ /** 系统名称 */
24
+ title: {
25
+ type: String,
26
+ default: '系统登录',
27
+ },
28
+ /** 登录中状态 */
29
+ loading: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ /** 登录提交回调 */
34
+ onSubmit: {
35
+ type: Function as unknown as () => (form: LoginForm) => Promise<void> | void,
36
+ default: undefined,
37
+ },
38
+ },
39
+ setup(props, ctx) {
40
+ const username = ref('');
41
+ const password = ref('');
42
+ const remember = ref(false);
43
+
44
+ const handleSubmit = async (e: Event) => {
45
+ e.preventDefault();
46
+ if (props.loading) return;
47
+ await props.onSubmit?.({
48
+ username: username.value,
49
+ password: password.value,
50
+ remember: remember.value,
51
+ });
52
+ };
53
+
54
+ return () => (
55
+ <div class="k-login-page">
56
+ <div class="k-login-card">
57
+ {/* Logo 插槽 */}
58
+ {ctx.slots.logo && (
59
+ <div class="k-login-logo">
60
+ {ctx.slots.logo()}
61
+ </div>
62
+ )}
63
+
64
+ {/* 标题 */}
65
+ <h2 class="k-login-title">{props.title}</h2>
66
+
67
+ {/* 登录表单 */}
68
+ <form class="k-login-form" onSubmit={handleSubmit}>
69
+ {/* 用户名 */}
70
+ <div class="k-login-form-item">
71
+ <label>用户名</label>
72
+ <KInput
73
+ modelValue={username.value}
74
+ placeholder="请输入用户名"
75
+ onUpdate:modelValue={(v: string) => { username.value = v; }}
76
+ />
77
+ </div>
78
+
79
+ {/* 密码 */}
80
+ <div class="k-login-form-item">
81
+ <label>密码</label>
82
+ <KInput
83
+ type="password"
84
+ modelValue={password.value}
85
+ placeholder="请输入密码"
86
+ onUpdate:modelValue={(v: string) => { password.value = v; }}
87
+ />
88
+ </div>
89
+
90
+ {/* 记住我 */}
91
+ <label class="k-login-remember">
92
+ <input
93
+ type="checkbox"
94
+ checked={remember.value}
95
+ onChange={(e: Event) => {
96
+ remember.value = (e.target as HTMLInputElement).checked;
97
+ }}
98
+ />
99
+ 记住我
100
+ </label>
101
+
102
+ {/* 提交按钮 */}
103
+ <KButton
104
+ type="primary"
105
+ text={props.loading ? '登录中...' : '登 录'}
106
+ disabled={props.loading}
107
+ loading={props.loading}
108
+ onClick={handleSubmit}
109
+ />
110
+ </form>
111
+
112
+ {/* 额外登录方式插槽 */}
113
+ {ctx.slots.extra && (
114
+ <div class="k-login-extra">
115
+ {ctx.slots.extra()}
116
+ </div>
117
+ )}
118
+ </div>
119
+
120
+ {/* 底部版权插槽 */}
121
+ {ctx.slots.footer && (
122
+ <div class="k-login-footer">
123
+ {ctx.slots.footer()}
124
+ </div>
125
+ )}
126
+ </div>
127
+ );
128
+ },
129
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @description login 模块导出
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.1
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export { default as KLoginPage } from './KLoginPage';
10
+ export type { LoginForm } from './KLoginPage';
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @description KLoginPage 登录页样式 — Phosphor 主题
3
+ * @author 阿怪
4
+ * @date 2026/2/26
5
+ * @version v0.0.2
6
+ */
7
+
8
+ /* ===== 全屏容器 ===== */
9
+ .k-login-page {
10
+ display: flex;
11
+ flex-direction: column;
12
+ align-items: center;
13
+ justify-content: center;
14
+ min-height: 100vh;
15
+ width: 100%;
16
+ box-sizing: border-box;
17
+ padding: var(--kine-spacing-12);
18
+ background-color: var(--kine-color-bg-secondary);
19
+ }
20
+
21
+ /* ===== 登录卡片 ===== */
22
+ .k-login-card {
23
+ width: 100%;
24
+ max-width: 400px;
25
+ background-color: var(--kine-color-bg-primary);
26
+ border-radius: var(--kine-radius-md);
27
+ border: 1px solid var(--kine-color-border-default);
28
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
29
+ padding: var(--kine-spacing-20) 36px var(--kine-spacing-16);
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ @media (max-width: 480px) {
34
+ .k-login-card {
35
+ padding: var(--kine-spacing-16) var(--kine-spacing-10) var(--kine-spacing-12);
36
+ box-shadow: none;
37
+ border-radius: 0;
38
+ max-width: 100%;
39
+ }
40
+ }
41
+
42
+ /* ===== Logo 区域 ===== */
43
+ .k-login-logo {
44
+ display: flex;
45
+ justify-content: center;
46
+ margin-bottom: var(--kine-spacing-8);
47
+ }
48
+
49
+ /* ===== 标题 ===== */
50
+ .k-login-title {
51
+ text-align: center;
52
+ font-size: 22px; /* 22px 无精确 token,保留 */
53
+ font-weight: var(--kine-font-weight-semibold);
54
+ color: var(--kine-color-text-primary);
55
+ margin: 0 0 var(--kine-spacing-14);
56
+ line-height: 1.4;
57
+ font-family: var(--kine-font-family-system);
58
+ }
59
+
60
+ /* ===== 表单 ===== */
61
+ .k-login-form {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: var(--kine-spacing-8);
65
+ }
66
+
67
+ /* 表单项 */
68
+ .k-login-form-item {
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: var(--kine-spacing-3);
72
+ }
73
+
74
+ .k-login-form-item label {
75
+ font-size: var(--kine-font-size-lg);
76
+ color: var(--kine-color-text-secondary);
77
+ font-weight: var(--kine-font-weight-medium);
78
+ font-family: var(--kine-font-family-system);
79
+ }
80
+
81
+ /* KInput 宽度撑满表单项 */
82
+ .k-login-form-item .k-input {
83
+ width: 100%;
84
+ }
85
+
86
+ /* 记住我行 */
87
+ .k-login-remember {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: var(--kine-spacing-4);
91
+ font-size: var(--kine-font-size-lg);
92
+ color: var(--kine-color-text-secondary);
93
+ cursor: pointer;
94
+ user-select: none;
95
+ font-family: var(--kine-font-family-system);
96
+ }
97
+
98
+ .k-login-remember input[type='checkbox'] {
99
+ width: 15px;
100
+ height: 15px;
101
+ cursor: pointer;
102
+ accent-color: var(--kine-color-accent-default);
103
+ }
104
+
105
+ /* ===== 额外登录方式区域 ===== */
106
+ .k-login-extra {
107
+ margin-top: var(--kine-spacing-10);
108
+ text-align: center;
109
+ }
110
+
111
+ /* ===== 底部版权区域 ===== */
112
+ .k-login-footer {
113
+ margin-top: var(--kine-spacing-16);
114
+ text-align: center;
115
+ font-size: var(--kine-font-size-md);
116
+ color: var(--kine-color-text-muted);
117
+ font-family: var(--kine-font-family-system);
118
+ }