@shwfed/config 3.0.11 → 3.0.12

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 (48) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.d.vue.ts +86 -0
  3. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.vue +398 -0
  4. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.vue.d.ts +86 -0
  5. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/expr-field.d.vue.ts +14 -0
  6. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/expr-field.vue +39 -0
  7. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/expr-field.vue.d.ts +14 -0
  8. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/items-colgroup.d.vue.ts +3 -0
  9. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/items-colgroup.vue +20 -0
  10. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/items-colgroup.vue.d.ts +3 -0
  11. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/json-scope-provider.d.vue.ts +13 -0
  12. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/json-scope-provider.vue +19 -0
  13. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/json-scope-provider.vue.d.ts +13 -0
  14. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/rmb.d.ts +11 -0
  15. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/rmb.js +58 -0
  16. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/row-scope-provider.d.vue.ts +13 -0
  17. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/row-scope-provider.vue +22 -0
  18. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/row-scope-provider.vue.d.ts +13 -0
  19. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.d.vue.ts +86 -0
  20. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.vue +346 -0
  21. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.vue.d.ts +86 -0
  22. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/schema.d.ts +109 -0
  23. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/schema.js +189 -0
  24. package/dist/runtime/components/form/fields/2026-04-22/com.shwfed.form.field.textarea/config.d.vue.ts +2 -2
  25. package/dist/runtime/components/form/fields/2026-04-22/com.shwfed.form.field.textarea/config.vue.d.ts +2 -2
  26. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.combobox.single/config.d.vue.ts +16 -16
  27. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.combobox.single/config.vue.d.ts +16 -16
  28. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +6 -6
  29. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +6 -6
  30. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +8 -8
  31. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +8 -8
  32. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +2 -2
  33. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +2 -2
  34. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.d.vue.ts +16 -16
  35. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.vue.d.ts +16 -16
  36. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.d.vue.ts +6 -6
  37. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.vue.d.ts +6 -6
  38. package/dist/runtime/components/form/fields/2026-05-25/com.shwfed.form.field.combobox.multi/config.d.vue.ts +16 -16
  39. package/dist/runtime/components/form/fields/2026-05-25/com.shwfed.form.field.combobox.multi/config.vue.d.ts +16 -16
  40. package/dist/runtime/components/ui/date-picker/DatePickerInput.d.vue.ts +1 -1
  41. package/dist/runtime/components/ui/date-picker/DatePickerInput.vue.d.ts +1 -1
  42. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.d.vue.ts +1 -1
  43. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.vue.d.ts +1 -1
  44. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.d.vue.ts +1 -1
  45. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.vue.d.ts +1 -1
  46. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.d.vue.ts +2 -2
  47. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.vue.d.ts +2 -2
  48. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,13 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
9
+ type __VLS_WithSlots<T, S> = T & {
10
+ new (): {
11
+ $slots: S;
12
+ };
13
+ };
@@ -0,0 +1,19 @@
1
+ <script setup>
2
+ import { injectCELContext, provideCELContext } from "../../../../../utils/cel-context";
3
+ import { JSON_VAR } from "./schema";
4
+ defineOptions({ name: "ShwfedBlockInvoiceDigitalJsonScopeProvider" });
5
+ if (!injectCELContext().json) {
6
+ provideCELContext({
7
+ json: {
8
+ type: JSON_VAR.type,
9
+ label: JSON_VAR.label,
10
+ description: JSON_VAR.description,
11
+ value: void 0
12
+ }
13
+ });
14
+ }
15
+ </script>
16
+
17
+ <template>
18
+ <slot />
19
+ </template>
@@ -0,0 +1,13 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
9
+ type __VLS_WithSlots<T, S> = T & {
10
+ new (): {
11
+ $slots: S;
12
+ };
13
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 人民币金额小写 → 中文大写(票面「价税合计(大写)」栏)。
3
+ *
4
+ * 规则是国标固定的,所以在块内本地推导而不是多开一个配置字段 —— 大写与小写永远
5
+ * 不会不一致。负数(红字发票)走「(负数)」前缀,与税务总局版式一致。
6
+ */
7
+ /**
8
+ * 金额 → 中文大写。`6706.1` → `陆仟柒佰零陆圆壹角整`,`-308` → `(负数)叁佰零捌圆整`。
9
+ * 超出「万亿」量级或非有限数返回 `null`,由调用方决定回退(票面留空)。
10
+ */
11
+ export declare function toCapital(value: number): string | null;
@@ -0,0 +1,58 @@
1
+ const DIGITS = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
2
+ const UNITS = ["", "\u62FE", "\u4F70", "\u4EDF"];
3
+ const GROUPS = ["", "\u4E07", "\u4EBF", "\u4E07\u4EBF"];
4
+ function groupToCapital(group) {
5
+ let out = "";
6
+ let zero = false;
7
+ for (let i = 0; i < group.length; i++) {
8
+ const digit = Number(group[i]);
9
+ if (digit === 0) {
10
+ zero = true;
11
+ continue;
12
+ }
13
+ if (zero && out !== "") out += "\u96F6";
14
+ zero = false;
15
+ out += DIGITS[digit] + UNITS[group.length - 1 - i];
16
+ }
17
+ return out;
18
+ }
19
+ function integerToCapital(digits) {
20
+ const trimmed = digits.replace(/^0+/, "");
21
+ if (trimmed === "") return "\u96F6";
22
+ const groups = [];
23
+ for (let end = trimmed.length; end > 0; end -= 4) {
24
+ groups.unshift(trimmed.slice(Math.max(0, end - 4), end));
25
+ }
26
+ let out = "";
27
+ let pendingZero = false;
28
+ groups.forEach((group, i) => {
29
+ if (Number(group) === 0) {
30
+ pendingZero = out !== "";
31
+ return;
32
+ }
33
+ let text = groupToCapital(group);
34
+ const leadingZero = group.length === 4 && group[0] === "0";
35
+ if ((pendingZero || leadingZero) && out !== "") text = `\u96F6${text}`;
36
+ pendingZero = false;
37
+ out += text + GROUPS[groups.length - 1 - i];
38
+ });
39
+ return out;
40
+ }
41
+ export function toCapital(value) {
42
+ if (!Number.isFinite(value)) return null;
43
+ const negative = value < 0;
44
+ const cents = Math.round(Math.abs(value) * 100);
45
+ const yuan = Math.floor(cents / 100);
46
+ const jiao = Math.floor(cents % 100 / 10);
47
+ const fen = cents % 10;
48
+ if (String(yuan).length > 16) return null;
49
+ let out = `${integerToCapital(String(yuan))}\u5706`;
50
+ if (jiao === 0 && fen === 0) {
51
+ out += "\u6574";
52
+ } else {
53
+ if (jiao > 0) out += `${DIGITS[jiao]}\u89D2`;
54
+ if (fen > 0) out += `${jiao === 0 ? "\u96F6" : ""}${DIGITS[fen]}\u5206`;
55
+ else out += "\u6574";
56
+ }
57
+ return negative ? `\uFF08\u8D1F\u6570\uFF09${out}` : out;
58
+ }
@@ -0,0 +1,13 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
9
+ type __VLS_WithSlots<T, S> = T & {
10
+ new (): {
11
+ $slots: S;
12
+ };
13
+ };
@@ -0,0 +1,22 @@
1
+ <script setup>
2
+ import { provideCELContext } from "../../../../../utils/cel-context";
3
+ defineOptions({ name: "ShwfedBlockInvoiceDigitalRowScopeProvider" });
4
+ provideCELContext({
5
+ item: {
6
+ type: "dyn",
7
+ label: "item",
8
+ description: "\u5F53\u524D\u660E\u7EC6\u9879",
9
+ value: void 0
10
+ },
11
+ index: {
12
+ type: "number",
13
+ label: "index",
14
+ description: "\u5F53\u524D\u660E\u7EC6\u9879\u5E8F\u53F7\uFF0C\u4ECE 0 \u5F00\u59CB",
15
+ value: void 0
16
+ }
17
+ });
18
+ </script>
19
+
20
+ <template>
21
+ <slot />
22
+ </template>
@@ -0,0 +1,13 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
9
+ type __VLS_WithSlots<T, S> = T & {
10
+ new (): {
11
+ $slots: S;
12
+ };
13
+ };
@@ -0,0 +1,86 @@
1
+ import type { Environment } from '../../../../../vendor/cel-js/lib/index.js';
2
+ import { type Value } from './schema.js';
3
+ type __VLS_Props = {
4
+ configure?: (env: Environment) => void;
5
+ };
6
+ type __VLS_ModelProps = {
7
+ 'block': Value;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ "update:block": (value: {
12
+ readonly type: "com.shwfed.block.invoice.digital";
13
+ readonly id: string;
14
+ readonly line: {
15
+ readonly name: string;
16
+ readonly spec?: string | undefined;
17
+ readonly unit?: string | undefined;
18
+ readonly quantity?: string | undefined;
19
+ readonly unitPrice?: string | undefined;
20
+ readonly amount: string;
21
+ readonly taxRate?: string | undefined;
22
+ readonly taxAmount?: string | undefined;
23
+ };
24
+ readonly displayName?: string | undefined;
25
+ readonly kind?: "normal" | "special" | undefined;
26
+ readonly compatibilityDate: "2026-07-17";
27
+ readonly items: string;
28
+ readonly request?: string | undefined;
29
+ readonly itemsMaxHeight?: string | undefined;
30
+ readonly invoiceNumber: string;
31
+ readonly issueDate: string;
32
+ readonly buyer: {
33
+ readonly name: string;
34
+ readonly taxId: string;
35
+ };
36
+ readonly seller: {
37
+ readonly name: string;
38
+ readonly taxId: string;
39
+ };
40
+ readonly totalAmount: string;
41
+ readonly totalTax: string;
42
+ readonly grandTotal: string;
43
+ readonly remark?: string | undefined;
44
+ readonly issuer?: string | undefined;
45
+ readonly redLetter?: string | undefined;
46
+ }) => any;
47
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
48
+ "onUpdate:block"?: ((value: {
49
+ readonly type: "com.shwfed.block.invoice.digital";
50
+ readonly id: string;
51
+ readonly line: {
52
+ readonly name: string;
53
+ readonly spec?: string | undefined;
54
+ readonly unit?: string | undefined;
55
+ readonly quantity?: string | undefined;
56
+ readonly unitPrice?: string | undefined;
57
+ readonly amount: string;
58
+ readonly taxRate?: string | undefined;
59
+ readonly taxAmount?: string | undefined;
60
+ };
61
+ readonly displayName?: string | undefined;
62
+ readonly kind?: "normal" | "special" | undefined;
63
+ readonly compatibilityDate: "2026-07-17";
64
+ readonly items: string;
65
+ readonly request?: string | undefined;
66
+ readonly itemsMaxHeight?: string | undefined;
67
+ readonly invoiceNumber: string;
68
+ readonly issueDate: string;
69
+ readonly buyer: {
70
+ readonly name: string;
71
+ readonly taxId: string;
72
+ };
73
+ readonly seller: {
74
+ readonly name: string;
75
+ readonly taxId: string;
76
+ };
77
+ readonly totalAmount: string;
78
+ readonly totalTax: string;
79
+ readonly grandTotal: string;
80
+ readonly remark?: string | undefined;
81
+ readonly issuer?: string | undefined;
82
+ readonly redLetter?: string | undefined;
83
+ }) => any) | undefined;
84
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
85
+ declare const _default: typeof __VLS_export;
86
+ export default _default;
@@ -0,0 +1,346 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { Effect, Option } from "effect";
4
+ import { Fetch } from "fx-fetch";
5
+ import { cel } from "../../../../../utils/cel";
6
+ import { celBindings, injectCELContext } from "../../../../../utils/cel-context";
7
+ import { fetchJsonOption } from "../../../../../share/request";
8
+ import { ScrollArea } from "../../../../ui/scroll-area";
9
+ import ItemsColgroup from "./items-colgroup.vue";
10
+ import { toCapital } from "./rmb";
11
+ import { INVOICE_KIND_LABELS } from "./schema";
12
+ defineOptions({ name: "ShwfedBlockInvoiceDigitalRuntime" });
13
+ const block = defineModel("block", { type: null, ...{ required: true } });
14
+ defineProps({
15
+ configure: { type: Function, required: false }
16
+ });
17
+ const inheritedContext = injectCELContext();
18
+ const json = ref(void 0);
19
+ const jsonOption = computed(
20
+ () => json.value === void 0 ? Option.none() : Option.some(json.value)
21
+ );
22
+ const hasRequest = computed(() => {
23
+ const expr = block.value.request;
24
+ return !!expr && expr.trim().length > 0;
25
+ });
26
+ function baseContext() {
27
+ const inherited = celBindings(inheritedContext);
28
+ if (!hasRequest.value && "json" in inherited) return inherited;
29
+ return { ...inherited, json: jsonOption.value };
30
+ }
31
+ async function refresh() {
32
+ const expr = block.value.request;
33
+ if (!expr || expr.trim().length === 0) {
34
+ json.value = void 0;
35
+ return;
36
+ }
37
+ const program = Effect.gen(function* () {
38
+ const body = yield* fetchJsonOption(
39
+ yield* cel(expr, celBindings(inheritedContext))
40
+ );
41
+ return Option.getOrUndefined(body);
42
+ });
43
+ try {
44
+ json.value = await Effect.runPromise(Effect.provide(program, Fetch.layer));
45
+ } catch (err) {
46
+ console.warn("[shwfed-invoice-digital] request failed", err);
47
+ json.value = void 0;
48
+ }
49
+ }
50
+ onMounted(() => void refresh());
51
+ watch(() => block.value.request ?? "", () => void refresh());
52
+ function evaluate(expression, scope) {
53
+ if (!expression || expression.trim().length === 0) return void 0;
54
+ try {
55
+ return Effect.runSync(cel(expression, { ...baseContext(), ...scope }));
56
+ } catch (err) {
57
+ console.warn("[shwfed-invoice-digital] expression evaluation failed", expression, err);
58
+ return void 0;
59
+ }
60
+ }
61
+ function text(expression, scope) {
62
+ const v = evaluate(expression, scope);
63
+ return v === null || v === void 0 ? "" : String(v);
64
+ }
65
+ function toFiniteNumber(v) {
66
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
67
+ if (typeof v === "string" && v.trim() !== "") {
68
+ const n = Number(v);
69
+ return Number.isFinite(n) ? n : null;
70
+ }
71
+ return null;
72
+ }
73
+ const MONEY_FORMAT = new Intl.NumberFormat("zh-CN", {
74
+ minimumFractionDigits: 2,
75
+ maximumFractionDigits: 2
76
+ });
77
+ function money(expression, scope) {
78
+ const v = evaluate(expression, scope);
79
+ if (v === null || v === void 0) return "";
80
+ if (typeof v === "number") return Number.isFinite(v) ? MONEY_FORMAT.format(v) : "";
81
+ return String(v);
82
+ }
83
+ function plain(expression, scope) {
84
+ const v = evaluate(expression, scope);
85
+ if (v === null || v === void 0) return "";
86
+ return String(v);
87
+ }
88
+ const title = computed(
89
+ () => `\u7535\u5B50\u53D1\u7968\uFF08${INVOICE_KIND_LABELS[block.value.kind ?? "special"]}\uFF09`
90
+ );
91
+ const invoiceNumber = computed(() => text(block.value.invoiceNumber));
92
+ const issueDate = computed(() => text(block.value.issueDate));
93
+ const buyer = computed(() => ({
94
+ name: text(block.value.buyer.name),
95
+ taxId: text(block.value.buyer.taxId)
96
+ }));
97
+ const seller = computed(() => ({
98
+ name: text(block.value.seller.name),
99
+ taxId: text(block.value.seller.taxId)
100
+ }));
101
+ const items = computed(() => {
102
+ const result = evaluate(block.value.items);
103
+ return Array.isArray(result) ? result : [];
104
+ });
105
+ const rows = computed(
106
+ () => items.value.map((item, index) => {
107
+ const scope = { item, index };
108
+ const line = block.value.line;
109
+ return {
110
+ name: text(line.name, scope),
111
+ spec: text(line.spec, scope),
112
+ unit: text(line.unit, scope),
113
+ quantity: plain(line.quantity, scope),
114
+ unitPrice: plain(line.unitPrice, scope),
115
+ amount: money(line.amount, scope),
116
+ taxRate: text(line.taxRate, scope),
117
+ taxAmount: money(line.taxAmount, scope)
118
+ };
119
+ })
120
+ );
121
+ const totalAmount = computed(() => money(block.value.totalAmount));
122
+ const totalTax = computed(() => money(block.value.totalTax));
123
+ const grandTotal = computed(() => money(block.value.grandTotal));
124
+ const grandTotalCapital = computed(() => {
125
+ const n = toFiniteNumber(evaluate(block.value.grandTotal));
126
+ return n === null ? "" : toCapital(n) ?? "";
127
+ });
128
+ const remark = computed(() => text(block.value.remark));
129
+ const issuer = computed(() => text(block.value.issuer));
130
+ const redLetter = computed(() => evaluate(block.value.redLetter) === true);
131
+ const MIN_ROWS = 6;
132
+ const fillerRows = computed(() => Math.max(0, MIN_ROWS - rows.value.length));
133
+ const itemsStyle = computed(
134
+ () => block.value.itemsMaxHeight ? { maxHeight: block.value.itemsMaxHeight } : void 0
135
+ );
136
+ </script>
137
+
138
+ <template>
139
+ <div class="invoice">
140
+ <!-- 抬头:标题居中,号码 / 日期靠右 -->
141
+ <header class="head">
142
+ <div class="title-block">
143
+ <h1 class="title">
144
+ {{ title }}
145
+ </h1>
146
+ <div
147
+ class="title-rule"
148
+ aria-hidden="true"
149
+ />
150
+ </div>
151
+ <dl class="meta">
152
+ <div>
153
+ <dt>发票号码:</dt>
154
+ <dd>{{ invoiceNumber }}</dd>
155
+ </div>
156
+ <div>
157
+ <dt>开票日期:</dt>
158
+ <dd>{{ issueDate }}</dd>
159
+ </div>
160
+ </dl>
161
+ </header>
162
+
163
+ <div class="frame">
164
+ <!-- 购销双方 -->
165
+ <div class="parties">
166
+ <div class="party">
167
+ <div class="side-label">
168
+ 购买方信息
169
+ </div>
170
+ <div class="party-body">
171
+ <div class="party-row">
172
+ <span class="party-key">名称:</span>
173
+ <span class="party-value">{{ buyer.name }}</span>
174
+ </div>
175
+ <div class="party-row">
176
+ <span class="party-key">统一社会信用代码/纳税人识别号:</span>
177
+ <span class="party-value tax-id">{{ buyer.taxId }}</span>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ <div class="party">
182
+ <div class="side-label">
183
+ 销售方信息
184
+ </div>
185
+ <div class="party-body">
186
+ <div class="party-row">
187
+ <span class="party-key">名称:</span>
188
+ <span class="party-value">{{ seller.name }}</span>
189
+ </div>
190
+ <div class="party-row">
191
+ <span class="party-key">统一社会信用代码/纳税人识别号:</span>
192
+ <span class="party-value tax-id">{{ seller.taxId }}</span>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ <!-- 明细。列标题 / 内容 / 合计拆成三张 table,只有中间那张进 ScrollArea ——
199
+ ScrollArea 的滚动条是覆盖整个滚动容器高度的绝对定位条,表头表尾若也在里面
200
+ 就会被它横穿。三张靠共用的 ItemsColgroup 对齐(都是 table-layout: fixed)。 -->
201
+ <div class="items">
202
+ <!-- 水印:在明细内容**之下**(纸质票的红字是印在票面底纹上、被文字压住的),
203
+ 不参与布局,也不拦截选中 -->
204
+ <div
205
+ v-if="redLetter"
206
+ class="watermark"
207
+ aria-hidden="true"
208
+ >
209
+ 红字发票
210
+ </div>
211
+
212
+ <table class="items-head">
213
+ <ItemsColgroup />
214
+ <thead>
215
+ <tr>
216
+ <th>项目名称</th>
217
+ <th>规格型号</th>
218
+ <th>单 位</th>
219
+ <th>数 量</th>
220
+ <th>单 价</th>
221
+ <th>金 额</th>
222
+ <th>税率/征收率</th>
223
+ <th>税 额</th>
224
+ </tr>
225
+ </thead>
226
+ </table>
227
+
228
+ <!-- `overscroll-none`:明细滚到头就停住,既不橡皮筋回弹、也不把滚动传给外层页面
229
+ —— 票是嵌在页面里的一小块,滚它不该顺带滚走整张页面。 -->
230
+ <ScrollArea
231
+ class="items-body"
232
+ viewport-class="overscroll-none"
233
+ :style="itemsStyle"
234
+ >
235
+ <table>
236
+ <ItemsColgroup />
237
+ <tbody>
238
+ <tr
239
+ v-for="(row, index) in rows"
240
+ :key="index"
241
+ >
242
+ <td class="cell-text">
243
+ {{ row.name }}
244
+ </td>
245
+ <!-- 规格型号内容居中(项目名称是唯一左对齐的文本栏) -->
246
+ <td class="cell-text cell-center">
247
+ {{ row.spec }}
248
+ </td>
249
+ <td class="cell-center">
250
+ {{ row.unit }}
251
+ </td>
252
+ <td class="cell-num">
253
+ {{ row.quantity }}
254
+ </td>
255
+ <td class="cell-num">
256
+ {{ row.unitPrice }}
257
+ </td>
258
+ <td class="cell-num">
259
+ {{ row.amount }}
260
+ </td>
261
+ <td class="cell-center">
262
+ {{ row.taxRate }}
263
+ </td>
264
+ <td class="cell-num">
265
+ {{ row.taxAmount }}
266
+ </td>
267
+ </tr>
268
+ <!-- 撑满票面的空行,纸质票同款 -->
269
+ <tr
270
+ v-for="n in fillerRows"
271
+ :key="`filler-${n}`"
272
+ class="filler"
273
+ >
274
+ <td colspan="8">
275
+ &nbsp;
276
+ </td>
277
+ </tr>
278
+ </tbody>
279
+ </table>
280
+ </ScrollArea>
281
+
282
+ <table class="items-foot">
283
+ <ItemsColgroup />
284
+ <tfoot>
285
+ <tr>
286
+ <!-- 「合计」只占项目名称列,好让它正对下方的「价税合计(大写)」;
287
+ 跨 5 列居中会把它推到票面中间 -->
288
+ <td class="total-label">
289
+ 合&emsp;&emsp;计
290
+ </td>
291
+ <td colspan="4" />
292
+ <td class="cell-num">
293
+ <template v-if="totalAmount">
294
+ ¥{{ totalAmount }}
295
+ </template>
296
+ </td>
297
+ <td />
298
+ <td class="cell-num">
299
+ <template v-if="totalTax">
300
+ ¥{{ totalTax }}
301
+ </template>
302
+ </td>
303
+ </tr>
304
+ </tfoot>
305
+ </table>
306
+ </div>
307
+
308
+ <!-- 价税合计 -->
309
+ <div class="grand">
310
+ <div class="grand-label">
311
+ 价税合计(大写)
312
+ </div>
313
+ <div class="grand-capital">
314
+ <span class="cross">⊗</span>{{ grandTotalCapital }}
315
+ </div>
316
+ <div class="grand-lower">
317
+ <span class="grand-lower-key">(小写)</span>
318
+ <template v-if="grandTotal">
319
+ ¥{{ grandTotal }}
320
+ </template>
321
+ </div>
322
+ </div>
323
+
324
+ <!-- 备注 -->
325
+ <div class="remark">
326
+ <div class="side-label">
327
+ 备 注
328
+ </div>
329
+ <div class="remark-body">
330
+ {{ remark }}
331
+ </div>
332
+ </div>
333
+ </div>
334
+
335
+ <footer
336
+ v-if="issuer"
337
+ class="foot"
338
+ >
339
+ <span class="foot-label">开票人:</span>{{ issuer }}
340
+ </footer>
341
+ </div>
342
+ </template>
343
+
344
+ <style scoped>
345
+ .invoice{--invoice-ink:maroon;--invoice-data:#000;--invoice-font-song:"Songti SC",SimSun,STSong,"Source Han Serif SC","Noto Serif CJK SC","Noto Serif SC",serif;--invoice-font-kai:KaiTi,"Kaiti SC",STKaiti,"AR PL UKai CN","LXGW WenKai",var(--invoice-font-song);background:#fff;color:var(--invoice-data);container-type:inline-size;font-family:var(--invoice-font-song);font-size:clamp(9px,1.15cqw,14px);line-height:1.5;padding:1rem}.foot-label,.grand-label,.grand-lower-key,.items th,.meta dt,.party-key,.side-label,.title,.total-label{color:var(--invoice-ink);font-family:var(--invoice-font-kai)}.head{display:flex;justify-content:center;padding:1.8em 0 2em;position:relative}.title-block{display:flex;flex-direction:column}.title{font-size:2.6em;font-weight:400;letter-spacing:.1em;white-space:nowrap}.title-rule{border-bottom:1.5px solid var(--invoice-ink);border-top:1.5px solid var(--invoice-ink);height:.32em;margin-top:.45em}.meta{flex-direction:column;gap:.35em;position:absolute;right:0;top:50%;transform:translateY(-50%);white-space:nowrap}.meta,.meta div{display:flex}.meta dt{font-weight:400}.meta dd{letter-spacing:.02em;margin:0}.frame{border:1px solid var(--invoice-ink)}.parties{border-bottom:1px solid var(--invoice-ink);display:grid;grid-template-columns:1fr 1fr}.party{display:grid;grid-template-columns:auto 1fr}.party+.party{border-left:1px solid var(--invoice-ink)}.side-label{align-items:center;border-right:1px solid var(--invoice-ink);display:flex;justify-content:center;letter-spacing:.25em;padding:.5em .4em;text-orientation:upright;white-space:nowrap;writing-mode:vertical-rl}.party-body{flex-direction:column;justify-content:space-around;padding:.6em .8em}.party-body,.party-row{display:flex;min-width:0}.party-row{align-items:baseline}.party-key{white-space:nowrap}.party-value{min-width:0;word-break:break-all}.tax-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Courier New,monospace}.items{position:relative}.items table{border-collapse:collapse;table-layout:fixed;width:100%}.items-body,.items-foot,.items-head{position:relative;z-index:1}.items th{font-weight:400;letter-spacing:.05em;padding:.4em .5em;text-align:center}.items td{padding:.25em .5em;vertical-align:top}.cell-text{white-space:pre-wrap;word-break:break-all}.cell-center{text-align:center}.cell-num{font-variant-numeric:tabular-nums;text-align:right}.total-label{letter-spacing:.05em;text-align:center}.items tfoot td{padding-bottom:.35em;padding-top:.35em}.watermark{color:transparent;display:flex;font-size:3.4em;inset:0;justify-content:center;letter-spacing:.5em;pointer-events:none;position:absolute;z-index:0;-webkit-text-stroke:1px color-mix(in srgb,var(--invoice-ink) 40%,#fff);font-family:var(--invoice-font-kai);opacity:.85}.grand,.watermark{align-items:center}.grand{border-bottom:1px solid var(--invoice-ink);border-top:1px solid var(--invoice-ink);display:grid;grid-template-columns:22% 1fr auto}.grand-label{border-right:1px solid var(--invoice-ink);letter-spacing:.1em;padding:.5em 1.2em;text-align:center;white-space:nowrap}.grand-capital{min-width:0;padding:.5em .8em;word-break:break-all}.cross{margin-right:.3em}.grand-lower{font-variant-numeric:tabular-nums;padding:.5em 1.2em;white-space:nowrap}.grand-lower-key{margin-right:.4em}.remark{display:grid;grid-template-columns:auto 1fr;min-height:5em}.remark-body{padding:.5em .8em;white-space:pre-wrap;word-break:break-all}.foot{padding-left:3em;padding-top:.75em}
346
+ </style>