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

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

文章说明

本篇文章主要是我写的一个小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

源码下载

网址访问小工具

相关推荐
永乐春秋1 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿1 小时前
【前端】CSS
前端·css
ggdpzhk1 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
小曲曲2 小时前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS4 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜5 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点5 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow5 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o5 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app