基于Vue+Element UI的文件管理系统-Demo

记录一下之前写过的一个文件管理系统demo。
功能包括文件夹的新增、删除、重命名及移动,文件的上传、删除、移动及下载功能。
相关功能的操作直接和 后端 进行请求交互。
因为该demo集成在大的系统中,懒得提取建库开源,所以算是只记录思路。

运行截图

  • 右键文件夹时显示操作目录
    右键文件夹时显示操作目录
  • 右键文件时显示操作目录右键文件时显示操作目录
  • 新建文件夹在这里插入图片描述
  • 上传文件在这里插入图片描述
  • 重命名文件夹在这里插入图片描述
  • 移动在这里插入图片描述

实现代码

shareSpace.vue 为页面组件
addFolder.vue 为文件上传弹窗组件
moveFolder.vue 为移动文件/文件夹弹窗组件

// shareSpace.vue
<template>
  <div class="app-container">
    <el-page-header class="pageHeader" :content="'当前所处:' + currentLocationName" @back="goBack">
    </el-page-header>
    <el-divider></el-divider>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5" style="float: right;">
        <el-button plain icon="el-icon-refresh" size="mini" @click="refreshGetList">刷新</el-button>
      </el-col>
      <el-col :span="1.5" style="float: right;">
        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addFolder">新建文件夹</el-button>
      </el-col>
      <el-col :span="1.5" style="float: right;">
        <el-button type="success" plain icon="el-icon-upload" size="mini" @click="addFile">上传文件</el-button>
      </el-col>

    </el-row>

    <!-- 文件浏览区 -->
    <div style="overflow: hidden;">
      <el-card class="drawing_card" v-loading="cardLoading" style="height: 60vh">
        <template v-if="folderList.length === 0 && filesList.length === 0">
          <el-empty description="暂无文件,请创建一个文件夹吧" style="height:60vh"></el-empty>
        </template>
        <!-- 文件夹 -->
        <div v-for="( item, index ) in  folderList ">
          <div class="folderContainer">
            <div class="folderWrapper" @dblclick="doubleClickFolder(index, item)"
              @contextmenu.prevent.stop="rightClickFolder(index, item, $event)">
              <img src="@/assets/images/folder/folder.png" style="width: 100px;height: 90px;margin-top: -13px"
                @contextmenu.prevent.stop="rightClickFolder(index, item, $event)" />
              <div class="folderName">
                <span>{{
                  item.folderName.length > 10 ? item.folderName.substring(0, 6) + '...' : item.folderName
                }}</span>
              </div>

            </div>

          </div>
        </div>
        <!-- 文件 -->
        <div v-for="( item, index ) in  filesList ">
          <div class="folderContainer">
            <div class="folderWrapper" @dblclick="down(item.fileUrl)">
              <img src="@/assets/images/folder/fileImg.png" style="width: 100px;height: 90px;margin-top: -13px"
                @contextmenu.prevent.stop="rightClickfile(index, item, $event)" />
              <div class="folderName">
                <span>{{
                  item.fileName.length > 10 ? item.fileName.substring(0, 6) + '...' : item.fileName
                }}</span>
              </div>

            </div>
          </div>
        </div>
      </el-card>
    </div>


    <!-- 文件夹【右键菜单】 -->
    <div class="add-folder-9" :style="folderStyle" v-show="folderShow">
      <div class="add-folder-1">
        <div class="add-folder-2" @click="openFolder">
          打开文件夹
        </div>
        <div style="border: 2px solid rgba(18,17,42,.07)"></div>
        <div class="add-folder-2" @click="moveFolder">
          移动
        </div>
        <div style="border: 2px solid rgba(18,17,42,.07)"></div>
        <div class="add-folder-2" @click="updateFloder">
          重命名
        </div>
        <div style="border: 2px solid rgba(18,17,42,.07)"></div>
        <div class="add-folder-6" @click="deleteFolder">
          删 除
        </div>
      </div>
    </div>
    <!-- 文件【右键菜单】 -->
    <div class="add-folder-9" :style="fileStyle" v-show="fileShow">
      <div class="add-folder-1">

        <div class="add-folder-2">
          <a :href="clickFilePath" download="1">下载文件</a>

        </div>
        <div style="border: 2px solid rgba(18,17,42,.07)"></div>
        <!-- <div class="add-folder-2" @click="updateFloder">
          重命名
        </div>
         <div style="border: 2px solid rgba(18,17,42,.07)"></div> -->
        <div class="add-folder-2" @click="moveFolder">
          移动
        </div>
        <div style="border: 2px solid rgba(18,17,42,.07)"></div>
        <div class="add-folder-6" @click="deleteFileFun">
          删 除
        </div>
      </div>
    </div>
    <!-- 上传文件 弹窗 -->
    <addFolder ref="addFolder1" :currentLocationId="currentLocationId" />
    <moveFolder ref="moveFolder1" :moveData="moveData"></moveFolder>
  </div>
