Răsfoiți Sursa

add listType

baiyaaaaa 8 ani în urmă
părinte
comite
02a1f74022

+ 0 - 1
components.json

@@ -46,7 +46,6 @@
   "row": "./packages/row/index.js",
   "col": "./packages/col/index.js",
   "upload": "./packages/upload/index.js",
-  "upload-dragger": "./packages/upload/index.js",
   "progress": "./packages/progress/index.js",
   "spinner": "./packages/spinner/index.js",
   "message": "./packages/message/index.js",

+ 120 - 52
examples/docs/zh-CN/upload.md

@@ -10,6 +10,32 @@
     .upload-demo {
       width: 360px;
     }
+    .avatar-uploader {
+      .el-upload {
+        border: 1px dashed #d9d9d9;
+        border-radius: 6px;
+        cursor: pointer;
+        position: relative;
+        overflow: hidden;
+
+        &:hover {
+          border-color: #20a0ff;
+        }
+      }
+      .avatar-uploader-icon {
+        font-size: 28px;
+        color: #8c939d;
+        width: 178px;
+        height: @width;
+        line-height: @height;
+        text-align: center;
+      }
+      .avatar {
+        width: 178px;
+        height: @width;
+        display: block;
+      }
+    }
   }
 </style>
 <script>
@@ -24,7 +50,8 @@
           name: 'food2.jpeg',
           url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
           status: 'finished'
-        }]
+        }],
+        imageUrl: ''
       };
     },
     methods: {
@@ -52,6 +79,21 @@
       },
       submitUpload() {
         this.$refs.upload.submit();
+      },
+      handleAvatarScucess(res, file) {
+        this.imageUrl = URL.createObjectURL(file.raw);
+      },
+      beforeAvatarUpload(file) {
+        const isJPG = file.type === 'image/jpeg';
+        const isLt2M = file.size / 1024 / 1024 < 2;
+
+        if (!isJPG) {
+          this.$message.error('上传头像图片只能是 JPG 格式!');
+        }
+        if (!isLt2M) {
+          this.$message.error('上传头像图片大小不能超过 2MB!');
+        }
+        return isJPG && isLt2M;
       }
     }
   }
@@ -61,13 +103,13 @@
 
 通过点击或者拖拽上传文件
 
-### 点击上传多个文件
+### 点击上传
 
 ::: demo 通过 slot 你可以传入自定义的上传按钮类型和文字提示。
 ```html
 <el-upload
   class="upload-demo"
-  action="//jsonplaceholder.typicode.com/posts/"
+  action="http://localhost:9000/upload"
   :on-preview="handlePreview"
   :on-remove="handleRemove"
   :file-list="fileList">
@@ -94,44 +136,58 @@
 ```
 :::
 
-::: demo 通过 slot 你可以传入自定义的上传按钮类型和文字提示。
+### 用户头像上传
+
+使用 beforeUpload 限制用户上传的图片格式和大小。
+
+::: demo
 ```html
 <el-upload
-  class="upload-demo"
-  draggable
-  action="//jsonplaceholder.typicode.com/posts/"
-  :on-preview="handlePreview"
-  :on-remove="handleRemove"
-  :file-list="fileList"
-  thumbnail-mode>
-  <i class="el-icon-upload"></i>
-  <div class="el-upload-dragger__text">将文件拖到此处,或<em>点击上传</em></div>
-  <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
+  class="avatar-uploader"
+  action="http://localhost:9000/upload"
+  :show-file-list="false"
+  :on-success="handleAvatarScucess"
+  :before-upload="beforeAvatarUpload">
+  <img v-if="imageUrl" :src="imageUrl" class="avatar">
+  <i v-else class="el-icon-plus avatar-uploader-icon"></i>
 </el-upload>
+<script>
+  export default {
+    data() {
+      return {
+        fileList: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}, {name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}]
+      };
+    },
+    methods: {
+      handleRemove(file, fileList) {
+        console.log(file, fileList);
+      },
+      handlePreview(file) {
+        console.log(file);
+      }
+    }
+  }
+</script>
 ```
 :::
 
-### 手动上传
+### 照片墙
+
+使用 listType 属性来设置文件列表的样式。
 
 ::: demo
 ```html
 <el-upload
-  class="upload-demo"
-  ref="upload"
-  action="//jsonplaceholder.typicode.com/posts/"
-  :on-preview="handlePreview"
-  :on-remove="handleRemove"
-  :file-list="fileList"
-  :auto-upload="false">
-  <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
-  <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
-  <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
+  action="http://localhost:9000/upload"
+  list-type="picture-card"
+  :on-preview="handlePreview">
+  <i class="el-icon-plus"></i>
 </el-upload>
 <script>
   export default {
     data() {
       return {
-        fileList: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}, {name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}]
+        fileList: []
       };
     },
     methods: {
@@ -147,25 +203,19 @@
 ```
 :::
 
-<!-- ### 拖拽上传
+### 图片列表缩略图
 
