feat(electronic-wooden-fish): 实现电子木鱼页面及本地存储适配器
- 新增电子木鱼点击动画与计数功能 - 添加浮动提示动画效果 - 实现计数名称自定义输入框 - 增加重置所有数据按钮 - 创建跨平台本地存储适配器(支持浏览器和Chrome扩展) - 样式优化:按钮增加过渡动画和按下效果 - 页面结构重构,分离CSS样式文件
This commit is contained in:
+15
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 (<div>
|
||||
电子木鱼
|
||||
<img
|
||||
alt={'电子木鱼'}
|
||||
src={dzmyImg}
|
||||
style={{
|
||||
backgroundColor: 'black',
|
||||
width: '80%',
|
||||
height: '80%',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.12s ease',
|
||||
transform: active ? 'scale(0.92)' : 'scale(1)',
|
||||
}}
|
||||
onClick={clickAnimation}
|
||||
/>
|
||||
</div>);
|
||||
const resetAll = () => {
|
||||
setCount(0);
|
||||
setTipList([]);
|
||||
setCountName('金钱');
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="dzmy-bg">
|
||||
<div className="dzmy-count">{countName}: {count}</div>
|
||||
|
||||
<div className="dzmy-box">
|
||||
<div className="tips">
|
||||
{tipList.map((tip) => (
|
||||
<div key={tip.id} className="tip-item">
|
||||
{countName} +1
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<img
|
||||
alt="电子木鱼"
|
||||
src={dzmyImg}
|
||||
className={`dzmy-img ${active ? 'active' : ''}`}
|
||||
onClick={clickAnimation}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'dzmy-controls'}>
|
||||
<input
|
||||
type="text"
|
||||
value={countName}
|
||||
onChange={(e) => setCountName(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={'action-btn'}
|
||||
onClick={resetAll}>重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ElectronicWoodenFishPage;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user