倒计时

经验创意 · 143 次浏览
困困君 创建于 2026-05-21 11:54

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>浮岛倒计时</title>
<style>
html, body {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: transparent;
}

* {
    box-sizing: border-box;
}

body {
    font-family: "Microsoft YaHei", sans-serif;
    color: rgba(245, 248, 255, .94);
}

.card {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    border-radius: 18px;
    overflow: hidden;
    position: relative;
    padding: 14px;
    background:
        radial-gradient(circle at 18% 0%, rgba(88, 178, 255, .20), transparent 38%),
        radial-gradient(circle at 90% 15%, rgba(144, 104, 255, .18), transparent 35%),
        linear-gradient(145deg, rgba(18, 24, 38, .96), rgba(8, 13, 23, .97));
    border: 1px solid rgba(144, 190, 255, .24);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .08),
        inset 0 -1px 0 rgba(0, 0, 0, .28);
}

.card::before {
    content: "";
    position: absolute;
    left: 14px;
    right: 14px;
    top: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(205, 230, 255, .34), transparent);
    pointer-events: none;
}

.header {
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
}

.title {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 9px;
}

.logo {
    width: 22px;
    height: 22px;
    border-radius: 9px;
    background:
        radial-gradient(circle at 35% 28%, rgba(255,255,255,.78), transparent 16%),
        conic-gradient(from 210deg, #6bd6ff, #807dff, #7fffd4, #6bd6ff);
    box-shadow: 0 0 18px rgba(99, 174, 255, .22);
    flex: 0 0 auto;
}

.title-text {
    font-size: 15px;
    font-weight: 700;
    letter-spacing: .5px;
    white-space: nowrap;
}

.status {
    font-size: 11px;
    color: rgba(209, 225, 255, .58);
    white-space: nowrap;
    margin-top: 2px;
    max-width: 210px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.close-btn {
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: 10px;
    font-size: 16px;
    line-height: 26px;
}

.panel {
    height: calc(100% - 42px);
    display: grid;
    grid-template-columns: 1fr 190px;
    gap: 12px;
    min-height: 0;
}

.timer-area {
    min-width: 0;
    min-height: 0;
    border-radius: 15px;
    border: 1px solid rgba(150, 190, 255, .16);
    background:
        linear-gradient(180deg, rgba(255,255,255,.045), rgba(255,255,255,.023)),
        rgba(4, 9, 17, .35);
    overflow: hidden;
    position: relative;
    padding: 14px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.ring {
    position: relative;
    width: min(176px, 54vw, 55vh);
    height: min(176px, 54vw, 55vh);
    min-width: 132px;
    min-height: 132px;
    margin: 0 auto;
    border-radius: 999px;
    background:
        conic-gradient(rgba(111, 190, 255, .95) var(--progress), rgba(255, 255, 255, .075) 0);
    box-shadow:
        0 0 28px rgba(81, 161, 255, .10),
        inset 0 0 0 1px rgba(255,255,255,.05);
    display: flex;
    align-items: center;
    justify-content: center;
}

.ring::before {
    content: "";
    position: absolute;
    inset: 9px;
    border-radius: inherit;
    background:
        radial-gradient(circle at 45% 28%, rgba(255,255,255,.07), transparent 22%),
        linear-gradient(145deg, rgba(14, 21, 34, .98), rgba(6, 10, 18, .98));
    border: 1px solid rgba(150, 190, 255, .10);
}

.time {
    position: relative;
    z-index: 1;
    font-size: 38px;
    font-weight: 800;
    letter-spacing: 1px;
    font-variant-numeric: tabular-nums;
    text-shadow: 0 0 22px rgba(116, 190, 255, .20);
}

.mode {
    position: absolute;
    z-index: 2;
    left: 0;
    right: 0;
    bottom: 30px;
    text-align: center;
    font-size: 11px;
    color: rgba(212, 230, 255, .50);
}

.quick-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    margin-top: 13px;
    flex-wrap: wrap;
}

.side {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 9px;
}

.box {
    border-radius: 14px;
    border: 1px solid rgba(150, 190, 255, .14);
    background: rgba(4, 9, 17, .30);
    padding: 10px;
    min-width: 0;
}

.label {
    font-size: 11px;
    color: rgba(209, 225, 255, .55);
    margin-bottom: 7px;
}

.inputs {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 7px;
}

.field {
    min-width: 0;
}

.field span {
    display: block;
    text-align: center;
    font-size: 10px;
    color: rgba(209, 225, 255, .38);
    margin-top: 4px;
}

input {
    width: 100%;
    height: 31px;
    border: 1px solid rgba(155, 199, 255, .16);
    border-radius: 10px;
    outline: none;
    color: rgba(244, 248, 255, .94);
    background: rgba(255, 255, 255, .055);
    text-align: center;
    font-family: "Microsoft YaHei", sans-serif;
    font-size: 13px;
    transition: background .16s ease, border-color .16s ease, box-shadow .16s ease;
}

input:hover {
    background: rgba(255, 255, 255, .075);
    border-color: rgba(155, 199, 255, .27);
}

input:focus {
    background: rgba(255, 255, 255, .09);
    border-color: rgba(130, 196, 255, .50);
    box-shadow: 0 0 0 2px rgba(90, 160, 255, .10);
}

button {
    height: 30px;
    padding: 0 11px;
    border: 1px solid rgba(155, 199, 255, .20);
    border-radius: 10px;
    outline: none;
    cursor: pointer;
    color: rgba(241, 247, 255, .92);
    background: rgba(255, 255, 255, .075);
    font-family: "Microsoft YaHei", sans-serif;
    font-size: 12px;
    transition: background .16s ease, border-color .16s ease, transform .12s ease, color .16s ease;
    white-space: nowrap;
}

button:hover {
    background: rgba(109, 170, 255, .17);
    border-color: rgba(158, 204, 255, .38);
    color: #fff;
}

button:active {
    transform: scale(.96);
    background: rgba(95, 148, 230, .23);
}

button.primary {
    background: rgba(76, 155, 255, .20);
    border-color: rgba(120, 190, 255, .34);
}

button.primary:hover {
    background: rgba(76, 155, 255, .30);
    border-color: rgba(150, 210, 255, .50);
}

button.danger:hover {
    background: rgba(255, 104, 104, .16);
    border-color: rgba(255, 142, 142, .38);
}

.btn-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 7px;
}

.btn-row.three {
    grid-template-columns: 1fr 1fr 1fr;
}

.audio-name {
    height: 29px;
    line-height: 27px;
    padding: 0 9px;
    border-radius: 10px;
    border: 1px solid rgba(155, 199, 255, .12);
    color: rgba(218, 232, 255, .62);
    background: rgba(255, 255, 255, .04);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scroll {
    overflow-y: auto;
}

.scroll::-webkit-scrollbar {
    width: 7px;
}

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

.scroll::-webkit-scrollbar-thumb {
    background: rgba(145, 177, 226, .28);
    border-radius: 99px;
}

.scroll::-webkit-scrollbar-thumb:hover {
    background: rgba(160, 200, 255, .42);
}

.toast {
    position: absolute;
    right: 14px;
    bottom: 12px;
    max-width: 220px;
    height: 25px;
    line-height: 23px;
    padding: 0 10px;
    border-radius: 999px;
    border: 1px solid rgba(157, 207, 255, .24);
    background: rgba(12, 20, 34, .90);
    color: rgba(238, 246, 255, .92);
    font-size: 11px;
    opacity: 0;
    transform: translateY(6px);
    transition: opacity .18s ease, transform .18s ease;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
}

.toast.show {
    opacity: 1;
    transform: translateY(0);
}

.flash {
    animation: flashBg .72s ease-in-out infinite alternate;
}

@keyframes flashBg {
    from {
        border-color: rgba(144, 190, 255, .28);
        filter: brightness(1);
    }
    to {
        border-color: rgba(120, 220, 255, .78);
        filter: brightness(1.18);
    }
}

@media (max-width: 430px), (max-height: 270px) {
    .card {
        padding: 11px;
    }

    .header {
        height: 31px;
        margin-bottom: 7px;
    }

    .logo {
        width: 20px;
        height: 20px;
        border-radius: 8px;
    }

    .title-text {
        font-size: 14px;
    }

    .status {
        display: none;
    }

    .panel {
        height: calc(100% - 38px);
        grid-template-columns: 1fr 164px;
        gap: 8px;
    }

    .timer-area {
        padding: 10px;
    }

    .time {
        font-size: 31px;
    }

    .mode {
        bottom: 23px;
    }

    button {
        height: 27px;
        padding: 0 8px;
        font-size: 11px;
    }

    input {
        height: 28px;
    }

    .box {
        padding: 8px;
    }

    .quick-row {
        margin-top: 9px;
        gap: 5px;
    }
}
</style>
</head>
<body>
<div class="card" id="card">
    <div class="header">
        <div class="title">
            <div class="logo"></div>
            <div>
                <div class="title-text">浮岛倒计时</div>
                <div class="status" id="status">本地就绪,等待宿主同步</div>
            </div>
        </div>
        <button id="closeBtn" class="close-btn" type="button" title="隐藏浮岛">×</button>
    </div>

    <div class="panel">
        <div class="timer-area">
            <div class="ring" id="ring" style="--progress: 0%;">
                <div class="time" id="timeText">25:00</div>
                <div class="mode" id="modeText">未开始</div>
            </div>

            <div class="quick-row">
                <button type="button" data-quick="300">5 分钟</button>
                <button type="button" data-quick="900">15 分钟</button>
                <button type="button" data-quick="1500">25 分钟</button>
                <button type="button" data-quick="3600">60 分钟</button>
            </div>
        </div>

        <div class="side scroll">
            <div class="box">
                <div class="label">设置时长</div>
                <div class="inputs">
                    <div class="field">
                        <input id="hourInput" type="number" min="0" max="99" value="0">
                        <span>时</span>
                    </div>
                    <div class="field">
                        <input id="minuteInput" type="number" min="0" max="59" value="25">
                        <span>分</span>
                    </div>
                    <div class="field">
                        <input id="secondInput" type="number" min="0" max="59" value="0">
                        <span>秒</span>
                    </div>
                </div>
            </div>

            <div class="box">
                <div class="label">控制</div>
                <div class="btn-row">
                    <button id="startBtn" class="primary" type="button">开始</button>
                    <button id="pauseBtn" type="button">暂停</button>
                </div>
                <div class="btn-row three" style="margin-top:7px;">
                    <button id="resetBtn" type="button">重置</button>
                    <button id="testBtn" type="button">试听</button>
                    <button id="stopAudioBtn" class="danger" type="button">静音</button>
                </div>
            </div>

            <div class="box">
                <div class="label">结束提醒音频</div>
                <div class="audio-name" id="audioName">未选择音频文件</div>
                <div class="btn-row" style="margin-top:7px;">
                    <button id="selectAudioBtn" type="button">选择音频</button>
                    <button id="clearAudioBtn" class="danger" type="button">清除</button>
                </div>
            </div>
        </div>
    </div>

    <div class="toast" id="toast">已保存</div>
</div>

<audio id="audio" preload="auto"></audio>

<script>
(function () {
    var stateKey = "countdownState";

    var card = document.getElementById("card");
    var statusEl = document.getElementById("status");
    var ring = document.getElementById("ring");
    var timeText = document.getElementById("timeText");
    var modeText = document.getElementById("modeText");
    var toastEl = document.getElementById("toast");

    var hourInput = document.getElementById("hourInput");
    var minuteInput = document.getElementById("minuteInput");
    var secondInput = document.getElementById("secondInput");

    var startBtn = document.getElementById("startBtn");
    var pauseBtn = document.getElementById("pauseBtn");
    var resetBtn = document.getElementById("resetBtn");
    var testBtn = document.getElementById("testBtn");
    var stopAudioBtn = document.getElementById("stopAudioBtn");
    var closeBtn = document.getElementById("closeBtn");

    var selectAudioBtn = document.getElementById("selectAudioBtn");
    var clearAudioBtn = document.getElementById("clearAudioBtn");
    var audioName = document.getElementById("audioName");
    var audio = document.getElementById("audio");

    var fudaoReady = false;
    var isLoading = true;
    var timer = null;
    var saveTimer = null;
    var toastTimer = null;

    var totalSeconds = 25 * 60;
    var remainSeconds = totalSeconds;
    var running = false;
    var endAt = 0;

    var data = {
        hours: 0,
        minutes: 25,
        seconds: 0,
        audioPath: "",
        audioName: ""
    };

    function hasFudao() {
        return !!(window.fudao && typeof window.fudao.invoke === "function");
    }

    function invoke(method, args) {
        if (!hasFudao()) {
            return Promise.reject(new Error("fudao not ready"));
        }
        return window.fudao.invoke(method, args || {});
    }

    function setStatus(text) {
        statusEl.textContent = text;
    }

    function showToast(text) {
        toastEl.textContent = text;
        toastEl.className = "toast show";
        clearTimeout(toastTimer);
        toastTimer = setTimeout(function () {
            toastEl.className = "toast";
        }, 1500);
    }

    function pad(n) {
        n = Math.max(0, Math.floor(n));
        return n < 10 ? "0" + n : "" + n;
    }

    function formatTime(sec) {
        sec = Math.max(0, Math.floor(sec));
        var h = Math.floor(sec / 3600);
        var m = Math.floor((sec % 3600) / 60);
        var s = sec % 60;
        if (h > 0) {
            return pad(h) + ":" + pad(m) + ":" + pad(s);
        }
        return pad(m) + ":" + pad(s);
    }

    function clampNumber(value, min, max) {
        var n = parseInt(value, 10);
        if (isNaN(n)) n = 0;
        if (n < min) n = min;
        if (n > max) n = max;
        return n;
    }

    function getInputSeconds() {
        var h = clampNumber(hourInput.value, 0, 99);
        var m = clampNumber(minuteInput.value, 0, 59);
        var s = clampNumber(secondInput.value, 0, 59);
        var value = h * 3600 + m * 60 + s;
        return value > 0 ? value : 1;
    }

    function syncInputsToData() {
        data.hours = clampNumber(hourInput.value, 0, 99);
        data.minutes = clampNumber(minuteInput.value, 0, 59);
        data.seconds = clampNumber(secondInput.value, 0, 59);

        hourInput.value = data.hours;
        minuteInput.value = data.minutes;
        secondInput.value = data.seconds;
    }

    function applyDurationFromInputs() {
        syncInputsToData();
        totalSeconds = getInputSeconds();
        if (!running) {
            remainSeconds = totalSeconds;
        }
        render();
        scheduleSave();
    }

    function render() {
        timeText.textContent = formatTime(remainSeconds);

        var percent = 0;
        if (totalSeconds > 0) {
            percent = ((totalSeconds - remainSeconds) / totalSeconds) * 100;
        }

        if (percent < 0) percent = 0;
        if (percent > 100) percent = 100;
        ring.style.setProperty("--progress", percent + "%");

        if (running) {
            modeText.textContent = "倒计时进行中";
        } else if (remainSeconds <= 0) {
            modeText.textContent = "时间到";
        } else if (remainSeconds !== totalSeconds) {
            modeText.textContent = "已暂停";
        } else {
            modeText.textContent = "未开始";
        }

        audioName.textContent = data.audioName || "未选择音频文件";
    }

    function startTimer() {
        if (running) return;

        if (remainSeconds <= 0) {
            totalSeconds = getInputSeconds();
            remainSeconds = totalSeconds;
        }

        running = true;
        endAt = Date.now() + remainSeconds * 1000;
        setStatus("倒计时已开始");
        tick();

        clearInterval(timer);
        timer = setInterval(tick, 250);
    }

    function pauseTimer() {
        if (!running) return;

        running = false;
        clearInterval(timer);
        timer = null;
        remainSeconds = Math.max(0, Math.ceil((endAt - Date.now()) / 1000));
        setStatus("已暂停");
        render();
    }

    function resetTimer() {
        running = false;
        clearInterval(timer);
        timer = null;
        stopAudio();
        card.className = "card";
        totalSeconds = getInputSeconds();
        remainSeconds = totalSeconds;
        setStatus("已重置");
        render();
    }

    function tick() {
        remainSeconds = Math.max(0, Math.ceil((endAt - Date.now()) / 1000));
        render();

        if (remainSeconds <= 0) {
            running = false;
            clearInterval(timer);
            timer = null;
            finishTimer();
        }
    }

    function finishTimer() {
        card.className = "card flash";
        setStatus("倒计时结束");
        showToast("时间到");
        playSelectedAudio();

        setTimeout(function () {
            card.className = "card";
        }, 8000);
    }

    function guessMime(path) {
        var lower = (path || "").toLowerCase();
        if (lower.indexOf(".mp3") > -1) return "audio/mpeg";
        if (lower.indexOf(".wav") > -1) return "audio/wav";
        if (lower.indexOf(".ogg") > -1) return "audio/ogg";
        if (lower.indexOf(".m4a") > -1) return "audio/mp4";
        if (lower.indexOf(".aac") > -1) return "audio/aac";
        if (lower.indexOf(".flac") > -1) return "audio/flac";
        return "audio/mpeg";
    }

    function stopAudio() {
        try {
            audio.pause();
            audio.currentTime = 0;
        } catch (e) {
        }
    }

    function playSelectedAudio() {
        stopAudio();

        if (!data.audioPath) {
            beepFallback();
            return;
        }

        if (!fudaoReady) {
            beepFallback();
            return;
        }

        invoke("fs.readBase64", {
            path: data.audioPath
        }).then(function (res) {
            var base64 = "";

            if (typeof res === "string") {
                base64 = res;
            } else if (res && typeof res.base64 === "string") {
                base64 = res.base64;
            } else if (res && typeof res.value === "string") {
                base64 = res.value;
            } else if (res && typeof res.text === "string") {
                base64 = res.text;
            }

            if (!base64) {
                beepFallback();
                return;
            }

            audio.src = "data:" + guessMime(data.audioPath) + ";base64," + base64;
            var p = audio.play();
            if (p && p.catch) {
                p.catch(function () {
                    beepFallback();
                });
            }
        }).catch(function () {
            beepFallback();
        });
    }

    function beepFallback() {
        try {
            var AudioContextClass = window.AudioContext || window.webkitAudioContext;
            if (!AudioContextClass) return;

            var ctx = new AudioContextClass();
            var osc = ctx.createOscillator();
            var gain = ctx.createGain();

            osc.type = "sine";
            osc.frequency.value = 880;
            gain.gain.value = 0.0001;

            osc.connect(gain);
            gain.connect(ctx.destination);

            var now = ctx.currentTime;
            gain.gain.exponentialRampToValueAtTime(0.16, now + 0.03);
            gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.55);

            osc.start(now);
            osc.stop(now + 0.58);
        } catch (e) {
        }
    }

    function getFileName(path) {
        if (!path) return "";
        var p = String(path).replace(/\//g, "\\");
        var parts = p.split("\\");
        return parts[parts.length - 1] || p;
    }

    function extractPathFromSelectResult(res) {
        if (!res) return "";

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

        if (res.path) return res.path;
        if (res.filePath) return res.filePath;
        if (res.fullPath) return res.fullPath;
        if (res.value) return res.value;

        if (res.files && res.files.length) {
            var f = res.files[0];
            if (typeof f === "string") return f;
            return f.path || f.filePath || f.fullPath || f.value || "";
        }

        if (res.selected && res.selected.length) {
            var s = res.selected[0];
            if (typeof s === "string") return s;
            return s.path || s.filePath || s.fullPath || s.value || "";
        }

        return "";
    }

    function selectAudio() {
        if (!fudaoReady) {
            showToast("宿主未就绪");
            return;
        }

        invoke("fs.selectFile", {
            title: "选择倒计时结束音频",
            filter: "音频文件|*.mp3;*.wav;*.m4a;*.aac;*.ogg;*.flac|所有文件|*.*"
        }).then(function (res) {
            var path = extractPathFromSelectResult(res);
            if (!path) {
                showToast("未选择文件");
                return;
            }

            data.audioPath = path;
            data.audioName = getFileName(path);
            render();
            saveState(false);
            showToast("已选择音频");
        }).catch(function () {
            showToast("选择失败");
        });
    }

    function readLocalFallback() {
        try {
            var raw = localStorage.getItem("fudao_countdown_state") || "";
            return raw ? JSON.parse(raw) : null;
        } catch (e) {
            return null;
        }
    }

    function writeLocalFallback() {
        try {
            localStorage.setItem("fudao_countdown_state", JSON.stringify(data));
        } catch (e) {
        }
    }

    function applyData(next) {
        if (!next) return;

        data.hours = clampNumber(next.hours, 0, 99);
        data.minutes = clampNumber(next.minutes, 0, 59);
        data.seconds = clampNumber(next.seconds, 0, 59);
        data.audioPath = next.audioPath || "";
        data.audioName = next.audioName || getFileName(data.audioPath);

        hourInput.value = data.hours;
        minuteInput.value = data.minutes;
        secondInput.value = data.seconds;

        totalSeconds = getInputSeconds();
        remainSeconds = totalSeconds;
        render();
    }

    function loadFallbackFirst() {
        var local = readLocalFallback();
        if (local) {
            applyData(local);
        } else {
            applyData(data);
        }
    }

    function saveState(silent) {
        syncInputsToData();
        writeLocalFallback();

        if (!fudaoReady) {
            if (!silent) showToast("已本地暂存");
            setStatus("已暂存,宿主未就绪");
            return Promise.resolve(false);
        }

        return invoke("state.write", {
            key: stateKey,
            value: JSON.stringify(data)
        }).then(function () {
            if (!silent) showToast("已保存");
            setStatus("设置已保存");
            return true;
        }).catch(function () {
            if (!silent) showToast("已本地暂存");
            setStatus("保存失败,已本地暂存");
            return false;
        });
    }

    function scheduleSave() {
        clearTimeout(saveTimer);
        saveTimer = setTimeout(function () {
            saveState(true);
        }, 500);
    }

    function loadFromHost() {
        return invoke("state.read", {
            key: stateKey,
            defaultValue: JSON.stringify(data)
        }).then(function (res) {
            var raw = "";

            if (typeof res === "string") {
                raw = res;
            } else if (res && typeof res.value === "string") {
                raw = res.value;
            } else {
                raw = JSON.stringify(data);
            }

            try {
                applyData(JSON.parse(raw));
            } catch (e) {
                applyData(data);
            }

            writeLocalFallback();
            setStatus("宿主同步完成");
        }).catch(function () {
            setStatus("读取失败,使用本地暂存");
        }).then(function () {
            isLoading = false;
        });
    }

    function waitForFudao(retry) {
        if (hasFudao()) {
            fudaoReady = true;
            setStatus("正在读取设置");
            loadFromHost();
            return;
        }

        if (retry >= 40) {
            fudaoReady = false;
            isLoading = false;
            setStatus("宿主未就绪,使用本地暂存");
            return;
        }

        setTimeout(function () {
            waitForFudao(retry + 1);
        }, 150);
    }

    startBtn.addEventListener("click", function () {
        applyDurationFromInputs();
        startTimer();
    });

    pauseBtn.addEventListener("click", function () {
        pauseTimer();
    });

    resetBtn.addEventListener("click", function () {
        resetTimer();
    });

    testBtn.addEventListener("click", function () {
        playSelectedAudio();
        showToast(data.audioPath ? "正在试听" : "使用默认提示音");
    });

    stopAudioBtn.addEventListener("click", function () {
        stopAudio();
        card.className = "card";
        showToast("已静音");
    });

    closeBtn.addEventListener("click", function () {
        saveState(true).then(function () {
            if (fudaoReady) {
                invoke("window.close", {}).catch(function () {});
            }
        });
    });

    selectAudioBtn.addEventListener("click", selectAudio);

    clearAudioBtn.addEventListener("click", function () {
        data.audioPath = "";
        data.audioName = "";
        stopAudio();
        render();
        saveState(false);
        showToast("已清除音频");
    });

    hourInput.addEventListener("change", applyDurationFromInputs);
    minuteInput.addEventListener("change", applyDurationFromInputs);
    secondInput.addEventListener("change", applyDurationFromInputs);

    hourInput.addEventListener("blur", applyDurationFromInputs);
    minuteInput.addEventListener("blur", applyDurationFromInputs);
    secondInput.addEventListener("blur", applyDurationFromInputs);

    var quickButtons = document.querySelectorAll("[data-quick]");
    for (var i = 0; i < quickButtons.length; i++) {
        quickButtons[i].addEventListener("click", function () {
            var sec = parseInt(this.getAttribute("data-quick"), 10);
            var h = Math.floor(sec / 3600);
            var m = Math.floor((sec % 3600) / 60);
            var s = sec % 60;

            hourInput.value = h;
            minuteInput.value = m;
            secondInput.value = s;

            running = false;
            clearInterval(timer);
            timer = null;
            stopAudio();
            card.className = "card";

            applyDurationFromInputs();
            showToast("已设置 " + formatTime(sec));
        });
    }

    window.addEventListener("blur", function () {
        saveState(true);
    });

    loadFallbackFirst();
    waitForFudao(0);
})();
</script>
</body>
</html>


回复内容
暂无回复
回复主贴