Skip to main content
Web端开发指南

1. 开发前准备#

项目运行环境

要运行智汀家庭云,你必须要在本地搭建好以下环境

  1. 安装nodejs的开发环境(node安装参考教程

  2. 下载一个集成开发工具,一般这里推荐vscodewebstorm直接去官网下载安装即可

技能准备

以下是主要涉及的技术,为了你能顺利的进行开发,你必须先熟悉他们

项目获取

当你的环境安装好了,还有所需的技能已经掌握,那么现在你可以从源码开源网站获取我们的项目。

  1. Git Hub

    名称URL描述
    zhiting-nas-vuehttps://github.com/zhiting-tech/zhiting-nas-vueWeb源码
  2. gitee

    名称URL描述
    zhiting-nas-vuehttps://gitee.net/zhiting-tech/zhiting-nas-vueWeb源码

安装依赖

项目获取成功后,直接用vscode打开项目,这里是中文版列举

如图:

然后用快捷键 “ctrl + ~” 打开命令窗口

然后我们可以在窗口输入以下命令安装项目所需的依赖

npm installyarn install

如图:

如果你不是用vscode打开

那么你可以用你操作系统的命令窗口进入到该项目根目录

执行上面命令一样可以安装项目依赖

如图:

启动项目

等你项目依赖安装完后,你可以执行以下命令启动项目

npm run serve

如图:

2. 快速开始#

2.1 开发工具#

2.2 源码地址#

  1. Git Hub

    名称URL描述
    zhiting-nas-vuehttps://github.com/zhiting-tech/zhiting-nas-vueWeb源码
  2. gitee

    名称URL描述
    zhiting-nas-vuehttps://gitee.net/zhiting-tech/zhiting-nas-vueWeb源码

2.3 构建版本#

  • 首先我们通过git远程拉取项目
  • 项目拉取成功后,进入到项目的根目录,安装项目依赖
  • 安装好了依赖,我们就可以启动项目了

现在项目已经可以跑起来,并且可以在浏览器上看到我们的项目,但还只是停留在登录页面

那现在怎么让我们的项目整个流程走通,我们需要启动我们的后端项目

依赖后端Golang项目

使用 Docker 运行智汀家庭云

Golang开发环境搭建

有了后端接口,我们如何能让我们的项目调用到我们自己的接口?

修改我们接口的请求地址

在我们项目的根目录下找到vue.config.js这个配置文件,打开我们的vue.config.js

我们只需要把tartget改成接口的地址,我们就可以调用接口的数据了

更多配置请查看传送门

新增账号

首先项目跑起来需要你进去授权登录

此时就需要之前介绍过的智汀云账户进行授权,本来入口是在: 智汀云 -> 我的 -> 智汀家庭网盘

因为是独立项目,那么我们应该怎么授权呢?

  1. 点击授权
  2. 页面会加载异常,因为授权没有实际的已登录智汀云账户网址
  3. 此时你需要把本地网址修改成你已登录智汀云账户,刷新即可
  4. 显示授权信息正确后进行确认授权登录

账号登录成功后,进入智汀家庭网盘首页文

3. 开发指南#

3.1 项目架构#

这里主要讲下项目的结构和技术架构,方便我们快速开始开发

3.1.1 项目技术架构#

  • 本项目主要采用vue + webpack + vant
  • 项目主要用vue-cli3脚手架进行初始化,然后根据项目需求进行结构上的调整

vuejs特性

  • vuejs体积小
  • 学习成本低
  • 能快速提交开发效率
  • vuejs的生态完善

webpack特性

  • 模块化
  • 按需加载
  • 丰富的插件
  • 丰富的配置

vant特性

  • 提供 60 多个高质量组件,覆盖移动端各类场景
  • 性能极佳,组件平均体积不到 1kb(min+gzip)
  • 单元测试覆盖率 90%+,提供稳定性保障
  • 完善的中英文文档和示例
  • 支持 Vue 2 & Vue 3
  • 支持按需引入
  • 支持主题定制
  • 支持国际化
  • 支持 TypeScript
  • 支持 SSR

3.1.2 项目结构#

│  .browserslistrc │  .editorconfig│  .eslintrc.js          // eslint配置文件│  .gitignore            // git提交忽略配置│  babel.config.js       // babel配置文件│  package-lock.json     // 依赖缓存文件│  package.json          // package配置文件│  postcss.config.js     // postcss独立配置文件│  README.md             // 说明文件│  vue.config.js         // webpack相关配置文件├──public                // 静态资源文件├──plugins               // 插件集合└─src    ├─api    │   ├──http.js       // api请求文件,每个api请求都在这里    │   ├──instance.js   // api请求总入口,在这里可以做请求的统一处理    ├─assets             // 资源文件夹    ├─lang               // 多语言文件夹    ├─bus                // 全局vue bus    ├─components         // 通用组件库    ├─router.js          // 路由文件    ├─store.js           // store相关    ├─App.vue           // 程序入口vue文件    ├─main.js           // 程序入口文件    ├─utils              // 相关工具的方法集合    └─views              // 页面文件

3.1.3 示例:hello word#

怎样添加一个hello word的页面?

  • 步骤1: 在views文件夹新建一个hello-world的文件夹,然后在hello-world文件夹下新建一个index.vue的文件,index.vue的文件内容为
// 模板模块<template><div>hello word</div></template>// 逻辑模块<script></script>// 样式模块<style scoped></styple>
  • 步骤2: 建完页面文件后,我们需要在router.js路由文件配置hello-world页面的路由
// 引入页面文件import HelloWorld from './views/hello-world/index.vue'
// 配置路由文件export default new Router({  mode: 'hash',  base: process.env.BASE_URL,  routes: [    {      path: '/hello-world',      name: 'device',      component: HelloWorld    }  ]})
  • 步骤3:我想在hello-world页面上添加一张图片怎么办?我们可以把我们的图片资源丢到assets目录下,然后用相对路径引入就可以了
    例:要引入名为hello.png的图片
<template>    <div>        <img src=".././img/hello.png" />        <p>hello word</p>    </div></template>
  • 步骤4:我要用公用组件库的组件怎么办?我们可以在components目录下引入要使用的组件
    例:
// 模板模块<template>    <div>        <HelloComponent/>        <img src=".././img/hello.png" />        <p>hello word</p>    </div></template>// 引入组件<script>import HelloComponent from '@/components/HelloComponent.vue'export default {    // 在页面注册组件    compoents: {        HelloComponent    }}</script>
  • 步骤5:如果我们需要接口数据,我们可以在api文件夹下的http.js增加我们的接口
/*** 请求列子*/export const example = params => http.g(  `${apiHeader}/examle`,  params)
  • 步骤6:在浏览器输入<localhost:8080/#/hello-world>就可以看到我们的hello页面了

3.2 功能结构#

  智汀家庭云盘,从功能结构上来说,包括文件、文件/储存池管理等。

如下图:

图.智汀云盘功能结构图

3.3 业务功能:文件#

文件有基本控制功能:文件共享、文件移动、文件复制、文件夹详情、文件详情、文件上传、文件下载、文件删除等,接口数据都是走http协议

3.3.1 文件新建、上传文件#

初始数据根目录文件夹,我们可以再此上传文件,进入文件详情可以新建自己喜欢的文件夹

  • 新建文件夹

文件新建是根据vant组件封装的样式组件,可以新建文件名也可以更新文件名,只需要传参类型this.creatData.target"creat"为新建,"update"为更新,其他参数则以接口需求为准,组件详情侧自行查看项目源码

<!--全局新建文件/文件重命名组件--><creatFile  v-model="showCreat"  :params="creatData"  @onSuccess="getFileList(filePath)"/>
  • 上传文件夹
  

选择上传菜单类型上传你对应需要的文件类型,执行菜单函数后调用上传组件,使用的是 "simple-uploader.js" 上传分片上传插件,需要安装"sha256"进行"hash" 项目安装:

 npm i simple-uploader.js
 npm i js-sha256
    upload(item){      if (item.val === 'video') {        this.$EventBus.$emit('openUploader', { type: 'video', path: this.path })      } else if (item.val === 'image') {        this.$EventBus.$emit('openUploader', { type: 'image', path: this.path })      } else if (item.val === 'folder') {        this.$EventBus.$emit('openUploader', { type: 'folder', path: this.path })      } else {        this.$EventBus.$emit('openUploader', { type: '', path: this.path })      }    }

文件上传的是否成功可在传输列表查看成功列表文件跟失败列表文件

组件相关的上传逻辑详情侧自行查看项目源码

3.3.2 文件夹共享、文件移动、文件复制、文件删除,文件下载#

进入根目录文件夹详情后,里面的文件夹可分享,但是文件不可以分享

底部栏的操作类别是开发成一个公用调用的组件栏

<!--底部栏的操作类别--><Operation      v-model="isShowOperation"      :list="checkedList"      :path="filePath"      :breadcrumb="breadcrumb"      @onSuccess="getFileList(filePath)"/>
isShowOperation 控制组件的显示, 参数值为Boolean类型
checkedList 选中的列表, 参数值为Array类型
filePath 当前的文件夹路劲 参数值为String
breadcrumb 当前文件的面包屑地址
getFileList 回调函数

组件相关的控制条件逻辑详情自行查看项目源码

  • 文件夹共享
  

可多个文件夹共享单人,多人,用户列表是来自智汀云SA的用户列表

<!--共享组件--><Operation  v-model="isShowOperation"  :list="checkedList"  @onSuccess="getShareList"/>
isShowOperation 控制组件的显示, 参数值为Boolean类型
checkedList 选中的列表, 参数值为Array类型
getShareList 回调函数
<!--共享列表组件源码--><van-popup  v-model="show"  position="bottom"  :overlay="false"  :lock-scroll="false">  <div class="op-wrap">    <div      v-for="item in opList"      :key="item.val"      class="op-item"      :class="{'disabled' : item.disabled}"      @click="handleOp(item)">      <img :src="item.icon"/>      <p>{{ $t(`global.${item.name}`) }}</p>    </div>  </div></van-popup>
<!--选择确定后的共享弹窗--><SharePopup v-model="shareShow" :fileList="list"/>

组件的详细跟操作代码逻辑

<template>  <van-popup    v-model="show"    class="share-wrap"    position="bottom"    @open="getUserList">    <div class="wrapper-detail">      <div class="main-box">        <van-nav-bar          :title="$t('global.shared')"          left-arrow          :fixed="true"          :placeholder="true"          @click-left="onClickLeft"          @click-right="onClickRight">          <template #right>            <span              v-if="!loading"              class="finish"              :class="{ 'gray': !result.length }">              {{ $t('global.confirm') }}            </span>            <van-loading              v-else              class="header-right"              type="spinner"              size="0.36rem"              color="#a2a7ae"/>          </template>        </van-nav-bar>        <div class="file-box">          <div class="title">{{ $t('global.shareTip1_1') }}{{ fileList.length }}{{ $t('global.shareTip1_2') }}</div>          <div class="file-list clearfix">            <div class="item-file" v-for="(item,index) in fileList" :key="index">              <div class="file-icon">                <van-image                  :class="{'shape': item.type === 'folder' || item.type === 'zip'}"                  :src="item.icon"                  fit="contain"/>              </div>              <div class="file-info">                <div class="name one-line">{{ item.name }}</div>              </div>            </div>          </div>        </div>        <div class="target-box">          <div class="target-header">            <van-cell>              <template #title>                <span class="custom-title">{{ $t('global.sharedWith') }}</span>              </template>              <template #right-icon>                <van-checkbox                  v-model="check"                  label-position="left"                  shape="square"                  icon-size=".32rem"                  @click="checkAll(check)">{{ $t('global.selectAll') }}</van-checkbox>              </template>            </van-cell>          </div>          <div class="target-body">            <van-checkbox-group v-model="result" ref="checkboxGroup">              <van-cell-group>                <van-cell                  v-for="(item, index) in targetList" clickable :key="index"  @click="toggle(index)">                  <template #title>                    <van-image                      width=".8rem"                      height=".8rem"                      round                      fit="cover"                      :src="defaultHead"/>                    <span class="face-name">{{ item.nickname }}</span>                  </template>                  <template #right-icon>                    <van-checkbox                      shape="square"                      icon-size=".32rem"                      :name="item.user_id"                      ref="checkboxes"/>                  </template>                </van-cell>              </van-cell-group>            </van-checkbox-group>          </div>        </div>      </div>    </div>    <!--设置分享权限弹窗-->    <MemberDialog      v-model="memberShow"      :list="checkMemberList"      :powers="powerList"      @onChange="saveShare"/>  </van-popup></template>
import { mapGetters } from 'vuex'import MemberDialog from './MemberDialog.vue'
const empty = require('@/assets/empty-files.png')const defaultHead = require('@/assets/default-head.png')
export default {  name: 'shareDetail',  components: {    MemberDialog  },  props: {    value: {      type: Boolean,      default: false    },    fileList: {      type: Array,      default: () => []    }  },  data() {    return {      show: this.value,      defaultHead,      empty,      loading: false,      list: [],      check: false, // 全选控制      result: [],      targetList: [],      memberShow: false, // 用户权限设置弹窗      powerList: ['read', 'write', 'deleted'], // 操作权限列表    }  },  computed: {    ...mapGetters(['userInfo']),    isNoFiles() {      return this.list.length === 0    },    // 选择的用户列表    checkMemberList() {      return this.targetList.filter(user => this.result.includes(user.user_id))    }  },  watch: {    fileList(list) {      // 筛选文件夹权限并集      this.powerList = []      const power = {        read: true,        write: true,        deleted: true      }      list.forEach((file) => {        if (file.read === 0) {          power.read = false        } else if (file.write === 0) {          power.write = false        } else if (file.deleted === 0) {          power.deleted = false        }      })      Object.keys(power).forEach((key) => {        if (power[key]) {          this.powerList.push(key)        }      })    },    value(val) {      this.show = val    },    show(val) {      this.$emit('input', val)    },    result(list) {      this.check = list.length === this.targetList.length    }  },  methods: {    onClickLeft() {      this.show = false    },    onClickRight() {      // 至少选择一个共享的用户      if (!this.result.length) {        return      }      this.memberShow = true    },    // 提交分享数据    saveShare(power) {      const paths = []      this.fileList.forEach((item) => {        paths.push(item.path)      })      // to_users 分享的用户id paths 共享的用户目录      let params = {        to_users: this.result,        paths,        from_user: this.userInfo.nickname      }      params = Object.assign(params, power)      this.loading = true      this.http.shareFiles(params).then((res) => {        this.loading = false        if (res.status !== 0) {          return        }        this.show = false        this.result = []        // 去掉勾选        this.fileList.forEach((item) => {          item.checked = false        })        this.$toast(this.$t('global.sharedSuccessfully'))      }).catch(() => {        this.loading = false      })    },    toggle(index) {      this.$refs.checkboxes[index].toggle()    },    checkAll(checked) {      if (checked) {        this.$refs.checkboxGroup.toggleAll(true)      } else {        this.$refs.checkboxGroup.toggleAll(false)      }    },    // 初始用户数据    getUserList() {      this.http.getUserList().then((res) => {        if (res.status !== 0) {          return        }        this.targetList = res.data.users.filter(item => item.user_id !== this.userInfo.user_id - 0)      })    },  }}
  • 文件移动
  

文件移动权限判断,是否具有私密密码去权限,空文件显示处理,逻辑如下:

<template>  <div class="main-box">    <van-nav-bar      title=""      :left-text="homeName"      :fixed="true"      :placeholder="true"      :border="false"      @click-right="onClickRight">      <template #right>        <span>{{ $t('global.cancel') }}</span>      </template>    </van-nav-bar>    <Breadcrumb :list="breadcrumb" @onChange="changeBread"/>    <!-- 空文件夹 -->    <div class="empty-box" v-if="false">      <van-empty        class="custom-image"        :image="empty"        :description="$t('global.noChild')">      </van-empty>    </div>    <div class="file-box" v-else>      <div class="file-list">        <div          v-for="(item,index) in list"          :key="index"          @click="getLevelFile(item)"          class="item-file"          :class="{ 'disabled': item.disabled }">          <div class="file-icon">            <van-image :src="item.icon"/>            <div v-if="isRoot && item.is_encrypt === 1" class="lock-icon"></div>          </div>          <div class="file-info">            <div class="title one-line">{{item.name}}</div>          </div>        </div>      </div>    </div>    <div class="bottom-box" v-if="isShowBtn">      <van-button plain type="info" @click="creatFile()">{{ $t('global.newFolder') }}</van-button>      <van-button        type="info"        @click="handleAction"        :loading="saveLoading"        :disabled="saveLoading"        loading-type="spinner"        :loading-text="loadingText">        {{ homeName }}{{ $t('global.here') }}      </van-button>    </div>  </div></template>
// 确认操作handleAction() {  let sources = []  if (this.action === 'move') {    sources = this.$methods.getSession('moveSource')    sources = sources ? JSON.parse(sources) : []  } else {    sources = this.$methods.getSession('copySource')    sources = sources ? JSON.parse(sources) : []  }  const params = {    action: this.action,    destination: this.destination,    destination_pwd: this.currentFolderPass,    sources  }  this.saveLoading = true  this.http.moveFile(params).then((res) => {    this.saveLoading = false    if (res.status !== 0) {      return    }    if (this.action === 'move') {      this.$methods.setSession('moveSource', '')      this.$methods.setSession('breadcrumb', '')      this.$toast(this.$t('global.movedSuccessfully'))    } else {      this.$methods.setSession('copySource', '')      this.$toast(this.$t('global.copySuccessfully'))    }    this.$router.go(-1)  }).catch(() => {    this.saveLoading = false  })}
  • 文件复制
  

复制跟移动是公用同一文件,根据路由参数判断是复制文件还是移动文件进行做处理,在vue生命周期creat阶段进行获取判断

created() {    const { query } = this.$route    const { path } = query    if (query.type === 'copy') {      this.homeName = this.$t('global.copyTo')      this.loadingText = `${this.$t('global.copying')}...`    } else if (query.type === 'move') {      this.homeName = this.$t('global.moveTo')      this.loadingText = `${this.$t('global.moving')}...`    }    // 设置文件夹根目录    this.setPassword(path)    this.action = query.type    this.shareId = query.shareId    this.range = `${query.range}` === 'true'    this.filePath = path    // 如果有path则定位到path的目录下    if (path && this.range) {      this.getPathFile(path)    } else {      // 没有则默认进入到根目录      this.getinitList()    }}
  • 文件夹、文件删除

删除组件vant

<!--删除弹窗--><van-dialog  v-model="deleteShow"  cancelButtonColor="#A2A7AE"  confirmButtonColor="#427AED"  show-cancel-button  :beforeClose="sureDelete">  <div class="dialog-wrap">    <h3>{{ $t('global.delTip1_1') }}{{ list.length }}{{$t('global.delTip1_2')}}</h3>    <p>{{ $t('global.delTip2') }}</p>  </div></van-dialog>

异步删除反馈

sureDelete(action, done) {  if (action === 'confirm') {    const deleteArr = []    this.list.forEach((typeItem) => {      deleteArr.push(typeItem.path)    })    this.http.deleteFile({ paths: deleteArr }).then((res) => {      done()      if (res.status !== 0) {        return      }      this.$toast(this.$t('global.deletedSuccessfully'))      this.$emit('onSuccess')    })  } else {    done()  }}

组件相关的控制条件逻辑详情自行查看项目源码

  • 文件夹、文件下载

WEB网页下载默认以浏览器下载为准

3.3.3 文件夹详情、文件详情#

  • 文件夹详情

刚上面已有提到。。。

  • 文件详情
<!--详情文件管理组件--><ManageFile  v-model="showManage"  :params="manageData"  :path="filePath"  :breadcrumb="breadcrumb"  @onSuccess="getFileList(filePath)"/>
showManage 控制组件的显示, 参数值为Boolean类型
manageData 选中的文件对象参数, 参数值为Object类型
filePath 当前的文件夹路劲 参数值为String
breadcrumb 当前文件的面包屑地址
getFileList 回调函数

3.4 业务功能:共享文件#

3.4.1 共享文件#

  

共享文件跟文件基本大同小异的管理功能模式,我的文件和共享文件的家庭/公司是同步的,即我的文件展示是家庭A,那么共享文件也是展示家庭B

3.5 业务功能:我的#

主要有两大模块,一是储存管理,二是我的文件管理

3.5.1 存储管理#

管理主页这里主要就是两个列表,一个硬盘列表,一个存储池列表

<!--闲置硬盘列表 html--><div v-if="diskList.length" class="new-storage-box">    <p class="tip">{{ $t('storage.findTitle1') }}{{ diskList.length }}{{ $t('storage.findTitle2') }}</p>    <div class="new-scroll">      <div v-for="(item, index) in diskList" :key="index" class="new-storage-item"        :class="[`new-storage-item-${index % 4 + 1}`]">        <div class="disk-icon"></div>        <h3 class="name one-line">{{ item.name }}</h3>        <p class="many">{{ $methods.transformByte(item.capacity) }}</p>        <van-button          class="btn"          :class="[`btn-${index % 4 + 1}`]"          @click="handleAddDisk(item)">{{ $t('storage.addBtn') }}</van-button>      </div>    </div></div>
<!--存储池列表 html--><div class="storage-box"><p class="tip">{{ $t('storage.listTitle') }}</p><!-- 没有储存池 --><div class="empty-box" v-if="isNoStorages">  <van-empty    class="custom-image"    :description="$t('global.noStorage')">  </van-empty></div><!-- 有储存池 --><div class="storage-list" v-else>  <div    v-for="(item, index) in storageList" :key="index"    class="storage-item"    :class="{'blur': item.status==='TaskDelPool_0'}"    @click="!item.status?toStorageDetail(item):''">    <div class="clearfix">      <div class="disk-icon float-l"></div>      <div v-if="!item.status" class="more float-r"></div>      <div class="status-type" v-if="item.status==='TaskDelPool_1'"><van-icon name="clock-o" /> <span class="one-line">{{$t('global.deleting')}}</span></div>    </div>    <h3 class="name two-line">{{ item.name }}</h3>    <p class="many">{{ $methods.transformByte(item.capacity) }}</p>    <div class="mask-cover" v-if="item.status==='TaskDelPool_0'">      <div class="warning-icon"><img src="../../../assets/icon-warning.png" alt=""></div>      <div class="warning-text two-line">{{$t('storage.delStorage')}} ({{item.name}}) {{$t('global.fail')}}</div>      <div class="btn-box">        <van-button type="info" color="#427AED" size="small" @click="retry(item)">{{ $t('global.retry') }}</van-button>      </div>    </div>  </div></div></div>
// 初始化数据列表methods: {    // 获取闲置硬盘列表    getDiskList(params) {      this.http.disksList(params).then((res) => {        if (res.status !== 0) {          return        }        const list = res.data.list || []        this.diskList = list      })    },    // 获取存储池列表    getStorageList(params) {      this.loading = true      this.http.poolsList(params).then((res) => {        this.loading = false        if (res.status !== 0) {          return        }        const list = res.data.list || []        list.forEach((item) => {          item.checked = false        })        this.storageList = list      }).catch(() => {        this.loading = false      })    },  },created() {    this.getDiskList()    this.getStorageList()  }
  • 闲置硬盘添加
    

可以选择现有的存储池添加,或者新建一个储存,新建成功则默认已添加到新建的存储池中

// 确定添加储存池beforeAdd(action, done) {  if (action === 'confirm') {    this.submitLoading = true    this.http.addDisk({ pool_name: this.poolName, disk_name: this.diskName }).then((res) => {      this.submitLoading = false      if (res.status !== 0) {        if (res.status === 205) {          this.sureShow = true        }        return      }      this.$toast.success(this.$t('global.addSuccessfully'))      this.show = false      this.onClickLeft()    })  } else {    done()  }}
// 添加到新的存储池addStorage() {  this.addStorageShow = true},
<!--新的存储池组件 html--><StorageManage v-model="addStorageShow" :list="storageList"/>
  • 存储池详情

在权限允许的情况下,可进行编辑名称、删除、添加,修改分区,分区列表具有删除中、修改中、添加中、删除失败,修改失败,添加失败等状态,根据数据反馈做展示体现

  • 分区新增/分区编辑

分区新增/分区详情编辑,如果是MB单位,那只能输入是4的倍数单位,GB和TB则以整数int类型单位,单位参数

<!-- 正则匹配输入--><input  v-model="params.new_name"  type="text"  class="field-input"  maxlength="50"  oninput="value=value.replace(/[^A-Za-z0-9_.+-]/g,'')"  :placeholder="$t('partition.enter')"/>
// 获取大小跟单位transformByte(fileBytes) {  let size = ''  if (fileBytes < 1 * 1024) {    // 如果小于1KB转化成B    size = `${(fileBytes / (1024 * 1024)).toFixed(2)}MB`  } else if (fileBytes < 1 * 1024 * 1024) {    // 如果小于1MB转化成KB    size = `${(fileBytes / (1024 * 1024)).toFixed(2)}MB`  } else if (fileBytes < 1 * 1024 * 1024 * 1024) {    // 如果小于1GB转化成MB    size = `${(fileBytes / (1024 * 1024)).toFixed(2)}MB`  } else if (fileBytes < 1 * 1024 * 1024 * 1024 * 1024) {    // 其他转化成GB    const number = (fileBytes / (1024 * 1024 * 1024)).toFixed(2)    if (parseInt(number, 10) === parseFloat(number)) {      size = `${(fileBytes / (1024 * 1024 * 1024)).toFixed(2)}GB`    } else {      size = `${(fileBytes / (1024 * 1024)).toFixed(2)}MB`    }  } else {    // 其他转化成TB    const number = (fileBytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)    if (parseInt(number, 10) === parseFloat(number)) {      size = `${(fileBytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)}TB`    } else {      size = `${(fileBytes / (1024 * 1024 * 1024)).toFixed(2)}GB`    }  }  const sizeStr = `${size}`  const len = sizeStr.length  const unit = sizeStr.substr(-2)  const sizeNumber = Number(sizeStr.substr(0, len - 2))  if (unit === 'MB') {    this.capacity = sizeNumber / 4  } else {    this.capacity = sizeNumber  }  this.params.unit = unit}

3.5.2 我的文件#

  • 文件夹

这是一个下拉加载更多来实现翻页的文件列表,下拉加载是调用 vant 组件 van-list

<div class="folder-list">    <van-list      v-model="moreLoading"      :finished="finished"      :offset="10"      :immediate-check="false"      :finished-text="$t('global.noMore')"      @load="moreLoad">        <div          v-for="(item,index) in folderList"          :key="index"          class="folder-item"          :class="{'blur': item.status==='TaskMovingFolder_0' || item.status==='TaskDelFolder_0'}"          @click="!item.status?handleEdit(item):''">          <!-- 文件内容......-->        </div>    </van-list></div>
// 加载更多处理moreLoad() {  if (!this.pagerData.has_more) {    this.moreLoading = false    this.finished = true    return  }  this.page += 1  const params = {    page: this.page,    page_size: this.page_size  }  this.moreLoading = true  this.http.getFolderList(params).then((res) => {    this.moreLoading = false    if (res.status !== 0) {      return    }    const { list } = res.data || []    list.forEach((item) => {      item.showPopover = false    })    this.pagerData = res.data.pager    this.folderList = this.folderList.concat(list)    // 是否加载完    if (!this.pagerData.has_more) {      this.finished = true    }  }).catch(() => {    this.finished = true    this.moreLoading = false  })}
  • 文件新增/文件编辑
  

根据url是否携带folder_id判断是否存在,进而判断是新增文件夹还是文件夹编辑;新建,编辑文件具有私人文件,共享文件,私人文件可以设置密码文件跟无密码文件

// 是否存在文件folder_idcreated() {    const { query } = this.$route    if (query.folder_id) {      this.folderId = Number(query.folder_id)      this.getFolderDetail(this.folderId)    }},
// 保存编辑相关逻辑save() {    // 判断是否需要密码加密1需要,0不需要  if (!this.folderInfo.is_encrypt) {    this.folderInfo.is_encrypt = 0  }    // 判断是否已输入文件名称  if (!this.folderInfo.name) {    this.$toast(this.$t('global.enterFile'))    return  }    // 判断是否已选择了分区  if (this.currentPartition === this.$t('folder.select')) {    this.$toast(this.$t('partition.chosePartition'))    return  }    // 判断1私人文件夹 2共享文件夹  if (!this.folderInfo.mode) {    this.$toast(this.$t('folder.selectType'))    return  }    // 判断密码长度  if (this.folderInfo.is_encrypt === 1 && !this.isHasId) {    if (this.folderInfo.pwd.length < 6) {      this.$toast(this.$t('folder.inputNoPassword'))      return    }    // 判断确定密码长度并跟设置密码是否一致    if (this.folderInfo.confirm_pwd.length < 6) {      this.$toast(this.$t('folder.inputNoPassword'))      return    } if (this.folderInfo.confirm_pwd !== this.folderInfo.pwd) {      this.$toast(this.$t('folder.noSamePassword'))      return    }  } else {    delete this.folderInfo.pwd    delete this.folderInfo.confirm_pwd  }    // 判断是否添加成员  if (this.folderInfo.auth.length === 0) {    this.$toast(this.$t('folder.leastMember'))    return  }    // 判断是否是私密文件并只能添加一名成员  if (this.folderInfo.mode === 1) {    if (this.folderInfo.auth.length > 1) {      this.$toast(this.$t('folder.privateLimit'))      return    }  }    // 判断是编辑文件还是新建文件 folderId 存在是编辑反之新建  if (this.folderId) {    // 走编辑文件接口    if (this.partitionParams.history !== this.currentPartition) {      this.transferShow = true      this.partitionParams.name = this.folderInfo.name      this.partitionParams.current = this.currentPartition    } else {      this.saveLoading = true      this.http.folderEdit(this.folderId, this.folderInfo).then((res) => {        this.saveLoading = false        if (res.status !== 0) {          this.$toast(res.reason)          return        }        this.$toast.success(this.$t('global.savedSuccessfully'))        this.$router.go(-1)      }).catch(() => {        this.saveLoading = false      })    }  } else {    // 走新建文件接口    this.saveLoading = true    this.http.folderAdd(this.folderInfo).then((res) => {      this.saveLoading = false      if (res.status !== 0) {        return      }      this.$toast.success(this.$t('global.addSuccessfully'))      this.$router.go(-1)    }).catch(() => {      this.saveLoading = false    })  }}

3.6 部署应用#

部署应用是指编译或构建代码并将生成的 JavaScript、CSS 和 HTML 托管到 Web 服务器上的过程。

构建和托管你的应用

我们的项目开发完后,怎么发布,首先要构建我们的项目

npm run build

在根目录执行该命令后,项目的构建文件会输出到dist文件夹

我们下一步就是把我们的dist文件夹托管在服务器上,配置好请求转发和域名

至此,我们的应用已经部署成功了