@usethink/cf-admin-fe 0.1.1 → 0.2.8

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -53
  3. package/dist/composables/createAdminFlagSession.d.ts +42 -0
  4. package/dist/composables/createAdminRequest.d.ts +95 -0
  5. package/dist/composables/createAdminTokenSession.d.ts +49 -0
  6. package/dist/composables/index.d.ts +7 -1
  7. package/dist/composables/useAdminListLoader.d.ts +45 -0
  8. package/dist/composables/useAdminSidebar.d.ts +17 -0
  9. package/dist/composables/useClipboard.d.ts +24 -2
  10. package/dist/composables/useConfirmDialog.d.ts +10 -4
  11. package/dist/composables/useNow.d.ts +3 -0
  12. package/dist/composables/useTablePagination.d.ts +6 -0
  13. package/dist/composables/useTableSelection.d.ts +2 -2
  14. package/dist/composables/useToast.d.ts +1 -1
  15. package/dist/types/index.d.ts +1 -0
  16. package/dist/types/system-config.d.ts +34 -0
  17. package/dist/utils/admin-navigation.d.ts +66 -0
  18. package/dist/utils/admin-query.d.ts +29 -0
  19. package/dist/utils/batch-limit.d.ts +38 -0
  20. package/dist/utils/batch-result.d.ts +41 -0
  21. package/dist/utils/body-scroll-lock.d.ts +13 -0
  22. package/dist/utils/csv-export.d.ts +8 -0
  23. package/dist/utils/currency-cents.d.ts +16 -0
  24. package/dist/utils/datetime.d.ts +20 -0
  25. package/dist/utils/error-message.d.ts +7 -0
  26. package/dist/utils/format-metadata.d.ts +5 -0
  27. package/dist/utils/i18n.d.ts +14 -0
  28. package/dist/utils/index.d.ts +13 -0
  29. package/dist/utils/otp-input.d.ts +11 -0
  30. package/dist/utils/system-config-engine.d.ts +65 -0
  31. package/docs/001_cf-admin-fe/346/217/220/345/217/226/345/267/256/350/267/235/345/210/206/346/236/220/346/212/245/345/221/212_2026-08-05.md +225 -0
  32. package/docs/002_cf-admin-fe/344/275/277/347/224/250/350/257/264/346/230/216/344/271/246/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.md +805 -0
  33. package/docs/003_cf-admin-fe/346/236/201/350/207/264/345/214/226/346/226/271/346/241/210/344/270/216/345/217/226/350/210/215_2026-08-05.md +332 -0
  34. package/package.json +35 -6
  35. package/recipes/AdminLayout.vue +73 -0
  36. package/recipes/README.md +131 -0
  37. package/recipes/i18n-merge.snippet.ts +29 -0
  38. package/recipes/list-page.snippet.ts +141 -0
  39. package/recipes/main-styles.snippet.ts +13 -0
  40. package/recipes/reexports/components.ts +19 -0
  41. package/recipes/reexports/composables.ts +29 -0
  42. package/recipes/reexports/utils.ts +31 -0
  43. package/recipes/router-guard.snippet.ts +32 -0
  44. package/recipes/useAdminAuth.cookie.ts +29 -0
  45. package/recipes/useAdminAuth.shop.ts +29 -0
  46. package/recipes/useAdminRequest.cookie.ts +36 -0
  47. package/recipes/useAdminRequest.ts +37 -0
  48. package/src/components/AdminLoginShell.vue +31 -0
  49. package/src/components/AdminMetadataDetail.vue +121 -0
  50. package/src/components/AdminShell.vue +384 -0
  51. package/src/components/ConfigField.vue +387 -0
  52. package/src/components/ConfirmDialog.vue +67 -3
  53. package/src/components/index.ts +6 -0
  54. package/src/composables/createAdminFlagSession.ts +129 -0
  55. package/src/composables/createAdminRequest.ts +322 -0
  56. package/src/composables/createAdminTokenSession.ts +149 -0
  57. package/src/composables/index.ts +51 -0
  58. package/src/composables/useAdminBatchOperation.ts +4 -0
  59. package/src/composables/useAdminListLoader.ts +124 -0
  60. package/src/composables/useAdminSidebar.ts +68 -0
  61. package/src/composables/useClipboard.ts +41 -2
  62. package/src/composables/useConfirmDialog.ts +26 -4
  63. package/src/composables/useNow.ts +38 -0
  64. package/src/composables/useTablePagination.ts +6 -0
  65. package/src/composables/useTableSelection.ts +2 -2
  66. package/src/composables/useToast.ts +4 -1
  67. package/src/env.d.ts +2 -2
  68. package/src/i18n/index.ts +8 -0
  69. package/src/i18n/kit-messages.ts +297 -0
  70. package/src/index.ts +147 -4
  71. package/src/styles/admin-primitives.css +28 -1
  72. package/src/styles/index.css +1 -0
  73. package/src/styles/login-primitives.css +149 -0
  74. package/src/types/index.ts +7 -0
  75. package/src/types/system-config.ts +38 -0
  76. package/src/utils/admin-navigation.ts +138 -0
  77. package/src/utils/admin-query.ts +51 -0
  78. package/src/utils/batch-limit.ts +66 -0
  79. package/src/utils/batch-result.ts +81 -0
  80. package/src/utils/body-scroll-lock.ts +63 -0
  81. package/src/utils/csv-export.ts +25 -0
  82. package/src/utils/currency-cents.ts +30 -0
  83. package/src/utils/datetime.ts +54 -0
  84. package/src/utils/error-message.ts +32 -0
  85. package/src/utils/format-metadata.ts +11 -0
  86. package/src/utils/i18n.ts +63 -0
  87. package/src/utils/index.ts +87 -0
  88. package/src/utils/otp-input.ts +18 -0
  89. package/src/utils/system-config-engine.ts +212 -0
  90. package/dist/components/index.d.ts +0 -4
  91. package/dist/index.d.ts +0 -17
