v-nuxt-ui 0.2.30 → 0.2.32

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 (29) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/AsyncSelect.vue +1 -1
  3. package/dist/runtime/components/Select.vue +1 -1
  4. package/dist/runtime/components/badge/CodeModal.client.vue +5 -1
  5. package/dist/runtime/components/date-picker/Input.vue +0 -1
  6. package/dist/runtime/components/layout/default.vue +2 -1
  7. package/dist/runtime/components/sys/user/Table.vue +9 -1
  8. package/dist/runtime/components/table/Page.vue +33 -10
  9. package/dist/runtime/components/table/header/index.vue +3 -0
  10. package/dist/runtime/components/table/index.vue +2 -0
  11. package/dist/runtime/components/table/query/where/Newer.vue +28 -3
  12. package/dist/runtime/components/table/query/where/index.vue +231 -61
  13. package/dist/runtime/components/table/query/where/simple/item/ColumnPicker.vue +34 -10
  14. package/dist/runtime/components/table/query/where/simple/item/index.d.vue.ts +1 -0
  15. package/dist/runtime/components/table/query/where/simple/item/index.vue +11 -2
  16. package/dist/runtime/components/table/query/where/simple/item/index.vue.d.ts +1 -0
  17. package/dist/runtime/components/table/query/where/simple/item/opr/DatePicker.vue +15 -8
  18. package/dist/runtime/components/table/query/where/simple/item/opr/Input.vue +3 -0
  19. package/dist/runtime/composables/table/useTable.js +4 -0
  20. package/dist/runtime/composables/table/useTableQuery.js +4 -2
  21. package/dist/runtime/composables/table/useTableView.d.ts +1 -0
  22. package/dist/runtime/composables/table/useTableView.js +1 -0
  23. package/dist/runtime/constants/options.js +2 -2
  24. package/dist/runtime/types/components/table/column.d.ts +1 -0
  25. package/dist/runtime/types/components/table/header.d.ts +2 -0
  26. package/dist/runtime/types/components/table/index.d.ts +2 -0
  27. package/dist/runtime/types/components/table/query/where.d.ts +1 -0
  28. package/dist/runtime/types/storage.d.ts +1 -0
  29. package/package.json +2 -2
package/dist/module.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "dependencies": [
8
8
  "@nuxt/ui"
9
9
  ],
10
- "version": "0.2.30",
10
+ "version": "0.2.32",
11
11
  "builder": {
12
12
  "@nuxt/module-builder": "1.0.2",
13
13
  "unbuild": "3.6.1"
@@ -119,7 +119,7 @@ const onSelect = (values) => {
119
119
  }
120
120
  };
