657 lines
22 KiB
Vue
657 lines
22 KiB
Vue
<template>
|
|
<el-config-provider>
|
|
<main class="app-shell">
|
|
<aside class="sidebar">
|
|
<div class="brand">
|
|
<div class="brand-mark">棋</div>
|
|
<div>
|
|
<h1>自助棋牌室</h1>
|
|
<p>运营后台</p>
|
|
</div>
|
|
</div>
|
|
<nav class="nav-list" aria-label="后台模块">
|
|
<button class="nav-item active" type="button">
|
|
<Sparkles :size="18" />
|
|
<span>保洁运营</span>
|
|
</button>
|
|
<button class="nav-item" type="button" disabled>
|
|
<Store :size="18" />
|
|
<span>门店</span>
|
|
</button>
|
|
<button class="nav-item" type="button" disabled>
|
|
<RadioTower :size="18" />
|
|
<span>设备</span>
|
|
</button>
|
|
<button class="nav-item" type="button" disabled>
|
|
<WalletCards :size="18" />
|
|
<span>支付</span>
|
|
</button>
|
|
</nav>
|
|
</aside>
|
|
|
|
<section class="workspace">
|
|
<header class="topbar">
|
|
<div>
|
|
<p class="eyebrow">M08-B</p>
|
|
<h2>保洁任务与结算</h2>
|
|
</div>
|
|
<div class="token-box">
|
|
<el-input
|
|
v-model="tokenDraft"
|
|
type="password"
|
|
show-password
|
|
placeholder="后台访问令牌"
|
|
autocomplete="off"
|
|
@keyup.enter="saveToken"
|
|
/>
|
|
<el-tooltip content="保存令牌" placement="bottom">
|
|
<el-button :icon="Save" type="primary" @click="saveToken" />
|
|
</el-tooltip>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="metric-grid" aria-label="保洁概览">
|
|
<div class="metric">
|
|
<span>待验收</span>
|
|
<strong>{{ statistics.summary.pendingReview }}</strong>
|
|
</div>
|
|
<div class="metric">
|
|
<span>已完成</span>
|
|
<strong>{{ statistics.summary.completed }}</strong>
|
|
</div>
|
|
<div class="metric">
|
|
<span>待发放</span>
|
|
<strong>{{ money(statistics.summary.confirmedSettlementCents) }}</strong>
|
|
</div>
|
|
<div class="metric">
|
|
<span>待结算</span>
|
|
<strong>{{ money(statistics.summary.pendingSettlementCents) }}</strong>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="action-board" aria-label="运营待处理">
|
|
<button class="action-tile" type="button" @click="jumpToTaskStatus('SUBMITTED')">
|
|
<ClipboardCheck :size="20" />
|
|
<span>待验收任务</span>
|
|
<strong>{{ statistics.summary.pendingReview }}</strong>
|
|
</button>
|
|
<button class="action-tile" type="button" @click="jumpToTaskStatus('REJECTED')">
|
|
<RotateCcw :size="20" />
|
|
<span>驳回补做</span>
|
|
<strong>{{ statistics.summary.rejected }}</strong>
|
|
</button>
|
|
<button class="action-tile" type="button" @click="jumpToSettlementStatus('DRAFT')">
|
|
<ListTodo :size="20" />
|
|
<span>待确认结算</span>
|
|
<strong>{{ draftSettlementTotal }}</strong>
|
|
</button>
|
|
<button class="action-tile" type="button" @click="jumpToSettlementStatus('CONFIRMED')">
|
|
<Send :size="20" />
|
|
<span>待发放金额</span>
|
|
<strong>{{ money(statistics.summary.confirmedSettlementCents) }}</strong>
|
|
</button>
|
|
<button class="action-tile" type="button" @click="exportOperationalQueue">
|
|
<Download :size="20" />
|
|
<span>导出待处理</span>
|
|
<strong>{{ operationalQueueTotal }}</strong>
|
|
</button>
|
|
</section>
|
|
|
|
<el-alert
|
|
v-if="lastError"
|
|
class="alert-line"
|
|
:title="lastError"
|
|
type="error"
|
|
show-icon
|
|
:closable="true"
|
|
@close="lastError = ''"
|
|
/>
|
|
<el-alert
|
|
v-if="lastMessage"
|
|
class="alert-line"
|
|
:title="lastMessage"
|
|
type="success"
|
|
show-icon
|
|
:closable="true"
|
|
@close="lastMessage = ''"
|
|
/>
|
|
|
|
<el-tabs v-model="activeTab" class="work-tabs">
|
|
<el-tab-pane label="任务" name="tasks">
|
|
<CleaningTasksPanel
|
|
:session="session"
|
|
:loading="loading.tasks"
|
|
:items="tasks.items"
|
|
:cleaners="cleaners.items"
|
|
:total="tasks.total"
|
|
:page="tasks.page"
|
|
:page-size="tasks.pageSize"
|
|
:status="taskStatus"
|
|
:filters="taskFilters"
|
|
@refresh="loadTasks"
|
|
@status-change="changeTaskStatus"
|
|
@filter="changeTaskFilters"
|
|
@page-change="changeTaskPage"
|
|
@assign="handleAssign"
|
|
@complete="handleComplete"
|
|
@complete-many="handleCompleteMany"
|
|
@reject="handleReject"
|
|
@reject-many="handleRejectMany"
|
|
@exempt="handleExempt"
|
|
@reclaim="handleReclaim"
|
|
/>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="结算" name="settlements">
|
|
<CleaningSettlementsPanel
|
|
:session="session"
|
|
:loading="loading.settlements"
|
|
:items="settlements.items"
|
|
:cleaners="cleaners.items"
|
|
:total="settlements.total"
|
|
:page="settlements.page"
|
|
:page-size="settlements.pageSize"
|
|
:status="settlementStatus"
|
|
:filters="settlementFilters"
|
|
@refresh="loadSettlements"
|
|
@status-change="changeSettlementStatus"
|
|
@filter="changeSettlementFilters"
|
|
@page-change="changeSettlementPage"
|
|
@generate="handleGenerateSettlement"
|
|
@confirm="handleConfirmSettlement"
|
|
@transfer="handleTransfer"
|
|
@sync-transfer="handleSyncTransfer"
|
|
@sync-transfer-many="handleSyncTransferMany"
|
|
@failure="handleFailure"
|
|
/>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="统计" name="statistics">
|
|
<CleaningStatisticsPanel
|
|
:loading="loading.statistics"
|
|
:statistics="statistics"
|
|
:filters="statisticsFilters"
|
|
:cleaners="cleaners.items"
|
|
@refresh="loadStatistics"
|
|
@filter="changeStatisticsFilters"
|
|
/>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="保洁员" name="cleaners">
|
|
<CleanersPanel
|
|
:loading="loading.cleaners"
|
|
:items="cleaners.items"
|
|
:total="cleaners.total"
|
|
:page="cleaners.page"
|
|
:page-size="cleaners.pageSize"
|
|
:status="cleanerStatus"
|
|
:search="cleanerSearch"
|
|
@refresh="loadCleaners"
|
|
@status-change="changeCleanerStatus"
|
|
@search-change="changeCleanerSearch"
|
|
@page-change="changeCleanerPage"
|
|
@create="handleCreateCleaner"
|
|
@update="handleUpdateCleaner"
|
|
@status-toggle="handleToggleCleanerStatus"
|
|
@reset-sessions="handleResetCleanerSessions"
|
|
/>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="联调" name="handoff">
|
|
<CleaningFieldHandoffPanel />
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</section>
|
|
</main>
|
|
</el-config-provider>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
import { ElMessage } from 'element-plus';
|
|
import {
|
|
ClipboardCheck,
|
|
Download,
|
|
ListTodo,
|
|
RadioTower,
|
|
RotateCcw,
|
|
Save,
|
|
Send,
|
|
Sparkles,
|
|
Store,
|
|
WalletCards
|
|
} from '@lucide/vue';
|
|
import CleaningTasksPanel from './components/CleaningTasksPanel.vue';
|
|
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 {
|
|
ApiError,
|
|
assignCleaningTask,
|
|
completeCleaningTask,
|
|
confirmCleaningSettlement,
|
|
createStaffUser,
|
|
exemptCleaningTask,
|
|
executeWechatTransfer,
|
|
generateCleaningSettlement,
|
|
getCleaningStatistics,
|
|
listCleaningSettlements,
|
|
listCleaningTasks,
|
|
listStaffUsers,
|
|
reclaimCleaningTimeouts,
|
|
recordPayoutFailure,
|
|
rejectCleaningTask,
|
|
resetStaffSessions,
|
|
syncWechatTransfer,
|
|
updateStaffUser
|
|
} from './api';
|
|
import type {
|
|
CleaningSettlement,
|
|
CleaningStatistics,
|
|
CleaningTask,
|
|
ManagedUser,
|
|
PageResult,
|
|
PayoutStateFilter,
|
|
SettlementStatus,
|
|
TaskStatus,
|
|
TransferMode,
|
|
UserStatus
|
|
} from './types';
|
|
import { money } from './format';
|
|
|
|
const tokenDraft = ref(localStorage.getItem('qipai.admin.token') || '');
|
|
const activeTab = ref('tasks');
|
|
const lastError = ref('');
|
|
const lastMessage = ref('');
|
|
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 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>>({
|
|
items: [],
|
|
total: 0,
|
|
page: 1,
|
|
pageSize: 20
|
|
});
|
|
const cleaners = reactive<PageResult<ManagedUser>>({ items: [], total: 0, page: 1, pageSize: 20 });
|
|
const taskFilters = reactive({ storeId: '', cleanerUserId: '' });
|
|
const settlementFilters = reactive<{ storeId: string; cleanerUserId: string; payoutState: PayoutStateFilter | '' }>({
|
|
storeId: '',
|
|
cleanerUserId: '',
|
|
payoutState: ''
|
|
});
|
|
const statisticsFilters = reactive({ from: '', to: '', storeId: '', cleanerUserId: '' });
|
|
const statistics = reactive<CleaningStatistics>({
|
|
summary: {
|
|
taskTotal: 0,
|
|
pendingReview: 0,
|
|
active: 0,
|
|
rejected: 0,
|
|
completed: 0,
|
|
rewardCents: 0,
|
|
pendingSettlementCents: 0,
|
|
confirmedSettlementCents: 0,
|
|
paidSettlementCents: 0
|
|
},
|
|
byStatus: [],
|
|
byStore: [],
|
|
settlements: [],
|
|
members: [],
|
|
trend: []
|
|
});
|
|
const draftSettlementTotal = computed(() => statistics.settlements
|
|
.find((item) => item.status === 'DRAFT')?.total || 0);
|
|
const operationalQueueTotal = computed(() => (
|
|
statistics.summary.pendingReview
|
|
+ statistics.summary.rejected
|
|
+ draftSettlementTotal.value
|
|
));
|
|
|
|
function saveToken() {
|
|
localStorage.setItem('qipai.admin.token', tokenDraft.value.trim());
|
|
ElMessage.success('已保存');
|
|
}
|
|
|
|
async function runAction(work: () => Promise<void>, success: string) {
|
|
lastError.value = '';
|
|
lastMessage.value = '';
|
|
try {
|
|
await work();
|
|
lastMessage.value = success;
|
|
ElMessage.success(success);
|
|
} catch (error) {
|
|
lastError.value = error instanceof ApiError
|
|
? `${error.code}${error.traceId ? ` · ${error.traceId}` : ''}`
|
|
: error instanceof Error ? error.message : '操作失败';
|
|
}
|
|
}
|
|
|
|
async function loadTasks() {
|
|
loading.tasks = true;
|
|
await runAction(async () => {
|
|
const result = await listCleaningTasks(session.value, {
|
|
page: tasks.page,
|
|
pageSize: tasks.pageSize,
|
|
status: taskStatus.value || undefined,
|
|
storeId: taskFilters.storeId || undefined,
|
|
cleanerUserId: taskFilters.cleanerUserId || undefined
|
|
});
|
|
Object.assign(tasks, result);
|
|
}, '任务已刷新');
|
|
loading.tasks = false;
|
|
}
|
|
|
|
async function loadSettlements() {
|
|
loading.settlements = true;
|
|
await runAction(async () => {
|
|
const result = await listCleaningSettlements(session.value, {
|
|
page: settlements.page,
|
|
pageSize: settlements.pageSize,
|
|
status: settlementStatus.value || undefined,
|
|
payoutState: settlementFilters.payoutState || undefined,
|
|
storeId: settlementFilters.storeId || undefined,
|
|
cleanerUserId: settlementFilters.cleanerUserId || undefined
|
|
});
|
|
Object.assign(settlements, result);
|
|
}, '结算已刷新');
|
|
loading.settlements = false;
|
|
}
|
|
|
|
async function loadStatistics() {
|
|
loading.statistics = true;
|
|
await runAction(async () => {
|
|
const result = await getCleaningStatistics(session.value, {
|
|
from: statisticsFilters.from || undefined,
|
|
to: statisticsFilters.to || undefined,
|
|
storeId: statisticsFilters.storeId || undefined,
|
|
cleanerUserId: statisticsFilters.cleanerUserId || undefined
|
|
});
|
|
Object.assign(statistics.summary, result.summary);
|
|
statistics.byStatus = result.byStatus;
|
|
statistics.byStore = result.byStore;
|
|
statistics.settlements = result.settlements;
|
|
statistics.members = result.members;
|
|
statistics.trend = result.trend;
|
|
}, '统计已刷新');
|
|
loading.statistics = false;
|
|
}
|
|
|
|
async function loadCleaners() {
|
|
loading.cleaners = true;
|
|
await runAction(async () => {
|
|
const result = await listStaffUsers(session.value, {
|
|
page: cleaners.page,
|
|
pageSize: cleaners.pageSize,
|
|
role: 'CLEANER',
|
|
status: cleanerStatus.value || undefined,
|
|
search: cleanerSearch.value || undefined
|
|
});
|
|
Object.assign(cleaners, {
|
|
items: result.items,
|
|
total: result.total,
|
|
page: cleaners.page,
|
|
pageSize: cleaners.pageSize
|
|
});
|
|
}, '保洁员已刷新');
|
|
loading.cleaners = false;
|
|
}
|
|
|
|
function changeTaskStatus(status: TaskStatus | '') {
|
|
taskStatus.value = status;
|
|
tasks.page = 1;
|
|
void loadTasks();
|
|
}
|
|
|
|
function jumpToTaskStatus(status: TaskStatus) {
|
|
activeTab.value = 'tasks';
|
|
taskStatus.value = status;
|
|
tasks.page = 1;
|
|
void loadTasks();
|
|
}
|
|
|
|
function changeTaskFilters(input: { storeId: string; cleanerUserId: string }) {
|
|
Object.assign(taskFilters, input);
|
|
tasks.page = 1;
|
|
void loadTasks();
|
|
}
|
|
|
|
function changeSettlementStatus(status: SettlementStatus | '') {
|
|
settlementStatus.value = status;
|
|
settlements.page = 1;
|
|
void loadSettlements();
|
|
}
|
|
|
|
function jumpToSettlementStatus(status: SettlementStatus) {
|
|
activeTab.value = 'settlements';
|
|
settlementStatus.value = status;
|
|
settlementFilters.payoutState = '';
|
|
settlements.page = 1;
|
|
void loadSettlements();
|
|
}
|
|
|
|
function exportOperationalQueue() {
|
|
downloadCsv(`cleaning-operational-queue-${Date.now()}.csv`, [
|
|
['队列', '数量', '金额', '入口'],
|
|
['待验收任务', String(statistics.summary.pendingReview), '', '任务/SUBMITTED'],
|
|
['驳回补做', String(statistics.summary.rejected), '', '任务/REJECTED'],
|
|
['待确认结算', String(draftSettlementTotal.value), '', '结算/DRAFT'],
|
|
['待发放金额', '', money(statistics.summary.confirmedSettlementCents), '结算/CONFIRMED'],
|
|
['待结算金额', '', money(statistics.summary.pendingSettlementCents), '统计/待结算'],
|
|
['已完成任务', String(statistics.summary.completed), '', '统计/已完成']
|
|
]);
|
|
}
|
|
|
|
function downloadCsv(filename: string, rows: string[][]) {
|
|
const content = rows.map((row) => row.map(csvCell).join(',')).join('\r\n');
|
|
const blob = new Blob([`\uFEFF${content}`], { type: 'text/csv;charset=utf-8' });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = filename;
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function csvCell(value: string) {
|
|
const normalized = value.replace(/\r?\n/g, ' ');
|
|
return /[",\r\n]/.test(normalized) ? `"${normalized.replace(/"/g, '""')}"` : normalized;
|
|
}
|
|
|
|
function changeSettlementFilters(input: { storeId: string; cleanerUserId: string; payoutState: PayoutStateFilter | '' }) {
|
|
Object.assign(settlementFilters, input);
|
|
settlements.page = 1;
|
|
void loadSettlements();
|
|
}
|
|
|
|
function changeTaskPage(page: number) {
|
|
tasks.page = page;
|
|
void loadTasks();
|
|
}
|
|
|
|
function changeSettlementPage(page: number) {
|
|
settlements.page = page;
|
|
void loadSettlements();
|
|
}
|
|
|
|
function changeStatisticsFilters(input: { from: string; to: string; storeId: string; cleanerUserId: string }) {
|
|
Object.assign(statisticsFilters, input);
|
|
void loadStatistics();
|
|
}
|
|
|
|
function changeCleanerStatus(status: UserStatus | '') {
|
|
cleanerStatus.value = status;
|
|
cleaners.page = 1;
|
|
void loadCleaners();
|
|
}
|
|
|
|
function changeCleanerSearch(search: string) {
|
|
cleanerSearch.value = search;
|
|
cleaners.page = 1;
|
|
void loadCleaners();
|
|
}
|
|
|
|
function changeCleanerPage(page: number) {
|
|
cleaners.page = page;
|
|
void loadCleaners();
|
|
}
|
|
|
|
async function handleAssign(input: { taskId: string; cleanerUserId: string; note?: string }) {
|
|
await runAction(async () => {
|
|
await assignCleaningTask(session.value, input.taskId, input);
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, '已指派');
|
|
}
|
|
|
|
async function handleComplete(input: { taskId: string; note?: string }) {
|
|
await runAction(async () => {
|
|
await completeCleaningTask(session.value, input.taskId, input.note);
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, '已验收');
|
|
}
|
|
|
|
async function handleCompleteMany(input: { taskIds: string[]; note?: string }) {
|
|
await runAction(async () => {
|
|
for (const taskId of input.taskIds) {
|
|
await completeCleaningTask(session.value, taskId, input.note);
|
|
}
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, `已批量验收 ${input.taskIds.length} 个任务`);
|
|
}
|
|
|
|
async function handleReject(input: { taskId: string; reason: string }) {
|
|
await runAction(async () => {
|
|
await rejectCleaningTask(session.value, input.taskId, input.reason);
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, '已驳回');
|
|
}
|
|
|
|
async function handleRejectMany(input: { taskIds: string[]; reason: string }) {
|
|
await runAction(async () => {
|
|
for (const taskId of input.taskIds) {
|
|
await rejectCleaningTask(session.value, taskId, input.reason);
|
|
}
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, `已批量驳回 ${input.taskIds.length} 个任务`);
|
|
}
|
|
|
|
async function handleExempt(input: { taskId: string; note?: string }) {
|
|
await runAction(async () => {
|
|
await exemptCleaningTask(session.value, input.taskId, input.note);
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, '宸叉爣璁板厤娓呮磥');
|
|
}
|
|
|
|
async function handleReclaim(input: { olderThanMinutes: number; limit: number }) {
|
|
await runAction(async () => {
|
|
await reclaimCleaningTimeouts(session.value, input);
|
|
await Promise.all([loadTasks(), loadStatistics()]);
|
|
}, '已回收超时任务');
|
|
}
|
|
|
|
async function handleGenerateSettlement(
|
|
input: { cleanerUserId: string; storeId?: string; note?: string }
|
|
) {
|
|
await runAction(async () => {
|
|
await generateCleaningSettlement(session.value, input);
|
|
await Promise.all([loadTasks(), loadSettlements(), loadStatistics()]);
|
|
}, '已生成结算单');
|
|
}
|
|
|
|
async function handleConfirmSettlement(input: { settlementId: string; note?: string }) {
|
|
await runAction(async () => {
|
|
await confirmCleaningSettlement(session.value, input.settlementId, input.note);
|
|
await Promise.all([loadSettlements(), loadStatistics()]);
|
|
}, '已确认结算单');
|
|
}
|
|
|
|
async function handleTransfer(input: { settlementId: string; mode: TransferMode; note?: string }) {
|
|
await runAction(async () => {
|
|
await executeWechatTransfer(session.value, input.settlementId, {
|
|
mode: input.mode,
|
|
note: input.note
|
|
});
|
|
await Promise.all([loadSettlements(), loadStatistics()]);
|
|
}, '已提交微信转账');
|
|
}
|
|
|
|
async function handleSyncTransfer(input: { settlementId: string; note?: string }) {
|
|
await runAction(async () => {
|
|
await syncWechatTransfer(session.value, input.settlementId, input.note);
|
|
await Promise.all([loadSettlements(), loadStatistics()]);
|
|
}, '已同步微信状态');
|
|
}
|
|
|
|
async function handleSyncTransferMany(input: { settlementIds: string[]; note?: string }) {
|
|
await runAction(async () => {
|
|
for (const settlementId of input.settlementIds) {
|
|
await syncWechatTransfer(session.value, settlementId, input.note);
|
|
}
|
|
await Promise.all([loadSettlements(), loadStatistics()]);
|
|
}, `已批量同步 ${input.settlementIds.length} 个结算单`);
|
|
}
|
|
|
|
async function handleFailure(
|
|
input: { settlementId: string; error: string; payoutReference?: string; note?: string }
|
|
) {
|
|
await runAction(async () => {
|
|
await recordPayoutFailure(session.value, input.settlementId, {
|
|
error: input.error,
|
|
payoutChannel: 'WECHAT_TRANSFER',
|
|
payoutReference: input.payoutReference,
|
|
note: input.note
|
|
});
|
|
await Promise.all([loadSettlements(), loadStatistics()]);
|
|
}, '已记录失败');
|
|
}
|
|
|
|
async function handleCreateCleaner(
|
|
input: { nickname: string; phone: string; storeIds: string[]; note?: string }
|
|
) {
|
|
await runAction(async () => {
|
|
await createStaffUser(session.value, {
|
|
nickname: input.nickname,
|
|
phone: input.phone,
|
|
roles: ['CLEANER'],
|
|
storeIds: input.storeIds,
|
|
note: input.note
|
|
});
|
|
await loadCleaners();
|
|
}, '已新增保洁员');
|
|
}
|
|
|
|
async function handleUpdateCleaner(
|
|
input: { userId: string; nickname: string; phone?: string; storeIds: string[]; note?: string }
|
|
) {
|
|
await runAction(async () => {
|
|
await updateStaffUser(session.value, input.userId, {
|
|
nickname: input.nickname,
|
|
phone: input.phone,
|
|
roles: ['CLEANER'],
|
|
storeIds: input.storeIds,
|
|
note: input.note
|
|
});
|
|
await loadCleaners();
|
|
}, '已更新保洁员');
|
|
}
|
|
|
|
async function handleToggleCleanerStatus(input: { userId: string; status: UserStatus }) {
|
|
await runAction(async () => {
|
|
await updateStaffUser(session.value, input.userId, { status: input.status });
|
|
await loadCleaners();
|
|
}, input.status === 'ACTIVE' ? '已启用保洁员' : '已停用保洁员');
|
|
}
|
|
|
|
async function handleResetCleanerSessions(userId: string) {
|
|
await runAction(async () => {
|
|
await resetStaffSessions(session.value, userId);
|
|
await loadCleaners();
|
|
}, '已重置保洁员会话');
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (session.value.token) {
|
|
void Promise.all([loadTasks(), loadSettlements(), loadStatistics(), loadCleaners()]);
|
|
}
|
|
});
|
|
</script>
|