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

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

文章说明

本篇文章主要是我写的一个小demo,感觉效果还蛮不错的,作为一个记录新想法的实现思路;介绍了模拟浏览器页面的一些页面实现的小细节。
采用vue3结合electron设计的简单的网址访问小工具, 类似一个简单版的浏览器, 只不过有一些网页打不开, 然后目前提示信息功能做的还不是很完善, 等待后续继续补充
目前最大化、最小化、关闭按钮功能还没完善好; 同时部分网页无法在iframe中打开, 具体原因还未查明; 目前url输入框也还未添加记忆功能, 等待后续完善
在Tab栏中样式没有调整的很精细, 那个底部的弧形边框还没有很好的实现思路

核心代码

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

html 复制代码
<script setup>
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展示组件

html 复制代码
<script setup>
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

源码下载

网址访问小工具

相关推荐
拾光拾趣录4 分钟前
CSS 深入解析:提升网页样式技巧与常见问题解决方案
前端·css
莫空00005 分钟前
深入理解JavaScript属性描述符:从数据属性到存取器属性
前端·面试
guojl5 分钟前
深度剖析Kafka读写机制
前端
FogLetter6 分钟前
图片懒加载:让网页飞起来的魔法技巧 ✨
前端·javascript·css
Mxuan6 分钟前
vscode webview 插件开发(精装篇)
前端
Mxuan7 分钟前
vscode webview 插件开发(交付篇)
前端
Mxuan9 分钟前
vscode 插件与 electron 应用跳转网页进行登录的实践
前端
拾光拾趣录9 分钟前
JavaScript 加载对浏览器渲染的影响
前端·javascript·浏览器
Codebee9 分钟前
OneCode图表配置速查手册
大数据·前端·数据可视化
然我10 分钟前
React 开发通关指南:用 HTML 的思维写 JS🚀🚀
前端·react.js·html