feat(M08-B): 补后台协作与结算详情

This commit is contained in:
Codex
2026-06-30 10:02:17 +08:00
parent 9506021b29
commit f59beb0e75
15 changed files with 529 additions and 23 deletions
+137 -3
View File
@@ -64,6 +64,7 @@
<template #default="{ row }">
<div class="row-actions">
<el-button size="small" :icon="UserPlus" @click="openAssign(row)">指派</el-button>
<el-button size="small" :icon="Users" @click="openMembers(row)">成员</el-button>
<el-button
size="small"
type="success"
@@ -120,6 +121,66 @@
<el-button type="danger" @click="submitReject">驳回</el-button>
</template>
</el-dialog>
<el-dialog v-model="memberDialog.open" title="协作者管理" width="640px">
<div class="dialog-stack">
<div class="detail-header">
<strong>{{ memberDialog.taskNo }}</strong>
<span>任务奖励 {{ money(memberDialog.taskRewardCents) }}</span>
</div>
<el-table :data="memberDialog.members" size="small" v-loading="memberDialog.loading">
<el-table-column prop="nickname" label="成员" min-width="140">
<template #default="{ row }">
<div class="stack">
<strong>{{ row.nickname || compactText(row.userId) }}</strong>
<span>ID {{ row.userId }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="memberRole" label="角色" width="90">
<template #default="{ row }">
<el-tag :type="row.memberRole === 'LEAD' ? 'success' : 'info'" effect="light">
{{ row.memberRole }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="rewardCents" label="分账" width="110">
<template #default="{ row }">{{ money(row.rewardCents) }}</template>
</el-table-column>
<el-table-column prop="settledAt" label="结算" width="130">
<template #default="{ row }">{{ shortDate(row.settledAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="{ row }">
<el-button
size="small"
type="danger"
:icon="UserMinus"
:disabled="row.memberRole === 'LEAD' || !!row.settledAt"
@click="removeMember(row.userId)"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
<el-form label-position="top" class="member-form">
<el-form-item label="协作者用户 ID">
<el-input v-model="memberDialog.cleanerUserId" inputmode="numeric" />
</el-form-item>
<el-form-item label="分账金额">
<el-input-number v-model="memberDialog.rewardCents" :min="0" :max="1000000" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="memberDialog.note" />
</el-form-item>
<el-button type="primary" :icon="UserPlus" @click="submitMember">添加/更新</el-button>
</el-form>
</div>
<template #footer>
<el-button @click="memberDialog.open = false">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
@@ -131,12 +192,20 @@ import {
RefreshCw,
RotateCcw,
Undo2,
UserPlus
UserMinus,
UserPlus,
Users
} from '@lucide/vue';
import { ElMessage } from 'element-plus';
import {
addCleaningTaskMember,
listCleaningTaskMembers,
removeCleaningTaskMember
} from '../api';
import { compactText, money, shortDate } from '../format';
import type { CleaningTask, PageResult, TaskStatus } from '../types';
import type { CleaningTask, CleaningTaskMember, PageResult, TaskStatus } from '../types';
defineProps<{
const props = defineProps<{
session: { token: string };
loading: boolean;
items: PageResult<CleaningTask>['items'];
@@ -168,6 +237,17 @@ const statusOptions = [
const reclaimForm = reactive({ olderThanMinutes: 60, limit: 20 });
const assignDialog = reactive({ open: false, taskId: '', cleanerUserId: '', note: '' });
const rejectDialog = reactive({ open: false, taskId: '', reason: '' });
const memberDialog = reactive({
open: false,
loading: false,
taskId: '',
taskNo: '',
taskRewardCents: 0,
cleanerUserId: '',
rewardCents: 0,
note: '',
members: [] as CleaningTaskMember[]
});
function taskTag(status: TaskStatus) {
if (status === 'SUBMITTED') return 'warning';
@@ -202,4 +282,58 @@ function submitReject() {
emit('reject', { taskId: rejectDialog.taskId, reason: rejectDialog.reason });
rejectDialog.open = false;
}
async function loadMembers() {
memberDialog.loading = true;
try {
memberDialog.members = await listCleaningTaskMembers(props.session, memberDialog.taskId);
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '成员加载失败');
} finally {
memberDialog.loading = false;
}
}
async function openMembers(row: CleaningTask) {
memberDialog.open = true;
memberDialog.taskId = row.id;
memberDialog.taskNo = row.taskNo;
memberDialog.taskRewardCents = row.rewardCents;
memberDialog.cleanerUserId = '';
memberDialog.rewardCents = 0;
memberDialog.note = '';
await loadMembers();
}
async function submitMember() {
try {
memberDialog.members = await addCleaningTaskMember(props.session, memberDialog.taskId, {
cleanerUserId: memberDialog.cleanerUserId,
rewardCents: memberDialog.rewardCents,
note: memberDialog.note
});
memberDialog.cleanerUserId = '';
memberDialog.rewardCents = 0;
memberDialog.note = '';
ElMessage.success('协作者已更新');
emit('refresh');
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '协作者更新失败');
}
}
async function removeMember(cleanerUserId: string) {
try {
memberDialog.members = await removeCleaningTaskMember(
props.session,
memberDialog.taskId,
cleanerUserId,
'后台移除协作者'
);
ElMessage.success('协作者已移除');
emit('refresh');
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '协作者移除失败');
}
}
</script>