</template>

<script>

import addFolder from '../components/addFolder.vue'
import moveFolder from '../components/moveFolder.vue'
import { qeryFolderList, createPublicFolder, renameFolder, deleteFolder, deleteFile } from '@/api/folder/folder'


export default {
  name: 'shareSpace',
  components: { addFolder, moveFolder },
  data() {
    return {
      historyFolderId: 0,//历史文件夹id,用于【返回上一级】
      historyFolderName: '',//历史文件夹name,用于【返回上一级】

      currentLocationId: 0,//当前所处位置(文件夹)id,0为根目录
      currentLocationName: '共享空间',//当前所处位置(文件夹)名

      //移动文件(夹)时需要的参数
      moveData: {
        typeofFolder: 0,//所选对象的类型(1:文件夹;2:文件)
        clickFolderId: -1,//被右键的文件夹id
      },

      cardLoading: false,
      folderList: [],//文件夹列表
      filesList: [],//文件列表
      // 文件夹 右键菜单栏
      folderStyle: {
        left: '0px',
        top: '0px'
      },
      folderShow: false,
      clickFolderId: -1,//被右键的文件夹id
      clickFolderName: '',//被右键的文件夹名
      // 文件 右键菜单栏
      fileStyle: {
        left: '0px',
        top: '0px'
      },
      fileShow: false,
      clickFileId: -1,//被右键的文件id
      clickFileName: '',//被右键的文件名
      clickFilePath: '',//被右键的文件路径-已加上下载的路径网站前端


      queryParams: {  //查询参数
        folderId: 0 //目标文件(夹)id,值为0则查询根目录文件(夹)
      }
    }
  },
  methods: {
    a() {
      window.open(`这里填服务器储存文件的地址啦~` + this.clickFileName);
    },

    //返回上一级
    goBack() {
      if (this.currentLocationId == 0) {
        this.$message({
          message: '已经不能再往后退啦!',
          type: 'warning'
        });
      } else {
        this.queryParams.folderId = this.historyFolderId;
        this.currentLocationId = this.historyFolderId;
        this.currentLocationName = this.historyFolderName == null ? '文件管理空间' : this.historyFolderName;
        this.historyFolderId = this.currentLocationId;
        this.historyFolderName = this.currentLocationName;
        this.getList();
      }

    },
    // 获取列表数据
    getList() {
      this.loading = true
      qeryFolderList(this.queryParams).then(response => {
        console.log(response)
        this.folderList = response.data.folders
        this.filesList = response.data.sysFiles

        this.historyFolderId = response.data.sysFolder == null ? 0 : response.data.sysFolder.parentId;
        this.historyFolderName = response.data.sysFolder == null ? '文件管理空间' : response.data.sysFolder.parentName;

      })
    },
    // 刷新当前列表
    refreshGetList() {
      this.queryParams.folderId = this.currentLocationId;
      this.getList()
      this.$message({
        message: '已经成功获取最新数据啦!',
        type: 'success'
      });
      this.initClickId()
    },

    //上传文件
    addFile() {
      this.$refs.addFolder1.open();
    },

    //创建文件夹
    addFolder() {

      this.$prompt('请输入新文件夹名称', '创建文件夹', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
      }).then(({ value }) => {
        let sysFolder = {
          folderName: value,
          parentId: this.currentLocationId
        }
        createPublicFolder(sysFolder).then(res => {
          if (res.code == 200) {
            this.$message({
              type: 'success',
              message: '创建成功 '
            });
            const that = this;
            setTimeout(function () {
              that.refreshGetList();  // 刷新当前页面
            }, 500);

          } else {
            this.$message({
              type: 'error',
              message: '创建失败 '
            });
          }
        })

      }).catch(() => {
      });
    },
    // 重命名文件夹
    updateFloder() {
      this.folderShow = false;
      this.$prompt('请输入文件夹的新名称', '重命名', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        inputValue: this.clickFolderName,
        inputErrorMessage: '输入不能为空',
        inputValidator: (value) => {       // 点击按钮时,对文本框里面的值进行验证
          if (!value) {
            return '输入不能为空';
          }
        },
      }).then(({ value }) => {
        let sysFolder = {
          folderName: value,
          folderId: this.clickFolderId //默认为0
        }
        renameFolder(sysFolder).then(res => {
          if (res.code == 200) {
            this.$message({
              type: 'success',
              message: '修改成功 '
            });
            let that = this;
            setTimeout(function () {
              that.refreshGetList();  // 刷新当前页面
            }, 500);

          } else {
            this.$message({
              type: 'error',
              message: '修改失败 '
            });
          }
        })

      })

    },
    //删除文件夹
    deleteFolder() {
      this.folderShow = false;
      this.$confirm('此操作将永久删除该文件夹,包括文件夹内的所有内容,是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteFolder(this.clickFolderId).then(res => {
          if (res.code == 200) {
            this.$message({
              type: 'success',
              message: '删除成功 '
            });
            let that = this;
            setTimeout(function () {
              that.refreshGetList();  // 刷新当前页面
            }, 1000);

          } else {
            this.$message({
              type: 'error',
              message: '删除失败! '
            });
          }
        })

      })
    },
    //打开文件夹
    openFolder() {
      this.folderShow = false;
      this.queryParams.folderId = this.clickFolderId;

      this.currentLocationId = this.clickFolderId;
      this.currentLocationName = this.clickFolderName;
      this.getList();


    },
    //鼠标双击文件夹
    doubleClickFolder(index, item) {

      this.clickFolderId = item.folderId;
      this.clickFolderName = item.folderName;
      this.openFolder();
    },

    //文件夹右键
    rightClickFolder(index, item, e) {

      this.initClickId()

      this.clickFolderId = item.folderId
      this.clickFolderName = item.folderName
      this.folderStyle.left = e.pageX - 140 + 'px'
      this.folderStyle.top = e.pageY - 70 + 'px'
      this.folderShow = true

      this.moveData.typeofFolder = 1


    },
    //文件 右键
    rightClickfile(index, item, e) {
      this.initClickId()

      this.clickFileId = item.fileId
      this.clickFileName = item.fileName
      this.clickFilePath = "https://huang-pu.oss-cn-guangzhou.aliyuncs.com/" + item.filePath
      this.fileStyle.left = e.pageX - 140 + 'px'
      this.fileStyle.top = e.pageY - 70 + 'px'
      this.fileShow = true


      this.moveData.typeofFolder = 2
    },
    //删除文件
    deleteFileFun() {
      this.fileShow = false;
      this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteFile(this.clickFileId).then(res => {
          if (res.code == 200) {
            this.$message({
              type: 'success',
              message: '删除成功 '
            });
            let that = this;
            setTimeout(function () {
              that.refreshGetList();  // 刷新当前页面
            }, 1000);

          } else {
            this.$message({
              type: 'error',
              message: '删除失败! '
            });
          }
        })

      })

    },

    //移动文件(夹)
    moveFolder() {
      this.fileShow = false

      //通过判断文件/文件夹被右键选择而进行参数存储
      if (this.clickFolderId != -1) {
        this.moveData.clickFolderId = this.clickFolderId;
      } else {
        this.moveData.clickFolderId = this.clickFileId;
      }

      this.$refs.moveFolder1.open();
    },

    //初始化右键选择相关参数
    initClickId() {
      this.clickFileId = -1;
      this.clickFolderId = -1;
      this.fileShow = false;
      this.folderShow = false;
    }

  },
  mounted() {
    //监听鼠标点击事件
    document.addEventListener("click", (e) => {
      if (!this.folderShow && !this.fileShow) return; // 如果右键菜单不显示,则不处理点击事件
      let target = e.target;
      while (target && target.parentNode) {
        if (target.parentNode.class === "folderContainer") {
          return;
        }
        target = target.parentNode;
      }
      this.folderShow = false;
      this.fileShow = false; // 如果点击的是其他区域,则隐藏
      this.clickFolderId = -1;
      this.clickFileId = -1;
    });
  },

  created() {
    this.getList()
  }
}
</script>


