mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-11-27 04:09:51 +08:00
feat(upload): wip
This commit is contained in:
parent
4a40f9fb0f
commit
7b1b667792
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ test-size
|
||||
test-bundle
|
||||
test/unit/coverage
|
||||
package-lock.json
|
||||
playground/temp
|
||||
yarn.lock
|
||||
lib
|
||||
es
|
||||
|
@ -1,4 +1,6 @@
|
||||
# 基础用法
|
||||
```html
|
||||
<n-upload />
|
||||
<n-upload action="http://localhost:3000/upload-test">
|
||||
<n-button>上传文件</n-button>
|
||||
</n-upload>
|
||||
```
|
@ -59,6 +59,7 @@
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"chai": "^4.2.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^5.2.1",
|
||||
"css-loader": "^2.1.1",
|
||||
"cssnano": "^4.1.10",
|
||||
@ -66,6 +67,7 @@
|
||||
"emoji-unicode": "^1.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"express": "^4.17.1",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"glob": "^7.1.6",
|
||||
@ -84,9 +86,9 @@
|
||||
"marked": "^0.7.0",
|
||||
"memory-fs": "^0.4.1",
|
||||
"mocha": "^6.2.2",
|
||||
"multer": "^1.4.2",
|
||||
"ncp": "^2.0.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"sass-loader": "^7.3.1",
|
||||
"postcss": "^7.0.26",
|
||||
"postcss-cli": "^6.1.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
@ -96,6 +98,7 @@
|
||||
"rollup": "^1.31.0",
|
||||
"rollup-plugin-terser": "^5.2.0",
|
||||
"rollup-plugin-vue": "^5.1.6",
|
||||
"sass-loader": "^7.3.1",
|
||||
"schema-utils": "^1.0.0",
|
||||
"sinon": "^7.5.0",
|
||||
"style-loader": "^0.23.1",
|
||||
|
15
playground/uploadServer.js
Normal file
15
playground/uploadServer.js
Normal file
@ -0,0 +1,15 @@
|
||||
const express = require('express')
|
||||
const multer = require('multer')
|
||||
const cors = require('cors')
|
||||
const path = require('path')
|
||||
|
||||
const app = express()
|
||||
const upload = multer({ dest: path.resolve(__dirname, 'temp') })
|
||||
|
||||
app.options('/upload-test', cors())
|
||||
app.post('/upload-test', cors(), upload.any(), function (req, res, next) {
|
||||
console.log(req.files)
|
||||
res.send('very good')
|
||||
})
|
||||
|
||||
app.listen(3000)
|
@ -90,7 +90,9 @@
|
||||
<div
|
||||
class="n-progress-graph-line-rail"
|
||||
:style="{
|
||||
backgroundColor: safeRailColor
|
||||
backgroundColor: safeRailColor,
|
||||
height: styleHeight,
|
||||
borderRadius: styleBorderRadius
|
||||
}"
|
||||
>
|
||||
<div
|
||||
@ -100,7 +102,9 @@
|
||||
}"
|
||||
:style="{
|
||||
maxWidth: fillStyleMaxWidth + '%',
|
||||
backgroundColor: safeColor
|
||||
backgroundColor: safeColor,
|
||||
height: styleHeight,
|
||||
borderRadius: styleBorderRadius
|
||||
}"
|
||||
>
|
||||
<div
|
||||
@ -234,7 +238,7 @@ import mdCloseCircle from '../../_icons/md-close-circle'
|
||||
import fontawareable from '../../_mixins/fontawarable'
|
||||
import withapp from '../../_mixins/withapp'
|
||||
import themeable from '../../_mixins/themeable'
|
||||
import asthemecontext from '../../_mixins/asthemecontext'
|
||||
import formatLength from '../../_utils/css/formatLength'
|
||||
|
||||
function circlePath (r, sw, vw = 100) {
|
||||
return `m ${vw / 2} ${vw / 2 - r} a ${r} ${r} 0 1 1 0 ${2 * r} a ${r} ${r} 0 1 1 0 -${2 * r}`
|
||||
@ -251,7 +255,7 @@ export default {
|
||||
mdCheckmarkCircle,
|
||||
mdCloseCircle
|
||||
},
|
||||
mixins: [withapp, themeable, asthemecontext, fontawareable],
|
||||
mixins: [withapp, themeable, fontawareable],
|
||||
props: {
|
||||
processing: {
|
||||
type: Boolean,
|
||||
@ -316,6 +320,10 @@ export default {
|
||||
circleGap: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@ -325,6 +333,13 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
styleHeight () {
|
||||
return formatLength(this.height)
|
||||
},
|
||||
styleBorderRadius () {
|
||||
if (this.height) return formatLength(this.height / 2)
|
||||
return null
|
||||
},
|
||||
syntheticIndicatorPlacement () {
|
||||
return this.indicatorPlacement || this.indicatorPosition
|
||||
},
|
||||
|
@ -1,33 +1,125 @@
|
||||
<template>
|
||||
<div
|
||||
class="n-upload"
|
||||
@click="handleClick"
|
||||
:class="{
|
||||
[`n-${syntheticTheme}-theme`]: syntheticTheme
|
||||
}"
|
||||
>
|
||||
<input
|
||||
ref="input"
|
||||
type="file"
|
||||
class="~n-upload__file-input"
|
||||
class="n-upload__file-input"
|
||||
:accept="accept"
|
||||
:multiple="multiple"
|
||||
@change="handleFileInputChange"
|
||||
>
|
||||
<div>
|
||||
<div v-for="(file, index) in fileList" :key="index">
|
||||
{{ file.name }} <n-progress type="line" :show-indicator="false" :percentage="50" style="width: 50%;" /></n-progress>
|
||||
</div>
|
||||
<div class="n-upload__activator" @click="handleActivatorClick">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="n-upload-file-list">
|
||||
<template v-for="(file, index) in fileList">
|
||||
<div
|
||||
v-if="!isFileRemoved(file)"
|
||||
:key="index"
|
||||
class="n-upload-file"
|
||||
:class="{
|
||||
[`n-upload-file--${getProgressStatus(file)}-status`]: true
|
||||
}"
|
||||
>
|
||||
<div class="n-upload-file-info">
|
||||
<div class="n-upload-file-info__name">
|
||||
{{ file.name }}
|
||||
</div>
|
||||
<div class="n-upload-file-info__action">
|
||||
<n-button circle size="tiny">
|
||||
<template v-slot:icon>
|
||||
<close-outline />
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button circle size="tiny">
|
||||
<template v-slot:icon>
|
||||
<download-outline />
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<n-upload-progress
|
||||
:show="!isFileUploaded(file)"
|
||||
:percentage="file.percentage"
|
||||
:status="getProgressStatus(file)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NProgress from '../../Progress'
|
||||
import NButton from '../../Button'
|
||||
import closeOutline from '../../_icons/close-outline'
|
||||
import downloadOutline from '../../_icons/download-outline'
|
||||
import withapp from '../../_mixins/withapp'
|
||||
import themeable from '../../_mixins/themeable'
|
||||
import NUploadProgress from './UploadProgress'
|
||||
|
||||
function XHRHandlers (componentInstance, index) {
|
||||
return {
|
||||
handleXHRLoad (e) {
|
||||
const file = componentInstance.fileList[index]
|
||||
file.status = 'done'
|
||||
file.percentage = 100
|
||||
console.log('!!!done')
|
||||
},
|
||||
handleXHRAbort (e) {
|
||||
componentInstance.fileList[index].status = 'removed'
|
||||
console.log('!!!abort')
|
||||
},
|
||||
handleXHRError (e) {
|
||||
componentInstance.fileList[index].status = 'error'
|
||||
console.log('!!!error')
|
||||
},
|
||||
handleXHRProgress (e) {
|
||||
console.log('!!!progress')
|
||||
if (e.lengthComputable) {
|
||||
const file = componentInstance.fileList[index]
|
||||
const progress = Math.ceil((e.loaded / e.total) * 100)
|
||||
file.percentage = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerHandler (request, componentInstance, index) {
|
||||
const handlers = XHRHandlers(componentInstance, index)
|
||||
request.onabort = handlers.handleXHRAbort
|
||||
request.onerror = handlers.handleXHRError
|
||||
request.onload = handlers.handleXHRLoad
|
||||
if (request.upload) {
|
||||
request.upload.onprogress = handlers.handleXHRProgress
|
||||
}
|
||||
}
|
||||
|
||||
function submit (method = 'POST', action, formData, index, componentInstance) {
|
||||
const request = new XMLHttpRequest()
|
||||
registerHandler(request, componentInstance, index)
|
||||
request.open(method, action)
|
||||
request.send(formData)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'NUpload',
|
||||
components: {
|
||||
NProgress
|
||||
NButton,
|
||||
NUploadProgress,
|
||||
closeOutline,
|
||||
downloadOutline
|
||||
},
|
||||
mixins: [ withapp, themeable ],
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file'
|
||||
},
|
||||
accept: {
|
||||
type: [String, Array],
|
||||
default: null
|
||||
@ -68,19 +160,45 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
fileList: [],
|
||||
formData: new FormData()
|
||||
formDataList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
// this.$refs.input.click()
|
||||
handleActivatorClick () {
|
||||
if (this.disabled) return
|
||||
this.$refs.input.click()
|
||||
},
|
||||
isFileRemoved (file) {
|
||||
return file.status === 'removed'
|
||||
},
|
||||
isFileUploaded (file) {
|
||||
return file.status === 'done'
|
||||
},
|
||||
getProgressStatus (file) {
|
||||
if (file.status === 'done') return 'success'
|
||||
if (file.status === 'error') return 'error'
|
||||
return 'info'
|
||||
},
|
||||
handleFileInputChange (e) {
|
||||
const currentFileList = this.fileList
|
||||
this.fileList = currentFileList.concat(e.target.files)
|
||||
const fileList = this.fileList
|
||||
const formDataList = this.formDataList
|
||||
const fieldName = this.name
|
||||
const memorizedFileListLength = fileList.length
|
||||
Array.from(e.target.files).forEach((file, index) => {
|
||||
fileList.push({
|
||||
name: file.name,
|
||||
status: 'pending',
|
||||
percentage: 0
|
||||
})
|
||||
const formData = new FormData()
|
||||
formData.append(fieldName, file)
|
||||
formDataList.push(formData)
|
||||
this.submit(formData, memorizedFileListLength + index)
|
||||
})
|
||||
e.target.value = null
|
||||
},
|
||||
submit () {
|
||||
// clear the queue of current form data
|
||||
submit (formData, index) {
|
||||
submit(this.method, this.action, formData, index, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
src/Upload/src/UploadProgress.vue
Normal file
61
src/Upload/src/UploadProgress.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<n-fade-in-height-expand-transition>
|
||||
<n-progress
|
||||
v-if="postponedShow"
|
||||
type="line"
|
||||
:show-indicator="false"
|
||||
:percentage="percentage"
|
||||
:status="status"
|
||||
:height="2"
|
||||
/>
|
||||
</n-fade-in-height-expand-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
|
||||
import NProgress from '../../Progress'
|
||||
|
||||
export default {
|
||||
name: 'NUploadProgress',
|
||||
components: {
|
||||
NProgress,
|
||||
NFadeInHeightExpandTransition
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
percentage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 600
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
postponedShow: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show (value) {
|
||||
if (value) this.postponedShow = true
|
||||
else {
|
||||
window.setTimeout(() => {
|
||||
this.postponedShow = false
|
||||
}, this.delay)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.postponedShow = this.show
|
||||
}
|
||||
}
|
||||
</script>
|
@ -8,6 +8,10 @@ export default {
|
||||
width: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
appear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
@ -78,7 +82,8 @@ export default {
|
||||
render (h) {
|
||||
return h('transition', {
|
||||
props: {
|
||||
name: this.width ? 'n-fade-in-width-expand-transition' : 'n-fade-in-height-expand-transition'
|
||||
name: this.width ? 'n-fade-in-width-expand-transition' : 'n-fade-in-height-expand-transition',
|
||||
appear: this.appear
|
||||
},
|
||||
on: {
|
||||
beforeLeave: this.handleBeforeLeave,
|
||||
|
@ -7,5 +7,70 @@
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
@include e(activator) {
|
||||
display: inline-block;
|
||||
}
|
||||
@include b(upload-file-list) {
|
||||
margin-top: 8px;
|
||||
line-height: 1.5;
|
||||
@include b(upload-file) {
|
||||
cursor: pointer;
|
||||
padding: 6px 12px 0 12px;
|
||||
transition: background-color .3s $--n-ease-in-out-cubic-bezier;
|
||||
border-radius: 6px;
|
||||
&:hover {
|
||||
background-color: map-get($--upload-file-item-background-color, 'hover');
|
||||
@include b(upload-file-info) {
|
||||
@include e(action) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include m(error-status) {
|
||||
@include b(upload-file-info) {
|
||||
@include e(name) {
|
||||
color: $--error-6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include m(success-status) {
|
||||
@include b(upload-file-info) {
|
||||
@include e(name) {
|
||||
color: $--success-6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include b(upload-file-info) {
|
||||
position: relative;
|
||||
padding-bottom: 6px;
|
||||
@include e(name) {
|
||||
font-size: 14px;
|
||||
color: $--n-secondary-text-color;
|
||||
transition: color .3s $--n-ease-in-out-cubic-bezier;
|
||||
}
|
||||
@include e(action) {
|
||||
padding-bottom: inherit;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: opacity .2s $--n-ease-in-out-cubic-bezier;
|
||||
flex-direction: row-reverse;
|
||||
@include b(button) {
|
||||
&:not(:first-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@include b(progress) {
|
||||
@include fade-in-height-expand-transition;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
styles/themes/dark/components/Upload.scss
Normal file
5
styles/themes/dark/components/Upload.scss
Normal file
@ -0,0 +1,5 @@
|
||||
@mixin setup-dark-upload {
|
||||
$--uploadd-file-item-background-color: (
|
||||
'hover': change-color($--primary-6, $alpha: .15)
|
||||
) !global;
|
||||
}
|
@ -55,6 +55,7 @@
|
||||
@import 'components/Result.scss';
|
||||
@import 'components/Tree.scss';
|
||||
@import 'components/Typography.scss';
|
||||
@import 'components/Upload.scss';
|
||||
|
||||
@mixin setup-dark-theme($in-js-env: false) {
|
||||
@include setup-dark-colors();
|
||||
@ -142,5 +143,6 @@
|
||||
@include setup-dark-result;
|
||||
@include setup-dark-tree;
|
||||
@include setup-dark-typography;
|
||||
@include setup-dark-upload;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
@mixin setup-light-progress {
|
||||
$--progress-rail-color: rgba(0, 0, 0, .1) !global;
|
||||
$--progress-rail-color: $--n-action-panel-color !global;
|
||||
$--progress-fill-color: (
|
||||
'default': $--info-6,
|
||||
'info': $--info-6,
|
||||
|
5
styles/themes/light/components/Upload.scss
Normal file
5
styles/themes/light/components/Upload.scss
Normal file
@ -0,0 +1,5 @@
|
||||
@mixin setup-light-upload {
|
||||
$--upload-file-item-background-color: (
|
||||
'hover': change-color($--primary-6, $alpha: .1)
|
||||
) !global;
|
||||
}
|
@ -55,6 +55,7 @@
|
||||
@import 'components/Result.scss';
|
||||
@import 'components/Tree.scss';
|
||||
@import 'components/Typography.scss';
|
||||
@import 'components/Upload.scss';
|
||||
|
||||
@mixin setup-light-theme($in-js-env: false) {
|
||||
@include setup-light-colors();
|
||||
@ -141,5 +142,6 @@
|
||||
@include setup-light-result;
|
||||
@include setup-light-tree;
|
||||
@include setup-light-typography;
|
||||
@include setup-light-upload;
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin fade-in-height-expand-transition ($name: 'fade-in-height-expand', $duration: .3s, $delay: 0s, $original-transition: ()) {
|
||||
@mixin fade-in-height-expand-transition ($name: 'fade-in-height-expand', $duration: .3s, $original-transition: (), $leave-delay: 0s) {
|
||||
&.#{$namespace}-#{$name}-transition-leave, &.#{$namespace}-#{$name}-transition-enter-to {
|
||||
opacity: 1;
|
||||
}
|
||||
@ -147,10 +147,10 @@
|
||||
&.#{$namespace}-#{$name}-transition-leave-active {
|
||||
overflow: hidden;
|
||||
transition:
|
||||
max-height $duration $--n-ease-in-out-cubic-bezier,
|
||||
opacity $duration $--n-ease-out-cubic-bezier,
|
||||
margin-top $duration $--n-ease-in-out-cubic-bezier,
|
||||
margin-bottom $duration $--n-ease-in-out-cubic-bezier,
|
||||
max-height $duration $--n-ease-in-out-cubic-bezier $leave-delay,
|
||||
opacity $duration $--n-ease-out-cubic-bezier $leave-delay,
|
||||
margin-top $duration $--n-ease-in-out-cubic-bezier $leave-delay,
|
||||
margin-bottom $duration $--n-ease-in-out-cubic-bezier $leave-delay,
|
||||
$original-transition;
|
||||
}
|
||||
&.#{$namespace}-#{$name}-transition-enter-active {
|
||||
|
Loading…
Reference in New Issue
Block a user