当前ip

经验创意 · 180 次浏览
困困君 创建于 2026-05-20 19:35

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
  <meta name="color-scheme" content="dark" />
  <title>当前 IP 信息</title>
  <style>
    :root{
      --txt:#eaf2ff;
      --muted:rgba(234,242,255,.68);
      --muted2:rgba(234,242,255,.46);
      --line:rgba(160,200,255,.18);
      --accent:#82b7ff;
      --good:#39d98a;
      --warn:#ffb347;
      --bad:#ff5d5d;
      --shadow:0 14px 34px rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.05);
    }

    html, body{
      margin:0;
      width:100%;
      height:100%;
      overflow:hidden;
      background:transparent;
      font-family:"Microsoft YaHei", sans-serif;
      -webkit-font-smoothing:antialiased;
      text-rendering:optimizeLegibility;
    }

    *{box-sizing:border-box}
    button{font:inherit}

    .card{
      width:100%;
      height:100%;
      position:relative;
      overflow:hidden;
      border-radius:18px;
      border:1px solid var(--line);
      background:
        radial-gradient(120% 100% at 0% 0%, rgba(130,183,255,.14), transparent 58%),
        radial-gradient(90% 120% at 100% 0%, rgba(167,139,250,.10), transparent 50%),
        linear-gradient(160deg, rgba(15,25,39,.98) 0%, rgba(10,17,28,.98) 100%);
      box-shadow:var(--shadow);
      color:var(--txt);
      padding:14px 14px 12px;
      display:flex;
      flex-direction:column;
      gap:10px;
    }

    .card::before{
      content:"";
      position:absolute;
      inset:0;
      border-radius:18px;
      padding:1px;
      background:linear-gradient(180deg, rgba(255,255,255,.13), rgba(255,255,255,0) 26%, rgba(255,255,255,0) 80%, rgba(255,255,255,.07));
      -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
      -webkit-mask-composite:xor;
      mask-composite:exclude;
      pointer-events:none;
      opacity:.9;
    }

    .card::after{
      content:"";
      position:absolute;
      inset:-2px;
      border-radius:20px;
      background:linear-gradient(135deg, rgba(255,255,255,.06), transparent 28%, transparent 72%, rgba(255,255,255,.04));
      pointer-events:none;
      mix-blend-mode:screen;
      opacity:.55;
    }

    .topbar{
      position:relative;
      z-index:1;
      display:flex;
      align-items:flex-start;
      justify-content:space-between;
      gap:10px;
      min-height:34px;
    }

    .title-wrap{
      min-width:0;
      display:flex;
      flex-direction:column;
      gap:4px;
      flex:1 1 auto;
    }

    .title{
      margin:0;
      font-size:15px;
      line-height:1.1;
      font-weight:700;
      letter-spacing:.2px;
      color:var(--txt);
      user-select:none;
    }

    .title .sub{
      font-weight:600;
      color:var(--muted);
      margin-left:6px;
      font-size:12px;
    }

    .actions{
      display:flex;
      align-items:center;
      gap:8px;
      flex:0 0 auto;
    }

    .btn{
      appearance:none;
      outline:none;
      height:28px;
      padding:0 11px;
      border-radius:10px;
      color:var(--txt);
      background:linear-gradient(180deg, rgba(130,183,255,.16), rgba(130,183,255,.08));
      border:1px solid rgba(130,183,255,.22);
      box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
      cursor:pointer;
      transition:transform .12s ease, background .12s ease, border-color .12s ease, opacity .12s ease, box-shadow .12s ease;
      font-size:12px;
      font-weight:700;
      letter-spacing:.2px;
      user-select:none;
      white-space:nowrap;
    }

    .btn:hover{
      background:linear-gradient(180deg, rgba(130,183,255,.22), rgba(130,183,255,.12));
      border-color:rgba(130,183,255,.34);
      transform:translateY(-1px);
      box-shadow:inset 0 1px 0 rgba(255,255,255,.10);
    }

    .btn:active{
      transform:translateY(0);
      opacity:.92;
    }

    .btn.secondary{
      background:rgba(255,255,255,.04);
      border-color:rgba(255,255,255,.09);
      color:var(--muted);
    }

    .btn.secondary:hover{
      background:rgba(255,255,255,.07);
      border-color:rgba(160,200,255,.14);
      color:var(--txt);
    }

    .btn[disabled]{
      cursor:not-allowed;
      opacity:.58;
      transform:none;
    }

    .content{
      position:relative;
      z-index:1;
      flex:1 1 auto;
      min-height:0;
      display:grid;
      grid-template-columns:104px minmax(0,1fr);
      gap:12px;
      align-items:center;
    }

    .scorebox{
      width:104px;
      height:104px;
      border-radius:22px;
      position:relative;
      display:flex;
      align-items:center;
      justify-content:center;
      background:
        radial-gradient(circle at 50% 28%, rgba(255,255,255,.08), transparent 38%),
        linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
      border:1px solid rgba(255,255,255,.08);
      box-shadow:inset 0 1px 0 rgba(255,255,255,.06);
      overflow:hidden;
    }

    .ring{
      width:80px;
      height:80px;
      border-radius:50%;
      background:
        radial-gradient(circle at center, rgba(9,17,28,.98) 0 46%, transparent 47%),
        conic-gradient(var(--ring-color) var(--ring-angle), rgba(255,255,255,.08) 0);
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
      box-shadow:0 0 0 1px rgba(255,255,255,.05) inset;
    }

    .ring::before{
      content:"";
      position:absolute;
      inset:6px;
      border-radius:50%;
      border:1px solid rgba(255,255,255,.06);
      box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
    }

    .score-inner{
      position:relative;
      z-index:1;
      text-align:center;
      line-height:1;
    }

    .score{
      font-size:24px;
      font-weight:800;
      letter-spacing:-.6px;
      color:var(--ring-color);
    }

    .score-unit{
      display:block;
      margin-top:4px;
      font-size:10px;
      color:var(--muted2);
      letter-spacing:.8px;
    }

    .score-tag{
      position:absolute;
      bottom:7px;
      left:50%;
      transform:translateX(-50%);
      font-size:10px;
      color:var(--muted2);
      letter-spacing:.4px;
      white-space:nowrap;
    }

    .info{
      min-width:0;
      display:flex;
      flex-direction:column;
      gap:7px;
    }

    .row{
      display:flex;
      align-items:flex-start;
      gap:8px;
      min-width:0;
    }

    .label{
      flex:0 0 auto;
      width:42px;
      color:var(--muted2);
      font-size:11px;
      line-height:18px;
      letter-spacing:.2px;
    }

    .value{
      min-width:0;
      flex:1 1 auto;
      color:var(--txt);
      font-size:12px;
      line-height:18px;
      word-break:break-all;
    }

    .value.muted{color:var(--muted)}

    .chips{
      display:flex;
      flex-wrap:wrap;
      gap:6px;
      margin-top:1px;
      max-height:50px;
      overflow:auto;
      padding-right:2px;
    }

    .chip{
      height:22px;
      padding:0 8px;
      display:inline-flex;
      align-items:center;
      border-radius:999px;
      font-size:11px;
      border:1px solid rgba(255,255,255,.08);
      background:rgba(255,255,255,.04);
      color:var(--muted);
      white-space:nowrap;
    }

    .chip.good{
      color:#bff6de;
      border-color:rgba(57,217,138,.2);
      background:rgba(57,217,138,.08);
    }

    .chip.warn{
      color:#ffe0b3;
      border-color:rgba(255,179,71,.2);
      background:rgba(255,179,71,.08);
    }

    .chip.bad{
      color:#ffd0d0;
      border-color:rgba(255,93,93,.2);
      background:rgba(255,93,93,.08);
    }

    .bottom{
      position:relative;
      z-index:1;
      display:flex;
      justify-content:space-between;
      align-items:center;
      gap:10px;
      min-height:28px;
    }

    .status{
      min-width:0;
      display:flex;
      flex-direction:column;
      gap:2px;
      justify-content:center;
    }

    .status .line1{
      font-size:11px;
      color:var(--muted);
      line-height:1.1;
      white-space:nowrap;
      overflow:hidden;
      text-overflow:ellipsis;
    }

    .status .line2{
      font-size:10px;
      color:var(--muted2);
      line-height:1.1;
      white-space:nowrap;
      overflow:hidden;
      text-overflow:ellipsis;
    }

    .tiny{
      font-size:10px;
      color:var(--muted2);
      white-space:nowrap;
      overflow:hidden;
      text-overflow:ellipsis;
      max-width:42%;
    }

    .loading{
      display:inline-flex;
      align-items:center;
      gap:8px;
    }

    .spinner{
      width:12px;
      height:12px;
      border-radius:50%;
      border:2px solid rgba(255,255,255,.14);
      border-top-color:rgba(130,183,255,.9);
      animation:spin .85s linear infinite;
      flex:0 0 auto;
    }

    @keyframes spin{to{transform:rotate(360deg)}}

    .skeleton{
      position:relative;
      overflow:hidden;
      background:rgba(255,255,255,.05);
      border-radius:8px;
      color:transparent !important;
      min-height:12px;
    }

    .skeleton::after{
      content:"";
      position:absolute;
      inset:0;
      transform:translateX(-100%);
      background:linear-gradient(90deg, transparent, rgba(255,255,255,.12), transparent);
      animation:shimmer 1.2s infinite;
    }

    @keyframes shimmer{
      100%{transform:translateX(100%)}
    }

    .error{color:#ffd0d0 !important}

    ::-webkit-scrollbar{
      width:6px;
      height:6px;
    }

    ::-webkit-scrollbar-track{
      background:rgba(255,255,255,.035);
      border-radius:999px;
    }

    ::-webkit-scrollbar-thumb{
      background:rgba(130,183,255,.25);
      border-radius:999px;
    }

    ::-webkit-scrollbar-thumb:hover{
      background:rgba(130,183,255,.38);
    }

    @media (max-width:330px){
      .card{padding:12px 12px 10px}
      .content{grid-template-columns:94px minmax(0,1fr)}
      .scorebox{width:94px;height:94px;border-radius:20px}
      .ring{width:74px;height:74px}
      .score{font-size:22px}
      .label{width:38px}
      .actions{gap:6px}
      .btn{padding:0 9px}
    }
  </style>
</head>
<body>
  <div class="card" id="app">
    <div class="topbar">
      <div class="title-wrap">
        <div class="title">当前 IP<span class="sub">网络信息</span></div>
      </div>
      <div class="actions">
        <button class="btn secondary" id="copyBtn" type="button">复制IP</button>
        <button class="btn" id="refreshBtn" type="button">刷新</button>
      </div>
    </div>

    <div class="content">
      <div class="scorebox">
        <div class="ring" id="ring" style="--ring-angle:0deg;--ring-color:var(--accent)">
          <div class="score-inner">
            <div class="score" id="scoreText">--</div>
            <span class="score-unit">INFO</span>
          </div>
        </div>
        <div class="score-tag" id="riskTag">网络类型</div>
      </div>

      <div class="info">
        <div class="row">
          <div class="label">IP</div>
          <div class="value" id="ipText">正在获取…</div>
        </div>
        <div class="row">
          <div class="label">位置</div>
          <div class="value muted" id="locationText">正在获取…</div>
        </div>
        <div class="row">
          <div class="label">ISP</div>
          <div class="value muted" id="ispText">正在获取…</div>
        </div>
        <div class="row">
          <div class="label">信息</div>
          <div class="chips" id="chips"></div>
        </div>
      </div>
    </div>

    <div class="bottom">
      <div class="status">
        <div class="line1" id="statusLine">
          <span class="loading"><span class="spinner"></span><span>正在拉取当前 IP 数据</span></span>
        </div>
        <div class="line2" id="subStatus">宿主请求失败时会回退到缓存。</div>
      </div>
      <div class="tiny" id="updatedAt">--</div>
    </div>
  </div>

  <script>
    (() => {
      const STORE_KEY = "fudao.current.ip.info.cache.v1";
      const IP_API_URL = "http://ip-api.com/json/?lang=zh-CN";

      const $ = (id) => document.getElementById(id);

      const state = {
        loading: false,
        data: null,
        updatedAt: "",
        hostReady: false
      };

      function nowText(ts = Date.now()) {
        const d = new Date(ts);
        const pad = (n) => String(n).padStart(2, "0");
        return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
      }

      function clamp(n, min, max) {
        return Math.max(min, Math.min(max, n));
      }

      function safeText(v, fallback = "未知") {
        if (v === null || v === undefined) return fallback;
        const s = String(v).trim();
        return s ? s : fallback;
      }

      function escapeHTML(str) {
        return String(str).replace(/[&<>"']/g, (m) => ({
          "&":"&amp;",
          "<":"&lt;",
          ">":"&gt;",
          "\"":"&quot;",
          "'":"&#39;"
        }[m]));
      }

      function normalizeStateValue(res) {
        if (res == null) return null;
        if (typeof res === "string") return res;
        if (typeof res === "object") {
          if (typeof res.value === "string") return res.value;
          if (typeof res.text === "string") return res.text;
          if (typeof res.data === "string") return res.data;
          if (typeof res.result === "string") return res.result;
          try { return JSON.stringify(res); } catch (_) { return null; }
        }
        return String(res);
      }

      function parseJSONMaybe(raw) {
        if (raw == null) return null;
        if (typeof raw === "object") return raw;
        if (typeof raw === "string") {
          try { return JSON.parse(raw); } catch (_) { return null; }
        }
        return null;
      }

      function loadLocal() {
        try {
          const raw = localStorage.getItem(STORE_KEY);
          return raw ? parseJSONMaybe(raw) : null;
        } catch (_) {
          return null;
        }
      }

      function saveLocal(data) {
        try {
          localStorage.setItem(STORE_KEY, JSON.stringify(data));
        } catch (_) {}
      }

      async function invokeYanm(method, args) {
        if (!window.yanm || typeof window.yanm.invoke !== "function") {
          throw new Error("宿主未就绪");
        }
        const ret = window.yanm.invoke(method, args || {});
        return ret && typeof ret.then === "function" ? await ret : ret;
      }

      async function readHostCache() {
        try {
          const res = await invokeYanm("state.read", {
            key: STORE_KEY,
            defaultValue: ""
          });
          return parseJSONMaybe(normalizeStateValue(res));
        } catch (_) {
          return null;
        }
      }

      async function writeHostCache(data) {
        try {
          await invokeYanm("state.write", {
            key: STORE_KEY,
            value: JSON.stringify(data)
          });
        } catch (_) {}
      }

      async function persist(data) {
        saveLocal(data);
        await writeHostCache(data);
      }

      async function getTextByHttp(url, timeoutMs = 10000) {
        const res = await invokeYanm("http.get", {
          url,
          headers: {
            "Accept": "application/json,text/plain,*/*"
          },
          timeoutMs
        });

        if (typeof res === "string") return res;

        if (res && typeof res === "object") {
          if (res.ok === false) {
            throw new Error(res.error || res.message || ("HTTP " + (res.status || "ERR")));
          }
          if (typeof res.text === "string") return res.text;
          if (typeof res.content === "string") return res.content;
          if (typeof res.body === "string") return res.body;
          if (typeof res.data === "string") return res.data;
          return JSON.stringify(res);
        }

        return String(res || "");
      }

      async function getJsonByHttp(url, timeoutMs = 10000) {
        const text = await getTextByHttp(url, timeoutMs);
        try {
          return JSON.parse(text);
        } catch (_) {
          throw new Error("返回内容不是有效 JSON");
        }
      }

      function analyzeNetwork(isp, org, asText) {
        const text = `${safeText(isp, "")} ${safeText(org, "")} ${safeText(asText, "")}`.toLowerCase();

        const homeKeywords = [
          "电信", "移动", "联通", "宽带", "china mobile", "china unicom", "chinanet",
          "telecom", "unicom", "comcast", "verizon", "spectrum", "cox", "frontier",
          "optimum", "sky", "virgin media", "bt ", "broadband", "fiber", "cable"
        ];

        const cloudKeywords = [
          "vps", "vpn", "proxy", "hosting", "cloud", "server", "datacenter", "data center",
          "colo", "colocation", "dedicated", "aws", "amazon", "google", "microsoft", "azure",
          "digitalocean", "linode", "ovh", "hetzner", "vultr", "contabo", "leaseweb",
          "alibaba", "tencent", "oracle", "cloudflare"
        ];

        const mobileKeywords = [
          "mobile", "wireless", "cellular", "lte", "5g", "4g"
        ];

        const isCloud = cloudKeywords.some(k => text.includes(k.toLowerCase()));
        const isHome = homeKeywords.some(k => text.includes(k.toLowerCase()));
        const isMobile = mobileKeywords.some(k => text.includes(k.toLowerCase()));

        let score = 52;
        if (isHome) score = 82;
        if (isMobile) score = 70;
        if (isCloud) score = 34;
        if (/vpn|proxy|datacenter|hosting|cloud|server/i.test(text)) score -= 12;

        score = clamp(score, 0, 100);

        let type = "普通网络";
        if (isCloud) type = "机房 / 云服务";
        else if (isMobile) type = "移动网络";
        else if (isHome) type = "家宽 / 运营商";

        return {
          score,
          type,
          isCloud,
          isHome,
          isMobile
        };
      }

      function getColor(score) {
        if (score >= 75) return "#39d98a";
        if (score >= 45) return "#ffb347";
        return "#ff5d5d";
      }

      function renderNullState(message) {
        $("ipText").textContent = "无法获取数据";
        $("locationText").textContent = message || "请检查网络或宿主 http.get 能力";
        $("ispText").textContent = "—";
        $("scoreText").textContent = "--";
        $("scoreText").style.color = "var(--txt)";
        $("ring").style.setProperty("--ring-angle", "0deg");
        $("ring").style.setProperty("--ring-color", "var(--accent)");
        $("riskTag").textContent = "网络类型";
        $("chips").innerHTML = '<span class="chip bad">无有效数据</span>';
        $("updatedAt").textContent = "--";
      }

      function render(payload) {
        if (!payload || !payload.data) {
          renderNullState(payload && payload.message ? payload.message : "");
          return;
        }

        const d = payload.data;
        const score = clamp(Number(d.score || 0), 0, 100);
        const color = getColor(score);

        $("ipText").textContent = safeText(d.ip);
        $("locationText").textContent = safeText(d.location);
        $("ispText").textContent = safeText(d.isp);
        $("scoreText").textContent = String(score);
        $("scoreText").style.color = color;
        $("ring").style.setProperty("--ring-angle", `${score * 3.6}deg`);
        $("ring").style.setProperty("--ring-color", color);
        $("riskTag").textContent = safeText(d.type, "网络类型");

        const cloudClass = d.isCloud ? "warn" : "good";
        const homeClass = d.isHome ? "good" : "warn";
        const mobileClass = d.isMobile ? "warn" : "";

        $("chips").innerHTML = [
          `<span class="chip ${homeClass}">${d.isHome ? "疑似家宽" : "非典型家宽"}</span>`,
          `<span class="chip ${cloudClass}">${d.isCloud ? "疑似机房" : "非明显机房"}</span>`,
          `<span class="chip ${mobileClass}">${d.isMobile ? "移动网络" : "固定网络"}</span>`
        ].join("");

        $("updatedAt").textContent = payload.updatedAt ? `更新于 ${payload.updatedAt}` : "--";
      }

      function setLoading(yes) {
        state.loading = yes;
        $("refreshBtn").disabled = yes;

        $("statusLine").innerHTML = yes
          ? '<span class="loading"><span class="spinner"></span><span>正在拉取当前 IP 数据</span></span>'
          : '<span>就绪</span>';

        ["ipText", "locationText", "ispText", "scoreText"].forEach((id) => {
          const node = $(id);
          if (node) node.classList.toggle("skeleton", yes);
        });
      }

      function setStatusError(message) {
        $("statusLine").innerHTML = `<span class="error">${escapeHTML(message)}</span>`;
      }

      async function refresh() {
        if (state.loading) return;

        if (!window.yanm || typeof window.yanm.invoke !== "function") {
          const cached = loadLocal();
          if (cached && cached.data) {
            state.data = cached.data;
            state.updatedAt = cached.updatedAt || "";
            render(cached);
            setStatusError("宿主未就绪,当前显示本地缓存");
          } else {
            renderNullState("等待浮岛宿主注入 window.yanm");
            setStatusError("宿主未就绪");
          }
          return;
        }

        setLoading(true);
        $("subStatus").textContent = "正在通过浮岛宿主 http.get 获取公网 IP 信息。";

        try {
          const ipInfo = await getJsonByHttp(IP_API_URL, 10000);

          if (!ipInfo || ipInfo.status !== "success") {
            throw new Error(ipInfo && ipInfo.message ? String(ipInfo.message) : "IP 接口返回失败");
          }

          const ip = safeText(ipInfo.query, "");
          if (!ip) throw new Error("IP 为空");

          const country = safeText(ipInfo.country, "未知");
          const regionName = safeText(ipInfo.regionName, "未知");
          const city = safeText(ipInfo.city, "");
          const isp = safeText(ipInfo.isp, "未知");
          const org = safeText(ipInfo.org, "");
          const asText = safeText(ipInfo.as, "");

          const network = analyzeNetwork(isp, org, asText);

          const payload = {
            data: {
              ip: ip,
              location: [country, regionName, city].filter(Boolean).join(" "),
              isp: isp,
              org: org,
              as: asText,
              timezone: safeText(ipInfo.timezone, ""),
              score: network.score,
              type: network.type,
              isCloud: network.isCloud,
              isHome: network.isHome,
              isMobile: network.isMobile
            },
            updatedAt: nowText(),
            source: "ip-api.com"
          };

          state.data = payload.data;
          state.updatedAt = payload.updatedAt;

          render(payload);
          await persist(payload);

          $("statusLine").innerHTML = "<span>当前 IP 信息已更新</span>";
          $("subStatus").textContent = payload.data.timezone ? `时区:${payload.data.timezone}` : "数据来自当前公网出口。";
        } catch (err) {
          const msg = err && err.message ? err.message : "未知错误";
          const cached = loadLocal();

          if (cached && cached.data) {
            state.data = cached.data;
            state.updatedAt = cached.updatedAt || "";
            render(cached);
            setStatusError("刷新失败,已回退到缓存");
            $("subStatus").textContent = msg;
          } else {
            renderNullState(`获取失败:${msg}`);
            setStatusError(`刷新失败:${msg}`);
            $("subStatus").textContent = "请检查网络、接口或宿主 http.get 能力。";
          }
        } finally {
          setLoading(false);
        }
      }

      async function copyIp() {
        const ip = state.data && state.data.ip ? state.data.ip : "";
        if (!ip) {
          setStatusError("没有可复制的 IP");
          return;
        }

        try {
          if (window.yanm && typeof window.yanm.invoke === "function") {
            await invokeYanm("clipboard.write", { text: ip });
          } else if (navigator.clipboard && navigator.clipboard.writeText) {
            await navigator.clipboard.writeText(ip);
          } else {
            throw new Error("剪贴板不可用");
          }
          $("statusLine").innerHTML = "<span>IP 已复制到剪贴板</span>";
        } catch (_) {
          setStatusError("复制失败");
        }
      }

      async function hydrateFromCache() {
        const local = loadLocal();
        if (local && local.data) {
          state.data = local.data;
          state.updatedAt = local.updatedAt || "";
          render(local);
          $("statusLine").innerHTML = "<span>已载入本地缓存</span>";
        }

        if (window.yanm && typeof window.yanm.invoke === "function") {
          const host = await readHostCache();
          if (host && host.data) {
            state.data = host.data;
            state.updatedAt = host.updatedAt || "";
            render(host);
            saveLocal(host);
            $("statusLine").innerHTML = "<span>已载入宿主缓存</span>";
          }
        }
      }

      function waitForHost() {
        if (window.yanm && typeof window.yanm.invoke === "function") {
          state.hostReady = true;
          hydrateFromCache().then(refresh);
          return;
        }

        setTimeout(waitForHost, 250);
      }

      function initFallback() {
        $("ipText").textContent = "正在获取…";
        $("locationText").textContent = "正在获取…";
        $("ispText").textContent = "正在获取…";
        $("scoreText").textContent = "--";
        $("riskTag").textContent = "网络类型";
        $("chips").innerHTML = `
          <span class="chip">等待数据</span>
          <span class="chip">等待数据</span>
          <span class="chip">等待数据</span>
        `;
        $("updatedAt").textContent = "--";
        $("statusLine").innerHTML = '<span class="loading"><span class="spinner"></span><span>正在等待浮岛宿主</span></span>';
        $("subStatus").textContent = "第一屏先显示本地兜底,宿主就绪后自动刷新。";
      }

      function bindUI() {
        $("refreshBtn").addEventListener("click", refresh);
        $("copyBtn").addEventListener("click", copyIp);

        window.addEventListener("keydown", (e) => {
          if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "r") {
            e.preventDefault();
            refresh();
          }
          if (e.key === "F5") {
            e.preventDefault();
            refresh();
          }
        });
      }

      function boot() {
        initFallback();
        bindUI();
        hydrateFromCache();
        setTimeout(waitForHost, 100);
      }

      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", boot, { once:true });
      } else {
        boot();
      }
    })();
  </script>
</body>
</html>


回复内容

为啥后面那个框显示的有问题呀?

多了一个 <标题 出来

我的梦想捐钱修路建学校 最后更新于 2026-05-20 19:41

你更新了吗?更新动作到最新。

解决了 原来更新完要重启QK 只是关闭动作还不行

右上角关闭动作,你点击空白处只是隐藏。动作介绍说了。

是的 我用的就是右上角关闭动作 尝试过几次 故障一样 最终是重启QK

重启也是好办法。

回复主贴