@complex-suite/component-antd 4.10.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 (51) hide show
  1. package/LayoutResizeObserver.ts +104 -0
  2. package/LocalResizeObserver.ts +46 -0
  3. package/README.md +67 -0
  4. package/antdConfig.ts +361 -0
  5. package/format.ts +458 -0
  6. package/history.md +325 -0
  7. package/icon.ts +65 -0
  8. package/index.test.ts +5 -0
  9. package/index.ts +55 -0
  10. package/package.json +39 -0
  11. package/plugin.ts +95 -0
  12. package/quick/QuickCascade.tsx +107 -0
  13. package/quick/QuickEdit.tsx +117 -0
  14. package/quick/QuickFloat.tsx +32 -0
  15. package/quick/QuickFloatModal.tsx +95 -0
  16. package/quick/QuickFloatValue.tsx +103 -0
  17. package/quick/QuickList.tsx +433 -0
  18. package/quick/data/FloatData.ts +77 -0
  19. package/src/AutoSpin.vue +39 -0
  20. package/src/AutoText.vue +101 -0
  21. package/src/ButtonView.tsx +62 -0
  22. package/src/CollapseArea.tsx +88 -0
  23. package/src/EditArea.tsx +205 -0
  24. package/src/EditView.tsx +179 -0
  25. package/src/FlexBox.tsx +74 -0
  26. package/src/FormList.tsx +226 -0
  27. package/src/ImageViewer.tsx +122 -0
  28. package/src/InfoArea.tsx +182 -0
  29. package/src/InfoView.tsx +150 -0
  30. package/src/MenuView.tsx +91 -0
  31. package/src/ModalView.tsx +274 -0
  32. package/src/MultipleImport.tsx +231 -0
  33. package/src/SearchArea.tsx +170 -0
  34. package/src/SelectText.vue +59 -0
  35. package/src/SimpleTable.tsx +256 -0
  36. package/src/SingleImport.tsx +189 -0
  37. package/src/TableView.tsx +415 -0
  38. package/src/components/AutoRender.tsx +19 -0
  39. package/src/components/ChoiceInfo.vue +73 -0
  40. package/src/components/PaginationView.tsx +103 -0
  41. package/src/components/TableMenu.tsx +93 -0
  42. package/src/dictionary/AutoEditItem.tsx +164 -0
  43. package/src/dictionary/AutoInfoItem.tsx +126 -0
  44. package/src/dictionary/AutoItem.tsx +219 -0
  45. package/src/icons/EmptyImage.vue +30 -0
  46. package/src/icons/ErrorImage.vue +30 -0
  47. package/src/style/index.css +304 -0
  48. package/tsconfig.json +8 -0
  49. package/type.ts +4 -0
  50. package/vitest.config.ts +11 -0
  51. package/widthCalculator.ts +20 -0
