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

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

文章说明

本篇文章主要是我写的一个小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 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
CodeClimb6 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
沈梦研6 小时前
【Vscode】Vscode不能执行vue脚本的原因及解决方法
ide·vue.js·vscode
hunter2062066 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb6 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角6 小时前
CSS 颜色
前端·css
轻口味6 小时前
Vue.js 组件之间的通信模式
vue.js
九酒6 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔7 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm