Files
yxe-mp/pages/mine/index.vue
T
weicw-dshsa aff1e52e3e feat(自主填报): 拆分驻店/跑店填报并新增跑店填报页
「自主填报」原本只有一种表单。本次拆成两种类型:

1. 新增选择填报类型页 packageUser/self-report/select
   - 按设计稿:hero 页头 + 两张类型卡(驻店填报 / 跑店填报)+ 确认选择
   - 入口(首页、我的)由直接进 create 改为先进 select

2. 原自主填报页改为「驻店填报」
   - 仅改导航标题,表单内容不变

3. 新增跑店填报页 packageUser/self-report/visit
   - 复制现有跑店/巡店任务(pages/task/detail 的 cateId=4 分支)的填报页并改造:
     保留巡店列表与下方内容,去掉列表上方的任务卡片/总进度/任务详情;
     打卡、摆台照、销量、小票、门店评分一并不保留
   - 巡店列表内「连锁筛选」「门店筛选」是两个并列的独立输入框,
     各自弹各自的选择器(不串联),样式与驻店填报逐项对齐;
     换连锁会清空已选门店
   - 每条巡店可新增/删除,独立获取定位 + 打卡拍照(最多 9 张)
   - 定位不强制(巡店任务里是强制的,这里放开)
   - 底部「保存草稿」+「提交填报」,草稿交互与驻店填报一致:
     进页面询问是否加载、提交成功后清除;storage key 与驻店分开
   - 驳回后重新提交复用同一页面(mode=resubmit)

4. 填报记录页加「驻店 / 跑店」类型切换
   - 两表分表查询,各自分页;跑店卡片展示门店数、巡店列表(门店/定位/照片)
   - 连锁与门店关键词筛选落到巡店列表的每一行

5. 药审核加「跑店填报」类型
   - 新增 AuditVisitReportItem,展开时按 id 拉详情(列表不下发 store_list)
   - 跑店没有销量小计,审核通过需手填奖金,故补奖金输入弹层

顺带重构:
- 门店解析(扁平列表 / 连锁嵌套两种结构的兼容、门店明细不入页面 data
  的性能处理)抽到 util/store-picker,create 与 visit 共用一份,
  避免两页各自维护导致解析口径不一致
2026-10-11 22:50:15 +08:00

