feat(M08-D): 建立后台运营总览

This commit is contained in:
Codex
2026-08-10 11:22:13 +08:00
parent b08a2cca43
commit 65f2e7c916
9 changed files with 775 additions and 39 deletions
+41 -11
View File
@@ -10,7 +10,21 @@
</div>
</div>
<nav class="nav-list" aria-label="后台模块">
<button class="nav-item active" type="button">
<button
class="nav-item"
:class="{ active: activeModule === 'overview' }"
type="button"
@click="activeModule = 'overview'"
>
<LayoutDashboard :size="18" />
<span>运营总览</span>
</button>
<button
class="nav-item"
:class="{ active: activeModule === 'cleaning' }"
type="button"
@click="activeModule = 'cleaning'"
>
<Sparkles :size="18" />
<span>保洁运营</span>
</button>
@@ -32,8 +46,8 @@
<section class="workspace">
<header class="topbar">
<div>
<p class="eyebrow">M08-B</p>
<h2>保洁任务与结算</h2>
<p class="eyebrow">{{ activeModule === 'overview' ? 'M08-D' : 'M08-B' }}</p>
<h2>{{ activeModule === 'overview' ? '平台运营总览' : '保洁任务与结算' }}</h2>
</div>
<div class="token-box">
<el-input
@@ -50,6 +64,13 @@
</div>
</header>
<OperationsOverviewPanel
v-if="activeModule === 'overview'"
:session="session"
@open-cleaning="activeModule = 'cleaning'"
/>
<template v-else>
<section class="metric-grid" aria-label="保洁概览">
<div class="metric">
<span>待验收</span>
@@ -198,17 +219,19 @@
<CleaningFieldHandoffPanel />
</el-tab-pane>
</el-tabs>
</template>
</section>
</main>
</el-config-provider>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue';
import { computed, reactive, ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import {
ClipboardCheck,
Download,
LayoutDashboard,
ListTodo,
RadioTower,
RotateCcw,
@@ -223,6 +246,7 @@ import CleaningSettlementsPanel from './components/CleaningSettlementsPanel.vue'
import CleaningStatisticsPanel from './components/CleaningStatisticsPanel.vue';
import CleanersPanel from './components/CleanersPanel.vue';
import CleaningFieldHandoffPanel from './components/CleaningFieldHandoffPanel.vue';
import OperationsOverviewPanel from './components/OperationsOverviewPanel.vue';
import {
ApiError,
assignCleaningTask,
@@ -257,7 +281,9 @@ import type {
} from './types';
import { money } from './format';
const tokenDraft = ref(localStorage.getItem('qipai.admin.token') || '');
const savedToken = ref(localStorage.getItem('qipai.admin.token') || '');
const tokenDraft = ref(savedToken.value);
const activeModule = ref<'overview' | 'cleaning'>('overview');
const activeTab = ref('tasks');
const lastError = ref('');
const lastMessage = ref('');
@@ -265,7 +291,7 @@ const taskStatus = ref<TaskStatus | ''>('SUBMITTED');
const settlementStatus = ref<SettlementStatus | ''>('CONFIRMED');
const cleanerStatus = ref<UserStatus | ''>('ACTIVE');
const cleanerSearch = ref('');
const session = computed(() => ({ token: tokenDraft.value }));
const session = computed(() => ({ token: savedToken.value }));
const loading = reactive({ tasks: false, settlements: false, statistics: false, cleaners: false });
const tasks = reactive<PageResult<CleaningTask>>({ items: [], total: 0, page: 1, pageSize: 20 });
const settlements = reactive<PageResult<CleaningSettlement>>({
@@ -310,8 +336,14 @@ const operationalQueueTotal = computed(() => (
));
function saveToken() {
localStorage.setItem('qipai.admin.token', tokenDraft.value.trim());
savedToken.value = tokenDraft.value.trim();
localStorage.setItem('qipai.admin.token', savedToken.value);
ElMessage.success('已保存');
if (activeModule.value === 'cleaning' && savedToken.value) void loadCleaningWorkspace();
}
function loadCleaningWorkspace() {
return Promise.all([loadTasks(), loadSettlements(), loadStatistics(), loadCleaners()]);
}
async function runAction(work: () => Promise<void>, success: string) {
@@ -659,9 +691,7 @@ async function handleResetCleanerSessions(userId: string) {
}, '已重置保洁员会话');
}
onMounted(() => {
if (session.value.token) {
void Promise.all([loadTasks(), loadSettlements(), loadStatistics(), loadCleaners()]);
}
watch(activeModule, (module) => {
if (module === 'cleaning' && session.value.token) void loadCleaningWorkspace();
});
</script>