@svadmin/lite 0.25.7 → 0.27.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/README.md +11 -5
- package/dist/components/LiteAnnouncementBanner.svelte +88 -0
- package/dist/components/LiteAnnouncementBanner.svelte.d.ts +12 -0
- package/dist/components/LiteAsyncExportModal.svelte +113 -0
- package/dist/components/LiteAsyncExportModal.svelte.d.ts +14 -0
- package/dist/components/LiteAuditLog.svelte +102 -36
- package/dist/components/LiteAuditLog.svelte.d.ts +6 -3
- package/dist/components/LiteConcurrentConflictModal.svelte +110 -0
- package/dist/components/LiteConcurrentConflictModal.svelte.d.ts +21 -0
- package/dist/components/LiteContextMenu.svelte +114 -0
- package/dist/components/LiteContextMenu.svelte.d.ts +18 -0
- package/dist/components/LiteCronExpressionBuilder.svelte +78 -0
- package/dist/components/LiteCronExpressionBuilder.svelte.d.ts +17 -0
- package/dist/components/LiteDualApprovalGate.svelte +58 -0
- package/dist/components/LiteDualApprovalGate.svelte.d.ts +11 -0
- package/dist/components/LiteJsonViewer.svelte +88 -0
- package/dist/components/LiteJsonViewer.svelte.d.ts +9 -0
- package/dist/components/LiteKeyboardShortcutsModal.svelte +56 -0
- package/dist/components/LiteKeyboardShortcutsModal.svelte.d.ts +13 -0
- package/dist/components/LiteKpiCard.svelte +169 -0
- package/dist/components/LiteKpiCard.svelte.d.ts +25 -0
- package/dist/components/LiteMetricGrid.svelte +143 -0
- package/dist/components/LiteMetricGrid.svelte.d.ts +31 -0
- package/dist/components/LiteMicroAppFrame.svelte +57 -0
- package/dist/components/LiteMicroAppFrame.svelte.d.ts +15 -0
- package/dist/components/LiteOrgChart.svelte +78 -0
- package/dist/components/LiteOrgChart.svelte.d.ts +17 -0
- package/dist/components/LitePresenceAvatarGroup.svelte +118 -15
- package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +8 -3
- package/dist/components/LiteResultPage.svelte +135 -0
- package/dist/components/LiteResultPage.svelte.d.ts +17 -0
- package/dist/components/LiteSafeDeleteConfirm.svelte +86 -0
- package/dist/components/LiteSafeDeleteConfirm.svelte.d.ts +21 -0
- package/dist/components/LiteSensitiveDataMask.svelte +34 -2
- package/dist/components/LiteSensitiveDataMask.svelte.d.ts +1 -2
- package/dist/components/LiteSignaturePad.svelte +3 -1
- package/dist/components/LiteSignaturePad.svelte.d.ts +1 -0
- package/dist/components/LiteStepWizard.svelte +98 -0
- package/dist/components/LiteStepWizard.svelte.d.ts +16 -0
- package/dist/components/LiteTaskCenter.svelte +133 -0
- package/dist/components/LiteTaskCenter.svelte.d.ts +24 -0
- package/dist/components/LiteTextDiffViewer.svelte +111 -0
- package/dist/components/LiteTextDiffViewer.svelte.d.ts +10 -0
- package/dist/components/LiteTour.svelte +4 -0
- package/dist/components/LiteTour.svelte.d.ts +4 -0
- package/dist/components/LiteTrendIndicator.svelte +119 -0
- package/dist/components/LiteTrendIndicator.svelte.d.ts +19 -0
- package/dist/components/LiteUnsavedChangesGuard.svelte +53 -0
- package/dist/components/LiteUnsavedChangesGuard.svelte.d.ts +10 -0
- package/dist/components/LiteVirtualTreeSelect.svelte +92 -0
- package/dist/components/LiteVirtualTreeSelect.svelte.d.ts +20 -0
- package/dist/components/LiteWatermarkContainer.svelte +58 -0
- package/dist/components/LiteWatermarkContainer.svelte.d.ts +9 -0
- package/dist/components/LiteWorkflowStepper.svelte +56 -0
- package/dist/components/LiteWorkflowStepper.svelte.d.ts +14 -0
- package/dist/components/widgets/LiteGaugeChart.svelte +50 -3
- package/dist/components/widgets/LiteGaugeChart.svelte.d.ts +12 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +24 -0
- package/dist/rich-content.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export interface LiteContextMenuItem {
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
shortcut?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
danger?: boolean;
|
|
10
|
+
divider?: boolean;
|
|
11
|
+
href?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
items: LiteContextMenuItem[];
|
|
16
|
+
class?: string;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
items = [],
|
|
22
|
+
class: className = '',
|
|
23
|
+
children,
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
class="lite-context-menu-wrapper {className}"
|
|
29
|
+
data-svadmin-component="LiteContextMenu"
|
|
30
|
+
>
|
|
31
|
+
<div class="lite-context-target">
|
|
32
|
+
{#if children}{@render children()}{/if}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{#if items.length > 0}
|
|
36
|
+
<ul class="lite-context-menu-list" role="menu">
|
|
37
|
+
{#each items as item (item.key)}
|
|
38
|
+
{#if item.divider}
|
|
39
|
+
<li class="lite-context-divider" role="separator"></li>
|
|
40
|
+
{:else}
|
|
41
|
+
<li
|
|
42
|
+
class="lite-context-item"
|
|
43
|
+
class:lite-context-danger={item.danger}
|
|
44
|
+
class:lite-context-disabled={item.disabled}
|
|
45
|
+
role="menuitem"
|
|
46
|
+
>
|
|
47
|
+
{#if item.href && !item.disabled}
|
|
48
|
+
<a href={item.href} class="lite-context-link">
|
|
49
|
+
<span>{item.label}</span>
|
|
50
|
+
{#if item.shortcut}<kbd>{item.shortcut}</kbd>{/if}
|
|
51
|
+
</a>
|
|
52
|
+
{:else}
|
|
53
|
+
<span class="lite-context-label">{item.label}</span>
|
|
54
|
+
{#if item.shortcut}<kbd>{item.shortcut}</kbd>{/if}
|
|
55
|
+
{/if}
|
|
56
|
+
</li>
|
|
57
|
+
{/if}
|
|
58
|
+
{/each}
|
|
59
|
+
</ul>
|
|
60
|
+
{/if}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<style>
|
|
64
|
+
.lite-context-menu-wrapper {
|
|
65
|
+
position: relative;
|
|
66
|
+
display: block;
|
|
67
|
+
}
|
|
68
|
+
.lite-context-menu-list {
|
|
69
|
+
margin: 8px 0 0;
|
|
70
|
+
padding: 4px;
|
|
71
|
+
list-style: none;
|
|
72
|
+
border: 1px solid #e5e6eb;
|
|
73
|
+
border-radius: 6px;
|
|
74
|
+
background: #ffffff;
|
|
75
|
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
flex-wrap: wrap;
|
|
78
|
+
}
|
|
79
|
+
.lite-context-menu-list > * + * { margin-left: 4px; }
|
|
80
|
+
.lite-context-divider {
|
|
81
|
+
width: 1px;
|
|
82
|
+
height: 16px;
|
|
83
|
+
background: #e5e6eb;
|
|
84
|
+
align-self: center;
|
|
85
|
+
margin: 0 4px;
|
|
86
|
+
}
|
|
87
|
+
.lite-context-item {
|
|
88
|
+
font-size: 12px;
|
|
89
|
+
border-radius: 4px;
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
padding: 4px 8px;
|
|
93
|
+
color: #1d2129;
|
|
94
|
+
background: #f7f8fa;
|
|
95
|
+
}
|
|
96
|
+
.lite-context-item > * + * { margin-left: 6px; }
|
|
97
|
+
.lite-context-link {
|
|
98
|
+
color: inherit;
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
}
|
|
103
|
+
.lite-context-link > * + * { margin-left: 6px; }
|
|
104
|
+
.lite-context-danger { color: #f53f3f; background: #ffece8; }
|
|
105
|
+
.lite-context-disabled { opacity: 0.5; pointer-events: none; }
|
|
106
|
+
kbd {
|
|
107
|
+
font-size: 10px;
|
|
108
|
+
font-family: monospace;
|
|
109
|
+
background: #e5e6eb;
|
|
110
|
+
padding: 1px 4px;
|
|
111
|
+
border-radius: 3px;
|
|
112
|
+
color: #4e5969;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export interface LiteContextMenuItem {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
shortcut?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
danger?: boolean;
|
|
8
|
+
divider?: boolean;
|
|
9
|
+
href?: string;
|
|
10
|
+
}
|
|
11
|
+
interface Props {
|
|
12
|
+
items: LiteContextMenuItem[];
|
|
13
|
+
class?: string;
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const LiteContextMenu: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type LiteContextMenu = ReturnType<typeof LiteContextMenu>;
|
|
18
|
+
export default LiteContextMenu;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface CronPreset {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
value?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
presets?: CronPreset[];
|
|
13
|
+
formAction?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
value = '',
|
|
20
|
+
name = 'cron',
|
|
21
|
+
label = 'Cron expression',
|
|
22
|
+
description = 'Use a preset value or edit the expression directly; the visual builder is a client enhancement.',
|
|
23
|
+
presets = [
|
|
24
|
+
{ label: 'Every minute', value: '* * * * *' },
|
|
25
|
+
{ label: 'Every hour', value: '0 * * * *' },
|
|
26
|
+
{ label: 'Every day at midnight', value: '0 0 * * *' },
|
|
27
|
+
{ label: 'Every Monday at 09:00', value: '0 9 * * 1' },
|
|
28
|
+
],
|
|
29
|
+
formAction,
|
|
30
|
+
disabled = false,
|
|
31
|
+
class: className = '',
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
let expression = $state(value);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<section class="lite-cron-builder lite-card {className}" aria-label={label}>
|
|
38
|
+
<header>
|
|
39
|
+
<h2>{label}</h2>
|
|
40
|
+
{#if description}<p>{description}</p>{/if}
|
|
41
|
+
</header>
|
|
42
|
+
|
|
43
|
+
<div class="lite-cron-preset">
|
|
44
|
+
<span class="lite-cron-preset-label">Presets</span>
|
|
45
|
+
<ul>
|
|
46
|
+
{#each presets as preset (preset.value)}
|
|
47
|
+
<li><strong>{preset.label}</strong> <code>{preset.value}</code></li>
|
|
48
|
+
{/each}
|
|
49
|
+
</ul>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{#if formAction}
|
|
53
|
+
<form method="POST" action={formAction}>
|
|
54
|
+
<label for="lite-cron-expression">Expression</label>
|
|
55
|
+
<input id="lite-cron-expression" {name} bind:value={expression} {disabled} autocomplete="off" spellcheck="false" />
|
|
56
|
+
<button type="submit" {disabled}>Save schedule</button>
|
|
57
|
+
</form>
|
|
58
|
+
{:else}
|
|
59
|
+
<label for="lite-cron-expression">Expression</label>
|
|
60
|
+
<input id="lite-cron-expression" {name} bind:value={expression} {disabled} autocomplete="off" spellcheck="false" />
|
|
61
|
+
{/if}
|
|
62
|
+
|
|
63
|
+
<code class="lite-cron-preview">{expression || '—'}</code>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.lite-cron-builder { padding: 16px; }
|
|
68
|
+
.lite-cron-builder header h2 { margin: 0; color: #1d2129; font-size: 16px; }
|
|
69
|
+
.lite-cron-builder header p { margin: 4px 0 12px; color: #4e5969; font-size: 12px; line-height: 1.5; }
|
|
70
|
+
.lite-cron-builder label { display: block; margin: 10px 0 4px; color: #4e5969; font-size: 12px; font-weight: 600; }
|
|
71
|
+
.lite-cron-preset-label { display: block; margin: 10px 0 4px; color: #4e5969; font-size: 12px; font-weight: 600; }
|
|
72
|
+
.lite-cron-preset ul { margin: 0; padding: 0; list-style: none; }
|
|
73
|
+
.lite-cron-preset li { padding: 5px 0; color: #4e5969; font-size: 12px; }
|
|
74
|
+
.lite-cron-preset li strong { margin-right: 6px; color: #1d2129; }
|
|
75
|
+
.lite-cron-builder input { width: 100%; box-sizing: border-box; padding: 7px 9px; border: 1px solid #e5e6eb; border-radius: 4px; background: #fff; color: #1d2129; font: inherit; font-size: 13px; }
|
|
76
|
+
.lite-cron-builder button { margin-top: 10px; padding: 7px 12px; border: 0; border-radius: 4px; background: #165dff; color: #fff; font-size: 12px; cursor: pointer; }
|
|
77
|
+
.lite-cron-preview { display: block; margin-top: 10px; padding: 8px 10px; border-radius: 4px; background: #f7f8fa; color: #1d2129; font-size: 12px; }
|
|
78
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface CronPreset {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
interface Props {
|
|
6
|
+
value?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
presets?: CronPreset[];
|
|
11
|
+
formAction?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const LiteCronExpressionBuilder: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type LiteCronExpressionBuilder = ReturnType<typeof LiteCronExpressionBuilder>;
|
|
17
|
+
export default LiteCronExpressionBuilder;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
actionTitle: string;
|
|
6
|
+
requiredRole?: string;
|
|
7
|
+
formAction?: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
actionTitle,
|
|
14
|
+
requiredRole = '合规主管',
|
|
15
|
+
formAction = '/approval/verify',
|
|
16
|
+
class: className = '',
|
|
17
|
+
children,
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class="lite-dual-approval {className}"
|
|
23
|
+
data-svadmin-component="LiteDualApprovalGate"
|
|
24
|
+
>
|
|
25
|
+
<div class="lite-approval-header">
|
|
26
|
+
<span class="lite-approval-badge">双人联签 (Two-Person Rule)</span>
|
|
27
|
+
<h3 class="lite-approval-title">{actionTitle}</h3>
|
|
28
|
+
<p class="lite-approval-desc">特权高危操作,须由第二位具备 [{requiredRole}] 权限的主管输入口令核验。</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{#if children}{@render children()}{/if}
|
|
32
|
+
|
|
33
|
+
<form action={formAction} method="POST" class="lite-approval-form">
|
|
34
|
+
<div class="lite-form-row">
|
|
35
|
+
<label for="lite-rev-id" class="lite-label">复核人工号</label>
|
|
36
|
+
<input id="lite-rev-id" name="reviewerId" type="text" required class="lite-input" placeholder="输入工号" />
|
|
37
|
+
</div>
|
|
38
|
+
<div class="lite-form-row">
|
|
39
|
+
<label for="lite-rev-sec" class="lite-label">复核人口令/2FA</label>
|
|
40
|
+
<input id="lite-rev-sec" name="secret" type="password" required class="lite-input" placeholder="输入独立核验口令" />
|
|
41
|
+
</div>
|
|
42
|
+
<button type="submit" class="lite-approval-btn">确认双人联签并执行</button>
|
|
43
|
+
</form>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<style>
|
|
47
|
+
.lite-dual-approval { border: 1px solid #fbbbb0; background: #fffdfc; border-radius: 8px; padding: 20px; }
|
|
48
|
+
.lite-approval-badge { display: inline-block; font-size: 11px; font-weight: 600; color: #f53f3f; background: #ffece8; padding: 2px 8px; border-radius: 4px; margin-bottom: 8px; }
|
|
49
|
+
.lite-approval-title { font-size: 16px; font-weight: 700; color: #1d2129; margin: 0 0 6px 0; }
|
|
50
|
+
.lite-approval-desc { font-size: 12px; color: #86909c; margin: 0 0 16px 0; }
|
|
51
|
+
.lite-approval-form { display: flex; flex-direction: column; }
|
|
52
|
+
.lite-approval-form > * + * { margin-top: 12px; }
|
|
53
|
+
.lite-form-row { display: flex; flex-direction: column; }
|
|
54
|
+
.lite-form-row > * + * { margin-top: 4px; }
|
|
55
|
+
.lite-label { font-size: 12px; font-weight: 600; color: #4e5969; }
|
|
56
|
+
.lite-input { height: 32px; border: 1px solid #e5e6eb; border-radius: 4px; padding: 0 10px; font-size: 12px; }
|
|
57
|
+
.lite-approval-btn { height: 34px; background: #f53f3f; color: #fff; border: none; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer; }
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
actionTitle: string;
|
|
4
|
+
requiredRole?: string;
|
|
5
|
+
formAction?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
declare const LiteDualApprovalGate: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type LiteDualApprovalGate = ReturnType<typeof LiteDualApprovalGate>;
|
|
11
|
+
export default LiteDualApprovalGate;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
data: unknown;
|
|
4
|
+
initialDepth?: number;
|
|
5
|
+
title?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
data,
|
|
11
|
+
initialDepth = 2,
|
|
12
|
+
title,
|
|
13
|
+
class: className = '',
|
|
14
|
+
}: Props = $props();
|
|
15
|
+
|
|
16
|
+
const formattedJson = $derived.by(() => {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(data, null, 2);
|
|
19
|
+
} catch {
|
|
20
|
+
return String(data);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const itemCount = $derived.by(() => {
|
|
25
|
+
if (typeof data === 'object' && data !== null) {
|
|
26
|
+
return Array.isArray(data) ? `${data.length} items` : `${Object.keys(data).length} keys`;
|
|
27
|
+
}
|
|
28
|
+
return typeof data;
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div
|
|
33
|
+
class="lite-json-viewer {className}"
|
|
34
|
+
data-svadmin-component="LiteJsonViewer"
|
|
35
|
+
data-initial-depth={initialDepth}
|
|
36
|
+
role="region"
|
|
37
|
+
aria-label={title ?? 'JSON Viewer'}
|
|
38
|
+
>
|
|
39
|
+
{#if title}
|
|
40
|
+
<header class="lite-json-header">
|
|
41
|
+
<h4 class="lite-json-title">{title}</h4>
|
|
42
|
+
<span class="lite-json-meta">{itemCount}</span>
|
|
43
|
+
</header>
|
|
44
|
+
{/if}
|
|
45
|
+
|
|
46
|
+
<div class="lite-json-body">
|
|
47
|
+
<div class="lite-json-details">
|
|
48
|
+
<div class="lite-json-summary">
|
|
49
|
+
<span>Payload Tree ({itemCount})</span>
|
|
50
|
+
</div>
|
|
51
|
+
<pre class="lite-json-code"><code>{formattedJson}</code></pre>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.lite-json-viewer {
|
|
58
|
+
border: 1px solid #e5e6eb;
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
background: #f7f8fa;
|
|
61
|
+
font-family: monospace;
|
|
62
|
+
font-size: 12px;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
.lite-json-header {
|
|
66
|
+
display: flex;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
align-items: center;
|
|
69
|
+
padding: 8px 12px;
|
|
70
|
+
border-bottom: 1px solid #e5e6eb;
|
|
71
|
+
background: #ffffff;
|
|
72
|
+
}
|
|
73
|
+
.lite-json-title { margin: 0; font-size: 13px; font-weight: 600; color: #1d2129; }
|
|
74
|
+
.lite-json-meta { font-size: 11px; color: #86909c; }
|
|
75
|
+
.lite-json-body { padding: 10px 12px; max-height: 400px; overflow: auto; }
|
|
76
|
+
.lite-json-summary { cursor: pointer; color: #165dff; font-weight: 500; margin-bottom: 6px; outline: none; }
|
|
77
|
+
.lite-json-code {
|
|
78
|
+
margin: 0;
|
|
79
|
+
padding: 8px;
|
|
80
|
+
background: #ffffff;
|
|
81
|
+
border: 1px solid #e5e6eb;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
color: #1d2129;
|
|
84
|
+
white-space: pre-wrap;
|
|
85
|
+
word-break: break-all;
|
|
86
|
+
line-height: 1.5;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
data: unknown;
|
|
3
|
+
initialDepth?: number;
|
|
4
|
+
title?: string;
|
|
5
|
+
class?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const LiteJsonViewer: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type LiteJsonViewer = ReturnType<typeof LiteJsonViewer>;
|
|
9
|
+
export default LiteJsonViewer;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface LiteShortcutItem {
|
|
3
|
+
label: string;
|
|
4
|
+
keys: string[];
|
|
5
|
+
category?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
shortcuts?: LiteShortcutItem[];
|
|
10
|
+
title?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
shortcuts = [
|
|
16
|
+
{ label: '全局搜索 / 命令面板', keys: ['Ctrl', 'K'], category: '导航' },
|
|
17
|
+
{ label: '保存当前表单', keys: ['Ctrl', 'S'], category: '编辑' },
|
|
18
|
+
{ label: '打开快捷键向导', keys: ['?'], category: '系统' },
|
|
19
|
+
],
|
|
20
|
+
title = '快捷键总览',
|
|
21
|
+
class: className = '',
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
class="lite-shortcuts-card {className}"
|
|
27
|
+
data-svadmin-component="LiteKeyboardShortcutsModal"
|
|
28
|
+
role="region"
|
|
29
|
+
aria-label={title}
|
|
30
|
+
>
|
|
31
|
+
<h4 class="lite-shortcuts-title">{title}</h4>
|
|
32
|
+
<table class="lite-shortcuts-table">
|
|
33
|
+
<tbody>
|
|
34
|
+
{#each shortcuts as item, idx (idx)}
|
|
35
|
+
<tr class="lite-shortcut-row">
|
|
36
|
+
<td class="lite-shortcut-label">{item.label}</td>
|
|
37
|
+
<td class="lite-shortcut-keys">
|
|
38
|
+
{#each item.keys as k, kIdx (kIdx)}
|
|
39
|
+
<kbd class="lite-kbd">{k}</kbd>
|
|
40
|
+
{/each}
|
|
41
|
+
</td>
|
|
42
|
+
</tr>
|
|
43
|
+
{/each}
|
|
44
|
+
</tbody>
|
|
45
|
+
</table>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.lite-shortcuts-card { border: 1px solid #e5e6eb; border-radius: 8px; background: #ffffff; padding: 16px; }
|
|
50
|
+
.lite-shortcuts-title { font-size: 14px; font-weight: 600; margin: 0 0 12px 0; color: #1d2129; }
|
|
51
|
+
.lite-shortcuts-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
52
|
+
.lite-shortcut-row td { padding: 6px 8px; border-bottom: 1px solid #f2f3f5; }
|
|
53
|
+
.lite-shortcut-label { color: #1d2129; font-weight: 500; }
|
|
54
|
+
.lite-shortcut-keys { text-align: right; }
|
|
55
|
+
.lite-kbd { font-family: monospace; font-size: 11px; background: #f2f3f5; border: 1px solid #e5e6eb; border-radius: 4px; padding: 2px 6px; color: #4e5969; margin-left: 4px; }
|
|
56
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface LiteShortcutItem {
|
|
2
|
+
label: string;
|
|
3
|
+
keys: string[];
|
|
4
|
+
category?: string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
shortcuts?: LiteShortcutItem[];
|
|
8
|
+
title?: string;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const LiteKeyboardShortcutsModal: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type LiteKeyboardShortcutsModal = ReturnType<typeof LiteKeyboardShortcutsModal>;
|
|
13
|
+
export default LiteKeyboardShortcutsModal;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface TargetProgress {
|
|
5
|
+
current: number;
|
|
6
|
+
target: number;
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
title: string;
|
|
12
|
+
value: number | string;
|
|
13
|
+
prefix?: string;
|
|
14
|
+
suffix?: string;
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
trendText?: string;
|
|
17
|
+
trendDirection?: 'up' | 'down' | 'neutral';
|
|
18
|
+
targetProgress?: TargetProgress;
|
|
19
|
+
sparklineData?: number[];
|
|
20
|
+
alertStatus?: 'normal' | 'warning' | 'danger';
|
|
21
|
+
alertMessage?: string;
|
|
22
|
+
class?: string;
|
|
23
|
+
actions?: Snippet;
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
title,
|
|
29
|
+
value,
|
|
30
|
+
prefix = '',
|
|
31
|
+
suffix = '',
|
|
32
|
+
subtitle,
|
|
33
|
+
trendText,
|
|
34
|
+
trendDirection = 'neutral',
|
|
35
|
+
targetProgress,
|
|
36
|
+
sparklineData,
|
|
37
|
+
alertStatus = 'normal',
|
|
38
|
+
alertMessage,
|
|
39
|
+
class: className = '',
|
|
40
|
+
actions,
|
|
41
|
+
children,
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
const formattedValue = $derived(
|
|
45
|
+
typeof value === 'number' ? value.toLocaleString('en-US') : String(value)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const progressPercent = $derived.by(() => {
|
|
49
|
+
if (!targetProgress || targetProgress.target <= 0) return 0;
|
|
50
|
+
const pct = (targetProgress.current / targetProgress.target) * 100;
|
|
51
|
+
return Math.max(0, Math.min(100, Math.round(pct)));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const sparklinePoints = $derived.by(() => {
|
|
55
|
+
if (!sparklineData || sparklineData.length < 2) return '';
|
|
56
|
+
const min = Math.min(...sparklineData);
|
|
57
|
+
const max = Math.max(...sparklineData);
|
|
58
|
+
const range = max - min || 1;
|
|
59
|
+
const width = 100;
|
|
60
|
+
const height = 30;
|
|
61
|
+
const padding = 2;
|
|
62
|
+
|
|
63
|
+
return sparklineData
|
|
64
|
+
.map((val, idx) => {
|
|
65
|
+
const x = padding + (idx / (sparklineData.length - 1)) * (width - padding * 2);
|
|
66
|
+
const y = height - padding - ((val - min) / range) * (height - padding * 2);
|
|
67
|
+
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
|
68
|
+
})
|
|
69
|
+
.join(' ');
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<div
|
|
74
|
+
class="lite-kpi-card lite-kpi-card-{alertStatus} {className}"
|
|
75
|
+
data-svadmin-component="LiteKpiCard"
|
|
76
|
+
role="region"
|
|
77
|
+
aria-label={title}
|
|
78
|
+
>
|
|
79
|
+
<header class="lite-kpi-card-header">
|
|
80
|
+
<div class="lite-kpi-card-title-group">
|
|
81
|
+
<h4 class="lite-kpi-card-title">{title}</h4>
|
|
82
|
+
{#if subtitle}
|
|
83
|
+
<span class="lite-kpi-card-subtitle">{subtitle}</span>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
{#if actions}
|
|
87
|
+
<div class="lite-kpi-card-actions">{@render actions()}</div>
|
|
88
|
+
{/if}
|
|
89
|
+
</header>
|
|
90
|
+
|
|
91
|
+
<div class="lite-kpi-card-body">
|
|
92
|
+
<div class="lite-kpi-card-metric">
|
|
93
|
+
{#if prefix}<span class="lite-kpi-card-prefix">{prefix}</span>{/if}
|
|
94
|
+
<span class="lite-kpi-card-value">{formattedValue}</span>
|
|
95
|
+
{#if suffix}<span class="lite-kpi-card-suffix">{suffix}</span>{/if}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{#if trendText}
|
|
99
|
+
<div class="lite-kpi-card-trend lite-kpi-card-trend-{trendDirection}">
|
|
100
|
+
{#if trendDirection === 'up'}↑{:else if trendDirection === 'down'}↓{/if}
|
|
101
|
+
<span>{trendText}</span>
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
105
|
+
{#if sparklinePoints}
|
|
106
|
+
<div class="lite-kpi-card-sparkline">
|
|
107
|
+
<svg viewBox="0 0 100 30" class="lite-kpi-sparkline-svg" aria-hidden="true">
|
|
108
|
+
<polyline points={sparklinePoints} fill="none" stroke="currentColor" stroke-width="2" />
|
|
109
|
+
</svg>
|
|
110
|
+
</div>
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{#if targetProgress}
|
|
115
|
+
<div class="lite-kpi-card-progress">
|
|
116
|
+
<div class="lite-kpi-progress-label">
|
|
117
|
+
<span>{targetProgress.label ?? 'Target progress'}</span>
|
|
118
|
+
<span class="lite-kpi-progress-pct">{progressPercent}%</span>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="lite-kpi-progress-track">
|
|
121
|
+
<div class="lite-kpi-progress-bar" style="width: {progressPercent}%;"></div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
125
|
+
|
|
126
|
+
{#if alertMessage && alertStatus !== 'normal'}
|
|
127
|
+
<div class="lite-kpi-card-alert lite-kpi-alert-{alertStatus}" role="alert">
|
|
128
|
+
<span>{alertMessage}</span>
|
|
129
|
+
</div>
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
{#if children}
|
|
133
|
+
<div class="lite-kpi-card-extra">{@render children()}</div>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<style>
|
|
138
|
+
.lite-kpi-card {
|
|
139
|
+
border: 1px solid #e5e6eb;
|
|
140
|
+
border-radius: 8px;
|
|
141
|
+
padding: 16px;
|
|
142
|
+
background: #ffffff;
|
|
143
|
+
box-sizing: border-box;
|
|
144
|
+
}
|
|
145
|
+
.lite-kpi-card-warning { border-color: #ffc766; background: #fffdf5; }
|
|
146
|
+
.lite-kpi-card-danger { border-color: #ffd8d2; background: #fff7f6; }
|
|
147
|
+
.lite-kpi-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; }
|
|
148
|
+
.lite-kpi-card-title { margin: 0; font-size: 13px; font-weight: 600; color: #4e5969; }
|
|
149
|
+
.lite-kpi-card-subtitle { font-size: 11px; color: #86909c; display: block; margin-top: 2px; }
|
|
150
|
+
.lite-kpi-card-body { display: flex; align-items: baseline; flex-wrap: wrap; margin-bottom: 8px; }
|
|
151
|
+
.lite-kpi-card-body > * + * { margin-left: 8px; }
|
|
152
|
+
.lite-kpi-card-metric { font-family: monospace; font-size: 24px; font-weight: 700; color: #1d2129; }
|
|
153
|
+
.lite-kpi-card-prefix, .lite-kpi-card-suffix { font-size: 13px; font-weight: 400; color: #86909c; margin: 0 2px; }
|
|
154
|
+
.lite-kpi-card-trend { font-size: 11px; font-weight: 600; padding: 2px 6px; border-radius: 4px; display: inline-flex; align-items: center; }
|
|
155
|
+
.lite-kpi-card-trend > * + * { margin-left: 2px; }
|
|
156
|
+
.lite-kpi-card-trend-up { color: #00b42a; background: #e8ffea; }
|
|
157
|
+
.lite-kpi-card-trend-down { color: #f53f3f; background: #ffece8; }
|
|
158
|
+
.lite-kpi-card-trend-neutral { color: #4e5969; background: #f2f3f5; }
|
|
159
|
+
.lite-kpi-card-sparkline { margin-left: auto; width: 80px; height: 24px; color: #165dff; }
|
|
160
|
+
.lite-kpi-sparkline-svg { width: 100%; height: 100%; display: block; }
|
|
161
|
+
.lite-kpi-card-progress { margin-top: 8px; }
|
|
162
|
+
.lite-kpi-progress-label { display: flex; justify-content: space-between; font-size: 11px; color: #86909c; margin-bottom: 4px; }
|
|
163
|
+
.lite-kpi-progress-track { height: 4px; background: #f2f3f5; border-radius: 2px; overflow: hidden; }
|
|
164
|
+
.lite-kpi-progress-bar { height: 100%; background: #165dff; border-radius: 2px; }
|
|
165
|
+
.lite-kpi-card-alert { margin-top: 10px; padding: 6px 10px; border-radius: 4px; font-size: 12px; }
|
|
166
|
+
.lite-kpi-alert-warning { background: #fff7e8; color: #d46b08; border: 1px solid #ffd591; }
|
|
167
|
+
.lite-kpi-alert-danger { background: #fff1f0; color: #cf1322; border: 1px solid #ffa39e; }
|
|
168
|
+
.lite-kpi-card-extra { margin-top: 10px; border-top: 1px solid #f2f3f5; padding-top: 8px; }
|
|
169
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface TargetProgress {
|
|
3
|
+
current: number;
|
|
4
|
+
target: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
interface Props {
|
|
8
|
+
title: string;
|
|
9
|
+
value: number | string;
|
|
10
|
+
prefix?: string;
|
|
11
|
+
suffix?: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
trendText?: string;
|
|
14
|
+
trendDirection?: 'up' | 'down' | 'neutral';
|
|
15
|
+
targetProgress?: TargetProgress;
|
|
16
|
+
sparklineData?: number[];
|
|
17
|
+
alertStatus?: 'normal' | 'warning' | 'danger';
|
|
18
|
+
alertMessage?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
actions?: Snippet;
|
|
21
|
+
children?: Snippet;
|
|
22
|
+
}
|
|
23
|
+
declare const LiteKpiCard: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type LiteKpiCard = ReturnType<typeof LiteKpiCard>;
|
|
25
|
+
export default LiteKpiCard;
|