xinli/xinlidsj/pages/profile/compare.vue
2026-02-24 16:49:05 +08:00

370 lines
15 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page" :class="{ big: isH5 }">
<view class="card">
<view class="kv">
<view class="kv-item"><text class="k">用户ID</text><text class="v">{{ userId || '—' }}</text></view>
</view>
<view class="card-title">选择对比记录</view>
<picker mode="selector" :range="scaleLabels" :value="selectedScaleIndex" @change="onPickScale">
<view class="picker">量表{{ selectedScaleLabel }}</view>
</picker>
<picker mode="selector" :range="attemptLabels" :value="selectedAttempt1Index" @change="onPickAttempt1">
<view class="picker">测评记录1{{ selectedAttempt1Label }}</view>
</picker>
<picker mode="selector" :range="attemptLabels" :value="selectedAttempt2Index" @change="onPickAttempt2">
<view class="picker">测评记录2{{ selectedAttempt2Label }}</view>
</picker>
<button class="btn primary" :disabled="loading" @click="doCompare">开始对比</button>
<button class="btn ghost" :disabled="loading || !rows.length" @click="copyReport">复制报告</button>
<button class="btn ghost" :disabled="loading || !canOpenReport1" @click="openReport1">查看测评1报告</button>
<button class="btn ghost" :disabled="loading || !canOpenReport2" @click="openReport2">查看测评2报告</button>
<view v-if="errorMsg" class="error">{{ errorMsg }}</view>
</view>
<view class="card">
<view class="card-title">结果</view>
<view v-if="summaryText" class="summary">
<view class="summary-title">对比摘要</view>
<view class="summary-text">{{ summaryText }}</view>
</view>
<view v-if="loading" class="state">
<uni-icons type="spinner-cycle" size="28" color="#94A3B8"></uni-icons>
<view class="state-text">加载中...</view>
</view>
<view v-else>
<view v-if="rows.length === 0" class="state">
<uni-icons type="list" size="34" color="#CBD5E1"></uni-icons>
<view class="state-text">暂无数据</view>
</view>
<view v-else class="list">
<view class="item" v-for="(r, idx) in rows" :key="idx">
<view class="item-head">
<view class="item-title">{{ r.scaleName || ('量表#' + r.scaleId) }}</view>
<view class="pill" :class="{ up: r.pctChange > 0, down: r.pctChange < 0 }">
{{ r.pctChange == null ? '—' : (r.pctChange + '%') }}
</view>
</view>
<view class="item-grid">
<view class="item-desc">得分1{{ r.totalScore1 == null ? '—' : r.totalScore1 }}</view>
<view class="item-desc">得分2{{ r.totalScore2 == null ? '—' : r.totalScore2 }}</view>
<view class="item-desc">差值{{ r.diff == null ? '—' : r.diff }}</view>
<view class="item-desc">测评1{{ formatTime(r.submitTime1) }}</view>
<view class="item-desc">测评2{{ formatTime(r.submitTime2) }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { compareUserAssessment, compareUserAssessmentReport, getUserAssessmentSummary } from '../../api/psychology/assessment'
import UniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue'
export default {
components: {
UniIcons
},
data() {
return {
isH5: false,
userId: '',
summary: null,
scales: [],
selectedScaleIndex: 0,
selectedAttempt1Index: 0,
selectedAttempt2Index: 0,
loading: false,
errorMsg: '',
summaryText: '',
summaryLines: [],
rows: []
}
},
onLoad(options) {
try {
const info = uni.getSystemInfoSync()
const p = info ? (info.uniPlatform || info.platform) : ''
this.isH5 = p === 'web' || p === 'h5'
} catch (e) {
this.isH5 = false
}
this.userId = options && options.userId ? options.userId : ''
this.loadSummary()
},
computed: {
scaleLabels() {
const list = this.scales || []
if (!list.length) return ['暂无量表']
return list.map((s) => (s && s.scaleName) ? s.scaleName : ('量表#' + (s && s.scaleId)))
},
selectedScale() {
const list = this.scales || []
if (!list.length) return null
return list[this.selectedScaleIndex] || list[0] || null
},
selectedScaleLabel() {
if (!this.selectedScale) return '请选择'
return (this.selectedScale.scaleName || ('量表#' + this.selectedScale.scaleId))
},
attempts() {
const s = this.selectedScale
if (!s || !Array.isArray(s.attempts)) return []
return s.attempts
.slice()
.filter((a) => a && a.assessmentId)
.sort((a, b) => {
const ta = a.submitTime ? new Date(a.submitTime).getTime() : 0
const tb = b.submitTime ? new Date(b.submitTime).getTime() : 0
return tb - ta
})
},
attemptLabels() {
const list = this.attempts
if (!list.length) return ['暂无记录']
return list.map((a) => {
const t = a.submitTime || a.startTime
return `${this.formatTime(t)} · 得分 ${a.totalScore == null ? '—' : a.totalScore} · #${a.assessmentId}`
})
},
selectedAttempt1() {
const list = this.attempts
return list[this.selectedAttempt1Index] || null
},
selectedAttempt2() {
const list = this.attempts
return list[this.selectedAttempt2Index] || null
},
selectedAttempt1Label() {
if (!this.selectedAttempt1) return '请选择'
return this.attemptLabels[this.selectedAttempt1Index] || '请选择'
},
selectedAttempt2Label() {
if (!this.selectedAttempt2) return '请选择'
return this.attemptLabels[this.selectedAttempt2Index] || '请选择'
},
canOpenReport1() {
return !!(this.selectedAttempt1 && this.selectedAttempt1.reportId)
},
canOpenReport2() {
return !!(this.selectedAttempt2 && this.selectedAttempt2.reportId)
}
},
methods: {
loadSummary() {
if (!this.userId) {
this.errorMsg = '缺少 userId'
return
}
this.loading = true
this.errorMsg = ''
return getUserAssessmentSummary(this.userId)
.then((res) => {
this.loading = false
const data = res && res.data ? res.data : null
if (!data || data.code !== 200) {
this.errorMsg = (data && data.msg) ? data.msg : '加载失败'
return
}
this.summary = data.data || null
this.scales = (this.summary && Array.isArray(this.summary.scales)) ? this.summary.scales : []
this.selectedScaleIndex = 0
this.selectedAttempt1Index = 0
this.selectedAttempt2Index = 0
})
.catch((e) => {
this.loading = false
this.errorMsg = e && e.message ? e.message : '网络错误'
})
},
toDateTimeString(val) {
if (!val) return ''
try {
const d = new Date(val)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
const ss = String(d.getSeconds()).padStart(2, '0')
return `${y}-${m}-${day} ${hh}:${mm}:${ss}`
} catch (e) {
return ''
}
},
onPickScale(e) {
const idx = e && e.detail ? Number(e.detail.value) : 0
this.selectedScaleIndex = isNaN(idx) ? 0 : idx
this.selectedAttempt1Index = 0
this.selectedAttempt2Index = 0
},
onPickAttempt1(e) {
const idx = e && e.detail ? Number(e.detail.value) : 0
this.selectedAttempt1Index = isNaN(idx) ? 0 : idx
},
onPickAttempt2(e) {
const idx = e && e.detail ? Number(e.detail.value) : 0
this.selectedAttempt2Index = isNaN(idx) ? 0 : idx
},
openReport1() {
if (!this.canOpenReport1) return
uni.navigateTo({
url: `/pages/report/detail?reportId=${encodeURIComponent(this.selectedAttempt1.reportId)}&sourceType=assessment`
})
},
openReport2() {
if (!this.canOpenReport2) return
uni.navigateTo({
url: `/pages/report/detail?reportId=${encodeURIComponent(this.selectedAttempt2.reportId)}&sourceType=assessment`
})
},
copyReport() {
if (!this.userId || !this.selectedScale || !this.selectedAttempt1 || !this.selectedAttempt2) {
this.errorMsg = '请先选择两次测评记录并完成对比'
return
}
const t1 = this.toDateTimeString(this.selectedAttempt1.submitTime || this.selectedAttempt1.startTime)
const t2 = this.toDateTimeString(this.selectedAttempt2.submitTime || this.selectedAttempt2.startTime)
this.loading = true
compareUserAssessmentReport({ userId: this.userId, t1, t2, scaleId: this.selectedScale.scaleId }).then((res) => {
this.loading = false
const data = res && res.data ? res.data : null
if (!data || data.code !== 200) {
this.errorMsg = (data && data.msg) ? data.msg : '生成报告失败'
return
}
const payload = data.data || {}
const text = payload.text || ''
if (!text) {
this.errorMsg = '报告为空'
return
}
uni.setClipboardData({
data: text,
success: () => {
uni.showToast({ title: '已复制', icon: 'success' })
}
})
}).catch((e) => {
this.loading = false
this.errorMsg = e && e.message ? e.message : '网络错误'
})
},
formatTime(val) {
if (!val) return '—'
try {
const d = new Date(val)
if (isNaN(d.getTime())) return '—'
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return `${y}-${m}-${day} ${hh}:${mm}`
} catch (e) {
return '—'
}
},
doCompare() {
if (!this.userId) {
this.errorMsg = '缺少 userId'
return
}
if (!this.selectedScale) {
this.errorMsg = '请选择量表'
return
}
if (!this.selectedAttempt1 || !this.selectedAttempt2) {
this.errorMsg = '请选择两条测评记录'
return
}
this.loading = true
this.errorMsg = ''
this.summaryText = ''
this.summaryLines = []
this.rows = []
const t1 = this.toDateTimeString(this.selectedAttempt1.submitTime || this.selectedAttempt1.startTime)
const t2 = this.toDateTimeString(this.selectedAttempt2.submitTime || this.selectedAttempt2.startTime)
compareUserAssessment({ userId: this.userId, t1, t2, scaleId: this.selectedScale.scaleId }).then((res) => {
this.loading = false
const data = res && res.data ? res.data : null
if (!data || data.code !== 200) {
this.errorMsg = (data && data.msg) ? data.msg : '对比失败'
return
}
const payload = data.data || {}
this.summaryLines = payload.summaryLines || []
this.summaryText = payload.summaryText || (Array.isArray(this.summaryLines) ? this.summaryLines.join('') : '')
this.rows = payload.items || []
}).catch((e) => {
this.loading = false
this.errorMsg = e && e.message ? e.message : '网络错误'
})
}
}
}
</script>
<style>
.page { min-height: 100vh; padding: 24rpx 24rpx 120rpx; box-sizing: border-box; background: #F4F6FB; }
.page.big {
padding: 14rpx 14rpx 120rpx;
background-image:
radial-gradient(1100rpx 520rpx at 50% 14%, rgba(43, 107, 255, 0.30) 0%, rgba(6, 16, 40, 0.0) 65%),
linear-gradient(180deg, rgba(5, 11, 24, 0.90) 0%, rgba(8, 20, 45, 0.85) 42%, rgba(6, 16, 40, 0.92) 100%),
url('/static/bg.png');
background-size: auto, auto, cover;
background-position: center, center, center;
background-repeat: no-repeat, no-repeat, no-repeat;
}
.card { background: rgba(255, 255, 255, 0.95); border-radius: 18rpx; padding: 24rpx; border: 1px solid rgba(15, 23, 42, 0.06); margin-bottom: 18rpx; box-shadow: 0 10rpx 22rpx rgba(15, 23, 42, 0.05); }
.card-title { font-size: 28rpx; font-weight: 700; color: #111827; margin-bottom: 14rpx; }
.picker { background: rgba(15, 23, 42, 0.04); border-radius: 16rpx; padding: 18rpx; font-size: 26rpx; box-sizing: border-box; margin-bottom: 16rpx; border: 1px solid rgba(15,23,42,0.06); }
.btn { width: 100%; height: 84rpx; line-height: 84rpx; border-radius: 18rpx; font-size: 28rpx; margin-top: 12rpx; }
.btn.primary { background: #1677ff; color: #FFFFFF; }
.btn.ghost { background: #FFFFFF; color: #1F2937; border: 1px solid rgba(17,24,39,0.12); }
.kv-item { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1px solid rgba(15, 23, 42, 0.06); }
.kv-item:last-child { border-bottom: 0; }
.k { color: #6B7280; font-size: 24rpx; }
.v { color: #111827; font-size: 24rpx; font-weight: 700; }
.list { margin-top: 8rpx; }
.item { background: #FFFFFF; border-radius: 20rpx; padding: 22rpx; border: 1px solid rgba(15, 23, 42, 0.06); margin-bottom: 18rpx; }
.item-head { display: flex; justify-content: space-between; align-items: center; }
.item-title { font-size: 28rpx; font-weight: 700; color: #111827; }
.pill { padding: 8rpx 14rpx; border-radius: 999rpx; font-size: 22rpx; color: #2B6BFF; background: rgba(43, 107, 255, 0.12); }
.pill.up { color: #DC2626; background: rgba(220, 38, 38, 0.12); }
.pill.down { color: #16A34A; background: rgba(22, 163, 74, 0.12); }
.item-grid { margin-top: 10rpx; display: flex; flex-wrap: wrap; justify-content: space-between; }
.item-desc { width: 48%; margin-top: 8rpx; font-size: 24rpx; color: #4B5563; }
.summary { margin-top: 10rpx; margin-bottom: 14rpx; padding: 18rpx; border-radius: 18rpx; background: rgba(43, 107, 255, 0.06); border: 1px solid rgba(43, 107, 255, 0.12); }
.summary-title { font-size: 26rpx; font-weight: 700; color: #1F2937; }
.summary-text { margin-top: 10rpx; font-size: 24rpx; color: #374151; line-height: 38rpx; }
.state { height: 360rpx; border-radius: 20rpx; background: rgba(255, 255, 255, 0.85); border: 1px solid rgba(15, 23, 42, 0.06); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12rpx; }
.state-text { font-size: 24rpx; color: #94A3B8; font-weight: 700; }
.error { margin-top: 14rpx; font-size: 24rpx; color: #EF4444; line-height: 36rpx; }
.page.big .card,
.page.big .item,
.page.big .state {
border: 1px solid rgba(116, 216, 255, 0.22);
background: linear-gradient(180deg, rgba(10, 18, 38, 0.75) 0%, rgba(5, 10, 22, 0.55) 100%);
box-shadow: 0 12rpx 24rpx rgba(0, 0, 0, 0.35);
}
.page.big .card-title,
.page.big .item-title,
.page.big .summary-title,
.page.big .v { color: rgba(235, 248, 255, 0.92); }
.page.big .k,
.page.big .item-desc,
.page.big .summary-text,
.page.big .state-text { color: rgba(201, 242, 255, 0.65); }
.page.big .picker { background: rgba(7, 13, 28, 0.35); border-color: rgba(116, 216, 255, 0.18); color: rgba(235, 248, 255, 0.92); }
.page.big .btn.primary { background: linear-gradient(90deg, rgba(116, 216, 255, 0.95) 0%, rgba(43, 107, 255, 0.92) 100%); color: #0b1226; font-weight: 900; }
.page.big .btn.ghost { background: rgba(7, 13, 28, 0.35); color: rgba(201, 242, 255, 0.86); border-color: rgba(116, 216, 255, 0.18); }
.page.big .summary { background: rgba(116, 216, 255, 0.10); border-color: rgba(116, 216, 255, 0.18); }
.page.big .pill { color: rgba(201, 242, 255, 0.90); background: rgba(116, 216, 255, 0.10); border: 1px solid rgba(116, 216, 255, 0.18); }
.page.big .pill.up { color: rgba(255, 72, 92, 0.92); background: rgba(255, 72, 92, 0.12); border-color: rgba(255, 72, 92, 0.22); }
.page.big .pill.down { color: rgba(34, 197, 94, 0.92); background: rgba(34, 197, 94, 0.12); border-color: rgba(34, 197, 94, 0.22); }
</style>