refactor(upload): clean codes

This commit is contained in:
07akioni 2021-09-19 15:13:07 +08:00
parent 01bd890b73
commit 78f5db7620
13 changed files with 203 additions and 171 deletions

View File

@ -5,7 +5,6 @@
### Breaking Changes ### Breaking Changes
- `n-layout-sider`'s `arrow-circle` trigger is changed into new style. - `n-layout-sider`'s `arrow-circle` trigger is changed into new style.
- `n-upload` add `abstract` prop, add `n-upload-trigger``n-upload-file-list` component, closes [#1102](https://github.com/TuSimple/naive-ui/issues/1102).
### Feats ### Feats
@ -14,6 +13,7 @@
- `n-input-number` add `readonly` prop , closes [#1198](https://github.com/TuSimple/naive-ui/issues/1198). - `n-input-number` add `readonly` prop , closes [#1198](https://github.com/TuSimple/naive-ui/issues/1198).
- `n-spin` add `description` prop and slot. - `n-spin` add `description` prop and slot.
- `n-anchor` add `variant` prop. - `n-anchor` add `variant` prop.
- `n-upload` add `abstract` prop, add `n-upload-trigger``n-upload-file-list` component, closes [#1102](https://github.com/TuSimple/naive-ui/issues/1102).
### Fixes ### Fixes

View File

@ -5,7 +5,6 @@
### Breaking Changes ### Breaking Changes
- `n-layout-sider``arrow-circle` 类型触发按钮采用了新样式 - `n-layout-sider``arrow-circle` 类型触发按钮采用了新样式
- `n-upload` 新增 `abstract` 属性,新增 `n-upload-trigger``n-upload-file-list` 组件,关闭 [#1102](https://github.com/TuSimple/naive-ui/issues/1102)
### Feats ### Feats
@ -14,6 +13,7 @@
- `n-input-number` 新增 `readonly` 属性,关闭 [#1198](https://github.com/TuSimple/naive-ui/issues/1198) - `n-input-number` 新增 `readonly` 属性,关闭 [#1198](https://github.com/TuSimple/naive-ui/issues/1198)
- `n-spin` 新增 `description` prop 和 slot - `n-spin` 新增 `description` prop 和 slot
- `n-anchor` 新增 `variant` 属性 - `n-anchor` 新增 `variant` 属性
- `n-upload` 新增 `abstract` 属性,新增 `n-upload-trigger``n-upload-file-list` 组件,关闭 [#1102](https://github.com/TuSimple/naive-ui/issues/1102)
### Fixes ### Fixes

View File

@ -1,29 +1,25 @@
# No Wrapper DOM # Split Trigger and List
`n-upload` set `abstract`. Set `abstract` on `n-upload`.
`n-upload-trigger` and `n-upload-file-list` need to be called from within `n-upload`. `n-upload-trigger` and `n-upload-file-list` need to be called from within `n-upload`.
```html ```html
<div> <n-upload
<n-upload
abstract abstract
:default-file-list="fileList" :default-file-list="fileList"
action="http://www.mocky.io/v2/5e4bafc63100007100d8b70f" action="http://www.mocky.io/v2/5e4bafc63100007100d8b70f"
> >
<n-button-group> <n-button-group>
<n-button> Eat </n-button> <n-button>Useless</n-button>
<n-button> Sleep </n-button> <n-upload-trigger #="{handleClick}" abstract>
<n-upload-trigger #="{handleClick}">
<n-button @click="handleClick">Upload</n-button> <n-button @click="handleClick">Upload</n-button>
</n-upload-trigger> </n-upload-trigger>
</n-button-group> </n-button-group>
<n-card style="margin-top: 12px;" title="File List"> <n-card style="margin-top: 12px;" title="File List">
<n-upload-file-list /> <n-upload-file-list />
</n-card> </n-card>
</n-upload> </n-upload>
</div>
``` ```
```js ```js

View File

@ -23,7 +23,7 @@ abstract
| Name | Type | Default | Description | | Name | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| abstract | `boolean` | `false` | Whether or not DOM wrapping does not exist. | | abstract | `boolean` | `false` | Whether or not DOM wrapping does not exist. Not supported for `image-card` type. |
| accept | `string` | `undefined` | The accept type of upload. See <n-a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept">accept</n-a>. | | accept | `string` | `undefined` | The accept type of upload. See <n-a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept">accept</n-a>. |
| action | `string` | `undefined` | The URL to submit data to. | | action | `string` | `undefined` | The URL to submit data to. |
| create-thumbnail-url | `(file: File) => Promise<string>` | `undefined` | Customize file thumbnails. | | create-thumbnail-url | `(file: File) => Promise<string>` | `undefined` | Customize file thumbnails. |
@ -64,6 +64,12 @@ abstract
| type? | `string \| null` | MIME type. | | type? | `string \| null` | MIME type. |
| url? | `string \| null` | File URL. | | url? | `string \| null` | File URL. |
### UploadTrigger Props
| Name | Type | Default | Description |
| -------- | --------- | ------- | ------------------------------------------- |
| abstract | `boolean` | `false` | Whether or not DOM wrapping does not exist. |
### Upload Methods ### Upload Methods
| Name | Type | Description | | Name | Type | Description |

View File

@ -1,29 +1,25 @@
# 不需要包裹 DOM # 拆分触发器和列表
`n-upload` 设置 `abstract` `n-upload` 设置 `abstract`
`n-upload-trigger``n-upload-file-list` 需在 `n-upload` 内调用。 `n-upload-trigger``n-upload-file-list` 需在 `n-upload` 内调用。
```html ```html
<div> <n-upload
<n-upload
abstract abstract
:default-file-list="fileList" :default-file-list="fileList"
action="http://www.mocky.io/v2/5e4bafc63100007100d8b70f" action="http://www.mocky.io/v2/5e4bafc63100007100d8b70f"
> >
<n-button-group> <n-button-group>
<n-button> Eat </n-button> <n-button>点我没用</n-button>
<n-button> Sleep </n-button> <n-upload-trigger #="{handleClick}" abstract>
<n-upload-trigger #="{handleClick}"> <n-button @click="handleClick">上传</n-button>
<n-button @click="handleClick">Upload</n-button>
</n-upload-trigger> </n-upload-trigger>
</n-button-group> </n-button-group>
<n-card style="margin-top: 12px;" title="文件列表">
<n-card style="margin-top: 12px;" title="File List">
<n-upload-file-list /> <n-upload-file-list />
</n-card> </n-card>
</n-upload> </n-upload>
</div>
``` ```
```js ```js

View File

@ -23,7 +23,7 @@ abstract
| 名称 | 类型 | 默认值 | 说明 | | 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| abstract | `boolean` | `false` | 是否不存在 DOM 包裹 | | abstract | `boolean` | `false` | 是否不存在 DOM 包裹,不支持 `image-card` 类型的 Upload |
| accept | `string` | `undefined` | 接受的文件类型,参考 <n-a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept">accept</n-a> | | accept | `string` | `undefined` | 接受的文件类型,参考 <n-a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept">accept</n-a> |
| action | `string` | `undefined` | 请求提交的地址 | | action | `string` | `undefined` | 请求提交的地址 |
| create-thumbnail-url | `(file: File) => Promise<string>` | `undefined` | 自定义文件缩略图 | | create-thumbnail-url | `(file: File) => Promise<string>` | `undefined` | 自定义文件缩略图 |
@ -63,6 +63,12 @@ abstract
| type? | `string \| null` | MIME 类型 | | type? | `string \| null` | MIME 类型 |
| url? | `string \| null` | 文件下载 URL | | url? | `string \| null` | 文件下载 URL |
### UploadTrigger Props
| 名称 | 类型 | 默认值 | 说明 |
| -------- | --------- | ------- | ------------------- |
| abstract | `boolean` | `false` | 是否不存在 DOM 包裹 |
### Upload Methods ### Upload Methods
| 名称 | 类型 | 说明 | | 名称 | 类型 | 说明 |

View File

@ -7,7 +7,6 @@ import {
ref, ref,
PropType, PropType,
CSSProperties, CSSProperties,
renderSlot,
Fragment, Fragment,
Teleport, Teleport,
nextTick nextTick
@ -18,7 +17,6 @@ import { useConfig, useTheme, useFormItem } from '../../_mixins'
import type { ThemeProps } from '../../_mixins' import type { ThemeProps } from '../../_mixins'
import { import {
ExtractPublicPropTypes, ExtractPublicPropTypes,
getFirstSlotVNode,
warn, warn,
MaybeArray, MaybeArray,
call, call,
@ -501,7 +499,8 @@ export default defineComponent({
openFileDialog, openFileDialog,
draggerInsideRef, draggerInsideRef,
handleFileAddition, handleFileAddition,
fileListStyle: props.fileListStyle, mergedDisabledRef,
fileListStyleRef: toRef(props, 'fileListStyle'),
abstractRef: toRef(props, 'abstract'), abstractRef: toRef(props, 'abstract'),
cssVarsRef cssVarsRef
}) })
@ -509,7 +508,6 @@ export default defineComponent({
mergedClsPrefix: mergedClsPrefixRef, mergedClsPrefix: mergedClsPrefixRef,
draggerInsideRef, draggerInsideRef,
inputElRef, inputElRef,
mergedDisabled: mergedDisabledRef,
mergedTheme: themeRef, mergedTheme: themeRef,
dragOver: dragOverRef, dragOver: dragOverRef,
handleFileInputChange, handleFileInputChange,
@ -519,53 +517,44 @@ export default defineComponent({
}, },
render () { render () {
const { draggerInsideRef, mergedClsPrefix, $slots } = this const { draggerInsideRef, mergedClsPrefix, $slots } = this
if ($slots.default && !this.abstract) { if ($slots.default && !this.abstract) {
const firstChild = getFirstSlotVNode($slots, 'default') const firstChild = $slots.default()[0]
// @ts-expect-error if ((firstChild as any)?.type?.[uploadDraggerKey]) {
if (firstChild?.type?.[uploadDraggerKey]) {
draggerInsideRef.value = true draggerInsideRef.value = true
} }
} }
return this.abstract ? ( const inputNode = (
<>
{renderSlot(this.$slots, 'default')}
<Teleport to="body">
<input <input
ref="inputElRef" ref="inputElRef"
type="file" type="file"
class={`${mergedClsPrefix}-upload__file-input`} class={`${mergedClsPrefix}-upload-file-input`}
accept={this.accept} accept={this.accept}
multiple={this.multiple} multiple={this.multiple}
onChange={this.handleFileInputChange} onChange={this.handleFileInputChange}
/> />
</Teleport> )
return this.abstract ? (
<>
{$slots.default?.()}
<Teleport to="body">{inputNode}</Teleport>
</> </>
) : ( ) : (
<div <div
class={[ class={[
`${mergedClsPrefix}-upload`, `${mergedClsPrefix}-upload`,
{ draggerInsideRef.value && `${mergedClsPrefix}-upload--dragger-inside`,
[`${mergedClsPrefix}-upload--dragger-inside`]: this.dragOver && `${mergedClsPrefix}-upload--drag-over`
draggerInsideRef.value,
[`${mergedClsPrefix}-upload--drag-over`]: this.dragOver,
[`${mergedClsPrefix}-upload--disabled`]: this.mergedDisabled
}
]} ]}
style={this.cssVars as CSSProperties} style={this.cssVars as CSSProperties}
> >
<input {inputNode}
ref="inputElRef"
type="file"
class={`${mergedClsPrefix}-upload__file-input`}
accept={this.accept}
multiple={this.multiple}
onChange={this.handleFileInputChange}
/>
{this.listType !== 'image-card' && ( {this.listType !== 'image-card' && (
<NUploadTrigger>{this.$slots}</NUploadTrigger> <NUploadTrigger>{$slots}</NUploadTrigger>
)} )}
{this.showFileList && <NUploadFileList>{this.$slots}</NUploadFileList>} {this.showFileList && <NUploadFileList>{$slots}</NUploadFileList>}
</div> </div>
) )
} }

View File

@ -15,10 +15,21 @@ export default defineComponent({
'`n-upload-dragger` must be placed inside `n-upload`.' '`n-upload-dragger` must be placed inside `n-upload`.'
) )
} }
return () => ( return () => {
<div class={`${NUpload.mergedClsPrefixRef.value}-upload-dragger`}> const {
mergedClsPrefixRef: { value: mergedClsPrefix },
mergedDisabledRef: { value: mergedDisabled }
} = NUpload
return (
<div
class={[
`${mergedClsPrefix}-upload-dragger`,
mergedDisabled && `${mergedClsPrefix}-upload-dragger--disabled`
]}
>
{slots} {slots}
</div> </div>
) )
} }
}
}) })

