开发 Vue Uploads 移动端上传组件

通过使用 Uploads 上传组件,实现文件上传,也可以使用上传组件的打包下载功能打包下载上传的文件。

设计思路

使用平台基于 vant Uploader 组件进行二次封装的 Uploader 组件,来实现 Uploads 上传组件核心逻辑,基于自定义默认插槽来增加“打包下载”按钮。设置 dom 中的 input[type='file'] 节点不显示,来取消上传区域“打开文件选择对话框”的行为,设置“打包下载”按钮的下载逻辑。通过调用 vant Uploader 组件提供的 api 设置“上传”按钮的打开对话框逻辑。使用 css 给“上传”和“打包下载”按钮设置间距样式。

vant 组件

使用方法如下


if (showBatchDownload) {
  if (uploadProps.listType == "" || !uploadProps.listType) {
    if(!props.disabled){
      vSlots.default = () => <div class={"van-uploader__upload_container"} >
        <div class={classnames("van-uploader__upload" ,"hydrated","van-upload-flag", {"van-uploader__upload--readonly":props.disabled})} onClick={handleClick}>
          <Icon name={"photograph"} class={"van-uploader__upload-icon"}></Icon>
        </div>
        <div class={classnames("van-uploader__upload" , {"van-uploader__upload--readonly":props.disabled})}>
          <Icon name={"down"} class={"van-uploader__upload-icon"} onClick={handleDownload}></Icon>
        </div>
      </div>
    }
  } else {
    vSlots.default = () => <div  class={"van-space"}>
      <div class={"van-space-item"}>
        <Button plain hairline type="primary" class={"van-upload-flag"} icon={"photo"} text="上传" onClick={handleClick}
        ></Button >
      </div>
      <div class={"van-space-item"}>
        <Button plain hairline type="success" icon={"down"} onClick={handleDownload} text="打包下载"
        ></Button>
      </div>
    </div>;
  }
}
return <Uploader ref={myRef} {...attrs} {...uploadProps} {...others} vSlots={vSlots}></Uploader>;

开发过程

新建组件

创建 vuemobilecomp 组件包,创建 Uploads 组件,注意大小写。创建组件功能参考《创建组件包和组件》。

配置组件名称、图标和模板

打开组件模板文件 UI2/comp/vuemobilecomp/components/Uploads/designer/Uploads.xml,如下所示

<element name="vuemobilecomp:Uploads"
             tag-name="Uploads"
             text="上传"
             icon="Uploads.png"
             discriminate-condition="executeXpath(name()='vuemobilecomp:Uploads')"
             component-type="layout-container"
             design-view="web-designer">
    <events>
        <include path="$UI/wxsys/comps/vueContainer/commonConfig.xml#//vue-events-mx/*"/>
    </events>
    <templates>
        <template name="default">
            <![CDATA[
                <vuemobilecomp:Uploads xmlns:vuemobilecomp="$UI/comp/vuemobilecomp/components" >
                </vuemobilecomp:Uploads> 
            ]]></template>
    </templates>
</element>
  • element 中的 text 是组件名称
  • element 中的 icon 是组件图标
    • 图片文件位于 UI2/comp/vuemobilecomp/components/Uploads/designer/img/Uploads.png
  • include 引用标准 W3C 事件,非组件事件。
  • template 中的代码是组件模板
    • 在页面添加组件后,模板是加在 w 文件中的代码

在组件面板中显示

打开组件面板配置文件 UI2/comp/vuemobilecomp/components/vuemobilecomp.components.xml,如下所示

<reg-info>
    <!-- 组件目录注册 -->
    <component-dirs framework="vue">Uploads</component-dirs>

    <!-- 组件工具箱配置 -->
    <toolbox>
    </toolbox>

    <!-- 兼容旧端工具箱配置 -->
    <quickide-toolbox>
    </quickide-toolbox>

    <!-- 傻瓜式ide工具栏配置 -->
    <uixide-toolbox framework="vue">
        <catalog name="定制" order="13">
            <item component-name="vuemobilecomp:Uploads" device="mx"/>
        </catalog>
    </uixide-toolbox>

    <depend-css>
    </depend-css>
    <depend-js>
    </depend-js>
</reg-info>
  • catalog 中的 name 是组件分类名称
  • item 中的 device 属性声明组件所在端
  • item 中的 module 属性一般组件不需要,删除即可

切换到 vuemobilecomp 开发端后,点击 vuemobilecomp 组件包右侧的“更新配置文件”按钮,如下图所示。

更新后,切换到桌面端或移动端,在组件面板的高级分类中会显示出该组件

在设计器中显示

在设计器 js 文件 (UI2/comp/vuemobilecomp/components/Uploads/designer/Uploads.js) ,引入样式文件,和设计时 js 文件。

