开发初衷
市面上常见的多媒体资源管理器并不少见,比如很有名的本地电子书管理工具-Calibre,图片管理工具-Eagle,以及音频爱好者喜爱的foobar2000。它们在各自的领域内都完美解决了诸多痛点,但人的需求是在不断变化的,互联网的环境也是在不断发生改变的。
作为一名仓鼠党,很多时候面对资源的收集与整理都会手足无措,起初多媒体文件数量相对较少的情况下,可以采用较为随意的管理方式对文件进行管理,但随着文件资源数量的增加,如果没有或缺乏一个合理的文件管理方式就会导致文件之间关系混乱,渐渐地,自己也会疲于维护与管理。而避免这种问题的方式就是通过文件管理工具对我们收集的资源或文件进行统一管理。
理想的情况是我们在软件使用初期定义我们的行为习惯,后续我们只需要将所有文件统一化的保存,工具就会帮我们进行统一的管理。这种管理方式在Calibre中就有所体现,我们在初次使用过程中定义电子书的保存地址,同时定义我们的元数据链接,后续我们在保存电子书的过程中就可以自动帮我们利用元数据链接(豆瓣,亚马逊等)获取电子书基本信息,从而进行统一管理。
而现有的多媒体文件资源管理器应用虽然数量众多,但有些在功能性上有所欠缺,有些在兼容性上出现问题,无法真正确保对大部分资源的统一管理。所以才有了开发针对于个人的统一多媒体文件资源管理器的想法。
功能分析
开发过程记录
图片的瀑布流展示实现
瀑布流实现的主要思路是:
- 确定所有图片的固定宽度
- 实时监听-获取窗口当前宽度
- 根据图片固定宽度和窗口宽度确定每行排列的图片数量
- 依次获取图片信息,准备开始进行瀑布流渲染
- 根据图片原尺寸信息以及固定宽度进行图片的缩放并保存缩放后的图片长度
- 第一行图片只需要按照顺序依次渲染图片
- 从第二行开始,根据之前保存的缩放图片长度确定当前最短列,在该位置渲染图片,直到整个渲染过程结束
瀑布流扩展功能:
- 通过功能键(CTRL+鼠标滚轮滑动)实现图片的放大缩小(主要在于调整图片的固定宽度计算图片新长度以及重新实现渲染图片过程)
- 懒加载,在滑动到图片位置前不加载图片以节省系统开销
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
| <template> <el-scrollbar v-if="targetVal == contentArr.length"> <div> <el-button type="primary" @click="enlargeImage">enlargeImage</el-button> <el-button type="primary" @click="decreaseImage">decreaseImage</el-button> <el-button type="primary" @click="getContentArr">getContentArr</el-button> </div> <div style="position: relative"> <div class="container"> <div v-for="(column, index) in columns" :key="index" class="column"> <div v-for="(item, i) in column.columnArr" :key="i" class="item" :style="{ width: itemWidth + 'px' }"> <el-image :src="item.src" fit="cover" :style="{ height: item.height + 'px', width: itemWidth + 'px' }" class="image" lazy /> </div> </div> </div> </div> </el-scrollbar> </template>
<script> export default { props: { contentArr: { type: Object, required: true } }, data() { return { itemWidth: 220, targetVal: 0, columns: [], arrIndex: [] } }, watch: { targetVal: { handler(newValue, oldValue) { if (newValue == this.contentArr.length) { this.initPage() } }, immediate: true } }, created() { this.getImgHeight() }, mounted() { this.keyDownAndScroll() }, methods: { initPage() { this.init() window.onresize = () => { this.init() } },
getMinHeight(arr) { let a = [] for (let i = 0; i < arr.length; i++) { a.push(parseInt(arr[i].height) + parseInt(arr[i].top)) } return Math.min.apply(null, a) },
getMinIndex(val) { for (let i = 0; i < this.columns.length; i++) { let height = this.columns[i].columnArr[this.columns[i].columnArr.length - 1].height let top = this.columns[i].columnArr[this.columns[i].columnArr.length - 1].top if (parseInt(height) + parseInt(top) == val) { this.arrIndex.push(i) } } },
async getImgHeight() { let sel = this for (let i = 0; i < this.contentArr.length; i++) { console.log(i) let img = new Image() img.src = this.contentArr[i].src + '?' + Date.parse(new Date()) var promise = new Promise((reslove) => { img.onload = function () { let scale = sel.itemWidth / img.width let width = img.width var height = Math.floor(scale * img.height) let trueHeight = img.height
let data = { height: height, trueHeight: trueHeight, width: width } reslove(data) } }) await promise promise.then(function (data) { console.log(data) sel.contentArr[i].height = data.height sel.contentArr[i].trueHeight = data.trueHeight sel.contentArr[i].width = data.width sel.targetVal++ }) } },
getContentArr() { console.log(this.contentArr) },
refreshImageHeight() { for (let i = 0; i < this.contentArr.length; i++) { let scale = this.itemWidth / this.contentArr[i].width let trueHeight = this.contentArr[i].trueHeight let height = Math.floor(scale * trueHeight) this.contentArr[i].height = height } },
init() { this.columns = [] let contentLen = this.contentArr.length let cWidth = document.documentElement.clientWidth || document.body.clientWidth let cLen = Math.floor(cWidth / (this.itemWidth + 20) - 1) console.log(cLen)
for (let i = 0; i < cLen; i++) { this.contentArr[i].top = 0 this.columns.push({ columnArr: [this.contentArr[i]] }) }
for (var index = cLen; index < contentLen; index++) { this.arrIndex = [] let arr = [] let minHeight = 0 let pushIndex = 0
for (let i = 0; i < this.columns.length; i++) { arr.push({ height: this.columns[i].columnArr[this.columns[i].columnArr.length - 1].height, top: this.columns[i].columnArr[this.columns[i].columnArr.length - 1].top }) }
minHeight = this.getMinHeight(arr) this.getMinIndex(minHeight) if (this.arrIndex.length > 0) { pushIndex = Math.min.apply(null, this.arrIndex) }
this.contentArr[index].top = minHeight + 20 this.columns[pushIndex].columnArr.push(this.contentArr[index]) } },
enlargeImage() { this.itemWidth += 10 this.refreshImageHeight() this.init() }, decreaseImage() { this.itemWidth -= 10 this.refreshImageHeight() this.init() },
keyDownAndScroll() { let ctrlDown = false ;(document.onkeydown = function (e) { let e1 = e || event || window.event || arguments.callee.caller.arguments[0] if (e1.keyCode === 17) ctrlDown = true }), (document.onkeyup = function (e) { let e1 = e || event || window.event || arguments.callee.caller.arguments[0] if (e1.keyCode === 17) ctrlDown = false }), document.addEventListener( 'mousewheel', (e) => { let e1 = e || event || window.event || arguments.callee.caller.arguments[0] if (ctrlDown) { if (e1.wheelDeltaY > 0) { console.log('放大') this.enlargeImage() } else { console.log('缩小') this.decreaseImage() } } }, false ) } } } </script>
|
开发进度
日期 |
完成内容 |
待处理 |
2022/1/26 |
框架搭建,文件夹选择器,IndexedDB测试用例 |
文件夹内文件预览,打开 |
2022/1/27 |
文件内文件预览,文件打开以及所在文件夹打开 |
文件信息编辑(加tag,改名,移除等) |
2022/1/27-2 |
窗体最小宽度调整,图片预览部分功能按键设置 |
|
2022/1/28 |
在card下打开所在文件夹,删除该文件,编辑文件名等功能,以及打分模块示例 |
|
2022/1/30 |
视频封面选择测试,图片压缩功能测试 |
|
2022/2/3 |
右键菜单,视频封面图选择与删除 |
压缩文件第一张图片预览 |
2022/2/4 |
压缩文件读取与选择性解压,设置压缩文件封面 |
高级选择表单 |
2022/2/4-2 |
本地文件分页 |
|
2022/4/6 |
本地重新部署 |
调用python执行功能性文件,页面设计 |
2022/7/7 |
全局变量文件Global.vue测试 |
|
2022/7/8 |
引入NaiveUI |
|
2022/7/9 |
完成全局路径基本配置,配置electron builder进行前端打包,解决打包后iconfont显示问题,后端基本部署完成,H2数据库引入完成,测试基本使用功能正常,由后端监听启动前端部分测试成功 |
资源管理方式定义,资源信息存储方式,后端打包方式,H2数据库可视化部分测试 |
2022/7/10 |
添加图集功能测试,前后端连接,H2数据库构建,图集录入功能实现 |
图集展示功能 |
2022/7/23 |
图集选择栏 |
图集展示功能 |
2022/7/25 |
瀑布流图片展示功能demo完成 |
进一步优化瀑布流展示 |
2022/7/26 |
瀑布流优化,还有进一步优化空间 |
creted,mounted选择 |
2022/7/27 |
瀑布流功能暂定版(修正数据加载跳闪,数据多次重加载) |
图像信息表单主动填入,图像多种展示方式 |
2022/7/28 |
瀑布流下拉无线刷新初版 |
|
2022/8/26 |
瀑布流图片放大缩小功能实现,同时监听鼠标滚轮和CTRL按键后进行放大缩小实现 |
功能继续测试与整合 |
2022/8/28 |
实现后端自动获取视频缩略图功能,实现前端获取后端生成的视频缩略图功能 |
缩略图保存位置以及数据统一 |