View File

@ -21,14 +21,16 @@ export default defineComponent({
mergedClsPrefixRef, mergedClsPrefixRef,
listTypeRef, listTypeRef,
mergedFileListRef, mergedFileListRef,
fileListStyle, fileListStyleRef,
cssVarsRef cssVarsRef,
mergedDisabledRef
} = NUpload } = NUpload
const isImageCardTypeRef = computed( const isImageCardTypeRef = computed(
() => listTypeRef.value === 'image-card' () => listTypeRef.value === 'image-card'
) )
const createFileList = (): VNode[] => const renderFileList = (): VNode[] =>
mergedFileListRef.value.map((file) => ( mergedFileListRef.value.map((file) => (
<NUploadFile <NUploadFile
clsPrefix={mergedClsPrefixRef.value} clsPrefix={mergedClsPrefixRef.value}
@ -38,29 +40,34 @@ export default defineComponent({
/> />
)) ))
const createUploadFileList = (): VNode => const renderUploadFileList = (): VNode =>
isImageCardTypeRef.value ? ( isImageCardTypeRef.value ? (
<NImageGroup>{{ default: createFileList }}</NImageGroup> <NImageGroup>{{ default: renderFileList }}</NImageGroup>
) : ( ) : (
<NFadeInExpandTransition group> <NFadeInExpandTransition group>
{{ {{
default: createFileList default: renderFileList
}} }}
</NFadeInExpandTransition> </NFadeInExpandTransition>
) )
return () => ( return () => {
const { value: mergedClsPrefix } = mergedClsPrefixRef
return (
<div <div
class={[ class={[
`${mergedClsPrefixRef.value}-upload-file-list`, `${mergedClsPrefix}-upload-file-list`,
mergedDisabledRef.value &&
`${mergedClsPrefix}-upload-file-list--disabled`,
isImageCardTypeRef.value && isImageCardTypeRef.value &&
`${mergedClsPrefixRef.value}-upload-file-list--grid` `${mergedClsPrefix}-upload-file-list--grid`
]} ]}
style={[cssVarsRef.value, fileListStyle as CSSProperties]} style={[cssVarsRef.value, fileListStyleRef.value as CSSProperties]}
> >
{createUploadFileList()} {renderUploadFileList()}
{isImageCardTypeRef.value && <NUploadTrigger>{slots}</NUploadTrigger>} {isImageCardTypeRef.value && <NUploadTrigger>{slots}</NUploadTrigger>}
</div> </div>
) )
} }
}
}) })

