fix: repair admin page api contracts
This commit is contained in:
@@ -31,8 +31,8 @@
|
||||
<div v-if="currentActivity">
|
||||
<h4>{{ currentActivity.title }} — 双向互选列表</h4>
|
||||
<el-table :data="mutualList" border size="small" style="margin-top:16px">
|
||||
<el-table-column prop="sourceName" label="表达者" width="120" />
|
||||
<el-table-column prop="targetName" label="被表达者" width="120" />
|
||||
<el-table-column prop="sourceUserId" label="表达者ID" min-width="180" />
|
||||
<el-table-column prop="targetUserId" label="被表达者ID" min-width="180" />
|
||||
<el-table-column prop="confirmedAt" label="确认时间" width="180" />
|
||||
</el-table>
|
||||
<el-empty v-if="!mutualList.length" description="暂无双向互选记录" />
|
||||
@@ -57,7 +57,10 @@ async function loadData() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/activities/operator/activity-interest-summary', {}, { Authorization: `Bearer ${token}` })
|
||||
tableData.value = res || []
|
||||
const rows = res.data || []
|
||||
tableData.value = searchActivityId.value
|
||||
? rows.filter((row: any) => row.activityId.includes(searchActivityId.value.trim()))
|
||||
: rows
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
@@ -67,14 +70,11 @@ async function loadData() {
|
||||
|
||||
async function viewMutuals(activityId: string) {
|
||||
drawerVisible.value = true
|
||||
currentActivity.value = tableData.value.find(row => row.activityId === activityId)
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const [actRes, mutualRes]: any = await Promise.all([
|
||||
get(`/activities/${activityId}`, {}, { Authorization: `Bearer ${token}` }),
|
||||
get(`/activities/${activityId}/interests/mutual`, {}, { Authorization: `Bearer ${token}` })
|
||||
])
|
||||
currentActivity.value = actRes
|
||||
mutualList.value = mutualRes || []
|
||||
const mutualRes: any = await get(`/activities/operator/${activityId}/mutuals`, {}, { Authorization: `Bearer ${token}` })
|
||||
mutualList.value = mutualRes.data || []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
@@ -122,34 +122,37 @@ async function loadFunnel() {
|
||||
}, { Authorization: `Bearer ${token}` }),
|
||||
get('/operator/analytics/safety', {}, { Authorization: `Bearer ${token}` })
|
||||
])
|
||||
funnelStats.value = userRes
|
||||
matchFunnelStats.value = matchRes
|
||||
safetyStats.value = safetyRes
|
||||
const userData = userRes.data || {}
|
||||
const matchData = matchRes.data || {}
|
||||
const safetyData = safetyRes.data || {}
|
||||
funnelStats.value = userData
|
||||
matchFunnelStats.value = matchData
|
||||
safetyStats.value = safetyData
|
||||
|
||||
// 更新漏斗显示
|
||||
funnelStages[0].value = userRes.totalRegistered || 0
|
||||
funnelStages[1].value = userRes.verifiedCount || 0
|
||||
funnelStages[1].rate = userRes.verifyRate || '-'
|
||||
funnelStages[2].value = userRes.profileApproved || 0
|
||||
funnelStages[2].rate = userRes.approvalRate || '-'
|
||||
funnelStages[3].value = userRes.activeUsers || 0
|
||||
funnelStages[3].rate = userRes.activeRate || '-'
|
||||
funnelStages[0].value = userData.totalRegistered || 0
|
||||
funnelStages[1].value = userData.verifiedCount || 0
|
||||
funnelStages[1].rate = userData.verifyRate || '-'
|
||||
funnelStages[2].value = userData.profileApproved || 0
|
||||
funnelStages[2].rate = userData.approvalRate || '-'
|
||||
funnelStages[3].value = userData.activeUsers || 0
|
||||
funnelStages[3].rate = userData.activeRate || '-'
|
||||
|
||||
matchStages[0].value = matchRes.totalRecommended || 0
|
||||
matchStages[1].value = matchRes.totalLiked || 0
|
||||
matchStages[1].rate = matchRes.likeRate || '-'
|
||||
matchStages[2].value = matchRes.totalMatches || 0
|
||||
matchStages[2].rate = matchRes.matchRate || '-'
|
||||
matchStages[3].value = matchRes.chatsStarted || 0
|
||||
matchStages[3].rate = matchRes.chatRate || '-'
|
||||
matchStages[4].value = matchRes.gettingToKnow || 0
|
||||
matchStages[5].value = matchRes.successPairs || 0
|
||||
matchStages[5].rate = matchRes.successRate || '-'
|
||||
matchStages[0].value = matchData.totalRecommended || 0
|
||||
matchStages[1].value = matchData.totalLiked || 0
|
||||
matchStages[1].rate = matchData.likeRate || '-'
|
||||
matchStages[2].value = matchData.totalMatches || 0
|
||||
matchStages[2].rate = matchData.matchRate || '-'
|
||||
matchStages[3].value = matchData.chatsStarted || 0
|
||||
matchStages[3].rate = matchData.chatRate || '-'
|
||||
matchStages[4].value = matchData.gettingToKnow || 0
|
||||
matchStages[5].value = matchData.successPairs || 0
|
||||
matchStages[5].rate = matchData.successRate || '-'
|
||||
|
||||
safetyCards[0].value = safetyRes.pendingReports || 0
|
||||
safetyCards[1].value = safetyRes.resolvedReports || 0
|
||||
safetyCards[2].value = safetyRes.highRiskUsers || 0
|
||||
safetyCards[3].value = safetyRes.bannedUsers || 0
|
||||
safetyCards[0].value = safetyData.pendingReports || 0
|
||||
safetyCards[1].value = safetyData.resolvedReports || 0
|
||||
safetyCards[2].value = safetyData.highRiskUsers || 0
|
||||
safetyCards[3].value = safetyData.bannedUsers || 0
|
||||
}
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
@@ -158,7 +161,7 @@ async function loadDailySummary() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/analytics/daily-summary', { days: 30 }, { Authorization: `Bearer ${token}` })
|
||||
dailySummary.value = res || []
|
||||
dailySummary.value = res.data || []
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ async function loadData() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/appeals/pending', {}, { Authorization: `Bearer ${token}` })
|
||||
tableData.value = res || []
|
||||
tableData.value = res.data || []
|
||||
loadStats()
|
||||
} catch (e) { console.error(e) }
|
||||
finally { loading.value = false }
|
||||
@@ -120,12 +120,13 @@ async function loadStats() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/appeals/stats', {}, { Authorization: `Bearer ${token}` })
|
||||
const data = res.data || {}
|
||||
stats.value = [
|
||||
{ key: 'pending', label: '待审核', value: res.pendingCount || 0, color: '#E96B6B' },
|
||||
{ key: 'reviewing', label: '审核中', value: res.reviewingCount || 0, color: '#E59A32' },
|
||||
{ key: 'approved', label: '已通过', value: res.approvedCount || 0, color: '#67C23A' },
|
||||
{ key: 'rejected', label: '已驳回', value: res.rejectedCount || 0, color: '#909399' },
|
||||
{ key: 'escalated', label: '已升级', value: res.escalatedCount || 0, color: '#F56C6C' }
|
||||
{ key: 'pending', label: '待审核', value: data.pendingCount || 0, color: '#E96B6B' },
|
||||
{ key: 'reviewing', label: '审核中', value: data.reviewingCount || 0, color: '#E59A32' },
|
||||
{ key: 'approved', label: '已通过', value: data.approvedCount || 0, color: '#67C23A' },
|
||||
{ key: 'rejected', label: '已驳回', value: data.rejectedCount || 0, color: '#909399' },
|
||||
{ key: 'escalated', label: '已升级', value: data.escalatedCount || 0, color: '#F56C6C' }
|
||||
]
|
||||
} catch {}
|
||||
}
|
||||
@@ -138,7 +139,7 @@ async function openDetail(id: string) {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get(`/operator/appeals/${id}`, {}, { Authorization: `Bearer ${token}` })
|
||||
detail.value = res
|
||||
detail.value = res.data
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
</template>
|
||||
<el-table :data="banners" v-loading="loading" stripe>
|
||||
<el-table-column prop="title" label="标题" width="150" />
|
||||
<el-table-column prop="target_type" label="跳转类型" width="120">
|
||||
<el-table-column prop="targetType" label="跳转类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ bannerTargetTypeLabel(row.target_type) }}</el-tag>
|
||||
<el-tag size="small">{{ bannerTargetTypeLabel(row.targetType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="audience_scope" label="展示范围" width="120">
|
||||
<el-table-column prop="audienceScope" label="展示范围" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="audienceScopeTag(row.audience_scope)">{{ audienceScopeLabel(row.audience_scope) }}</el-tag>
|
||||
<el-tag size="small" :type="audienceScopeTag(row.audienceScope)">{{ audienceScopeLabel(row.audienceScope) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
@@ -24,10 +24,10 @@
|
||||
<el-tag :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort_order" label="排序" width="80" />
|
||||
<el-table-column prop="sortOrder" label="排序" width="80" />
|
||||
<el-table-column label="时间范围" width="200">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDateTime(row.start_at) }} ~ {{ formatDateTime(row.end_at) }}</span>
|
||||
<span>{{ formatDateTime(row.startAt) }} ~ {{ formatDateTime(row.endAt) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
@@ -47,7 +47,7 @@
|
||||
<el-form-item label="标题"><el-input v-model="form.title" /></el-form-item>
|
||||
<el-form-item label="副标题"><el-input v-model="form.subtitle" /></el-form-item>
|
||||
<el-form-item label="跳转类型">
|
||||
<el-select v-model="form.target_type">
|
||||
<el-select v-model="form.targetType">
|
||||
<el-option label="公益活动" value="activity" />
|
||||
<el-option label="公益红娘" value="matchmaker" />
|
||||
<el-option label="婚恋咨询" value="counseling" />
|
||||
@@ -58,9 +58,9 @@
|
||||
<el-option label="无" value="none" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标值"><el-input v-model="form.target_value" placeholder="活动ID/文章ID等" /></el-form-item>
|
||||
<el-form-item label="目标值"><el-input v-model="form.targetValue" placeholder="活动ID/文章ID等" /></el-form-item>
|
||||
<el-form-item label="展示范围">
|
||||
<el-select v-model="form.audience_scope">
|
||||
<el-select v-model="form.audienceScope">
|
||||
<el-option label="全部用户" value="all" />
|
||||
<el-option label="未注册" value="onboarding" />
|
||||
<el-option label="正在寻找" value="searching" />
|
||||
@@ -68,9 +68,9 @@
|
||||
<el-option label="成功牵手" value="successful" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序"><el-input-number v-model="form.sort_order" :min="0" /></el-form-item>
|
||||
<el-form-item label="展示开始"><el-date-picker v-model="form.start_at" type="datetime" /></el-form-item>
|
||||
<el-form-item label="展示结束"><el-date-picker v-model="form.end_at" type="datetime" /></el-form-item>
|
||||
<el-form-item label="排序"><el-input-number v-model="form.sortOrder" :min="0" /></el-form-item>
|
||||
<el-form-item label="展示开始"><el-date-picker v-model="form.startAt" type="datetime" /></el-form-item>
|
||||
<el-form-item label="展示结束"><el-date-picker v-model="form.endAt" type="datetime" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
@@ -89,7 +89,7 @@ const loading = ref(false)
|
||||
const banners = ref<any[]>([])
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref<string | null>(null)
|
||||
const form = ref({ title: '', subtitle: '', target_type: 'none', target_value: '', audience_scope: 'all', sort_order: 0, start_at: '', end_at: '' })
|
||||
const form = ref({ title: '', subtitle: '', targetType: 'none', targetValue: '', audienceScope: 'all', sortOrder: 0, startAt: '', endAt: '' })
|
||||
|
||||
const TARGET_TYPE_LABEL: Record<string, string> = { activity: '活动', matchmaker: '红娘', counseling: '咨询', premarital: '婚前辅导', service_location: '服务站', article: '文章', internal_page: '内部页', none: '无' }
|
||||
const AUDIENCE_SCOPE_LABEL: Record<string, string> = { all: '全部', onboarding: '未注册', searching: '寻找中', getting_to_know: '初步了解', successful: '成功牵手' }
|
||||
@@ -106,21 +106,21 @@ function formatDateTime(d: string) { return d ? new Date(d).toLocaleString('zh-C
|
||||
async function loadBanners() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await get('/home/banners')
|
||||
const res: any = await get('/operator/banners')
|
||||
banners.value = res.data || []
|
||||
} catch (e) { console.error(e) } finally { loading.value = false }
|
||||
}
|
||||
|
||||
function openCreate() { editingId.value = null; Object.assign(form.value, { title: '', subtitle: '', target_type: 'none', target_value: '', audience_scope: 'all', sort_order: 0, start_at: '', end_at: '' }); dialogVisible.value = true }
|
||||
function openEdit(row: any) { editingId.value = row.id; Object.assign(form.value, { title: row.title, subtitle: row.subtitle, target_type: row.target_type, target_value: row.target_value, audience_scope: row.audience_scope, sort_order: row.sort_order, start_at: row.start_at, end_at: row.end_at }); dialogVisible.value = true }
|
||||
function openCreate() { editingId.value = null; Object.assign(form.value, { title: '', subtitle: '', targetType: 'none', targetValue: '', audienceScope: 'all', sortOrder: 0, startAt: '', endAt: '' }); dialogVisible.value = true }
|
||||
function openEdit(row: any) { editingId.value = row.id; Object.assign(form.value, { title: row.title, subtitle: row.subtitle, targetType: row.targetType, targetValue: row.targetValue, audienceScope: row.audienceScope, sortOrder: row.sortOrder, startAt: row.startAt, endAt: row.endAt }); dialogVisible.value = true }
|
||||
|
||||
async function saveBanner() {
|
||||
try {
|
||||
if (editingId.value) {
|
||||
await put(`/home/banners/${editingId.value}`, form.value)
|
||||
await put(`/operator/banners/${editingId.value}`, form.value)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await post('/home/banners', form.value)
|
||||
await post('/operator/banners', form.value)
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
@@ -131,7 +131,7 @@ async function saveBanner() {
|
||||
async function toggleStatus(row: any) {
|
||||
try {
|
||||
const newStatus = row.status === 'active' ? 'inactive' : 'active'
|
||||
await put(`/home/banners/${row.id}`, { ...row, status: newStatus })
|
||||
await put(`/operator/banners/${row.id}`, { ...row, status: newStatus })
|
||||
await loadBanners()
|
||||
} catch (e) { ElMessage.error('操作失败') }
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ async function loadData() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/operators', { page: page.value, pageSize: pageSize.value }, { Authorization: `Bearer ${token}` })
|
||||
tableData.value = res.list || []
|
||||
total.value = res.total || 0
|
||||
tableData.value = res.data?.list || []
|
||||
total.value = res.data?.total || 0
|
||||
} catch (e) { console.error(e) }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
@@ -91,7 +91,7 @@ async function loadOrgs() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/operators/organizations', {}, { Authorization: `Bearer ${token}` })
|
||||
orgList.value = res || []
|
||||
orgList.value = res.data || []
|
||||
orgList.value.forEach(o => { orgMap.value[o.id] = o.name })
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ async function loadData() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/organizations', {}, { Authorization: `Bearer ${token}` })
|
||||
tableData.value = res || []
|
||||
tableData.value = res.data || []
|
||||
} catch (e) { console.error(e) }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ async function loadData() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get(`/operator/risk/reports/${statusFilter.value}`, {}, { Authorization: `Bearer ${token}` })
|
||||
tableData.value = res || []
|
||||
tableData.value = res.data || []
|
||||
loadStats()
|
||||
} catch (e) { console.error(e) }
|
||||
finally { loading.value = false }
|
||||
@@ -135,11 +135,12 @@ async function loadStats() {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get('/operator/risk/reports/stats', {}, { Authorization: `Bearer ${token}` })
|
||||
const data = res.data || {}
|
||||
stats.value = [
|
||||
{ key: 'pending', label: '待处理', value: res.pendingCount || 0, color: '#E96B6B' },
|
||||
{ key: 'processing', label: '处理中', value: res.processingCount || 0, color: '#E59A32' },
|
||||
{ key: 'resolved', label: '已处理', value: res.resolvedCount || 0, color: '#67C23A' },
|
||||
{ key: 'dismissed', label: '已驳回', value: res.dismissedCount || 0, color: '#909399' }
|
||||
{ key: 'pending', label: '待处理', value: data.pendingCount || 0, color: '#E96B6B' },
|
||||
{ key: 'processing', label: '处理中', value: data.processingCount || 0, color: '#E59A32' },
|
||||
{ key: 'resolved', label: '已处理', value: data.resolvedCount || 0, color: '#67C23A' },
|
||||
{ key: 'dismissed', label: '已驳回', value: data.dismissedCount || 0, color: '#909399' }
|
||||
]
|
||||
} catch {}
|
||||
}
|
||||
@@ -152,7 +153,7 @@ async function openDetail(reportId: string) {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken')
|
||||
const res: any = await get(`/operator/risk/reports/${reportId}`, {}, { Authorization: `Bearer ${token}` })
|
||||
detail.value = res
|
||||
detail.value = res.data
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<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-button value="all">全部</el-radio-button>
|
||||
<el-radio-button value="submitted">待确认</el-radio-button>
|
||||
<el-radio-button value="confirmed">已确认</el-radio-button>
|
||||
<el-radio-button value="completed">已完成</el-radio-button>
|
||||
<el-radio-button value="cancelled">已取消</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div></template>
|
||||
<el-table :data="appointments" stripe v-loading="loading">
|
||||
@@ -57,8 +57,9 @@ function formatDateTime(d: string) { return d ? new Date(d).toLocaleString('zh-C
|
||||
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 || []
|
||||
const res: any = await get('/service-appointments/operator/list')
|
||||
const rows = res.data || []
|
||||
appointments.value = filterStatus.value === 'all' ? rows : rows.filter((row: any) => row.status === filterStatus.value)
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<template #header><div class="card-header"><span>善缘服务站管理</span><el-button type="primary" size="small" @click="openCreate">新建服务站</el-button></div></template>
|
||||
<el-table :data="stations" stripe v-loading="loading">
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="short_name" label="简称" width="120" />
|
||||
<el-table-column prop="shortName" label="简称" width="120" />
|
||||
<el-table-column prop="address" label="地址" show-overflow-tooltip />
|
||||
<el-table-column prop="contact_phone_display" label="联系电话" width="130" />
|
||||
<el-table-column prop="opening_hours_text" label="营业时间" width="180">
|
||||
<template #default="{ row }">{{ row.opening_hours_text || '-' }}</template>
|
||||
<el-table-column prop="contactPhoneDisplay" label="联系电话" width="130" />
|
||||
<el-table-column prop="openingHoursText" label="营业时间" width="180">
|
||||
<template #default="{ row }">{{ row.openingHoursText || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }"><el-tag :type="row.status==='active'?'success':row.status==='closed'?'danger':'info'">{{ row.status }}</el-tag></template>
|
||||
@@ -24,10 +24,10 @@
|
||||
<el-dialog v-model="dialogVisible" title="编辑服务站" width="600px">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="名称"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="简称"><el-input v-model="form.short_name" /></el-form-item>
|
||||
<el-form-item label="简称"><el-input v-model="form.shortName" /></el-form-item>
|
||||
<el-form-item label="地址"><el-input v-model="form.address" /></el-form-item>
|
||||
<el-form-item label="电话显示"><el-input v-model="form.contact_phone_display" /></el-form-item>
|
||||
<el-form-item label="营业时间"><el-input v-model="form.opening_hours_text" type="textarea" :rows="3" /></el-form-item>
|
||||
<el-form-item label="电话显示"><el-input v-model="form.contactPhoneDisplay" /></el-form-item>
|
||||
<el-form-item label="营业时间"><el-input v-model="form.openingHoursText" type="textarea" :rows="3" /></el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="form.status">
|
||||
<el-option label="营业中" value="active" />
|
||||
@@ -63,7 +63,7 @@ async function loadStations() {
|
||||
}
|
||||
|
||||
function openEdit(row: any) { Object.assign(form.value, row); dialogVisible.value = true }
|
||||
function openCreate() { Object.assign(form.value, { name: '', short_name: '', address: '', contact_phone_display: '', opening_hours_text: '', status: 'active' }); dialogVisible.value = true }
|
||||
function openCreate() { Object.assign(form.value, { id: undefined, name: '', shortName: '', address: '', contactPhoneDisplay: '', openingHoursText: '', status: 'active' }); dialogVisible.value = true }
|
||||
|
||||
async function saveStation() {
|
||||
try {
|
||||
|
||||
@@ -90,8 +90,8 @@ async function loadData() {
|
||||
page: page.value,
|
||||
pageSize: pageSize.value
|
||||
}, { Authorization: `Bearer ${token}` })
|
||||
tableData.value = res.list || []
|
||||
total.value = res.total || 0
|
||||
tableData.value = res.data?.list || []
|
||||
total.value = res.data?.total || 0
|
||||
} catch (e) { console.error(e) }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
+23
@@ -3,6 +3,10 @@ package com.danyang.match.platform.activity.controller;
|
||||
import com.danyang.match.platform.activity.dto.ActivityFeedbackDTO;
|
||||
import com.danyang.match.platform.activity.dto.ActivityMatchItemDTO;
|
||||
import com.danyang.match.platform.activity.service.ActivityInterestService;
|
||||
import com.danyang.match.platform.activity.mapper.ActivityMapper;
|
||||
import com.danyang.match.platform.activity.entity.Activity;
|
||||
import com.danyang.match.platform.activity.entity.ActivityInterest;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
|
||||
import com.danyang.match.platform.relationship.service.RelationshipService;
|
||||
import com.danyang.match.platform.shared.dto.ApiResponse;
|
||||
@@ -26,6 +30,25 @@ public class ActivityInterestController {
|
||||
|
||||
private final ActivityInterestService interestService;
|
||||
private final RelationshipService relationshipService;
|
||||
private final ActivityMapper activityMapper;
|
||||
|
||||
@GetMapping("/operator/activity-interest-summary")
|
||||
@PreAuthorize("hasAuthority('activity.view')")
|
||||
public ApiResponse<List<Map<String, Object>>> getActivityInterestSummary() {
|
||||
return ApiResponse.ok(activityMapper.selectList(new LambdaQueryWrapper<Activity>()
|
||||
.isNull(Activity::getDeletedAt).orderByDesc(Activity::getCreatedAt)).stream().map(activity -> {
|
||||
Map<String, Object> item = new HashMap<>(interestService.getActivityInterestStats(activity.getId()));
|
||||
item.put("activityTitle", activity.getTitle());
|
||||
item.put("feedbackCount", 0);
|
||||
return item;
|
||||
}).toList());
|
||||
}
|
||||
|
||||
@GetMapping("/operator/{id}/mutuals")
|
||||
@PreAuthorize("hasAuthority('activity.view')")
|
||||
public ApiResponse<List<ActivityInterest>> getMutualsForOperator(@PathVariable String id) {
|
||||
return ApiResponse.ok(interestService.getMutualInterestsForOperator(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交活动反馈 + 表达意向
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ public class ActivityOperatorController {
|
||||
@AuthenticationPrincipal OperatorPrincipal principal) {
|
||||
List<Activity> activities = activityMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Activity>()
|
||||
.eq(Activity::getDeletedAt, 0)
|
||||
.isNull(Activity::getDeletedAt)
|
||||
.orderByDesc(Activity::getCreatedAt)
|
||||
);
|
||||
return ApiResponse.ok(activities.stream().map(a -> {
|
||||
|
||||
+4
@@ -207,6 +207,10 @@ public class ActivityInterestService {
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<ActivityInterest> getMutualInterestsForOperator(String activityId) {
|
||||
return interestMapper.findMutualsByActivity(activityId);
|
||||
}
|
||||
|
||||
// ==================== 私有辅助方法 ====================
|
||||
|
||||
private List<String> getApprovedParticipants(String activityId) {
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.danyang.match.platform.banner.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.danyang.match.platform.banner.entity.HomeBanner;
|
||||
import com.danyang.match.platform.banner.mapper.HomeBannerMapper;
|
||||
import com.danyang.match.platform.shared.dto.ApiResponse;
|
||||
import com.danyang.match.platform.shared.enums.ApiResponseCode;
|
||||
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/operator/banners")
|
||||
@PreAuthorize("hasAuthority('banner.view')")
|
||||
@RequiredArgsConstructor
|
||||
public class HomeBannerOperatorController {
|
||||
private final HomeBannerMapper bannerMapper;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<HomeBanner>> list() {
|
||||
return ApiResponse.ok(bannerMapper.selectList(new LambdaQueryWrapper<HomeBanner>()
|
||||
.isNull(HomeBanner::getDeletedAt).orderByAsc(HomeBanner::getSortOrder)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<HomeBanner> create(@AuthenticationPrincipal OperatorPrincipal principal,
|
||||
@RequestBody HomeBanner banner) {
|
||||
banner.setId(null);
|
||||
banner.setCreatedBy(principal.id());
|
||||
banner.setUpdatedBy(principal.id());
|
||||
bannerMapper.insert(banner);
|
||||
return ApiResponse.ok(banner);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<HomeBanner> update(@AuthenticationPrincipal OperatorPrincipal principal,
|
||||
@PathVariable String id,
|
||||
@RequestBody HomeBanner banner) {
|
||||
if (bannerMapper.selectById(id) == null) return ApiResponse.error(ApiResponseCode.NOT_FOUND);
|
||||
banner.setId(id);
|
||||
banner.setUpdatedBy(principal.id());
|
||||
bannerMapper.updateById(banner);
|
||||
return ApiResponse.ok(bannerMapper.selectById(id));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -35,7 +35,7 @@ public class DiscoverService {
|
||||
LambdaQueryWrapper<UserProfile> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.ne(UserProfile::getGender, myGender)
|
||||
.eq(UserProfile::getProfileStatus, Constants.PROFILE_STATUS_APPROVED)
|
||||
.eq(UserProfile::getDeletedAt, 0);
|
||||
.isNull(UserProfile::getDeletedAt);
|
||||
|
||||
// 应用临时筛选条件
|
||||
if (tempFilter.containsKey("minAge")) {
|
||||
|
||||
+2
-1
@@ -266,7 +266,8 @@ public class HomeApplicationService {
|
||||
}
|
||||
|
||||
private boolean isProfileApproved(User user) {
|
||||
String status = user.getProfileStatus();
|
||||
String status = userProfileMapper.findByUserId(user.getId())
|
||||
.map(UserProfile::getProfileStatus).orElse(null);
|
||||
return Constants.PROFILE_STATUS_APPROVED.equals(status)
|
||||
|| Constants.PROFILE_STATUS_COMPLETED.equals(status);
|
||||
}
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ public class RecommendationEngineService {
|
||||
wrapper.isNotNull(UserProfile::getGender)
|
||||
.ne(UserProfile::getGender, getMyGender(userId))
|
||||
.eq(UserProfile::getProfileStatus, Constants.PROFILE_STATUS_APPROVED)
|
||||
.eq(UserProfile::getDeletedAt, 0);
|
||||
.isNull(UserProfile::getDeletedAt);
|
||||
|
||||
// 排除当前用户已Block的人
|
||||
List<String> blockedIds = relationMapper.findBlockedByUser(userId)
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ public class MessagingService {
|
||||
new LambdaQueryWrapper<Message>()
|
||||
.eq(Message::getSenderId, userId)
|
||||
.eq(Message::getClientMessageId, clientMessageId)
|
||||
.eq(Message::getDeletedAt, null));
|
||||
.isNull(Message::getDeletedAt));
|
||||
if (existing != null) {
|
||||
return toMessageDTO(existing, userId);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -94,7 +95,7 @@ public class PhotoService {
|
||||
photoMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<UserPhoto>()
|
||||
.eq(UserPhoto::getUserId, userId)
|
||||
.eq(UserPhoto::getPhotoType, "avatar")
|
||||
.set(UserPhoto::getDeletedAt, 1));
|
||||
.set(UserPhoto::getDeletedAt, Instant.now()));
|
||||
}
|
||||
|
||||
UserPhoto photo = new UserPhoto();
|
||||
@@ -213,7 +214,7 @@ public class PhotoService {
|
||||
public void deletePhoto(String photoId) {
|
||||
photoMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<UserPhoto>()
|
||||
.eq(UserPhoto::getId, photoId)
|
||||
.set(UserPhoto::getDeletedAt, 1));
|
||||
.set(UserPhoto::getDeletedAt, Instant.now()));
|
||||
log.info("照片已软删除: photoId={}", photoId);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,5 +43,5 @@ public class UserProfile {
|
||||
private Instant updatedAt;
|
||||
|
||||
@TableField("deleted_at")
|
||||
private Integer deletedAt;
|
||||
private Instant deletedAt;
|
||||
}
|
||||
|
||||
+4
-4
@@ -110,7 +110,7 @@ public class RelationshipStatusService {
|
||||
RelationshipSuccessRequest existing = successRequestMapper.selectOne(
|
||||
new LambdaQueryWrapper<RelationshipSuccessRequest>()
|
||||
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
|
||||
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
|
||||
.isNull(RelationshipSuccessRequest::getDeletedAt)
|
||||
.orderByDesc(RelationshipSuccessRequest::getCreatedAt)
|
||||
.last("LIMIT 1"));
|
||||
|
||||
@@ -177,7 +177,7 @@ public class RelationshipStatusService {
|
||||
RelationshipSuccessRequest request = successRequestMapper.selectOne(
|
||||
new LambdaQueryWrapper<RelationshipSuccessRequest>()
|
||||
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
|
||||
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
|
||||
.isNull(RelationshipSuccessRequest::getDeletedAt)
|
||||
.orderByDesc(RelationshipSuccessRequest::getCreatedAt)
|
||||
.last("LIMIT 1"));
|
||||
|
||||
@@ -206,7 +206,7 @@ public class RelationshipStatusService {
|
||||
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
|
||||
.eq(RelationshipSuccessRequest::getInitiatedBy, otherUserId)
|
||||
.eq(RelationshipSuccessRequest::getStatus, Constants.SUCCESS_REQUEST_STATUS_CONFIRMED)
|
||||
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
|
||||
.isNull(RelationshipSuccessRequest::getDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
|
||||
// 5. 标记当前用户已确认
|
||||
@@ -281,7 +281,7 @@ public class RelationshipStatusService {
|
||||
RelationshipSuccessRequest request = successRequestMapper.selectOne(
|
||||
new LambdaQueryWrapper<RelationshipSuccessRequest>()
|
||||
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
|
||||
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
|
||||
.isNull(RelationshipSuccessRequest::getDeletedAt)
|
||||
.orderByDesc(RelationshipSuccessRequest::getCreatedAt)
|
||||
.last("LIMIT 1"));
|
||||
|
||||
|
||||
+10
@@ -90,4 +90,14 @@ public class ServiceAppointmentController {
|
||||
return ApiResponse.ok(serviceAppointmentService.completeAppointment(id, principal.id()));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('service_appointment.confirm')")
|
||||
@PostMapping("/operator/{id}/cancel")
|
||||
public ApiResponse<ServiceAppointmentDTO> cancelByOperator(
|
||||
@AuthenticationPrincipal OperatorPrincipal principal,
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, String> body) {
|
||||
return ApiResponse.ok(serviceAppointmentService.cancelAppointmentByOperator(
|
||||
id, principal.id(), body.getOrDefault("reason", "运营取消")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.danyang.match.platform.service_appointment.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.danyang.match.platform.service_appointment.entity.ServiceLocation;
|
||||
import com.danyang.match.platform.service_appointment.mapper.ServiceLocationMapper;
|
||||
import com.danyang.match.platform.shared.dto.ApiResponse;
|
||||
import com.danyang.match.platform.shared.enums.ApiResponseCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/operator/service-locations")
|
||||
@PreAuthorize("hasAuthority('service_location.view')")
|
||||
@RequiredArgsConstructor
|
||||
public class ServiceLocationOperatorController {
|
||||
private final ServiceLocationMapper locationMapper;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<ServiceLocation>> list() {
|
||||
return ApiResponse.ok(locationMapper.selectList(new LambdaQueryWrapper<ServiceLocation>()
|
||||
.isNull(ServiceLocation::getDeletedAt).orderByAsc(ServiceLocation::getName)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<ServiceLocation> create(@RequestBody ServiceLocation location) {
|
||||
location.setId(null);
|
||||
locationMapper.insert(location);
|
||||
return ApiResponse.ok(location);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<ServiceLocation> update(@PathVariable String id, @RequestBody ServiceLocation location) {
|
||||
if (locationMapper.selectById(id) == null) return ApiResponse.error(ApiResponseCode.NOT_FOUND);
|
||||
location.setId(id);
|
||||
locationMapper.updateById(location);
|
||||
return ApiResponse.ok(locationMapper.selectById(id));
|
||||
}
|
||||
}
|
||||
+15
@@ -164,6 +164,21 @@ public class ServiceAppointmentService {
|
||||
return toDTO(appt);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ServiceAppointmentDTO cancelAppointmentByOperator(String appointmentId, String operatorId, String reason) {
|
||||
ServiceAppointment appt = appointmentMapper.selectById(appointmentId);
|
||||
if (appt == null || Constants.APPOINTMENT_STATUS_COMPLETED.equals(appt.getStatus())
|
||||
|| Constants.APPOINTMENT_STATUS_CANCELLED.equals(appt.getStatus())) {
|
||||
throw new RuntimeException("预约不存在或状态不允许取消");
|
||||
}
|
||||
assertOperatorScope(appointmentId, operatorId);
|
||||
appt.setStatus(Constants.APPOINTMENT_STATUS_CANCELLED);
|
||||
appt.setCancelReason(reason);
|
||||
appt.setUpdatedAt(Instant.now());
|
||||
appointmentMapper.updateById(appt);
|
||||
return toDTO(appt);
|
||||
}
|
||||
|
||||
private void assertOperatorScope(String appointmentId, String operatorId) {
|
||||
if (operatorId == null || !appointmentMapper.canAccessByOperator(appointmentId, operatorId)) {
|
||||
dataScopeGuard.deny(operatorId, "service_appointment", appointmentId,
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.util.UUID;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 自动填充处理器
|
||||
* 自动填充:createdAt、updatedAt、deletedAt、id(UUID)
|
||||
* 自动填充:createdAt、updatedAt、id(UUID)
|
||||
*/
|
||||
@Component
|
||||
public class AutoFillHandler implements MetaObjectHandler {
|
||||
@@ -23,11 +23,10 @@ public class AutoFillHandler implements MetaObjectHandler {
|
||||
}
|
||||
this.strictInsertFill(metaObject, "createdAt", Instant.class, Instant.now());
|
||||
this.strictInsertFill(metaObject, "updatedAt", Instant.class, Instant.now());
|
||||
this.strictInsertFill(metaObject, "deletedAt", Integer.class, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updatedAt", Instant.class, Instant.now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ public abstract class BaseEntity {
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Instant updatedAt;
|
||||
|
||||
// 软删除标记(NOT NULL, 0=未删除, 1=已删除)
|
||||
// 注意:不使用 @TableLogic,由业务代码手动处理
|
||||
private Integer deletedAt;
|
||||
// 软删除时间;null 表示未删除。
|
||||
private Instant deletedAt;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ public class User extends BaseEntity {
|
||||
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
private String profileStatus;
|
||||
|
||||
@TableField("account_status")
|
||||
private String accountStatus;
|
||||
|
||||
|
||||
@@ -59,15 +59,28 @@ public class UserService {
|
||||
query.eq(User::getAccountStatus, accountStatus);
|
||||
}
|
||||
if (profileStatus != null && !profileStatus.isBlank()) {
|
||||
query.eq(User::getProfileStatus, profileStatus);
|
||||
List<String> userIds = userProfileMapper.selectList(
|
||||
new LambdaQueryWrapper<UserProfile>()
|
||||
.eq(UserProfile::getProfileStatus, profileStatus)
|
||||
.select(UserProfile::getUserId))
|
||||
.stream().map(UserProfile::getUserId).toList();
|
||||
query.in(User::getId, userIds.isEmpty()
|
||||
? List.of("00000000-0000-0000-0000-000000000000") : userIds);
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<User> countQuery = new LambdaQueryWrapper<User>();
|
||||
if (keyword != null && !keyword.isBlank()) countQuery.and(q -> q.like(User::getId, keyword).or().like(User::getWechatOpenid, keyword));
|
||||
if (accountStatus != null && !accountStatus.isBlank()) countQuery.eq(User::getAccountStatus, accountStatus);
|
||||
if (profileStatus != null && !profileStatus.isBlank()) countQuery.eq(User::getProfileStatus, profileStatus);
|
||||
if (profileStatus != null && !profileStatus.isBlank()) {
|
||||
List<String> userIds = userProfileMapper.selectList(
|
||||
new LambdaQueryWrapper<UserProfile>()
|
||||
.eq(UserProfile::getProfileStatus, profileStatus)
|
||||
.select(UserProfile::getUserId))
|
||||
.stream().map(UserProfile::getUserId).toList();
|
||||
countQuery.in(User::getId, userIds.isEmpty()
|
||||
? List.of("00000000-0000-0000-0000-000000000000") : userIds);
|
||||
}
|
||||
long totalLong = userMapper.selectCount(countQuery); int total = totalLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) totalLong;
|
||||
long offsetLong = (page != null ? page : 0L) * (pageSize != null ? pageSize : 20L); int offset = offsetLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) offsetLong;
|
||||
int limit = pageSize != null ? pageSize : 20;
|
||||
List<User> users = userMapper.selectPage(
|
||||
new Page((page != null ? page : 0), limit), query
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE content_categories ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE home_banners ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE service_locations ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
@@ -75,4 +75,16 @@ const adminLayout = read('admin-web/src/layouts/MainLayout.vue')
|
||||
assert.match(adminLayout, /善缘丹阳/, '后台品牌名称必须为善缘丹阳')
|
||||
assert.doesNotMatch(adminLayout, /#304156/i, '后台不得继续使用旧式深蓝侧栏')
|
||||
|
||||
const adminApiContracts = [
|
||||
['admin-web/src/modules/banner/BannerManagementView.vue', '/operator/banners'],
|
||||
['admin-web/src/modules/service-station/ServiceStationView.vue', '/operator/service-locations'],
|
||||
['admin-web/src/modules/service-appointment/ServiceAppointmentView.vue', '/service-appointments/operator/list'],
|
||||
['admin-web/src/modules/activity/ActivityInterestView.vue', '/activities/operator/activity-interest-summary'],
|
||||
]
|
||||
for (const [file, endpoint] of adminApiContracts) {
|
||||
assert.ok(read(file).includes(endpoint), `${file} 未使用实际后台接口 ${endpoint}`)
|
||||
}
|
||||
assert.doesNotMatch(read('admin-web/src/modules/risk/ReportCenterView.vue'), /tableData\.value = res \|\|/, '举报列表不能把响应信封当数组')
|
||||
assert.doesNotMatch(read('admin-web/src/modules/appeal/AppealCenterView.vue'), /tableData\.value = res \|\|/, '申诉列表不能把响应信封当数组')
|
||||
|
||||
console.log('UI contracts passed')
|
||||
|
||||
Reference in New Issue
Block a user