Files
msh-system/msh_single_uniapp/pages/tool/calculator-history.vue
msh-agent a9686c7d45 feat(user-tag): 后台用户列表展示标签 + 小程序用户中心会员等级下方展示标签
后台 msh_single_admin user/list/index.vue:
- 在「分组」「推荐人」之间新增「用户标签」列,用 el-tag 渲染(多标签 ',' 切分)
- 加入默认显示项 checkedCities / columnData

后端 UserCenterResponse + UserServiceImpl:
- UserCenterResponse 新增 tagName 字段
- getUserCenter 在已注入的 userTagService 基础上回填标签名(已存在 getGroupNameInId)

小程序 pages/user/index.vue:
- 用户名 + VIP 行下方新增 .user-tags 容器,按 ',' 切分多标签
- 半透明白底胶囊,与顶部渐变橙色背景协调

附带修复:
- pages/tool/calculator-history.vue formatTime 兼容 ISO/数组/数字/旧字符串四种来源
- 解决「NaN-NaN-NaN NaN:NaN」问题(ISO 字符串里的 'T' 被替换 / 后变非法日期)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 03:53:56 +08:00

205 lines
5.5 KiB
Vue
Raw 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="history-page">
<view class="history-list" v-if="list.length > 0">
<view
class="history-item"
v-for="(item, index) in list"
:key="item.id || index"
@click="openDetail(item)"
>
<view class="row top">
<text class="time">{{ formatTime(item.createdAt) }}</text>
<text class="status" :class="{ adopted: item.isAdopted === 1 }">
{{ item.isAdopted === 1 ? '已采纳' : '未采纳' }}
</text>
</view>
<view class="row metrics">
<view class="metric">
<text class="label">CKD</text>
<text class="value">{{ item.ckdStage || '—' }}</text>
</view>
<view class="metric">
<text class="label">BMI</text>
<text class="value">{{ formatBmi(item.bmi) }}</text>
</view>
<view class="metric">
<text class="label">蛋白质</text>
<text class="value">{{ item.proteinIntake || '—' }} g</text>
</view>
<view class="metric">
<text class="label">能量</text>
<text class="value">{{ item.energyIntake || '—' }} kcal</text>
</view>
</view>
<view class="row hint" v-if="item.hasDialysis === 1">
<text>· 已透析</text>
</view>
</view>
</view>
<view class="empty" v-else-if="!loading">
<text class="empty-text">暂无计算记录</text>
<view class="empty-btn" @click="goCalculator">去计算</view>
</view>
<view class="loading-tip" v-if="loading">加载中</view>
<view class="more-tip" v-if="hasMore && !loading && list.length > 0" @click="loadMore">点击加载更多</view>
<view class="end-tip" v-if="!hasMore && list.length > 0">没有更多了</view>
</view>
</template>
<script>
import { getCalculatorHistory } from '@/api/tool.js'
export default {
name: 'CalculatorHistory',
data() {
return {
list: [],
page: 1,
limit: 20,
hasMore: true,
loading: false
}
},
onLoad() {
// 运行时强制回写中文标题,避免开发者工具/旧编译缓存读取 pages.json 时
// 出现页面标题乱码(部分版本会把 UTF-8 当 GBK 渲染)
uni.setNavigationBarTitle({ title: '我的计算记录' })
this.fetch()
},
onPullDownRefresh() {
this.list = []
this.page = 1
this.hasMore = true
this.fetch().finally(() => uni.stopPullDownRefresh())
},
onReachBottom() {
if (this.hasMore && !this.loading) this.loadMore()
},
methods: {
async fetch() {
if (this.loading) return
this.loading = true
try {
const res = await getCalculatorHistory({ page: this.page, limit: this.limit })
const rows = (res && res.data && res.data.list) || (res && res.list) || []
this.list = this.page === 1 ? rows : this.list.concat(rows)
if (rows.length < this.limit) this.hasMore = false
} catch (e) {
console.error('加载历史失败', e)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
loadMore() {
this.page += 1
this.fetch()
},
openDetail(item) {
if (!item || !item.id) return
uni.navigateTo({ url: '/pages/tool/calculator-result?id=' + item.id })
},
goCalculator() {
uni.navigateTo({ url: '/pages/tool/calculator' })
},
formatTime(s) {
if (s == null || s === '') return ''
// 兼容 4 种来源:
// ① 数字时间戳;② Java LocalDateTime 序列化为数组 [y,m,d,h,m,s,nano]
// ③ ISO 字符串 "2026-05-03T01:21:18"(含 T旧版 replace 后变 "2026/05/03T01:21:18" 解析为 Invalid Date → NaN
// ④ 旧式 "2026-05-03 01:21:18"iOS 需把 - 替换成 /
let d
if (typeof s === 'number') {
d = new Date(s)
} else if (Array.isArray(s) && s.length >= 3) {
d = new Date(s[0], (s[1] || 1) - 1, s[2] || 1, s[3] || 0, s[4] || 0, s[5] || 0)
} else if (typeof s === 'string') {
d = /T/.test(s) ? new Date(s) : new Date(s.replace(/-/g, '/'))
} else {
d = new Date(s)
}
if (!d || isNaN(d.getTime())) return String(s)
const pad = (n) => (n < 10 ? '0' + n : '' + n)
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
+ ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes())
},
formatBmi(b) {
if (b == null) return '—'
return Number(b).toFixed(1)
}
}
}
</script>
<style lang="scss" scoped>
.history-page {
min-height: 100vh;
background: #f4f5f7;
padding: 24rpx;
}
.history-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.history-item {
background: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
}
.row {
display: flex;
flex-direction: row;
align-items: center;
}
.top {
justify-content: space-between;
margin-bottom: 16rpx;
.time { font-size: 26rpx; color: #6b7280; }
.status {
font-size: 24rpx;
padding: 4rpx 12rpx;
border-radius: 8rpx;
background: #eef0f4;
color: #6b7280;
&.adopted { background: #e8f5e9; color: #2e7d32; }
}
}
.metrics {
flex-wrap: wrap;
gap: 24rpx;
.metric {
display: flex;
flex-direction: column;
min-width: 140rpx;
.label { font-size: 22rpx; color: #9ca3af; margin-bottom: 4rpx; }
.value { font-size: 28rpx; color: #1f2937; font-weight: 600; }
}
}
.hint { margin-top: 12rpx; color: #b91c1c; font-size: 24rpx; }
.empty {
display: flex;
flex-direction: column;
align-items: center;
padding: 200rpx 0;
.empty-text { color: #9ca3af; font-size: 28rpx; margin-bottom: 32rpx; }
.empty-btn {
padding: 16rpx 48rpx;
background: #fc4141;
color: #fff;
border-radius: 32rpx;
font-size: 28rpx;
}
}
.loading-tip, .more-tip, .end-tip {
text-align: center;
color: #9ca3af;
font-size: 24rpx;
padding: 32rpx 0;
}
.more-tip { color: #2563eb; }
</style>