986 lines
26 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="mine-page">
<view class="mine-hero">
<!-- <AppNavbar class="mine-navbar" title="我的"/>-->
<view class="hero-content">
<view class="profile-left">
<u-avatar :src="isLoggedIn ? user.avatar : ''" size="120rpx"/>
<view class="profile-info">
<view class="name">{{ isLoggedIn ? user.nickname : '未登录' }}</view>
<view class="phone">{{ isLoggedIn ? user.phone : '点击登录同步手机号' }}</view>
</view>
<view class="primary-btn login-btn" @tap="toLogin" v-if="!isLoggedIn">去登录</view>
</view>
<view class="hero-right">
<image class="hero-mascot"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/a8cc7a2ca9d342f5a10657b735c623cb.png"
mode="aspectFit"></image>
<view class="city-chip">{{ currentCity }}</view>
</view>
</view>
<view class="flex justify-between items-center text-25" style="margin-top: 60rpx">
<text class="text-main">转发分享给新用户,可获得积分</text>
<!-- <text class="text-red font-bold">{{ getPoint }}积分</text>-->
</view>
<!-- <view class="notice-bar" v-if="lastCashLog">-->
<!-- <text class="notice-label">奖励:</text>-->
<!-- <text class="notice-text">{{ lastCashLog.user.nickname }}领取了{{ lastCashLog.money }}元</text>-->
<!-- <view class="notice-more">></view>-->
<!-- <u-icon name="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/9fd7c6fb0046d565cbd36c316023ee87.png"/>-->
<!-- </view>-->
</view>
<view class="income-card">
<!-- <view class="income-item" @click="openBonus">-->
<!-- <view class="flex justify-start items-center">-->
<!-- <view class="income-value">{{ bonus.taskIncome }}</view>-->
<!-- <view class="income-unit">元</view>-->
<!-- </view>-->
<!-- <view class="income-label">任务收益</view>-->
<!-- </view>-->
<!-- <view class="income-item" @click="openBonus">-->
<!-- <view class="flex justify-start items-center">-->
<!-- <view class="income-value">{{ bonus.shareIncome }}</view>-->
<!-- <view class="income-unit">元</view>-->
<!-- </view>-->
<!-- <view class="income-label">晒单收益</view>-->
<!-- </view>-->
<view class="income-item" @click="openPoints">
<view class="flex justify-start items-center">
<view class="income-value income-value--points">{{ pointSummary.currentPoints }}</view>
<view class="income-unit income-unit--points">积分</view>
</view>
<view class="income-label">我的积分</view>
</view>
<!-- <view class="income-btn" @tap="openWithdraw">去提现</view>-->
</view>
<!-- <view class="income-card" v-if="isLoggedIn && user.canAuth === 1">-->
<!-- <view class="income-item" @click="openBonus">-->
<!-- <view class="flex justify-start items-center">-->
<!-- <view class="income-value">{{ bonus.totalTaskIncome }}</view>-->
<!-- <view class="income-unit">元</view>-->
<!-- </view>-->
<!-- <view class="income-label">总任务收益</view>-->
<!-- </view>-->
<!-- <view class="income-item" @click="openBonus">-->
<!-- <view class="flex justify-start items-center">-->
<!-- <view class="income-value">{{ bonus.totalShareIncome }}</view>-->
<!-- <view class="income-unit">元</view>-->
<!-- </view>-->
<!-- <view class="income-label">总晒单收益</view>-->
<!-- </view>-->
<!-- <view class="income-item" @click="openBonus">-->
<!-- <view class="flex justify-start items-center">-->
<!-- <view class="income-value">{{ bonus.totalTaskIncome + bonus.totalShareIncome }}</view>-->
<!-- <view class="income-unit">元</view>-->
<!-- </view>-->
<!-- <view class="income-label">合计收益</view>-->
<!-- </view>-->
<!-- </view>-->
<view class="feature-banner-group">
<view class="self-report-banner" @tap="openSelfReport">
<text>自主填报</text>
</view>
<view class="self-report-banner" @tap="openVisit">
<text>客户拜访</text>
</view>
</view>
<view class="audit-banner" @tap="openAudit" v-if="user.canAuth === 1">
<image class="audit-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/0fd31838127cd57b1c1db44f2c749353.png"
mode="aspectFit"></image>
<text>药审核</text>
</view>
<view class="quick-card">
<view class="quick-item" @tap="openProfile">
<image class="quick-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/f64dbb797897f7e997b9e390c9f5b3ff.png"
mode="aspectFit"></image>
<text>用户资料</text>
</view>
<view class="quick-item" @tap="openOrder">
<image class="quick-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/b8462c751e121744fa20f460c1f43ed8.png"
mode="aspectFit"></image>
<text>全部订单</text>
</view>
<view class="quick-item" @tap="openCoupon">
<image class="quick-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/d4a24c4f61bf29755396ed7c2eecb45a.png"
mode="aspectFit"></image>
<text>领券中心</text>
</view>
<view class="quick-item" @tap="openPoints">
<image class="quick-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/7a9be3484448c3e8253140b8ff05afc6.png"
mode="aspectFit"></image>
<text>我的积分</text>
</view>
</view>
<view class="section-card">
<view class="section-title">服务内容</view>
<view class="grid">
<view class="grid-item" @tap="openMyShare">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/f64dbb797897f7e997b9e390c9f5b3ff.png"
mode="aspectFit"></image>
<text>我的分享</text>
</view>
<view class="grid-item" @tap="openAddress">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/b8462c751e121744fa20f460c1f43ed8.png"
mode="aspectFit"></image>
<text>收货地址</text>
</view>
<!-- <view class="grid-item" @tap="openBonus">-->
<!-- <image class="grid-icon"-->
<!-- src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/d4a24c4f61bf29755396ed7c2eecb45a.png"-->
<!-- mode="aspectFit"></image>-->
<!-- <text>奖金记录</text>-->
<!-- </view>-->
<!-- <view class="grid-item" @tap="openWithdraw">-->
<!-- <image class="grid-icon"-->
<!-- src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/7a9be3484448c3e8253140b8ff05afc6.png"-->
<!-- mode="aspectFit"></image>-->
<!-- <text>资金提现</text>-->
<!-- </view>-->
<view class="grid-item" @tap="openCertification">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/6aaa42c6f235c6720735aae14cb2c16a.png"
mode="aspectFit"></image>
<text>个人信息认证</text>
</view>
<view class="grid-item" @tap="openTravel">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/791479d861e69e833c3aa0073b602908.png"
mode="aspectFit"></image>
<text>差旅报销</text>
</view>
</view>
</view>
<view class="section-card">
<view class="section-title">慢病管理</view>
<view class="grid">
<view class="grid-item" @tap="openChronic">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/a9249f7069c609222b031809809e4b70.png"
mode="aspectFit"></image>
<text>慢病信息收集</text>
</view>
<view class="grid-item" @tap="openChronicManage">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/e3f81b35a32d0771d2fb7c5a23e393db.png"
mode="aspectFit"></image>
<text>慢病信息管理</text>
</view>
<view class="grid-item" @tap="openService">
<image class="grid-icon"
src="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/5677d46c66b7ab57c9497e60e75daef5.png"
mode="aspectFit"></image>
<text>联系客服</text>
</view>
</view>
</view>
<view class="logout-btn" @click="logout" v-if="isLoggedIn">
<u-icon name="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/0193f5a958ccd4797d6e76403cb14096.png"/>
<text>退出登录</text>
</view>
<!-- <view class="card block">-->
<!-- <view class="section-title">奖金记录</view>-->
<!-- <view class="bonus-grid">-->
<!-- <view class="bonus-item">-->
<!-- <view class="bonus-value">¥ {{ bonus.movement }}</view>-->
<!-- <view class="bonus-label">药动销提成</view>-->
<!-- </view>-->
<!-- <view class="bonus-item">-->
<!-- <view class="bonus-value">¥ {{ bonus.exchange }}</view>-->
<!-- <view class="bonus-label">药兑换提成</view>-->
<!-- </view>-->
<!-- </view>-->
<!-- <view class="bonus-list">-->
<!-- <view class="bonus-row" v-for="(item, idx) in bonus.history" :key="idx">-->
<!-- <view>-->
<!-- <view class="bonus-name">{{ item.name }}</view>-->
<!-- <view class="bonus-meta">{{ item.source }} · {{ item.date }}</view>-->
<!-- </view>-->
<!-- <view class="bonus-amount">+¥{{ item.amount }}</view>-->
<!-- </view>-->
<!-- </view>-->
<!-- </view>-->
<!-- <view class="card block">-->
<!-- <view class="section-title">奖金提现</view>-->
<!-- <view class="withdraw-row">-->
<!-- <view>-->
<!-- <view class="withdraw-label">可提现余额</view>-->
<!-- <view class="withdraw-value">¥ {{ withdraw.available }}</view>-->
<!-- </view>-->
<!-- <view class="primary-btn withdraw-btn" @tap="applyWithdraw">申请提现</view>-->
<!-- </view>-->
<!-- </view>-->
<Login ref="Login" @done="getUserData"/>
<u-popup :show="servicePopupVisible" mode="bottom" round="24rpx" close-on-click-overlay @close="closeServicePopup">
<view class="service-popup">
<view class="service-popup-head">
<text class="service-popup-title">联系客服</text>
<view class="service-popup-close" @tap="closeServicePopup">
<u-icon name="close" size="32rpx" color="#7a8495"/>
</view>
</view>
<view class="service-popup-body">
<view v-if="serviceList.length" class="service-list">
<view
class="service-item"
v-for="(item, index) in serviceList"
:key="index"
@tap="handleServiceItem(item)"
>
<text class="service-item-text">{{ item.type }}: {{ item.title }}{{ item.content }}</text>
</view>
</view>
<view v-else class="service-empty">暂无客服内容</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {countUserIncomeReq, userInfoReq} from "@/api/user";
import Login from '@/components/Login.vue'
import AppNavbar from "@/components/AppNavbar.vue";
import {clearUserInfo, getUserInfo} from "@/util/user";
import {getLocationAction} from "@/util";
import {dictDataInfoReq, yxeServiceReq} from "@/api/common";
import {dealerWithdrawPageReq} from "@/api/bonus";
import {userPointLogUserListReq} from "@/api/shop";
export default {
components: {Login, AppNavbar},
data() {
return {
isLoggedIn: false,
defaultAvatar: '/static/logo.png',
user: {
avatar: '',
name: '',
phone: '',
city: '',
qualification: '',
canAuth: 0,
balance: 0,
},
bonus: {
movement: 0,
exchange: 0,
taskIncome: 0,
shareIncome: 0,
totalTaskIncome: 0,
totalShareIncome: 0,
history: []
},
pointSummary: {
currentPoints: 0,
},
currentCity: '定位中',
serviceList: [],
servicePopupVisible: false,
lastCashLog: null,
getPoint: 0,
}
},
async onShow() {
await this.getPointConfig()
await this.refreshPageData()
},
async onPullDownRefresh() {
try {
await this.refreshPageData()
} finally {
uni.stopPullDownRefresh()
}
},
methods: {
async getPointConfig() {
const {data} = await dictDataInfoReq(591)
this.getPoint = data.dictDataCode
},
async refreshPageData() {
await this.getServiceList()
await this.getLastCashLog()
if (getUserInfo().token) {
this.isLoggedIn = true
await this.getUserData()
}
const res = await getLocationAction().catch(() => {
uni.showToast({
title: '请允许位置授权',
icon: 'none'
})
})
if (res) {
this.currentCity = res.city_str
}
},
async getServiceList() {
try {
const {data} = await yxeServiceReq()
this.serviceList = this.normalizeServiceList(data)
} catch (error) {
this.serviceList = []
}
},
async getUserData() {
const {data} = await userInfoReq()
this.isLoggedIn = true
this.user = data
await Promise.all([
this.getUserIncomeData(),
this.getUserPointData()
])
},
async getLastCashLog() {
const {data} = await dealerWithdrawPageReq({
page: 1,
limit: 1,
})
if (data.list.length) this.lastCashLog = data.list[0]
},
async getUserIncomeData() {
const {data} = await countUserIncomeReq()
this.bonus = data
},
async getUserPointData() {
try {
const {data} = await userPointLogUserListReq({
page: 1,
limit: 20
})
this.pointSummary.currentPoints = this.extractCurrentPoints(data)
} catch (error) {
this.pointSummary.currentPoints = this.resolveUserPoints(this.user)
}
},
extractCurrentPoints(payload) {
const payloadPoints = this.resolveUserPoints(payload)
if (payloadPoints !== 0) return payloadPoints
const payloadList = Array.isArray(payload)
? payload
: (payload && Array.isArray(payload.list) ? payload.list : [])
const listPoints = payloadList.reduce((sum, item) => sum + this.resolvePointDelta(item), 0)
if (listPoints !== 0) return listPoints
return this.resolveUserPoints(this.user)
},
resolveUserPoints(source = {}) {
const candidates = [
source.currentPoints,
source.currentPoint,
source.points,
source.point,
source.totalPoints,
source.totalPoint,
source.integral,
source.score
]
for (let i = 0; i < candidates.length; i++) {
const value = Number(candidates[i])
if (Number.isFinite(value)) return value
}
return 0
},
resolvePointDelta(source = {}) {
const candidates = [
source.changePoint,
source.changePoints,
source.changeNum,
source.delta,
source.num,
source.amount,
source.value,
source.point,
source.points,
source.integral,
source.score
]
for (let i = 0; i < candidates.length; i++) {
const value = Number(candidates[i])
if (Number.isFinite(value)) return value
}
return 0
},
toLogin() {
this.$refs.Login.open()
},
normalizeServiceList(payload) {
return Array.isArray(payload)
? payload
: (payload && Array.isArray(payload.list) ? payload.list : (payload ? [payload] : []))
},
async openService() {
if (!this.serviceList.length) {
await this.getServiceList()
}
if (!this.serviceList.length) {
uni.showToast({
title: '暂无客服信息',
icon: 'none'
})
return
}
this.servicePopupVisible = true
},
closeServicePopup() {
this.servicePopupVisible = false
},
formatServiceItem(item) {
if (!item) return ''
return `${item.type}:${item.value}`
},
handleServiceItem(item) {
console.log(item)
if (item.type === '电话') {
uni.makePhoneCall({
phoneNumber: item.content
})
return
}
if (item.type === '微信') {
uni.setClipboardData({
data: item.content,
success: () => {
uni.showToast({
title: '微信号已复制',
icon: 'none'
})
}
})
}
},
openOrder() {
this.jumpWithLogin('/pages/order/list')
},
openCoupon() {
this.jumpWithLogin('/packageUser/coupon/index')
},
openPoints() {
this.jumpWithLogin('/packageUser/points/index')
},
openProfile() {
if (!this.isLoggedIn) return this.$refs.Login.open()
uni.navigateTo({
url: '/pages/mine/profile'
})
},
openMyShare() {
this.jumpWithLogin('/pages/share/mine')
},
openAddress() {
this.jumpWithLogin('/packageUser/address/index')
},
openBonus() {
this.jumpWithLogin('/pages/bonus/index')
},
openSelfReport() {
// 「自主填报」先选填报类型(驻店填报 / 跑店填报),再进对应表单页
this.jumpWithLogin('/packageUser/self-report/select')
},
openWithdraw() {
this.jumpWithLogin('/pages/bonus/withdraw')
},
openCertification() {
this.jumpWithLogin('/pages/certification/index')
},
openTravel() {
this.jumpWithLogin('/packageUser/travel/index')
},
openVisit() {
this.jumpWithLogin('/pages/visit/index')
},
openAudit() {
this.jumpWithLogin('/pages/audit/index')
},
openChronic() {
this.jumpWithLogin('/packageUser/chronic/index')
},
openChronicManage() {
this.jumpWithLogin('/packageUser/chronic/manage')
},
jumpWithLogin(url){
if (!this.isLoggedIn)
return this.$refs.Login.open()
uni.navigateTo({
url
})
},
logout(){
clearUserInfo()
this.user = {}
this.isLoggedIn = false
this.bonus = {
taskIncome: 0,
shareIncome: 0,
totalTaskIncome: 0,
totalShareIncome: 0,
}
this.pointSummary = {
currentPoints: 0,
}
// this.$refs.Login.open()
}
}
}
</script>
<style lang="scss" scoped>
.mine-page {
min-height: 100vh;
padding: 0;
background: #f5f5f5;
}
.mine-hero {
padding: 180rpx 20rpx 20rpx 20rpx;
background: url('https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/46361d4be9d61af01e0dd97870d8765c.png') no-repeat top center / 100% auto;
}
.mine-navbar {
padding: 0 6rpx;
}
.hero-content {
margin-top: 16rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 14rpx;
}
.profile-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.profile-info {
font-size: 26rpx;
}
.name {
color: #1f2937;
font-weight: 700;
}
.phone {
color: #6b7280;
margin-top: 6rpx;
}
.login-btn {
padding: 10rpx 18rpx;
margin-left: 6rpx;
}
.hero-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8rpx;
position: relative;
}
.hero-mascot {
width: 150rpx;
height: 115rpx;
}
.city-chip {
padding: 6rpx 18rpx;
border-radius: 999rpx;
background: #ffffff;
border: 1rpx solid #2fc6a7;
color: #2fc6a7;
font-size: 24rpx;
font-weight: 700;
position: absolute;
bottom: -40rpx;
left: 50%;
transform: translateX(-50%);
width: 120rpx;
text-align: center;
}
.notice-bar {
margin: 60rpx 20rpx 0;
padding: 12rpx 16rpx;
background: #ffffff;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 10rpx;
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.06);
}
.notice-label {
color: #2fc6a7;
font-weight: 700;
font-size: 24rpx;
}
.notice-text {
flex: 1;
font-size: 24rpx;
color: #111827;
}
.notice-more {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 1rpx solid #2fc6a7;
display: flex;
align-items: center;
justify-content: center;
color: #2fc6a7;
}
.income-card {
margin: 16rpx 20rpx 0;
background: #ffffff;
border-radius: 20rpx;
padding: 18rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
flex-wrap: wrap;
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.06);
border: 1px solid #2fc6a7;
border-top: 20px solid #2fc6a7;
}
.income-item {
display: flex;
flex-direction: column;
gap: 4rpx;
flex: 1;
min-width: 0;
}
.income-value {
font-size: 30rpx;
font-weight: 700;
color: #ff3b30;
}
.income-unit {
font-size: 22rpx;
color: #6b7280;
}
.income-value--points,
.income-unit--points {
color: #2fc6a7;
}
.income-label {
font-size: 22rpx;
color: #6b7280;
}
.income-btn {
padding: 10rpx 20rpx;
border-radius: 999rpx;
background: #2fc6a7;
color: #ffffff;
font-size: 24rpx;
font-weight: 700;
flex-shrink: 0;
}
.feature-banner-group {
margin: 20rpx 20rpx 0;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.audit-banner {
flex: 1;
padding: 24rpx 14rpx;
background: linear-gradient(164.72deg, rgba(215,177,115,1) 28.42%,rgba(226,190,133,1) 60.06%,rgba(214,173,106,1) 89.27%);
color: #ffffff;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
font-size: 34rpx;
font-weight: 700;
margin: 0 20rpx 20rpx;
}
.self-report-row {
margin: 14rpx 20rpx 0;
}
.self-report-banner {
flex: 1;
border-radius: 40rpx;
background: linear-gradient(135deg, #33d0b3, #22c3a1);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 700;
box-shadow: 0 10rpx 24rpx rgba(22, 163, 74, 0.18);
margin-bottom: 20rpx;
padding: 24rpx 14rpx;
}
.audit-icon {
width: 32rpx;
height: 32rpx;
}
.quick-card {
margin: 16rpx 20rpx 0;
background: #ffffff;
border-radius: 20rpx;
padding: 20rpx 10rpx;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10rpx;
text-align: center;
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.06);
}
.quick-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
color: #1f2937;
}
.quick-icon {
width: 44rpx;
height: 44rpx;
}
.section-card {
margin: 16rpx 20rpx 0;
background: #ffffff;
border-radius: 20rpx;
padding: 18rpx 14rpx 20rpx;
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.06);
}
.section-title {
font-size: 28rpx;
font-weight: 700;
color: #1f2937;
margin-bottom: 14rpx;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18rpx 10rpx;
text-align: center;
}
.grid-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
color: #1f2937;
}
.grid-icon {
width: 48rpx;
height: 48rpx;
}
.logout-btn {
margin: 22rpx 20rpx 0;
padding: 18rpx 0;
border-radius: 999rpx;
border: 1rpx solid #2fc6a7;
color: #2fc6a7;
text-align: center;
font-size: 26rpx;
font-weight: 700;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
.bonus-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12rpx;
margin-bottom: 12rpx;
}
.bonus-item {
background: linear-gradient(135deg, #f0f5ff, #ffffff);
border-radius: 14rpx;
padding: 12rpx;
border: 1rpx solid #e7eef8;
}
.bonus-value {
font-size: 32rpx;
color: var(--brand-primary);
}
.bonus-label {
color: var(--text-sub);
font-size: 24rpx;
margin-top: 4rpx;
}
.bonus-list {
border-top: 1rpx solid #edf1f7;
}
.bonus-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 0;
border-bottom: 1rpx dashed #e3eaf4;
}
.bonus-row:last-child {
border-bottom: none;
}
.bonus-name {
font-size: 28rpx;
}
.bonus-meta {
color: var(--text-sub);
font-size: 24rpx;
}
.bonus-amount {
color: #ff3b30;
}
.withdraw-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.withdraw-label {
color: var(--text-sub);
font-size: 24rpx;
}
.withdraw-value {
font-size: 30rpx;
color: var(--brand-primary);
}
.withdraw-btn {
padding: 12rpx 20rpx;
}
.cert-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 0;
border-bottom: 1rpx solid #edf1f7;
}
.cert-row:last-child {
border-bottom: none;
}
.cert-title {
font-size: 28rpx;
}
.cert-meta {
color: var(--text-sub);
font-size: 24rpx;
}
.service-popup {
background: #ffffff;
padding: 24rpx 24rpx 32rpx;
border-radius: 24rpx 24rpx 0 0;
}
.service-popup-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.service-popup-title {
font-size: 30rpx;
font-weight: 700;
color: #1f2937;
}
.service-popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.service-popup-body {
margin-top: 16rpx;
}
.service-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.service-item {
padding: 14rpx 16rpx;
border-radius: 14rpx;
background: #f8fafc;
}
.service-item-text {
font-size: 26rpx;
color: #111827;
}
.service-empty {
padding: 20rpx 0;
text-align: center;
font-size: 24rpx;
color: #9ca3af;
}
</style>