@usethink/cf-admin-fe 0.1.0 → 0.2.6
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/LICENSE +21 -0
- package/README.md +242 -53
- package/dist/composables/createAdminFlagSession.d.ts +42 -0
- package/dist/composables/createAdminRequest.d.ts +95 -0
- package/dist/composables/createAdminTokenSession.d.ts +49 -0
- package/dist/composables/index.d.ts +7 -1
- package/dist/composables/useAdminListLoader.d.ts +45 -0
- package/dist/composables/useAdminSidebar.d.ts +17 -0
- package/dist/composables/useClipboard.d.ts +24 -2
- package/dist/composables/useConfirmDialog.d.ts +10 -4
- package/dist/composables/useNow.d.ts +3 -0
- package/dist/composables/useTablePagination.d.ts +6 -0
- package/dist/composables/useTableSelection.d.ts +2 -2
- package/dist/composables/useToast.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/system-config.d.ts +34 -0
- package/dist/utils/admin-navigation.d.ts +66 -0
- package/dist/utils/admin-query.d.ts +29 -0
- package/dist/utils/batch-limit.d.ts +38 -0
- package/dist/utils/batch-result.d.ts +41 -0
- package/dist/utils/body-scroll-lock.d.ts +13 -0
- package/dist/utils/csv-export.d.ts +8 -0
- package/dist/utils/currency-cents.d.ts +16 -0
- package/dist/utils/datetime.d.ts +20 -0
- package/dist/utils/error-message.d.ts +7 -0
- package/dist/utils/format-metadata.d.ts +5 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/otp-input.d.ts +11 -0
- package/dist/utils/system-config-engine.d.ts +65 -0
- 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 +224 -0
- 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 +801 -0
- package/package.json +30 -6
- package/recipes/AdminLayout.vue +73 -0
- package/recipes/README.md +130 -0
- package/recipes/i18n-merge.snippet.ts +29 -0
- package/recipes/list-page.snippet.ts +141 -0
- package/recipes/main-styles.snippet.ts +13 -0
- package/recipes/reexports/components.ts +19 -0
- package/recipes/reexports/composables.ts +29 -0
- package/recipes/reexports/utils.ts +31 -0
- package/recipes/router-guard.snippet.ts +32 -0
- package/recipes/useAdminAuth.cookie.ts +29 -0
- package/recipes/useAdminAuth.shop.ts +29 -0
- package/recipes/useAdminRequest.cookie.ts +36 -0
- package/recipes/useAdminRequest.ts +37 -0
- package/src/components/AdminLoginShell.vue +31 -0
- package/src/components/AdminMetadataDetail.vue +121 -0
- package/src/components/AdminShell.vue +396 -0
- package/src/components/ConfigField.vue +401 -0
- package/src/components/ConfirmDialog.vue +67 -3
- package/src/components/index.ts +6 -0
- package/src/composables/createAdminFlagSession.ts +129 -0
- package/src/composables/createAdminRequest.ts +322 -0
- package/src/composables/createAdminTokenSession.ts +149 -0
- package/src/composables/index.ts +51 -0
- package/src/composables/useAdminBatchOperation.ts +4 -0
- package/src/composables/useAdminListLoader.ts +124 -0
- package/src/composables/useAdminSidebar.ts +68 -0
- package/src/composables/useClipboard.ts +41 -2
- package/src/composables/useConfirmDialog.ts +26 -4
- package/src/composables/useNow.ts +38 -0
- package/src/composables/useTablePagination.ts +6 -0
- package/src/composables/useTableSelection.ts +2 -2
- package/src/composables/useToast.ts +4 -1
- package/src/env.d.ts +2 -2
- package/src/i18n/index.ts +8 -0
- package/src/i18n/kit-messages.ts +297 -0
- package/src/index.ts +143 -4
- package/src/styles/admin-primitives.css +23 -0
- package/src/styles/index.css +1 -0
- package/src/styles/login-primitives.css +149 -0
- package/src/types/index.ts +7 -0
- package/src/types/system-config.ts +38 -0
- package/src/utils/admin-navigation.ts +138 -0
- package/src/utils/admin-query.ts +51 -0
- package/src/utils/batch-limit.ts +66 -0
- package/src/utils/batch-result.ts +81 -0
- package/src/utils/body-scroll-lock.ts +63 -0
- package/src/utils/csv-export.ts +25 -0
- package/src/utils/currency-cents.ts +30 -0
- package/src/utils/datetime.ts +54 -0
- package/src/utils/error-message.ts +32 -0
- package/src/utils/format-metadata.ts +11 -0
- package/src/utils/index.ts +81 -0
- package/src/utils/otp-input.ts +18 -0
- package/src/utils/system-config-engine.ts +212 -0
- package/dist/components/index.d.ts +0 -4
- package/dist/index.d.ts +0 -17
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 响应式后台侧边栏状态(1024px 以下显示汉堡菜单)。
|
|
3
|
+
* 布局骨架由产品方维护;这里只负责媒体查询与打开状态。
|
|
4
|
+
*/
|
|
5
|
+
import { onMounted, onUnmounted, ref, type Ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
export type UseAdminSidebarOptions = {
|
|
8
|
+
/** matchMedia 查询条件;默认 `(max-width: 1023px)` */
|
|
9
|
+
query?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type UseAdminSidebarReturn = {
|
|
13
|
+
isMobile: Ref<boolean>
|
|
14
|
+
sidebarOpen: Ref<boolean>
|
|
15
|
+
toggleSidebar: () => void
|
|
16
|
+
closeSidebar: () => void
|
|
17
|
+
openSidebar: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useAdminSidebar(
|
|
21
|
+
options: UseAdminSidebarOptions = {},
|
|
22
|
+
): UseAdminSidebarReturn {
|
|
23
|
+
const query = options.query ?? '(max-width: 1023px)'
|
|
24
|
+
const isMobile = ref(false)
|
|
25
|
+
const sidebarOpen = ref(false)
|
|
26
|
+
|
|
27
|
+
let mql: MediaQueryList | null = null
|
|
28
|
+
|
|
29
|
+
function onMqChange(e: MediaQueryListEvent | MediaQueryList) {
|
|
30
|
+
isMobile.value = e.matches
|
|
31
|
+
if (!e.matches) {
|
|
32
|
+
// 桌面端:强制关闭遮罩模式,保证固定侧边栏的 CSS 保持干净
|
|
33
|
+
sidebarOpen.value = false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toggleSidebar() {
|
|
38
|
+
sidebarOpen.value = !sidebarOpen.value
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function closeSidebar() {
|
|
42
|
+
sidebarOpen.value = false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function openSidebar() {
|
|
46
|
+
sidebarOpen.value = true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onMounted(() => {
|
|
50
|
+
if (typeof window === 'undefined' || !window.matchMedia) return
|
|
51
|
+
mql = window.matchMedia(query)
|
|
52
|
+
onMqChange(mql)
|
|
53
|
+
mql.addEventListener('change', onMqChange)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
onUnmounted(() => {
|
|
57
|
+
mql?.removeEventListener('change', onMqChange)
|
|
58
|
+
mql = null
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
isMobile,
|
|
63
|
+
sidebarOpen,
|
|
64
|
+
toggleSidebar,
|
|
65
|
+
closeSidebar,
|
|
66
|
+
openSidebar,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** 剪贴板工具:一键复制;按钮视觉反馈;或成功/失败 toast(管理端列表/详情常用) */
|
|
2
|
+
|
|
3
|
+
import { useToast, type ToastType } from './useToast'
|
|
2
4
|
|
|
3
5
|
const FEEDBACK_MS = 1500
|
|
4
6
|
|
|
5
|
-
/**
|
|
7
|
+
/** 写入剪贴板(无 UI 反馈) */
|
|
6
8
|
export async function writeClipboardText(text: string): Promise<void> {
|
|
7
9
|
if (navigator.clipboard?.writeText) {
|
|
8
10
|
await navigator.clipboard.writeText(text)
|
|
@@ -21,6 +23,43 @@ export async function writeClipboardText(text: string): Promise<void> {
|
|
|
21
23
|
if (!copied) throw new Error('Clipboard copy failed')
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 复制到剪贴板,并用全局 toast 提示成功/失败。
|
|
28
|
+
* 与 `copyText`(按钮内 ✅/❌ 反馈)互补:管理端列表/详情更常用 toast。
|
|
29
|
+
*
|
|
30
|
+
* 文案一律由产品传入(i18n),套件不内置默认中英文案。
|
|
31
|
+
*
|
|
32
|
+
* @returns 是否复制成功(调用方可在成功后关弹层等)
|
|
33
|
+
*/
|
|
34
|
+
export type CopyWithToastOptions = {
|
|
35
|
+
/** 成功 toast 文案(由产品 i18n 提供) */
|
|
36
|
+
successMessage: string
|
|
37
|
+
/** 失败 toast 文案(由产品 i18n 提供) */
|
|
38
|
+
errorMessage: string
|
|
39
|
+
/**
|
|
40
|
+
* 自定义 toast 出口;默认 `useToast().showToast`。
|
|
41
|
+
* 测试或未挂 ToastContainer 的场景可注入。
|
|
42
|
+
*/
|
|
43
|
+
showToast?: (message: string, type: ToastType) => void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function copyWithToast(
|
|
47
|
+
text: string,
|
|
48
|
+
options: CopyWithToastOptions,
|
|
49
|
+
): Promise<boolean> {
|
|
50
|
+
const toast = options.showToast ?? useToast().showToast
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await writeClipboardText(text)
|
|
54
|
+
toast(options.successMessage, 'success')
|
|
55
|
+
return true
|
|
56
|
+
} catch {
|
|
57
|
+
toast(options.errorMessage, 'error')
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 复制文本,更新触发按钮的文字反馈(前台交付明细等) */
|
|
24
63
|
export function copyText(text: string, e: Event) {
|
|
25
64
|
writeClipboardText(text).then(() => {
|
|
26
65
|
feedback(e, '✅')
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 确认弹窗状态机:与 `<ConfirmDialog />` 配对使用。
|
|
3
|
+
* 支持可选勾选项与 `requirePhrase` 键入确认。
|
|
4
|
+
*/
|
|
1
5
|
import { ref, watch } from 'vue'
|
|
2
6
|
|
|
3
7
|
export type ConfirmOptionDef = {
|
|
@@ -5,7 +9,7 @@ export type ConfirmOptionDef = {
|
|
|
5
9
|
label: string
|
|
6
10
|
/** 补充说明,显示在 label 下方 */
|
|
7
11
|
hint?: string
|
|
8
|
-
/**
|
|
12
|
+
/** 默认是否勾选;默认 false */
|
|
9
13
|
defaultChecked?: boolean
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -14,6 +18,11 @@ export type ConfirmAskOptions = {
|
|
|
14
18
|
danger?: boolean
|
|
15
19
|
/** 可选勾选项,默认全部未勾选;打开弹窗时按 defaultChecked 重置 */
|
|
16
20
|
options?: ConfirmOptionDef[]
|
|
21
|
+
/**
|
|
22
|
+
* 须键入的确认短语(如硬删除的 "DELETE")。
|
|
23
|
+
* 有值时确认按钮在输入完全匹配前禁用;与服务端 confirm 字面量对齐。
|
|
24
|
+
*/
|
|
25
|
+
requirePhrase?: string
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
export type ConfirmResult = {
|
|
@@ -27,6 +36,8 @@ export function useConfirmDialog() {
|
|
|
27
36
|
const confirmMessage = ref('')
|
|
28
37
|
const confirmOptionDefs = ref<ConfirmOptionDef[]>([])
|
|
29
38
|
const confirmOptionValues = ref<Record<string, boolean>>({})
|
|
39
|
+
/** 非空则弹窗要求键入该短语 */
|
|
40
|
+
const confirmRequirePhrase = ref<string | null>(null)
|
|
30
41
|
let confirmCallback: ((result: ConfirmResult) => void) | null = null
|
|
31
42
|
|
|
32
43
|
function snapshotOptions(): Record<string, boolean> {
|
|
@@ -47,10 +58,13 @@ export function useConfirmDialog() {
|
|
|
47
58
|
|
|
48
59
|
/**
|
|
49
60
|
* 简单确认:仅返回 boolean(兼容旧调用)。
|
|
50
|
-
*
|
|
61
|
+
* 需要 Checkbox / requirePhrase 时请用 askConfirmWithOptions,或把 options 作为第二参传入。
|
|
51
62
|
*/
|
|
52
|
-
function askConfirm(
|
|
53
|
-
|
|
63
|
+
function askConfirm(
|
|
64
|
+
message: string,
|
|
65
|
+
options: ConfirmAskOptions = {},
|
|
66
|
+
): Promise<boolean> {
|
|
67
|
+
return askConfirmWithOptions(message, options).then((r) => r.confirmed)
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
function askConfirmWithOptions(
|
|
@@ -62,6 +76,10 @@ export function useConfirmDialog() {
|
|
|
62
76
|
const defs = options.options ?? []
|
|
63
77
|
confirmOptionDefs.value = defs
|
|
64
78
|
resetOptionValues(defs)
|
|
79
|
+
confirmRequirePhrase.value =
|
|
80
|
+
typeof options.requirePhrase === 'string' && options.requirePhrase.length > 0
|
|
81
|
+
? options.requirePhrase
|
|
82
|
+
: null
|
|
65
83
|
confirmVisible.value = true
|
|
66
84
|
return new Promise((resolve) => {
|
|
67
85
|
confirmCallback = (result) => resolve(result)
|
|
@@ -88,6 +106,9 @@ export function useConfirmDialog() {
|
|
|
88
106
|
confirmCallback = null
|
|
89
107
|
callback({ confirmed: false, options: snapshotOptions() })
|
|
90
108
|
}
|
|
109
|
+
if (!visible) {
|
|
110
|
+
confirmRequirePhrase.value = null
|
|
111
|
+
}
|
|
91
112
|
})
|
|
92
113
|
|
|
93
114
|
return {
|
|
@@ -95,6 +116,7 @@ export function useConfirmDialog() {
|
|
|
95
116
|
confirmMessage,
|
|
96
117
|
confirmOptionDefs,
|
|
97
118
|
confirmOptionValues,
|
|
119
|
+
confirmRequirePhrase,
|
|
98
120
|
askConfirm,
|
|
99
121
|
askConfirmWithOptions,
|
|
100
122
|
onConfirm,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享的当前时间 composable
|
|
3
|
+
*
|
|
4
|
+
* 多个组件需要展示倒计时/实时时钟时,
|
|
5
|
+
* 共享同一个 60 秒定时器,避免 N 个组件创建 N 个 setInterval。
|
|
6
|
+
*
|
|
7
|
+
* 使用 ref 计数模式:仅当至少一个组件订阅时才启动定时器,
|
|
8
|
+
* 所有组件卸载后自动清理。
|
|
9
|
+
*/
|
|
10
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
11
|
+
|
|
12
|
+
const nowMs = ref(Date.now())
|
|
13
|
+
let timer: number | undefined
|
|
14
|
+
let subscriberCount = 0
|
|
15
|
+
|
|
16
|
+
export function useNow() {
|
|
17
|
+
onMounted(() => {
|
|
18
|
+
subscriberCount++
|
|
19
|
+
if (subscriberCount === 1) {
|
|
20
|
+
timer = window.setInterval(() => {
|
|
21
|
+
nowMs.value = Date.now()
|
|
22
|
+
}, 60000)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
onUnmounted(() => {
|
|
27
|
+
subscriberCount--
|
|
28
|
+
if (subscriberCount <= 0) {
|
|
29
|
+
subscriberCount = 0
|
|
30
|
+
if (timer !== undefined) {
|
|
31
|
+
window.clearInterval(timer)
|
|
32
|
+
timer = undefined
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return { nowMs }
|
|
38
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理端表格 offset 分页状态(page / limit / total)。
|
|
3
|
+
* 列表请求与列定义仍由产品页面负责。
|
|
4
|
+
*/
|
|
1
5
|
import { ref, computed, type Ref, type ComputedRef } from 'vue'
|
|
2
6
|
|
|
3
7
|
export interface UseTablePaginationOptions {
|
|
8
|
+
/** 初始页码;默认 1 */
|
|
4
9
|
initialPage?: number
|
|
10
|
+
/** 初始每页条数;默认 20 */
|
|
5
11
|
initialLimit?: number
|
|
6
12
|
}
|
|
7
13
|
|
|
@@ -5,8 +5,8 @@ export interface UseTableSelectionOptions<T> {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 接受行列表的 Ref 或 ComputedRef(MaybeRef),使产品视图可以直接传入
|
|
9
|
+
* 过滤后的 computed 表格,避免双包 Ref 类型赋值的摩擦。
|
|
10
10
|
*/
|
|
11
11
|
export function useTableSelection<T>(
|
|
12
12
|
rows: MaybeRef<T[]>,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局 toast 队列(模块单例)。应用根节点挂一次 `<ToastContainer />` 即可。
|
|
3
|
+
*/
|
|
1
4
|
import { ref } from 'vue'
|
|
2
5
|
|
|
3
6
|
export type ToastType = 'success' | 'error' | 'info'
|
|
@@ -7,7 +10,7 @@ export type ToastItem = { id: number; message: string; type: ToastType }
|
|
|
7
10
|
const toasts = ref<ToastItem[]>([])
|
|
8
11
|
let count = 0
|
|
9
12
|
|
|
10
|
-
/**
|
|
13
|
+
/** 最多叠加显示的 toast 数;超过后丢弃较早的条目(抽奖加固)。 */
|
|
11
14
|
export const MAX_TOASTS = 5
|
|
12
15
|
|
|
13
16
|
function showToast(message: string, type: ToastType = 'info', duration = 3000) {
|
package/src/env.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ declare module '*.css' {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* 宿主应用(shop / lottery / auth)注入 vue-i18n;套件组件会调用 `$t('pagination.*')` 等。
|
|
16
|
+
* 在两个入口都声明,便于 vue-tsc 消费方共用同一处类型增强。
|
|
17
17
|
*/
|
|
18
18
|
declare module 'vue' {
|
|
19
19
|
interface ComponentCustomProperties {
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kit 组件所需的最小 i18n 消息集。
|
|
3
|
+
* 宿主应用应将它们深度合并到本地化消息中,使批量复制的
|
|
4
|
+
* 管理端不缺 `pagination.*` / `confirm.*` / ConfigField 键。
|
|
5
|
+
*
|
|
6
|
+
* 产品专属菜单(`adminLayout.menu*`)保留在产品侧。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type AdminKitLocale = 'zh-CN' | 'en'
|
|
10
|
+
|
|
11
|
+
/** 与 vue-i18n `messages` 兼容的嵌套消息树 */
|
|
12
|
+
export type AdminKitMessageTree = {
|
|
13
|
+
pagination: Record<string, string>
|
|
14
|
+
confirm: Record<string, string>
|
|
15
|
+
adminModal: Record<string, string>
|
|
16
|
+
adminConfigField: Record<string, string>
|
|
17
|
+
adminLayout: Record<string, string>
|
|
18
|
+
adminLogs: Record<string, string>
|
|
19
|
+
adminConfig: Record<string, string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const adminKitMessagesZhCN: AdminKitMessageTree = {
|
|
23
|
+
pagination: {
|
|
24
|
+
listLabel: '分页',
|
|
25
|
+
total: '共 {total} 条',
|
|
26
|
+
perPage: '每页',
|
|
27
|
+
perPageUnit: '条',
|
|
28
|
+
prev: '上一页',
|
|
29
|
+
next: '下一页',
|
|
30
|
+
prevLabel: '上一页',
|
|
31
|
+
nextLabel: '下一页',
|
|
32
|
+
page: '第 {page} 页',
|
|
33
|
+
pageOf: '{page} / {total}',
|
|
34
|
+
jumpTo: '跳至',
|
|
35
|
+
jumpPage: '页',
|
|
36
|
+
jump: '跳转',
|
|
37
|
+
jumpLabel: '页码',
|
|
38
|
+
},
|
|
39
|
+
confirm: {
|
|
40
|
+
confirm: '确认',
|
|
41
|
+
cancel: '取消',
|
|
42
|
+
delete: '删除选项',
|
|
43
|
+
requirePhrase: '请键入 {phrase} 以确认',
|
|
44
|
+
},
|
|
45
|
+
adminModal: {
|
|
46
|
+
title: '对话框',
|
|
47
|
+
close: '关闭',
|
|
48
|
+
},
|
|
49
|
+
adminConfigField: {
|
|
50
|
+
reset: '重置',
|
|
51
|
+
saving: '保存中…',
|
|
52
|
+
range: '范围 {min} ~ {max}',
|
|
53
|
+
yuanUnit: '元',
|
|
54
|
+
hint: '按元编辑,入库为分',
|
|
55
|
+
override: '已配置,留空不修改',
|
|
56
|
+
notConfigured: '未配置',
|
|
57
|
+
enumEmptyHint: '至少保留一项',
|
|
58
|
+
unlimited: '不限',
|
|
59
|
+
placeholder: '请输入有效金额',
|
|
60
|
+
minError: '不能小于 {min}',
|
|
61
|
+
maxError: '不能大于 {max}',
|
|
62
|
+
},
|
|
63
|
+
adminLayout: {
|
|
64
|
+
logout: '退出',
|
|
65
|
+
toggleSidebar: '菜单',
|
|
66
|
+
admin: '管理员',
|
|
67
|
+
title: '管理后台',
|
|
68
|
+
loggedOut: '已退出登录',
|
|
69
|
+
},
|
|
70
|
+
adminLogs: {
|
|
71
|
+
title: '操作日志',
|
|
72
|
+
search: '搜索操作类型',
|
|
73
|
+
targetSearch: '搜索目标类型',
|
|
74
|
+
targetSearchPlaceholder: '搜索目标',
|
|
75
|
+
searchBtn: '查询',
|
|
76
|
+
clearing: '清除中…',
|
|
77
|
+
clearAll: '清除全部日志',
|
|
78
|
+
allLogs: '全部日志',
|
|
79
|
+
clearFilters: '清空筛选条件',
|
|
80
|
+
reload: '重新加载',
|
|
81
|
+
selectedCount: '当前页已选 {count} 条日志',
|
|
82
|
+
clearSelection: '清空选择',
|
|
83
|
+
copySelected: '复制已选',
|
|
84
|
+
exportSelectedCsv: '导出已选 CSV',
|
|
85
|
+
batchDelete: '批量删除({count})',
|
|
86
|
+
deleting: '删除中…',
|
|
87
|
+
colTime: '时间',
|
|
88
|
+
colOperator: '操作人',
|
|
89
|
+
colAction: '操作类型',
|
|
90
|
+
colTarget: '目标类型',
|
|
91
|
+
colTargetId: '目标标识',
|
|
92
|
+
colIpFingerprint: 'IP 标识',
|
|
93
|
+
colMetadata: '元数据',
|
|
94
|
+
colDetail: '详情',
|
|
95
|
+
noData: '暂无日志',
|
|
96
|
+
viewMetadata: '查看',
|
|
97
|
+
tableScrollArea: '日志表格滚动区域',
|
|
98
|
+
tableLabel: '管理日志列表',
|
|
99
|
+
selectAllPage: '选择当前页日志',
|
|
100
|
+
selectRow: '选择日志 {log}',
|
|
101
|
+
metadataTitle: '日志详情',
|
|
102
|
+
basicInfo: '基本信息',
|
|
103
|
+
labelAction: '操作',
|
|
104
|
+
labelTargetType: '目标类型',
|
|
105
|
+
labelTargetId: '目标 ID',
|
|
106
|
+
labelTime: '时间',
|
|
107
|
+
labelIpFingerprint: 'IP 标识(哈希)',
|
|
108
|
+
labelMetadata: '元数据',
|
|
109
|
+
presetOrder: '订单处理',
|
|
110
|
+
presetOrderDesc: '付款确认、取消、补偿备注',
|
|
111
|
+
presetStock: '库存维护',
|
|
112
|
+
presetStockDesc: '导入、启停、批量禁用卡密',
|
|
113
|
+
presetProduct: '商品调整',
|
|
114
|
+
presetProductDesc: '创建、更新、删除商品',
|
|
115
|
+
presetCleanup: '系统清理',
|
|
116
|
+
presetCleanupDesc: '过期订单和锁定库存清理',
|
|
117
|
+
confirmClearAll: '确认清除全部操作日志?此操作不可撤销。',
|
|
118
|
+
exportSelectedSuccess: '已导出 {count} 条日志',
|
|
119
|
+
batchDeleteConfirm: '确认删除选中的 {count} 条日志?',
|
|
120
|
+
batchDeleteSuccess: '已删除 {count} 条日志',
|
|
121
|
+
batchDeleteFailed: '批量删除日志失败',
|
|
122
|
+
copySelectedSuccess: '已复制 {count} 条日志',
|
|
123
|
+
copySelectedFailed: '复制失败,请手动查看或导出 CSV',
|
|
124
|
+
},
|
|
125
|
+
adminConfig: {
|
|
126
|
+
title: '系统配置',
|
|
127
|
+
refresh: '刷新',
|
|
128
|
+
reload: '重新加载',
|
|
129
|
+
search: '搜索配置项',
|
|
130
|
+
colKey: '配置项',
|
|
131
|
+
colValue: '值',
|
|
132
|
+
colAction: '操作',
|
|
133
|
+
actionEdit: '编辑',
|
|
134
|
+
save: '保存',
|
|
135
|
+
saved: '保存成功',
|
|
136
|
+
saveFailed: '保存失败',
|
|
137
|
+
noData: '暂无配置',
|
|
138
|
+
sectionTabsLabel: '系统配置分区',
|
|
139
|
+
itemCount: '{count} 项',
|
|
140
|
+
loadFailed: '加载系统配置失败',
|
|
141
|
+
notSynced: ',但当前页面未能刷新前台配置;重新加载页面后可同步',
|
|
142
|
+
resetConfirm: '确认重置 {key} 为默认值?',
|
|
143
|
+
resetFailed: '重置失败',
|
|
144
|
+
resetSuccess: '已重置',
|
|
145
|
+
clearing: '清除中…',
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const adminKitMessagesEn: AdminKitMessageTree = {
|
|
150
|
+
pagination: {
|
|
151
|
+
listLabel: 'Pagination',
|
|
152
|
+
total: '{total} total',
|
|
153
|
+
perPage: 'Per page',
|
|
154
|
+
perPageUnit: '',
|
|
155
|
+
prev: 'Prev',
|
|
156
|
+
next: 'Next',
|
|
157
|
+
prevLabel: 'Previous page',
|
|
158
|
+
nextLabel: 'Next page',
|
|
159
|
+
page: 'Page {page}',
|
|
160
|
+
pageOf: '{page} / {total}',
|
|
161
|
+
jumpTo: 'Go to',
|
|
162
|
+
jumpPage: '',
|
|
163
|
+
jump: 'Go',
|
|
164
|
+
jumpLabel: 'Page number',
|
|
165
|
+
},
|
|
166
|
+
confirm: {
|
|
167
|
+
confirm: 'Confirm',
|
|
168
|
+
cancel: 'Cancel',
|
|
169
|
+
delete: 'Delete options',
|
|
170
|
+
requirePhrase: 'Type {phrase} to confirm',
|
|
171
|
+
},
|
|
172
|
+
adminModal: {
|
|
173
|
+
title: 'Dialog',
|
|
174
|
+
close: 'Close',
|
|
175
|
+
},
|
|
176
|
+
adminConfigField: {
|
|
177
|
+
reset: 'Reset',
|
|
178
|
+
saving: 'Saving…',
|
|
179
|
+
range: 'Range {min} ~ {max}',
|
|
180
|
+
yuanUnit: 'CNY',
|
|
181
|
+
hint: 'Edit in yuan; stored as cents',
|
|
182
|
+
override: 'Configured — leave blank to keep',
|
|
183
|
+
notConfigured: 'Not configured',
|
|
184
|
+
enumEmptyHint: 'Keep at least one option',
|
|
185
|
+
unlimited: 'Unlimited',
|
|
186
|
+
placeholder: 'Enter a valid amount',
|
|
187
|
+
minError: 'Must be ≥ {min}',
|
|
188
|
+
maxError: 'Must be ≤ {max}',
|
|
189
|
+
},
|
|
190
|
+
adminLayout: {
|
|
191
|
+
logout: 'Log out',
|
|
192
|
+
toggleSidebar: 'Menu',
|
|
193
|
+
admin: 'Admin',
|
|
194
|
+
title: 'Admin',
|
|
195
|
+
loggedOut: 'Signed out',
|
|
196
|
+
},
|
|
197
|
+
adminLogs: {
|
|
198
|
+
title: 'Audit Logs',
|
|
199
|
+
search: 'Search action type',
|
|
200
|
+
targetSearch: 'Search target type',
|
|
201
|
+
targetSearchPlaceholder: 'Search target',
|
|
202
|
+
searchBtn: 'Search',
|
|
203
|
+
clearing: 'Clearing…',
|
|
204
|
+
clearAll: 'Clear all logs',
|
|
205
|
+
allLogs: 'All logs',
|
|
206
|
+
clearFilters: 'Clear filters',
|
|
207
|
+
reload: 'Reload',
|
|
208
|
+
selectedCount: '{count} logs selected',
|
|
209
|
+
clearSelection: 'Clear selection',
|
|
210
|
+
copySelected: 'Copy selected',
|
|
211
|
+
exportSelectedCsv: 'Export CSV',
|
|
212
|
+
batchDelete: 'Batch delete ({count})',
|
|
213
|
+
deleting: 'Deleting…',
|
|
214
|
+
colTime: 'Time',
|
|
215
|
+
colOperator: 'Operator',
|
|
216
|
+
colAction: 'Action',
|
|
217
|
+
colTarget: 'Target type',
|
|
218
|
+
colTargetId: 'Target ID',
|
|
219
|
+
colIpFingerprint: 'IP fingerprint',
|
|
220
|
+
colMetadata: 'Metadata',
|
|
221
|
+
colDetail: 'Details',
|
|
222
|
+
noData: 'No logs',
|
|
223
|
+
viewMetadata: 'View',
|
|
224
|
+
tableScrollArea: 'Log table scroll area',
|
|
225
|
+
tableLabel: 'Audit log list',
|
|
226
|
+
selectAllPage: 'Select all on this page',
|
|
227
|
+
selectRow: 'Select log {log}',
|
|
228
|
+
metadataTitle: 'Log details',
|
|
229
|
+
basicInfo: 'Basic info',
|
|
230
|
+
labelAction: 'Action',
|
|
231
|
+
labelTargetType: 'Target type',
|
|
232
|
+
labelTargetId: 'Target ID',
|
|
233
|
+
labelTime: 'Time',
|
|
234
|
+
labelIpFingerprint: 'IP fingerprint (hash)',
|
|
235
|
+
labelMetadata: 'Metadata',
|
|
236
|
+
presetOrder: 'Order Processing',
|
|
237
|
+
presetOrderDesc: 'Payment confirmation, cancellation, compensation notes',
|
|
238
|
+
presetStock: 'Stock Management',
|
|
239
|
+
presetStockDesc: 'Import, enable/disable, batch disable cards',
|
|
240
|
+
presetProduct: 'Product Adjustment',
|
|
241
|
+
presetProductDesc: 'Create, update, delete products',
|
|
242
|
+
presetCleanup: 'System Cleanup',
|
|
243
|
+
presetCleanupDesc: 'Clean expired orders and locked stock',
|
|
244
|
+
confirmClearAll: 'Confirm clearing all logs? This action is irreversible.',
|
|
245
|
+
exportSelectedSuccess: 'Exported {count} logs',
|
|
246
|
+
batchDeleteConfirm: 'Confirm deleting {count} selected logs?',
|
|
247
|
+
batchDeleteSuccess: 'Deleted {count} logs',
|
|
248
|
+
batchDeleteFailed: 'Batch delete failed',
|
|
249
|
+
copySelectedSuccess: 'Copied {count} logs',
|
|
250
|
+
copySelectedFailed: 'Copy failed, please view or export CSV instead',
|
|
251
|
+
},
|
|
252
|
+
adminConfig: {
|
|
253
|
+
title: 'System Config',
|
|
254
|
+
refresh: 'Refresh',
|
|
255
|
+
reload: 'Reload',
|
|
256
|
+
search: 'Search config items',
|
|
257
|
+
colKey: 'Key',
|
|
258
|
+
colValue: 'Value',
|
|
259
|
+
colAction: 'Action',
|
|
260
|
+
actionEdit: 'Edit',
|
|
261
|
+
save: 'Save',
|
|
262
|
+
saved: 'Saved',
|
|
263
|
+
saveFailed: 'Save failed',
|
|
264
|
+
noData: 'No configuration',
|
|
265
|
+
sectionTabsLabel: 'Config sections',
|
|
266
|
+
itemCount: '{count} items',
|
|
267
|
+
loadFailed: 'Failed to load system config',
|
|
268
|
+
notSynced:
|
|
269
|
+
', but the public config could not be refreshed on this page. Reload the page to sync.',
|
|
270
|
+
resetConfirm: 'Confirm reset {key} to default?',
|
|
271
|
+
resetFailed: 'Reset failed',
|
|
272
|
+
resetSuccess: 'Reset successfully',
|
|
273
|
+
clearing: 'Clearing…',
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const adminKitMessages: Record<AdminKitLocale, AdminKitMessageTree> = {
|
|
278
|
+
'zh-CN': adminKitMessagesZhCN,
|
|
279
|
+
en: adminKitMessagesEn,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 将套件消息浅合并进宿主本地化对象。
|
|
284
|
+
* 默认:套件补空缺,宿主同名键覆盖套件(产品文案优先)。
|
|
285
|
+
*/
|
|
286
|
+
export function mergeAdminKitMessages<T extends Record<string, unknown>>(
|
|
287
|
+
host: T,
|
|
288
|
+
locale: AdminKitLocale = 'zh-CN',
|
|
289
|
+
): T & AdminKitMessageTree {
|
|
290
|
+
const kit = adminKitMessages[locale]
|
|
291
|
+
const out = { ...host } as T & AdminKitMessageTree
|
|
292
|
+
for (const section of Object.keys(kit) as (keyof AdminKitMessageTree)[]) {
|
|
293
|
+
const hostSection = (host[section as string] as Record<string, string> | undefined) ?? {}
|
|
294
|
+
;(out as Record<string, unknown>)[section] = { ...kit[section], ...hostSection }
|
|
295
|
+
}
|
|
296
|
+
return out
|
|
297
|
+
}
|