feat(M08-B): 补任务结算筛选
This commit is contained in:
+24
-2
@@ -99,8 +99,10 @@
|
||||
: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"
|
||||
@@ -118,8 +120,10 @@
|
||||
: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"
|
||||
@@ -226,6 +230,8 @@ const settlements = reactive<PageResult<CleaningSettlement>>({
|
||||
pageSize: 20
|
||||
});
|
||||
const cleaners = reactive<PageResult<ManagedUser>>({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
const taskFilters = reactive({ storeId: '', cleanerUserId: '' });
|
||||
const settlementFilters = reactive({ storeId: '', cleanerUserId: '' });
|
||||
const statisticsFilters = reactive({ from: '', to: '', storeId: '' });
|
||||
const statistics = reactive<CleaningStatistics>({
|
||||
summary: {
|
||||
@@ -271,7 +277,9 @@ async function loadTasks() {
|
||||
const result = await listCleaningTasks(session.value, {
|
||||
page: tasks.page,
|
||||
pageSize: tasks.pageSize,
|
||||
status: taskStatus.value || undefined
|
||||
status: taskStatus.value || undefined,
|
||||
storeId: taskFilters.storeId || undefined,
|
||||
cleanerUserId: taskFilters.cleanerUserId || undefined
|
||||
});
|
||||
Object.assign(tasks, result);
|
||||
}, '任务已刷新');
|
||||
@@ -284,7 +292,9 @@ async function loadSettlements() {
|
||||
const result = await listCleaningSettlements(session.value, {
|
||||
page: settlements.page,
|
||||
pageSize: settlements.pageSize,
|
||||
status: settlementStatus.value || undefined
|
||||
status: settlementStatus.value || undefined,
|
||||
storeId: settlementFilters.storeId || undefined,
|
||||
cleanerUserId: settlementFilters.cleanerUserId || undefined
|
||||
});
|
||||
Object.assign(settlements, result);
|
||||
}, '结算已刷新');
|
||||
@@ -335,12 +345,24 @@ function changeTaskStatus(status: TaskStatus | '') {
|
||||
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 changeSettlementFilters(input: { storeId: string; cleanerUserId: string }) {
|
||||
Object.assign(settlementFilters, input);
|
||||
settlements.page = 1;
|
||||
void loadSettlements();
|
||||
}
|
||||
|
||||
function changeTaskPage(page: number) {
|
||||
tasks.page = page;
|
||||
void loadTasks();
|
||||
|
||||
+6
-2
@@ -57,13 +57,15 @@ async function request<T>(
|
||||
|
||||
export function listCleaningTasks(
|
||||
session: ApiSession,
|
||||
input: { page: number; pageSize: number; status?: TaskStatus }
|
||||
input: { page: number; pageSize: number; status?: TaskStatus; storeId?: string; cleanerUserId?: string }
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
page: String(input.page),
|
||||
pageSize: String(input.pageSize)
|
||||
});
|
||||
if (input.status) params.set('status', input.status);
|
||||
if (input.storeId) params.set('storeId', input.storeId);
|
||||
if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId);
|
||||
return request<PageResult<CleaningTask>>(session, `/cleaning/tasks?${params}`);
|
||||
}
|
||||
|
||||
@@ -210,13 +212,15 @@ export function reclaimCleaningTimeouts(
|
||||
|
||||
export function listCleaningSettlements(
|
||||
session: ApiSession,
|
||||
input: { page: number; pageSize: number; status?: SettlementStatus }
|
||||
input: { page: number; pageSize: number; status?: SettlementStatus; storeId?: string; cleanerUserId?: string }
|
||||
) {
|
||||
const params = new URLSearchParams({
|
||||
page: String(input.page),
|
||||
pageSize: String(input.pageSize)
|
||||
});
|
||||
if (input.status) params.set('status', input.status);
|
||||
if (input.storeId) params.set('storeId', input.storeId);
|
||||
if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId);
|
||||
return request<PageResult<CleaningSettlement>>(session, `/cleaning/settlements?${params}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
<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-segmented
|
||||
:model-value="status"
|
||||
:options="statusOptions"
|
||||
@update:model-value="$emit('status-change', $event as SettlementStatus | '')"
|
||||
/>
|
||||
<el-input
|
||||
v-model="filterDraft.storeId"
|
||||
class="narrow-input"
|
||||
placeholder="门店 ID"
|
||||
clearable
|
||||
@keyup.enter="submitFilters"
|
||||
@clear="submitFilters"
|
||||
/>
|
||||
<el-select
|
||||
v-model="filterDraft.cleanerUserId"
|
||||
class="filter-select"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="保洁员"
|
||||
@change="submitFilters"
|
||||
@clear="submitFilters"
|
||||
>
|
||||
<el-option
|
||||
v-for="cleaner in cleaners"
|
||||
:key="cleaner.id"
|
||||
:label="cleanerLabel(cleaner)"
|
||||
:value="cleaner.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :icon="Search" @click="submitFilters">筛选</el-button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button :icon="FilePlus2" type="primary" @click="generateDialog.open = true">
|
||||
生成
|
||||
@@ -263,7 +290,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import { reactive, watch } from 'vue';
|
||||
import {
|
||||
BadgeCheck,
|
||||
CircleAlert,
|
||||
@@ -271,6 +298,7 @@ import {
|
||||
FilePlus2,
|
||||
ListChecks,
|
||||
RefreshCw,
|
||||
Search,
|
||||
Send
|
||||
} from '@lucide/vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
@@ -295,11 +323,13 @@ const props = defineProps<{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status: SettlementStatus | '';
|
||||
filters: { storeId: string; cleanerUserId: string };
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
refresh: [];
|
||||
'status-change': [SettlementStatus | ''];
|
||||
filter: [{ storeId: string; cleanerUserId: string }];
|
||||
'page-change': [number];
|
||||
generate: [{ cleanerUserId: string; storeId?: string; note?: string }];
|
||||
confirm: [{ settlementId: string; note?: string }];
|
||||
@@ -315,6 +345,7 @@ const statusOptions = [
|
||||
{ label: '已发', value: 'PAID' },
|
||||
{ label: '取消', value: 'CANCELLED' }
|
||||
];
|
||||
const filterDraft = reactive({ storeId: props.filters.storeId, cleanerUserId: props.filters.cleanerUserId });
|
||||
const generateDialog = reactive({ open: false, cleanerUserId: '', storeId: '', note: '' });
|
||||
const transferDialog = reactive({
|
||||
open: false,
|
||||
@@ -322,6 +353,12 @@ const transferDialog = reactive({
|
||||
mode: 'API' as TransferMode,
|
||||
note: ''
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.filters,
|
||||
(value) => Object.assign(filterDraft, value),
|
||||
{ deep: true }
|
||||
);
|
||||
const preflightDialog = reactive<{
|
||||
open: boolean;
|
||||
result: WechatTransferPreflight | null;
|
||||
@@ -366,6 +403,13 @@ function cleanerLabel(cleaner: ManagedUser) {
|
||||
return `${name}${phone}${stores}`;
|
||||
}
|
||||
|
||||
function submitFilters() {
|
||||
emit('filter', {
|
||||
storeId: filterDraft.storeId.trim(),
|
||||
cleanerUserId: filterDraft.cleanerUserId
|
||||
});
|
||||
}
|
||||
|
||||
function submitGenerate() {
|
||||
if (!generateDialog.cleanerUserId) {
|
||||
ElMessage.warning('请选择保洁员');
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
<template>
|
||||
<section class="panel">
|
||||
<div class="panel-toolbar">
|
||||
<el-segmented
|
||||
:model-value="status"
|
||||
:options="statusOptions"
|
||||
@update:model-value="$emit('status-change', $event as TaskStatus | '')"
|
||||
/>
|
||||
<div class="toolbar-actions">
|
||||
<el-segmented
|
||||
:model-value="status"
|
||||
:options="statusOptions"
|
||||
@update:model-value="$emit('status-change', $event as TaskStatus | '')"
|
||||
/>
|
||||
<el-input
|
||||
v-model="filterDraft.storeId"
|
||||
class="narrow-input"
|
||||
placeholder="门店 ID"
|
||||
clearable
|
||||
@keyup.enter="submitFilters"
|
||||
@clear="submitFilters"
|
||||
/>
|
||||
<el-select
|
||||
v-model="filterDraft.cleanerUserId"
|
||||
class="filter-select"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="保洁员"
|
||||
@change="submitFilters"
|
||||
@clear="submitFilters"
|
||||
>
|
||||
<el-option
|
||||
v-for="cleaner in cleaners"
|
||||
:key="cleaner.id"
|
||||
:label="cleanerLabel(cleaner)"
|
||||
:value="cleaner.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :icon="Search" @click="submitFilters">筛选</el-button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-popover trigger="click" width="280">
|
||||
<template #reference>
|
||||
@@ -226,13 +253,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import { reactive, watch } from 'vue';
|
||||
import {
|
||||
BadgeCheck,
|
||||
Check,
|
||||
Image,
|
||||
RefreshCw,
|
||||
RotateCcw,
|
||||
Search,
|
||||
Undo2,
|
||||
UserMinus,
|
||||
UserPlus,
|
||||
@@ -256,11 +284,13 @@ const props = defineProps<{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
status: TaskStatus | '';
|
||||
filters: { storeId: string; cleanerUserId: string };
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
refresh: [];
|
||||
'status-change': [TaskStatus | ''];
|
||||
filter: [{ storeId: string; cleanerUserId: string }];
|
||||
'page-change': [number];
|
||||
assign: [{ taskId: string; cleanerUserId: string; note?: string }];
|
||||
complete: [{ taskId: string; note?: string }];
|
||||
@@ -277,6 +307,7 @@ const statusOptions = [
|
||||
{ label: '完成', value: 'COMPLETED' },
|
||||
{ label: '驳回', value: 'REJECTED' }
|
||||
];
|
||||
const filterDraft = reactive({ storeId: props.filters.storeId, cleanerUserId: props.filters.cleanerUserId });
|
||||
const reclaimForm = reactive({ olderThanMinutes: 60, limit: 20 });
|
||||
const assignDialog = reactive({ open: false, taskId: '', cleanerUserId: '', note: '' });
|
||||
const rejectDialog = reactive({ open: false, taskId: '', reason: '' });
|
||||
@@ -292,6 +323,12 @@ const memberDialog = reactive({
|
||||
members: [] as CleaningTaskMember[]
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.filters,
|
||||
(value) => Object.assign(filterDraft, value),
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function taskTag(status: TaskStatus) {
|
||||
if (status === 'SUBMITTED') return 'warning';
|
||||
if (status === 'COMPLETED' || status === 'SETTLED') return 'success';
|
||||
@@ -306,6 +343,13 @@ function cleanerLabel(cleaner: ManagedUser) {
|
||||
return `${name}${phone}${stores}`;
|
||||
}
|
||||
|
||||
function submitFilters() {
|
||||
emit('filter', {
|
||||
storeId: filterDraft.storeId.trim(),
|
||||
cleanerUserId: filterDraft.cleanerUserId
|
||||
});
|
||||
}
|
||||
|
||||
function openAssign(row: CleaningTask) {
|
||||
assignDialog.open = true;
|
||||
assignDialog.taskId = row.id;
|
||||
|
||||
@@ -206,6 +206,14 @@ textarea {
|
||||
width: min(240px, 100%);
|
||||
}
|
||||
|
||||
.narrow-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.profile-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user