@ithinkdt/ui 4.0.0-23 → 4.0.0-31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/locale.d.ts CHANGED
@@ -62,6 +62,9 @@ export interface UILocale {
62
62
  resetText: string
63
63
  cancelText: string
64
64
  selectFileText: string
65
+ validate: {
66
+ fileErrorMessage: string
67
+ }
65
68
  }
66
69
  filter: {
67
70
  submitText: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ithinkdt/ui",
3
- "version": "4.0.0-23",
3
+ "version": "4.0.0-31",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "iThinkDT UI",
@@ -63,7 +63,7 @@
63
63
  "sortablejs": "^1.15.6",
64
64
  "@types/sortablejs": "^1.15.8",
65
65
  "nanoid": "^5.1.6",
66
- "@ithinkdt/common": "^4.0.0-12"
66
+ "@ithinkdt/common": "^4.0.0-30"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@ithinkdt/page": ">=4.0",
@@ -82,13 +82,13 @@
82
82
  },
83
83
  "devDependencies": {
84
84
  "@vitejs/plugin-vue-jsx": "^5.1.1",
85
- "ithinkdt-ui": "^1.7.3",
85
+ "ithinkdt-ui": "^1.8.0",
86
86
  "typescript": "~5.9.3",
87
87
  "unocss": ">=66.5.4",
88
- "vite": "npm:rolldown-vite@^7.1.17",
88
+ "vite": "npm:rolldown-vite@^7.1.19",
89
89
  "vue": "^3.5.22",
90
- "vue-router": "^4.6.2",
91
- "@ithinkdt/page": "^4.0.0-16"
90
+ "vue-router": "^4.6.3",
91
+ "@ithinkdt/page": "^4.0.0-31"
92
92
  },
