Skip to content

Commit 7e667ca

Browse files
committed
完善历史记录功能,支持从抽取历史和名单变更历史还原状态;添加清空历史记录的功能
1 parent 8647af9 commit 7e667ca

2 files changed

Lines changed: 209 additions & 26 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ AI 生成的小工具,用于随机抽取人员,支持 URL 传参,支持静
77
- 支持名单搜索、清空、分享链接
88
- 支持通过 URL 传参快速导入名单
99
- 支持本地收藏/加载/删除常用名单(localStorage,无需后端,静态部署友好)
10+
- 完整的操作历史记录(抽取历史和名单变更历史)
11+
- 支持从历史记录还原到任意时刻的状态
12+
1013

1114
**在线体验地址:**
1215
https://wangjunwei233.github.io/RandomChoice.NextJS/

app/page.tsx

Lines changed: 206 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,7 @@ const RandomNamePickerContent = () => {
2727

2828
// 初始化:加载URL名单和本地收藏名单
2929
useEffect(() => {
30-
const list = searchParams.get('list');
31-
if (list) {
32-
const decodedList = decodeURIComponent(list);
33-
const initialNames = decodedList
34-
.split(/\n|,||\[|\]/)
35-
.map((name) => name.trim())
36-
.filter((name) => name);
37-
setNames(initialNames);
38-
setMaxSelectable(initialNames.length);
39-
// 记录URL导入名单的历史
40-
addNameListHistory('URL导入', initialNames, `导入${initialNames.length}个名字`);
41-
}
42-
// 加载收藏名单
43-
try {
44-
const fav = localStorage.getItem('favoriteLists');
45-
if (fav) {
46-
setFavoriteLists(JSON.parse(fav));
47-
}
48-
} catch {}
49-
// 加载历史记录
30+
// 先加载历史记录
5031
try {
5132
const drawHist = localStorage.getItem('drawHistory');
5233
if (drawHist) {
@@ -57,6 +38,54 @@ const RandomNamePickerContent = () => {
5738
setNameListHistory(JSON.parse(nameHist));
5839
}
5940
} catch {}
41+
42+
// 加载收藏名单
43+
try {
44+
const fav = localStorage.getItem('favoriteLists');
45+
if (fav) {
46+
setFavoriteLists(JSON.parse(fav));
47+
}
48+
} catch {}
49+
50+
// 处理URL参数(只在页面首次加载时)
51+
const list = searchParams.get('list');
52+
if (list && names.length === 0) { // 只在名单为空时处理,避免重复
53+
const decodedList = decodeURIComponent(list);
54+
const initialNames = decodedList
55+
.split(/\n|,||\[|\]/)
56+
.map((name) => name.trim())
57+
.filter((name) => name);
58+
59+
if (initialNames.length > 0) {
60+
setNames(initialNames);
61+
setMaxSelectable(initialNames.length);
62+
63+
// 延迟记录历史,确保状态已更新且避免重复
64+
setTimeout(() => {
65+
const newRecord = {
66+
timestamp: new Date().toLocaleString(),
67+
action: 'URL导入',
68+
nameList: [...initialNames],
69+
details: `导入${initialNames.length}个名字`
70+
};
71+
setNameListHistory(prev => {
72+
// 检查是否已经有相同的URL导入记录
73+
const hasUrlImport = prev.some(record =>
74+
record.action === 'URL导入' &&
75+
record.nameList.length === initialNames.length &&
76+
record.nameList.every((name, index) => name === initialNames[index])
77+
);
78+
79+
if (!hasUrlImport) {
80+
const newHistory = [newRecord, ...prev].slice(0, 50);
81+
localStorage.setItem('nameListHistory', JSON.stringify(newHistory));
82+
return newHistory;
83+
}
84+
return prev;
85+
});
86+
}, 100);
87+
}
88+
}
6089
}, [searchParams]);
6190

6291
// 添加名单历史记录
@@ -68,6 +97,20 @@ const RandomNamePickerContent = () => {
6897
details
6998
};
7099
setNameListHistory(prev => {
100+
// 检查是否与最近的记录重复(避免连续相同操作重复记录)
101+
const lastRecord = prev[0];
102+
if (lastRecord &&
103+
lastRecord.action === action &&
104+
lastRecord.nameList.length === nameList.length &&
105+
lastRecord.nameList.every((name, index) => name === nameList[index]) &&
106+
lastRecord.details === details) {
107+
// 如果完全相同且时间间隔很短(1秒内),则不重复记录
108+
const timeDiff = new Date().getTime() - new Date(lastRecord.timestamp).getTime();
109+
if (timeDiff < 1000) {
110+
return prev;
111+
}
112+
}
113+
71114
const newHistory = [newRecord, ...prev].slice(0, 50); // 最多保留50条记录
72115
localStorage.setItem('nameListHistory', JSON.stringify(newHistory));
73116
return newHistory;
@@ -83,6 +126,20 @@ const RandomNamePickerContent = () => {
83126
numPicked
84127
};
85128
setDrawHistory(prev => {
129+
// 检查是否与最近的记录重复
130+
const lastRecord = prev[0];
131+
if (lastRecord &&
132+
lastRecord.nameList.length === nameList.length &&
133+
lastRecord.drawnNames.length === drawnNames.length &&
134+
lastRecord.numPicked === numPicked &&
135+
lastRecord.drawnNames.every((name, index) => name === drawnNames[index])) {
136+
// 如果抽取结果完全相同且时间间隔很短(1秒内),则不重复记录
137+
const timeDiff = new Date().getTime() - new Date(lastRecord.timestamp).getTime();
138+
if (timeDiff < 1000) {
139+
return prev;
140+
}
141+
}
142+
86143
const newHistory = [newRecord, ...prev].slice(0, 50); // 最多保留50条记录
87144
localStorage.setItem('drawHistory', JSON.stringify(newHistory));
88145
return newHistory;
@@ -129,6 +186,70 @@ const RandomNamePickerContent = () => {
129186
localStorage.setItem('favoriteLists', JSON.stringify(newFavs));
130187
};
131188

189+
// 清空所有历史记录
190+
const handleClearAllHistory = () => {
191+
if (!window.confirm('确定要清空所有历史记录吗?此操作不可恢复!')) return;
192+
193+
// 清空状态
194+
setDrawHistory([]);
195+
setNameListHistory([]);
196+
197+
// 清空本地存储
198+
localStorage.removeItem('drawHistory');
199+
localStorage.removeItem('nameListHistory');
200+
201+
alert('所有历史记录已清空!');
202+
};
203+
204+
// 清空抽取历史记录
205+
const handleClearDrawHistory = () => {
206+
if (!window.confirm('确定要清空抽取历史记录吗?此操作不可恢复!')) return;
207+
208+
setDrawHistory([]);
209+
localStorage.removeItem('drawHistory');
210+
211+
alert('抽取历史记录已清空!');
212+
};
213+
214+
// 清空名单变更历史记录
215+
const handleClearNameHistory = () => {
216+
if (!window.confirm('确定要清空名单变更历史记录吗?此操作不可恢复!')) return;
217+
218+
setNameListHistory([]);
219+
localStorage.removeItem('nameListHistory');
220+
221+
alert('名单变更历史记录已清空!');
222+
};
223+
224+
// 从抽取历史还原名单和抽取结果
225+
const handleRestoreFromDrawHistory = (record: {timestamp: string, nameList: string[], drawnNames: string[], numPicked: number}) => {
226+
if (!window.confirm('确定要还原到这次抽取时的状态吗?当前名单将被替换。')) return;
227+
setNames(record.nameList);
228+
setSelectedNames(record.drawnNames);
229+
setNumToPick(record.numPicked);
230+
setInput('');
231+
setSearchTerm('');
232+
// 还原操作只记录一次简单的记录,避免重复
233+
setTimeout(() => {
234+
addNameListHistory('从抽取历史还原', record.nameList, `还原到${record.timestamp}的抽取状态`);
235+
}, 50);
236+
alert('已还原到该次抽取时的状态!');
237+
};
238+
239+
// 从名单历史还原名单
240+
const handleRestoreFromNameHistory = (record: {timestamp: string, action: string, nameList: string[], details?: string}) => {
241+
if (!window.confirm('确定要还原到这个时刻的名单吗?当前名单将被替换。')) return;
242+
setNames(record.nameList);
243+
setSelectedNames([]);
244+
setInput('');
245+
setSearchTerm('');
246+
// 还原操作只记录一次简单的记录,避免重复
247+
setTimeout(() => {
248+
addNameListHistory('从名单历史还原', record.nameList, `还原到${record.timestamp}的名单状态`);
249+
}, 50);
250+
alert('已还原到该时刻的名单状态!');
251+
};
252+
132253
useEffect(() => {
133254
setMaxSelectable(names.length || 1);
134255
if (numToPick > names.length) {
@@ -386,16 +507,52 @@ const RandomNamePickerContent = () => {
386507
</button>
387508
</div>
388509

510+
{/* 历史记录总控制区域 */}
511+
<div className="history-control-section">
512+
<div className="list-header">
513+
<h2>历史记录管理</h2>
514+
<button
515+
onClick={handleClearAllHistory}
516+
className="button danger"
517+
disabled={drawHistory.length === 0 && nameListHistory.length === 0}
518+
style={{ padding: '8px 16px', fontSize: '14px' }}
519+
>
520+
清空所有历史记录
521+
</button>
522+
</div>
523+
</div>
524+
389525
{/* 历史记录部分 */}
390526
<div className="history-section">
391-
<h2>抽取历史记录</h2>
527+
<div className="list-header">
528+
<h2>抽取历史记录</h2>
529+
<div className="button-group">
530+
<button
531+
onClick={handleClearDrawHistory}
532+
className="button danger"
533+
disabled={drawHistory.length === 0}
534+
style={{ padding: '6px 12px', fontSize: '14px' }}
535+
>
536+
清空抽取历史
537+
</button>
538+
</div>
539+
</div>
392540
{drawHistory.length > 0 ? (
393541
<div className="history-container">
394542
{drawHistory.slice(0, 10).map((record, index) => (
395543
<div key={index} className="history-item">
396544
<div className="history-header">
397545
<span className="history-time">{record.timestamp}</span>
398-
<span className="history-info">{record.nameList.length} 人中抽取 {record.numPicked}</span>
546+
<div>
547+
<span className="history-info">{record.nameList.length} 人中抽取 {record.numPicked}</span>
548+
<button
549+
onClick={() => handleRestoreFromDrawHistory(record)}
550+
className="button primary"
551+
style={{ marginLeft: 8, padding: '2px 8px', fontSize: '12px' }}
552+
>
553+
还原此状态
554+
</button>
555+
</div>
399556
</div>
400557
<div className="history-content">
401558
<div>当时名单: {record.nameList.join(', ')}</div>
@@ -410,14 +567,35 @@ const RandomNamePickerContent = () => {
410567
</div>
411568

412569
<div className="history-section">
413-
<h2>名单变更历史</h2>
570+
<div className="list-header">
571+
<h2>名单变更历史</h2>
572+
<div className="button-group">
573+
<button
574+
onClick={handleClearNameHistory}
575+
className="button danger"
576+
disabled={nameListHistory.length === 0}
577+
style={{ padding: '6px 12px', fontSize: '14px' }}
578+
>
579+
清空变更历史
580+
</button>
581+
</div>
582+
</div>
414583
{nameListHistory.length > 0 ? (
415584
<div className="history-container">
416585
{nameListHistory.slice(0, 10).map((record, index) => (
417586
<div key={index} className="history-item">
418587
<div className="history-header">
419588
<span className="history-time">{record.timestamp}</span>
420-
<span className="history-action">{record.action}</span>
589+
<div>
590+
<span className="history-action">{record.action}</span>
591+
<button
592+
onClick={() => handleRestoreFromNameHistory(record)}
593+
className="button primary"
594+
style={{ marginLeft: 8, padding: '2px 8px', fontSize: '12px' }}
595+
>
596+
还原名单
597+
</button>
598+
</div>
421599
</div>
422600
<div className="history-content">
423601
{record.details && <div style={{fontSize: '14px', color: '#666'}}>{record.details}</div>}
@@ -445,7 +623,7 @@ const RandomNamePickerContent = () => {
445623
className="search-input"
446624
style={{ width: 180, marginRight: 8 }}
447625
/>
448-
<button onClick={handleSaveFavorite} className="button secondary">
626+
<button onClick={handleSaveFavorite} className="button primary">
449627
收藏当前名单
450628
</button>
451629
</div>
@@ -546,7 +724,7 @@ const RandomNamePickerContent = () => {
546724
font-size: 2.2rem;
547725
}
548726
549-
.input-section, .list-section, .picker-section, .range-section, .history-section, .favorite-section {
727+
.input-section, .list-section, .picker-section, .range-section, .history-section, .favorite-section, .history-control-section {
550728
margin-bottom: 30px;
551729
}
552730
@@ -761,6 +939,8 @@ const RandomNamePickerContent = () => {
761939
justify-content: space-between;
762940
align-items: center;
763941
margin-bottom: 8px;
942+
flex-wrap: wrap;
943+
gap: 8px;
764944
}
765945
766946
.history-time {

0 commit comments

Comments
 (0)