@@ -0,0 +1,91 @@
1
+ import { defineComponent, h, PropType } from "vue"
2
+ import { Button } from "ant-design-vue"
3
+ import type { ButtonType } from "ant-design-vue/es/button"
4
+ import { isPromise } from "@complex-suite/utils"
5
+ import type { MenuValue } from "@complex-suite/data"
6
+ import icon from "../icon"
7
+ import antdConfig from "../antdConfig"
8
+
9
+ export default defineComponent({
10
+ name: 'MenuView',
11
+ props: {
12
+ data: {
13
+ type: Object as PropType<MenuValue<any>>,
14
+ required: true
15
+ },
16
+ disabled: {
17
+ type: Boolean,
18
+ required: false
19
+ },
20
+ loading: {
21
+ type: Boolean,
22
+ required: false
23
+ }
24
+ },
25
+ data() {
26
+ return {
27
+ operate: false
28
+ }
29
+ },
30
+ render() {
31
+ if (!this.data.render) {
32
+ const type = this.data.type
33
+ let loading = this.operate
34
+ if (!loading) {
35
+ loading = this.loading || (typeof this.data.loading === 'function' ? this.data.loading() : (this.data.loading || false))
36
+ }
37
+ const disabled = this.disabled || (typeof this.data.disabled === 'function' ? this.data.disabled() : (this.data.disabled || false))
38
+ const render = h(Button, {
39
+ class: 'complex-menu',
40
+ loading: loading,
41
+ type: type === 'danger' ? 'primary' : type as ButtonType,
42
+ danger: type === 'danger',
43
+ icon: icon.parse(this.data.icon),
44
+ disabled: disabled,
45
+ onClick: (e: MouseEvent) => {
46
+ if (this.data.modifiers) {
47
+ if (this.data.modifiers.includes('.stop')) {
48
+ e.stopPropagation()
49
+ }
50
+ if (this.data.modifiers.includes('.prevent')) {
51
+ e.preventDefault()
52
+ }
53
+ }
54
+ antdConfig.parseMenuConfirm(this.data.confirm, () => {
55
+ this.$emit('click', e)
56
+ if (this.data.click) {
57
+ const res = this.data.click(e)
58
+ if (isPromise(res)) {
59
+ this.operate = true
60
+ res.finally(() => {
61
+ if (this.data.debounce) {
62
+ setTimeout(() => {
63
+ this.operate = false
64
+ }, this.data.debounce)
65
+ } else {
66
+ this.operate = false
67
+ }
68
+ })
69
+ } else {
70
+ if (this.data.debounce) {
71
+ this.operate = true
72
+ setTimeout(() => {
73
+ this.operate = false
74
+ }, this.data.debounce)
75
+ }
76
+ }
77
+ }
78
+ })
79
+ },
80
+ ...this.$attrs
81
+ }, {
82
+ default: () => this.data.name
83
+ })
84
+ return render
85
+ } else {
86
+ return this.data.render({
87
+ data: this.data
88
+ })
89
+ }
90
+ }
91
+ })
@@ -0,0 +1,274 @@
1
+ import { defineComponent, h, markRaw, PropType } from "vue"
2
+ import { Modal } from "ant-design-vue"
3
+ import type { ModalProps } from "ant-design-vue"
4
+ import { deepCloneData, updateData } from "@complex-suite/utils"
5
+ import { dataConfig } from "@complex-suite/data"
6
+ import type { MenuValue } from "@complex-suite/data"
7
+ import ButtonView from "./ButtonView"
8
+ import antdConfig from "../antdConfig"
9
+
10
+ export type modalLayoutOption = {
11
+ type: 'auto' | 'fixed'
12
+ top: number
13
+ bottom: number
14
+ header: number
15
+ menu: number
16
+ padding: [number, number, number, number]
17
+ mainPadding: [number, number, number, number]
18
+ }
19
+
20
+ export interface ModalViewProps {
21
+ width?: number
22
+ title?: string
23
+ destroyOnClose?: boolean
24
+ layout?: Partial<modalLayoutOption>
25
+ menu?: (string | MenuValue)[] // 菜单列表,字符串则通过config.modal.getMenu实现
26
+ menuOption?: Record<string, Partial<MenuValue>> // 根据prop匹配菜单列表中的字符串数据获取的默认值,通过可选参数重写属性
27
+ submit?: () => Promise<unknown>
28
+ modalProps?: ModalProps
29
+ }
30
+
31
+ export interface ModalViewSlotProps {
32
+ width: number
33
+ height: number
34
+ modal: any
35
+ }
36
+
37
+ export default defineComponent({
38
+ name: 'ModalView',
39
+ emits: {
40
+ // 关闭
41
+ close: (from: string) => {
42
+ return typeof from === 'string'
43
+ },
44
+ // 菜单
45
+ menu: (prop: string, _self: any) => {
46
+ return typeof prop === 'string'
47
+ }
48
+ },
49
+ props: {
50
+ width: {
51
+ type: Number,
52
+ required: false
53
+ },
54
+ title: {
55
+ type: String,
56
+ required: false
57
+ },
58
+ destroyOnClose: {
59
+ type: Boolean,
60
+ required: false,
61
+ default: () => {
62
+ return antdConfig.modal.destroyOnClose
63
+ }
64
+ },
65
+ layout: {
66
+ type: Object as PropType<ModalViewProps['layout']>,
67
+ required: false
68
+ },
69
+ menu: {
70
+ type: Object as PropType<ModalViewProps['menu']>,
71
+ required: false
72
+ },
73
+ menuOption: {
74
+ type: Object as PropType<ModalViewProps['menuOption']>,
75
+ required: false
76
+ },
77
+ submit: {
78
+ type: Function as PropType<ModalViewProps['submit']>,
79
+ required: false
80
+ },
81
+ modalProps: {
82
+ type: Object as PropType<ModalViewProps['modalProps']>,
83
+ required: false,
84
+ default: () => {
85
+ return null
86
+ }
87
+ }
88
+ },
89
+ data() {
90
+ return {
91
+ $open: false,
92
+ localTitle: undefined as undefined | string,
93
+ localModalProps: undefined as undefined | ModalProps
94
+ }
95
+ },
96
+ computed: {
97
+ currentTitle() {
98
+ return this.localTitle || this.title
99
+ },
100
+ currentLayout() {
101
+ return updateData(deepCloneData(antdConfig.modal.layout), this.layout)
102
+ },
103
+ menuList() {
104
+ let menuList: MenuValue[]
105
+ const close = () => {
106
+ this.close('close')
107
+ }
108
+ const submit = () => {
109
+ if (this.submit) {
110
+ const promise = this.submit!()
111
+ promise.then(() => {
112
+ this.close('submit')
113
+ })
114
+ return promise
115
+ } else {
116
+ console.error('submit按钮需要定义对应的submit函数')
117
+ }
118
+ }
119
+ if (!this.menu) {
120
+ menuList = [
121
+ antdConfig.modal.getMenu('close', {
122
+ click: close
123
+ })
124
+ ]
125
+ } else {
126
+ menuList = this.menu.map(menu => {
127
+ if (typeof menu !== 'object') {
128
+ const menuOption = this.menuOption ? this.menuOption[menu] : undefined
129
+ if (menu === 'close' || menu === 'cancel') {
130
+ return antdConfig.modal.getMenu(menu, {
131
+ ...menuOption,
132
+ click: close
133
+ })
134
+ } else if (menu === 'submit') {
135
+ return antdConfig.modal.getMenu(menu, {
136
+ ...menuOption,
137
+ click: submit
138
+ })
139
+ } else {
140
+ return antdConfig.modal.getMenu(menu, {
141
+ ...menuOption,
142
+ })
143
+ }
144
+ } else {
145
+ return menu
146
+ }
147
+ })
148
+ }
149
+ return menuList
150
+ },
151
+ currentWidth() {
152
+ if (!this.width) {
153
+ return antdConfig.modal.width
154
+ } else {
155
+ return this.width
156
+ }
157
+ },
158
+ contentHeight() {
159
+ const mainHeight = document.documentElement.clientHeight
160
+ let height = mainHeight - this.currentLayout.top - this.currentLayout.bottom - this.currentLayout.header - this.currentLayout.padding[0] - this.currentLayout.padding[2] - this.currentLayout.mainPadding[0] - this.currentLayout.mainPadding[2]
161
+ if (this.menuList.length > 0) {
162
+ height = height - this.currentLayout.menu
163
+ }
164
+ return height
165
+ },
166
+ contentWidth() {
167
+ return this.currentWidth - this.currentLayout.padding[1] - this.currentLayout.padding[3] - this.currentLayout.mainPadding[1] - this.currentLayout.mainPadding[3]
168
+ }
169
+ },
170
+ methods: {
171
+ show(title?: string, option?: ModalProps) {
172
+ this.localTitle = title
173
+ this.localModalProps = option
174
+ this.$data.$open = true
175
+ },
176
+ close(from: string) {
177
+ this.$data.$open = false
178
+ this.localTitle = undefined
179
+ this.localModalProps = undefined
180
+ this.$emit('close', from)
181
+ },
182
+ renderContent() {
183
+ return this.$slots.default ? this.$slots.default({
184
+ width: this.contentWidth,
185
+ height: this.contentHeight,
186
+ modal: markRaw(this)
187
+ }) : null
188
+ },
189
+ onMenu(prop: string) {
190
+ this.$emit('menu', prop, this)
191
+ },
192
+ renderFooter() {
193
+ if (this.menuList.length > 0) {
194
+ return this.menuList.map(item => {
195
+ if (!item.render) {
196
+ const onClick = item.click
197
+ return h(ButtonView, {
198
+ data: {
199
+ ...item,
200
+ click: (payload: any) => {
201
+ this.onMenu(item.prop!)
202
+ if (onClick) {
203
+ return onClick(payload)
204
+ }
205
+ }
206
+ }
207
+ })
208
+ } else {
209
+ return item.render({
210
+ modal: this,
211
+ menuList: this.menuList,
212
+ menu: item
213
+ })
214
+ }
215
+ })
216
+ } else {
217
+ return null
218
+ }
219
+ }
220
+ },
221
+ render() {
222
+ const top = dataConfig.formatPixel(this.currentLayout.top)
223
+ const padding = this.currentLayout.padding.map(num => dataConfig.formatPixel(num)).join(' ')
224
+ interface ModalPropsWithClass extends ModalProps {
225
+ class: string
226
+ }
227
+ const props: ModalPropsWithClass = {
228
+ class: 'complex-modal',
229
+ open: this.$data.$open,
230
+ width: this.currentWidth,
231
+ title: this.currentTitle,
232
+ destroyOnClose: this.destroyOnClose,
233
+ ...this.modalProps,
234
+ ...this.localModalProps,
235
+ onCancel: (e: MouseEvent | KeyboardEvent) => {
236
+ if (e instanceof KeyboardEvent) {
237
+ this.close('escape')
238
+ } else {
239
+ const target = e.target as HTMLDivElement
240
+ if (target.classList.contains('ant-modal-wrap')) {
241
+ this.close('mask')
242
+ } else {
243
+ this.close('modal')
244
+ }
245
+ }
246
+ }
247
+ }
248
+ if (this.menuList.length === 0) {
249
+ props.footer = null
250
+ }
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ if (!(props as any).style) {
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
+ (props as any).style = {
255
+ top: top
256
+ }
257
+ } else {
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ (props as any).style.top = top
260
+ }
261
+ if (!props.bodyStyle) {
262
+ props.bodyStyle = {
263
+ padding: padding
264
+ }
265
+ } else {
266
+ props.bodyStyle.padding = padding
267
+ }
268
+ const render = h(Modal, props, {
269
+ default: () => [this.renderContent()],
270
+ footer: () => this.renderFooter()
271
+ })
272
+ return render
273
+ }
274
+ })
@@ -0,0 +1,231 @@
1
+ import { defineComponent, h, PropType, ref, watch, VNode, computed } from "vue"
2
+ import { useInjectFormItemContext } from "ant-design-vue/es/form"
3
+ import { notice } from "@complex-suite/plugin"
4
+ import type { fileDataType } from "@complex-suite/data"
5
+ import type { FileEditOption } from "@complex-suite/data"
6
+ import { FileValue } from "@complex-suite/data"
7
+ import { FileMultipleValue, type fileValueType } from "@complex-suite/data"
8
+ import { FileView } from "@complex-suite/component"
9
+ import type { FileProps, MultipleFileProps } from "@complex-suite/component"
10
+ import type { DefaultImportProps } from "./SingleImport"
11
+ import antdConfig from "../antdConfig"
12
+
13
+ export interface MultipleImportProps extends FileProps, MultipleFileProps, DefaultImportProps{
14
+ value?: fileValueType[]
15
+ upload?: FileEditOption<true>['upload']
16
+ }
17
+
18
+ export const defaultMultipleUpload = function(fileList: File[]) {
19
+ return Promise.resolve({ file: fileList.map(file => { return { value: file, name: file.name} } ) })
20
+ } as NonNullable<MultipleImportProps['upload']>
21
+
22
+ export default defineComponent({
23
+ name: 'MultipleImport',
24
+ props: {
25
+ value: {
26
+ type: Array as PropType<MultipleImportProps['value']>
27
+ },
28
+ button: {
29
+ type: Object as PropType<MultipleImportProps['button']>,
30
+ required: false
31
+ },
32
+ complex: {
33
+ type: Boolean as PropType<MultipleImportProps['complex']>,
34
+ required: false
35
+ },
36
+ isUrl: {
37
+ type: Boolean as PropType<MultipleImportProps['isUrl']>,
38
+ required: false
39
+ },
40
+ image: {
41
+ type: Object as PropType<MultipleImportProps['image']>,
42
+ required: false
43
+ },
44
+ upload: {
45
+ type: Function as PropType<MultipleImportProps['upload']>,
46
+ required: false
47
+ },
48
+ render: {
49
+ type: Object as PropType<MultipleImportProps['render']>,
50
+ required: false
51
+ },
52
+ loading: {
53
+ type: Boolean,
54
+ required: false
55
+ },
56
+ accept: {
57
+ type: String,
58
+ required: false
59
+ },
60
+ size: { // MB
61
+ type: Number,
62
+ required: false
63
+ },
64
+ multiple: {
65
+ type: Object as PropType<MultipleImportProps['multiple']>,
66
+ required: false
67
+ },
68
+ disabled: {
69
+ type: Boolean,
70
+ required: false
71
+ }
72
+ },
73
+ setup(props, { emit }) {
74
+ const formItemContext = useInjectFormItemContext()
75
+ const parseValue = function (value?: fileValueType[]) {
76
+ return new FileMultipleValue((value || []).map(valueItem => new FileValue(valueItem, props.isUrl)))
77
+ }
78
+ const operate = ref(false)
79
+ const currentValue = ref(props.value)
80
+ const data = ref(parseValue(props.value))
81
+
82
+ const syncValue = () => {
83
+ // 此处基于外部数据整合内部数据
84
+ if (props.value !== currentValue.value) {
85
+ currentValue.value = props.value
86
+ }
87
+ // 多选模式下,value可能存在splice的改变或者是splice后重新赋值,此时需要将额外数据删除
88
+ if (props.value) {
89
+ data.value.assign(parseValue(props.value))
90
+ } else {
91
+ data.value.reset()
92
+ }
93
+ }
94
+
95
+ watch(() => props.value, () => {
96
+ syncValue()
97
+ formItemContext.onFieldChange()
98
+ })
99
+
100
+ const onSelect = (file: File[]) => {
101
+ operate.value = true;
102
+ (props.upload || defaultMultipleUpload)(file).then(res => {
103
+ onUpload(res.file, true)
104
+ }).catch((err: unknown) => {
105
+ console.error(err)
106
+ }).finally(() => {
107
+ operate.value = false
108
+ })
109
+ }
110
+
111
+ const max = computed(() => {
112
+ if (props.multiple) {
113
+ return props.multiple.max || 0
114
+ } else {
115
+ return 0
116
+ }
117
+ })
118
+
119
+ const onUpload = (fileList: fileDataType[], emit?: boolean) => {
120
+ if (currentValue.value) {
121
+ fileList.forEach(file => {
122
+ // 通过data判断,避免complex模式下的判断错误
123
+ if (!data.value.has(file.value)) {
124
+ currentValue.value!.push(!props.complex ? file.value : file)
125
+ data.value.push(new FileValue(file, props.isUrl))
126
+ }
127
+ })
128
+ } else {
129
+ currentValue.value = []
130
+ fileList.forEach(file => {
131
+ currentValue.value!.push(!props.complex ? file.value : file)
132
+ data.value.push(new FileValue(file, props.isUrl))
133
+ })
134
+ }
135
+ if (max.value && currentValue.value.length > max.value) {
136
+ currentValue.value.length = max.value
137
+ data.value.truncation(max.value)
138
+ notice.message(`当前选择的文件数量超过限制值${max.value},超过部分已被删除!`, 'error')
139
+ }
140
+ if (emit) {
141
+ emitData()
142
+ }
143
+ }
144
+
145
+ const emitData = () => {
146
+ emit('change', currentValue.value)
147
+ formItemContext.onFieldChange()
148
+ }
149
+
150
+ const renderFile = () => {
151
+ let disabled = props.disabled
152
+ if (max.value && currentValue.value && currentValue.value.length >= max.value) {
153
+ disabled = true
154
+ }
155
+ return h(FileView, {
156
+ class: 'complex-import-file',
157
+ ref: 'file',
158
+ accept: props.accept,
159
+ multiple: props.multiple,
160
+ disabled: disabled,
161
+ size: props.size,
162
+ onSelect: onSelect,
163
+ onChange(e: Event) {
164
+ e.stopPropagation() // 阻止事件冒泡
165
+ }
166
+ })
167
+ }
168
+
169
+ const renderList = (list: FileMultipleValue) => {
170
+ return h('div', {
171
+ class: !props.image ? 'complex-import-content-list' : 'complex-import-image-list'
172
+ }, {
173
+ default: () => list.value.map((file, index) => {
174
+ return renderContent(file, index)
175
+ })
176
+ })
177
+ }
178
+
179
+ const deleteData = (key: any, index: number) => {
180
+ if (props.disabled || props.loading) {
181
+ return
182
+ }
183
+ currentValue.value!.splice(index, 1)
184
+ data.value.delete(key)
185
+ emitData()
186
+ }
187
+
188
+ const renderContent = (file: FileValue, index: number) => {
189
+ return antdConfig.import.renderContent(file, props.disabled, props.image, () => {
190
+ deleteData(file.value, index)
191
+ })
192
+ }
193
+
194
+ return {
195
+ operate,
196
+ currentValue,
197
+ data,
198
+ onSelect,
199
+ onUpload,
200
+ emitData,
201
+ renderFile,
202
+ renderList,
203
+ deleteData,
204
+ renderContent
205
+ }
206
+ },
207
+ render() {
208
+ let content: null | VNode | VNode[]
209
+ if (this.$slots.content || (this.render && this.render.content)) {
210
+ content = (this.$slots.content || this.render!.content)!({
211
+ props: {
212
+ multiple: this.multiple,
213
+ upload: this.upload,
214
+ value: this.currentValue,
215
+ data: this.data
216
+ }
217
+ })
218
+ } else {
219
+ content = this.renderList(this.data)
220
+ }
221
+ return h('div', {
222
+ class: 'complex-import'
223
+ }, {
224
+ default: () => [
225
+ this.renderFile(),
226
+ antdConfig.import.renderMenu(this as any),
227
+ content
228
+ ]
229
+ })
230
+ }
231
+ })