<style lang="scss">
.pageHeader {
  .el-page-header__content {
    font-size: 16px !important;
  }
}
</style>


<style scoped lang="scss">
.drawing_card {
  width: 100%;
  height: 100%;
  float: left;
  margin-top: 15px;
  overflow: auto;
  box-shadow: 0 5px 5px rgb(0 0 0 /10%);
  transition: all 0.9s;
  border-radius: 10px;
}

.folderContainer {
  width: 150px;
  float: left;
  display: block;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 20px;
  margin-left: 30px;
}



.folder {
  width: 110px;
  height: 80px;
  perspective: 600px;
  transform-style: preserve-3d;
  cursor: pointer;
}

.folderWrapper {
  width: 140px;
  height: 130px;
  padding: 20px 20px 10px 20px;
  position: relative;
  transition: all .2s ease;
  border-radius: 6px;
  cursor: pointer;
}

.folderWrapper:hover {
  background-color: aliceblue;
}


.folderName {
  margin-top: 5px;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  width: 100px;
}


.add-folder-9 {
  position: absolute;
  display: flex;
  justify-content: center;
  padding: 2px;
  align-items: center;
  width: 130px;
  background-color: rgba(6, 13, 20, .18);
  border-radius: 12px;
  box-shadow: 0px 8px 24px rgba(25, 25, 26, .06), 0px 4px 16px rgba(25, 25, 26, .04), 0px 0px 4px rgba(25, 25, 26, .04);
}

