本次分享是对之前写的个人项目CodeasilyX进行一个回顾分享,主要是为了记录这些值得分享的经验,本文只涉及核心代码分享,不包括完整实现和样式实现,重在理解,不为copy
。
本文指的简单的ide文件管理组件
是CodeasilyX里面的那个文件管理组件,因为是自己业余时间纯手写的,只有一些基础功能,操作性功能有:新建文件
、新建文件夹
、删除文件
、重命名
,辅助性功能有:文件名排序
、文件夹展开收缩
、记录文件打开状态
。虽然功能少,但底层的基础结构得花不少时间,最后实现这几个功能只是在基础结构上开放出来而已。
功能展示
下面展示这个组件的功能
操作性功能:新建文件
、新建文件夹
、删除文件
、重命名
辅助性功能:文件名排序
、文件夹展开收缩
、记录文件打开状态
。
结构设计
首先这个文件管理的目录结构是需要做到可序列化
和反序列化
的,因为只有序列化了才能进行合理的存储和传输,这是一个网络应用不可少的。
我们还要考虑这个文件管理组件会有什么功能,前面有提到过有四个基本的功能,如果只考虑这四个基本功能的话,那么可以用简单一些的结构设计,如果有更复杂的功能(比如支持创建时间排序、复制剪切粘贴等)那就得设计更适合的结构才能支撑起这些功能。
根据上面的功能展示的动图,对应的结构就是下面这段json数据:
示例文件结构json
从上图可看出每个item有name
,type
,layer
,index
,path
和children
这几个字段,这里主要解释一下index
这个字段,其他都很好理解,index
代表文件在当前文件夹的位置,由层级layer
进一步以尖括号>
进一步区分。比如0>2
代表第1个文件夹里的第3个文件。这个位置在文件操作上起到绝对性作用,它记录了文件的位置层级,从而能够解析如/xxx/xx
这种字符串路径快速查找。
代码实现
界面上这种目录结构可以用vue的组件嵌套递归实现,核心的递归组件代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| <template> <div class="file-component"> <div v-if="data.type == 'file'" class="file-type" :style="{paddingLeft: (data.layer-1) * 15 + 5 + 'px'}" :class="{'active': data.path == _state.curFile}" @click="changeFile(data.path)" :data-type="data.type" :data-path="data.path"> {{data.name}} </div> <div v-else class="folder-type"> <div class="folder-name" :style="{paddingLeft: (data.layer-1) * 1.5 + 'em'}" :data-type="data.type" :data-path="data.path" :class="{'expanded icon-folder-open': expandStatus, 'icon-folder': !expandStatus}" @click="handle_expand">{{data.name}}</div> <div v-if="expandStatus"> <file-component v-for="(item, index) in data.childrens" :key="item.name + index" :data="item" :changeFile="changeFile"/> </div> </div> </div> </template>
<script> export default { name: 'file-component', props: [ 'data', 'isExpand', 'changeFile' ], data() { return { } }, computed: { expandStatus() { return this.data.type == 'folder' ? !!this._state.folderExpand.match(this.data.path) : false } }, methods: { handle_expand() { if (this._state.isPlaying) { return; } var path = this.data.path if (~path.indexOf(this._state.folderExpand) || ~this._state.folderExpand.indexOf(path)) { var isExpand = this._state.folderExpand.match(path); var resultExpand = isExpand ? path.replace(/\/[^\/]+$/, '') : this._state.folderExpand + path.replace(this._state.folderExpand, ''); this._set({folderExpand: resultExpand}); } else { this._state.folderExpand = path; } }, } }; </script>
<style lang="less" scoped> .file-component { width: 100%; color: #fff; }
.file-type { cursor: pointer; &:hover,&:active { background: lighten(#32393D, 5%); } &.active { background: lighten(#32393D, 20%); } white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .folder-type { .folder-name { padding: 0 5px; line-height: 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; &:hover,&:active { background: lighten(#32393D, 5%); } cursor: pointer; &:before { content: "\e92f"; display: inline-block; margin-right: 5px; font-size: 12px; } &.expanded { &:before { content: "\e930"; } } } } </style>
|
核心js
接下来就最核心的部分,管理着结构变动和目录索引的几个纯函数
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| export function layerInclude(data) { let newData = JSON.parse(JSON.stringify(data)); let layer = 0; const layerData = function(data, parentIndex='', presetPath='') { data.map((item, index) => { index = index + '' if (index == 0) { layer++ item.layer = layer } else { item.layer = layer = data[index-1].layer; } item.path = presetPath + '/' + item.name if (item.childrens) { item.index = parentIndex + index ; layerData(fileNameSort(item.childrens), item.index + '>', item.path) } else { item.index = parentIndex ? parentIndex + index : index } }) } layerData(fileNameSort(newData)); return newData; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export function indexToPath(filesData, index) { if (!index) { return '/' } var fileData = filesData var result = '/'; var indexArr = index.split('>'); indexArr.map((key) => { if (fileData[key].type === 'folder') { result += fileData[key].name + '/'; fileData = fileData[key].childrens; } else { result += fileData[key].name } }) return result; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| export function getFileByEntryPath(filesData, indexFile, srcPath) { var fileData = JSON.parse(JSON.stringify(filesData)); fileData = pathToFileObj(fileData, indexFile, 'folder') if (/^(\/\w+)+\.*\w+$/.test(srcPath)) { return pathToFileObj(filesData, srcPath) } else if (/^(\.\/)/.test(srcPath)) { srcPath = srcPath.replace(/^(\.\/)/, '') return pathToFileObj(fileData, srcPath) } else if (/^(\.\.\/)+(\w+\/)*/.test(srcPath)) { let upfileData = filesData; var uplayerStr = srcPath.match(/^(\.\.\/)+/)[0]; let lastPath = srcPath.replace(uplayerStr, ''); var layer = uplayerStr.match(/..\//g).length; var layerPath = indexFile.split('/'); layerPath.pop() let folderPath = layerPath.slice(0, layerPath.length - layer - 1); folderPath.map((path) => { upfileData = upfileData.find((item) => item.path == path).childrens || upfileData; }) return pathToFileObj(upfileData, lastPath) } }
|
总结
解释的比较少,主要是阅读代码自己理解学习为主,实现一个简单的文件管理组件需要考虑的还是比较多的,尤其是最初的基础数据格式设计,需要根据需求功能,设计适当的数据格式,性能也是着重考虑的部分,合适的数据格式能让操作性能大幅提升,如果需要更复杂的功能则可能需要重构。
希望本文对你有所帮助~