chore: establish V1.1 recovery baseline

This commit is contained in:
2026-09-09 22:05:00 +08:00
commit b46b48eb33
947 changed files with 155471 additions and 0 deletions
@@ -0,0 +1,80 @@
<template>
<div class="appointment-management">
<el-card>
<template #header><div class="card-header"><span>服务预约管理</span>
<el-radio-group v-model="filterStatus" size="small" @change="loadAppointments">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="submitted">待确认</el-radio-button>
<el-radio-button label="confirmed">已确认</el-radio-button>
<el-radio-button label="completed">已完成</el-radio-button>
<el-radio-button label="cancelled">已取消</el-radio-button>
</el-radio-group>
</div></template>
<el-table :data="appointments" stripe v-loading="loading">
<el-table-column prop="id" label="ID" width="100" />
<el-table-column prop="userId" label="用户ID" width="120" />
<el-table-column prop="serviceType" label="服务类型" width="150" />
<el-table-column prop="source" label="来源" width="140">
<template #default="{ row }"><el-tag size="small">{{ sourceLabel(row.source) }}</el-tag></template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }"><el-tag :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag></template>
</el-table-column>
<el-table-column prop="preferredDate" label="预约日期" width="110" />
<el-table-column prop="assignedOperatorId" label="分配人员" width="100" />
<el-table-column prop="createdAt" label="创建时间" width="160">
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button v-if="row.status==='submitted'||row.status==='pending_confirmation'" size="small" type="success" @click="confirmAppointment(row)">确认</el-button>
<el-button v-if="row.status==='confirmed'" size="small" type="primary" @click="completeAppointment(row)">完成</el-button>
<el-button v-if="row.status==='submitted'||row.status==='pending_confirmation'" size="small" type="danger" @click="cancelAppointment(row)">取消</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { get, post } from '@/api/client'
import { ElMessage } from 'element-plus'
const loading = ref(false)
const appointments = ref<any[]>([])
const filterStatus = ref('all')
const SOURCE_LABEL: Record<string, string> = { user_self: '用户自申请', matchmaker_referral: '红娘转介', operator_assisted: '工作人员协助', activity_followup: '活动跟进', service_station: '服务站推荐' }
const STATUS_LABEL: Record<string, string> = { submitted: '已提交', pending_confirmation: '待确认', confirmed: '已确认', completed: '已完成', cancelled: '已取消' }
const STATUS_TAG: Record<string, string> = { submitted: 'warning', pending_confirmation: 'warning', confirmed: 'success', completed: 'info', cancelled: 'danger' }
function sourceLabel(s: string) { return SOURCE_LABEL[s] || s }
function statusLabel(s: string) { return STATUS_LABEL[s] || s }
function statusTag(s: string) { return STATUS_TAG[s] || '' }
function formatDateTime(d: string) { return d ? new Date(d).toLocaleString('zh-CN') : '-' }
async function loadAppointments() {
loading.value = true
try {
const res: any = await get('/operator/service-appointments', { status: filterStatus.value === 'all' ? undefined : filterStatus.value })
appointments.value = res.data || []
} finally { loading.value = false }
}
async function confirmAppointment(row: any) {
try { await post(`/service-appointments/operator/${row.id}/confirm`, {}); ElMessage.success('确认成功'); loadAppointments() }
catch (e) { ElMessage.error('操作失败') }
}
async function completeAppointment(row: any) {
try { await post(`/service-appointments/operator/${row.id}/complete`, {}); ElMessage.success('完成成功'); loadAppointments() }
catch (e) { ElMessage.error('操作失败') }
}
async function cancelAppointment(row: any) {
try { await post(`/service-appointments/operator/${row.id}/cancel`, { reason: '运营取消' }); ElMessage.success('取消成功'); loadAppointments() }
catch (e) { ElMessage.error('操作失败') }
}
onMounted(loadAppointments)
</script>
<style scoped>.card-header{display:flex;justify-content:space-between;align-items:center}</style>