写在正面
什么是流影书签
//file.ts
import createMessage from 'base/Message/index';
// 文件下载
export const downloadFile = (jsonStr: any) => {
// 将数据转换为字符串
jsonStr = JSON.stringify(jsonStr);
// 创建 blob 对象
const blob = new Blob([jsonStr]);
// 创建一个a标签
const el = document.createElement('a');
// 创建一个 URL 对象并传给 a 的 href
el.href = URL.createObjectURL(blob);
// 设置下载的默认文件名
el.download = '流莺书签数据备份.json';
// 模拟点击链接进行下载
el.click();
};
// 文件上传
export const uploadFile = (e: any) => {
return new Promise((reject) => {
// 如果没有选择文件就什么也不做
if (e.target.value === '' || e.target.files.length < 1) {
return;
}
// 如果不是json格式的文件,给出提示
if (e.target.files[0].type !== 'application/json') {
createMessage('请上传由本站导出的json格式的文件 !');
return;
}
// 创建 FileReader对象
const reader = new FileReader();
// 把文件读取为字符串
reader.readAsText(e.target.files[0]);
// 文件读取完成
reader.onload = function (ev: any) {
reject(ev);
};
});
};
“流影”是我最喜欢的词。指的是最初飞歌的武士,像我一样,我是一个很不寻常的人。 “流影书签”是一个用于以某种方式存储和管理收藏网址的网站。虽然浏览器有最爱的功能,可以创建多个文件夹,但我个人感觉还是比较难找,而且很长。十分难看。所以我想做一个既实用又漂亮的书签,就叫“流影书签”。
柳影为什么被加书签
在开始这个项目之前,公司使用的是VUE2
+ JS
系列,我的博客也是以VUE2
为基础的。另一方面,在VUE3
正式版发布后,我公司计划更新VUE3
,我也想学习更多的技术来提高自己的技术和竞争力。另外,自从见到她以来,我已经有一段时间没有和TS
练习了,所以我们只是想练习一下。
项目地址
??项目查看地址,可直接设置为浏览器页面或桌面快捷方式使用
地址源代码
完全开源,任何人都可以随意探索和开发。当然也非常欢迎大家点击Star⭐⭐⭐
??源码链接(gitee)??源码链接(github)
项目介绍
项目目录
是 。基础组件和业务组件分开存储。每个项目都是一个文件夹。文件夹下方是 VUE文件
、TS文件
和 TS文件
,用于存储数据和类型声明。
├── src
├── assets // 存放静态资源
├── baseComponents // 基础组件
│ └──Form // 表单组件
│ │ ├──Form.vue
│ │ └──index.ts
│ ├── Input // 输入框组件
│ │ ├──Input.vue
│ │ └──index.ts
│ ....
├── components // 业务组件
│ ├── BookMark // 页面主要内容相关组件
│ │ ├──BookMark.vue
│ │ ├──useLabels.ts
│ │ └──index.ts
│ .....
├── hooks // 封装的一些复用逻辑
├── styles // 样式
├── utils // 存放自己封装的工具
├── APP.vue
└── main.ts
墓地/工作
墓地
✅ 操作标签
├── src
├── assets // 存放静态资源
├── baseComponents // 基础组件
│ └──Form // 表单组件
│ │ ├──Form.vue
│ │ └──index.ts
│ ├── Input // 输入框组件
│ │ ├──Input.vue
│ │ └──index.ts
│ ....
├── components // 业务组件
│ ├── BookMark // 页面主要内容相关组件
│ │ ├──BookMark.vue
│ │ ├──useLabels.ts
│ │ └──index.ts
│ .....
├── hooks // 封装的一些复用逻辑
├── styles // 样式
├── utils // 存放自己封装的工具
├── APP.vue
└── main.ts
也是分类的,支持操作、删除、修改
✅使用书签
为保存的网址,支持添加、删除、编辑
✅搜索
您可以在输入框中输入内容,然后点击相应图标进行搜索。目前支持百度、Google、Bing。按回车默认使用百度搜索
✅翻译
点击翻译图标即可快速翻译,百度支持
✅ 导出、导入
因为没有账户功能,为了防止困难数据丢失,或者与朋友分享您的数据,它支持结构化导出和导入恢复
特点
?本地存储
项目使用localStorage
来保存数据,所以不要随意删除缓存,除非你已经做了备份,否则你会烧毁你所有的收藏
?为他获取
输入目标网址时,可以自动下载图标和标题。但接口有限,无法适用于所有站点,所以支持手动输入,也可以使用默认图标
。?主要元素
项目不使用任何元素库,涵盖了一些基本元素,例如Dialog
、Message
、Input
、Form
等。 ❀
未使用的物品
❎vue-router4,vuex4
vue3
生态系统支持vue-router4
和vuex4
,但由于项目并不复杂,所以没有使用。随着工作的扩展,这可以在将来添加。
❎<脚本安装>
我以前学过这个糖语法,但我是初学者vue3
,所以我决定先打好基础,然后再做俏皮的操作。也许我会在未来的几页上尝试这个语法糖。 。
特点
由于文章篇幅,这些要素的介绍是单独一篇文章,点击链接即可跳转查看。
建设项目
??从零开始搭建一个Vite2+VUE3+TS项目
包装主要成分
??基本组件(1):按钮、叠加、对话框、消息
??基本要素(二):外观、简介
组件
??业务延迟,一般模式(无)
进口、出口出口
首先你把上传文件、文件、用到的两个函数、注释都写得很详细了
//file.ts
import createMessage from 'base/Message/index';
// 文件下载
export const downloadFile = (jsonStr: any) => {
// 将数据转换为字符串
jsonStr = JSON.stringify(jsonStr);
// 创建 blob 对象
const blob = new Blob([jsonStr]);
// 创建一个a标签
const el = document.createElement('a');
// 创建一个 URL 对象并传给 a 的 href
el.href = URL.createObjectURL(blob);
// 设置下载的默认文件名
el.download = '流莺书签数据备份.json';
// 模拟点击链接进行下载
el.click();
};
// 文件上传
export const uploadFile = (e: any) => {
return new Promise((reject) => {
// 如果没有选择文件就什么也不做
if (e.target.value === '' || e.target.files.length < 1) {
return;
}
// 如果不是json格式的文件,给出提示
if (e.target.files[0].type !== 'application/json') {
createMessage('请上传由本站导出的json格式的文件 !');
return;
}
// 创建 FileReader对象
const reader = new FileReader();
// 把文件读取为字符串
reader.readAsText(e.target.files[0]);
// 文件读取完成
reader.onload = function (ev: any) {
reject(ev);
};
});
};
用于元素。我隐藏了上传的文件input
并使用图标来跟踪下载按钮的点击。文件成功完成后,promise
输入reject
,然后检查输入,看看文件是否符合格式。如果不匹配,会给出错误信息,如果匹配,则替换数据。
//这里是简略过的伪代码,详情请查阅源码
<template>
<i class="iconfont" @click='handleClick(1)'></i>
<i class="iconfont" @click='handleClick(2)'></i>
<input type="file" id="file-select" @change="handleUploadFile">
</template>
<script lang='ts'>
//此处省略引用
export default defineComponent({
setup(props, { emit }) {
// 点击图标的处理函数
const handleClick = (index:number) => {
if (index === 1) {
// 获取type为file的input元素
const input = document.querySelector('#file-select') as HTMLInputElement;
// 模拟点击
input.click();
}
if (index === 2) {
// 从浏览器本地取出数据
const warblerData = getItem('WARBLER_DATA');
const themeData = getItem('THEME_DATA');
// 整合数据
let jsonStr: any = {
warblerData,
themeData,
};
downloadFile(jsonStr);
}
};
// 触发上传文件函数
const handleUploadFile = (e: Event) => {
uploadFile(e).then((ev: any) => {
// 为什么要包裹一层try catch, 因为 JSON.parse在转换的时候,如果格式不符合要求会报错 如果报错说明上传的JSON不是我们想要的,给出提示即可
try {
// 把 JSON 字符串转换为 JSON 对象
const jsonObj = JSON.parse(ev.target.result);
// 验证JSON的格式是不是我们需要的格式
const test = () => {
// 标志变量
let flag = true;
// 循环我们需要的key 在读取的文件中判断是否具有我们所需要的所有key值 如果没有就返回错误
dataFormat.forEach((dataItem) => {
const result = Object.keys(jsonObj).find((jsonItem) => dataItem === jsonItem);
if (!result) {
flag = false;
}
});
return flag;
};
// 如果格式不符合本站要求,给出提示
if (!test()) {
createMessage('请上传由本站导出的json格式的文件 !');
return;
}
// 如果符合要求 触发更新数据方法
emitter.emit('update-warblerData', jsonObj.warblerData);
} catch (error) {
createMessage('请上传由本站导出的json格式的文件 !');
}
});
};
},
});
</script>
//此处省略css,详情请查看源码
获取它的图标
我没有编写爬虫的经验。分析信息后,我为网站图标和标题编写了一个漂亮的界面。因此,有些网站无法控制。如果有更好的抓取方式,请告诉我。
我的博客是基于express
,并且托管在我自己的服务器上,所以我就免费在我的博客项目中编写了这个接口。
/*
* @Description:获取网站标题和图标的爬虫
* 1.某些网站有大佬设计了反爬,我就是写了最基本的爬虫,根本进不去网站
* 2.某些网站虽然能进去,但是图标经过了各种骚操作,我找不到
* 所以前端支持自动获取失败的时候,手动选择图标
* 3.错误码 300 没有填写网址 301请求失败
* 4.请求失败 也会在error返回text字段 里面包含网站图标 只不过取不到网站内容
* 我们不需要内容 只需要title和icon 所以我们在错误处理中也进行一次爬取
*/
// 用来发送请求的模块
const superagent = require('superagent');
// 用来托管html的模块
const cheerio = require('cheerio');
//获取网站主域名
const getFinallyUrl = (targetUrl) => {
// 将目标域名以“//”进行分割
const urlArray = targetUrl.split('//');
//定义最终的域名
let finallyUrl = '';
//这个判断的意思是 如果数组存在第[1]项 那么证明这个网址是以http/https开头的 否则就是不带有http/https的
if (urlArray[1]) {
//如果带有http/https 咱们就把http/https给拼上返回
finallyUrl = urlArray[0] + '//' + urlArray[1];
} else {
//如果不带有http/https 咱也不知道是http还是https 就返回原来的值
finallyUrl = urlArray[0] + '/';
}
return finallyUrl;
};
//获取最终的图标地址
const getFinallyIcon = (finallyUrl, icon) => {
let finallyIcon = '';
if (icon) {
//这个判断的意思是 如果存在://或者www. 则证明路径中是绝对路径 否则是相对路径
if (icon.indexOf('//') > -1 || icon.indexOf('www.') > -1) {
//如果是绝对路径直接使用
finallyIcon = icon;
} else {
//如果是相对路径的话,就给拼接上当前网站的主域名
finallyIcon = finallyUrl + icon;
}
}
return finallyIcon;
};
//获取标题和图标地址
const getTitleAndIcon = (finallyUrl, text) => {
//获取到的网页是本文格式,node自身无法解析,所以交给cheerio进行托管
const $ = cheerio.load(text);
//获取网站标题
const title = $('title').text();
//由于不同网站的icon格式不同,这里预设了几种
//但是由于某些网站的大佬进行了各种包装,导致基本的爬取方式获取不到
const icon1 = $("[rel='icon']").attr('href');
const icon2 = $("[href$='.ico']").attr('href');
const icon3 = $("[rel='shortcut icon']").attr('href');
let icon = icon1 || icon2 || icon3;
//获取最终图标的地址
const finallyIcon = getFinallyIcon(finallyUrl, icon);
return {
title,
finallyIcon,
};
};
module.exports = async (req, res) => {
//从请求体里获取将要爬取网站的url
const targetUrl = req.body.targetUrl;
//判断一下url是否为空,虽然前端也会校验
if (!targetUrl) {
return res.json({
errorStatus: 300,
errorMsg: '目标网址路径不可为空',
});
}
const finallyUrl = getFinallyUrl(targetUrl);
//模拟打开对应url的网页
superagent
.get(targetUrl)
.then((superagentRes) => {
//成功的话直接取text
const { title, finallyIcon } = getTitleAndIcon(finallyUrl, superagentRes.text);
//接口返回标题和图标地址
res.json({
title: title,
icon: finallyIcon,
});
})
.catch((error) => {
console.log('??~ error:', error);
//错误处理
//部分网站失败了也会有text字段 只不过取不到网站内容 我们不需要内容 只需要title和icon
const { title, finallyIcon } = getTitleAndIcon(finallyUrl, error.response.text);
res.json({
errorStatus: 301,
errorMsg: error.message,
title: title,
icon: finallyIcon,
});
});
};
下一步发展计划
?按住标签页,拖放书签,书签可以拖到其他标签页
?移动端兼容,仅支持电脑浏览器
?要更换主题,网站本身提供了深色和浅色两个主题,并且支持自定义
写在最后
欢迎大家给我建议,评论或者私信,非常感谢???.♂
✅我觉得我的设计有些部分太复杂了,有没有更简单或更好的方法来包装它们
✅ 觉得你的一些代码太旧了,可以提供新的API或者最后一句
✅有些文章内容看不懂
//file.ts
import createMessage from 'base/Message/index';
// 文件下载
export const downloadFile = (jsonStr: any) => {
// 将数据转换为字符串
jsonStr = JSON.stringify(jsonStr);
// 创建 blob 对象
const blob = new Blob([jsonStr]);
// 创建一个a标签
const el = document.createElement('a');
// 创建一个 URL 对象并传给 a 的 href
el.href = URL.createObjectURL(blob);
// 设置下载的默认文件名
el.download = '流莺书签数据备份.json';
// 模拟点击链接进行下载
el.click();
};
// 文件上传
export const uploadFile = (e: any) => {
return new Promise((reject) => {
// 如果没有选择文件就什么也不做
if (e.target.value === '' || e.target.files.length < 1) {
return;
}
// 如果不是json格式的文件,给出提示
if (e.target.files[0].type !== 'application/json') {
createMessage('请上传由本站导出的json格式的文件 !');
return;
}
// 创建 FileReader对象
const reader = new FileReader();
// 把文件读取为字符串
reader.readAsText(e.target.files[0]);
// 文件读取完成
reader.onload = function (ev: any) {
reject(ev);
};
});
};
✅回答我帖子里的一些问题
//file.ts
import createMessage from 'base/Message/index';
// 文件下载
export const downloadFile = (jsonStr: any) => {
// 将数据转换为字符串
jsonStr = JSON.stringify(jsonStr);
// 创建 blob 对象
const blob = new Blob([jsonStr]);
// 创建一个a标签
const el = document.createElement('a');
// 创建一个 URL 对象并传给 a 的 href
el.href = URL.createObjectURL(blob);
// 设置下载的默认文件名
el.download = '流莺书签数据备份.json';
// 模拟点击链接进行下载
el.click();
};
// 文件上传
export const uploadFile = (e: any) => {
return new Promise((reject) => {
// 如果没有选择文件就什么也不做
if (e.target.value === '' || e.target.files.length < 1) {
return;
}
// 如果不是json格式的文件,给出提示
if (e.target.files[0].type !== 'application/json') {
createMessage('请上传由本站导出的json格式的文件 !');
return;
}
// 创建 FileReader对象
const reader = new FileReader();
// 把文件读取为字符串
reader.readAsText(e.target.files[0]);
// 文件读取完成
reader.onload = function (ev: any) {
reject(ev);
};
});
};
✅认为一些交互和功能需要修复,寻找BUG
✅我想增加新的功能,并且对设计和外观有更好的建议
最后感谢大家的耐心观看。当我们都在这里时,点击“赞”然后出发
介绍链接
?项目查看地址(GitHub页面):??alanhzw.github.io
?备用地址(私服):??warbler.duwanyu.com
?源码地址(gitee):??gitee.com/hzw_0174/wa…
?源码地址(github):??github.com/alanhzw/War…
❀?流影书签-从零开始搭建一个Vite+Vue3+Ts项目:??juejin.cn/post/695130…?流英信-关键要素介绍(格式、介绍):??juejin.cn/post/696393…
?流影书签关键元素(Button、Overlay、Dialog、Message):??juejin.cn/post/696394…?流影书签-公司介绍:??没有
?我的博客:??www.duwanyu.com
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。