@shwfed/config 2.12.6 → 2.12.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.
- package/dist/module.json +1 -1
- package/dist/runtime/components/config/blocks/2026-06-17/com.shwfed.block.tree.single/runtime.vue +54 -50
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts +2 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue +38 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts +2 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/runtime.vue +11 -2
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.d.ts +3 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.js +7 -0
- package/dist/runtime/components/form/fields/2026-05-23/com.shwfed.form.field.tree.multi/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-23/com.shwfed.form.field.tree.multi/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.multi/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.multi/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.single/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-26/com.shwfed.form.field.tree.combobox.single/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.multi/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.combobox.single/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.multi/config.vue.d.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/config.d.vue.ts +18 -18
- package/dist/runtime/components/form/fields/2026-05-28/com.shwfed.form.field.tree.single/config.vue.d.ts +18 -18
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/runtime.vue +6 -73
- package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.number-input/config.vue +44 -0
- package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.number-input/runtime.vue +8 -2
- package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.number-input/schema.d.ts +3 -0
- package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.number-input/schema.js +7 -0
- package/dist/runtime/components/table/index.vue +180 -168
- package/dist/runtime/share/number-format.d.ts +7 -0
- package/dist/runtime/share/number-format.js +72 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { cel as _rawCel } from "../../../../../utils/cel";
|
|
|
7
7
|
import { celScope, injectCELContext } from "../../../../../utils/cel-context";
|
|
8
8
|
import { useFormReadonly } from "../../../../form/utils/readonly";
|
|
9
9
|
import { getLocalizedText } from "../../../../../share/locale";
|
|
10
|
+
import { formatNumberDisplay } from "../../../../../share/number-format";
|
|
10
11
|
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupNumberField } from "../../../../ui/input-group";
|
|
11
12
|
import ShwfedActions from "../../../../actions/components/group.vue";
|
|
12
13
|
import { useFieldValue } from "../../../../form/utils/field-value";
|
|
@@ -70,6 +71,11 @@ const { draft, commit } = useFieldValue({
|
|
|
70
71
|
return props.column.valueAsString ? String(next) : next;
|
|
71
72
|
}
|
|
72
73
|
});
|
|
74
|
+
const readonlyDisplay = computed(() => formatNumberDisplay(draft.value, {
|
|
75
|
+
displayMode: props.column.displayMode,
|
|
76
|
+
precision: props.column.precision,
|
|
77
|
+
roundingMode: props.column.roundingMode
|
|
78
|
+
}));
|
|
73
79
|
function applyRounding(n, precision, mode) {
|
|
74
80
|
const factor = 10 ** precision;
|
|
75
81
|
switch (mode) {
|
|
@@ -133,10 +139,10 @@ async function onBlur() {
|
|
|
133
139
|
v-if="effectiveReadonly"
|
|
134
140
|
:class="[
|
|
135
141
|
'flex items-center h-7 w-full px-2 text-[0.75rem] truncate',
|
|
136
|
-
draft !== void 0 ? ['text-zinc-700', justifyClass] : 'justify-center font-mono text-zinc-300 select-none'
|
|
142
|
+
draft !== void 0 ? ['text-zinc-700', justifyClass, column.displayMode === 'monoThousand' && 'font-mono'] : 'justify-center font-mono text-zinc-300 select-none'
|
|
137
143
|
]"
|
|
138
144
|
>
|
|
139
|
-
{{
|
|
145
|
+
{{ readonlyDisplay }}
|
|
140
146
|
</span>
|
|
141
147
|
<InputGroup
|
|
142
148
|
v-else
|
|
@@ -33,6 +33,9 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
|
|
|
33
33
|
valueAsString: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
34
34
|
min: Schema.optional<Schema.Schema<string, string, never>>;
|
|
35
35
|
max: Schema.optional<Schema.Schema<string, string, never>>;
|
|
36
|
+
displayMode: Schema.optionalWith<Schema.Literal<["plain", "uppercaseChinese", "monoThousand"]>, {
|
|
37
|
+
default: () => "plain";
|
|
38
|
+
}>;
|
|
36
39
|
derived: Schema.optional<Schema.Struct<{
|
|
37
40
|
mode: Schema.Literal<["formula", "prefill"]>;
|
|
38
41
|
expression: Schema.Schema<string, string, never>;
|
|
@@ -68,6 +68,13 @@ export function schema(configure) {
|
|
|
68
68
|
title: "\u6700\u5927\u503C",
|
|
69
69
|
description: "\u5141\u8BB8\u8F93\u5165\u7684\u6700\u5927\u503C\u8868\u8FBE\u5F0F\uFF0C\u53EF\u8BBF\u95EE `row` / `index` \u548C `form`\uFF1B\u7559\u7A7A\u65F6\u4E0D\u9650\u5236\u4E0A\u9650"
|
|
70
70
|
})),
|
|
71
|
+
displayMode: Schema.optionalWith(
|
|
72
|
+
Schema.Literal("plain", "uppercaseChinese", "monoThousand").annotations({
|
|
73
|
+
title: "\u6A21\u5F0F",
|
|
74
|
+
description: "\u53EA\u8BFB\u5C55\u793A\u65F6\u7684\u6A21\u5F0F\uFF1A\u6570\u503C\uFF08\u539F\u6837\uFF09\u3001\u4E2D\u6587\u5927\u5199\uFF08\u5982 4200 \u2192 `\u8086\u4EDF\u8D30\u4F70\u96F6\u62FE`\uFF09\u3001\u7B49\u5BBD\u5B57\u4F53\u52A0\u5343\u5206\u4F4D\uFF08\u5982 `4,200`\uFF09\uFF1B\u7F16\u8F91\u6001\u4E0D\u53D7\u5F71\u54CD"
|
|
75
|
+
}),
|
|
76
|
+
{ default: () => "plain" }
|
|
77
|
+
),
|
|
71
78
|
derived: derivedRowField(configure, "number"),
|
|
72
79
|
// `Schema.optional` rebuilds the struct's union member, and because the
|
|
73
80
|
// `optionalWith`-default fields make `AddonActions` a `Transformation`, the
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { useVirtualizer } from "@tanstack/vue-virtual";
|
|
13
13
|
import { Effect, Fiber, Option } from "effect";
|
|
14
14
|
import { Fetch } from "fx-fetch";
|
|
15
|
-
import { Pagination } from "reka-ui/namespaced";
|
|
15
|
+
import { Pagination, ScrollArea } from "reka-ui/namespaced";
|
|
16
16
|
import { computed, h, onMounted, ref, toRaw, watch } from "vue";
|
|
17
17
|
import { fetchJsonOption } from "../../share/request";
|
|
18
18
|
import { useI18n } from "vue-i18n";
|
|
@@ -24,6 +24,7 @@ import { Markdown } from "../ui/markdown";
|
|
|
24
24
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
|
25
25
|
import { NumberField, NumberFieldInput } from "../ui/number-field";
|
|
26
26
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
27
|
+
import { ScrollBar } from "../ui/scroll-area";
|
|
27
28
|
import ShwfedActions from "../actions/components/group.vue";
|
|
28
29
|
import ShwfedForm from "../form/index.vue";
|
|
29
30
|
import { evaluateInitial } from "../form/utils/initial";
|
|
@@ -105,7 +106,8 @@ const { t, locale } = useI18n({
|
|
|
105
106
|
}
|
|
106
107
|
});
|
|
107
108
|
const inheritedContext = injectCELContext();
|
|
108
|
-
const
|
|
109
|
+
const viewportRef = ref(null);
|
|
110
|
+
const containerRef = computed(() => viewportRef.value?.viewportElement ?? null);
|
|
109
111
|
const appliedInitialStateKey = ref("");
|
|
110
112
|
const frozenGrowColumns = ref(/* @__PURE__ */ new Set());
|
|
111
113
|
function isEffectiveGrow(column) {
|
|
@@ -788,29 +790,35 @@ export { TableConfig, createTableConfig, getColumnTechnicalKey } from "./schema"
|
|
|
788
790
|
@reset="resetCustomization"
|
|
789
791
|
/>
|
|
790
792
|
<ClientOnly>
|
|
791
|
-
<!-- Scrollable table container
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
793
|
+
<!-- Scrollable table container: reka-ui ScrollArea replaces the native
|
|
794
|
+
scrollbar. The Viewport is the real scroll element (both axes, since
|
|
795
|
+
both ScrollBars are mounted) and the sticky thead / pinned columns
|
|
796
|
+
resolve against it. -->
|
|
797
|
+
<ScrollArea.Root
|
|
798
|
+
class="flex-1 min-h-0 border border-zinc-200 rounded relative overflow-hidden"
|
|
795
799
|
>
|
|
796
|
-
<
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
<
|
|
804
|
-
v-
|
|
805
|
-
|
|
806
|
-
class="flex w-full border-b border-zinc-200"
|
|
800
|
+
<ScrollArea.Viewport
|
|
801
|
+
ref="viewportRef"
|
|
802
|
+
class="size-full overscroll-none"
|
|
803
|
+
>
|
|
804
|
+
<div :style="{ height: `${rowTotalSize}px` }">
|
|
805
|
+
<table class="grid">
|
|
806
|
+
<!-- Header -->
|
|
807
|
+
<thead
|
|
808
|
+
v-if="columns.length > 0"
|
|
809
|
+
class="grid sticky top-0 z-10 select-none"
|
|
807
810
|
>
|
|
808
|
-
<
|
|
809
|
-
v-for="
|
|
810
|
-
:key="
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
811
|
+
<tr
|
|
812
|
+
v-for="group in tableApi.getHeaderGroups()"
|
|
813
|
+
:key="group.id"
|
|
814
|
+
class="flex w-full border-b border-zinc-200"
|
|
815
|
+
>
|
|
816
|
+
<th
|
|
817
|
+
v-for="header in group.headers"
|
|
818
|
+
:key="header.id"
|
|
819
|
+
:colspan="header.colSpan"
|
|
820
|
+
:data-col-id="header.column.id"
|
|
821
|
+
:class="[
|
|
814
822
|
'flex items-center gap-2 border-zinc-300 py-2 text-zinc-600',
|
|
815
823
|
'text-xs flex items-center justify-center relative',
|
|
816
824
|
'bg-[color-mix(in_srgb,var(--primary)_7%,white)] group',
|
|
@@ -818,7 +826,7 @@ export { TableConfig, createTableConfig, getColumnTechnicalKey } from "./schema"
|
|
|
818
826
|
shouldHaveRightBorder(header.column) && 'border-r',
|
|
819
827
|
shouldHaveLeftBorder(header.column) && 'border-l'
|
|
820
828
|
]"
|
|
821
|
-
|
|
829
|
+
:style="{
|
|
822
830
|
// Grow until first resize (see freezeGrowColumns): fill the
|
|
823
831
|
// remaining space, with `width` as the flex-basis so the
|
|
824
832
|
// configured size acts as a floor. Group headers grow by
|
|
@@ -827,82 +835,82 @@ export { TableConfig, createTableConfig, getColumnTechnicalKey } from "./schema"
|
|
|
827
835
|
...headerGrowStyle(header),
|
|
828
836
|
...pinnedStyle(header.column)
|
|
829
837
|
}"
|
|
830
|
-
>
|
|
831
|
-
<!-- Column header with tooltip: dashed underline + Markdown tooltip -->
|
|
832
|
-
<Tooltip
|
|
833
|
-
v-if="!header.isPlaceholder && header.column.columnDef.meta?.tooltip"
|
|
834
|
-
:delay-duration="180"
|
|
835
838
|
>
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
:props="header.getContext()"
|
|
841
|
-
/>
|
|
842
|
-
</span>
|
|
843
|
-
</TooltipTrigger>
|
|
844
|
-
<TooltipContent
|
|
845
|
-
align="center"
|
|
846
|
-
side="top"
|
|
847
|
-
class="max-w-xs text-xs"
|
|
839
|
+
<!-- Column header with tooltip: dashed underline + Markdown tooltip -->
|
|
840
|
+
<Tooltip
|
|
841
|
+
v-if="!header.isPlaceholder && header.column.columnDef.meta?.tooltip"
|
|
842
|
+
:delay-duration="180"
|
|
848
843
|
>
|
|
849
|
-
<
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
844
|
+
<TooltipTrigger as-child>
|
|
845
|
+
<span class="decoration-dashed underline decoration-(--primary) underline-offset-4 px-4 cursor-help">
|
|
846
|
+
<FlexRender
|
|
847
|
+
:render="header.column.columnDef.header"
|
|
848
|
+
:props="header.getContext()"
|
|
849
|
+
/>
|
|
850
|
+
</span>
|
|
851
|
+
</TooltipTrigger>
|
|
852
|
+
<TooltipContent
|
|
853
|
+
align="center"
|
|
854
|
+
side="top"
|
|
855
|
+
class="max-w-xs text-xs"
|
|
856
|
+
>
|
|
857
|
+
<Markdown
|
|
858
|
+
:source="header.column.columnDef.meta.tooltip"
|
|
859
|
+
class="prose prose-xs prose-zinc"
|
|
860
|
+
/>
|
|
861
|
+
</TooltipContent>
|
|
862
|
+
</Tooltip>
|
|
855
863
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
864
|
+
<!-- Column header without tooltip: plain render -->
|
|
865
|
+
<FlexRender
|
|
866
|
+
v-else-if="!header.isPlaceholder"
|
|
867
|
+
:render="header.column.columnDef.header"
|
|
868
|
+
:props="header.getContext()"
|
|
869
|
+
/>
|
|
862
870
|
|
|
863
|
-
|
|
871
|
+
<!-- Edit affordance: marks an editable column, hidden when
|
|
864
872
|
the column reads as readonly (form-wide or its own
|
|
865
873
|
`readonly` condition). -->
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
874
|
+
<Icon
|
|
875
|
+
v-if="!header.isPlaceholder && header.column.columnDef.meta?.editable && !isHeaderReadonly(header.column.columnDef.meta?.readonlyExpr)"
|
|
876
|
+
icon="fluent:edit-20-regular"
|
|
877
|
+
class="size-3 shrink-0 text-zinc-400"
|
|
878
|
+
aria-hidden="true"
|
|
879
|
+
/>
|
|
872
880
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
881
|
+
<!-- Sort toggle -->
|
|
882
|
+
<Tooltip
|
|
883
|
+
v-if="!header.isPlaceholder && header.column.getCanSort()"
|
|
884
|
+
:delay-duration="800"
|
|
885
|
+
>
|
|
886
|
+
<TooltipTrigger as-child>
|
|
887
|
+
<Button
|
|
888
|
+
variant="ghost"
|
|
889
|
+
size="xs"
|
|
890
|
+
:class="[
|
|
883
891
|
'absolute hover:bg-transparent right-1 top-1/2 -translate-y-1/2 transform-3d transition-opacity duration-180',
|
|
884
892
|
!header.column.getIsSorted() ? 'opacity-30 hover:opacity-60' : 'text-(--primary)/80 hover:text-(--primary)'
|
|
885
893
|
]"
|
|
886
|
-
|
|
887
|
-
>
|
|
888
|
-
<button
|
|
889
|
-
type="button"
|
|
890
|
-
:aria-label="getSortLabel(header.column)"
|
|
891
|
-
@click="header.column.getToggleSortingHandler()?.($event)"
|
|
894
|
+
as-child
|
|
892
895
|
>
|
|
893
|
-
<
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
896
|
+
<button
|
|
897
|
+
type="button"
|
|
898
|
+
:aria-label="getSortLabel(header.column)"
|
|
899
|
+
@click="header.column.getToggleSortingHandler()?.($event)"
|
|
900
|
+
>
|
|
901
|
+
<Icon :icon="getSortIcon(header.column)" />
|
|
902
|
+
</button>
|
|
903
|
+
</Button>
|
|
904
|
+
</TooltipTrigger>
|
|
905
|
+
<TooltipContent>
|
|
906
|
+
{{ getSortLabel(header.column) }}
|
|
907
|
+
</TooltipContent>
|
|
908
|
+
</Tooltip>
|
|
901
909
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
910
|
+
<!-- Resize handle -->
|
|
911
|
+
<div
|
|
912
|
+
v-if="!header.isPlaceholder && header.column.getCanResize() && shouldShowResizeHandle(header.column)"
|
|
913
|
+
:class="[
|
|
906
914
|
'group',
|
|
907
915
|
'absolute',
|
|
908
916
|
'top-0',
|
|
@@ -918,40 +926,40 @@ export { TableConfig, createTableConfig, getColumnTechnicalKey } from "./schema"
|
|
|
918
926
|
'hover:opacity-100',
|
|
919
927
|
'cursor-col-resize'
|
|
920
928
|
]"
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
929
|
+
@mousedown="onResizeMouseDown(header, $event)"
|
|
930
|
+
>
|
|
931
|
+
<div class="w-2pt h-full translate-x-1pt transform-3d bg-[color-mix(in_srgb,var(--primary)_80%,white)]" />
|
|
932
|
+
</div>
|
|
933
|
+
</th>
|
|
934
|
+
</tr>
|
|
935
|
+
</thead>
|
|
928
936
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
>
|
|
935
|
-
<template
|
|
936
|
-
v-for="r in rowWindow"
|
|
937
|
-
:key="rows[r.index]?.id ?? r.index"
|
|
937
|
+
<!-- Body -->
|
|
938
|
+
<tbody
|
|
939
|
+
v-if="columns.length > 0"
|
|
940
|
+
class="grid relative"
|
|
941
|
+
:style="{ height: `${rowTotalSize}px` }"
|
|
938
942
|
>
|
|
939
|
-
<
|
|
940
|
-
v-
|
|
941
|
-
:
|
|
942
|
-
:display-index="getDisplayIndex(rows[r.index])"
|
|
943
|
-
:derived-columns="derivedEditableColumns"
|
|
944
|
-
:get-row="getRowAt"
|
|
945
|
-
:set-row="setRowAt"
|
|
946
|
-
:scope-id="config?.id"
|
|
943
|
+
<template
|
|
944
|
+
v-for="r in rowWindow"
|
|
945
|
+
:key="rows[r.index]?.id ?? r.index"
|
|
947
946
|
>
|
|
948
|
-
<
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
:
|
|
952
|
-
:
|
|
947
|
+
<TableRowProvider
|
|
948
|
+
v-if="rows[r.index]"
|
|
949
|
+
:source-index="rows[r.index].index"
|
|
950
|
+
:display-index="getDisplayIndex(rows[r.index])"
|
|
951
|
+
:derived-columns="derivedEditableColumns"
|
|
952
|
+
:get-row="getRowAt"
|
|
953
|
+
:set-row="setRowAt"
|
|
954
|
+
:scope-id="config?.id"
|
|
953
955
|
>
|
|
954
|
-
|
|
956
|
+
<tr
|
|
957
|
+
:ref="getRowRefFn(rows[r.index].id)"
|
|
958
|
+
class="flex absolute w-full border-b border-zinc-300"
|
|
959
|
+
:data-index="rows[r.index].index"
|
|
960
|
+
:style="{ transform: `translate3d(0, ${r.start}px, 0)` }"
|
|
961
|
+
>
|
|
962
|
+
<!--
|
|
955
963
|
Drop indicator. Absolutely positioned so it overlays
|
|
956
964
|
the row without affecting flex layout, and sits at
|
|
957
965
|
z-30 above sticky pinned cells (which use z-15) so
|
|
@@ -962,54 +970,54 @@ export { TableConfig, createTableConfig, getColumnTechnicalKey } from "./schema"
|
|
|
962
970
|
to the same pixel band, so the indicator doesn't
|
|
963
971
|
jitter as the hitbox flips between them.
|
|
964
972
|
-->
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
973
|
+
<span
|
|
974
|
+
v-if="rowReorder.instructionFor(rows[r.index].id) === 'reorder-above'"
|
|
975
|
+
aria-hidden="true"
|
|
976
|
+
class="absolute left-0 right-0 -top-px h-0.5 z-30 pointer-events-none bg-(--primary)"
|
|
977
|
+
/>
|
|
978
|
+
<span
|
|
979
|
+
v-else-if="rowReorder.instructionFor(rows[r.index].id) === 'reorder-below'"
|
|
980
|
+
aria-hidden="true"
|
|
981
|
+
class="absolute left-0 right-0 -bottom-px h-0.5 z-30 pointer-events-none bg-(--primary)"
|
|
982
|
+
/>
|
|
983
|
+
<td
|
|
984
|
+
v-for="cell in rows[r.index].getVisibleCells()"
|
|
985
|
+
:key="cell.id"
|
|
986
|
+
:class="[
|
|
979
987
|
'border-zinc-300',
|
|
980
988
|
isEffectiveGrow(cell.column) && 'flex-[1_1_auto]',
|
|
981
989
|
cell.column.getIsPinned() && 'sticky z-15',
|
|
982
990
|
shouldHaveRightBorder(cell.column) && 'border-r',
|
|
983
991
|
shouldHaveLeftBorder(cell.column) && 'border-l'
|
|
984
992
|
]"
|
|
985
|
-
|
|
993
|
+
:style="{
|
|
986
994
|
width: `${cell.column.getSize()}px`,
|
|
987
995
|
...pinnedStyle(cell.column),
|
|
988
996
|
...getCellStyles(cell.getContext())
|
|
989
997
|
}"
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
998
|
+
>
|
|
999
|
+
<FlexRender
|
|
1000
|
+
:render="cell.column.columnDef.cell"
|
|
1001
|
+
:props="cell.getContext()"
|
|
1002
|
+
/>
|
|
1003
|
+
</td>
|
|
1004
|
+
</tr>
|
|
1005
|
+
</TableRowProvider>
|
|
1006
|
+
</template>
|
|
1007
|
+
</tbody>
|
|
1000
1008
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1009
|
+
<!-- Footer -->
|
|
1010
|
+
<tfoot class="hidden has-data-footer:grid sticky bottom-0 z-10 select-none border-t border-zinc-200">
|
|
1011
|
+
<tr
|
|
1012
|
+
v-for="group in tableApi.getFooterGroups()"
|
|
1013
|
+
:key="group.id"
|
|
1014
|
+
class="flex w-full border-zinc-200"
|
|
1015
|
+
>
|
|
1016
|
+
<th
|
|
1017
|
+
v-for="header in group.headers"
|
|
1018
|
+
:key="header.id"
|
|
1019
|
+
:colspan="header.colSpan"
|
|
1020
|
+
:class="[
|
|
1013
1021
|
'flex items-center gap-2 border-zinc-300 text-zinc-600 p-0',
|
|
1014
1022
|
'text-xs flex items-center justify-center relative',
|
|
1015
1023
|
'bg-[color-mix(in_srgb,var(--primary)_7%,white)] group',
|
|
@@ -1017,23 +1025,27 @@ export { TableConfig, createTableConfig, getColumnTechnicalKey } from "./schema"
|
|
|
1017
1025
|
shouldHaveRightBorder(header.column) && 'border-r',
|
|
1018
1026
|
shouldHaveLeftBorder(header.column) && 'border-l'
|
|
1019
1027
|
]"
|
|
1020
|
-
|
|
1028
|
+
:style="{
|
|
1021
1029
|
width: `${header.getSize()}px`,
|
|
1022
1030
|
...headerGrowStyle(header),
|
|
1023
1031
|
...pinnedStyle(header.column)
|
|
1024
1032
|
}"
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1033
|
+
>
|
|
1034
|
+
<FlexRender
|
|
1035
|
+
v-if="!header.isPlaceholder"
|
|
1036
|
+
:render="header.column.columnDef.footer"
|
|
1037
|
+
:props="header.getContext()"
|
|
1038
|
+
/>
|
|
1039
|
+
</th>
|
|
1040
|
+
</tr>
|
|
1041
|
+
</tfoot>
|
|
1042
|
+
</table>
|
|
1043
|
+
</div>
|
|
1044
|
+
</ScrollArea.Viewport>
|
|
1045
|
+
<ScrollBar orientation="vertical" />
|
|
1046
|
+
<ScrollBar orientation="horizontal" />
|
|
1047
|
+
<ScrollArea.Corner />
|
|
1048
|
+
</ScrollArea.Root>
|
|
1037
1049
|
</ClientOnly>
|
|
1038
1050
|
|
|
1039
1051
|
<!-- Pagination -->
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type NumberDisplayMode = 'plain' | 'uppercaseChinese' | 'monoThousand';
|
|
2
|
+
export declare function toChineseUppercase(n: number): string;
|
|
3
|
+
export declare function formatNumberDisplay(value: unknown, opts: {
|
|
4
|
+
displayMode?: NumberDisplayMode;
|
|
5
|
+
precision?: number;
|
|
6
|
+
roundingMode?: 'round' | 'floor' | 'ceil';
|
|
7
|
+
}): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const CHINESE_DIGITS = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
|
|
2
|
+
const CHINESE_UNITS = ["", "\u62FE", "\u4F70", "\u4EDF"];
|
|
3
|
+
const CHINESE_GROUP_UNITS = ["", "\u4E07", "\u4EBF", "\u5146"];
|
|
4
|
+
export function toChineseUppercase(n) {
|
|
5
|
+
if (Number.isNaN(n)) return "-";
|
|
6
|
+
if (n === 0) return "\u96F6";
|
|
7
|
+
const isNegative = n < 0;
|
|
8
|
+
const absN = Math.abs(n);
|
|
9
|
+
const [intPart, decPart] = String(absN).split(".");
|
|
10
|
+
let result = "";
|
|
11
|
+
const digits = intPart.split("").reverse();
|
|
12
|
+
let zeroFlag = false;
|
|
13
|
+
for (let i = 0; i < digits.length; i++) {
|
|
14
|
+
const d = Number(digits[i]);
|
|
15
|
+
const groupIndex = Math.floor(i / 4);
|
|
16
|
+
const posInGroup = i % 4;
|
|
17
|
+
if (d === 0) {
|
|
18
|
+
zeroFlag = true;
|
|
19
|
+
} else {
|
|
20
|
+
if (zeroFlag) {
|
|
21
|
+
result = CHINESE_DIGITS[0] + result;
|
|
22
|
+
zeroFlag = false;
|
|
23
|
+
}
|
|
24
|
+
result = CHINESE_DIGITS[d] + CHINESE_UNITS[posInGroup] + result;
|
|
25
|
+
}
|
|
26
|
+
if (posInGroup === 0 && groupIndex > 0 && !zeroFlag) {
|
|
27
|
+
result = (CHINESE_GROUP_UNITS[groupIndex] ?? "") + result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (decPart) {
|
|
31
|
+
result += "\u70B9";
|
|
32
|
+
for (const ch of decPart) {
|
|
33
|
+
result += CHINESE_DIGITS[Number(ch)];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return isNegative ? `\u8D1F${result}` : result;
|
|
37
|
+
}
|
|
38
|
+
export function formatNumberDisplay(value, opts) {
|
|
39
|
+
if (value === void 0 || value === null) return "-";
|
|
40
|
+
const n = Number(value);
|
|
41
|
+
if (Number.isNaN(n)) return "-";
|
|
42
|
+
const displayMode = opts.displayMode ?? "plain";
|
|
43
|
+
if (displayMode === "uppercaseChinese") {
|
|
44
|
+
return toChineseUppercase(n);
|
|
45
|
+
}
|
|
46
|
+
let result;
|
|
47
|
+
if (opts.precision !== void 0) {
|
|
48
|
+
const mode = opts.roundingMode ?? "round";
|
|
49
|
+
const digits = opts.precision;
|
|
50
|
+
const factor = 10 ** digits;
|
|
51
|
+
let processed;
|
|
52
|
+
switch (mode) {
|
|
53
|
+
case "floor":
|
|
54
|
+
processed = Math.floor(n * factor) / factor;
|
|
55
|
+
break;
|
|
56
|
+
case "ceil":
|
|
57
|
+
processed = Math.ceil(n * factor) / factor;
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
processed = Math.round(n * factor) / factor;
|
|
61
|
+
}
|
|
62
|
+
result = processed.toFixed(digits);
|
|
63
|
+
} else {
|
|
64
|
+
result = String(n);
|
|
65
|
+
}
|
|
66
|
+
if (displayMode === "monoThousand") {
|
|
67
|
+
const [integer, decimal] = result.split(".");
|
|
68
|
+
const formattedInt = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
69
|
+
result = decimal !== void 0 ? `${formattedInt}.${decimal}` : formattedInt;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|