From 6dc71bf0568c08c733f735ae0974f0b9fc7d8c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Sat, 20 Dec 2025 11:43:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(electronic-wooden-fish):=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=94=B5=E5=AD=90=E6=9C=A8=E9=B1=BC=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8F=8A=E6=9C=AC=E5=9C=B0=E5=AD=98=E5=82=A8=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增电子木鱼点击动画与计数功能 - 添加浮动提示动画效果 - 实现计数名称自定义输入框 - 增加重置所有数据按钮 - 创建跨平台本地存储适配器(支持浏览器和Chrome扩展) - 样式优化:按钮增加过渡动画和按下效果 - 页面结构重构,分离CSS样式文件 --- src/App.css | 16 +++- src/pages/ElectronicWoodenFish.css | 64 +++++++++++++++ src/pages/ElectronicWoodenFishPage.js | 109 +++++++++++++++++++++----- src/utils/storageAdapter.js | 30 +++++++ 4 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 src/pages/ElectronicWoodenFish.css create mode 100644 src/utils/storageAdapter.js diff --git a/src/App.css b/src/App.css index d5f35e9..9187feb 100644 --- a/src/App.css +++ b/src/App.css @@ -75,15 +75,29 @@ padding: 8px 16px; background: #4caf50; color: white; - border: none; border-radius: 4px; cursor: pointer; + border: 1px solid #d9d9d9; + box-shadow: 0 2px #00000004; + transition: .8s; + opacity: 4; } .action-btn:hover { background: #3d8b40; } +.action-btn:active { + box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.2); + opacity: 0; +} + +.action-btn:active::after { + box-shadow: none; + opacity: 0.4; + transition: 0s; +} + .stop-btn { margin-right: 10px; padding: 8px 16px; diff --git a/src/pages/ElectronicWoodenFish.css b/src/pages/ElectronicWoodenFish.css new file mode 100644 index 0000000..4c15948 --- /dev/null +++ b/src/pages/ElectronicWoodenFish.css @@ -0,0 +1,64 @@ +.dzmy-bg { + background-color: black; + width: 100%; + height: 500px; + display: flex; + flex-direction: column; + justify-content: space-evenly; + position: relative; +} + +.dzmy-count { + position: absolute; + top: 10px; + left: 10px; + color: #ffffff; +} + +.dzmy-img { + width: 160px; + height: 120px; + cursor: pointer; + transition: transform 0.12s ease; + transform: scale(1); +} + +.dzmy-img.active { + transform: scale(0.92); +} + +.dzmy-box { + text-align: center; + position: relative; +} + +.tips { + position: absolute; + top: -50px; + width: 100%; + text-align: center; + pointer-events: none; +} + +.tip-item { + position: absolute; + width: 100%; + animation: floatUp 0.5s ease-out forwards; + color: #fadb14; + font-weight: bold; +} + +@keyframes floatUp { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(-50px); + opacity: 0; + } +} + +.dzmy-controls { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/src/pages/ElectronicWoodenFishPage.js b/src/pages/ElectronicWoodenFishPage.js index 7826446..dc8493c 100644 --- a/src/pages/ElectronicWoodenFishPage.js +++ b/src/pages/ElectronicWoodenFishPage.js @@ -1,32 +1,99 @@ +import React, {useState, useCallback, useEffect} from 'react'; import dzmyImg from '../assets/images/dzmy.png'; -import {useState} from "react"; +import './ElectronicWoodenFish.css'; +import storageAdapter from "../utils/storageAdapter"; const ElectronicWoodenFishPage = () => { const [active, setActive] = useState(false); + const [count, setCount] = useState(0); + const [tipList, setTipList] = useState([]); + const [countName, setCountName] = useState('金钱'); - const clickAnimation = () => { + useEffect(() => { + const init = async () => { + const savedCount = await storageAdapter.get('count'); + const savedCountName = await storageAdapter.get('countName'); + if (savedCount) { + setCount(savedCount); + } else { + await storageAdapter.set('count', 0); + } + if (savedCountName) { + setCountName(savedCountName); + } else { + await storageAdapter.set('countName', '金钱'); + } + } + init(); + }, []); + + const clickAnimation = useCallback(async () => { + // 1. 木鱼缩放动画触发 setActive(true); + setTimeout(() => setActive(false), 100); + + // 2. 更新计数 + setCount((prev) => prev + 1); + + // 3. 生成唯一的 Tip 对象 + const newTip = { + id: Date.now() + Math.random(), // 唯一 ID + value: count + 1 + }; + + // 4. 添加到列表 + setTipList((prev) => [...prev, newTip]); + + // 5. 自动移除:500ms 后删除该特定 Tip setTimeout(() => { - setActive(false); - }, 120); - }; + setTipList((prev) => prev.filter((t) => t.id !== newTip.id)); + }, 500); + + await storageAdapter.set('count', count + 1); + await storageAdapter.set('countName', countName); + }, [count]); - return (
- 电子木鱼 - {'电子木鱼'} -
); + const resetAll = () => { + setCount(0); + setTipList([]); + setCountName('金钱'); + } + + return ( +
+
+
{countName}: {count}
+ +
+
+ {tipList.map((tip) => ( +
+ {countName} +1 +
+ ))} +
+ + 电子木鱼 +
+
+
+ setCountName(e.target.value)} + /> + +
+
+ ); }; export default ElectronicWoodenFishPage; \ No newline at end of file diff --git a/src/utils/storageAdapter.js b/src/utils/storageAdapter.js new file mode 100644 index 0000000..355f684 --- /dev/null +++ b/src/utils/storageAdapter.js @@ -0,0 +1,30 @@ +// 判断当前是否处于 Chrome 插件环境 +const isExtension = typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local; + +const storageAdapter = { + // 获取数据 + get: async (key) => { + if (isExtension) { + const result = await chrome.storage.local.get([key]); + return result[key]; + } else { + const item = localStorage.getItem(key); + try { + return JSON.parse(item); // 保持与插件版一致的对象处理 + } catch { + return item; + } + } + }, + + // 设置数据 + set: async (key, value) => { + if (isExtension) { + await chrome.storage.local.set({[key]: value}); + } else { + localStorage.setItem(key, JSON.stringify(value)); + } + } +}; + +export default storageAdapter; \ No newline at end of file