-可将文件拖入指定区域进行上传。
-
-::: demo 将 `type` 属性指定为 'drag' 可以将上传控件变为支持拖拽的形式,并且你可以通过 `multiple` 属性来控制是否支持多选,`on-preview` 和 `on-remove` 是一个钩子函数,分别在点击上传后的文件链接和点击移除上传后的文件后被调用。
+::: demo
 ```html
 <el-upload
-  action="//jsonplaceholder.typicode.com/posts/"
-  type="drag"
-  :multiple="true"
+  class="upload-demo"
+  action="http://localhost:9000/upload"
   :on-preview="handlePreview"
   :on-remove="handleRemove"
-  :on-success="handleSuccess"
-  :on-error="handleError"
   :file-list="fileList"
->
-  <i class="el-icon-upload"></i>
-  <div class="el-dragger__text">将文件拖到此处,或<em>点击上传</em></div>
-  <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
+  list-type="picture">
+  <el-button size="small" type="primary">点击上传</el-button>
+  <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
 </el-upload>
 <script>
   export default {
@@ -187,24 +237,41 @@
 ```
 :::
 
-### 上传单个图片
-
-专门针对图片类型文件的上传,上传后在原位置显示缩略图。
+### 拖拽上传
 
-::: demo `thumbnail-mode` 属性允许你将上传组件强制只允许图片上传,并支持展示上传文件的缩略图。
+::: demo
 ```html
 <el-upload
-  action="//jsonplaceholder.typicode.com/posts/"
-  type="drag"
-  :thumbnail-mode="true"
+  class="upload-demo"
+  drag
+  action="http://localhost:9000/upload"
   :on-preview="handlePreview"
   :on-remove="handleRemove"
   :file-list="fileList"
->
+  mutiple>
   <i class="el-icon-upload"></i>
-  <div class="el-dragger__text">将文件拖到此处,或<em>点击上传</em></div>
+  <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
   <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
 </el-upload>
+```
+:::
+
+### 手动上传
+
+::: demo
+```html
+<el-upload
+  class="upload-demo"
+  ref="upload"
+  action="http://localhost:9000/upload"
+  :on-preview="handlePreview"
+  :on-remove="handleRemove"
+  :file-list="fileList"
+  :auto-upload="false">
+  <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
+  <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
+  <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
+</el-upload>
 <script>
   export default {
     data() {
@@ -223,7 +290,7 @@
   }
 </script>
 ```
-::: -->
+:::
 
 ### Attribute
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
@@ -234,7 +301,7 @@
 | data | 可选参数, 上传时附带的额外参数 | object | — | — |
 | name | 可选参数, 上传的文件字段名 | string | — | file |
 | with-credentials | 支持发送 cookie 凭证信息 | boolean | — | false |
-| show-upload-list | 是否显示已上传文件列表 | boolean | — | true |
+| show-file-list | 是否显示已上传文件列表 | boolean | — | true |
 | type | 上传控件类型 | string | select,drag | select |
 | accept | 可选参数, 接受上传的[文件类型](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept)(thumbnail-mode 模式下此参数无效)| string | — | — |
 | on-preview | 可选参数, 点击已上传的文件链接时的钩子, 可以通过 file.response 拿到服务端返回数据 | function(file) | — | — |
@@ -243,8 +310,9 @@
 | on-error | 可选参数, 文件上传失败时的钩子 | function(err, response, file) | — | — |
 | on-progress | 可选参数, 文件上传时的钩子 | function(event, file, fileList) | — | — |
 | before-upload | 可选参数, 上传文件之前的钩子,参数为上传的文件,若返回 false 或者 Promise 则停止上传。 | function(file) | — | — |
-| thumbnail-mode | 是否设置为图片模式,该模式下会显示图片缩略图 | boolean | — | false |
-| fileList | 默认已上传的文件列表, 例如: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}] | array | — | [] |
+| list-type | 文件列表的类型 | string | text/picture/picture-card | text |
+| auto-upload | 是否在选取文件后立即进行上传 | boolean | — | true |
+| fileList | 上传的文件列表, 例如: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}] | array | — | [] |
 
 ### Methods
 | 方法名      | 说明          | 参数 |

+ 9 - 1
packages/theme-default/src/common/transition.css

@@ -57,5 +57,13 @@
 }
 
 .collapse-transition {
-    transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
+  transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
+}
+
+.list-enter-active, .list-leave-active {
+  transition: all 1s;
+}
+.list-enter, .list-leave-active {
+  opacity: 0;
+  transform: translateY(-30px);
 }

+ 254 - 99
packages/theme-default/src/upload.css