.add-folder-1 {
  overflow: hidden;
  width: 97%;
  height: 96%;
  background-color: #fff;
  border-radius: 10px;
}

.add-folder-2 {
  color: #19191a;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 36px;
  // margin-top: 5px;
  // margin-bottom: 5px
}

.add-folder-2:hover {
  background-color: rgba(6, 13, 20, .18);
  // border-radius: 10px;
  cursor: pointer;
}

.add-folder-6 {
  color: #19191a;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 36px;
  // margin-top: 5px;
}

.add-folder-6:hover {
  background-color: red;
  // border-radius: 10px;
  cursor: pointer;
}
</style>

移动操作时,当点击移动后,需要先获取整个系统的树型目录,将该文件(夹)所属的父文件夹id更改为 树型目录中所选的文件夹id 。

//moveFolder.vue
<template>
    <el-dialog v-if="dialogVisible" :modal-append-to-body="false" :close-on-click-modal="false" title="移动"
        :visible.sync="dialogVisible" :show-close="false" width="400px" class="moveFolderDialog">
        <el-alert title="请选择要将当前文件(夹)移动到:" type="info" show-icon>
        </el-alert>
        <el-tree accordion :data="data" node-key="id" ref="tree" highlight-current @node-click="handleNodeClick">
        </el-tree>
        <div style="margin-top: 20px">
            <el-button type="success" @click="submit">确定</el-button>
            <el-button @click="close">取消</el-button>
        </div>
    </el-dialog>
