feat(M08-B): 建立保洁管理后台
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<section class="panel">
|
||||
<div class="panel-toolbar">
|
||||
<el-segmented
|
||||
:model-value="status"
|
||||
:options="statusOptions"
|
||||
@update:model-value="$emit('status-change', $event as SettlementStatus | '')"
|
||||
/>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="FilePlus2" type="primary" @click="generateDialog.open = true">
|
||||
生成
|
||||
</el-button>
|
||||
<el-tooltip content="刷新结算">
|
||||
<el-button :icon="RefreshCw" :loading="loading" circle @click="$emit('refresh')" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="items" :loading="loading" class="data-table" row-key="id">
|
||||
<el-table-column prop="settlementNo" label="结算单" min-width="190" fixed>
|
||||
<template #default="{ row }">
|
||||
<div class="stack">
|
||||
<strong>{{ row.settlementNo }}</strong>
|
||||
<span>{{ row.cleanerName }} · {{ compactText(row.storeName) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="115">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="settlementTag(row.status)" effect="light">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskCount" label="任务" width="80" />
|
||||
<el-table-column prop="totalRewardCents" label="金额" width="110">
|
||||
<template #default="{ row }">{{ money(row.totalRewardCents) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payoutState" label="转账状态" width="130">
|
||||
<template #default="{ row }">{{ compactText(row.payoutState) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payoutReference" label="流水" min-width="150">
|
||||
<template #default="{ row }">{{ compactText(row.payoutReference) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payoutError" label="失败原因" min-width="150">
|
||||
<template #default="{ row }">{{ compactText(row.payoutError) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建" width="130">
|
||||
<template #default="{ row }">{{ shortDate(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="row-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
:icon="BadgeCheck"
|
||||
:disabled="row.status !== 'DRAFT'"
|
||||
@click="$emit('confirm', { settlementId: row.id, note: '后台确认结算' })"
|
||||
>
|
||||
确认
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:icon="Send"
|
||||
:disabled="row.status !== 'CONFIRMED'"
|
||||
@click="openTransfer(row)"
|
||||
>
|
||||
转账
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
:icon="RefreshCw"
|
||||
:disabled="row.status !== 'CONFIRMED'"
|
||||
@click="$emit('sync-transfer', { settlementId: row.id, note: '后台同步微信转账状态' })"
|
||||
>
|
||||
同步
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
:icon="CircleAlert"
|
||||
:disabled="row.status !== 'CONFIRMED'"
|
||||
@click="openFailure(row)"
|
||||
>
|
||||
失败
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
layout="prev, pager, next, total"
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
@current-change="$emit('page-change', $event)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="generateDialog.open" title="生成结算单" width="420px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="保洁员用户 ID">
|
||||
<el-input v-model="generateDialog.cleanerUserId" inputmode="numeric" />
|
||||
</el-form-item>
|
||||
<el-form-item label="门店 ID">
|
||||
<el-input v-model="generateDialog.storeId" inputmode="numeric" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="generateDialog.note" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="generateDialog.open = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitGenerate">生成</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="transferDialog.open" title="微信转账" width="420px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="模式">
|
||||
<el-radio-group v-model="transferDialog.mode">
|
||||
<el-radio-button label="API">API</el-radio-button>
|
||||
<el-radio-button label="MOCK">MOCK</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="transferDialog.note" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="transferDialog.open = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitTransfer">发起</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="failureDialog.open" title="记录失败" width="420px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="失败原因">
|
||||
<el-input v-model="failureDialog.error" />
|
||||
</el-form-item>
|
||||
<el-form-item label="外部流水">
|
||||
<el-input v-model="failureDialog.payoutReference" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="failureDialog.note" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="failureDialog.open = false">取消</el-button>
|
||||
<el-button type="danger" @click="submitFailure">记录</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import {
|
||||
BadgeCheck,
|
||||
CircleAlert,
|
||||
FilePlus2,
|
||||
RefreshCw,
|
||||
Send
|
||||
} from '@lucide/vue';
|
||||
import { compactText, money, shortDate } from '../format';
|
||||
import type {
|
||||
CleaningSettlement,
|
||||
PageResult,
|
||||
SettlementStatus,
|
||||
TransferMode
|
||||
} from '../types';
|
||||
|
||||
defineProps<{
|
||||
session: { token: string };
|
||||
loading: boolean;
|
||||
items: PageResult<CleaningSettlement>['items'];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status: SettlementStatus | '';
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
refresh: [];
|
||||
'status-change': [SettlementStatus | ''];
|
||||
'page-change': [number];
|
||||
generate: [{ cleanerUserId: string; storeId?: string; note?: string }];
|
||||
confirm: [{ settlementId: string; note?: string }];
|
||||
transfer: [{ settlementId: string; mode: TransferMode; note?: string }];
|
||||
'sync-transfer': [{ settlementId: string; note?: string }];
|
||||
failure: [{ settlementId: string; error: string; payoutReference?: string; note?: string }];
|
||||
}>();
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '草稿', value: 'DRAFT' },
|
||||
{ label: '待发', value: 'CONFIRMED' },
|
||||
{ label: '已发', value: 'PAID' },
|
||||
{ label: '取消', value: 'CANCELLED' }
|
||||
];
|
||||
const generateDialog = reactive({ open: false, cleanerUserId: '', storeId: '', note: '' });
|
||||
const transferDialog = reactive({
|
||||
open: false,
|
||||
settlementId: '',
|
||||
mode: 'API' as TransferMode,
|
||||
note: ''
|
||||
});
|
||||
const failureDialog = reactive({
|
||||
open: false,
|
||||
settlementId: '',
|
||||
error: '',
|
||||
payoutReference: '',
|
||||
note: ''
|
||||
});
|
||||
|
||||
function settlementTag(status: SettlementStatus) {
|
||||
if (status === 'PAID') return 'success';
|
||||
if (status === 'CONFIRMED') return 'warning';
|
||||
if (status === 'CANCELLED') return 'info';
|
||||
return 'primary';
|
||||
}
|
||||
|
||||
function submitGenerate() {
|
||||
emit('generate', {
|
||||
cleanerUserId: generateDialog.cleanerUserId,
|
||||
storeId: generateDialog.storeId || undefined,
|
||||
note: generateDialog.note
|
||||
});
|
||||
generateDialog.open = false;
|
||||
}
|
||||
|
||||
function openTransfer(row: CleaningSettlement) {
|
||||
transferDialog.open = true;
|
||||
transferDialog.settlementId = row.id;
|
||||
transferDialog.mode = 'API';
|
||||
transferDialog.note = '';
|
||||
}
|
||||
|
||||
function submitTransfer() {
|
||||
emit('transfer', {
|
||||
settlementId: transferDialog.settlementId,
|
||||
mode: transferDialog.mode,
|
||||
note: transferDialog.note
|
||||
});
|
||||
transferDialog.open = false;
|
||||
}
|
||||
|
||||
function openFailure(row: CleaningSettlement) {
|
||||
failureDialog.open = true;
|
||||
failureDialog.settlementId = row.id;
|
||||
failureDialog.error = row.payoutError || '';
|
||||
failureDialog.payoutReference = row.payoutReference || '';
|
||||
failureDialog.note = '';
|
||||
}
|
||||
|
||||
function submitFailure() {
|
||||
emit('failure', {
|
||||
settlementId: failureDialog.settlementId,
|
||||
error: failureDialog.error,
|
||||
payoutReference: failureDialog.payoutReference,
|
||||
note: failureDialog.note
|
||||
});
|
||||
failureDialog.open = false;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user