@@ -0,0 +1,384 @@
1
+ <template>
2
+ <div class="admin-layout">
3
+ <div
4
+ v-if="isMobile && sidebarOpen"
5
+ class="sidebar-overlay"
6
+ @click="closeSidebar"
7
+ />
8
+
9
+ <aside class="admin-sidebar" :class="{ 'sidebar-open': sidebarOpen }">
10
+ <div class="sidebar-brand">
11
+ <slot name="brand">{{ brand }}</slot>
12
+ </div>
13
+
14
+ <nav class="sidebar-nav" @click="isMobile && closeSidebar()">
15
+ <slot name="nav" :items="menuItems" :current-name="currentName" :on-nav="onNavClick">
16
+ <a
17
+ v-for="item in menuItems"
18
+ :key="item.to"
19
+ class="nav-item"
20
+ :class="{ active: currentName === item.name, ...(item.className ? { [item.className]: true } : {}) }"
21
+ :href="item.to"
22
+ :title="item.label"
23
+ @click.prevent="onNavClick(item.to)"
24
+ >
25
+ <span
26
+ v-if="item.iconHtml"
27
+ class="nav-icon"
28
+ v-html="item.iconHtml"
29
+ />
30
+ <span v-else class="nav-icon">{{ item.icon || '' }}</span>
31
+ <span class="nav-label">{{ item.label }}</span>
32
+ </a>
33
+ </slot>
34
+ <slot name="nav-extra" />
35
+ </nav>
36
+
37
+ <!-- 仅当产品显式提供 #sidebar-footer 时渲染(默认用户信息在顶栏右上,省侧栏高度) -->
38
+ <div v-if="$slots['sidebar-footer']" class="sidebar-footer">
39
+ <slot name="sidebar-footer" />
40
+ </div>
41
+ </aside>
42
+
43
+ <div class="admin-main" :class="{ 'sidebar-open': sidebarOpen }">
44
+ <header class="admin-header">
45
+ <div class="admin-header-left">
46
+ <button
47
+ v-if="isMobile"
48
+ class="btn-hamburger"
49
+ type="button"
50
+ :aria-label="resolvedToggleLabel"
51
+ @click="toggleSidebar"
52
+ >
53
+ <span class="hamburger-line" />
54
+ <span class="hamburger-line" />
55
+ <span class="hamburger-line" />
56
+ </button>
57
+ <h1 class="admin-header-title">
58
+ <slot name="title">{{ resolvedTitle }}</slot>
59
+ </h1>
60
+ </div>
61
+ <div class="admin-header-actions">
62
+ <slot name="header-actions" />
63
+ <div v-if="showUser" class="header-user" :title="userName">
64
+ <span class="header-user-avatar" aria-hidden="true">{{ userAvatar }}</span>
65
+ <span class="header-user-name">{{ userName }}</span>
66
+ </div>
67
+ <button
68
+ v-if="showLogout"
69
+ class="btn btn-ghost header-logout"
70
+ type="button"
71
+ @click="$emit('logout')"
72
+ >{{ resolvedLogoutLabel }}</button>
73
+ </div>
74
+ </header>
75
+
76
+ <main class="admin-content">
77
+ <slot />
78
+ </main>
79
+ </div>
80
+
81
+ <slot name="banner" />
82
+ </div>
83
+ </template>
84
+
85
+ <script setup lang="ts">
86
+ import { computed } from 'vue'
87
+ import { useAdminSidebar } from '../composables/useAdminSidebar'
88
+ import { tKit } from '../utils/i18n'
89
+
90
+ export type AdminShellMenuItem = {
91
+ name: string
92
+ to: string
93
+ label: string
94
+ /** 纯文本 / emoji 图标 */
95
+ icon?: string
96
+ /** HTML 实体图标(shop 使用 `&#x1F4CA;`)——尽可能优先使用文本图标 */
97
+ iconHtml?: string
98
+ className?: string
99
+ }
100
+
101
+ const props = withDefaults(
102
+ defineProps<{
103
+ brand?: string
104
+ menuItems?: AdminShellMenuItem[]
105
+ /** 当前激活的路由名;缺省时标题回退为 brand */
106
+ currentName?: string | null
107
+ pageTitle?: string
108
+ userName?: string
109
+ userAvatar?: string
110
+ /** 顶栏右上展示头像+名称(默认 true;侧栏底默认不再占位) */
111
+ showUser?: boolean
112
+ showLogout?: boolean
113
+ logoutLabel?: string
114
+ toggleSidebarLabel?: string
115
+ }>(),
116
+ {
117
+ brand: 'Admin',
118
+ menuItems: () => [],
119
+ currentName: null,
120
+ pageTitle: '',
121
+ userName: 'Admin',
122
+ userAvatar: 'A',
123
+ showUser: true,
124
+ showLogout: true,
125
+ logoutLabel: '',
126
+ toggleSidebarLabel: '',
127
+ },
128
+ )
129
+
130
+ const emit = defineEmits<{
131
+ logout: []
132
+ navigate: [to: string]
133
+ }>()
134
+
135
+ const { isMobile, sidebarOpen, toggleSidebar, closeSidebar } = useAdminSidebar()
136
+
137
+ const resolvedLogoutLabel = computed(
138
+ () => props.logoutLabel || tKit('adminLayout.logout', '退出'),
139
+ )
140
+ const resolvedToggleLabel = computed(
141
+ () => props.toggleSidebarLabel || tKit('adminLayout.toggleSidebar', '菜单'),
142
+ )
143
+
144
+ const resolvedTitle = computed(() => {
145
+ if (props.pageTitle) return props.pageTitle
146
+ const item = props.menuItems.find((m) => m.name === props.currentName)
147
+ return item?.label || props.brand
148
+ })
149
+
150
+ function onNavClick(to: string) {
151
+ emit('navigate', to)
152
+ }
153
+ </script>
154
+
155
+ <style scoped>
156
+ .admin-layout {
157
+ display: flex;
158
+ height: 100vh;
159
+ background: var(--tg-bg, #0a0e17);
160
+ overflow: hidden;
161
+ color-scheme: dark;
162
+ }
163
+
164
+ .sidebar-overlay {
165
+ position: fixed;
166
+ inset: 0;
167
+ background: rgba(0, 0, 0, 0.45);
168
+ z-index: 90;
169
+ }
170
+
171
+ .admin-sidebar {
172
+ width: var(--admin-sidebar-w, 220px);
173
+ background: #1a1a2e;
174
+ color: #fff;
175
+ display: flex;
176
+ flex-direction: column;
177
+ position: fixed;
178
+ inset: 0 auto 0 0;
179
+ z-index: 100;
180
+ overflow-y: auto;
181
+ transform: translateX(0);
182
+ transition: transform 0.2s ease;
183
+ }
184
+
185
+ .sidebar-brand {
186
+ padding: 16px 14px;
187
+ font-size: 15px;
188
+ font-weight: 700;
189
+ letter-spacing: 0.3px;
190
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
191
+ }
192
+
193
+ .sidebar-nav {
194
+ flex: 1;
195
+ padding: 8px 0;
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 1px;
199
+ }
200
+
201
+ .nav-item {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 8px;
205
+ padding: 9px 14px;
206
+ color: rgba(255, 255, 255, 0.72);
207
+ text-decoration: none;
208
+ font-size: 13px;
209
+ transition: background 0.15s ease, color 0.15s ease;
210
+ border-left: 3px solid transparent;
211
+ }
212
+
213
+ .nav-item:hover {
214
+ background: rgba(255, 255, 255, 0.08);
215
+ color: #fff;
216
+ }
217
+
218
+ .nav-item.active {
219
+ background: rgba(255, 255, 255, 0.14);
220
+ color: #fff;
221
+ font-weight: 600;
222
+ border-left-color: var(--admin-accent, #f59e0b);
223
+ }
224
+
225
+ .nav-icon {
226
+ font-size: 16px;
227
+ width: 20px;
228
+ text-align: center;
229
+ }
230
+
231
+ .sidebar-footer {
232
+ padding: 12px 14px;
233
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: 8px;
237
+ flex-shrink: 0;
238
+ }
239
+
240
+ .admin-main {
241
+ flex: 1;
242
+ margin-left: var(--admin-sidebar-w, 220px);
243
+ display: flex;
244
+ flex-direction: column;
245
+ min-width: 0;
246
+ height: 100vh;
247
+ overflow: hidden;
248
+ }
249
+
250
+ .admin-header {
251
+ position: sticky;
252
+ top: 0;
253
+ z-index: 50;
254
+ flex-shrink: 0;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: space-between;
258
+ gap: 10px;
259
+ padding: 8px var(--admin-content-pad, 12px);
260
+ border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.1));
261
+ backdrop-filter: saturate(180%) blur(12px);
262
+ background: rgba(var(--tg-bg-rgb, 10, 14, 23), 0.88);
263
+ }
264
+
265
+ .admin-header-left {
266
+ display: flex;
267
+ align-items: center;
268
+ gap: 10px;
269
+ min-width: 0;
270
+ }
271
+
272
+ .admin-header-title {
273
+ margin: 0;
274
+ font-size: 16px;
275
+ font-weight: 650;
276
+ color: var(--tg-text, #f0f2f5);
277
+ white-space: nowrap;
278
+ overflow: hidden;
279
+ text-overflow: ellipsis;
280
+ }
281
+
282
+ .admin-header-actions {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 8px;
286
+ flex-shrink: 0;
287
+ }
288
+
289
+ .header-user {
290
+ display: inline-flex;
291
+ align-items: center;
292
+ gap: 8px;
293
+ max-width: 160px;
294
+ min-width: 0;
295
+ padding: 2px 4px 2px 2px;
296
+ border-radius: 999px;
297
+ }
298
+
299
+ .header-user-avatar {
300
+ width: 26px;
301
+ height: 26px;
302
+ border-radius: 50%;
303
+ background: var(--admin-accent, #f59e0b);
304
+ color: #111;
305
+ display: inline-flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ font-size: 12px;
309
+ font-weight: 700;
310
+ flex-shrink: 0;
311
+ }
312
+
313
+ .header-user-name {
314
+ font-size: 13px;
315
+ font-weight: 500;
316
+ color: var(--tg-text, #f0f2f5);
317
+ white-space: nowrap;
318
+ overflow: hidden;
319
+ text-overflow: ellipsis;
320
+ }
321
+
322
+ .header-logout {
323
+ flex-shrink: 0;
324
+ height: 28px;
325
+ padding: 0 10px;
326
+ font-size: 12px;
327
+ border: 1px solid var(--border, rgba(255, 255, 255, 0.12));
328
+ border-radius: 6px;
329
+ color: var(--tg-hint, #9aa4b2);
330
+ background: transparent;
331
+ cursor: pointer;
332
+ }
333
+
334
+ .header-logout:hover {
335
+ color: var(--tg-text, #f0f2f5);
336
+ border-color: rgba(255, 255, 255, 0.22);
337
+ background: rgba(255, 255, 255, 0.06);
338
+ }
339
+
340
+ .admin-content {
341
+ flex: 1;
342
+ min-height: 0;
343
+ overflow: auto;
344
+ padding: var(--admin-content-pad, 12px);
345
+ }
346
+
347
+ .btn-hamburger {
348
+ display: inline-flex;
349
+ flex-direction: column;
350
+ justify-content: center;
351
+ gap: 4px;
352
+ width: 36px;
353
+ height: 36px;
354
+ padding: 8px;
355
+ border: none;
356
+ border-radius: 8px;
357
+ background: transparent;
358
+ cursor: pointer;
359
+ }
360
+
361
+ .hamburger-line {
362
+ display: block;
363
+ height: 2px;
364
+ width: 100%;
365
+ background: var(--tg-text, #f0f2f5);
366
+ border-radius: 1px;
367
+ }
368
+
369
+ @media (max-width: 1023px) {
370
+ .admin-sidebar {
371
+ transform: translateX(-100%);
372
+ }
373
+ .admin-sidebar.sidebar-open {
374
+ transform: translateX(0);
375
+ }
376
+ .admin-main {
377
+ margin-left: 0;
378
+ }
379
+ /* 窄屏顶栏优先保退出;名称可藏,头像仍可见 */
380
+ .header-user-name {
381
+ display: none;
382
+ }
383
+ }
384
+ </style>