require("css!./css/Uploads").load();
vuemobilecompBaseComponent.Uploads = require("babel!./Uploads.react");

打开组件设计时 css 文件 (UI2/comp/vuemobilecomp/components/Uploads/designer/css/Uploads.css) ,设置“上传”和“打包下载”按钮的间距样式,设置 input[type='file'] 隐藏,定义样式如下。

.van-uploads .van-space {
    display: inline-flex;
}

.van-uploads .van-space .van-space-item {
    margin-right: .4rem;
    margin-bottom: 0.2rem;
}

.van-uploads .van-space .van-space-item:last-child {
    margin-right: 0;
}

.van-uploads .van-uploader__input {
    display: none;
}

设计时 js 文件,引入依赖的组件,完成组件设计时渲染。


<script lang="jsx">
let {commonProps, useRefData, useUse, useFireEvent, useData, omitProps} = window.vueContainer;
</script>
<script setup lang="jsx">
let {Icon, Button} = vant;

let {useAttrs, useSlots, reactive, watch} = Vue;

let props = defineProps({
  showBatchDownload: {
    type: Boolean, default: true
  },
  class: {
    type: String, default: ""
  },
  ...commonProps
})
let emits = defineEmits([]);
let fireEvent = useFireEvent(emits);
let attrs = useAttrs();
let slots = useSlots();

let use = useUse(props);
let refData = useRefData(props);

let handleClick = event => {
  let node = event.target;
  let getParentNode = (nodeName, node) => {
    while (node && !(node.nodeName.toLowerCase() == nodeName && node.classList.contains("van-upload-flag"))) {
      if (node.classList.contains("van-uploads")) {
        node = null;
        break;
      }
      node = node.parentNode;
    }
    return node;
  }
  node = getParentNode("div", node);
  if (!node) {
    event.stopPropagation()
  }
}

let UploadsProRender = () => {
  let {Uploader} = vantBaseComponent;
  let {...vSlots} = slots;
  let {showBatchDownload = true, class: className = "", ...others} = props;
  others.class = className + " van-uploads ";
  let omittedProps = omitProps(others, commonProps);
  let uploadProps = use.getProps("Uploader", "uploader", {});
  if (showBatchDownload) {
    if (uploadProps.listType == "" || !uploadProps.listType) {
      //为了和taro 保持一致,此处必须要用view 才能阻止冒泡。
      vSlots.default = () => <div class={"van-uploader__upload_container"} onClick={handleClick}>
        <div class="van-uploader__upload hydrated van-upload-flag">
          <Icon name={"photograph"} class={"van-uploader__upload-icon"}></Icon>
        </div>

        <div class="van-uploader__upload">
          <Icon name={"down"} class={"van-uploader__upload-icon"}></Icon>
        </div>
      </div>
    } else {
      vSlots.default = () => <div onClick={handleClick} class={"van-space"}>
        <div class={"van-space-item"}>
          <Button plain hairline type="primary" class={"van-upload-flag"} icon={"photo"}
          >上传</Button>
        </div>
        <div class={"van-space-item"}>
          <Button plain hairline type="success" icon={"down"}
          >打包下载</Button>
        </div>
      </div>;
    }
  }
  return <Uploader {...attrs} {...uploadProps} {...others} vSlots={vSlots}></Uploader>;
}
</script>

<template>
  <UploadsProRender>
    <slot/>
  </UploadsProRender>
</template>

配置组件属性

打开组件 meta 文件 UI2/comp/vuemobilecomp/components/Uploads/Uploads.meta.json,定义属性,如下所示

{
  "Uploads": {
    "properties": {
      "bind:ref": {
          "type": "dataRef",
          "label": "绑定数据列",
          "editor": "dataRef",
          "required": "true"
      },
      "showBatchDownload": {
          "label": "显示打包下载",
          "type": "boolean",
          "default-value": "true"
      }
    }
  }
}
  • 在 properties 中定义属性
  • 所有属性都需要定义
  • 不显示的属性不定义 label
  • bind:ref 绑定数据集某字段

配置引用

通过使用 ext:use 特性,引用平台封装的 Uploader 组件提供的属性。来减少组件元信息文件中的冗余配置,以达到简便开发目的。

{
    "Uploads": {
        "uses": {
            "uploader": {
                "key": "uploader",
                "componentName": "vant:Uploader",
                "label": "上传配置",
                "editor-parameter": {
                    "ignore-properties": [
                        "bind:ref"
                    ]
                }
            }
        }
    }
}

在运行时显示

在组件运行时 js 文件 (UI2/comp/vuemobilecomp/components/Uploads/Uploads.vue) 中,导入平台封装的 Uploader 组件和 vant 的 Notify|Icon 组件,以及 vue_addon/core 和样式文件。代码如下

