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

文章说明

核心代码

<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>
<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>

运行截图

源码下载

10-29 05:17