v-nuxt-ui 0.2.25 → 0.2.26

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 (87) hide show
  1. package/README.md +6 -0
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/AsyncSelect.vue +16 -1
  4. package/dist/runtime/components/AsyncTreeSelect.vue +15 -9
  5. package/dist/runtime/components/DeleteModal.d.vue.ts +25 -22
  6. package/dist/runtime/components/DeleteModal.vue +39 -4
  7. package/dist/runtime/components/DeleteModal.vue.d.ts +25 -22
  8. package/dist/runtime/components/ScrollArea.vue +20 -9
  9. package/dist/runtime/components/form/save-model-template/ConfirmUpdateModal.d.vue.ts +19 -0
  10. package/dist/runtime/components/form/save-model-template/ConfirmUpdateModal.vue +111 -0
  11. package/dist/runtime/components/form/save-model-template/ConfirmUpdateModal.vue.d.ts +19 -0
  12. package/dist/runtime/components/form/{create-modal-template → save-model-template}/WithApi.d.vue.ts +2 -2
  13. package/dist/runtime/components/form/{create-modal-template → save-model-template}/WithApi.vue +4 -3
  14. package/dist/runtime/components/form/{create-modal-template → save-model-template}/WithApi.vue.d.ts +2 -2
  15. package/dist/runtime/components/form/{create-modal-template → save-model-template}/index.d.vue.ts +2 -2
  16. package/dist/runtime/components/form/{create-modal-template → save-model-template}/index.vue +54 -7
  17. package/dist/runtime/components/form/{create-modal-template → save-model-template}/index.vue.d.ts +2 -2
  18. package/dist/runtime/components/layout/button/UserMenu.vue +1 -2
  19. package/dist/runtime/components/sys/company/{CreateModal.vue → SaveModal.vue} +2 -2
  20. package/dist/runtime/components/sys/company/Table.vue +5 -4
  21. package/dist/runtime/components/sys/department/{CreateModal.vue → SaveModal.vue} +3 -2
  22. package/dist/runtime/components/sys/department/Table.vue +6 -5
  23. package/dist/runtime/components/sys/flow/EditNodeModal.vue +4 -3
  24. package/dist/runtime/components/sys/flow/{CreateModal.vue → SaveModal.vue} +3 -2
  25. package/dist/runtime/components/sys/flow/Table.vue +5 -4
  26. package/dist/runtime/components/sys/issue-record/{CreateModal.vue → SaveModal.vue} +2 -2
  27. package/dist/runtime/components/sys/issue-record/Table.vue +5 -4
  28. package/dist/runtime/components/sys/job-title/{CreateModal.vue → SaveModal.vue} +2 -2
  29. package/dist/runtime/components/sys/job-title/Table.vue +5 -4
  30. package/dist/runtime/components/sys/menu/{CreateModal.vue → SaveModal.vue} +3 -2
  31. package/dist/runtime/components/sys/menu/Table.vue +6 -5
  32. package/dist/runtime/components/sys/role/{CreateModal.vue → SaveModal.vue} +8 -5
  33. package/dist/runtime/components/sys/role/Table.vue +5 -4
  34. package/dist/runtime/components/sys/table/{CreateModal.vue → SaveModal.vue} +4 -3
  35. package/dist/runtime/components/sys/table/Table.vue +4 -4
  36. package/dist/runtime/components/sys/table/TableColumnModal.vue +4 -3
  37. package/dist/runtime/components/sys/user/{CreateModal.vue → SaveModal.vue} +4 -3
  38. package/dist/runtime/components/sys/user/Table.vue +7 -6
  39. package/dist/runtime/components/table/Page.vue +2 -1
  40. package/dist/runtime/components/table/UpdateDiffModal.d.vue.ts +14 -0
  41. package/dist/runtime/components/table/UpdateDiffModal.vue +156 -0
  42. package/dist/runtime/components/table/UpdateDiffModal.vue.d.ts +14 -0
  43. package/dist/runtime/components/table/header/index.vue +181 -183
  44. package/dist/runtime/components/table/index.vue +2 -1
  45. package/dist/runtime/composables/form/index.d.ts +2 -0
  46. package/dist/runtime/composables/form/index.js +2 -0
  47. package/dist/runtime/composables/form/useForm.d.ts +21 -0
  48. package/dist/runtime/composables/{useForm.js → form/useForm.js} +23 -1
  49. package/dist/runtime/composables/form/useFormUpdate.d.ts +6 -0
  50. package/dist/runtime/composables/form/useFormUpdate.js +101 -0
  51. package/dist/runtime/composables/index.d.ts +1 -1
  52. package/dist/runtime/composables/index.js +1 -1
  53. package/dist/runtime/composables/table/useTable.js +10 -1
  54. package/dist/runtime/composables/table/useTableRowActions.d.ts +3 -0
  55. package/dist/runtime/composables/table/useTableRowActions.js +24 -0
  56. package/dist/runtime/composables/useDate.js +2 -0
  57. package/dist/runtime/types/components/form/field.d.ts +2 -0
  58. package/dist/runtime/types/components/form/index.d.ts +4 -3
  59. package/dist/runtime/types/components/table/header.d.ts +2 -0
  60. package/dist/runtime/types/components/table/index.d.ts +1 -0
  61. package/dist/runtime/types/time.d.ts +1 -1
  62. package/dist/runtime/utils/cron.d.ts +1 -0
  63. package/dist/runtime/utils/cron.js +182 -0
  64. package/dist/runtime/utils/index.d.ts +1 -0
  65. package/dist/runtime/utils/index.js +1 -0
  66. package/dist/runtime/utils/vueuse.d.ts +1 -1
  67. package/dist/runtime/utils/vueuse.js +1 -0
  68. package/package.json +16 -15
  69. package/dist/runtime/composables/useForm.d.ts +0 -9
  70. /package/dist/runtime/components/sys/company/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  71. /package/dist/runtime/components/sys/company/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  72. /package/dist/runtime/components/sys/department/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  73. /package/dist/runtime/components/sys/department/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  74. /package/dist/runtime/components/sys/flow/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  75. /package/dist/runtime/components/sys/flow/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  76. /package/dist/runtime/components/sys/issue-record/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  77. /package/dist/runtime/components/sys/issue-record/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  78. /package/dist/runtime/components/sys/job-title/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  79. /package/dist/runtime/components/sys/job-title/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  80. /package/dist/runtime/components/sys/menu/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  81. /package/dist/runtime/components/sys/menu/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  82. /package/dist/runtime/components/sys/role/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  83. /package/dist/runtime/components/sys/role/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  84. /package/dist/runtime/components/sys/table/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  85. /package/dist/runtime/components/sys/table/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
  86. /package/dist/runtime/components/sys/user/{CreateModal.d.vue.ts → SaveModal.d.vue.ts} +0 -0
  87. /package/dist/runtime/components/sys/user/{CreateModal.vue.d.ts → SaveModal.vue.d.ts} +0 -0