93
93
  "scripts": {
94
94
  "release": "pnpm publish --no-git-checks"
@@ -3,7 +3,7 @@ import { computed, defineComponent, ref, watch } from 'vue'
3
3
 
4
4
  import { useI18n } from '../use-i18n.js'
5
5
 
6
- export const NCheckboxes = defineComponent({
6
+ export const NCheckboxes = /* @__PURE__ */ defineComponent({
7
7
  name: 'Checkboxes',
8
8
  inheritAttrs: false,
9
9
  props: {
@@ -1,12 +1,12 @@
1
1
  import { toReactive, unrefElement, until } from '@vueuse/core'
2
- import { NButton, NDataTable, NFlex, NIcon, NPerformantEllipsis, NTooltip } from 'ithinkdt-ui'
2
+ import { NButton, NDataTable, NFlex, NIcon, NPerformantEllipsis, NTooltip, dataTableProps } from 'ithinkdt-ui'
3
3
  import { Sortable } from 'sortablejs'
4
4
  import { computed, defineComponent, inject, mergeProps, nextTick, ref, shallowRef, toValue, useTemplateRef, watch, withDirectives } from 'vue'
5
5
 
6
6
  import { PAGE_INJECTION } from '@ithinkdt/page'
7
7
 
8
8
  import { vTooltip } from '../directives/index.js'
9
- import { useClsPrefix } from '../use-style.js'
9
+ import useStyle, { c, cB, cE, useClsPrefix } from '../use-style.js'
10
10
 
11
11
  import { IHelp } from './assets.jsx'
12
12
 
@@ -124,16 +124,20 @@ export const DataTable = /* @__PURE__ */ defineComponent({
124
124
  selectedKeys: {
125
125
  type: Array,
126
126
  },
127
+ highlight: {
128
+ type: [Boolean, String, Number, Object],
129
+ default: true,
130
+ },
131
+ rowClassName: dataTableProps.rowClassName,
132
+ rowProps: dataTableProps.rowProps,
127
133
  },
128
- emits: {
129
- sort: () => true,
130
- select: () => true,
131
- },
134
+ emits: ['sort', 'select', 'custom', 'highlight'],
132
135
  setup(props, { slots, emit, expose }) {
133
136
  const { keyField } = inject(PAGE_INJECTION)
134
137
 
135
138
  const clsPrefix = useClsPrefix()
136
- const cls = `${clsPrefix.value}-data-table`
139
+ const cls = `${clsPrefix.value}-datatable`
140
+ useStyle('-datatable', style, clsPrefix)
137
141
 
138
142
  const tableRef = useTemplateRef('table-ref')
139
143
 
@@ -165,7 +169,7 @@ export const DataTable = /* @__PURE__ */ defineComponent({
165
169
  exposed.value.scrollToView = (key) => {
166
170
  const index = props.data.findIndex(row => rowKey.value(row) === key)
167
171
  if (index === -1) return
168
- const row = tableRef.value.$el.querySelector(`.${cls}-row-marker:nth-child(${index + 1})`)
172
+ const row = tableRef.value.$el.querySelector(`.${cls}__row-marker:nth-child(${index + 1})`)
169
173
  tableRef.value.scrollTo({
170
174
  top: row.offsetTop,
171
175
  behavior: 'smooth',
@@ -184,10 +188,33 @@ export const DataTable = /* @__PURE__ */ defineComponent({
184
188
  columns.value = _map(props.columns, width)
185
189
  }, { immediate: true, deep: 1 })
186
190
 
191
+ const highlightKey = ref()
192
+ watch(() => props.highlight, (val) => {
193
+ highlightKey.value = typeof val === 'boolean' ? null : (val ?? null)
194
+ }, { immediate: true })
187
195
  const rowClassName = computed(() => {
188
196
  return (row, i) => {
189
- const p = props.rowClassName?.(row, i) || ''
190
- return p + ` ${cls}-row-marker`
197
+ return [
198
+ props.rowClassName?.(row, i) || '',
199
+ `${cls}__row-marker`,
200
+ (highlightKey.value != null && rowKey.value(row) === highlightKey.value) ? `${cls}__row-highlight` : '',
201
+ ].join(' ')
202
+ }
203
+ })
204
+ const rowProps = computed(() => {
205
+ return (row, i) => {
206
+ const props2 = props.rowProps?.(row, i)
207
+ return {
208
+ ...props2,
209
+ onClick: (e) => {
210
+ const key = rowKey.value(row)
211
+ if (props.highlight === true) {
212
+ highlightKey.value = key
213
+ }
214
+ emit('highlight', key)
215
+ props2?.onClick?.(e)
216
+ },
217
+ }
191
218
  }
192
219
  })
193
220
 
@@ -207,6 +234,13 @@ export const DataTable = /* @__PURE__ */ defineComponent({
207
234
  emit('select', keys)
208
235
  }
209
236
 
237
+ const onColumnResize = (resizedWidth, limitedWidth, column) => {
238
+ emit('custom', {
239
+ key: column.key,
240
+ width: resizedWidth,
241
+ })
242
+ }
243
+
210
244
  return () => (
211
245
  <NDataTable
212
246
  class={cls}
@@ -217,9 +251,11 @@ export const DataTable = /* @__PURE__ */ defineComponent({
217
251
  ref="table-ref"
218
252
  scrollX={width.value}
219
253
  rowClassName={rowClassName.value}
254
+ rowProps={rowProps.value}
220
255
  checkedRowKeys={props.selectedKeys}
221
256
  onUpdateSorter={onUpdateSorter}
222
257
  onUpdateCheckedRowKeys={onUpdateCheckedRowKeys}
258
+ onUnstableColumnResize={onColumnResize}
223
259
  >
224
260
  {slots}
225
261
  </NDataTable>
@@ -227,6 +263,17 @@ export const DataTable = /* @__PURE__ */ defineComponent({
227
263
  },
228
264
  })
229
265
 
266
+ const style = /* @__PURE__ */ cB(
267
+ 'datatable',
268
+ [
269
+ cE('row-highlight', [
270
+ c('& > td', {
271
+ backgroundColor: 'var(--n-tr-highlight-color, var(--n-merged-border-color)) !important',
272
+ }),
273
+ ]),
274
+ ],
275
+ )
276
+
230
277
  export function useDataTableDrag(
231
278
  tableRef,
232
279
  { data, onSort, ...options }) {
@@ -149,6 +149,7 @@ export type DataTableProps<Data extends {}, KeyField extends keyof Data>
149
149
  data?: Data[] | undefined
150
150
  keyField?: KeyField | undefined
151
151
  selectedKeys?: Data[KeyField][] | undefined
152
+ highlight?: boolean | Data[KeyField] | undefined
152
153
  sorter?: SortParams<Data> | undefined
153
154
  onSort?: MaybeArray<(param: SortParams<Data>) => void> | undefined
154
155
  onSelect?: MaybeArray<(param: NonNullable<Data[KeyField]>[]) => void> | undefined
@@ -157,6 +158,8 @@ export type DataTableProps<Data extends {}, KeyField extends keyof Data>
157
158
  export type DataTableEmits<Data extends {}, KeyField extends keyof Data> = {
158
159
  (e: 'sort', param: SortParams<Data>): void
159
160
  (e: 'select', param: Data[KeyField][]): void
161
+ (e: 'highlight', rowKey: Data[KeyField]): void
162
+ (e: 'custom', params: { key: string, width: number }): void
160
163
  }
161
164
 
162
165
  export type DataTableInst<Data extends {}, KeyField extends keyof Data> = NDataTableInst & {
@@ -182,7 +185,7 @@ export declare function useDataTableDrag<T>(
182
185
  { data, onSort, ...options }: {
183
186
  data: MaybeRef<T[]>
184
187
  onSort: (info: { from: number, to: number }) => void
185
- } & Omit<Sortable.Options, 'onSort'>
188
+ } & Omit<Sortable.Options, 'onSort'>,
186
189
  ): void
187
190
 
188
191
  export type DataPaginationProps = (PageParams | { page: PageParams }) & {
@@ -302,7 +305,7 @@ export declare function DataLocaleInput(
302
305
  export declare function useLocaleEdit(
303
306
  title: VNodeChild | (() => VNodeChild),
304
307
  onSubmit: (data: Record<string, string | undefined>) => void,
305
- defaultRows?: MaybeRef<number | undefined>
308
+ defaultRows?: MaybeRef<number | undefined>,
306
309
  ): {
307
310
  open(this: void, data: MaybePromise<Record<string, string | undefined>>, readonly?: boolean): PromiseLike<Record<string, string | undefined>>
308
311
  setSupports(this: void, supports: { label: string, value: string, required?: boolean }[]): void
package/src/page.d.ts CHANGED
@@ -1,44 +1,46 @@
1
1
  import {
2
- CheckboxProps, DatePickerProps, DatePickerSlots,
2
+ CheckboxProps,
3
+ DatePickerProps, DatePickerSlots,
3
4
  DrawerContentProps, DrawerProps,
4
5
  InputNumberProps, InputNumberSlots, InputProps, InputSlots,
5
6
  ModalOptions,
6
7
  SelectGroupOption, SelectOption, SelectProps, SelectSlots,
7
- UploadFileInfo,
8
+ UploadFileInfo, UploadProps,
8
9
  } from 'ithinkdt-ui'
9
- import { MaybeRef, VNode, VNodeProps } from 'vue'
10
+ import { MaybeRef, VNode } from 'vue'
10
11
 
12
+ import { PublicProps } from '@ithinkdt/common'
11
13
  import { DictItem, DictTypeKey } from '@ithinkdt/common/dict'
12
14
  import { PageOptions } from '@ithinkdt/page'
13
15
 
14
16
  import { CheckboxesProps, RadiosProps, UserDeptProps, UserGroupOption } from './components'
15
17
 
16
- type DeepMaybeRef<T extends {}> = {
18
+ type ShallowMaybeRef<T extends {}> = {
17
19
  [Key in (keyof T)]: MaybeRef<T[Key]>
18
20
  }
19
21
 
20
22
  declare module '@ithinkdt/page' {
21
23
  interface FormComponentPresets {
22
24
  input: {
23
- props?: DeepMaybeRef<Omit<InputProps, 'value' | 'onUpdate:value' | 'disabled'>> & VNodeProps
24
- slots?: InputSlots
25
+ inputProps?: ShallowMaybeRef<Omit<InputProps, 'value' | 'onUpdate:value' | 'disabled'>> & PublicProps
26
+ inputSlots?: InputSlots
25
27
  }
26
28
  number: {
27
- props?: DeepMaybeRef<Omit<InputNumberProps, 'value' | 'onUpdate:value' | 'disabled'>> & VNodeProps
28
- slots?: InputNumberSlots
29
+ numberProps?: ShallowMaybeRef<Omit<InputNumberProps, 'value' | 'onUpdate:value' | 'disabled'>> & PublicProps
30
+ numberSlots?: InputNumberSlots
29
31
  }
30
32
  select: {
31
- props?: DeepMaybeRef<Omit<SelectProps, 'options' | 'value' | 'onUpdate:value' | 'disabled'> & VNodeProps
33
+ selectProps?: ShallowMaybeRef<Omit<SelectProps, 'options' | 'value' | 'onUpdate:value' | 'disabled'> & PublicProps
32
34
  & {
33
35
  dictType?: DictTypeKey | undefined
34
36
  options?: DictItem[] | (SelectOption | SelectGroupOption)[] | undefined
35
37
  }>
36
- slots?: SelectSlots
38
+ selectSlots?: SelectSlots
37
39
  }
38
40
 
39
41
  checkbox: {
40
- props?: DeepMaybeRef<Omit<CheckboxProps, 'checked' | 'onUpdate:checked' | 'disabled'>> & VNodeProps
41
- slots?: {
42
+ checkboxProps?: ShallowMaybeRef<Omit<CheckboxProps, 'checked' | 'onUpdate:checked' | 'disabled'>> & PublicProps
43
+ checkboxSlots?: {
42
44
  default?: (() => VNode[]) | undefined
43
45
  checked?: (() => VNode[]) | undefined
44
46
  unchecked?: (() => VNode[]) | undefined
@@ -46,45 +48,59 @@ declare module '@ithinkdt/page' {
46
48
  }
47
49
 
48
50
  checkboxes: {
49
- props?: DeepMaybeRef<Omit<CheckboxesProps, 'disabled'> & VNodeProps
51
+ checkboxesProps?: ShallowMaybeRef<Omit<CheckboxesProps, 'disabled'> & PublicProps
50
52
  & {
51
53
  dictType?: DictTypeKey | undefined
52
54
  options?: DictItem[] | undefined
53
55
  }>
54
- slots?: { }
56
+ checkboxesSlots?: { }
55
57
  }
56
58
 
57
59
  radios: {
58
- props?: DeepMaybeRef<Omit<RadiosProps, 'disabled'> & VNodeProps
60
+ radiosProps?: ShallowMaybeRef<Omit<RadiosProps, 'disabled'> & PublicProps
59
61
  & {
60
62
  dictType?: DictTypeKey | undefined
61
63
  options?: DictItem[] | undefined
62
64
  }>
63
- slots?: { }
65
+ radiosSlots?: { }
64
66
  }
65
67
 
66
68
  datepicker: {
67
- props?: DeepMaybeRef<Omit<DatePickerProps, 'value' | 'onUpdate:value' | 'disabled'>> & VNodeProps
68
- slots?: DatePickerSlots
69
+ datepickerProps?: ShallowMaybeRef<Omit<DatePickerProps, 'value' | 'onUpdate:value' | 'disabled'>> & PublicProps
70
+ datepickerSlots?: DatePickerSlots
69
71
  }
70
72
 
71
73
  file: {
72
- props?: DeepMaybeRef<{
74
+ fileProps?: ShallowMaybeRef<{
73
75
  type?: 'file' | 'image' | undefined
74
76
  multiple?: boolean | undefined
75
77
  max?: number | undefined
76
78
  accept?: string | undefined
77
79
  maxSize?: number | undefined
78
- } & VNodeProps>
79
- slots?: {
80
+ onBeforeUpload?: UploadProps['onBeforeUpload']
81
+ } & PublicProps>
82
+ fileSlots?: {
83
+ default?: (() => VNode[]) | undefined
84
+ }
85
+ }
86
+ upload: {
87
+ uploadProps?: ShallowMaybeRef<{
88
+ type?: 'file' | 'image' | undefined
89
+ multiple?: boolean | undefined
90
+ max?: number | undefined
91
+ accept?: string | undefined
92
+ maxSize?: number | undefined
93
+ onBeforeUpload?: UploadProps['onBeforeUpload']
94
+ } & PublicProps>
95
+ uploadSlots?: {
80
96
  default?: (() => VNode[]) | undefined
81
97
  }
82
98
  }
83
99
 
84
100
  user: {
85
- props?: DeepMaybeRef<Omit<UserDeptProps<boolean>, 'modelValue' | 'onUpdate:modelValue' | 'disabled'
86
- | 'users' | 'groups' | 'depts' | 'getUsersByGroup' | 'getUsersByDept'>> & VNodeProps
87
- slots?: {}
101
+ uploadProps?: ShallowMaybeRef<Omit<UserDeptProps<boolean>, 'modelValue' | 'onUpdate:modelValue' | 'disabled'
102
+ | 'users' | 'groups' | 'depts' | 'getUsersByGroup' | 'getUsersByDept'>> & PublicProps
103
+ uploadSlots?: {}
88
104
  }
89
105
  }
90
106
 
@@ -128,9 +144,9 @@ declare module '@ithinkdt/page' {
128
144
  }
129
145
 
130
146
  type ModalOptionsKey = 'type' | keyof import('@ithinkdt/page').ModalOptions
131
- interface ModalDrawerOptions extends DeepMaybeRef<Omit<DrawerContentProps, ModalOptionsKey>>, DeepMaybeRef<Omit<DrawerProps, ModalOptionsKey>> { }
147
+ interface ModalDrawerOptions extends ShallowMaybeRef<Omit<DrawerContentProps, ModalOptionsKey>>, ShallowMaybeRef<Omit<DrawerProps, ModalOptionsKey>> { }
132
148
 
133
- interface ModalDialogOptions extends DeepMaybeRef<Omit<ModalOptions, ModalOptionsKey>> {}
149
+ interface ModalDialogOptions extends ShallowMaybeRef<Omit<ModalOptions, ModalOptionsKey>> {}
134
150
  }
135
151
 
136
152
  export declare function createPageFormHelper(options?: {
@@ -142,6 +158,7 @@ export declare function createPageFormHelper(options?: {
142
158
  uploadFile?: (file: File, options?: {
143
159
  onProgress?: ((percent: number) => void) | undefined
144
160
  }) => Promise<string>
161
+ getFileInfos?: (files: string[]) => Promise<UploadFileInfo[]>
145
162
  }): PageOptions['getFormItemRenderer']
146
163
  export declare function createPageTableHelper(options?: {
147
164
  getUsersByUsername?: (usernames: string[]) => Promise<{ username: string, nickname: string }[]>
package/src/page.jsx CHANGED
@@ -1,10 +1,10 @@
1
+ import { until } from '@vueuse/core'
1
2
  import { format } from 'date-fns'
2
3
  import {
3
4
  NButton, NCheckbox, NColorPicker, NDatePicker, NDrawer, NDrawerContent, NFlex, NInput,
4
- NInputNumber, NModal, NScrollbar, NSelect, NText, NUpload,
5
- useMessage,
5
+ NInputNumber, NModal, NScrollbar, NSelect, NText, NUpload, useMessage,
6
6
  } from 'ithinkdt-ui'
7
- import { defineComponent, h, mergeProps, unref } from 'vue'
7
+ import { computed, defineComponent, h, mergeProps, shallowRef, unref } from 'vue'
8
8
 
9
9
  import { useDict, useDictMap } from '@ithinkdt/common/dict'
10
10
 
@@ -14,64 +14,68 @@ import { useI18n } from './use-i18n.js'
14
14
  const mapProps = (props) => {
15
15
  return Object.fromEntries(Object.entries(props || {}).map(([prop, value]) => [prop, unref(value)]))
16
16
  }
17
- export function createPageFormHelper({
18
- getUserGroups, getUsersByGroup, getUsersByDept, getUsersByUsername, uploadFile,
19
- }) {
20
- const SimpleUpload = defineComponent({
21
- name: 'SimpleUpload',
22
- props: {
23
- type: { type: String, default: 'file' }, // file | image
24
- multiple: { type: Boolean, default: false },
25
- max: { type: Number, default: undefined },
26
- accept: { type: String, default: undefined },
27
- maxSize: { type: Number, default: undefined }, // MB
28
- disabled: { type: Boolean, default: undefined },
29
- fileList: { type: Array, default: () => [] },
30
- onUpdateFileList: { type: [Array, Function] },
31
- },
32
- setup(props, { slots }) {
33
- const { t } = useI18n()
34
17
 
35
- const message = useMessage()
36
- const customRequest = computed(() => props.customRequest || (
37
- async ({ file, onProgress, onFinish, onError }) => {
38
- uploadFile(file, percent => onProgress({ percent }))
39
- .then((id) => {
40
- file.file.fileId = id
41
- onFinish()
42
- }).catch((error) => {
43
- message.success(error.message)
44
- onError(error)
45
- })
46
- }
47
- ))
48
- return () => {
49
- const { type, onUpdateFileList, ...props0 } = props
50
- return (
51
- <NUpload
52
- {...props0}
53
- onFinish={({ file, event }) => {
54
- props0.onFinish?.({ file, event })
55
- return {
56
- ...file,
57
- id: file.file.fileId,
58
- }
59
- }}
60
- customRequest={customRequest.value}
61
- listType={type === 'image' ? 'image-card' : 'text'}
62
- accept={props0.accept ?? type === 'image' ? 'image/*' : undefined}
63
- onUpdate:fileList={onUpdateFileList}
64
- >
65
- {{
66
- default: () => <NButton disabled={props.disabled}>{t('common.page.form.selectFileText')}</NButton>,
67
- ...slots,
68
- }}
69
- </NUpload>
70
- )
18
+ const SimpleUpload = /* @__PURE__ */ defineComponent({
19
+ name: 'SimpleUpload',
20
+ props: {
21
+ type: { type: String, default: 'file' }, // file | image
22
+ multiple: { type: Boolean, default: false },
23
+ max: { type: Number, default: undefined },
24
+ accept: { type: String, default: undefined },
25
+ maxSize: { type: Number, default: undefined }, // MB
26
+ disabled: { type: Boolean, default: undefined },
27
+ fileList: { type: Array, default: () => [] },
28
+ onUpdateFileList: { type: [Array, Function] },
29
+ uploadFile: { type: Function },
30
+ },
31
+ setup(props, { slots, expose }) {
32
+ const { t } = useI18n()
33
+ SimpleUpload.t = t
34
+
35
+ const message = useMessage()
36
+ const customRequest = computed(() => props.customRequest || (
37
+ ({ file, onProgress, onFinish, onError }) => {
38
+ props.uploadFile(file.file, percent => onProgress({ percent }))
39
+ .then((id) => {
40
+ file.file.fileId = id
41
+ onFinish()
42
+ }).catch((error) => {
43
+ message.error(error.message)
44
+ onError(error)
45
+ })
71
46
  }
72
- },
73
- })
47
+ ))
48
+
49
+ const inst = ref()
50
+ expose({
51
+ submit() {
52
+ return until(inst).toBeTruthy().then(inst => inst.submit())
53
+ },
54
+ })
55
+ return () => {
56
+ const { type, onUpdateFileList, ...props0 } = props
57
+ return (
58
+ <NUpload
59
+ {...props0}
60
+ ref={inst}
61
+ customRequest={customRequest.value}
62
+ listType={type === 'image' ? 'image-card' : 'text'}
63
+ accept={props0.accept ?? type === 'image' ? 'image/*' : undefined}
64
+ onUpdate:fileList={onUpdateFileList}
65
+ >
66
+ {{
67
+ default: () => <NButton disabled={props.disabled}>{t('common.page.form.selectFileText')}</NButton>,
68
+ ...slots,
69
+ }}
70
+ </NUpload>
71
+ )
72
+ }
73
+ },
74
+ })
74
75
 
76
+ export function createPageFormHelper({
77
+ getUserGroups, getUsersByGroup, getUsersByDept, getUsersByUsername, uploadFile, getFileInfos,
78
+ }) {
75
79
  return {
76
80
  input: () => (
77
81
  { slots, props },
@@ -298,9 +302,10 @@ export function createPageFormHelper({
298
302
  ) => {
299
303
  props = mapProps(props)
300
304
  if (readonly) {
301
- if (modelValue.length === 0) return
305
+ console.warn(`[file] 原则上此组建不应该显示在详情中`)
306
+ if (fileList.value.length === 0) return
302
307
  return (
303
- <NFlex gap="8" wrap>
308
+ <NFlex gap="8" vertical>
304
309
  {modelValue.map(it => (
305
310
  <a
306
311
  key={it.id}
@@ -318,11 +323,97 @@ export function createPageFormHelper({
318
323
  return h(SimpleUpload, {
319
324
  ...props,
320
325
  ...params,
326
+ defaultUpload: false,
327
+ uploadFile,
321
328
  fileList: modelValue,
322
329
  onUpdateFileList: onUpdateValue,
323
330
  }, slots)
324
331
  }
325
332
  },
333
+ upload: () => {
334
+ let lastModelValue = null
335
+ const idMap = new Map()
336
+ const fileMap = new Map()
337
+ const fileList = shallowRef([])
338
+ let key = 0
339
+
340
+ const update = (ids) => {
341
+ const key0 = ++key
342
+ fileList.value = ids.map(id => fileMap.get(id)).filter(Boolean)
343
+ const ids0 = ids.filter(id => !fileMap.has(id))
344
+ Promise.resolve(ids0.length > 0 ? getFileInfos(ids0) : [])
345
+ .then((res) => {
346
+ if (key0 !== key) return
347
+ fileList.value = ids.map(id => fileMap.get(id) || res.find(it => it.id === id))
348
+ fileMap.clear()
349
+ for (const it of fileList.value) {
350
+ fileMap.set(it.id, it)
351
+ }
352
+ })
353
+ }
354
+
355
+ let inst
356
+ const renderer = (
357
+ { slots, props },
358
+ { modelValue, 'onUpdate:modelValue': onUpdateValue, required, readonly, ...params },
359
+ ) => {
360
+ const props0 = mapProps(props)
361
+ inst = (props0.ref ??= shallowRef())
362
+ if (lastModelValue !== modelValue) {
363
+ lastModelValue = modelValue
364
+ update(modelValue?.split(',') ?? [])
365
+ }
366
+ if (readonly) {
367
+ if (fileList.value.length === 0) return
368
+ return (
369
+ <NFlex gap="8" vertical>
370
+ {fileList.value.map(it => (
371
+ <a
372
+ key={it.id}
373
+ href={it.url}
374
+ target="_blank"
375
+ rel="noreferrer"
376
+ title={it.name}
377
+ style="max-width: 100px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display: inline-block"
378
+ >
379
+ {it.name}
380
+ </a>
381
+ ))}
382
+ </NFlex>
383
+ )
384
+ }
385
+ return h(SimpleUpload, {
386
+ ...props0,
387
+ ...params,
388
+ uploadFile,
389
+ fileList: fileList.value,
390
+ onUpdateFileList: (fileList0 = []) => {
391
+ fileMap.clear()
392
+ for (const f of fileList0) {
393
+ if (f.file.fileId) {
394
+ idMap.set(f.id, f.file.fileId)
395
+ }
396
+
397
+ if (f.file.fileId) fileMap.set(f.file.fileId, f)
398
+ else fileMap.set(f.id, f)
399
+ }
400
+ fileList.value = fileList0
401
+ const ids = fileList.value.map(f => idMap.get(f.id) || f.id).join(',') || null
402
+ onUpdateValue(ids)
403
+ },
404
+ }, slots)
405
+ }
406
+ return {
407
+ renderer,
408
+ beforeSubmit: async () => {
409
+ await inst?.submit()
410
+ await until(fileList).toMatch(list => list.every(file => !['pending', 'uploading'].includes(file.status)))
411
+ if (fileList.value.some(file => file.status === 'error')) {
412
+ return SimpleUpload.t('common.page.form.validate.fileErrorMessage')
413
+ }
414
+ },
415
+ }
416
+ },
326
417
  user: () => {
327
418
  let users, groups, depts
328
419
  return (