<script setup lang="jsx">
import {commonProps, useRefData, useUse, useFireEvent, useData, omitProps} from "vue_addon/core";
import {useAttrs, useSlots, reactive, watch,ref} from "vue";
import {showNotify, Icon} from "vant";
import Button from "../../../vant/components/Button/Button.vue";
import Uploader from "../../../vant/components/Uploader/Uploader.vue";
import classnames from "classnames";
</script>
<style src="./css/Uploads.css"></style>
  • vue_addon/core 文件中,封装了可获取操作数据的各种 Api。

    详见《组件运行时 JS 文件》。

  • 运行时样式文件,设置“上传”和“打包下载”按钮间距样式,设置 .van-uploader__input 的 dom 节点不显示来实现,来阻止上传区域打开文件选择对话框。

.van-uploads .van-space {
    display: inline-flex;
}

.van-uploads .van-space .van-space-item{
    margin-right: .4rem;
    margin-bottom: 0.2rem;
}

.van-uploads .van-space .van-space-item:last-child{
    margin-right: 0;
}

.van-uploads .van-uploader__input {
    display: none;
}

.van-uploads .van-uploader__upload_container{
    display: flex;
}
  • 定义 render 方法,使用 use.getProps 来获取 ext:use 中设置的属性。代码如下:

let UploadsProRender = () => {
  let {...vSlots} = slots;
  let {showBatchDownload = true, class: className = "", ...others} = props;
  others.class = className + " van-uploads ";
  let omittedProps = omitProps(others, commonProps);
  let uploadProps = use.getProps("Uploader", "uploader", {});
  if (showBatchDownload) {
    if (uploadProps.listType == "" || !uploadProps.listType) {
      if(!props.disabled){
        vSlots.default = () => <div class={"van-uploader__upload_container"} >
          <div class={classnames("van-uploader__upload" ,"hydrated","van-upload-flag", {"van-uploader__upload--readonly":props.disabled})} onClick={handleClick}>
            <Icon name={"photograph"} class={"van-uploader__upload-icon"}></Icon>
          </div>
          <div class={classnames("van-uploader__upload" , {"van-uploader__upload--readonly":props.disabled})}>
            <Icon name={"down"} class={"van-uploader__upload-icon"} onClick={handleDownload}></Icon>
          </div>
        </div>
      }
    } else {
      vSlots.default = () => <div class={"van-space"}>
        <div class={"van-space-item"}>
          <Button plain hairline type="primary" class={"van-upload-flag"} icon={"photo"} text="上传" onClick={handleClick}
          ></Button >
        </div>
        <div class={"van-space-item"}>
          <Button plain hairline type="success" icon={"down"} onClick={handleDownload} text="打包下载"
          ></Button>
        </div>
      </div>;
    }
  }
  return <Uploader ref={myRef} {...attrs} {...uploadProps} {...others} vSlots={vSlots}></Uploader>;
}
  • 给“打包下载”按钮增加“打包下载”框逻辑。
let handleDownload = event => {
  if(props.disabled){
    return;
  }
  let files = refData.getRefValue();
  if (!files) {
    showNotify("请先上传附件");
    return;
  }
  let zipFileName = "多文件打包下载.zip";
  //调用storageapi前端自带接口实现打包下载
  fetch("/storage/batchStream?zipFileName=" + zipFileName, {
    headers: {'Content-Type': 'application/json'},
    method: 'POST', // 指定请求方法为POST
    body: files, // 将FormData对象作为请求体发送
    responseType: "blob"
  }).then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.blob(); // 将响应转换为blob格式
  }).then(blob => {
    // 创建一个链接元素用于下载
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = zipFileName; // 指定下载文件的名称
    document.body.appendChild(a);
    a.click(); // 触发下载
    window.URL.revokeObjectURL(url); // 释放URL对象
    document.body.removeChild(a); // 移除链接元素
  }).catch(error => {
    console.error('There was a problem with the fetch operation:', error);
  });
}
  • 给“上传”按钮增加“打开文件选择对话”框逻辑。
let handleClick = event => {
  myRef?.value.chooseFile();
}

调试组件

  • 切换到桌面端或移动端
  • 在页面上添加上传组件、选择数据、设置上传
  • 保存、预览

发布组件

导出组件包

切换到 vuemobilecomp 开发端,点击 vuemobilecomp 组件包右侧的导出按钮,如下图所示,导出 vuemobilecomp.zip 文件

上传市场并审核

进入控制台,打开“组件管理-组件发布”功能,点击“发布组件”按钮,输入组件包信息,上传组件包文件,点击“提交审核”按钮

控制台管理员 system 进入控制台,打开“组件管理-组件管理”功能,在“状态”选择器中选择“审核中”或“更新审核中”,显示出提交待审核的组件,点击“审核”按钮,再点击“通过”按钮

打开应用,添加市场组件,可以看到上传组件

至此,上传组件开发完毕。

results matching ""

    No results matching ""