@@ -0,0 +1,156 @@
1
+ <script setup>
2
+ import { ref, computed } from "vue";
3
+ import { isEmptyString } from "#v/utils";
4
+ import { diffWords } from "diff";
5
+ import { useRowRecordApi } from "#v/composables/api";
6
+ import { useDate } from "#v/composables";
7
+ const props = defineProps({
8
+ tableName: { type: String, required: true },
9
+ rowId: { type: Number, required: true },
10
+ columns: { type: Array, required: true },
11
+ title: { type: String, required: false }
12
+ });
13
+ const emit = defineEmits(["close"]);
14
+ const date = useDate();
15
+ const rowRecordApi = useRowRecordApi();
16
+ const loading = ref(false);
17
+ const rowRecords = ref([]);
18
+ async function fetchRowRecords() {
19
+ loading.value = true;
20
+ try {
21
+ const { data } = await rowRecordApi.getByTableNameAndRowId({
22
+ tableName: props.tableName,
23
+ rowId: props.rowId
24
+ });
25
+ rowRecords.value = data.value.data?.list ?? [];
26
+ } finally {
27
+ loading.value = false;
28
+ }
29
+ }
30
+ fetchRowRecords();
31
+ function resolveHeader(key) {
32
+ if (!key) return "";
33
+ const col = props.columns.find((c) => c.accessorKey === key || c.id === key);
34
+ return col?.header || key;
35
+ }
36
+ const aggregatedRecords = computed(() => {
37
+ if (!rowRecords.value.length) return [];
38
+ const groupMap = /* @__PURE__ */ new Map();
39
+ for (const record of rowRecords.value) {
40
+ const ver = record.rowVersion;
41
+ if (!groupMap.has(ver)) {
42
+ groupMap.set(ver, {
43
+ rowVersion: ver,
44
+ createdAt: record.createdAt,
45
+ creator: record.creator,
46
+ changes: []
47
+ });
48
+ }
49
+ const oldVal = record.oldValue || "";
50
+ const newVal = record.newValue || "";
51
+ groupMap.get(ver).changes.push({
52
+ header: resolveHeader(record.key || ""),
53
+ oldValue: oldVal,
54
+ newValue: newVal,
55
+ parts: diffWords(oldVal, newVal)
56
+ });
57
+ }
58
+ return Array.from(groupMap.values()).sort((a, b) => b.rowVersion - a.rowVersion);
59
+ });
60
+ const timelineItems = computed(
61
+ () => aggregatedRecords.value.map((record) => ({
62
+ record,
63
+ icon: "i-lucide-git-commit"
64
+ }))
65
+ );
66
+ </script>
67
+
68
+ <template>
69
+ <UModal
70
+ :title="title || '\u53D8\u66F4\u8BB0\u5F55'"
71
+ :close="{ onClick: () => emit('close', false) }"
72
+ :dismissible="false"
73
+ :ui="{ body: 'p-0!' }"
74
+ >
75
+ <template #body>
76
+ <!-- Loading -->
77
+ <div v-if="loading" class="flex items-center justify-center py-8">
78
+ <UIcon name="i-lucide-loader-circle" class="size-6 animate-spin text-dimmed" />
79
+ </div>
80
+
81
+ <!-- Timeline -->
82
+ <div v-else-if="timelineItems.length > 0" class="overflow-y-auto max-h-96 p-6">
83
+ <UTimeline
84
+ :items="timelineItems"
85
+ size="xs"
86
+ :ui="{ date: 'float-end' }"
87
+ >
88
+ <template #title="{ item }">
89
+ <div class="flex items-center gap-1">
90
+ <span class="font-semibold">{{ item.record.creator?.nickname || "\u672A\u77E5\u7528\u6237" }}</span>
91
+ <UBadge
92
+ :label="`v${item.record.rowVersion}`"
93
+ variant="subtle"
94
+ color="neutral"
95
+ size="xs"
96
+ />
97
+ <div class="ml-auto text-xs text-dimmed font-normal">
98
+ 更新于 {{ date.formatTimeUnit(date.isoUtcStringToDateValue(item.record.createdAt), "time") }}
99
+ </div>
100
+ </div>
101
+ </template>
102
+
103
+ <template #description="{ item }">
104
+ <div class="rounded-md ring ring-default mt-2 p-3 space-y-3">
105
+ <div v-for="(change, idx) in item.record.changes" :key="idx">
106
+ <div class="text-xs font-semibold text-dimmed mb-1">
107
+ {{ change.header }}
108
+ </div>
109
+ <div class="flex items-center flex-wrap gap-2">
110
+ <!-- Old -->
111
+ <span class="rounded-md bg-muted px-2 py-1 text-sm text-dimmed">
112
+ <span v-if="change.parts.filter((part2) => !part2.added).length === 0">{{ "\xA0" }}</span>
113
+ <template v-for="(part, pIdx) in change.parts" :key="pIdx">
114
+ <span
115
+ v-if="!part.added"
116
+ :class="{ 'bg-red-200 text-red-800 dark:bg-red-800 dark:text-red-200 rounded-sm line-through px-0.5': part.removed }"
117
+ >
118
+ {{ isEmptyString(part.value) ? "\xA0" : part.value }}
119
+ </span>
120
+ </template>
121
+ </span>
122
+ <UIcon name="i-lucide-arrow-right" class="text-dimmed shrink-0 size-4" />
123
+ <!-- New -->
124
+ <span class="rounded-md bg-elevated px-2 py-1 text-sm text-highlighted font-medium">
125
+ <span v-if="change.parts.filter((part2) => !part2.removed).length === 0">{{ "\xA0" }}</span>
126
+ <template v-for="(part, pIdx) in change.parts" :key="pIdx">
127
+ <span
128
+ v-if="!part.removed"
129
+ :class="{ 'bg-green-200 text-green-800 dark:bg-green-800 dark:text-green-200 rounded-sm px-0.5': part.added }"
130
+ >
131
+ {{ isEmptyString(part.value) ? "\xA0" : part.value }}
132
+ </span>
133
+ </template>
134
+ </span>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </template>
139
+ </UTimeline>
140
+ </div>
141
+
142
+ <!-- Empty -->
143
+ <div v-else class="text-center text-dimmed py-8">
144
+ 暂无变更记录
145
+ </div>
146
+ </template>
147
+ <template #footer>
148
+ <UButton
149
+ label="关闭"
150
+ color="neutral"
151
+ variant="subtle"
152
+ @click="emit('close', false)"
153
+ />
154
+ </template>
155
+ </UModal>
156
+ </template>
@@ -0,0 +1,14 @@
1
+ import type { VColumn } from '#v/types';
2
+ type __VLS_Props = {
3
+ tableName: string;
4
+ rowId: number;
5
+ columns: VColumn<any>[];
6
+ title?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ close: (args_0: boolean) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onClose?: ((args_0: boolean) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { computed, h } from "vue";
2
+ import { markRaw } from "vue";
3
3
  import { defu } from "defu";
4
4
  import { useOverlay } from "@nuxt/ui/composables";
5
5
  import UButton from "@nuxt/ui/components/Button.vue";
@@ -21,6 +21,8 @@ const props = defineProps({
21
21
  fetchList: { type: Function, required: true },
22
22
  onEditRowFromModal: { type: Function, required: false },
23
23
  selectedIds: { type: Array, required: false },
24
+ selectedModels: { type: Array, required: false },
25
+ displayFnInDeleteModal: { type: Function, required: false },
24
26
  disableCreation: { type: Boolean, required: false },
25
27
  disableWhereQuery: { type: Boolean, required: false },
26
28
  whereQueryProps: { type: Object, required: true },
@@ -36,190 +38,186 @@ const props = defineProps({
36
38
  });
37
39
  const defaultNewRow = { id: 0 };
38
40
  const overlay = useOverlay();
39
- const deleteModal = overlay.create(DeleteModal);
40
- const settingsModal = overlay.create(TableHeaderSettings);
41
- const excelExportModal = overlay.create(TableExcelExportModal);
42
- const oprButtons = computed(() => {
43
- const defaultButtons = props.oprOrder.map((opr) => {
44
- switch (opr) {
45
- case "create":
46
- if (props.disableCreation) return null;
47
- return h(
48
- UButton,
49
- {
50
- icon: "i-lucide-plus",
51
- size: props.size,
52
- onClick: async () => {
53
- const result = await props.onEditRowFromModal?.(props.onNew?.() ?? defaultNewRow);
54
- if (result) {
55
- props.fetchList();
56
- }
57
- }
58
- },
59
- () => "\u65B0\u589E"
60
- );
61
- case "refresh":
62
- return h(
63
- UButton,
64
- {
65
- icon: "i-lucide-refresh-ccw",
66
- size: props.size,
67
- color: "neutral",
68
- loading: props.fetching,
69
- variant: "outline",
70
- onClick: () => props.fetchList()
71
- },
72
- () => "\u5237\u65B0"
73
- );
74
- case "whereQuery":
75
- if (props.disableWhereQuery) return null;
76
- return h(
77
- UChip,
78
- { show: !props.whereQueryProps.isWhereQueryValueEmpty },
79
- () => h(
80
- UButton,
81
- {
82
- icon: "i-lucide-list-filter",
83
- size: props.size,
84
- color: props.whereQueryProps.whereQueryOpen ? "primary" : "neutral",
85
- loading: props.fetching,
86
- variant: "outline",
87
- onClick: () => {
88
- props.whereQueryProps.onUpdateWhereQueryOpen?.(!props.whereQueryProps.whereQueryOpen);
89
- }
90
- },
91
- () => "\u67E5\u8BE2"
92
- )
93
- );
94
- case "orderQuery":
95
- if (props.disableOrderQuery) return null;
96
- return h(
97
- TableQueryOrder,
98
- { ...props.orderQueryProps, size: props.size }
99
- );
100
- case "settings":
101
- if (props.disableSettings) return null;
102
- return props.rawBizColumns && props.rawBizColumns.length > 0 && props.onUpdateBizColumns && h(
103
- UButton,
104
- {
105
- icon: "i-lucide-settings",
106
- size: props.size,
107
- color: "neutral",
108
- variant: "outline",
109
- onClick: async () => {
110
- if (!props.onUpdateBizColumns) return;
111
- const updateFn = props.onUpdateBizColumns;
112
- await settingsModal.open({
113
- tblName: props.name,
114
- rawBizColumns: props.rawBizColumns,
115
- // 列数据由父组件创建(VColumn<T>),模态框仅重排顺序后返回,运行时类型不变
116
- onUpdateBizColumns: ((cols) => updateFn(cols))
117
- });
118
- }
119
- },
120
- () => "\u8BBE\u7F6E"
121
- );
122
- case "exportExcel": {
123
- if (!props.exportExcel) return null;
124
- const exportButton = h(
125
- UButton,
126
- {
127
- icon: "i-lucide-sheet",
128
- size: props.size,
129
- color: "neutral",
130
- variant: "outline",
131
- onClick: async () => {
132
- await excelExportModal.open({
133
- columns: props.rawBizColumns,
134
- filename: props.exportExcel.filename,
135
- filenameWithDateTime: props.exportExcel.filenameWithDateTime,
136
- listFn: props.apiGroup?.().countAndList,
137
- whereQueryOptions: props.whereQueryProps.whereOptions,
138
- extraWhereQueryInitValues: defu(props.extraWhereQueryInitValues, props.exportExcel.extraWhereQueryInitValues)
139
- });
140
- }
141
- },
142
- () => "\u5BFC\u51FA"
143
- );
144
- if (!props.exportExcel.permissionKey) {
145
- return exportButton;
146
- }
147
- return h(PermissionWrapper, { permission: props.exportExcel.permissionKey }, {
148
- default: () => exportButton
149
- });
150
- }
151
- case "batchDelete":
152
- if (props.disableBatchDeletion) return null;
153
- return props.selectedIds && props.selectedIds.length > 0 && h(
154
- UButton,
155
- {
156
- icon: "i-lucide-trash-2",
157
- size: props.size,
158
- color: "error",
159
- variant: "outline",
160
- onClick: async () => {
161
- const result = await deleteModal.open({
162
- ids: props.selectedIds,
163
- onDelete: (ids) => props.apiGroup?.().batchDelete({ ids })
164
- }).result;
165
- if (result) {
166
- await props.fetchList();
167
- }
168
- }
169
- },
170
- {
171
- default: () => "\u6279\u91CF\u5220\u9664",
172
- trailing: () => h(UKbd, { size: "sm" }, () => props.selectedIds?.length ?? 0)
173
- }
174
- );
175
- default:
176
- return null;
177
- }
41
+ const deleteModal = markRaw(overlay.create(DeleteModal));
42
+ const settingsModal = markRaw(overlay.create(TableHeaderSettings));
43
+ const excelExportModal = markRaw(overlay.create(TableExcelExportModal));
44
+ function omitOnClick(button) {
45
+ const { onClick: _, ...rest } = button;
46
+ return rest;
47
+ }
48
+ async function handleCreate() {
49
+ const result = await props.onEditRowFromModal?.(props.onNew?.() ?? defaultNewRow);
50
+ if (result) {
51
+ props.fetchList();
52
+ }
53
+ }
54
+ async function handleSettings() {
55
+ if (!props.onUpdateBizColumns) return;
56
+ const updateFn = props.onUpdateBizColumns;
57
+ await settingsModal.open({
58
+ tblName: props.name,
59
+ rawBizColumns: props.rawBizColumns,
60
+ onUpdateBizColumns: ((cols) => updateFn(cols))
178
61
  });
179
- const buttons = [
180
- ...props.extraButtons?.filter((btn) => btn.appendTo === "left")?.filter((btn) => btn.withBatchData ? props.selectedIds && props.selectedIds.length > 0 : true).map((btn) => h(
181
- UButton,
182
- {
183
- ...btn.button,
184
- onClick: async (e) => {
185
- if (btn.withBatchData && props.selectedIds && btn.batchFn) {
186
- await btn.batchFn(props.selectedIds);
187
- await props.fetchList();
188
- }
189
- if (Array.isArray(btn.button.onClick)) {
190
- btn.button.onClick.forEach((fn) => fn?.(e));
191
- } else {
192
- btn.button.onClick?.(e);
193
- }
194
- }
195
- }
196
- )) ?? [],
197
- ...defaultButtons,
198
- ...props.extraButtons?.filter((btn) => btn.appendTo === "right")?.filter((btn) => btn.withBatchData ? props.selectedIds && props.selectedIds.length > 0 : true).map((btn) => h(
199
- UButton,
200
- {
201
- ...btn.button,
202
- onClick: async (e) => {
203
- if (btn.withBatchData && props.selectedIds && btn.batchFn) {
204
- await btn.batchFn(props.selectedIds);
205
- }
206
- if (Array.isArray(btn.button.onClick)) {
207
- btn.button.onClick.forEach((fn) => fn?.(e));
208
- } else {
209
- btn.button.onClick?.(e);
210
- }
211
- }
212
- }
213
- )) ?? []
214
- ];
215
- return h(
216
- "div",
217
- { class: "flex flex-wrap items-center gap-3" },
218
- buttons
219
- );
220
- });
62
+ }
63
+ async function handleExportExcel() {
64
+ await excelExportModal.open({
65
+ columns: props.rawBizColumns,
66
+ filename: props.exportExcel.filename,
67
+ filenameWithDateTime: props.exportExcel.filenameWithDateTime,
68
+ listFn: props.apiGroup?.().countAndList,
69
+ whereQueryOptions: props.whereQueryProps.whereOptions,
70
+ extraWhereQueryInitValues: defu(props.extraWhereQueryInitValues, props.exportExcel.extraWhereQueryInitValues)
71
+ });
72
+ }
73
+ async function handleBatchDelete() {
74
+ const result = await deleteModal.open({
75
+ ids: props.selectedIds,
76
+ models: props.selectedModels,
77
+ displayFn: props.displayFnInDeleteModal,
78
+ onDelete: (ids) => props.apiGroup?.().batchDelete({ ids })
79
+ }).result;
80
+ if (result) {
81
+ await props.fetchList();
82
+ }
83
+ }
84
+ async function onLeftExtraButtonClick(btn, e) {
85
+ if (btn.withBatchData && props.selectedIds && btn.batchFn) {
86
+ await btn.batchFn(props.selectedIds);
87
+ await props.fetchList();
88
+ }
89
+ const originalOnClick = btn.button.onClick;
90
+ if (Array.isArray(originalOnClick)) {
91
+ originalOnClick.forEach((fn) => fn?.(e));
92
+ } else {
93
+ originalOnClick?.(e);
94
+ }
95
+ }
96
+ async function onRightExtraButtonClick(btn, e) {
97
+ if (btn.withBatchData && props.selectedIds && btn.batchFn) {
98
+ await btn.batchFn(props.selectedIds);
99
+ }
100
+ const originalOnClick = btn.button.onClick;
101
+ if (Array.isArray(originalOnClick)) {
102
+ originalOnClick.forEach((fn) => fn?.(e));
103
+ } else {
104
+ originalOnClick?.(e);
105
+ }
106
+ }
221
107
  </script>
222
108
 
223
109
  <template>
224
- <component :is="oprButtons" />
110
+ <div class="flex flex-wrap items-center gap-3">
111
+ <template v-for="(btn, i) in extraButtons" :key="`extra-left-${i}`">
112
+ <UButton
113
+ v-if="(!btn.withBatchData || selectedIds?.length) && btn.appendTo === 'left'"
114
+ v-bind="omitOnClick(btn.button)"
115
+ @click="(e) => onLeftExtraButtonClick(btn, e)"
116
+ />
117
+ </template>
118
+
119
+ <template v-for="opr in oprOrder" :key="opr">
120
+ <UButton
121
+ v-if="opr === 'create' && !disableCreation"
122
+ icon="i-lucide-plus"
123
+ :size="size"
124
+ @click="handleCreate"
125
+ >
126
+ 新增
127
+ </UButton>
128
+
129
+ <UButton
130
+ v-if="opr === 'refresh'"
131
+ icon="i-lucide-refresh-ccw"
132
+ :size="size"
133
+ color="neutral"
134
+ :loading="fetching"
135
+ variant="outline"
136
+ @click="fetchList"
137
+ >
138
+ 刷新
139
+ </UButton>
140
+
141
+ <UChip
142
+ v-if="opr === 'whereQuery' && !disableWhereQuery"
143
+ :show="!whereQueryProps.isWhereQueryValueEmpty"
144
+ >
145
+ <UButton
146
+ icon="i-lucide-list-filter"
147
+ :size="size"
148
+ :color="whereQueryProps.whereQueryOpen ? 'primary' : 'neutral'"
149
+ :loading="fetching"
150
+ variant="outline"
151
+ @click="whereQueryProps.onUpdateWhereQueryOpen?.(!whereQueryProps.whereQueryOpen)"
152
+ >
153
+ 查询
154
+ </UButton>
155
+ </UChip>
156
+
157
+ <TableQueryOrder
158
+ v-if="opr === 'orderQuery' && !disableOrderQuery"
159
+ v-bind="orderQueryProps"
160
+ :size="size"
161
+ />
162
+
163
+ <UButton
164
+ v-if="opr === 'settings' && !disableSettings && rawBizColumns?.length && onUpdateBizColumns"
165
+ icon="i-lucide-settings"
166
+ :size="size"
167
+ color="neutral"
168
+ variant="outline"
169
+ @click="handleSettings"
170
+ >
171
+ 设置
172
+ </UButton>
173
+
174
+ <template v-if="opr === 'exportExcel' && exportExcel">
175
+ <PermissionWrapper v-if="exportExcel.permissionKey" :permission="exportExcel.permissionKey">
176
+ <UButton
177
+ icon="i-lucide-sheet"
178
+ :size="size"
179
+ color="neutral"
180
+ variant="outline"
181
+ @click="handleExportExcel"
182
+ >
183
+ 导出
184
+ </UButton>
185
+ </PermissionWrapper>
186
+ <UButton
187
+ v-else
188
+ icon="i-lucide-sheet"
189
+ :size="size"
190
+ color="neutral"
191
+ variant="outline"
192
+ @click="handleExportExcel"
193
+ >
194
+ 导出
195
+ </UButton>
196
+ </template>
197
+
198
+ <UButton
199
+ v-if="opr === 'batchDelete' && !disableBatchDeletion && selectedIds?.length"
200
+ icon="i-lucide-trash-2"
201
+ :size="size"
202
+ color="error"
203
+ variant="outline"
204
+ @click="handleBatchDelete"
205
+ >
206
+ 批量删除
207
+ <template #trailing>
208
+ <UKbd size="sm">
209
+ {{ selectedIds?.length ?? 0 }}
210
+ </UKbd>
211
+ </template>
212
+ </UButton>
213
+ </template>
214
+
215
+ <template v-for="(btn, i) in extraButtons" :key="`extra-right-${i}`">
216
+ <UButton
217
+ v-if="(!btn.withBatchData || selectedIds?.length) && btn.appendTo === 'right'"
218
+ v-bind="omitOnClick(btn.button)"
219
+ @click="(e) => onRightExtraButtonClick(btn, e)"
220
+ />
221
+ </template>
222
+ </div>
225
223
  </template>
@@ -43,7 +43,8 @@ const props = defineProps({
43
43
  expandable: { type: Boolean, required: false },
44
44
  expandVNode: { type: Function, required: false },
45
45
  rowSpanColumns: { type: Array, required: false },
46
- customRowCopyFn: { type: Function, required: false }
46
+ customRowCopyFn: { type: Function, required: false },
47
+ displayFnInDeleteModal: { type: Function, required: false }
47
48
  });
48
49
  const {
49
50
  // data
@@ -0,0 +1,2 @@
1
+ export * from './useForm.js';
2
+ export * from './useFormUpdate.js';
@@ -0,0 +1,2 @@
1
+ export * from "./useForm.js";
2
+ export * from "./useFormUpdate.js";
@@ -0,0 +1,21 @@
1
+ import { type Ref, type MaybeRefOrGetter } from 'vue';
2
+ import type { ApiGroup, BaseModel, VFormFieldProps } from '#v/types';
3
+ import type { ConfirmDiffItem } from '#v/components/form/save-model-template/ConfirmUpdateModal.vue';
4
+ export declare const useFormValues: <T>(raw: Ref<T>, defaultValues?: Partial<T>) => {
5
+ oldValues: Ref<T>;
6
+ newValues: Ref<T>;
7
+ };
8
+ export declare const useFormSubmission: <T extends BaseModel>(oldValues: Ref<T>, newValues: Ref<T>, close: (ok: boolean) => void, save: (model: T) => void, apiGroup: () => ApiGroup<T>, arrayTypeFieldKeys?: (keyof T)[], rowKey?: keyof T, versionKey?: keyof T, getExtraFields?: () => Record<string, any>) => {
9
+ onSubmit: () => Promise<void>;
10
+ };
11
+ export declare const useConfirmDiff: (fields: MaybeRefOrGetter<VFormFieldProps[]>, diffItems: MaybeRefOrGetter<ConfirmDiffItem[]>, oldModelValue: MaybeRefOrGetter<Record<string, unknown>>, newModelValue: MaybeRefOrGetter<Record<string, unknown>>) => {
12
+ diffedItems: import("vue").ComputedRef<{
13
+ oldDisplay: string;
14
+ newDisplay: string;
15
+ parts: import("diff").ChangeObject<string>[];
16
+ field: VFormFieldProps | undefined;
17
+ fieldName: string;
18
+ oldValue: unknown;
19
+ newValue: unknown;
20
+ }[]>;
21
+ };
@@ -1,6 +1,7 @@
1
- import { ref, watch } from "vue";
1
+ import { ref, watch, computed, toValue } from "vue";
2
2
  import { defu } from "defu";
3
3
  import { getObjWithModifiedFields } from "#v/utils";
4
+ import { resolveDisplayValue, smartDiff } from "#v/composables";
4
5
  import { useToast } from "@nuxt/ui/composables";
5
6
  export const useFormValues = (raw, defaultValues) => {
6
7
  const oldValues = ref({});
@@ -62,3 +63,24 @@ export const useFormSubmission = (oldValues, newValues, close, save, apiGroup, a
62
63
  }
63
64
  return { onSubmit };
64
65
  };
66
+ export const useConfirmDiff = (fields, diffItems, oldModelValue, newModelValue) => {
67
+ const resolvedItems = computed(
68
+ () => toValue(diffItems).map((item) => ({
69
+ ...item,
70
+ field: toValue(fields).find((f) => f.name === item.fieldName)
71
+ })).filter((item) => item.field != null)
72
+ );
73
+ const diffedItems = computed(
74
+ () => resolvedItems.value.map((item) => {
75
+ const oldDisplay = resolveDisplayValue(item.field, item.oldValue, toValue(oldModelValue));
76
+ const newDisplay = resolveDisplayValue(item.field, item.newValue, toValue(newModelValue));
77
+ return {
78
+ ...item,
79
+ oldDisplay,
80
+ newDisplay,
81
+ parts: smartDiff(oldDisplay, newDisplay)
82
+ };
83
+ })
84
+ );
85
+ return { diffedItems };
86
+ };
@@ -0,0 +1,6 @@
1
+ import type { VFormFieldProps } from '#v/types';
2
+ export declare function resolveSelectLabel(rawVal: unknown, items: readonly any[]): string;
3
+ export declare function resolveDisplayValue(field: VFormFieldProps, rawVal: unknown, modelSource: Record<string, unknown>): string;
4
+ export declare function smartDiff(oldStr: string, newStr: string): import("diff").ChangeObject<string>[];
5
+ export declare const diffEligibleTypes: Set<string>;
6
+ export declare function formatValue(val: unknown): string;