476 lines
16 KiB
Vue
476 lines
16 KiB
Vue
<template>
|
||
|
||
|
||
<div class="user-base-line">
|
||
<div class="user-block">
|
||
<t-cell-group title="用户基本信息" >
|
||
<t-cell title="用户名" :note="`${user.name}`" class="line-start line-end" ></t-cell>
|
||
<t-cell title="性别" :note="`${user.sex == 1? '男':'女'}`" class="line-end" ></t-cell>
|
||
<t-cell title="出生年月日" :note="`${user.birthday}(${new Date().getFullYear() - +user.birthday?.slice(0, 4)}岁)`" class="line-end" ></t-cell>
|
||
<!-- <t-cell title="方案开始时间" :note="user.before?.day" class="line-end"></t-cell>
|
||
<t-cell title="方案结束时间" :note="user.after?.day" class="line-end"></t-cell> -->
|
||
</t-cell-group>
|
||
</div>
|
||
|
||
<t-cell-group title="用户标签" v-if="user.labelList">
|
||
<div class="user-label-line ">
|
||
<span class="user-label user-label-active" v-for="label in user.labelList">{{ label.label }}</span>
|
||
</div>
|
||
</t-cell-group>
|
||
<div class="user-block" v-if="user.before?.picList.length > 0">
|
||
<t-cell-group title="方案前照片">
|
||
<t-row style="margin:16px;">
|
||
<t-col span="8" class="dark" v-for="pic in user.before?.picList">
|
||
<t-image
|
||
class="user-pic-item"
|
||
fit="fill"
|
||
:lazy="true"
|
||
:src="pic"
|
||
@click="beforeVisible = true"
|
||
></t-image>
|
||
</t-col>
|
||
</t-row>
|
||
</t-cell-group>
|
||
|
||
<t-image-viewer v-model:images="beforePicList" :visible="beforeVisible" @close="beforeVisible = false">
|
||
<template #image="{ src, className, onLoad, onTransitionstart, onTransitionend, style }">
|
||
<div>
|
||
<img
|
||
:src="src"
|
||
:style="style"
|
||
:class="className"
|
||
@load="onLoad"
|
||
@transitionstart="onTransitionstart"
|
||
@transitionend="onTransitionend"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</t-image-viewer>
|
||
</div>
|
||
|
||
<div class="user-block" v-if="user?.before?.videoList.length > 0">
|
||
<t-cell-group title="方案前视频">
|
||
<t-row style="margin:16px;">
|
||
<t-col span="8" class="dark" v-for="(video,index) in user.before?.videoList">
|
||
<div class="video-cover-wrapper">
|
||
<!-- 视频封面:使用 TDesign Image 组件 -->
|
||
<t-image
|
||
:src="video.cover"
|
||
fit="fill"
|
||
:lazy="true"
|
||
class="user-pic-item"
|
||
@click="openVideoPreview('before', index)"
|
||
>
|
||
</t-image>
|
||
|
||
<div class="play-mask">
|
||
<t-icon name="play-circle-filled" size="48px" />
|
||
</div>
|
||
</div>
|
||
</t-col>
|
||
</t-row>
|
||
</t-cell-group>
|
||
<!-- 预览弹窗 -->
|
||
<t-dialog
|
||
v-model:visible="visibleVideo"
|
||
header="视频预览"
|
||
:footer="false"
|
||
width="100vw"
|
||
close-btn
|
||
destroy-on-close
|
||
@close="handleVideoClose"
|
||
>
|
||
<video
|
||
v-if="visibleVideo"
|
||
ref="videoRef"
|
||
:src="videoUrl"
|
||
:poster="videoCover"
|
||
controls
|
||
autoplay
|
||
playsinline
|
||
style="width: 100%; height: auto; border-radius: 4px"
|
||
/>
|
||
</t-dialog>
|
||
</div>
|
||
<div class="user-block" v-if="user.after?.picList.length > 0">
|
||
<t-cell-group title="方案后照片">
|
||
<t-row style="margin:16px;">
|
||
<t-col span="8" class="dark" v-for="pic in user.after?.picList">
|
||
<t-image
|
||
class="user-pic-item"
|
||
fit="fill"
|
||
:lazy="true"
|
||
:src="pic"
|
||
@click="afterVisible = true"
|
||
></t-image>
|
||
</t-col>
|
||
</t-row>
|
||
</t-cell-group>
|
||
|
||
<t-image-viewer v-model:images="afterPicList" :visible="afterVisible" @close="afterVisible = false">
|
||
<template #image="{ src, className, onLoad, onTransitionstart, onTransitionend, style }">
|
||
<div>
|
||
<img
|
||
:src="src"
|
||
:style="style"
|
||
:class="className"
|
||
@load="onLoad"
|
||
@transitionstart="onTransitionstart"
|
||
@transitionend="onTransitionend"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</t-image-viewer>
|
||
</div>
|
||
|
||
<div class="user-block" v-if="user.after?.videoList.length > 0">
|
||
<t-cell-group title="方案后视频">
|
||
<t-row style="margin:16px;">
|
||
<t-col span="8" class="dark" v-for="(video,index) in user.after?.videoList">
|
||
<div class="video-cover-wrapper">
|
||
<!-- 视频封面:使用 TDesign Image 组件 -->
|
||
<t-image
|
||
:src="video.cover"
|
||
fit="fill"
|
||
:lazy="true"
|
||
class="user-pic-item"
|
||
@click="openVideoPreview('after', index)"
|
||
>
|
||
</t-image>
|
||
|
||
<div class="play-mask">
|
||
<t-icon name="play-circle-filled" size="48px" />
|
||
</div>
|
||
</div>
|
||
</t-col>
|
||
</t-row>
|
||
</t-cell-group>
|
||
<!-- 预览弹窗 -->
|
||
</div>
|
||
|
||
<div v-if="user.weightList">
|
||
<t-cell-group title="体重变化" v-if="weightData">
|
||
<t-cell title="方案开始体重" :note="weightData.weightStart + '斤('+weightData.dayStart+')'"></t-cell>
|
||
<t-cell title="方案开始体重" :note="weightData.weightEnd + '斤('+weightData.dayEnd+')'"></t-cell>
|
||
<t-cell title="体重变化" :note="weightData.weightTotal + '斤(共'+weightData.dayTotal+'天)'"></t-cell>
|
||
<Canvas :style="{ width: '100%', height: '300px' }" v-if="weightChartData.length > 1">
|
||
<Chart :data="weightChartData" :scale="scaleConfig">
|
||
<Axis field="day" />
|
||
<Axis field="weight" />
|
||
<Line x="day" y="weight" />
|
||
</Chart>
|
||
</Canvas>
|
||
</t-cell-group>
|
||
</div>
|
||
<div v-if="user.comment">
|
||
<t-cell-group title="教练小结" v-if="user.comment">
|
||
<div class="comment">{{ user.comment }}</div>
|
||
</t-cell-group>
|
||
</div>
|
||
<div>
|
||
</div>
|
||
<div class="empty-bar"></div>
|
||
<div class="empty-bar"></div>
|
||
</div>
|
||
</template>
|
||
<script lang="ts" setup>
|
||
import { Icon as TIcon } from 'tdesign-icons-vue-next';
|
||
import { onMounted, ref } from 'vue';
|
||
import {useRoute, useRouter} from 'vue-router'
|
||
import Canvas from '@antv/f-vue';
|
||
import { Chart, Interval, Axis, Line } from '@antv/f2';
|
||
|
||
import {labelList, userList} from '../api/user'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
|
||
const userID = ref("")
|
||
const beforeVisible = ref(false)
|
||
const afterVisible = ref(false)
|
||
const beforePicList = ref([])
|
||
const afterPicList = ref([])
|
||
const visibleVideo = ref(false)
|
||
const videoUrl = ref('')
|
||
const videoCover = ref('')
|
||
const weightData = ref({})
|
||
const weightChartData = ref([
|
||
{weight: 0, day: '2026-09-01'}
|
||
])
|
||
const openVideoPreview = (source, index) => {
|
||
visibleVideo.value = true
|
||
if (source == 'before'){
|
||
videoCover.value = user.value.before.videoList[index].cover
|
||
videoUrl.value = user.value.before.videoList[index].url
|
||
}else{
|
||
videoCover.value = user.value.after.videoList[index].cover
|
||
videoUrl.value = user.value.after.videoList[index].url
|
||
}
|
||
}
|
||
const handleVideoClose = () => {
|
||
visibleVideo.value = false
|
||
}
|
||
|
||
const user1 = ref({
|
||
username: '用户名 1',
|
||
id: 1,
|
||
sex: 1,
|
||
birthday: '1985-09-01',
|
||
before: {
|
||
day: '2026-09-01',
|
||
picList: [
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example6.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example6.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example6.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
],
|
||
videoList: [
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example4.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example6.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example4.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example6.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example4.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
]
|
||
},
|
||
after: {
|
||
day: '2026-09-01',
|
||
picList: [
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example6.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example6.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example6.png',
|
||
'https://tdesign.gtimg.com/mobile/demos/example4.png',
|
||
],
|
||
videoList: [
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example4.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example6.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example4.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example6.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
{cover: 'https://tdesign.gtimg.com/mobile/demos/example4.png', url: 'https://lorem.video/bunny_720p_h264_30fps_20s_23crf_aac_192kbps.mp4'},
|
||
]
|
||
},
|
||
labelList:[
|
||
'病症 1','病症病症 2','病症病症病症 3','病症病 4','病病症 5','病症 6','病症病症病症病症 7','病症 8','病症 9'
|
||
],
|
||
weightList:[
|
||
{day: '2026-09-01', weight: 180.4},
|
||
{day: '2026-09-02', weight: 175.5},
|
||
{day: '2026-09-03', weight: 169.6},
|
||
{day: '2026-09-04', weight: 158.7},
|
||
{day: '2026-09-05', weight: 154.8},
|
||
{day: '2026-09-06', weight: 152.9},
|
||
{day: '2026-09-07', weight: 145.1},
|
||
{day: '2026-09-08', weight: 143.2},
|
||
{day: '2026-09-09', weight: 140.3},
|
||
{day: '2026-09-10', weight: 138.4},
|
||
{day: '2026-09-11', weight: 135.5},
|
||
]
|
||
})
|
||
|
||
const user = ref({})
|
||
const setWeightData = (weightList) => {
|
||
const total = weightList.length
|
||
const weightStart = weightList[0].weight
|
||
const weightEnd = weightList[total - 1].weight
|
||
const dayStart = weightList[0].day
|
||
const dayEnd = weightList[total - 1].day
|
||
const weightTotal = (weightEnd - weightStart).toFixed(2)
|
||
weightData.value = {
|
||
weightStart : weightStart.toFixed(2),
|
||
weightEnd : weightEnd.toFixed(2),
|
||
dayStart : dayStart,
|
||
dayEnd: dayEnd,
|
||
dayTotal: total,
|
||
weightTotal: weightTotal,
|
||
}
|
||
}
|
||
onMounted(() => {
|
||
console.log('route', route)
|
||
console.log('router', router)
|
||
const id = route.params.id
|
||
userID.value = id
|
||
userList.forEach(u => {
|
||
console.log('u', u)
|
||
if (u.id == id){
|
||
user.value = u
|
||
console.log('user', user)
|
||
// 1. body_images 下所有值变成数组
|
||
u.body_images = Object.fromEntries(
|
||
Object.entries(u.body_images).map(([k, v]) => [k, [v]])
|
||
);
|
||
|
||
// 2. after_body_images 下所有值变成数组
|
||
u.after_body_images = Object.fromEntries(
|
||
Object.entries(u.after_body_images).map(([k, v]) => [k, [v]])
|
||
);
|
||
|
||
// 3. 新增 user.before.picList —— 取 body_images 的数组值
|
||
u.before = {
|
||
picList: Object.values(u.body_images)
|
||
};
|
||
|
||
// 4. 新增 user.after.picList —— 取 after_body_images 的数组值
|
||
u.after = {
|
||
picList: Object.values(u.after_body_images)
|
||
};
|
||
if (u.weightList){
|
||
weightChartData.value = u.weightList
|
||
beforePicList.value = u.before.picList
|
||
afterPicList.value = u.after.picList
|
||
setWeightData(u.weightList)
|
||
}
|
||
u.before.videoList = []
|
||
u.after.videoList = []
|
||
user.value = u
|
||
}
|
||
})
|
||
})
|
||
|
||
const scaleConfig = {
|
||
day: { range: [0, 1], tickCount: 5 },
|
||
};
|
||
|
||
</script>
|
||
<style lang="less">
|
||
.card {
|
||
position: relative;
|
||
margin: 16px;
|
||
border-radius: 6px;
|
||
// overflow: hidden;
|
||
box-sizing: border-box;
|
||
border: 1.5px solid var(--bg-color-demo, #fff);
|
||
border-color: var(--td-success-color-4);
|
||
}
|
||
|
||
.card--active {
|
||
border-color: var(--td-brand-color);
|
||
}
|
||
|
||
.card--active::after {
|
||
content: '';
|
||
display: block;
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 0;
|
||
border: 14px solid var(--td-success-color-4);
|
||
border-bottom-color: transparent;
|
||
border-right-color: transparent;
|
||
}
|
||
|
||
.card__icon {
|
||
color: var(--td-text-color-anti);
|
||
position: absolute;
|
||
left: 1.5px;
|
||
top: 1.5px;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 横向布局 */
|
||
.horizontal-box {
|
||
width: calc(100% - 32px);
|
||
display: flex;
|
||
align-items: start;
|
||
margin: 16px;
|
||
white-space: nowrap
|
||
}
|
||
|
||
.horizontal-box .card {
|
||
flex: 1;
|
||
margin: 0;
|
||
}
|
||
|
||
.horizontal-box .card + .card {
|
||
margin-left: 12px;
|
||
}
|
||
|
||
.horizontal-box .card::after {
|
||
border-width: 12px;
|
||
}
|
||
|
||
.horizontal-box .card__icon {
|
||
font-size: 12px;
|
||
}
|
||
.user-line{
|
||
width: calc(100vw - 8px);
|
||
margin: 4px;
|
||
}
|
||
.user-pic-item{
|
||
padding: 4px;
|
||
}
|
||
.line-start{
|
||
border-top: 1px solid var(--td-success-color-4);
|
||
}
|
||
.line-end{
|
||
border-bottom: 1px solid var(--td-success-color-4);
|
||
}
|
||
.user-line-block{
|
||
width: 50%;
|
||
}
|
||
.user-pic-block{
|
||
width: 100%;
|
||
}
|
||
.user-pic{
|
||
padding: 16px;
|
||
}
|
||
.user-day{
|
||
padding-top: 8px;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
.user-label-line{
|
||
margin:16px;
|
||
}
|
||
.user-label{
|
||
margin: 4px;
|
||
padding: 4px 12px 4px 12px;
|
||
line-height: 36px;
|
||
border-radius: 16px;
|
||
color: #000;
|
||
white-space: nowrap;
|
||
border: 1px solid var(--td-success-color-4);
|
||
}
|
||
.user-label-active{
|
||
|
||
color: #fff;
|
||
background-color: var(--td-success-color-4);
|
||
}
|
||
.user-name-line{
|
||
border-bottom: 1px solid var(--td-success-color-4);
|
||
// border-radius: 8px;
|
||
}
|
||
.user-name{
|
||
border-radius: 8px;
|
||
}
|
||
.t-checkbox--block {
|
||
padding: 4px;
|
||
padding-left: 16px;
|
||
}
|
||
.video-cover-wrapper {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 100%;
|
||
}
|
||
.play-mask {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #fff;
|
||
// background: rgba(0, 0, 0, 0.35);
|
||
pointer-events: none; /* 让点击穿透到外层 */
|
||
// padding: 4px;
|
||
// margin: 4px;
|
||
}
|
||
.comment{
|
||
text-indent: 2em;
|
||
padding: 16px;
|
||
}
|
||
</style>
|