View File

@ -1,11 +1,14 @@
import { h, defineComponent, inject, renderSlot, computed } from 'vue' import { h, defineComponent, inject, computed } from 'vue'
import { throwError } from '../../_utils' import { throwError } from '../../_utils'
import { uploadInjectionKey } from './interface' import { uploadInjectionKey } from './interface'
import NUploadDragger from './UploadDragger' import NUploadDragger from './UploadDragger'
export default defineComponent({ export default defineComponent({
name: 'UploadTrigger', name: 'UploadTrigger',
setup (_, { slots }) { props: {
abstract: Boolean
},
setup (props, { slots }) {
const NUpload = inject(uploadInjectionKey, null) const NUpload = inject(uploadInjectionKey, null)
if (!NUpload) { if (!NUpload) {
throwError( throwError(
@ -16,13 +19,13 @@ export default defineComponent({
const { const {
mergedClsPrefixRef, mergedClsPrefixRef,
mergedDisabledRef,
listTypeRef, listTypeRef,
disabledRef, disabledRef,
dragOverRef, dragOverRef,
openFileDialog, openFileDialog,
draggerInsideRef, draggerInsideRef,
handleFileAddition, handleFileAddition
abstractRef
} = NUpload } = NUpload
const isImageCardTypeRef = computed( const isImageCardTypeRef = computed(
@ -56,9 +59,10 @@ export default defineComponent({
dragOverRef.value = false dragOverRef.value = false
} }
return () => return () => {
abstractRef.value ? ( const { value: mergedClsPrefix } = mergedClsPrefixRef
renderSlot(slots, 'default', { return props.abstract ? (
slots.default?.({
handleClick: handleTriggerClick, handleClick: handleTriggerClick,
handleDrop: handleTriggerDrop, handleDrop: handleTriggerDrop,
handleDragOver: handleTriggerDragOver, handleDragOver: handleTriggerDragOver,
@ -68,9 +72,11 @@ export default defineComponent({
) : ( ) : (
<div <div
class={[ class={[
`${mergedClsPrefixRef.value}-upload__trigger`, `${mergedClsPrefix}-upload-trigger`,
mergedDisabledRef.value &&
`${mergedClsPrefix}-upload-trigger--disabled`,
isImageCardTypeRef.value && isImageCardTypeRef.value &&
`${mergedClsPrefixRef.value}-upload__trigger--image-card` `${mergedClsPrefix}-upload-trigger--image-card`
]} ]}
onClick={handleTriggerClick} onClick={handleTriggerClick}
onDrop={handleTriggerDrop} onDrop={handleTriggerDrop}
@ -86,4 +92,5 @@ export default defineComponent({
</div> </div>
) )
} }
}
}) })

View File

@ -65,19 +65,20 @@ export interface UploadInjection {
onRemoveRef: Ref<OnRemove | undefined> onRemoveRef: Ref<OnRemove | undefined>
onDownloadRef: Ref<OnDownload | undefined> onDownloadRef: Ref<OnDownload | undefined>
XhrMap: Map<string, XMLHttpRequest> XhrMap: Map<string, XMLHttpRequest>
submit: (fileId?: string) => void
doChange: DoChange doChange: DoChange
showPreivewButtonRef: Ref<boolean> showPreivewButtonRef: Ref<boolean>
onPreviewRef: Ref<OnPreview | undefined> onPreviewRef: Ref<OnPreview | undefined>
getFileThumbnailUrl: (file: FileInfo) => Promise<string>
listTypeRef: Ref<listType> listTypeRef: Ref<listType>
dragOverRef: Ref<boolean> dragOverRef: Ref<boolean>
draggerInsideRef: { value: boolean } draggerInsideRef: { value: boolean }
handleFileAddition: (files: FileList | null, e?: Event) => void fileListStyleRef: Ref<string | CSSProperties | undefined>
fileListStyle: string | CSSProperties | undefined mergedDisabledRef: Ref<boolean>
openFileDialog: () => void
abstractRef: Ref<boolean> abstractRef: Ref<boolean>
cssVarsRef: Ref<CSSProperties> cssVarsRef: Ref<CSSProperties>
submit: (fileId?: string) => void
getFileThumbnailUrl: (file: FileInfo) => Promise<string>
handleFileAddition: (files: FileList | null, e?: Event) => void
openFileDialog: () => void
} }
export const uploadInjectionKey: InjectionKey<UploadInjection> = export const uploadInjectionKey: InjectionKey<UploadInjection> =

View File

@ -4,10 +4,50 @@ import createIconSwitchTransition from '../../../_styles/transitions/icon-switch
export default c([ export default c([
cB('upload', [ cB('upload', [
cM('dragger-inside', [
cE('trigger', ` cE('trigger', `
display: block;
`)
]),
cM('drag-over', [
cB('upload-dragger', `
border: var(--dragger-border-hover);
`)
])
]),
cB('upload-dragger', `
cursor: pointer;
box-sizing: border-box;
width: 100%;
text-align: center;
border-radius: var(--border-radius);
padding: 24px;
opacity: 1;
transition:
opacity: .3s var(--bezier),
border-color .3s var(--bezier),
background-color .3s var(--bezier);
background-color: var(--dragger-color);
border: var(--dragger-border);
`, [
c('&:hover', `
border: var(--dragger-border-hover);
`),
cM('disabled', `
opacity: var(--item-disabled-opacity);
cursor: not-allowed;
`)
]),
cB('upload-trigger', `
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
opacity: 1;
transition: opacity .3s var(--bezier);
`, [ `, [
cM('disabled', `
opacity: var(--item-disabled-opacity);
cursor: not-allowed;
`),
cM('image-card', [ cM('image-card', [
cB('upload-dragger', ` cB('upload-dragger', `
padding: 0; padding: 0;
@ -19,54 +59,18 @@ export default c([
`) `)
]) ])
]), ]),
cM('dragger-inside', [
cE('trigger', `
display: block;
`)
]),
cB('upload-dragger', `
cursor: pointer;
box-sizing: border-box;
width: 100%;
text-align: center;
border-radius: var(--border-radius);
padding: 24px;
transition:
border-color .3s var(--bezier),
background-color .3s var(--bezier);
background-color: var(--dragger-color);
border: var(--dragger-border);
`, [
c('&:hover', `
border: var(--dragger-border-hover);
`)
]),
cM('disabled', `
opacity: var(--item-disabled-opacity);
`, [
cE('trigger', `
cursor: not-allowed;
`),
cB('upload-file', `
cursor: not-allowed;
`),
cB('upload-file-list', `
cursor: not-allowed;
`),
cB('upload-dragger', `
cursor: not-allowed;
`)
]),
cM('drag-over', [
cB('upload-dragger', `
border: var(--dragger-border-hover);
`)
])
]),
cB('upload-file-list', ` cB('upload-file-list', `
margin-top: 8px; margin-top: 8px;
line-height: var(--line-height); line-height: var(--line-height);
opacity: 1;
transition: opacity .3s var(--bezier);
`, [ `, [
cM('disabled', `
opacity: var(--item-disabled-opacity);
cursor: not-allowed;
`, [
cB('upload-file', 'cursor: not-allowed;')
]),
cM('grid', ` cM('grid', `
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, 96px); grid-template-columns: repeat(auto-fill, 96px);
@ -307,7 +311,7 @@ export default c([
]) ])
]) ])
]), ]),
cB('upload__file-input', ` cB('upload-file-input', `
display: block; display: block;
width: 0; width: 0;
height: 0; height: 0;

View File

@ -31,10 +31,19 @@ describe('n-upload', () => {
it('should work with `disabled` prop', async () => { it('should work with `disabled` prop', async () => {
const wrapper = mount(NUpload) const wrapper = mount(NUpload)
expect(wrapper.find('.n-upload--disabled').exists()).not.toBe(true) const disabledClasses = [
'n-upload-trigger--disabled',
'n-upload-file-list--disabled'
]
for (const disabledClass of disabledClasses) {
expect(wrapper.find(disabledClass).exists()).not.toBe(true)
}
await wrapper.setProps({ disabled: true }) await wrapper.setProps({ disabled: true })
expect(wrapper.find('.n-upload').classes()).toContain('n-upload--disabled') for (const disabledClass of disabledClasses) {
expect(
wrapper.find('.' + disabledClass.split('--')[0]).classes()
).toContain(disabledClass)
}
}) })
it('should work with `on-before-upload` prop', async () => { it('should work with `on-before-upload` prop', async () => {
@ -347,7 +356,7 @@ describe('n-upload-trigger', () => {
default: () => h(NButton, null, { default: () => 'button1' }) default: () => h(NButton, null, { default: () => 'button1' })
} }
}) })
const triggerItem = wrapper.find('.n-upload__trigger') const triggerItem = wrapper.find('.n-upload-trigger')
await triggerItem.trigger('click') await triggerItem.trigger('click')
await triggerItem.trigger('drop') await triggerItem.trigger('drop')