121
121
  const ui = computed(() => ({
122
- root: ["min-w-32", props.roundedNone && "rounded-none"].filter(Boolean).join(" "),
122
+ root: [props.roundedNone && "rounded-none"].filter(Boolean).join(" "),
123
123
  base: "peer",
124
124
  content: "min-w-fit",
125
125
  tagsItem: "max-w-48 inline-flex min-w-0",
@@ -30,7 +30,7 @@ const filteredItems = computed(() => {
30
30
  return props.items.filter((item) => getItemLabel(item).toLowerCase().includes(searchTerm.value.toLowerCase()));
31
31
  });
32
32
  const ui = computed(() => ({
33
- root: ["min-w-32", props.roundedNone && "rounded-none"].filter(Boolean).join(" "),
33
+ root: [props.roundedNone && "rounded-none"].filter(Boolean).join(" "),
34
34
  base: "peer",
35
35
  content: "min-w-fit",
36
36
  tagsInput: "min-w-4 w-0"
@@ -47,7 +47,11 @@ watch([codeText, () => colorMode.value], updateHighlight, { immediate: true });
47
47
  variant="subtle"
48
48
  :label="btnTxt"
49
49
  class="cursor-pointer"
50
- @click="open = true"
50
+ @click="(e) => {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ open = true;
54
+ }"
51
55
  />
52
56
  <template #body>
53
57
  <div class="overflow-hidden text-sm">
@@ -58,7 +58,6 @@ defineExpose({
58
58
  v-model="inputBuffer"
59
59
  v-maska="'####-##-##'"
60
60
  size="sm"
61
- class="font-semibold"
62
61
  :class="inputClass"
63
62
  :icon="icon"
64
63
  :placeholder="placeholder"
@@ -46,7 +46,8 @@ watch(
46
46
  body: 'px-0 sm:px-0',
47
47
  footer: 'h-(--ui-footer-height)',
48
48
  container: 'ease-in-out duration-360',
49
- gap: 'ease-in-out duration-360'
49
+ gap: 'ease-in-out duration-360',
50
+ inner: 'bg-default'
50
51
  }"
51
52
  >
52
53
  <template #header="{ state }">
@@ -54,6 +54,7 @@ const columns = [
54
54
  header: "\u76F4\u5C5E\u4E0A\u7EA7",
55
55
  sortOption: true,
56
56
  cell: ({ row }) => row.original.supervisor?.nickname,
57
+ preferred: false,
57
58
  filterOption: {
58
59
  type: "async-select",
59
60
  listApi: useUserApi().list,
@@ -66,7 +67,8 @@ const columns = [
66
67
  },
67
68
  {
68
69
  accessorKey: "roles",
69
- header: "\u7CFB\u7EDF\u6743\u9650\u89D2\u8272",
70
+ header: "\u6743\u9650\u89D2\u8272",
71
+ preferred: false,
70
72
  cell: ({ row }) => row.original.roles?.map((role) => role.name).join(", "),
71
73
  filterOption: {
72
74
  type: "async-select",
@@ -97,6 +99,7 @@ const columns = [
97
99
  type: "select",
98
100
  items: loginTypeOptions
99
101
  },
102
+ preferred: false,
100
103
  meta: {
101
104
  class: {
102
105
  td: "min-w-24"
@@ -113,9 +116,11 @@ const columns = [
113
116
  {
114
117
  accessorKey: "needFillWh",
115
118
  header: "\u662F\u5426\u9700\u8981\u586B\u5199\u5DE5\u65F6",
119
+ preferred: false,
116
120
  filterOption: {
117
121
  type: "select",
118
122
  items: booleanOptions,
123
+ multiple: true,
119
124
  empty: {
120
125
  label: "\u5426",
121
126
  variant: "outline",
@@ -146,6 +151,7 @@ const columns = [
146
151
  accessorKey: "entryDate",
147
152
  header: "\u5165\u804C\u65F6\u95F4",
148
153
  sortOption: true,
154
+ preferred: false,
149
155
  filterOption: {
150
156
  type: "date-picker"
151
157
  },
@@ -154,6 +160,7 @@ const columns = [
154
160
  {
155
161
  accessorKey: "resignDate",
156
162
  header: "\u79BB\u804C\u65F6\u95F4",
163
+ preferred: false,
157
164
  filterOption: {
158
165
  type: "date-picker"
159
166
  },
@@ -176,6 +183,7 @@ const columns = [
176
183
  accessorKey: "isAdmin",
177
184
  header: "\u662F\u5426\u662F\u7CFB\u7EDF\u7BA1\u7406\u5458",
178
185
  sortOption: true,
186
+ preferred: false,
179
187
  filterOption: {
180
188
  type: "select",
181
189
  items: booleanOptions
@@ -1,5 +1,7 @@
1
1
  <script setup>
2
+ import { useId } from "vue";
2
3
  import { useProTableView } from "#v/composables/table/useTableView";
4
+ import USlideover from "@nuxt/ui/components/Slideover.vue";
3
5
  import TableHeader from "#v/components/table/header/index.vue";
4
6
  import TableQueryWhere from "#v/components/table/query/where/index.vue";
5
7
  import TablePagination from "#v/components/table/Pagination.vue";
@@ -25,6 +27,8 @@ const props = defineProps({
25
27
  extraButtons: { type: Array, required: false },
26
28
  onNew: { type: Function, required: false },
27
29
  disableWhereQuery: { type: Boolean, required: false },
30
+ whereQueryMode: { type: String, required: false },
31
+ whereQueryPopoverWidth: { type: [String, Number], required: false },
28
32
  whereQueryOpen: { type: Boolean, required: false },
29
33
  onUpdateWhereQueryOpen: { type: Function, required: false },
30
34
  extraWhereQueryOptions: { type: Array, required: false },
@@ -48,6 +52,7 @@ const props = defineProps({
48
52
  customRowCopyFn: { type: Function, required: false },
49
53
  displayFnInDeleteModal: { type: Function, required: false }
50
54
  });
55
+ const tablePortalId = `v-table-portal-${useId().replace(/[^A-Za-z0-9_-]/g, "-")}`;
51
56
  const {
52
57
  // data
53
58
  data,
@@ -77,9 +82,9 @@ defineExpose({ createRow, updateRow, deleteRow, refresh: fetchList, stats, data
77
82
  </script>
78
83
 
79
84
  <template>
80
- <div class="flex-1 flex flex-col overflow-hidden">
85
+ <div class="flex-1 flex flex-col overflow-hidden divide-y divide-default">
81
86
  <!-- header -->
82
- <div class="h-(--ui-header-height) flex items-center pl-2.5 pr-2.5 border-b border-default z-1">
87
+ <div class="h-(--ui-header-height) flex items-center pl-2.5 pr-2.5 z-3 bg-default">
83
88
  <div class="flex items-center gap-1">
84
89
  <LayoutButtonCollapse />
85
90
  <span class="font-bold">{{ cnName }}</span>
@@ -87,15 +92,8 @@ defineExpose({ createRow, updateRow, deleteRow, refresh: fetchList, stats, data
87
92
  <TableHeader v-bind="tblHeaderProps" class="ml-auto" />
88
93
  </div>
89
94
 
90
- <!-- query where -->
91
- <UCollapsible :open="tblHeaderProps.whereQueryProps.whereQueryOpen">
92
- <template #content>
93
- <TableQueryWhere ref="proTableQueryWhere" v-bind="tblWhereQueryProps" class="bg-muted border-b border-default" />
94
- </template>
95
- </UCollapsible>
96
-
97
95
  <!-- table -->
98
- <div ref="table" class="flex-1 flex flex-col h-full overflow-hidden border-b border-default">
96
+ <div :id="tablePortalId" ref="table" class="relative flex-1 flex flex-col h-full overflow-hidden">
99
97
  <!-- table -->
100
98
  <UContextMenu :items="tblContextMenuItems">
101
99
  <ScrollArea class="flex-1">
@@ -126,5 +124,30 @@ defineExpose({ createRow, updateRow, deleteRow, refresh: fetchList, stats, data
126
124
 
127
125
  <!-- pagination -->
128
126
  <TablePagination v-bind="tblPaginationProps" />
127
+
128
+ <!-- query where (slideover from top, portalled into table) -->
129
+ <USlideover
130
+ :open="tblHeaderProps.whereQueryProps.whereQueryOpen"
131
+ side="top"
132
+ :close="false"
133
+ :portal="`#${tablePortalId}`"
134
+ :ui="{
135
+ content: '!absolute z-2',
136
+ body: 'p-0 sm:p-0',
137
+ overlay: 'z-1'
138
+ }"
139
+ @update:open="tblHeaderProps.whereQueryProps.onUpdateWhereQueryOpen"
140
+ >
141
+ <template #body>
142
+ <TableQueryWhere
143
+ ref="proTableQueryWhere"
144
+ v-bind="tblWhereQueryProps"
145
+ :trigger-fetching="async (fromStart) => {
146
+ await tblWhereQueryProps.triggerFetching(fromStart);
147
+ tblHeaderProps.whereQueryProps.onUpdateWhereQueryOpen?.(false);
148
+ }"
149
+ />
150
+ </template>
151
+ </USlideover>
129
152
  </div>
130
153
  </template>
@@ -25,6 +25,8 @@ const props = defineProps({
25
25
  displayFnInDeleteModal: { type: Function, required: false },
26
26
  disableCreation: { type: Boolean, required: false },
27
27
  disableWhereQuery: { type: Boolean, required: false },
28
+ whereQueryMode: { type: String, required: false },
29
+ whereQueryPopoverWidth: { type: [String, Number], required: false },
28
30
  whereQueryProps: { type: Object, required: true },
29
31
  disableOrderQuery: { type: Boolean, required: false },
30
32
  orderQueryProps: { type: Object, required: true },
@@ -139,6 +141,7 @@ async function onRightExtraButtonClick(btn, e) {
139
141
  刷新
140
142
  </UButton>
141
143
 
144
+ <!-- whereQuery: toggle panel -->
142
145
  <UChip
143
146
  v-if="opr === 'whereQuery' && !disableWhereQuery"
144
147
  :show="!whereQueryProps.isWhereQueryValueEmpty"
@@ -24,6 +24,8 @@ const props = defineProps({
24
24
  extraButtons: { type: Array, required: false },
25
25
  onNew: { type: Function, required: false },
26
26
  disableWhereQuery: { type: Boolean, required: false },
27
+ whereQueryMode: { type: String, required: false },
28
+ whereQueryPopoverWidth: { type: [String, Number], required: false },
27
29
  whereQueryOpen: { type: Boolean, required: false },
28
30
  onUpdateWhereQueryOpen: { type: Function, required: false },
29
31
  extraWhereQueryOptions: { type: Array, required: false },
@@ -10,7 +10,7 @@ const props = defineProps({
10
10
  });
11
11
  const emit = defineEmits(["new"]);
12
12
  const popoverOpen = ref(false);
13
- const unselectedOptions = computed(() => props.unselectedFields.map((field) => {
13
+ function buildFieldItem(field) {
14
14
  const option = props.options.find((option2) => option2.field === field);
15
15
  const column = props.bizColumns.find((column2) => column2["accessorKey"] === field);
16
16
  return {
@@ -21,12 +21,37 @@ const unselectedOptions = computed(() => props.unselectedFields.map((field) => {
21
21
  popoverOpen.value = false;
22
22
  }
23
23
  };
24
- }));
24
+ }
25
+ const unselectedOptions = computed(() => {
26
+ const commonFields = [];
27
+ const otherFields = [];
28
+ props.unselectedFields.forEach((field) => {
29
+ const option = props.options.find((opt) => opt.field === field);
30
+ if (option?.preferred !== false) {
31
+ commonFields.push(field);
32
+ } else {
33
+ otherFields.push(field);
34
+ }
35
+ });
36
+ const items = [];
37
+ if (commonFields.length > 0) {
38
+ items.push({ type: "label", label: "\u5E38\u7528\u6761\u4EF6" });
39
+ commonFields.forEach((field) => items.push(buildFieldItem(field)));
40
+ }
41
+ if (otherFields.length > 0) {
42
+ if (items.length > 0) {
43
+ items.push({ type: "separator" });
44
+ }
45
+ items.push({ type: "label", label: "\u5176\u4ED6\u6761\u4EF6" });
46
+ otherFields.forEach((field) => items.push(buildFieldItem(field)));
47
+ }
48
+ return items;
49
+ });
25
50
  </script>
26
51
 
27
52
  <template>
28
53
  <!-- NOTE: 自己实现DropdownMenu, 原生DropdownMenu的Focus有问题,会让查询字段打开的Popover关闭 -->
29
- <ButtonDropdown :items="unselectedOptions">
54
+ <ButtonDropdown :items="unselectedOptions" class="w-fit h-fit">
30
55
  <UButton
31
56
  label="新增"
32
57
  :size="size"
@@ -1,7 +1,10 @@
1
1
  <script setup>
2
- import { computed, ref, nextTick } from "vue";
2
+ import { computed, ref, reactive, watch, nextTick } from "vue";
3
3
  import { useTableOpr } from "#v/composables/table/useTableOpr";
4
4
  import { useToast } from "@nuxt/ui/composables";
5
+ import UFieldGroup from "@nuxt/ui/components/FieldGroup.vue";
6
+ import UDropdownMenu from "@nuxt/ui/components/DropdownMenu.vue";
7
+ import Dnd from "#v/components/Dnd.client.vue";
5
8
  import TableQueryWhereSimpleItem from "#v/components/table/query/where/simple/item/index.vue";
6
9
  import TableQueryWhereNewer from "#v/components/table/query/where/Newer.vue";
7
10
  const props = defineProps({
@@ -24,6 +27,16 @@ const selectedWhereFields = computed(() => {
24
27
  const unselectedWhereFields = computed(() => {
25
28
  return props.whereOptions.map((option) => option.field).filter((field) => !selectedWhereFields.value.includes(field));
26
29
  });
30
+ const unselectedPreferredFields = computed(
31
+ () => unselectedWhereFields.value.filter(
32
+ (field) => props.whereOptions.find((opt) => opt.field === field)?.preferred !== false
33
+ )
34
+ );
35
+ const unselectedOtherFields = computed(
36
+ () => unselectedWhereFields.value.filter(
37
+ (field) => props.whereOptions.find((opt) => opt.field === field)?.preferred === false
38
+ )
39
+ );
27
40
  const itemRefMap = ref(/* @__PURE__ */ new Map());
28
41
  function setItemRef(field, el) {
29
42
  if (el && "focus" in el && typeof el.focus === "function") {
@@ -63,18 +76,133 @@ const onNewField = (field) => {
63
76
  }
64
77
  });
65
78
  };
66
- const isWhereQueryEmpty = computed(() => {
67
- return !props.whereQuery || Object.keys(props.whereQuery ?? {}).length === 0 || // 仅检查有对应option的item的值是否为空
68
- (props.whereQuery?.items?.filter((query) => props.whereOptions.find((option) => option.field === query.field)).length ?? 0) === 0 && (props.whereQuery?.groups?.length ?? 0) === 0;
69
- });
79
+ const getDefaultKeys = () => props.extraWhereQueryInitValues?.items?.map((query) => query.field) ?? [];
70
80
  const whereQueryWithoutInitValues = computed(() => {
71
81
  if (!props.whereQuery) return [];
72
- const defaultKeys = props.extraWhereQueryInitValues?.items?.map((query) => query.field) ?? [];
82
+ const defaultKeys = getDefaultKeys();
73
83
  return props.whereQuery.items?.filter((query) => {
74
84
  const field = query.field;
75
85
  return !defaultKeys.includes(field);
76
86
  }) ?? [];
77
87
  });
88
+ const whereQuerySectionExtraDataKey = "__whereQuerySection";
89
+ function getBaseItemSection(item) {
90
+ return props.whereOptions.find((opt) => opt.field === item.field)?.preferred === false ? "other" : "preferred";
91
+ }
92
+ function getItemSectionOverride(item) {
93
+ if (!item.extraData || typeof item.extraData !== "object" || Array.isArray(item.extraData)) return void 0;
94
+ const section = item.extraData[whereQuerySectionExtraDataKey];
95
+ return section === "preferred" || section === "other" ? section : void 0;
96
+ }
97
+ function getItemSection(item) {
98
+ return getItemSectionOverride(item) ?? getBaseItemSection(item);
99
+ }
100
+ function isPreferredItem(item) {
101
+ return getItemSection(item) === "preferred";
102
+ }
103
+ function setItemSection(item, section) {
104
+ const baseSection = getBaseItemSection(item);
105
+ if (item.extraData && (typeof item.extraData !== "object" || Array.isArray(item.extraData))) {
106
+ return item;
107
+ }
108
+ const extraData = item.extraData ? { ...item.extraData } : {};
109
+ if (section === baseSection) {
110
+ const { [whereQuerySectionExtraDataKey]: _section, ...restExtraData } = extraData;
111
+ return {
112
+ ...item,
113
+ extraData: Object.keys(restExtraData).length > 0 ? restExtraData : void 0
114
+ };
115
+ } else {
116
+ ;
117
+ extraData[whereQuerySectionExtraDataKey] = section;
118
+ }
119
+ return {
120
+ ...item,
121
+ extraData: Object.keys(extraData).length > 0 ? extraData : void 0
122
+ };
123
+ }
124
+ const preferredItems = computed(
125
+ () => whereQueryWithoutInitValues.value.filter(isPreferredItem)
126
+ );
127
+ const otherItems = computed(
128
+ () => whereQueryWithoutInitValues.value.filter((item) => !isPreferredItem(item))
129
+ );
130
+ const preferredDndItems = ref([]);
131
+ const otherDndItems = ref([]);
132
+ watch([preferredItems, otherItems], () => {
133
+ preferredDndItems.value = [...preferredItems.value];
134
+ otherDndItems.value = [...otherItems.value];
135
+ }, { immediate: true });
136
+ function onDndEnd() {
137
+ const defaultKeys = getDefaultKeys();
138
+ const initItems = props.whereQuery?.items?.filter((q) => defaultKeys.includes(q.field)) ?? [];
139
+ props.onUpdateWhereQuery({
140
+ ...props.whereQuery,
141
+ items: [
142
+ ...initItems,
143
+ ...preferredDndItems.value.map((item) => setItemSection(item, "preferred")),
144
+ ...otherDndItems.value.map((item) => setItemSection(item, "other"))
145
+ ]
146
+ });
147
+ }
148
+ function onUpdateWhereQueryItem(field, newWhereQueryItem) {
149
+ const items = props.whereQuery?.items ?? [];
150
+ const realIdx = items.findIndex((query) => query.field === field);
151
+ if (realIdx === -1) return;
152
+ const currentItem = items[realIdx];
153
+ if (!currentItem) return;
154
+ const currentSection = getItemSection(currentItem);
155
+ const updatedItems = [...items];
156
+ updatedItems[realIdx] = setItemSection(newWhereQueryItem, currentSection);
157
+ props.onUpdateWhereQuery({
158
+ ...props.whereQuery,
159
+ items: updatedItems
160
+ });
161
+ }
162
+ const rangeOprList = ["range_gt_lt", "range_gt_lte", "range_gte_lt", "range_gte_lte"];
163
+ const isDateRangeQueryItem = (item) => {
164
+ const option = props.whereOptions.find((option2) => option2.field === item.field);
165
+ return option?.type === "date-picker" && rangeOprList.includes(item.opr);
166
+ };
167
+ const onClearValues = () => {
168
+ if (!props.whereQuery?.items) return;
169
+ props.onUpdateWhereQuery({
170
+ ...props.whereQuery,
171
+ items: props.whereQuery.items.map((item) => ({ ...item, value: null }))
172
+ });
173
+ };
174
+ const onResetAll = () => {
175
+ props.onUpdateWhereQuery(props.defaultWhereQuery);
176
+ };
177
+ const onFillMissingFields = () => {
178
+ const currentFields = new Set((props.whereQuery?.items ?? []).map((item) => item.field));
179
+ const missingOptions = props.whereOptions.filter((opt) => !currentFields.has(opt.field));
180
+ if (missingOptions.length === 0) return;
181
+ const preferred = missingOptions.filter((opt) => opt.preferred !== false);
182
+ const other = missingOptions.filter((opt) => opt.preferred === false);
183
+ const newItems = [...preferred, ...other].map((opt) => ({
184
+ field: opt.field,
185
+ opr: opt.defaultOpr ?? useTableOpr().getDefaultOprByType(opt.type),
186
+ value: null,
187
+ custom: opt.custom
188
+ }));
189
+ props.onUpdateWhereQuery({
190
+ ...props.whereQuery,
191
+ items: [...props.whereQuery?.items ?? [], ...newItems]
192
+ });
193
+ };
194
+ const onRemoveAllFields = () => {
195
+ const defaultKeys = props.extraWhereQueryInitValues?.items?.map((q) => q.field) ?? [];
196
+ const initItems = props.whereQuery?.items?.filter((q) => defaultKeys.includes(q.field)) ?? [];
197
+ props.onUpdateWhereQuery({
198
+ ...props.whereQuery,
199
+ items: initItems.map((item) => ({ ...item, value: null }))
200
+ });
201
+ };
202
+ const moreActions = computed(() => [
203
+ { label: "\u8865\u5168\u5B57\u6BB5", icon: "i-lucide-list-plus", onSelect: onFillMissingFields },
204
+ { label: "\u5220\u9664\u6240\u6709\u5B57\u6BB5", icon: "i-lucide-trash-2", onSelect: onRemoveAllFields }
205
+ ]);
78
206
  const focusField = (field) => {
79
207
  const item = itemRefMap.value.get(field);
80
208
  if (item) {
@@ -83,67 +211,109 @@ const focusField = (field) => {
83
211
  }
84
212
  return false;
85
213
  };
214
+ const conditionListClass = "grid grid-cols-24 gap-3";
215
+ const sections = reactive([
216
+ { key: "preferred", label: "\u5E38\u7528\u6761\u4EF6", dndItems: preferredDndItems, unselectedFields: unselectedPreferredFields },
217
+ { key: "other", label: "\u5176\u4ED6\u6761\u4EF6", dndItems: otherDndItems, unselectedFields: unselectedOtherFields }
218
+ ]);
86
219
  defineExpose({ focusField });
87
220
  </script>
88
221
 
89
222
  <template>
90
- <div class="flex items-start gap-2 pl-4 pr-2.5 py-2.5">
91
- <!-- conditions -->
92
- <div class="flex flex-wrap items-center gap-2.5">
93
- <!-- key如果是field,那么field修改后,不能聚焦后面的组件,所以这里的key用idx代替 -->
94
- <template v-if="!isWhereQueryEmpty">
95
- <TableQueryWhereSimpleItem
96
- v-for="(item, idx) in whereQueryWithoutInitValues"
97
- :ref="(el) => setItemRef(item.field, el)"
98
- :key="idx"
99
- :where-query-item="item"
100
- :options="whereOptions"
101
- :fetching="fetching"
102
- :trigger-fetching="() => triggerFetching(true)"
103
- @remove="onRemoveFilter"
104
- @update:where-query-item="(newWhereQueryItem) => {
105
- const items = props.whereQuery?.items ?? [];
106
- const realIdx = items.findIndex((q) => q.field === item.field);
107
- if (realIdx === -1) return;
108
- const updatedItems = [...items];
109
- updatedItems[realIdx] = newWhereQueryItem;
110
- onUpdateWhereQuery({
111
- ...whereQuery,
112
- items: updatedItems
113
- });
114
- }"
115
- />
116
- </template>
117
- <TableQueryWhereNewer
118
- :options="whereOptions"
119
- :unselected-fields="unselectedWhereFields"
120
- :biz-columns="bizColumns ?? []"
121
- size="sm"
122
- @new="onNewField"
123
- />
124
- <USeparator orientation="vertical" class="h-4" />
125
- <UButton
126
- v-if="!hideQueryButton"
127
- label="查询"
128
- color="neutral"
129
- variant="subtle"
130
- size="sm"
131
- :loading="fetching"
132
- icon="i-lucide-search"
133
- @click="async () => {
223
+ <div class="divide-y divide-default">
224
+ <div class="@container p-2.5 space-y-4">
225
+ <div
226
+ v-for="section in sections"
227
+ :key="section.key"
228
+ >
229
+ <div class="font-bold text-xs text-dimmed mb-1.5">
230
+ {{ section.label }}
231
+ </div>
232
+ <Dnd
233
+ v-model="section.dndItems"
234
+ group="where-query"
235
+ handle=".where-query-handle"
236
+ :on-end="onDndEnd"
237
+ :class="conditionListClass"
238
+ >
239
+ <div
240
+ v-for="item in section.dndItems"
241
+ :key="item.field"
242
+ class="flex items-center gap-1 col-span-24 @3xl:col-span-12 @5xl:col-span-8 @7xl:col-span-6"
243
+ :class="isDateRangeQueryItem(item) ? '@3xl:col-span-24 @5xl:col-span-12 @7xl:col-span-8' : void 0"
244
+ >
245
+ <TableQueryWhereSimpleItem
246
+ :ref="(el) => setItemRef(item.field, el)"
247
+ :where-query-item="item"
248
+ :options="whereOptions"
249
+ :fetching="fetching"
250
+ :trigger-fetching="() => triggerFetching(true)"
251
+ handle-class-name="where-query-handle"
252
+ @remove="onRemoveFilter"
253
+ @update:where-query-item="(newWhereQueryItem) => onUpdateWhereQueryItem(item.field, newWhereQueryItem)"
254
+ />
255
+ </div>
256
+ <div class="col-span-24 @3xl:col-span-12 @5xl:col-span-8 @7xl:col-span-6">
257
+ <TableQueryWhereNewer
258
+ v-if="unselectedWhereFields.length > 0"
259
+ :options="whereOptions"
260
+ :unselected-fields="unselectedWhereFields"
261
+ :biz-columns="bizColumns ?? []"
262
+ size="sm"
263
+ @new="onNewField"
264
+ />
265
+ </div>
266
+ </Dnd>
267
+ </div>
268
+ </div>
269
+ <!-- action bar -->
270
+ <div class="flex items-center gap-2.5 p-2.5">
271
+ <div class="flex-1" />
272
+ <div class="flex items-center gap-2.5">
273
+ <UButton
274
+ v-if="!hideQueryButton"
275
+ label="查询"
276
+ color="neutral"
277
+ variant="subtle"
278
+ size="sm"
279
+ :loading="fetching"
280
+ icon="i-lucide-search"
281
+ @click="async () => {
134
282
  await triggerFetching(true);
135
283
  }"
136
- />
137
- <UButton
138
- color="neutral"
139
- variant="subtle"
140
- size="sm"
141
- icon="i-lucide-timer-reset"
142
- :disabled="fetching"
143
- @click="() => onUpdateWhereQuery(defaultWhereQuery)"
144
- >
145
- 重置
146
- </UButton>
284
+ />
285
+ <UButton
286
+ color="neutral"
287
+ variant="subtle"
288
+ size="sm"
289
+ icon="i-lucide-eraser"
290
+ :disabled="fetching"
291
+ @click="onClearValues"
292
+ >
293
+ 清空
294
+ </UButton>
295
+ </div>
296
+ <div class="flex-1 flex justify-end items-center">
297
+ <UFieldGroup size="sm">
298
+ <UButton
299
+ color="neutral"
300
+ variant="subtle"
301
+ icon="i-lucide-timer-reset"
302
+ :disabled="fetching"
303
+ @click="onResetAll"
304
+ >
305
+ 还原默认
306
+ </UButton>
307
+ <UDropdownMenu :items="moreActions">
308
+ <UButton
309
+ color="neutral"
310
+ variant="subtle"
311
+ icon="i-lucide-ellipsis"
312
+ :disabled="fetching"
313
+ />
314
+ </UDropdownMenu>
315
+ </UFieldGroup>
316
+ </div>
147
317
  </div>
148
318
  </div>
149
319
  </template>
@@ -9,17 +9,41 @@ const props = defineProps({
9
9
  focus: { type: Function, required: false }
10
10
  });
11
11
  const whereQueryItem = defineModel("whereQueryItem", { type: Object, ...{ required: true } });
12
- const items = computed(() => props.options.map((option) => ({
13
- label: option.label,
14
- value: option.field,
15
- icon: tableWhereQueryItemIconMap.get(option.type) || "field",
16
- onSelect: () => {
17
- modelValue.value = option.field;
18
- nextTick(() => {
19
- props.focus?.();
20
- });
12
+ const items = computed(() => {
13
+ const commonItems = [];
14
+ const otherItems = [];
15
+ props.options.forEach((option) => {
16
+ const item = {
17
+ label: option.label,
18
+ value: option.field,
19
+ icon: tableWhereQueryItemIconMap.get(option.type) || "field",
20
+ onSelect: () => {
21
+ modelValue.value = option.field;
22
+ nextTick(() => {
23
+ props.focus?.();
24
+ });
25
+ }
26
+ };
27
+ if (option.preferred === true) {
28
+ commonItems.push(item);
29
+ } else {
30
+ otherItems.push(item);
31
+ }
32
+ });
33
+ const result = [];
34
+ if (commonItems.length > 0) {
35
+ result.push({ type: "label", label: "\u5E38\u7528\u6761\u4EF6" });
36
+ result.push(...commonItems);
21
37
  }
22
- })));
38
+ if (otherItems.length > 0) {
39
+ if (result.length > 0) {
40
+ result.push({ type: "separator" });
41
+ }
42
+ result.push({ type: "label", label: "\u5176\u4ED6\u6761\u4EF6" });
43
+ result.push(...otherItems);
44
+ }
45
+ return result;
46
+ });
23
47
  const modelValue = computed({
24
48
  get() {
25
49
  return whereQueryItem.value.field;
@@ -5,6 +5,7 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
5
5
  fetching?: boolean;
6
6
  triggerFetching?: () => Promise<void>;
7
7
  onRemove: (field: string) => void;
8
+ handleClassName?: string;
8
9
  } & {
9
10
  whereQueryItem: WhereQueryItem<T>;
10
11
  }) & {
@@ -7,7 +7,8 @@ const props = defineProps({
7
7
  options: { type: Array, required: true },
8
8
  fetching: { type: Boolean, required: false },
9
9
  triggerFetching: { type: Function, required: false },
10
- onRemove: { type: Function, required: true }
10
+ onRemove: { type: Function, required: true },
11
+ handleClassName: { type: String, required: false }
11
12
  });
12
13
  const whereQueryItem = defineModel("whereQueryItem", { type: Object, ...{ required: true } });
13
14
  const option = computed(() => props.options.find((option2) => option2.field === whereQueryItem.value.field));
@@ -26,11 +27,18 @@ defineExpose({
26
27
  </script>
27
28
 
28
29
  <template>
29
- <UFieldGroup size="sm">
30
+ <UFieldGroup size="sm" class="w-full">
31
+ <UButton
32
+ variant="outline"
33
+ icon="i-lucide-grip-vertical"
34
+ color="neutral"
35
+ :class="handleClassName"
36
+ />
30
37
  <TableQueryWhereSimpleItemColumnPicker
31
38
  v-model:where-query-item="whereQueryItem"
32
39
  :options="options"
33
40
  :fetching="fetching"
41
+ disabled
34
42
  :focus="() => oprRef?.focus()"
35
43
  />
36
44
  <TableQueryWhereSimpleItemOprPicker
@@ -46,6 +54,7 @@ defineExpose({
46
54
  :options="options"
47
55
  :fetching="fetching"
48
56
  :trigger-fetching="triggerFetching"
57
+ class="w-full"
49
58
  />
50
59
  <UButton
51
60
  color="neutral"
@@ -5,6 +5,7 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
5
5
  fetching?: boolean;
6
6
  triggerFetching?: () => Promise<void>;
7
7
  onRemove: (field: string) => void;
8
+ handleClassName?: string;
8
9
  } & {
9
10
  whereQueryItem: WhereQueryItem<T>;
10
11
  }) & {
@@ -153,14 +153,16 @@ const dateRangeShortcuts = [
153
153
  <!-- 输入框直接暴露 -->
154
154
  <template v-if="isNoCalendarOpr" />
155
155
 
156
- <template v-else-if="isRangeOpr">
157
- <!-- 必须加这个div,不然第一个输入框无法正确渲染 -->
156
+ <UFieldGroup
157
+ v-else-if="isRangeOpr"
158
+ class="w-full"
159
+ >
158
160
  <div />
159
161
  <DatePickerInput
160
162
  ref="startDateStrValueInput"
161
163
  v-model:value="startDateStrValue"
162
164
  icon=""
163
- input-class="w-32"
165
+ input-class="min-w-28 flex-1"
164
166
  placeholder="YYYY-MM-DD"
165
167
  @focus="onOpenCalendar"
166
168
  @blur="onCloseCalendar"
@@ -172,25 +174,30 @@ const dateRangeShortcuts = [
172
174
  ref="endDateStrValueInput"
173
175
  v-model:value="endDateStrValue"
174
176
  icon=""
175
- input-class="w-32"
177
+ input-class="min-w-28 flex-1"
176
178
  placeholder="YYYY-MM-DD"
177
179
  @focus="onOpenCalendar"
178
180
  @blur="onCloseCalendar"
179
181
  />
180
- </template>
182
+ <div />
183
+ </UFieldGroup>
181
184
 
182
- <template v-else>
185
+ <div
186
+ v-else
187
+ class="w-full"
188
+ >
183
189
  <div />
184
190
  <DatePickerInput
185
191
  ref="singleDateStrValueInput"
186
192
  v-model:value="singleDateStrValue"
187
193
  icon=""
188
- input-class="w-32"
194
+ input-class="w-full min-w-32"
189
195
  placeholder="YYYY-MM-DD"
190
196
  @focus="onOpenCalendar"
191
197
  @blur="onCloseCalendar"
192
198
  />
193
- </template>
199
+ <div />
200
+ </div>
194
201
 
195
202
  <template #content>
196
203
  <div
@@ -36,6 +36,9 @@ defineExpose({
36
36
  :placeholder="`\u8BF7\u8F93\u5165${label.toString()}`"
37
37
  :disabled="disabled"
38
38
  color="neutral"
39
+ :ui="{
40
+ base: 'h-full'
41
+ }"
39
42
  @keyup.enter="async () => {
40
43
  await triggerFetching?.();
41
44
  }"
@@ -25,6 +25,8 @@ export function useTable(props) {
25
25
  onNew,
26
26
  // where query
27
27
  disableWhereQuery,
28
+ whereQueryMode,
29
+ whereQueryPopoverWidth,
28
30
  extraWhereQueryOptions,
29
31
  extraWhereQueryInitValues,
30
32
  // order query
@@ -316,6 +318,8 @@ export function useTable(props) {
316
318
  displayFnInDeleteModal,
317
319
  disableCreation,
318
320
  disableWhereQuery,
321
+ whereQueryMode,
322
+ whereQueryPopoverWidth,
319
323
  whereQueryProps: tblWhereQueryProps.value,
320
324
  disableOrderQuery,
321
325
  orderQueryProps: tblOrderQueryProps.value,
@@ -10,6 +10,7 @@ export function useTableQuery(props) {
10
10
  const options = bizColumns.filter((col) => col.filterOption).map((col) => ({
11
11
  field: col.accessorKey,
12
12
  label: col.header,
13
+ preferred: col.preferred,
13
14
  ...col.filterOption
14
15
  }));
15
16
  extraWhereQueryOptions?.forEach((option) => {
@@ -19,7 +20,7 @@ export function useTableQuery(props) {
19
20
  });
20
21
  const whereQueryInitValues = computed(() => {
21
22
  const initValues = {
22
- items: whereQueryOptions.value.filter((option) => option.initValues !== void 0 && option.initValues !== null || option.initHide === false).map((option) => ({
23
+ items: whereQueryOptions.value.map((option) => ({
23
24
  field: option.field,
24
25
  opr: option.defaultOpr ?? useTableOpr().getDefaultOprByType(option.type),
25
26
  value: option.initValues ?? null,
@@ -46,7 +47,8 @@ export function useTableQuery(props) {
46
47
  () => bizColumns.map((col) => ({
47
48
  accessorKey: col["accessorKey"],
48
49
  fixed: "unfixed",
49
- checked: !col.initHide
50
+ checked: !col.initHide,
51
+ preferred: col.preferred
50
52
  }))
51
53
  );
52
54
  const localStgSettings = useLocalStorage(`${name}-table-settings`, {
@@ -16,6 +16,7 @@ export interface UseProTableViewReturn<T> {
16
16
  tblPaginationProps: ComputedRef<TablePaginationProps<T>>;
17
17
  tblContextMenuItems: Ref<ContextMenuItem[]>;
18
18
  tableWidth: Ref<number>;
19
+ tableDiv: Ref<HTMLDivElement | null>;
19
20
  updateTableWidth: () => void;
20
21
  tblClasses: ComputedRef<(string | boolean | (string | boolean)[])[]>;
21
22
  tblUi: ComputedRef<{
@@ -145,6 +145,7 @@ export function useProTableView(props) {
145
145
  tblContextMenuItems,
146
146
  // view
147
147
  tableWidth,
148
+ tableDiv,
148
149
  updateTableWidth,
149
150
  tblClasses,
150
151
  tblUi,
@@ -1,6 +1,6 @@
1
1
  export const booleanOptions = [
2
- { label: "Yes", value: true },
3
- { label: "No", value: false }
2
+ { label: "\u662F", value: true },
3
+ { label: "\u5426", value: false }
4
4
  ];
5
5
  export var Gender = /* @__PURE__ */ ((Gender2) => {
6
6
  Gender2[Gender2["MALE"] = 1] = "MALE";
@@ -31,5 +31,6 @@ export type VColumn<T> = {
31
31
  sortOption?: OrderQueryColumnOption | true;
32
32
  initHide?: boolean;
33
33
  checked?: boolean;
34
+ preferred?: boolean;
34
35
  exportCell?(row: T): string | string[];
35
36
  } & TableColumn<T>;
@@ -17,6 +17,8 @@ export type TableHeaderProps<T> = {
17
17
  displayFnInDeleteModal?: (model: T) => string | undefined;
18
18
  disableCreation?: boolean;
19
19
  disableWhereQuery?: boolean;
20
+ whereQueryMode?: 'inline' | 'popover';
21
+ whereQueryPopoverWidth?: string | number;
20
22
  whereQueryProps: WhereQueryProps<T>;
21
23
  disableOrderQuery?: boolean;
22
24
  orderQueryProps: OrderQueryProps<T>;
@@ -82,6 +82,8 @@ export type VTableProps<T> = {
82
82
  extraButtons?: TableHeaderProps<T>['extraButtons'];
83
83
  onNew?: TableHeaderProps<T>['onNew'];
84
84
  disableWhereQuery?: TableHeaderProps<T>['disableWhereQuery'];
85
+ whereQueryMode?: 'inline' | 'popover';
86
+ whereQueryPopoverWidth?: string | number;
85
87
  whereQueryOpen?: boolean;
86
88
  onUpdateWhereQueryOpen?: (open: boolean) => void;
87
89
  extraWhereQueryOptions?: WhereQueryOption<T>[];
@@ -2,6 +2,7 @@ import type { VColumn, WhereQuery, WhereQueryColumnOption, WhereQueryItemGroup }
2
2
  export type WhereQueryOption<T> = {
3
3
  field: string & keyof T | string;
4
4
  label: string;
5
+ preferred?: boolean;
5
6
  } & WhereQueryColumnOption<T>;
6
7
  export type WhereQueryProps<T> = {
7
8
  whereOptions: WhereQueryOption<T>[];
@@ -13,6 +13,7 @@ export type Column = {
13
13
  checked?: boolean;
14
14
  width?: string | number;
15
15
  fixed: 'left' | 'right' | 'unfixed';
16
+ preferred?: boolean;
16
17
  };
17
18
  export type TableSettings<T> = {
18
19
  columns?: Column[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "v-nuxt-ui",
3
- "version": "0.2.30",
3
+ "version": "0.2.32",
4
4
  "description": "Veken UI Component Library - Reusable Nuxt UI components, composables, and utilities for enterprise applications",
5
5
  "type": "module",
6
6
  "style": "./dist/runtime/index.css",
@@ -72,7 +72,7 @@
72
72
  "@nuxt/eslint-config": "^1.15.2",
73
73
  "@nuxt/fonts": "^0.12.1",
74
74
  "@nuxt/module-builder": "^1.0.2",
75
- "@nuxt/ui": "^4.7.1",
75
+ "@nuxt/ui": "^4.8.1",
76
76
  "@nuxtjs/i18n": "^10.3.0",
77
77
  "@nuxtjs/mdc": "^0.20.2",
78
78
  "@tanstack/table-core": "^8.21.3",