</template>
<script>
import { getTreeDirectory, moveFolder, moveFile } from "@/api/folder/folder";
export default {
    name: 'moveFolder',
    props: ['moveData'],
    // moveDate对象中有两个参数: typeofFolder: 0,//所选对象的类型(1:文件夹;2:文件)
    //                          clickFolderId: -1,//所选对象的id
    data() {
        return {
            dialogVisible: false,
            data: [],//树型目录
            defaultProps: {
                children: 'children',
                label: 'label'
            },
            clickDirectoryId: -1,//所选择的移动目标文件夹id

        }
    },
    methods: {
        open() {
            this.dialogVisible = true,
                getTreeDirectory().then(res => {
                    console.log(res)
                    this.data = res.data
                })
        },
        close() {
            this.dialogVisible = false

        },
        submit() {
            if (this.moveData.typeofFolder == 1 && this.moveData.clickFolderId == this.clickDirectoryId) {
                this.$message({
                    type: 'error',
                    message: '移动失败!请勿把文件夹移动到它本身中! '
                });
            } else {
                if (this.moveData.typeofFolder == 1) {
                    this.moveFolderFun()
                } else if (this.moveData.typeofFolder == 2) {
                    this.moveFileFun()
                }
            }


        },
        //移动 文件夹
        moveFolderFun() {
            let dataObj = {
                folderId: this.moveData.clickFolderId,
                parentId: this.clickDirectoryId
            }
            moveFolder(dataObj).then(res => {
                if (res.code == 200) {
                    this.$message({
                        type: 'success',
                        message: '修改成功 '
                    });
                    let that = this;
                    this.dialogVisible = false
                    setTimeout(function () {
                        that.$parent.refreshGetList();  // 刷新当前页面
                    }, 500);

                } else {
                    this.$message({
                        type: 'error',
                        message: '修改失败 '
                    });
                }
            })

        },
        //移动 文件
        moveFileFun() {
            let dataObj = {
                fileId: this.moveData.clickFolderId,
                folderId: this.clickDirectoryId
            }
            moveFile(dataObj).then(res => {
                if (res.code == 200) {
                    this.$message({
                        type: 'success',
                        message: '修改成功 '
                    });
                    let that = this;
                    this.dialogVisible = false
                    setTimeout(function () {
                        that.$parent.refreshGetList();  // 刷新当前页面
                    }, 500);

                } else {
                    this.$message({
                        type: 'error',
                        message: '修改失败 '
                    });
                }
            })
        },

        //树型目录被选择时
        handleNodeClick(DirectoryId) {
            this.clickDirectoryId = DirectoryId.id
        }
    }

}
</script>
<style lang="scss">
.moveFolderDialog {
    .el-dialog__body {
        padding-top: 0 !important;
    }

    .el-alert {
        margin-bottom: 20px;
    }
}
</style>

文件上传时,后端同学需要对文件信息做进一步处理,即先执行自定义policy()方法获取服务器存储的key加入文件信息再存入数据库。
不必要,可根据个人需求直接修改返回文件信息即可。

//addFolder.vue
<template>
    <el-dialog v-if="dialogVisible" :modal-append-to-body="false" :close-on-click-modal="false" title="上传文件"
        :visible.sync="dialogVisible" :show-close="false" width="400px">
        <el-upload ref="upload" :data="dataObj" action="这里填写文件上传到的服务器地址" class="upload-demo"
            drag :limit="1" :on-success="uploadSuccess" :on-error="uploadError" :on-exceed="handleExceed"
            :before-upload="beforeUpload" :auto-upload="false">
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">将文件拖到此处,或<em>点击上传,当前目录只允许上传1个文件</em></div>
        </el-upload>
        <div style="margin-top: 20px">
            <el-button icon="el-icon-upload2" type="success" @click="submit">提交</el-button>
            <el-button @click="close">取消</el-button>
        </div>
    </el-dialog>
</template>
<script>

