refactor: optimize data cleanup functions in director console
- Add redrawCurrentRound() method to clear current round winners - Add /api/admin/lottery/redraw API endpoint - Rename "重置" to "重置本轮" (reset current round state only) - Add "重抽本轮" button (clear winners and allow re-draw) - Rename "紧急操作" to "数据管理" with clearer button labels - Change "高级清理" to collapsible "开发者选项" - Update confirmation modal text for clarity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,15 @@ const confirmResetCode = ref('');
|
||||
const showResetModal = ref(false);
|
||||
const resetScope = ref<'all' | 'voting' | 'lottery'>('all');
|
||||
|
||||
// Advanced cleanup state
|
||||
const showAdvancedCleanupModal = ref(false);
|
||||
const confirmCleanupCode = ref('');
|
||||
const cleanupLoading = ref(false);
|
||||
const cleanupOptions = ref({
|
||||
lottery: { redis: true, mysql: true },
|
||||
voting: { redis: false, mysql: false },
|
||||
});
|
||||
|
||||
// Toast notification state
|
||||
const toast = ref<{ show: boolean; message: string; type: 'error' | 'success' | 'info' }>({
|
||||
show: false,
|
||||
@@ -237,7 +246,7 @@ function goBack() {
|
||||
}
|
||||
|
||||
// Phase control
|
||||
function setPhase(phase: 'IDLE' | 'VOTING' | 'LOTTERY' | 'RESULTS') {
|
||||
function setPhase(phase: 'IDLE' | 'VOTING' | 'LOTTERY' | 'RESULTS' | 'LOTTERY_RESULTS') {
|
||||
admin.setPhase(phase);
|
||||
}
|
||||
|
||||
@@ -315,6 +324,28 @@ const resetLottery = debounceLeading(() => {
|
||||
admin.controlLottery('reset');
|
||||
}, 500);
|
||||
|
||||
// Redraw current round
|
||||
const showRedrawConfirm = ref(false);
|
||||
|
||||
async function redrawCurrentRound() {
|
||||
try {
|
||||
const res = await fetch('/api/admin/lottery/redraw', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
showToast(data.message || '本轮已重置,可重新抽取', 'success');
|
||||
} else {
|
||||
showToast(data.error || '重抽失败', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('网络错误', 'error');
|
||||
} finally {
|
||||
showRedrawConfirm.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Emergency reset
|
||||
function openResetModal(scope: 'all' | 'voting' | 'lottery') {
|
||||
resetScope.value = scope;
|
||||
@@ -333,6 +364,54 @@ function cancelReset() {
|
||||
confirmResetCode.value = '';
|
||||
}
|
||||
|
||||
// Advanced cleanup
|
||||
function openAdvancedCleanupModal() {
|
||||
confirmCleanupCode.value = '';
|
||||
cleanupOptions.value = {
|
||||
lottery: { redis: true, mysql: true },
|
||||
voting: { redis: false, mysql: false },
|
||||
};
|
||||
showAdvancedCleanupModal.value = true;
|
||||
}
|
||||
|
||||
function cancelAdvancedCleanup() {
|
||||
showAdvancedCleanupModal.value = false;
|
||||
confirmCleanupCode.value = '';
|
||||
}
|
||||
|
||||
async function confirmAdvancedCleanup() {
|
||||
if (confirmCleanupCode.value !== 'ADVANCED_CLEANUP') return;
|
||||
|
||||
cleanupLoading.value = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/cleanup', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
lottery: cleanupOptions.value.lottery,
|
||||
voting: cleanupOptions.value.voting,
|
||||
confirmCode: confirmCleanupCode.value,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
const results = data.data;
|
||||
let message = '清理完成:';
|
||||
if (results.lottery?.mysql) message += `抽奖数据 ${results.lottery.dbCount} 条;`;
|
||||
if (results.voting?.mysql) message += `投票数据 ${results.voting.dbCount} 条;`;
|
||||
showToast(message, 'success');
|
||||
} else {
|
||||
showToast(data.error || '清理失败', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('网络错误', 'error');
|
||||
} finally {
|
||||
cleanupLoading.value = false;
|
||||
showAdvancedCleanupModal.value = false;
|
||||
confirmCleanupCode.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Music control
|
||||
function toggleMusic() {
|
||||
if (admin.musicPlaying) {
|
||||
@@ -379,6 +458,7 @@ const phaseLabel = computed(() => {
|
||||
case 'VOTING': return '投票中';
|
||||
case 'LOTTERY': return '抽奖中';
|
||||
case 'RESULTS': return '结果展示';
|
||||
case 'LOTTERY_RESULTS': return '抽奖结果';
|
||||
default: return '未知';
|
||||
}
|
||||
});
|
||||
@@ -643,7 +723,13 @@ onMounted(() => {
|
||||
:disabled="admin.pendingAction === 'lottery_reset'"
|
||||
@click="resetLottery"
|
||||
>
|
||||
重置
|
||||
重置本轮
|
||||
</button>
|
||||
<button
|
||||
class="ctrl-btn warning"
|
||||
@click="showRedrawConfirm = true"
|
||||
>
|
||||
重抽本轮
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -800,6 +886,13 @@ onMounted(() => {
|
||||
>
|
||||
结果展示
|
||||
</button>
|
||||
<button
|
||||
class="ctrl-btn"
|
||||
:class="{ active: admin.systemPhase === 'LOTTERY_RESULTS' }"
|
||||
@click="setPhase('LOTTERY_RESULTS')"
|
||||
>
|
||||
抽奖结果
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -833,15 +926,25 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Developer Options (Collapsible) -->
|
||||
<details class="control-group dev-options">
|
||||
<summary><h4>开发者选项</h4></summary>
|
||||
<div class="button-group">
|
||||
<button class="ctrl-btn warning-outline" @click="openAdvancedCleanupModal">
|
||||
高级数据清理
|
||||
</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Emergency Reset -->
|
||||
<div class="control-group danger-zone">
|
||||
<h4>紧急操作</h4>
|
||||
<h4>数据管理</h4>
|
||||
<div class="button-group">
|
||||
<button class="ctrl-btn danger-outline" @click="openResetModal('voting')">
|
||||
重置投票
|
||||
清空投票数据
|
||||
</button>
|
||||
<button class="ctrl-btn danger-outline" @click="openResetModal('lottery')">
|
||||
重置抽奖
|
||||
清空抽奖数据
|
||||
</button>
|
||||
<button class="ctrl-btn danger" @click="openResetModal('all')">
|
||||
全部重置
|
||||
@@ -855,12 +958,12 @@ onMounted(() => {
|
||||
<!-- Reset Confirmation Modal -->
|
||||
<div v-if="showResetModal" class="modal-overlay" @click.self="cancelReset">
|
||||
<div class="modal">
|
||||
<h3>确认重置</h3>
|
||||
<h3>确认操作</h3>
|
||||
<p>
|
||||
您即将重置
|
||||
您即将清空
|
||||
<strong>{{ resetScope === 'all' ? '所有数据' : resetScope === 'voting' ? '投票数据' : '抽奖数据' }}</strong>
|
||||
</p>
|
||||
<p class="warning-text">此操作不可撤销!</p>
|
||||
<p class="warning-text">此操作将清除 Redis 缓存和数据库记录,不可撤销!</p>
|
||||
<div class="modal-input">
|
||||
<label>请输入 <code>RESET</code> 确认:</label>
|
||||
<input
|
||||
@@ -882,6 +985,86 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Redraw Confirmation Modal -->
|
||||
<div v-if="showRedrawConfirm" class="modal-overlay" @click.self="showRedrawConfirm = false">
|
||||
<div class="modal">
|
||||
<h3>确认重抽本轮</h3>
|
||||
<p>您即将清除<strong>第{{ admin.lotteryRound }}轮</strong>的中奖者记录</p>
|
||||
<p class="warning-text">清除后可重新抽取本轮,此操作不可撤销!</p>
|
||||
<div class="modal-actions">
|
||||
<button class="ctrl-btn outline" @click="showRedrawConfirm = false">取消</button>
|
||||
<button class="ctrl-btn warning" @click="redrawCurrentRound">
|
||||
确认重抽
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Cleanup Modal -->
|
||||
<div v-if="showAdvancedCleanupModal" class="modal-overlay" @click.self="cancelAdvancedCleanup">
|
||||
<div class="modal cleanup-modal">
|
||||
<h3>高级数据清理</h3>
|
||||
<p>选择要清理的数据类型和存储层:</p>
|
||||
|
||||
<div class="cleanup-sections">
|
||||
<!-- Lottery Cleanup Options -->
|
||||
<div class="cleanup-section">
|
||||
<h4>🎁 抽奖数据</h4>
|
||||
<div class="cleanup-options">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="cleanupOptions.lottery.redis" />
|
||||
<span>Redis 缓存</span>
|
||||
<small>(中奖者集合、抽奖历史)</small>
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="cleanupOptions.lottery.mysql" />
|
||||
<span>MySQL 数据库</span>
|
||||
<small>(中奖记录、抽奖会话)</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Voting Cleanup Options -->
|
||||
<div class="cleanup-section">
|
||||
<h4>🗳️ 投票数据</h4>
|
||||
<div class="cleanup-options">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="cleanupOptions.voting.redis" />
|
||||
<span>Redis 缓存</span>
|
||||
<small>(实时票数、排行榜)</small>
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="cleanupOptions.voting.mysql" />
|
||||
<span>MySQL 数据库</span>
|
||||
<small>(投票记录、票数统计)</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="warning-text">⚠️ 此操作不可撤销!系统状态将保持不变。</p>
|
||||
<div class="modal-input">
|
||||
<label>请输入 <code>ADVANCED_CLEANUP</code> 确认:</label>
|
||||
<input
|
||||
v-model="confirmCleanupCode"
|
||||
type="text"
|
||||
placeholder="ADVANCED_CLEANUP"
|
||||
@keyup.enter="confirmAdvancedCleanup"
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="ctrl-btn outline" @click="cancelAdvancedCleanup">取消</button>
|
||||
<button
|
||||
class="ctrl-btn warning"
|
||||
:disabled="confirmCleanupCode !== 'ADVANCED_CLEANUP' || cleanupLoading"
|
||||
@click="confirmAdvancedCleanup"
|
||||
>
|
||||
{{ cleanupLoading ? '清理中...' : '确认清理' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1368,6 +1551,16 @@ $admin-danger: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
&.warning-outline {
|
||||
background: transparent;
|
||||
border-color: $admin-warning;
|
||||
color: $admin-warning;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba($admin-warning, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&.outline {
|
||||
background: transparent;
|
||||
border-color: $admin-border;
|
||||
@@ -1535,6 +1728,47 @@ $admin-danger: #ef4444;
|
||||
color: $admin-danger;
|
||||
}
|
||||
}
|
||||
|
||||
&.warning-zone {
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba($admin-warning, 0.3);
|
||||
|
||||
h4 {
|
||||
color: $admin-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&.dev-options {
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba($admin-warning, 0.3);
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h4 {
|
||||
display: inline;
|
||||
color: $admin-warning;
|
||||
|
||||
&::before {
|
||||
content: '▶ ';
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[open] summary h4::before {
|
||||
content: '▼ ';
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modal
|
||||
@@ -1609,6 +1843,75 @@ $admin-danger: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
// Advanced Cleanup Modal Styles
|
||||
.cleanup-modal {
|
||||
width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cleanup-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.cleanup-section {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid $admin-border;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
color: $admin-text;
|
||||
margin: 0 0 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.cleanup-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: $admin-warning;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: $admin-text;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
small {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: $admin-text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Award Statistics Styles
|
||||
.award-stats {
|
||||
display: grid;
|
||||
@@ -1899,6 +2202,30 @@ $admin-danger: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.cleanup-modal {
|
||||
width: 95vw;
|
||||
}
|
||||
|
||||
.cleanup-section {
|
||||
padding: 12px;
|
||||
|
||||
h4 {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.cleanup-options .checkbox-label {
|
||||
padding: 8px 10px;
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-input input {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
|
||||
Reference in New Issue
Block a user