feat(supervisor): add invite and bind pages
This commit is contained in:
@@ -132,3 +132,19 @@ export function updateRewardGoal(id, data) {
|
|||||||
return request.request({ url: `/reward-goals/${id}`, method: 'PUT', data, baseUrl: BASE_URL_V2 })
|
return request.request({ url: `/reward-goals/${id}`, method: 'PUT', data, baseUrl: BASE_URL_V2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监督人机制(Phase 3)
|
||||||
|
export function createSupervisorInvite(days = 7) {
|
||||||
|
return request.request({ url: '/supervisor/invites', method: 'POST', data: { days }, baseUrl: BASE_URL_V2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindSupervisorInvite(token) {
|
||||||
|
return request.request({ url: '/supervisor/bind', method: 'POST', data: { token }, baseUrl: BASE_URL_V2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSupervisorOverview() {
|
||||||
|
return request.request({ url: '/supervisor/overview', method: 'GET', baseUrl: BASE_URL_V2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSupervisorStatus() {
|
||||||
|
return request.request({ url: '/supervisor/status', method: 'GET', baseUrl: BASE_URL_V2 })
|
||||||
|
}
|
||||||
|
|||||||
@@ -96,6 +96,20 @@
|
|||||||
"navigationStyle": "default",
|
"navigationStyle": "default",
|
||||||
"navigationBarTitleText": "梦想清单"
|
"navigationBarTitleText": "梦想清单"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/supervisor/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "监督人",
|
||||||
|
"navigationStyle": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/supervisor/bind",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "绑定监督",
|
||||||
|
"navigationStyle": "default"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
|
|||||||
@@ -59,6 +59,19 @@
|
|||||||
|
|
||||||
<view class="menu-divider"></view>
|
<view class="menu-divider"></view>
|
||||||
|
|
||||||
|
<view class="menu-item" @tap="goSupervisor">
|
||||||
|
<view class="menu-icon menu-icon-accent">
|
||||||
|
<text class="menu-glyph">督</text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-content">
|
||||||
|
<text class="menu-label">监督人</text>
|
||||||
|
<text class="menu-desc">邀请朋友监督你,或查看你监督的人</text>
|
||||||
|
</view>
|
||||||
|
<text class="menu-arrow">›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="menu-divider"></view>
|
||||||
|
|
||||||
<view class="menu-item" @tap="goNSTI">
|
<view class="menu-item" @tap="goNSTI">
|
||||||
<view class="menu-icon menu-icon-nsti">
|
<view class="menu-icon menu-icon-nsti">
|
||||||
<text class="menu-glyph">测</text>
|
<text class="menu-glyph">测</text>
|
||||||
@@ -286,6 +299,10 @@ function goNSTI() {
|
|||||||
uni.navigateTo({ url: '/pages/nsti/index' })
|
uni.navigateTo({ url: '/pages/nsti/index' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goSupervisor() {
|
||||||
|
uni.navigateTo({ url: '/pages/supervisor/index' })
|
||||||
|
}
|
||||||
|
|
||||||
function clearCache() {
|
function clearCache() {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '清除缓存',
|
title: '清除缓存',
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="card">
|
||||||
|
<text class="title">绑定监督</text>
|
||||||
|
<text class="desc">输入朋友发给你的邀请口令,即可查看对方的戒烟概览</text>
|
||||||
|
|
||||||
|
<input class="input" v-model="token" placeholder="请输入邀请口令" />
|
||||||
|
|
||||||
|
<button class="btn" :disabled="loading || !token" @tap="doBind">
|
||||||
|
{{ loading ? '绑定中...' : '确认绑定' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-ghost" @tap="gotoSupervisorHome">返回监督人页面</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { useLogin } from '@/hooks/useLogin'
|
||||||
|
import { bindSupervisorInvite } from '@/api/smoke'
|
||||||
|
|
||||||
|
const { waitForLogin } = useLogin()
|
||||||
|
|
||||||
|
const token = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
function gotoSupervisorHome() {
|
||||||
|
uni.navigateTo({ url: '/pages/supervisor/index' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doBind() {
|
||||||
|
if (loading.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await bindSupervisorInvite(token.value.trim())
|
||||||
|
uni.showToast({ title: '绑定成功', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({ url: '/pages/supervisor/index' })
|
||||||
|
}, 600)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('bind error:', e)
|
||||||
|
uni.showToast({ title: e?.message || '绑定失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(async (query) => {
|
||||||
|
await waitForLogin()
|
||||||
|
if (query?.token) {
|
||||||
|
token.value = String(query.token)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 40rpx 28rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: linear-gradient(180deg, #eef7f3 0%, #fbfdff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: rgba(255, 255, 255, 0.94);
|
||||||
|
border-radius: 26rpx;
|
||||||
|
border: 1rpx solid rgba(15, 23, 42, 0.06);
|
||||||
|
padding: 28rpx 24rpx;
|
||||||
|
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
font-size: 38rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
display: block;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin-top: 22rpx;
|
||||||
|
height: 84rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
background: rgba(241, 245, 249, 0.9);
|
||||||
|
border: 1rpx solid rgba(15, 23, 42, 0.08);
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
height: 78rpx;
|
||||||
|
line-height: 78rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
background: linear-gradient(180deg, #1aa37a 0%, #0f766e 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn[disabled] {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0f766e;
|
||||||
|
border: 1rpx solid rgba(15, 118, 110, 0.25);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,438 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="header">
|
||||||
|
<text class="title">监督人机制</text>
|
||||||
|
<text class="subtitle">邀请朋友监督你的戒烟旅程,或者你来监督别人</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card">
|
||||||
|
<view class="card-head">
|
||||||
|
<text class="card-title">邀请监督人</text>
|
||||||
|
<text class="card-meta">有效期默认 7 天</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="inviteToken" class="invite-box">
|
||||||
|
<text class="invite-label">邀请口令</text>
|
||||||
|
<text class="invite-token">{{ inviteToken }}</text>
|
||||||
|
<text class="invite-hint">对方打开小程序后,进入“绑定监督”页面输入口令即可</text>
|
||||||
|
|
||||||
|
<view class="invite-actions">
|
||||||
|
<button class="btn btn-ghost" @tap="copyInviteToken">复制口令</button>
|
||||||
|
<button class="btn" @tap="copyInvitePath">复制链接</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<text v-if="inviteExpireAt" class="invite-expire">过期时间:{{ formatDateTime(inviteExpireAt) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="invite-empty">
|
||||||
|
<text class="invite-empty-text">生成一个邀请口令,发给你信得过的人</text>
|
||||||
|
<button class="btn" :disabled="inviteLoading" @tap="generateInvite">
|
||||||
|
{{ inviteLoading ? '生成中...' : '生成邀请口令' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card">
|
||||||
|
<view class="card-head">
|
||||||
|
<text class="card-title">我监督的人</text>
|
||||||
|
<text class="card-meta">{{ overviewItems.length }} 人</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="overviewItems.length === 0" class="empty">
|
||||||
|
<text class="empty-text">还没有绑定监督关系</text>
|
||||||
|
<text class="empty-hint">收到口令后可去“绑定监督”页面完成绑定</text>
|
||||||
|
<button class="btn btn-ghost" @tap="gotoBindPage">去绑定监督</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="list">
|
||||||
|
<view v-for="item in overviewItems" :key="item.owner?.user_id" class="row">
|
||||||
|
<image class="avatar" :src="item.owner?.avatar_url || defaultAvatar" mode="aspectFill"></image>
|
||||||
|
<view class="row-main">
|
||||||
|
<text class="name">{{ item.owner?.nickname || `用户 ${item.owner?.user_id}` }}</text>
|
||||||
|
<view class="meta">
|
||||||
|
<text class="pill">连续 {{ item.home?.summary?.current_streak_days || 0 }} 天</text>
|
||||||
|
<text class="pill">HP {{ item.home?.summary?.hp_current ?? '--' }}</text>
|
||||||
|
<text class="pill" :class="hpDeltaClass(item.home?.summary?.hp_change_today)">
|
||||||
|
{{ hpDeltaText(item.home?.summary?.hp_change_today) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<text class="status">今日:{{ statusText(item.home?.daily_status?.status) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card">
|
||||||
|
<view class="card-head">
|
||||||
|
<text class="card-title">监督我的人</text>
|
||||||
|
<text class="card-meta">{{ supervisorItems.length }} 人</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="supervisorItems.length === 0" class="empty">
|
||||||
|
<text class="empty-text">还没有人监督你</text>
|
||||||
|
<text class="empty-hint">你可以先生成邀请口令发送给朋友</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="list">
|
||||||
|
<view v-for="u in supervisorItems" :key="u.user_id" class="row">
|
||||||
|
<image class="avatar" :src="u.avatar_url || defaultAvatar" mode="aspectFill"></image>
|
||||||
|
<view class="row-main">
|
||||||
|
<text class="name">{{ u.nickname || `用户 ${u.user_id}` }}</text>
|
||||||
|
<text class="status">可查看你的戒烟概览</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="footer">
|
||||||
|
<button class="btn btn-ghost" :disabled="loading" @tap="refreshAll">刷新</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { useLogin } from '@/hooks/useLogin'
|
||||||
|
import { createSupervisorInvite, getSupervisorOverview, getSupervisorStatus } from '@/api/smoke'
|
||||||
|
|
||||||
|
const { waitForLogin } = useLogin()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const inviteLoading = ref(false)
|
||||||
|
const inviteToken = ref('')
|
||||||
|
const inviteExpireAt = ref('')
|
||||||
|
|
||||||
|
const overviewItems = ref([])
|
||||||
|
const supervisorItems = ref([])
|
||||||
|
|
||||||
|
const defaultAvatar = 'https://linghu-wmr.oss-cn-beijing.aliyuncs.com/smt/avatar.png'
|
||||||
|
|
||||||
|
function formatDateTime(value) {
|
||||||
|
if (!value) return '--'
|
||||||
|
const d = new Date(value)
|
||||||
|
if (Number.isNaN(d.getTime())) return value
|
||||||
|
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}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusText(status) {
|
||||||
|
if (status === 'checked_in') return '已打卡'
|
||||||
|
if (status === 'relapsed') return '已复吸'
|
||||||
|
if (status === 'pending') return '未打卡'
|
||||||
|
if (status === 'missed') return '缺失'
|
||||||
|
return status || '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
function hpDeltaText(delta) {
|
||||||
|
const n = Number(delta)
|
||||||
|
if (Number.isNaN(n) || n === 0) return '今日 0'
|
||||||
|
return n > 0 ? `今日 +${n}` : `今日 ${n}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function hpDeltaClass(delta) {
|
||||||
|
const n = Number(delta)
|
||||||
|
if (Number.isNaN(n) || n === 0) return 'pill-muted'
|
||||||
|
return n > 0 ? 'pill-up' : 'pill-down'
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy(text) {
|
||||||
|
if (!text) return
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: text,
|
||||||
|
success: () => uni.showToast({ title: '已复制', icon: 'success' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function invitePath() {
|
||||||
|
if (!inviteToken.value) return ''
|
||||||
|
return `/pages/supervisor/bind?token=${inviteToken.value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyInviteToken() {
|
||||||
|
copy(inviteToken.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyInvitePath() {
|
||||||
|
copy(invitePath())
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoBindPage() {
|
||||||
|
uni.navigateTo({ url: '/pages/supervisor/bind' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateInvite() {
|
||||||
|
if (inviteLoading.value) return
|
||||||
|
inviteLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await createSupervisorInvite(7)
|
||||||
|
inviteToken.value = res.data?.token || ''
|
||||||
|
inviteExpireAt.value = res.data?.expire_at || ''
|
||||||
|
if (!inviteToken.value) {
|
||||||
|
uni.showToast({ title: '生成失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('generateInvite error:', e)
|
||||||
|
uni.showToast({ title: '生成失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
inviteLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshAll() {
|
||||||
|
if (loading.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [overview, status] = await Promise.all([getSupervisorOverview(), getSupervisorStatus()])
|
||||||
|
overviewItems.value = overview.data?.items || []
|
||||||
|
supervisorItems.value = status.data?.items || []
|
||||||
|
} catch (e) {
|
||||||
|
console.error('refreshAll error:', e)
|
||||||
|
uni.showToast({ title: '刷新失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {})
|
||||||
|
|
||||||
|
onShow(async () => {
|
||||||
|
await waitForLogin()
|
||||||
|
await refreshAll()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 28rpx 28rpx 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: linear-gradient(180deg, #eef7f3 0%, #f7faf8 40%, #fbfdff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 8rpx 6rpx 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
border-radius: 26rpx;
|
||||||
|
border: 1rpx solid rgba(15, 23, 42, 0.06);
|
||||||
|
padding: 22rpx 22rpx;
|
||||||
|
box-shadow: 0 10rpx 26rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-box {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-token {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
color: #0f766e;
|
||||||
|
font-family: 'DIN Alternate', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 14rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-expire {
|
||||||
|
display: block;
|
||||||
|
margin-top: 14rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-empty {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-empty-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
padding: 16rpx 6rpx 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: rgba(241, 245, 249, 0.6);
|
||||||
|
border: 1rpx solid rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 74rpx;
|
||||||
|
height: 74rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e2e8f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
display: flex;
|
||||||
|
gap: 10rpx;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
padding: 8rpx 12rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1rpx solid rgba(15, 23, 42, 0.06);
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-muted {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-up {
|
||||||
|
color: #0f766e;
|
||||||
|
background: rgba(204, 251, 241, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-down {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: rgba(254, 226, 226, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 76rpx;
|
||||||
|
line-height: 76rpx;
|
||||||
|
padding: 0 22rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
background: linear-gradient(180deg, #1aa37a 0%, #0f766e 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn[disabled] {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0f766e;
|
||||||
|
border: 1rpx solid rgba(15, 118, 110, 0.25);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
Reference in New Issue
Block a user