import { policy, addFile } from "@/api/folder/folder";
import { getUUID } from "../../../utils/index"
export default {
    name: 'addFolder',
    props: ['currentLocationId'], //当前所处文件夹id
    data() {
        return {

            // oss资源
            dataObj: {},

            dialogVisible: false,

            // 文件信息 - 存于后端数据库
            fileInfo: {
                fileName: '',
                filePath: '',
                fileSize: 0,//单位为kb
                folderId: 0
            }
        }
    },
    methods: {
        open() {
            this.dialogVisible = true
        },
        close() {
            this.dialogVisible = false
            this.$parent.refreshGetList();
        },
        //文件改变调用
        handleExceed() {
            this.$message.error('当前目录只能上传一个文件!');
        },
        //上传成功
        uploadSuccess(res) {
         
            this.dialogVisible = false
            this.$parent.refreshGetList();
        },
        //上传失败
        uploadError() {
            this.$message.error('服务器异常请重试!');
        },
        //上传文件
        submit() {
            this.$refs.upload.submit();


        },
        // 资源上传前
        beforeUpload(files) {
            return new Promise((resolve, reject) => {
                policy().then(response => {
                //数据处理因为业务需求写入的,不必要。
                    //存储服务器数据处理
                    this.dataObj.policy = response.data.policy
                    this.dataObj.signature = response.data.signature
                    this.dataObj.ossaccessKeyId = response.data.accessid
                    this.dataObj.dir = response.data.dir
                    this.dataObj.host = response.data.host
                    this.dataObj.key = response.data.dir + getUUID() + files.name
                    console.log(this.dataObj)
                    //后端数据库数据处理
                    this.fileInfo.fileName = files.name;
                    this.fileInfo.filePath = this.dataObj.key;
                    this.fileInfo.fileSize = parseInt(files.size / 2024);//file.size的单位为字节,转换成kb
                    console.log(this.fileInfo)
                    resolve(true)

                    this.fileInfo.folderId = this.currentLocationId;//确定该文件所处的文件夹id 

                    //上传到后端数据库
                    addFile(this.fileInfo).then(res => {
                        if (res.code == 200) {
                            this.$message({
                                type: 'success',
                                message: '上传成功 '
                            });
                        } else {
                            this.$message({
                                type: 'error',
                                message: '上传失败 '
                            });
                        }
                    })
                })
            })
        },
    }
}
</script>
<style scoped lang="scss"></style>

api不知道需要不需要,一并丢上来好了。
该系统是集成在基于ruoyi框架的系统中。

//folder.js
import request from '@/utils/request'

//查询文件夹及文件列表
export function qeryFolderList(query) {
  return request({
    url: '/system/folder/listFolderAndFile/' + query.folderId,
    method: 'get',
    params: query
  })
}

//移动 前置请求-获取所有目录结构
export function getTreeDirectory() {
  return request({
    url: '/system/folder/listFolderids',
    method: 'get'
  })
}

//===================文件夹=====================

//新建公共文件夹
export function createPublicFolder(data) {
  return request({
    url: '/system/folder',
    method: 'post',
    data: data
  })
}
//重命名文件夹
export function renameFolder(data) {
  return request({
    url: '/system/folder',
    method: 'put',
    data: data
  })
}
//删除文件夹
export function deleteFolder(folderId) {
  return request({
    url: '/system/folder/' + folderId,
    method: 'delete'
  })
}
//移动文件夹
export function moveFolder(data) {
  return request({
    url: '/system/folder',
    method: 'put',
    data: data
  })
}

//===================文件=====================
// oss资源上传 - 后端服务器
export function policy() {
  return request({
    url: '/system/ziyuan/oss/policy',
    method: 'get'
  })
}
//上传文件-后端数据库
export function addFile(data) {
  return request({
    url: '/system/file',
    method: 'post',
    data: data
  })
}

//删除文件
export function deleteFile(fileId) {
  return request({
    url: '/system/file/' + fileId,
    method: 'delete'
  })
}

//移动文件夹
export function moveFile(data) {
  return request({
    url: '/system/file',
    method: 'put',
    data: data
  })
}

TODO:

没有制作分页查询操作。
有点乱(磕头,有空再整理。

  • 13
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值