开发初衷
市面上常见的多媒体资源管理器并不少见,比如很有名的本地电子书管理工具-Calibre,图片管理工具-Eagle,以及音频爱好者喜爱的foobar2000。它们在各自的领域内都完美解决了诸多痛点,但人的需求是在不断变化的,互联网的环境也是在不断发生改变的。
作为一名仓鼠党,很多时候面对资源的收集与整理都会手足无措,起初多媒体文件数量相对较少的情况下,可以采用较为随意的管理方式对文件进行管理,但随着文件资源数量的增加,如果没有或缺乏一个合理的文件管理方式就会导致文件之间关系混乱,渐渐地,自己也会疲于维护与管理。而避免这种问题的方式就是通过文件管理工具对我们收集的资源或文件进行统一管理。
理想的情况是我们在软件使用初期定义我们的行为习惯,后续我们只需要将所有文件统一化的保存,工具就会帮我们进行统一的管理。这种管理方式在Calibre中就有所体现,我们在初次使用过程中定义电子书的保存地址,同时定义我们的元数据链接,后续我们在保存电子书的过程中就可以自动帮我们利用元数据链接(豆瓣,亚马逊等)获取电子书基本信息,从而进行统一管理。
而现有的多媒体文件资源管理器应用虽然数量众多,但有些在功能性上有所欠缺,有些在兼容性上出现问题,无法真正确保对大部分资源的统一管理。所以才有了开发针对于个人的统一多媒体文件资源管理器的想法。
功能分析
开发过程记录
图片的瀑布流展示实现
瀑布流实现的主要思路是:
- 确定所有图片的固定宽度
- 实时监听-获取窗口当前宽度
- 根据图片固定宽度和窗口宽度确定每行排列的图片数量
- 依次获取图片信息,准备开始进行瀑布流渲染
- 根据图片原尺寸信息以及固定宽度进行图片的缩放并保存缩放后的图片长度
- 第一行图片只需要按照顺序依次渲染图片
- 从第二行开始,根据之前保存的缩放图片长度确定当前最短列,在该位置渲染图片,直到整个渲染过程结束
瀑布流扩展功能:
- 通过功能键(CTRL 鼠标滚轮滑动)实现图片的放大缩小(主要在于调整图片的固定宽度计算图片新长度以及重新实现渲染图片过程)
- 懒加载,在滑动到图片位置前不加载图片以节省系统开销
<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: []
// pageSize: 25,
// startPage: 1,
// pageNum: 1
}
},
watch: {
//监听标志值变化,直到所有图片长度成功获取
targetVal: {
handler(newValue, oldValue) {
if (newValue == this.contentArr.length) {
this.initPage()
}
},
immediate: true
}
},
created() {
this.getImgHeight()
},
mounted() {
// this.initPage()
//监听键盘与鼠标(CTRL 鼠标滚轮)实现瀑布流图片缩放
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() {
//在异步方法下的this与JS全局中的this意义不同,
//所以在方法开始时重新定义全局this变量用来获得全局数据
let sel = this
//遍历contentArr(从父组件获取并传递过来)
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())
//利用promise异步构造获取图像宽高等基本信息
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) //对原长度进行缩放(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
})
}
},
//测试方法,打印contentArr
getContentArr() {
console.log(this.contentArr)
},
//刷新图片高度
refreshImageHeight() {
//刷新图片高度(用于在页面中通过CTRL 鼠标滚轮或滑动条动态调整图片宽度后进行图片高度的刷新)
//由于在页面初始化中执行的getImgHeight方法已经获取到了图片的真实高度,
//所以在此处就只需要重新计算宽度修改过之后的长度缩放比例以及新的高度(避免了在刷新页面过程中使用异步方法)
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) //对原长度进行缩放(height是真实展示在页面上的长度)
//更新刷新后的新高度
this.contentArr[i].height = height
// console.log(this.contentArr[i])
}
},
//初始化(重载)页面瀑布流
init() {
this.columns = []
let contentLen = this.contentArr.length
// let contentLen = this.pageSize
// 根据可视区域的宽度判断需要几列
let cWidth = document.documentElement.clientWidth || document.body.clientWidth
// 假设图片宽度为240px
let cLen = Math.floor(cWidth / (this.itemWidth 20) - 1)
console.log(cLen)
// 初始化每一列的第一行元素
for (let i = 0; i < cLen; i ) {
this.contentArr[i].top = 0 //预设距离顶部值为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])
}
},
//图像放大(宽度加10)
enlargeImage() {
this.itemWidth = 10
this.refreshImageHeight()
this.init()
},
//图片缩小(宽度减10)
decreaseImage() {
this.itemWidth -= 10
this.refreshImageHeight()
this.init()
},
// 监听键盘和鼠标滚轮组合
keyDownAndScroll() {
let ctrlDown = false //ctrl按键按压情况
;(document.onkeydown = function (e) {
//事件对象兼容
let e1 = e || event || window.event || arguments.callee.caller.arguments[0]
//按下CTRL键下后记录当前情况
if (e1.keyCode === 17) ctrlDown = true
}),
(document.onkeyup = function (e) {
let e1 = e || event || window.event || arguments.callee.caller.arguments[0]
//松开后修改CTRL键按压情况
if (e1.keyCode === 17) ctrlDown = false
}),
//监听鼠标滚轮情况
document.addEventListener(
'mousewheel',
(e) => {
// e.preventDefault()
let e1 = e || event || window.event || arguments.callee.caller.arguments[0]
//判断CTRL键是否被按下
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 | 实现后端自动获取视频缩略图功能,实现前端获取后端生成的视频缩略图功能 | 缩略图保存位置以及数据统一 |