Commit 35f5872c5c9a175c851f51db068b4b36df2e43e1

Authored by 刘汉宸
1 parent bd5ed09d

feat: 新增Upload组件

examples/router/routes.js
... ... @@ -30,6 +30,12 @@ const _components = [
30 30 meta: { title: 'Select 选择器' },
31 31 component: () => import('@/views/docs/component/select.md'),
32 32 },
  33 + {
  34 + path: 'upload',
  35 + name: 'upload',
  36 + meta: { title: 'Upload 上传' },
  37 + component: () => import('@/views/docs/component/upload.md'),
  38 + },
33 39 ],
34 40 },
35 41 {
... ...
examples/views/docs/component/upload.md 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +# Upload 上传
  2 +
  3 +文件上传,支持图片与文件
  4 +
  5 +## 基础用法
  6 +
  7 +默认为图片上传
  8 +
  9 +::: snippet 图片上传
  10 +
  11 +```html
  12 +<template>
  13 + <div>
  14 + <z-upload v-model="model"></z-upload>
  15 + </div>
  16 +</template>
  17 +
  18 +<script>
  19 +export default {
  20 + data() {
  21 + return {
  22 + model: '',
  23 + }
  24 + }
  25 +}
  26 +</script>
  27 +```
  28 +
  29 +:::
  30 +
  31 +## API
  32 +
  33 +## Attribute 属性
  34 +
  35 +参数|说明|类型|可选值|默认值
  36 +-|-|-|-|-
  37 +value | 值 | String | - | -
  38 +
  39 +## Events 事件
  40 +
  41 +事件名称|说明|回调参数
  42 +-|-|-
  43 +change | 改变选中 | 值,选中项数据
0 44 \ No newline at end of file
... ...
packages/select/index.vue
1 1 <template>
2 2 <el-select
3   - class="zee__select"
  3 + class="zee-select"
4 4 :disabled="disabled"
5 5 :value-key="valueKey"
6 6 :filterable="filterable"
... ... @@ -234,7 +234,7 @@ export default {
234 234 </script>
235 235  
236 236 <style lang="scss">
237   -.zee__select {
  237 +.zee-select {
238 238 .el-input__prefix {
239 239 height: 100%;
240 240 display: flex;
... ...
packages/upload/image-viewer.vue 0 → 100644
... ... @@ -0,0 +1,332 @@
  1 +<template>
  2 + <transition name="viewer-fade">
  3 + <div tabindex="-1" ref="el-image-viewer__wrapper" class="el-image-viewer__wrapper" :style="{ 'z-index': zIndex }">
  4 + <div class="el-image-viewer__mask"></div>
  5 + <!-- CLOSE -->
  6 + <span class="el-image-viewer__btn el-image-viewer__close" @click="hide">
  7 + <i class="el-icon-circle-close"></i>
  8 + </span>
  9 + <!-- ARROW -->
  10 + <template v-if="!isSingle">
  11 + <span class="el-image-viewer__btn el-image-viewer__prev" :class="{ 'is-disabled': !infinite && isFirst }" @click="prev">
  12 + <i class="el-icon-arrow-left" />
  13 + </span>
  14 + <span class="el-image-viewer__btn el-image-viewer__next" :class="{ 'is-disabled': !infinite && isLast }" @click="next">
  15 + <i class="el-icon-arrow-right" />
  16 + </span>
  17 + </template>
  18 + <!-- ACTIONS -->
  19 + <div class="el-image-viewer__btn el-image-viewer__actions">
  20 + <div class="el-image-viewer__actions__inner">
  21 + <i class="el-icon-zoom-out el-image-viewer__actions-btn" @click="handleActions('zoomOut')"></i>
  22 + <i class="el-icon-zoom-in el-image-viewer__actions-btn" @click="handleActions('zoomIn')"></i>
  23 + <i class="el-image-viewer__actions__divider"></i>
  24 + <i class="el-image-viewer__actions-btn" :class="mode.icon" @click="toggleMode"></i>
  25 + <i class="el-image-viewer__actions__divider"></i>
  26 + <i class="el-icon-refresh-left el-image-viewer__actions-btn" @click="handleActions('anticlocelise')"></i>
  27 + <i class="el-icon-refresh-right el-image-viewer__actions-btn" @click="handleActions('clocelise')"></i>
  28 + <!-- 自定义修改 -->
  29 + <span class="el-radio__label">{{ index + 1 }} / {{ urlList.length }}</span>
  30 + </div>
  31 + </div>
  32 + <!-- CANVAS -->
  33 + <div class="el-image-viewer__canvas">
  34 + <template v-for="(url, i) in urlList">
  35 + <img
  36 + v-if="i === index"
  37 + ref="img"
  38 + class="el-image-viewer__img"
  39 + :key="url"
  40 + :src="currentImg"
  41 + :style="imgStyle"
  42 + @load="handleImgLoad"
  43 + @error="handleImgError"
  44 + @mousedown="handleMouseDown"
  45 + />
  46 + </template>
  47 + </div>
  48 + </div>
  49 + </transition>
  50 +</template>
  51 +
  52 +<script>
  53 +import { on, off } from 'element-ui/src/utils/dom';
  54 +import { rafThrottle, isFirefox } from 'element-ui/src/utils/util';
  55 +
  56 +const Mode = {
  57 + CONTAIN: {
  58 + name: 'contain',
  59 + icon: 'el-icon-full-screen',
  60 + },
  61 + ORIGINAL: {
  62 + name: 'original',
  63 + icon: 'el-icon-c-scale-to-original',
  64 + },
  65 +};
  66 +
  67 +const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';
  68 +
  69 +export default {
  70 + name: 'elImageViewer',
  71 +
  72 + props: {
  73 + urlList: {
  74 + type: Array,
  75 + default: () => [],
  76 + },
  77 + zIndex: {
  78 + type: Number,
  79 + default: 2000,
  80 + },
  81 + onSwitch: {
  82 + type: Function,
  83 + default: () => {},
  84 + },
  85 + onClose: {
  86 + type: Function,
  87 + default: () => {},
  88 + },
  89 + initialIndex: {
  90 + type: Number,
  91 + default: 0,
  92 + },
  93 + },
  94 +
  95 + data() {
  96 + return {
  97 + index: this.initialIndex,
  98 + isShow: false,
  99 + infinite: true,
  100 + loading: false,
  101 + mode: Mode.CONTAIN,
  102 + transform: {
  103 + scale: 1,
  104 + deg: 0,
  105 + offsetX: 0,
  106 + offsetY: 0,
  107 + enableTransition: false,
  108 + },
  109 + };
  110 + },
  111 + computed: {
  112 + isSingle() {
  113 + return this.urlList.length <= 1;
  114 + },
  115 + isFirst() {
  116 + return this.index === 0;
  117 + },
  118 + isLast() {
  119 + return this.index === this.urlList.length - 1;
  120 + },
  121 + currentImg() {
  122 + return this.urlList[this.index];
  123 + },
  124 + imgStyle() {
  125 + const { scale, deg, offsetX, offsetY, enableTransition } = this.transform;
  126 + const style = {
  127 + transform: `scale(${scale}) rotate(${deg}deg)`,
  128 + transition: enableTransition ? 'transform .3s' : '',
  129 + 'margin-left': `${offsetX}px`,
  130 + 'margin-top': `${offsetY}px`,
  131 + };
  132 + if (this.mode === Mode.CONTAIN) {
  133 + style.maxWidth = style.maxHeight = '100%';
  134 + }
  135 + return style;
  136 + },
  137 + },
  138 + watch: {
  139 + index: {
  140 + handler: function(val) {
  141 + this.reset();
  142 + this.onSwitch(val);
  143 + },
  144 + },
  145 + currentImg(val) {
  146 + this.$nextTick(_ => {
  147 + const $img = this.$refs.img[0];
  148 + if (!$img.complete) {
  149 + this.loading = true;
  150 + }
  151 + });
  152 + },
  153 + },
  154 + methods: {
  155 + hide() {
  156 + this.deviceSupportUninstall();
  157 + this.onClose();
  158 + },
  159 + deviceSupportInstall() {
  160 + this._keyDownHandler = rafThrottle(e => {
  161 + const keyCode = e.keyCode;
  162 + switch (keyCode) {
  163 + // ESC
  164 + case 27:
  165 + this.hide();
  166 + break;
  167 + // SPACE
  168 + case 32:
  169 + this.toggleMode();
  170 + break;
  171 + // LEFT_ARROW
  172 + case 37:
  173 + this.prev();
  174 + break;
  175 + // UP_ARROW
  176 + case 38:
  177 + this.handleActions('zoomIn');
  178 + break;
  179 + // RIGHT_ARROW
  180 + case 39:
  181 + this.next();
  182 + break;
  183 + // DOWN_ARROW
  184 + case 40:
  185 + this.handleActions('zoomOut');
  186 + break;
  187 + }
  188 + });
  189 + this._mouseWheelHandler = rafThrottle(e => {
  190 + const delta = e.wheelDelta ? e.wheelDelta : -e.detail;
  191 + if (delta > 0) {
  192 + this.handleActions('zoomIn', {
  193 + zoomRate: 0.015,
  194 + enableTransition: false,
  195 + });
  196 + } else {
  197 + this.handleActions('zoomOut', {
  198 + zoomRate: 0.015,
  199 + enableTransition: false,
  200 + });
  201 + }
  202 + });
  203 + on(document, 'keydown', this._keyDownHandler);
  204 + on(document, mousewheelEventName, this._mouseWheelHandler);
  205 + },
  206 + deviceSupportUninstall() {
  207 + off(document, 'keydown', this._keyDownHandler);
  208 + off(document, mousewheelEventName, this._mouseWheelHandler);
  209 + this._keyDownHandler = null;
  210 + this._mouseWheelHandler = null;
  211 + },
  212 + handleImgLoad(e) {
  213 + this.loading = false;
  214 + },
  215 + handleImgError(e) {
  216 + this.loading = false;
  217 + e.target.alt = '加载失败';
  218 + },
  219 + handleMouseDown(e) {
  220 + if (this.loading || e.button !== 0) return;
  221 +
  222 + const { offsetX, offsetY } = this.transform;
  223 + const startX = e.pageX;
  224 + const startY = e.pageY;
  225 + this._dragHandler = rafThrottle(ev => {
  226 + this.transform.offsetX = offsetX + ev.pageX - startX;
  227 + this.transform.offsetY = offsetY + ev.pageY - startY;
  228 + });
  229 + on(document, 'mousemove', this._dragHandler);
  230 + on(document, 'mouseup', ev => {
  231 + off(document, 'mousemove', this._dragHandler);
  232 + });
  233 +
  234 + e.preventDefault();
  235 + },
  236 + reset() {
  237 + this.transform = {
  238 + scale: 1,
  239 + deg: 0,
  240 + offsetX: 0,
  241 + offsetY: 0,
  242 + enableTransition: false,
  243 + };
  244 + },
  245 + toggleMode() {
  246 + if (this.loading) return;
  247 +
  248 + const modeNames = Object.keys(Mode);
  249 + const modeValues = Object.values(Mode);
  250 + const index = modeValues.indexOf(this.mode);
  251 + const nextIndex = (index + 1) % modeNames.length;
  252 + this.mode = Mode[modeNames[nextIndex]];
  253 + this.reset();
  254 + },
  255 + prev() {
  256 + if (this.isFirst && !this.infinite) return;
  257 + const len = this.urlList.length;
  258 + this.index = (this.index - 1 + len) % len;
  259 + },
  260 + next() {
  261 + if (this.isLast && !this.infinite) return;
  262 + const len = this.urlList.length;
  263 + this.index = (this.index + 1) % len;
  264 + },
  265 + handleActions(action, options = {}) {
  266 + if (this.loading) return;
  267 + const { zoomRate, rotateDeg, enableTransition } = {
  268 + zoomRate: 0.2,
  269 + rotateDeg: 90,
  270 + enableTransition: true,
  271 + ...options,
  272 + };
  273 + const { transform } = this;
  274 + switch (action) {
  275 + case 'zoomOut':
  276 + if (transform.scale > 0.2) {
  277 + transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
  278 + }
  279 + break;
  280 + case 'zoomIn':
  281 + transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
  282 + break;
  283 + case 'clocelise':
  284 + transform.deg += rotateDeg;
  285 + break;
  286 + case 'anticlocelise':
  287 + transform.deg -= rotateDeg;
  288 + break;
  289 + }
  290 + transform.enableTransition = enableTransition;
  291 + },
  292 + },
  293 + mounted() {
  294 + this.deviceSupportInstall();
  295 + // add tabindex then wrapper can be focusable via Javascript
  296 + // focus wrapper so arrow key can't cause inner scroll behavior underneath
  297 + this.$refs['el-image-viewer__wrapper'].focus();
  298 + },
  299 +};
  300 +</script>
  301 +
  302 +<style lang="scss">
  303 +.el-image-viewer__close {
  304 + color: #fff;
  305 + background-color: rgba(0, 0, 0, 0.1);
  306 + transition: background-color 300ms;
  307 + &:hover {
  308 + background-color: rgba(0, 0, 0, 0.8);
  309 + }
  310 +}
  311 +.el-image-viewer__prev,
  312 +.el-image-viewer__next,
  313 +.el-image-viewer__actions {
  314 + background-color: rgba(0, 0, 0, 0.5);
  315 + transition: background-color 300ms;
  316 + &:hover {
  317 + background-color: rgba(0, 0, 0, 0.8);
  318 + }
  319 +}
  320 +.el-image-viewer__actions {
  321 + cursor: auto !important;
  322 + &-btn {
  323 + cursor: pointer;
  324 + }
  325 +}
  326 +.el-image-viewer__img {
  327 + cursor: grab;
  328 + &:active {
  329 + cursor: grabbing;
  330 + }
  331 +}
  332 +</style>
... ...
packages/upload/index.vue 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +<template>
  2 + <div class="zee-upload">
  3 + <el-button type="text" @click="openViewer">查看图片</el-button>
  4 + <el-image-viewer :z-index="zIndex" :initial-index="imageIndex" v-if="showViewer" :on-close="closeViewer" :url-list="previewSrcList" />
  5 + </div>
  6 +</template>
  7 +
  8 +<script>
  9 +import ElImageViewer from './image-viewer';
  10 +
  11 +export default {
  12 + name: 'Upload',
  13 + components: {
  14 + ElImageViewer,
  15 + },
  16 + props: {
  17 + zIndex: {
  18 + type: Number,
  19 + default: 2000,
  20 + },
  21 + },
  22 + data() {
  23 + return {
  24 + showViewer: false,
  25 + src: 'https://zeyi-tms-test.oss-cn-hangzhou.aliyuncs.com/image/vehicle/e1612ad0-3c6f-465b-ac83-cec291d3013d.jpg',
  26 + previewSrcList: [
  27 + 'https://zeyi-tms-test.oss-cn-hangzhou.aliyuncs.com/image/vehicle/48655e39-31cf-42ca-849d-c4a5245d005b.jpg',
  28 + 'https://zeyi-tms-test.oss-cn-hangzhou.aliyuncs.com/image/vehicle/e1612ad0-3c6f-465b-ac83-cec291d3013d.jpg',
  29 + 'https://zeyi-tms-test.oss-cn-hangzhou.aliyuncs.com/image/vehicle/4511c610-4f73-4f04-8e26-62e87d3cb5b1.jpg',
  30 + ],
  31 + };
  32 + },
  33 + computed: {
  34 + imageIndex() {
  35 + let previewIndex = 0;
  36 + const srcIndex = this.previewSrcList.indexOf(this.src);
  37 + if (srcIndex >= 0) {
  38 + previewIndex = srcIndex;
  39 + }
  40 + return previewIndex;
  41 + },
  42 + },
  43 + methods: {
  44 + closeViewer() {
  45 + this.showViewer = false;
  46 + },
  47 + openViewer() {
  48 + this.showViewer = true;
  49 + },
  50 + },
  51 +};
  52 +</script>
  53 +
  54 +<style lang="scss">
  55 +.zee-upload {
  56 + display: inline-flex;
  57 +}
  58 +</style>
... ...