@@ -5,63 +5,110 @@
 @component-namespace el {
   @b upload {
     display: inline-block;
+    text-align: center;
 
     @e input {
       display: none;
     }
-    @e inner {
-      display: inline-block;
-      position: relative;
+    @e tip {
+      font-size: 12px;
+      color: var(--color-base-silver);
+      margin-top: 7px;
+    }
+    & iframe {
+      position: absolute;
+      z-index: -1;
+      top: 0;
+      left: 0;
+      opacity: 0;
+      filter: alpha(opacity=0);
+    }
+    /* 照片墙模式 */
+    @m picture-card {
+      background-color: #fbfdff;
+      border: 1px dashed #c0ccda;
+      border-radius: 6px;
+      box-sizing: border-box;
+      width: 148px;
+      height: @width;
+      cursor: pointer;
+      line-height: calc(@height - 2);
 
-      & iframe {
-        position: absolute;
-        z-index: -1;
-        top: 0;
-        left: 0;
-        opacity: 0;
-        filter: alpha(opacity=0);
+      i {
+        font-size: 28px;
+        color: #8c939d;
+      }
+
+      &:hover {
+        border-color: var(--color-primary);
+        color: var(--color-primary);
       }
     }
-    @e files {
-      margin: 0;
-      padding: 0;
-      list-style: none;
-      margin-bottom: 10px;
+  }
+  @b upload-dragger {
+    background-color: #fff;
+    border: 1px dashed #d9d9d9;
+    border-radius: 6px;
+    box-sizing: border-box;
+    width: 360px;
+    height: 180px;
+    text-align: center;
+    cursor: pointer;
+    position: relative;
+    overflow: hidden;
+
+    & .el-icon-upload {
+      font-size: 67px;
+      color: var(--color-light-silver);
+      margin: 40px 0 16px;
+      line-height: 50px;
+    }
+
+    & + .el-upload__tip {
+      text-align: center;
     }
-    @e file {
+    & ~ .el-upload__files {
+      border-top: 1px solid rgba(var(--color-extra-light-silver), .2);
+      margin-top: 7px;
+      padding-top: 5px;
+    }
+    .el-upload__text {
+      color: var(--color-light-silver);
+      font-size: 14px;
+      text-align: center;
+
+      & em {
+        color: var(--color-primary);
+        font-style: normal;
+      }
+    }
+
+    &:hover {
+      border-color: var(--color-primary);
+    }
+
+    @when dragOver {
+      background-color: rgba(32, 159, 255, .06);
+      border: 2px dashed var(--color-primary);
+    }
+  }
+  @b upload-list {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+
+    @e item {
       transition: all .5s cubic-bezier(.55,0,.1,1);
       font-size: 14px;
       color: var(--color-extra-light-black);
-      line-height: 32px;
+      line-height: 1.8;
+      margin-top: 5px;
       position: relative;
       box-sizing: border-box;
       border-radius: 4px;
       width: 100%;
       position: relative;
 
-      @e name {
-        color: var(--color-extra-light-black);
-        display: block;
-        margin-right: 40px;
-        overflow: hidden;
-        padding-left: 4px;
-        text-overflow: ellipsis;
-        transition: color .3s;
-        white-space: nowrap;
-
-        [class^="el-icon"] {
-          color: var(--color-light-silver);
-          margin-right: 7px;
-          height: 100%;
-          line-height: inherit;
-        }
-      }
-      @e icon {
-        position: absolute;
-        right: 0;
-        top: 0;
-        line-height: inherit;
-      }
       & .el-progress {
         position: absolute;
         bottom: -3px;
@@ -77,97 +124,205 @@
         margin-right: 0;
         padding-right: 0;
       }
+      &:first-child {
+        margin-top: 10px;
+      }
       &:hover {
         background-color: var(--color-extra-light-gray);
       }
       @when success {
-        .el-upload__file__icon {
-          color: var(--color-success);
-        }
-        .el-upload__file__name:hover {
+        .el-upload-list__item-name:hover {
           color: var(--link-hover-color);
           cursor: pointer;
         }
+        .el-icon-close {
+          display: none;
+        }
         &:hover {
-          .el-upload__file__icon {
-            display: none;
-          }
-          .el-upload__btn-delete {
-            display: block;
+          .el-icon-close {
+            display: inline-block;
             cursor: pointer;
+            opacity: .75;
+            transform: scale(.7);
+            color: var(--color-extra-light-black);
+            &:hover {
+              opacity: 1;
+            }
+          }
+          .el-icon-circle-check,
+          .el-icon-check {
+            display: none;
           }
         }
       }
-      @when fail {
-        .el-upload__file__icon {
-          color: var(--color-error);
-        }
+    }
+    @e item-name {
+      color: var(--color-extra-light-black);
+      display: block;
+      margin-right: 40px;
+      overflow: hidden;
+      padding-left: 4px;
+      text-overflow: ellipsis;
+      transition: color .3s;
+      white-space: nowrap;
+
+      [class^="el-icon"] {
+        color: var(--color-light-silver);
+        margin-right: 7px;
+        height: 100%;
+        line-height: inherit;
       }
     }
-    @e tip {
-      font-size: 12px;
-      color: var(--color-base-silver);
-      margin-top: 7px;
+    @e item-status-label {
+      position: absolute;
+      right: 10px;
+      top: 0;
+      line-height: inherit;
+      color: var(--color-success);
     }
-    @e btn-delete {
+    @e item-delete {
       position: absolute;
-      right: 15px;
+      right: 10px;
       top: 0;
       font-size: 12px;
-      color: var(--color-primary);
+      color: var(--color-extra-light-black);
       display: none;
-    }
-    /* 拖拽模式 */
-    @m draggable {
-      background-color: var(--color-dark-white);
-      border: 1px solid var(--color-extra-light-silver);
-      box-sizing: border-box;
-      width: 360px;
-      height: 180px;
-      border-radius: 4px;
-      text-align: center;
-      cursor: pointer;
-      position: relative;
-      overflow: hidden;
 
-      & .el-upload__inner {
-        display: block;
-        height: 100%;
-      }
-      & .el-icon-upload {
-        font-size: 67px;
-        color: var(--color-light-silver);
-        margin: 40px 0 16px;
-        line-height: 50px;
+      &: hover {
+        color: var(--color-primary);
       }
+    }
+    @m picture-card {
+      float: left;
+      margin: 0;
 
-      & + .el-upload__tip {
-        text-align: center;
+      .el-upload-list__item {
+        overflow: hidden;
+        background-color: #fff;
+        border: 1px solid #c0ccda;
+        border-radius: 6px;
+        box-sizing: border-box;
+        width: 148px;
+        height: @width;
+        margin: 0 8px 8px 0;
+        display: inline-block;
+
+        &:hover .el-upload-list__item-status-label {
+          display: none;
+        }
+      }
+      .el-upload-list__item-name {
+        display: none;
       }
-      & ~ .el-upload__files {
-        margin-top: 7px;
-        padding-top: 5px;
-        border-top: 1px solid rgba(var(--color-extra-light-silver), .2);
+      .el-upload-list__item-thumbnail {
+        width: 100%;
+        height: 100%;
       }
+      .el-upload-list__item-status-label {
+        position: absolute;
+        right: -15px;
+        top: -6px;
+        width: 40px;
+        height: 24px;
+        background: #13ce66;
+        text-align: center;
+        transform: rotate(45deg);
+        box-shadow: 0 0 1pc 1px rgba(0,0,0,0.2);
 
-      @e text {
-        color: var(--color-light-silver);
-        font-size: 14px;
+        i {
+          font-size: 12px;
+          margin-top: 11px;
+          transform: rotate(-45deg) scale(0.8);
+          color: #fff;
+        }
+      }
+      .el-upload-list__item-actions {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        left: 0;
+        top: 0;
+        cursor: default;
         text-align: center;
+        color: #fff;
+        opacity: 0;
+        font-size: 20px;
+        background-color: rgba(0, 0, 0, .5);
+        transition: opacity .3s;
+        @utils-vertical-center;
 
-        & em {
-          color: var(--color-primary);
-          font-style: normal;
+        span {
+          display: none;
+          cursor: pointer;
+        }
+        span + span {
+          margin-left: 15px;
+        }
+
+        .el-upload-list__item-delete {
+          position: static;
+          font-size: inherit;
+          color: inherit;
+        }
+
+        &:hover {
+          opacity: 1;
+          span {
+            display: inline-block;
+          }
         }
       }
+    }
+    @m picture {
+      .el-upload-list__item {
+        overflow: hidden;
+        background-color: #fff;
+        border: 1px solid #c0ccda;
+        border-radius: 6px;
+        box-sizing: border-box;
+        margin-top: 10px;
 
-      &:not(.is-showCover):hover {
-        border-color: var(--color-primary);
+        &:hover {
+          .el-upload-list__item-status-label {
+            background: transparent;
+            box-shadow: none;
+            top: -2px;
+            right: -12px;
+
+            .el-icon-close {
+              transform: rotate(45deg) scale(.7);
+            }
+          }
+        }
+      }
+      .el-upload-list__item-thumbnail {
+        vertical-align: middle;
+        display: inline-block;
+        width: 70px;
+        height: 70px;
+        margin: 10px;
+      }
+      .el-upload-list__item-name {
+        display: inline-block;
+        vertical-align: middle;
       }
+      .el-upload-list__item-status-label {
+        position: absolute;
+        right: -17px;
+        top: -7px;
+        width: 46px;
+        height: 26px;
+        background: #13ce66;
+        text-align: center;
+        transform: rotate(45deg);
+        box-shadow: 0 1px 1px #ccc;
 
-      @when dragOver {
-        background-color: rgba(32, 159, 255, .06);
-        border: 2px dashed var(--color-primary);
+        i {
+          font-size: 12px;
+          margin-top: 12px;
+          transform: rotate(-45deg) scale(0.8);
+          color: #fff;
+        }
       }
     }
   }

+ 0 - 8
packages/upload-dragger/index.js

@@ -1,8 +0,0 @@
-import UploadDragger from '../upload/src/upload-dragger';
-
-/* istanbul ignore next */
-UploadDragger.install = function(Vue) {
-  Vue.component(UploadDragger.name, UploadDragger);
-};
-
-export default UploadDragger;

+ 0 - 10
packages/upload/_index.js

@@ -1,10 +0,0 @@
-import Upload from './src';
-import UploadDragger from './src/upload-dragger';
-
-/* istanbul ignore next */
-Upload.install = function(Vue) {
-  Vue.component(Upload.name, Upload);
-  Vue.component(UploadDragger.name, UploadDragger);
-};
-
-export default { Upload, UploadDragger };

+ 0 - 61
packages/upload/src/cover.vue

@@ -1,61 +0,0 @@
-<template>
-  <div class="el-upload-cover" @click.stop v-if="image">
-    <transition name="el-fade-in">
-      <el-progress
-        class="el-upload-cover__progress"
-        v-if="image.status === 'uploading'"
-        :percentage="image.percentage"
-        :show-text="false"
-      >
-      </el-progress>
-    </transition>
-    <div
-      class="el-upload-cover__content"
-      v-if="image.status === 'success'"
-      @mouseenter="mouseover = true"
-      @mouseleave="mouseover = false"
-    >
-      <img :src="image.url">
-      <label class="el-upload-cover__label"><i class="el-icon-check"></i></label>
-      <transition name="el-fade-in">
-        <div v-show="mouseover" class="el-upload-cover__interact">
-          <div class="el-draggeer__cover__btns">
-            <span class="btn" @click="$parent.handleClick()"><i class="el-icon-upload2"></i><span>{{ t('el.upload.continue') }}</span></span>
-            <span class="btn" @click="$emit('preview', image)"><i class="el-icon-view"></i><span>{{ t('el.upload.preview') }}</span></span>
-            <span class="btn" @click="$emit('remove', image)"><i class="el-icon-delete2"></i><span>{{ t('el.upload.delete') }}</span></span>
-          </div>
-        </div>
-      </transition>
-      <transition name="el-zoom-in-bottom">
-        <h4 v-show="mouseover" class="el-upload-cover__title">{{image.name}}</h4>
-      </transition>
-    </div>
-  </div>
-</template>
-<script>
-  import Locale from 'element-ui/src/mixins/locale';
-  import ElProgress from 'element-ui/packages/progress';
-
-  export default {
-    mixins: [Locale],
-
-    components: { ElProgress },
-
-    props: {
-      image: {},
-      onPreview: {
-        type: Function,
-        default: function() {}
-      },
-      onRemove: {
-        type: Function,
-        default: function() {}
-      }
-    },
-    data() {
-      return {
-        mouseover: false
-      };
-    }
-  };
-</script>

+ 0 - 30
packages/upload/src/dragger.js

@@ -1,30 +0,0 @@
-import Cover from './cover';
-
-export default {
-  components: {
-    Cover
-  },
-  data() {
-    return {
-      dragOver: false
-    };
-  },
-  computed: {
-    lastestFile() {
-      return this.fileList[this.fileList.length - 1];
-    },
-    showCover() {
-      var file = this.lastestFile;
-      return this.thumbnailMode && file && file.status !== 'fail';
-    },
-    thumbnailMode() {
-      return this.$parent.thumbnailMode;
-    }
-  },
-  methods: {
-    onDrop(e) {
-      this.dragOver = false;
-      this.uploadFiles(e.dataTransfer.files);
-    }
-  }
-};

+ 41 - 54
packages/upload/src/iframe-upload.vue

@@ -1,9 +1,9 @@
 <script>
-import Cover from './cover';
+import UploadDragger from './upload-dragger.vue';
 
 export default {
   components: {
-    Cover
+    UploadDragger
   },
   props: {
     type: String,
@@ -30,12 +30,13 @@ export default {
     onRemove: {
       type: Function,
       default: function() {}
-    }
+    },
+    drag: Boolean,
+    listType: String
   },
 
   data() {
     return {
-      dragOver: false,
       mouseover: false,
       domain: '',
       file: null,
@@ -43,31 +44,22 @@ export default {
     };
   },
 
-  computed: {
-    lastestFile() {
-      var fileList = this.$parent.fileList;
-      return fileList[fileList.length - 1];
-    },
-    showCover() {
-      var file = this.lastestFile;
-      return this.thumbnailMode && file && file.status !== 'fail';
-    },
-    thumbnailMode() {
-      return this.$parent.thumbnailMode;
-    }
-  },
-
   methods: {
     isImage(str) {
       return str.indexOf('image') !== -1;
     },
     handleClick() {
-      if (!this.disabled) {
-        this.$refs.input.click();
-      }
+      this.$refs.input.click();
     },
     handleChange(ev) {
-      const file = ev.target.files[0];
+      const file = ev.target.value;
+      if (file) {
+        this.uploadFiles(file);
+      }
+    },
+    uploadFiles(file) {
+      if (this.disabled) return;
+      this.disabled = true;
       this.file = file;
       this.onStart(file);
 
@@ -86,58 +78,49 @@ export default {
       dataSpan.innerHTML = inputs.join('');
       formNode.submit();
       dataSpan.innerHTML = '';
-      this.disabled = true;
     },
     getFormNode() {
       return this.$refs.form;
     },
     getFormDataNode() {
       return this.$refs.data;
-    },
-    onDrop(e) {
-      e.preventDefault();
-      this.dragOver = false;
-      this.uploadFiles(e.dataTransfer.files);
-    },
-    handleDragover(e) {
-      e.preventDefault();
-      this.onDrop = true;
-    },
-    handleDragleave(e) {
-      e.preventDefault();
-      this.onDrop = false;
-    },
-    onload(e) {
-      this.disabled = false;
     }
   },
 
+  created() {
+    this.frameName = 'frame-' + Date.now();
+  },
+
   mounted() {
+    const self = this;
     !this.$isServer && window.addEventListener('message', (event) => {
-      var targetOrigin = new URL(this.action).origin;
-      if (event.origin !== targetOrigin) {
-        return false;
-      }
+      if (!self.file) return;
+      var targetOrigin = new URL(self.action).origin;
+      if (event.origin !== targetOrigin) return;
       var response = event.data;
       if (response.result === 'success') {
-        this.onSuccess(response, this.file);
+        self.onSuccess(response, self.file);
       } else if (response.result === 'failed') {
-        this.onSuccess(response, this.file);
+        self.onError(response, self.file);
       }
+      self.disabled = false;
+      self.file = null;
     }, false);
   },
 
   render(h) {
-    var cover = <cover image={this.lastestFile} onPreview={this.onPreview} onRemove={this.onRemove}></cover>;
-    var frameName = 'frame-' + Date.now();
+    const {
+      drag,
+      uploadFiles,
+      listType,
+      frameName
+    } = this;
+    const oClass = { 'el-upload': true };
+    oClass[`el-upload--${listType}`] = true;
+
     return (
       <div
-        class={{
-          'el-upload__inner': true,
-          'el-dragger': this.type === 'drag',
-          'is-dragOver': this.dragOver,
-          'is-showCover': this.showCover
-        }}
+        class={oClass}
         on-click={this.handleClick}
         nativeOn-drop={this.onDrop}
         nativeOn-dragover={this.handleDragover}
@@ -161,7 +144,11 @@ export default {
           <input type="hidden" name="documentDomain" value={ this.$isServer ? '' : document.domain } />
           <span ref="data"></span>
         </form>
-        {!this.showCover ? this.$slots.default : cover}
+        {
+          drag
+          ? <upload-dragger on-file={uploadFiles}>{this.$slots.default}</upload-dragger>
+          : this.$slots.default
+        }
       </div>
     );
   }

+ 23 - 31
packages/upload/src/index.vue

@@ -1,7 +1,6 @@
 <script>
 import UploadList from './upload-list';
 import Upload from './upload';
-import UploadDragger from './upload-dragger';
 import IframeUpload from './iframe-upload';
 import ElProgress from 'element-ui/packages/progress';
 
@@ -14,7 +13,6 @@ export default {
     ElProgress,
     UploadList,
     Upload,
-    UploadDragger,
     IframeUpload
   },
 
@@ -35,11 +33,11 @@ export default {
       type: String,
       default: 'file'
     },
-    draggable: Boolean,
+    drag: Boolean,
     dragger: Boolean,
     withCredentials: Boolean,
     thumbnailMode: Boolean,
-    showUploadList: {
+    showFileList: {
       type: Boolean,
       default: true
     },
@@ -58,8 +56,7 @@ export default {
       default: noop
     },
     onPreview: {
-      type: Function,
-      default: noop
+      type: Function
     },
     onSuccess: {
       type: Function,
@@ -82,6 +79,10 @@ export default {
     autoUpload: {
       type: Boolean,
       default: true
+    },
+    listType: {
+      type: String,
+      default: 'text'   // text,picture,picture-card
     }
   },
 
@@ -168,11 +169,6 @@ export default {
       });
       return target;
     },
-    handlePreview(file) {
-      if (file.status === 'finished') {
-        this.onPreview(file);
-      }
-    },
     clearFiles() {
       this.uploadFiles = [];
     },
@@ -188,12 +184,13 @@ export default {
   render(h) {
     var uploadList;
 
-    if (this.showUploadList && this.uploadFiles.length) {
+    if (this.showFileList) {
       uploadList = (
         <UploadList
+          listType={this.listType}
           files={this.uploadFiles}
           on-remove={this.handleRemove}
-          on-preview={this.handlePreview}>
+          handlePreview={this.onPreview}>
         </UploadList>
       );
     }
@@ -201,7 +198,7 @@ export default {
     var uploadData = {
       props: {
         type: this.type,
-        draggable: this.draggable,
+        drag: this.drag,
         action: this.action,
         multiple: this.multiple,
         'before-upload': this.beforeUpload,
@@ -212,36 +209,31 @@ export default {
         accept: this.thumbnailMode ? 'image/gif, image/png, image/jpeg, image/bmp, image/webp' : this.accept,
         fileList: this.uploadFiles,
         autoUpload: this.autoUpload,
+        listType: this.listType,
         'on-start': this.handleStart,
         'on-progress': this.handleProgress,
         'on-success': this.handleSuccess,
         'on-error': this.handleError,
-        'on-preview': this.handlePreview,
+        'on-preview': this.onPreview,
         'on-remove': this.handleRemove
       },
       ref: 'upload-inner'
     };
 
-    // var uploadComponent = (typeof FormData !== 'undefined' || this.$isServer)
-    //     ? <upload {...props}>{this.$slots.default}</upload>
-    //     : <iframeUpload {...props}>{this.$slots.default}</iframeUpload>;
-
-    if (this.draggable) {
-      return (
-        <div>
-          <upload {...uploadData}>{this.$slots.trigger || this.$slots.default}</upload>
-          {this.$slots.tip}
-          {uploadList}
-        </div>
-      );
-    }
+    const trigger = this.$slots.trigger || this.$slots.default;
+    const uploadComponent = (typeof FormData !== 'undefined' || this.$isServer)
+        ? <upload {...uploadData}>{trigger}</upload>
+        : <iframeUpload {...uploadData}>{trigger}</iframeUpload>;
 
     return (
       <div>
-        {uploadList}
-        <upload {...uploadData}>{this.$slots.trigger || this.$slots.default}</upload>
-        {this.$slots.default}
+        {
+          this.$slots.trigger
+          ? [uploadComponent, this.$slots.default]
+          : uploadComponent
+        }
         {this.$slots.tip}
+        {uploadList}
       </div>
     );
   }

+ 4 - 61
packages/upload/src/upload-dragger.vue

@@ -2,86 +2,29 @@
   <div
     class="el-upload-dragger"
     :class="{
-      'is-dragOver': dragOver,
-      'is-showCover': showCover
+      'is-dragOver': dragOver
     }"
-    @click="handleClick"
     @drop.prevent="onDrop"
     @dragover.prevent="dragOver = true"
     @dragleave.prevent="dragOver = false"
   >
-    <slot v-if="!showCover"></slot>
-    <cover :image="lastestFile" :on-preview="onPreview" :on-remove="onRemove" v-else></cover>
-    <input class="el-upload__input" type="file" ref="input" @change="handleChange" :multiple="multiple" :accept="accept">
+    <slot></slot>
   </div>
 </template>
 <script>
-  import Upload from './upload';
-  import IframeUpload from './iframe-upload';
-  import Cover from './cover';
-
   export default {
-    name: 'ElUploadDragger',
-
-    extends: Upload,
+    name: 'ElUploadDrag',
 
-    components: {
-      IframeUpload,
-      Cover
-    },
     data() {
       return {
         dragOver: false
       };
     },
-    props: {
-      draggable: {
-        type: Boolean,
-        default: true
-      }
-    },
-    computed: {
-      lastestFile() {
-        var fileList = this.$parent.fileList;
-        return fileList[fileList.length - 1];
-      },
-      showCover() {
-        var file = this.lastestFile;
-        return this.thumbnailMode && file && file.status !== 'fail';
-      },
-      thumbnailMode() {
-        return this.$parent.thumbnailMode;
-      }
-    },
     methods: {
       onDrop(e) {
         this.dragOver = false;
-        this.uploadFiles(e.dataTransfer.files);
+        this.$emit('file', e.dataTransfer.files);
       }
     }
-    // render(h) {
-    //   let {
-    //     dragover
-    //   } = this;
-
-    //   let content = this.showCover
-    //     ? <cover image={this.lastestFile} on-preview={this.onPreview} on-remove={this.onRemove}></cover>
-    //     : this.$slots.default
-    //   return (
-    //     <div
-    //       class={{
-    //         'el-upload-dragger': true,
-    //         'is-dragOver': this.dragOver,
-    //         'is-showCover': this.showCover
-    //       }}
-    //       @drop.prevent="onDrop"
-    //       @dragover.prevent="dragOver = true"
-    //       @dragleave.prevent="dragOver = false"
-    //     >
-    //       {content}
-    //       <input class="el-upload__input" type="file" ref="input" on-change={this.handleChange} multiple={this.multiple} accept={this.accept} />
-    //     </div>
-    //   );
-    // }
   };
 </script>

+ 44 - 11
packages/upload/src/upload-list.vue

@@ -1,20 +1,48 @@
 <template>
-  <transition-group tag="ul" class="el-upload__files" name="list">
+  <transition-group
+    tag="ul"
+    :class="['el-upload-list', 'el-upload-list--' + listType]"
+    name="list"
+  >
     <li
       v-for="file in files"
-      :class="['el-upload__file', 'is-' + file.status]"
+      :class="['el-upload-list__item', 'is-' + file.status]"
       :key="file"
-      @click="$emit('clickFile', file)"
     >
-      <a class="el-upload__file__name" @click="$emit('preview', file)">
+      <img class="el-upload-list__item-thumbnail" v-if="['picture-card', 'picture'].indexOf(listType) > -1" :src="file.url" alt="">
+      <a class="el-upload-list__item-name" @click="handleClick(file)">
         <i class="el-icon-document"></i>{{file.name}}
       </a>
-      <i :class="{
-        'el-upload__file__icon': true,
-        'el-icon-circle-check': file.status === 'success',
-        'el-icon-circle-cross': file.status === 'fail'
-      }"></i>
-      <span class="el-upload__btn-delete" @click="$emit('remove', file)" v-show="file.status === 'success'">{{ t('el.upload.delete') }}</span>
+      <label v-show="file.status === 'success'" class="el-upload-list__item-status-label">
+        <i :class="{
+            'el-icon-circle-check': listType === 'text',
+            'el-icon-check': ['picture-card', 'picture'].indexOf(listType) > -1
+        }"></i>
+        <i class="el-icon-close" @click="$emit('remove', file)"></i>
+      </label>
+      <span class="el-upload-list__item-actions"
+        v-if="
+          listType === 'picture-card' &&
+          file.status === 'success'
+        "
+      >
+        <span
+          v-if="
+            handlePreview &&
+            listType === 'picture-card'
+          "
+          @click="handlePreview(file)"
+          class="el-upload-list__item-preview"
+        >
+          <i class="el-icon-view"></i>
+        </span>
+        <span
+          class="el-upload-list__item-delete"
+          @click="$emit('remove', file)"
+        >
+          <i class="el-icon-delete2"></i>
+        </span>
+      </span>
       <el-progress
         v-if="file.status === 'uploading'"
         :stroke-width="2"
@@ -38,11 +66,16 @@
         default() {
           return [];
         }
-      }
+      },
+      handlePreview: Function,
+      listType: String
     },
     methods: {
       parsePercentage(val) {
         return parseInt(val, 10);
+      },
+      handleClick(file) {
+        this.handlePreview && this.handlePreview(file);
       }
     }
   };

+ 13 - 35
packages/upload/src/upload.vue

@@ -1,11 +1,11 @@
 <script>
-import merge from 'element-ui/src/utils/merge';
 import ajax from './ajax';
-import dragger from './dragger';
+import UploadDragger from './upload-dragger.vue';
 
 export default {
-  mixins: [dragger],
-
+  components: {
+    UploadDragger
+  },
   props: {
     type: String,
     action: {
@@ -26,7 +26,7 @@ export default {
     onSuccess: Function,
     onError: Function,
     beforeUpload: Function,
-    draggable: Boolean,
+    drag: Boolean,
     onPreview: {
       type: Function,
       default: function() {}
@@ -36,7 +36,8 @@ export default {
       default: function() {}
     },
     fileList: Array,
-    autoUpload: Boolean
+    autoUpload: Boolean,
+    listType: String
   },
 
   data() {
@@ -118,15 +119,12 @@ export default {
   render(h) {
     let {
       handleClick,
-      draggable,
-      onDrop,
-      showCover,
-      onPreview,
-      onRemove,
+      drag,
       handleChange,
       multiple,
       accept,
-      lastestFile
+      listType,
+      uploadFiles
     } = this;
     const data = {
       class: {
@@ -136,32 +134,12 @@ export default {
         click: handleClick
       }
     };
-    if (draggable) {
-      merge(data.on, {
-        dragover: (ev) => {
-          ev.preventDefault();
-          this.dragOver = true;
-        },
-        dragleave: (ev) => {
-          ev.preventDefault();
-          this.dragOver = false;
-        },
-        drop: (ev) => {
-          ev.preventDefault();
-          onDrop(ev);
-        }
-      });
-      merge(data.class, {
-        'el-upload--draggable': true,
-        'is-dragOver': this.dragOver,
-        'is-showCover': this.showCover
-      });
-    }
+    data.class[`el-upload--${listType}`] = true;
     return (
       <div {...data}>
         {
-          showCover
-          ? <cover image={lastestFile} on-preview={onPreview} on-remove={onRemove}></cover>
+          drag
+          ? <upload-dragger on-file={uploadFiles}>{this.$slots.default}</upload-dragger>
           : this.$slots.default
         }
         <input class="el-upload__input" type="file" ref="input" on-change={handleChange} multiple={multiple} accept={accept}></input>

+ 0 - 3
src/index.js

@@ -47,7 +47,6 @@ import Icon from '../packages/icon';
 import Row from '../packages/row';
 import Col from '../packages/col';
 import Upload from '../packages/upload';
-import UploadDragger from '../packages/upload-dragger';
 import Progress from '../packages/progress';
 import Spinner from '../packages/spinner';
 import Message from '../packages/message';
@@ -109,7 +108,6 @@ const components = [
   Row,
   Col,
   Upload,
-  UploadDragger,
   Progress,
   Spinner,
   Badge,
@@ -203,7 +201,6 @@ module.exports = {
   Row,
   Col,
   Upload,
-  UploadDragger,
   Progress,
   Spinner,
   Message,