网址访问小工具(模拟浏览器)

server/2024/11/28 6:13:00/

网址访问小工具(模拟浏览器)

    • 文章说明
    • 核心代码
    • 运行截图
    • 源码下载

文章说明

本篇文章主要是我写的一个小demo,感觉效果还蛮不错的,作为一个记录新想法的实现思路;介绍了模拟浏览器页面的一些页面实现的小细节。

采用vue3结合electron设计的简单的网址访问小工具, 类似一个简单版的浏览器, 只不过有一些网页打不开, 然后目前提示信息功能做的还不是很完善, 等待后续继续补充

目前最大化、最小化、关闭按钮功能还没完善好; 同时部分网页无法在iframe中打开, 具体原因还未查明; 目前url输入框也还未添加记忆功能, 等待后续完善

在Tab栏中样式没有调整的很精细, 那个底部的弧形边框还没有很好的实现思路

核心代码

App.vue(Tab栏和页面展示)

<script setup>javascript">
import {reactive} from "vue";
import SinglePage from "@/components/SinglePage.vue";let pageId = 1;const data = reactive({pages: [{id: pageId,name: "新建标签页",}],currentPage: pageId,status: "normal",
});function addTab() {pageId++;data.pages.push({id: pageId,name: "新建标签页",});data.currentPage = data.pages[data.pages.length - 1].id;
}function closeTab() {for (let i = 0; i < data.pages.length; i++) {if (data.pages[i].id === data.currentPage) {data.pages.splice(i, 1);break;}}if (data.pages.length === 0) {addTab();} else {data.currentPage = data.pages[data.pages.length - 1].id;}
}function toTab(item) {data.currentPage = item.id;
}function closeWindow() {if (confirm("确认关闭吗?")) {alert("关闭窗体");}
}function maximize() {document.documentElement.requestFullscreen().then(() => {data.status = "maximized";}).catch((err) => {alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);});
}function normal() {if (document.fullscreenElement) {document.exitFullscreen().then(() => {data.status = "normal";}).catch((err) => {alert(`Error attempting to exit full-screen mode: ${err.message} (${err.name})`);});}
}function minimize() {}
</script><template><div class="container" @contextmenu.prevent><div class="header"><div class="tab-container"><i class="iconfont icon-work"></i><template v-for="item in data.pages" :key="item.id"><div :class="item.id === data.currentPage ? ' current-tab-item ' : ''" class="tab-item" @click="toTab(item)"><div style="flex: 1">{{ item.name }}</div><i class="iconfont icon-close" @click.stop="closeTab"></i></div></template><i class="iconfont icon-add" @click="addTab"></i></div><div class="icon-container"><i class="iconfont icon-minimize" @click="minimize"></i><i v-show="data.status === 'normal'" class="iconfont icon-maximize" @click="maximize"></i><i v-show="data.status !== 'normal'" class="iconfont icon-maximized" @click="normal"></i><i class="iconfont icon-close" @click="closeWindow"></i></div></div><template v-for="item in data.pages" :key="item.id"><div v-show="data.currentPage === item.id" style="flex: 1; overflow: hidden"><SinglePage/></div></template></div>
</template><style lang="scss">
@import "@/css/iconfont.css";* {padding: 0;margin: 0;box-sizing: border-box;
}.container {display: flex;flex-direction: column;height: 100vh;width: 100vw;.header {height: 35px;width: 100%;background-color: #cdcdcd;display: flex;align-items: center;.tab-container {flex: 1;display: flex;align-items: center;.icon-work, .icon-add {height: 35px;width: 35px;color: black;font-size: 18px;display: flex;align-items: center;justify-content: center;}.icon-work {margin-right: 10px;&:hover {background-color: #bdbdbd;}}.icon-add {height: 30px;width: 30px;margin-left: 10px;&:hover {background-color: #adadad;border-radius: 8px;}}.tab-item {width: 260px;height: 33px;color: #000000;font-size: 12px;background-color: transparent;display: flex;align-items: center;padding: 10px;border-radius: 5px;&:hover {background-color: #dadada;}.icon-close {font-size: 12px;padding: 3px;color: #0d0d0d;&:hover {background-color: #d0d0d0;border-radius: 4px;}}}.current-tab-item {background-color: #f7f7f7;&:hover {background-color: #f7f7f7;}}}.icon-container {width: fit-content;height: 100%;display: flex;.iconfont {width: 40px;height: 35px;display: flex;align-items: center;justify-content: center;font-size: 14px;color: #000000;&:hover {background-color: #b8b8b8;}}.icon-close {&:hover {background-color: #e81123;color: #ffffff;}}}}
}
</style>

页面iframe展示组件

<script setup>javascript">
import {nextTick, onMounted, reactive, ref, watch} from "vue";const data = reactive({urls: [""],current: 0,url: "",loading: false,
});function last() {if (data.current === 0) {return;}data.current--;data.url = data.urls[data.current];inputRef.value.value = data.url;
}function next() {if (data.current === data.urls.length - 1) {return;}data.current++;data.url = data.urls[data.current];inputRef.value.value = data.url;
}const inputRef = ref();
const pageRef = ref();onMounted(() => {nextTick(() => {inputRef.value.focus();});
});watch(() => data.url, () => {data.loading = true;nextTick(() => {const iframe = pageRef.value.getElementsByTagName("iframe")[0];iframe.onload = function () {data.loading = false;}});
});function changeUrl(event) {const url = event.target.value;data.url = url;data.urls.push(url);data.current = data.urls.length - 1;inputRef.value.blur();
}function reload() {if (!data.url) {return;}data.url = data.urls[data.current] + "&time" + Date.now();
}
</script><template><div class="page-container" @contextmenu.prevent ref="pageRef"><div class="url-input"><i :class="data.urls.length <= 0 || data.current === 0 ? ' gray-iconfont ' : ''" class="iconfont icon-last"@click="last"></i><i :class="data.urls.length <= 0 || data.current === data.urls.length - 1 ? ' gray-iconfont ' : ''"class="iconfont icon-next" @click="next"></i><i class="iconfont icon-refresh" @click="reload"></i><input ref="inputRef" spellcheck="false" @change="changeUrl($event)"/></div><div class="iframe-container"><img v-show="!data.url" :src="require('@/css/background.png')" alt=""/><iframe v-show="data.url && !data.loading" :src="data.url"></iframe><img v-show="data.loading" :src="require('@/css/loading.gif')" alt="" class="loading"/></div></div>
</template><style lang="scss" scoped>
@import "@/css/iconfont.css";.page-container {display: flex;flex-direction: column;height: 100%;width: 100%;.url-input {height: 40px;width: 100%;background-color: #f7f7f7;display: flex;align-items: center;padding-left: 6px;.iconfont {width: 40px;height: 30px;display: flex;align-items: center;justify-content: center;font-size: 20px;color: #000000;&:hover {background-color: #e4e4e4;border-radius: 5px;}}.gray-iconfont {color: #cccccc;}.icon-next {transform: rotate(180deg);}input {width: 1300px;height: 28px;border-radius: 30px;border: 1px solid #d1d1d1;outline: none;font-size: 14px;padding: 0 14px;color: #270057;margin-left: 15px;&:focus {border: 1px solid #2169eb;}}}.iframe-container {width: 100%;flex: 1;display: flex;align-items: center;justify-content: center;overflow: hidden;img {width: 100%;height: 100%;}.loading {width: 100px;height: 100px;}iframe {border: none;outline: none;width: 100%;height: 100%;}}
}
</style>

运行截图

默认首页
在这里插入图片描述

访问页面
在这里插入图片描述

开启新Tab
在这里插入图片描述

源码下载

网址访问小工具


http://www.ppmy.cn/server/137726.html

相关文章

如何为STM32的EXTI(外部中断)编写程序

要为STM32的EXTI&#xff08;外部中断&#xff09;编写程序&#xff0c;你需要遵循以下步骤&#xff1a; 1. 初始化GPIO 首先&#xff0c;需要初始化连接到外部中断线的GPIO引脚。这个引脚需要配置为输入模式&#xff0c;并且根据需要选择上拉、下拉或浮空。 GPIO_InitTypeDe…

C语言——八股文(笔试面试题)——持续更新

目录 更新日历&#xff1a; 1、 什么是数组指针&#xff0c;什么是指针数组&#xff1f; 2、 什么是位段&#xff0c;什么是联合体 3、 什么是递归&#xff0c;什么是回调&#xff1f; 4、 什么是越界&#xff0c;什么是溢出&#xff1f; 5、#define和typedef的区别&#x…

Axure文本框读取和赋值高级交互

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;文本框赋值 主要内容&#xff1a;文本、文本域、时间等类型文本框读取和赋值、下拉列表读取和赋值 应用场景&#xff1a;新增数据、读取数据、修改…

【周末推荐】Windows无缝连接iPhone

关注“ONE生产力”&#xff0c;获取更多精彩推荐&#xff01; 又到了周末推荐时间了&#xff0c;今天我们介绍一个Windows内置的功能&#xff0c;能够帮助大家将自己的电脑和iPhone连接在一起。 很多用Windows的小伙伴羡慕macOS可以和iPhone无缝连接&#xff0c;轻松阅读和回…

从头开始学PHP之面向对象

首先介绍下最近情况&#xff0c;因为最近入职了且通勤距离较远&#xff0c;导致精力不够了&#xff0c;而且我发现&#xff0c;人一旦上了班&#xff0c;下班之后就不想再进行任何脑力劳动了&#xff08;对大部分牛马来说&#xff0c;精英除外&#xff09;。 话不多说进入今天的…

第12课 编码与数制

二进制起源于中国。上古奇书《易经》中就使用到了二进制。《易经》长期用来“卜筮”&#xff0c;对事态未来发展进行预测&#xff0c;俗称“算卦”。通过阳爻&#xff08;—&#xff09;和阴爻&#xff08;–&#xff09;说明天地、日月、人生、事物之间的变化法则&#xff0c;…

BGP路由优选+EVPN

BGP 的路由优选规则是一套多步决策链&#xff0c;用来确定在多个可行路由中选择最优的路由。BGP 是一种路径向量协议&#xff0c;通过这些优选规则&#xff0c;网络管理员可以控制数据流量的流向&#xff0c;确保网络的稳定性和效率。下面以一个实例来详细说明 BGP 的优选规则及…

文理学院数据库技术应用实验报告8

文理学院数据库技术应用实验报告8 实验名称数据聚合查询和分组查询实验日期2024年11月1日课程名称数据库技术应用实验项目数据聚合查询和分组查询 一、实验目的 聚合函数&#xff08;max、min、avg、sum、count&#xff09;分组查询&#xff08;group by子句、having子句&am…