微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"navigationBarTitleText": "客户看板",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"filter-dropdown": "/components/filter-dropdown/filter-dropdown",
|
||||
"heart-icon": "/components/heart-icon/heart-icon",
|
||||
"ai-float-button": "/components/ai-float-button/ai-float-button",
|
||||
"board-tab-bar": "/components/board-tab-bar/board-tab-bar",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
// 客户看板页 — 8 个维度查看前 100 名客户
|
||||
// TODO: 联调时替换 mock 数据为真实 API 调用
|
||||
export {}
|
||||
|
||||
/** 维度类型 → 卡片模板映射 */
|
||||
type DimType = 'recall' | 'potential' | 'balance' | 'recharge' | 'recent' | 'spend60' | 'freq60' | 'loyal'
|
||||
|
||||
const DIMENSION_TO_DIM: Record<string, DimType> = {
|
||||
recall: 'recall',
|
||||
potential: 'potential',
|
||||
balance: 'balance',
|
||||
recharge: 'recharge',
|
||||
recent: 'recent',
|
||||
spend60: 'spend60',
|
||||
freq60: 'freq60',
|
||||
loyal: 'loyal',
|
||||
}
|
||||
|
||||
const DIMENSION_OPTIONS = [
|
||||
{ value: 'recall', text: '最应召回' },
|
||||
{ value: 'potential', text: '最大消费潜力' },
|
||||
{ value: 'balance', text: '最高余额' },
|
||||
{ value: 'recharge', text: '最近充值' },
|
||||
{ value: 'recent', text: '最近到店' },
|
||||
{ value: 'spend60', text: '最高消费 近60天' },
|
||||
{ value: 'freq60', text: '最频繁 近60天' },
|
||||
{ value: 'loyal', text: '最专一 近60天' },
|
||||
]
|
||||
|
||||
const PROJECT_OPTIONS = [
|
||||
{ value: 'all', text: '全部' },
|
||||
{ value: 'chinese', text: '🎱 中式/追分' },
|
||||
{ value: 'snooker', text: '斯诺克' },
|
||||
{ value: 'mahjong', text: '🀄 麻将/棋牌' },
|
||||
{ value: 'karaoke', text: '🎤 团建/K歌' },
|
||||
]
|
||||
|
||||
interface AssistantInfo {
|
||||
name: string
|
||||
cls: string // assistant--assignee / assistant--abandoned / assistant--normal
|
||||
heartScore: number // 0-10,heart-icon 组件用
|
||||
badge?: string // 跟 / 弃
|
||||
badgeCls?: string // assistant-badge--follow / assistant-badge--drop
|
||||
}
|
||||
|
||||
interface CustomerItem {
|
||||
id: string
|
||||
name: string
|
||||
initial: string
|
||||
avatarCls: string
|
||||
// 召回维度
|
||||
idealDays: number
|
||||
elapsedDays: number
|
||||
overdueDays: number
|
||||
visits30d: number
|
||||
balance: string
|
||||
recallIndex: string
|
||||
// 消费潜力维度
|
||||
potentialTags: Array<{ text: string; theme: string }>
|
||||
spend30d: string
|
||||
avgVisits: string
|
||||
avgSpend: string
|
||||
// 余额维度
|
||||
lastVisit: string
|
||||
monthlyConsume: string
|
||||
availableMonths: string
|
||||
// 充值维度
|
||||
lastRecharge: string
|
||||
rechargeAmount: string
|
||||
recharges60d: string
|
||||
currentBalance: string
|
||||
// 消费60天
|
||||
spend60d: string
|
||||
visits60d: string
|
||||
highSpendTag: boolean
|
||||
// 频率60天
|
||||
avgInterval: string
|
||||
weeklyVisits: Array<{ val: number; pct: number }>
|
||||
// 专一度
|
||||
intimacy: string
|
||||
coachName: string
|
||||
coachRatio: string
|
||||
topCoachName: string
|
||||
topCoachHeart: number
|
||||
topCoachScore: string
|
||||
coachDetails: Array<{
|
||||
name: string
|
||||
cls: string
|
||||
heartScore: number
|
||||
badge?: string
|
||||
avgDuration: string
|
||||
serviceCount: string
|
||||
coachSpend: string
|
||||
relationIdx: number
|
||||
}>
|
||||
// 最近到店
|
||||
visitFreq: string
|
||||
daysAgo: number
|
||||
// 助教
|
||||
assistants: AssistantInfo[]
|
||||
}
|
||||
|
||||
/** Mock 数据(忠于 H5 原型 3 位客户) */
|
||||
const MOCK_CUSTOMERS: CustomerItem[] = [
|
||||
{
|
||||
id: 'u1', name: '王先生', initial: '王', avatarCls: 'avatar--amber',
|
||||
idealDays: 7, elapsedDays: 15, overdueDays: 8,
|
||||
visits30d: 3, balance: '¥2,680', recallIndex: '9.2',
|
||||
potentialTags: [
|
||||
{ text: '高频', theme: 'primary' },
|
||||
{ text: '高客单', theme: 'warning' },
|
||||
],
|
||||
spend30d: '¥4,200', avgVisits: '6.2次', avgSpend: '¥680',
|
||||
lastVisit: '3天前', monthlyConsume: '¥3,500', availableMonths: '0.8月',
|
||||
lastRecharge: '2月15日', rechargeAmount: '¥5,000', recharges60d: '2次', currentBalance: '¥2,680',
|
||||
spend60d: '¥8,400', visits60d: '12', highSpendTag: true,
|
||||
avgInterval: '5.0天', intimacy: '92',
|
||||
topCoachName: '小燕', topCoachHeart: 9.2, topCoachScore: '9.2',
|
||||
coachDetails: [
|
||||
{ name: '小燕', cls: 'assistant--assignee', heartScore: 9.2, badge: '跟', avgDuration: '2.3h', serviceCount: '14', coachSpend: '¥4,200', relationIdx: 9.2 },
|
||||
{ name: '泡芙', cls: 'assistant--normal', heartScore: 6.5, avgDuration: '1.5h', serviceCount: '8', coachSpend: '¥2,100', relationIdx: 7.2 },
|
||||
],
|
||||
weeklyVisits: [
|
||||
{ val: 2, pct: 60 }, { val: 2, pct: 60 }, { val: 1, pct: 30 }, { val: 3, pct: 100 },
|
||||
{ val: 2, pct: 60 }, { val: 3, pct: 100 }, { val: 1, pct: 30 }, { val: 3, pct: 100 },
|
||||
], coachName: '小燕', coachRatio: '78%',
|
||||
visitFreq: '6.2次/月',
|
||||
daysAgo: 3,
|
||||
assistants: [
|
||||
{ name: '小燕', cls: 'assistant--assignee', heartScore: 9.2, badge: '跟', badgeCls: 'assistant-badge--follow' },
|
||||
{ name: '泡芙', cls: 'assistant--normal', heartScore: 6.5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'u2', name: '李女士', initial: '李', avatarCls: 'avatar--pink',
|
||||
idealDays: 10, elapsedDays: 22, overdueDays: 12,
|
||||
visits30d: 1, balance: '¥8,200', recallIndex: '8.5',
|
||||
potentialTags: [
|
||||
{ text: '高余额', theme: 'success' },
|
||||
{ text: '低频', theme: 'gray' },
|
||||
],
|
||||
spend30d: '¥1,800', avgVisits: '2.1次', avgSpend: '¥860',
|
||||
lastVisit: '12天前', monthlyConsume: '¥1,800', availableMonths: '4.6月',
|
||||
lastRecharge: '1月20日', rechargeAmount: '¥10,000', recharges60d: '1次', currentBalance: '¥8,200',
|
||||
spend60d: '¥3,600', visits60d: '4', highSpendTag: false,
|
||||
avgInterval: '15.0天', intimacy: '68',
|
||||
topCoachName: 'Amy', topCoachHeart: 8.5, topCoachScore: '8.5',
|
||||
coachDetails: [
|
||||
{ name: 'Amy', cls: 'assistant--assignee', heartScore: 8.5, badge: '跟', avgDuration: '1.8h', serviceCount: '10', coachSpend: '¥3,600', relationIdx: 8.5 },
|
||||
{ name: '小燕', cls: 'assistant--abandoned', heartScore: 4.2, badge: '弃', avgDuration: '0.8h', serviceCount: '3', coachSpend: '¥600', relationIdx: 4.2 },
|
||||
],
|
||||
weeklyVisits: [
|
||||
{ val: 1, pct: 40 }, { val: 1, pct: 40 }, { val: 0, pct: 10 }, { val: 1, pct: 40 },
|
||||
{ val: 1, pct: 40 }, { val: 0, pct: 10 }, { val: 1, pct: 40 }, { val: 1, pct: 40 },
|
||||
], coachName: 'Amy', coachRatio: '62%',
|
||||
visitFreq: '2.1次/月',
|
||||
daysAgo: 12,
|
||||
assistants: [
|
||||
{ name: 'Amy', cls: 'assistant--assignee', heartScore: 8.5, badge: '跟', badgeCls: 'assistant-badge--follow' },
|
||||
{ name: '小燕', cls: 'assistant--abandoned', heartScore: 4.2, badge: '弃', badgeCls: 'assistant-badge--drop' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'u3', name: '张先生', initial: '张', avatarCls: 'avatar--blue',
|
||||
idealDays: 5, elapsedDays: 8, overdueDays: 3,
|
||||
visits30d: 5, balance: '¥1,200', recallIndex: '7.8',
|
||||
potentialTags: [
|
||||
{ text: '高频', theme: 'primary' },
|
||||
],
|
||||
spend30d: '¥3,500', avgVisits: '8.0次', avgSpend: '¥440',
|
||||
lastVisit: '1天前', monthlyConsume: '¥3,200', availableMonths: '0.4月',
|
||||
lastRecharge: '3月1日', rechargeAmount: '¥3,000', recharges60d: '3次', currentBalance: '¥1,200',
|
||||
spend60d: '¥7,000', visits60d: '16', highSpendTag: true,
|
||||
avgInterval: '3.8天', intimacy: '95',
|
||||
topCoachName: '泡芙', topCoachHeart: 9.5, topCoachScore: '9.5',
|
||||
coachDetails: [
|
||||
{ name: '泡芙', cls: 'assistant--assignee', heartScore: 9.5, badge: '跟', avgDuration: '2.1h', serviceCount: '11', coachSpend: '¥3,300', relationIdx: 9.5 },
|
||||
{ name: '小燕', cls: 'assistant--normal', heartScore: 6.8, avgDuration: '1.3h', serviceCount: '6', coachSpend: '¥1,800', relationIdx: 6.8 },
|
||||
{ name: 'Amy', cls: 'assistant--abandoned', heartScore: 3.2, badge: '弃', avgDuration: '0.5h', serviceCount: '2', coachSpend: '¥400', relationIdx: 3.2 },
|
||||
],
|
||||
weeklyVisits: [
|
||||
{ val: 2, pct: 50 }, { val: 3, pct: 75 }, { val: 2, pct: 50 }, { val: 4, pct: 100 },
|
||||
{ val: 3, pct: 75 }, { val: 3, pct: 75 }, { val: 2, pct: 50 }, { val: 3, pct: 75 },
|
||||
], coachName: '泡芙', coachRatio: '85%',
|
||||
visitFreq: '8.0次/月',
|
||||
daysAgo: 1,
|
||||
assistants: [
|
||||
{ name: '泡芙', cls: 'assistant--assignee', heartScore: 9.5, badge: '跟', badgeCls: 'assistant-badge--follow' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Page({
|
||||
data: {
|
||||
pageState: 'loading' as 'loading' | 'empty' | 'normal',
|
||||
activeTab: 'customer' as 'finance' | 'customer' | 'coach',
|
||||
|
||||
selectedDimension: 'recall',
|
||||
dimensionOptions: DIMENSION_OPTIONS,
|
||||
selectedProject: 'all',
|
||||
projectOptions: PROJECT_OPTIONS,
|
||||
|
||||
/** 当前维度类型,控制卡片模板 */
|
||||
dimType: 'recall' as DimType,
|
||||
|
||||
customers: [] as CustomerItem[],
|
||||
allCustomers: [] as CustomerItem[],
|
||||
totalCount: 0,
|
||||
|
||||
/** 筛选栏可见性 */
|
||||
filterBarVisible: true,
|
||||
},
|
||||
|
||||
_lastScrollTop: 0,
|
||||
_scrollAcc: 0,
|
||||
_scrollDir: null as 'up' | 'down' | null,
|
||||
|
||||
onLoad() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadData()
|
||||
setTimeout(() => wx.stopPullDownRefresh(), 500)
|
||||
},
|
||||
|
||||
/** 滚动隐藏/显示筛选栏 */
|
||||
onPageScroll(e: { scrollTop: number }) {
|
||||
const y = e.scrollTop
|
||||
const delta = y - this._lastScrollTop
|
||||
this._lastScrollTop = y
|
||||
|
||||
if (y <= 8) {
|
||||
if (!this.data.filterBarVisible) this.setData({ filterBarVisible: true })
|
||||
this._scrollAcc = 0
|
||||
this._scrollDir = null
|
||||
return
|
||||
}
|
||||
|
||||
if (Math.abs(delta) <= 2) return
|
||||
const dir = delta > 0 ? 'down' : 'up'
|
||||
if (dir !== this._scrollDir) {
|
||||
this._scrollDir = dir
|
||||
this._scrollAcc = 0
|
||||
}
|
||||
this._scrollAcc += Math.abs(delta)
|
||||
|
||||
const threshold = dir === 'up' ? 12 : 24
|
||||
if (this._scrollAcc < threshold) return
|
||||
|
||||
const visible = dir === 'up'
|
||||
if (this.data.filterBarVisible !== visible) {
|
||||
this.setData({ filterBarVisible: visible })
|
||||
}
|
||||
this._scrollAcc = 0
|
||||
},
|
||||
|
||||
loadData() {
|
||||
this.setData({ pageState: 'loading' })
|
||||
setTimeout(() => {
|
||||
const data = MOCK_CUSTOMERS
|
||||
if (!data || data.length === 0) {
|
||||
this.setData({ pageState: 'empty' })
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
allCustomers: data,
|
||||
customers: data,
|
||||
totalCount: data.length,
|
||||
pageState: 'normal',
|
||||
})
|
||||
}, 400)
|
||||
},
|
||||
|
||||
onTabChange(e: WechatMiniprogram.TouchEvent) {
|
||||
const tab = e.currentTarget.dataset.tab as string
|
||||
if (tab === 'finance') {
|
||||
wx.switchTab({ url: '/pages/board-finance/board-finance' })
|
||||
} else if (tab === 'coach') {
|
||||
wx.navigateTo({ url: '/pages/board-coach/board-coach' })
|
||||
}
|
||||
},
|
||||
|
||||
onDimensionChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) {
|
||||
const val = e.detail.value
|
||||
this.setData({
|
||||
selectedDimension: val,
|
||||
dimType: DIMENSION_TO_DIM[val] || 'recall',
|
||||
})
|
||||
},
|
||||
|
||||
onProjectChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) {
|
||||
this.setData({ selectedProject: e.detail.value })
|
||||
},
|
||||
|
||||
onCustomerTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const id = e.currentTarget.dataset.id as string
|
||||
wx.navigateTo({ url: '/pages/customer-detail/customer-detail?id=' + id })
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,301 @@
|
||||
<!-- 客户看板页 — 忠于 H5 原型,8 维度卡片模板 -->
|
||||
|
||||
<!-- 加载态 -->
|
||||
<view class="page-loading" wx:if="{{pageState === 'loading'}}">
|
||||
<t-loading theme="circular" size="70rpx" text="加载中..." />
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="page-empty" wx:elif="{{pageState === 'empty'}}">
|
||||
<t-empty description="暂无客户数据" />
|
||||
</view>
|
||||
|
||||
<!-- 正常态 -->
|
||||
<block wx:else>
|
||||
<!-- 顶部看板 Tab -->
|
||||
<view class="board-tabs">
|
||||
<view class="board-tab" data-tab="finance" bindtap="onTabChange">
|
||||
<text>财务</text>
|
||||
</view>
|
||||
<view class="board-tab board-tab--active" data-tab="customer">
|
||||
<text>客户</text>
|
||||
</view>
|
||||
<view class="board-tab" data-tab="coach" bindtap="onTabChange">
|
||||
<text>助教</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<view class="filter-bar {{filterBarVisible ? '' : 'filter-bar--hidden'}}">
|
||||
<view class="filter-bar-inner">
|
||||
<view class="filter-item filter-item--wide">
|
||||
<filter-dropdown
|
||||
label="最应召回"
|
||||
options="{{dimensionOptions}}"
|
||||
value="{{selectedDimension}}"
|
||||
bind:change="onDimensionChange"
|
||||
/>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<filter-dropdown
|
||||
label="全部"
|
||||
options="{{projectOptions}}"
|
||||
value="{{selectedProject}}"
|
||||
bind:change="onProjectChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表头部 -->
|
||||
<view class="list-header">
|
||||
<view class="list-header-left">
|
||||
<text class="list-header-title">客户列表</text>
|
||||
<text class="list-header-sub">· 前100名</text>
|
||||
</view>
|
||||
<text class="list-header-count">共{{totalCount}}名客户</text>
|
||||
</view>
|
||||
|
||||
<!-- 客户列表 -->
|
||||
<view class="customer-list">
|
||||
<view
|
||||
class="customer-card"
|
||||
wx:for="{{customers}}"
|
||||
wx:key="id"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="onCustomerTap"
|
||||
>
|
||||
<!-- ===== 卡片头部:头像 + 姓名 + 右侧指标 ===== -->
|
||||
<view class="card-header">
|
||||
<view class="card-avatar {{item.avatarCls}}">
|
||||
<text class="avatar-text">{{item.initial}}</text>
|
||||
</view>
|
||||
<text class="card-name" wx:if="{{dimType !== 'freq60'}}">{{item.name}}</text>
|
||||
<!-- 最频繁:姓名 + 下方小字垂直排列 -->
|
||||
<view class="card-name-group" wx:if="{{dimType === 'freq60'}}">
|
||||
<text class="card-name">{{item.name}}</text>
|
||||
<view class="card-name-sub">
|
||||
<text class="mid-text" style="white-space: nowrap;">平均间隔 <text class="mid-primary">{{item.avgInterval}}</text></text>
|
||||
<text class="mid-text" style="margin-left: 16rpx; white-space: nowrap;">60天消费 <text class="mid-dark">{{item.spend60d}}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-header-spacer"></view>
|
||||
|
||||
<!-- 最应召回:理想/已过/超期 -->
|
||||
<view class="header-metrics" wx:if="{{dimType === 'recall'}}">
|
||||
<text class="metric-gray">理想 <text class="metric-dark">{{item.idealDays}}天</text></text>
|
||||
<text class="metric-gray">已过 <text class="metric-error">{{item.elapsedDays}}天</text></text>
|
||||
<view class="overdue-tag {{item.overdueDays > 7 ? 'overdue-tag--danger' : 'overdue-tag--warn'}}">
|
||||
<text>超期 {{item.overdueDays}}天</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最大消费潜力:频率/客单/余额标签 -->
|
||||
<view class="header-metrics" wx:elif="{{dimType === 'potential'}}">
|
||||
<view class="potential-tag potential-tag--{{tag.theme}}" wx:for="{{item.potentialTags}}" wx:for-item="tag" wx:key="text">
|
||||
<text>{{tag.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最高余额:最近到店/理想 -->
|
||||
<view class="header-metrics" wx:elif="{{dimType === 'balance'}}">
|
||||
<text class="metric-gray">最近到店 <text class="metric-dark">{{item.lastVisit}}</text></text>
|
||||
<text class="metric-gray">理想 <text class="metric-dark">{{item.idealDays}}天</text></text>
|
||||
</view>
|
||||
|
||||
<!-- 最近充值:最近到店/理想 -->
|
||||
<view class="header-metrics" wx:elif="{{dimType === 'recharge'}}">
|
||||
<text class="metric-gray">最近到店 <text class="metric-dark">{{item.lastVisit}}</text></text>
|
||||
<text class="metric-gray">理想 <text class="metric-dark">{{item.idealDays}}天</text></text>
|
||||
</view>
|
||||
|
||||
<!-- 最频繁近60天:右上角大字到店次数 -->
|
||||
<view class="header-metrics header-metrics--freq" wx:elif="{{dimType === 'freq60'}}">
|
||||
<view class="freq-big-num">
|
||||
<text class="freq-big-val">{{item.visits60d}}</text>
|
||||
<text class="freq-big-unit">次</text>
|
||||
</view>
|
||||
<text class="freq-big-label">60天到店</text>
|
||||
</view>
|
||||
|
||||
<!-- 最专一近60天:右上角 ❤️ 助教名 + 关系指数 -->
|
||||
<view class="header-metrics header-metrics--loyal" wx:elif="{{dimType === 'loyal'}}">
|
||||
<heart-icon score="{{item.topCoachHeart}}" />
|
||||
<text class="loyal-top-name">{{item.topCoachName}}</text>
|
||||
<text class="loyal-top-score">{{item.topCoachScore}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 最高消费近60天:高消费标签 -->
|
||||
<view class="header-metrics" wx:elif="{{dimType === 'spend60'}}">
|
||||
<view class="potential-tag potential-tag--warning" wx:if="{{item.highSpendTag}}">
|
||||
<text>高消费</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近到店:右上角大字 X天前到店 -->
|
||||
<view class="header-metrics header-metrics--recent" wx:elif="{{dimType === 'recent'}}">
|
||||
<view class="recent-big-num">
|
||||
<text class="recent-big-val">{{item.daysAgo}}</text>
|
||||
<text class="recent-big-unit">天前到店</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ===== 卡片中间行:维度特定数据 ===== -->
|
||||
|
||||
<!-- 最应召回:30天到店 / 余额 / 召回指数 -->
|
||||
<view class="card-mid-row" wx:if="{{dimType === 'recall'}}">
|
||||
<text class="mid-text">30天到店 <text class="mid-dark">{{item.visits30d}}次</text></text>
|
||||
<text class="mid-text mid-ml">余额 <text class="mid-dark">{{item.balance}}</text></text>
|
||||
<text class="mid-text mid-right">召回指数 <text class="mid-primary-bold">{{item.recallIndex}}</text></text>
|
||||
</view>
|
||||
|
||||
<!-- 最大消费潜力:4 列网格(30天消费用橙色大字,和最高余额的余额值一致) -->
|
||||
<view class="card-grid card-grid--4" wx:elif="{{dimType === 'potential'}}">
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">30天消费</text>
|
||||
<text class="grid-val grid-val--warning grid-val--lg">{{item.spend30d}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">月均到店</text>
|
||||
<text class="grid-val">{{item.avgVisits}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">余额</text>
|
||||
<text class="grid-val grid-val--success">{{item.balance}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">次均消费</text>
|
||||
<text class="grid-val">{{item.avgSpend}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最高余额:3 列网格 -->
|
||||
<view class="card-grid card-grid--3" wx:elif="{{dimType === 'balance'}}">
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">余额</text>
|
||||
<text class="grid-val grid-val--warning grid-val--lg">{{item.balance}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">月均消耗</text>
|
||||
<text class="grid-val">{{item.monthlyConsume}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">可用</text>
|
||||
<text class="grid-val grid-val--success">{{item.availableMonths}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近充值:4 列网格 -->
|
||||
<view class="card-grid card-grid--4" wx:elif="{{dimType === 'recharge'}}">
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">最后充值</text>
|
||||
<text class="grid-val">{{item.lastRecharge}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">充值</text>
|
||||
<text class="grid-val grid-val--success">{{item.rechargeAmount}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">60天充值</text>
|
||||
<text class="grid-val">{{item.recharges60d}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">当前余额</text>
|
||||
<text class="grid-val grid-val--warning">{{item.currentBalance}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最高消费近60天:3 列网格 -->
|
||||
<view class="card-grid card-grid--3" wx:elif="{{dimType === 'spend60'}}">
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">近60天消费</text>
|
||||
<text class="grid-val grid-val--warning grid-val--lg">{{item.spend60d}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">到店次数</text>
|
||||
<text class="grid-val">{{item.visits60d}}</text>
|
||||
</view>
|
||||
<view class="grid-cell">
|
||||
<text class="grid-label">次均消费</text>
|
||||
<text class="grid-val">{{item.avgSpend}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最频繁近60天:无中间行(数据已在头部) -->
|
||||
|
||||
<!-- 最频繁:迷你柱状图(8 周) -->
|
||||
<view class="mini-chart" wx:if="{{dimType === 'freq60'}}">
|
||||
<view class="mini-chart-header">
|
||||
<text class="mini-chart-label">8周前</text>
|
||||
<text class="mini-chart-label">本周</text>
|
||||
</view>
|
||||
<view class="mini-chart-bars">
|
||||
<view class="mini-bar-col" wx:for="{{item.weeklyVisits}}" wx:for-item="wv" wx:for-index="wIdx" wx:key="wIdx">
|
||||
<view class="mini-bar" style="height:{{wv.pct}}%;opacity:{{0.2 + wv.pct * 0.006}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mini-chart-nums">
|
||||
<text class="mini-chart-num {{wIdx === item.weeklyVisits.length - 1 ? 'mini-chart-num--active' : ''}}" wx:for="{{item.weeklyVisits}}" wx:for-item="wv" wx:for-index="wIdx" wx:key="wIdx">{{wv.val}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最专一近60天:助教服务明细表 -->
|
||||
<view class="loyal-table" wx:elif="{{dimType === 'loyal'}}">
|
||||
<!-- 表头 -->
|
||||
<view class="loyal-row loyal-row--header">
|
||||
<text class="loyal-col loyal-col--name">助教</text>
|
||||
<text class="loyal-col">次均时长</text>
|
||||
<text class="loyal-col">服务次数</text>
|
||||
<text class="loyal-col">助教消费</text>
|
||||
<text class="loyal-col">关系指数</text>
|
||||
</view>
|
||||
<!-- 数据行 -->
|
||||
<view class="loyal-row" wx:for="{{item.coachDetails}}" wx:for-item="cd" wx:key="name">
|
||||
<view class="loyal-col loyal-col--name">
|
||||
<heart-icon score="{{cd.heartScore}}" />
|
||||
<text class="loyal-coach-name {{cd.cls}}">{{cd.name}}</text>
|
||||
<text class="assistant-badge assistant-badge--follow" wx:if="{{cd.badge === '跟'}}">跟</text>
|
||||
<text class="assistant-badge assistant-badge--drop" wx:elif="{{cd.badge === '弃'}}">弃</text>
|
||||
</view>
|
||||
<text class="loyal-col loyal-val">{{cd.avgDuration}}</text>
|
||||
<text class="loyal-col loyal-val">{{cd.serviceCount}}</text>
|
||||
<text class="loyal-col loyal-val">{{cd.coachSpend}}</text>
|
||||
<text class="loyal-col {{cd.relationIdx >= 8 ? 'loyal-val--primary' : 'loyal-val--gray'}}">{{cd.relationIdx}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近到店:理想间隔 / 60天到店 / 次均消费 -->
|
||||
<view class="card-mid-row" wx:elif="{{dimType === 'recent'}}">
|
||||
<text class="mid-text">理想间隔 <text class="mid-dark">{{item.idealDays}}天</text></text>
|
||||
<text class="mid-text mid-ml">60天到店 <text class="mid-dark">{{item.visits60d}}天</text></text>
|
||||
<text class="mid-text mid-ml">次均消费 <text class="mid-dark">{{item.avgSpend}}</text></text>
|
||||
</view>
|
||||
|
||||
<!-- ===== 卡片底部:助教行(最专一维度不显示,因为助教信息在表格中) ===== -->
|
||||
<view class="card-assistant-row" wx:if="{{item.assistants && item.assistants.length > 0 && dimType !== 'loyal'}}">
|
||||
<text class="assistant-label">助教:</text>
|
||||
<block wx:for="{{item.assistants}}" wx:for-item="ast" wx:for-index="astIdx" wx:key="name">
|
||||
<text class="assistant-sep" wx:if="{{astIdx > 0}}">|</text>
|
||||
<view class="assistant-tag">
|
||||
<heart-icon score="{{ast.heartScore}}" />
|
||||
<text class="assistant-name {{ast.cls}}">{{ast.name}}</text>
|
||||
<text class="assistant-badge assistant-badge--follow" wx:if="{{ast.badge === '跟'}}">跟</text>
|
||||
<text class="assistant-badge assistant-badge--drop" wx:elif="{{ast.badge === '弃'}}">弃</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区 -->
|
||||
<view class="safe-bottom"></view>
|
||||
</block>
|
||||
|
||||
<!-- 自定义底部导航栏 -->
|
||||
<board-tab-bar active="board" />
|
||||
|
||||
<!-- AI 悬浮按钮 -->
|
||||
<ai-float-button bottom="{{220}}" />
|
||||
|
||||
<dev-fab />
|
||||
@@ -0,0 +1,637 @@
|
||||
/* 客户看板页 — 忠于 H5 原型,87.5% 缩放 */
|
||||
|
||||
/* ===== 三态 ===== */
|
||||
.page-loading,
|
||||
.page-empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60vh;
|
||||
}
|
||||
|
||||
/* ===== 看板 Tab(对齐 board-coach 规范) ===== */
|
||||
.board-tabs {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
border-bottom: 2rpx solid #eeeeee;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.board-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #8b8b8b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.board-tab--active {
|
||||
color: #0052d9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.board-tab--active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 42rpx;
|
||||
height: 5rpx;
|
||||
background: linear-gradient(90deg, #0052d9, #5b9cf8);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
/* ===== 筛选区域(对齐 board-coach 规范) ===== */
|
||||
.filter-bar {
|
||||
background: #f3f3f3;
|
||||
padding: 14rpx 28rpx;
|
||||
position: sticky;
|
||||
top: 70rpx;
|
||||
z-index: 15;
|
||||
border-bottom: 2rpx solid #eeeeee;
|
||||
transition: transform 220ms ease, opacity 220ms ease;
|
||||
}
|
||||
|
||||
.filter-bar--hidden {
|
||||
opacity: 0;
|
||||
transform: translateY(-110%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.filter-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 14rpx;
|
||||
padding: 10rpx;
|
||||
border: 2rpx solid #eeeeee;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-item--wide {
|
||||
flex: 1.8;
|
||||
}
|
||||
|
||||
/* ===== 列表头部 ===== */
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 28rpx 12rpx;
|
||||
}
|
||||
|
||||
.list-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.list-header-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #242424;
|
||||
}
|
||||
|
||||
.list-header-sub {
|
||||
font-size: 24rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.list-header-count {
|
||||
font-size: 24rpx;
|
||||
color: #c5c5c5;
|
||||
}
|
||||
|
||||
/* ===== 客户列表 ===== */
|
||||
.customer-list {
|
||||
padding: 0 28rpx 24rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* ===== 客户卡片 ===== */
|
||||
.customer-card {
|
||||
background: #ffffff;
|
||||
border-radius: 28rpx;
|
||||
padding: 30rpx 28rpx 26rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.customer-card:active {
|
||||
opacity: 0.96;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* ===== 卡片头部 ===== */
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.card-avatar {
|
||||
width: 66rpx;
|
||||
height: 66rpx;
|
||||
border-radius: 14rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 头像渐变色 */
|
||||
.avatar--amber { background: linear-gradient(135deg, #fbbf24, #f97316); }
|
||||
.avatar--pink { background: linear-gradient(135deg, #f472b6, #f43f5e); }
|
||||
.avatar--blue { background: linear-gradient(135deg, #60a5fa, #6366f1); }
|
||||
.avatar--green { background: linear-gradient(135deg, #4ade80, #10b981); }
|
||||
.avatar--purple { background: linear-gradient(135deg, #a78bfa, #8b5cf6); }
|
||||
.avatar--cyan { background: linear-gradient(135deg, #22d3ee, #14b8a6); }
|
||||
.avatar--rose { background: linear-gradient(135deg, #fb7185, #e11d48); }
|
||||
.avatar--indigo { background: linear-gradient(135deg, #818cf8, #4f46e5); }
|
||||
|
||||
.card-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #242424;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 最频繁:姓名+小字垂直排列 */
|
||||
.card-name-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-name-sub {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 2rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 头部右侧指标区 */
|
||||
.header-metrics {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 最频繁维度:右上角大字到店次数 */
|
||||
.header-metrics--freq {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.freq-big-num {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.freq-big-val {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #0052d9;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.freq-big-unit {
|
||||
font-size: 22rpx;
|
||||
font-weight: 400;
|
||||
color: #a6a6a6;
|
||||
margin-left: 2rpx;
|
||||
}
|
||||
|
||||
.freq-big-label {
|
||||
font-size: 20rpx;
|
||||
color: #a6a6a6;
|
||||
margin-top: -2rpx;
|
||||
}
|
||||
|
||||
/* 最近到店维度:右上角大字 X天前到店 */
|
||||
.header-metrics--recent {
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.recent-big-num {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.recent-big-val {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #00a870;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.recent-big-unit {
|
||||
font-size: 22rpx;
|
||||
font-weight: 400;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.metric-gray {
|
||||
font-size: 22rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.metric-dark {
|
||||
color: #393939;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.metric-error {
|
||||
color: #e34d59;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 超期标签 */
|
||||
.overdue-tag {
|
||||
padding: 4rpx 10rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.overdue-tag--danger {
|
||||
background: rgba(227, 77, 89, 0.1);
|
||||
color: #e34d59;
|
||||
}
|
||||
|
||||
.overdue-tag--warn {
|
||||
background: rgba(237, 123, 47, 0.1);
|
||||
color: #ed7b2f;
|
||||
}
|
||||
|
||||
/* 消费潜力标签 */
|
||||
.potential-tag {
|
||||
padding: 4rpx 10rpx;
|
||||
font-size: 22rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.potential-tag--primary {
|
||||
background: rgba(0, 82, 217, 0.1);
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.potential-tag--warning {
|
||||
background: rgba(237, 123, 47, 0.1);
|
||||
color: #ed7b2f;
|
||||
}
|
||||
|
||||
.potential-tag--success {
|
||||
background: rgba(0, 168, 112, 0.1);
|
||||
color: #00a870;
|
||||
}
|
||||
|
||||
.potential-tag--gray {
|
||||
background: #eeeeee;
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
/* ===== 卡片中间行(flex 布局,左对齐名字位置) ===== */
|
||||
.card-mid-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6rpx 0 4rpx 80rpx;
|
||||
}
|
||||
|
||||
.mid-text {
|
||||
font-size: 24rpx;
|
||||
color: #c5c5c5;
|
||||
}
|
||||
|
||||
.mid-dark {
|
||||
color: #393939;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mid-primary {
|
||||
color: #0052d9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mid-primary-bold {
|
||||
color: #0052d9;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mid-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.mid-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.mid-error {
|
||||
color: #e34d59;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ===== 网格布局 ===== */
|
||||
.card-grid {
|
||||
display: grid;
|
||||
gap: 12rpx;
|
||||
padding: 6rpx 0 4rpx 80rpx;
|
||||
}
|
||||
|
||||
.card-grid--3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-grid--4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.grid-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.grid-label {
|
||||
font-size: 18rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.grid-val {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #393939;
|
||||
}
|
||||
|
||||
.grid-val--success {
|
||||
color: #00a870;
|
||||
}
|
||||
|
||||
.grid-val--warning {
|
||||
color: #ed7b2f;
|
||||
}
|
||||
|
||||
.grid-val--lg {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ===== 迷你柱状图(最频繁维度) ===== */
|
||||
.mini-chart {
|
||||
padding: 8rpx 0 4rpx 80rpx;
|
||||
}
|
||||
|
||||
.mini-chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.mini-chart-label {
|
||||
font-size: 18rpx;
|
||||
color: #c5c5c5;
|
||||
}
|
||||
|
||||
.mini-chart-bars {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 6rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.mini-bar-col {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mini-bar {
|
||||
width: 100%;
|
||||
background: rgba(0, 82, 217, 0.3);
|
||||
border-radius: 4rpx 4rpx 0 0;
|
||||
min-height: 4rpx;
|
||||
}
|
||||
|
||||
.mini-chart-nums {
|
||||
display: flex;
|
||||
gap: 6rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.mini-chart-num {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 18rpx;
|
||||
color: #c5c5c5;
|
||||
}
|
||||
|
||||
.mini-chart-num--active {
|
||||
color: #0052d9;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ===== 助教行 ===== */
|
||||
.card-assistant-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-top: 10rpx;
|
||||
margin-left: 80rpx;
|
||||
padding-top: 10rpx;
|
||||
border-top: 2rpx solid rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.assistant-label {
|
||||
font-size: 22rpx;
|
||||
color: #a6a6a6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.assistant-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.assistant-heart {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.assistant-name {
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.assistant-name.assistant--assignee {
|
||||
color: #e34d59;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.assistant-name.assistant--abandoned {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.assistant-name.assistant--normal {
|
||||
color: #242424;
|
||||
}
|
||||
|
||||
.assistant-sep {
|
||||
font-size: 20rpx;
|
||||
color: #c5c5c5;
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
|
||||
/* 跟/弃 badge — 忠于 H5 原型:白字 + 渐变背景 + 阴影 */
|
||||
.assistant-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 28rpx;
|
||||
height: 24rpx;
|
||||
padding: 0 8rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 18rpx;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5rpx;
|
||||
margin-left: 4rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.assistant-badge--follow {
|
||||
background: linear-gradient(135deg, #e34d59 0%, #f26a76 100%);
|
||||
border: 1rpx solid rgba(227, 77, 89, 0.28);
|
||||
box-shadow: 0 2rpx 6rpx rgba(227, 77, 89, 0.28);
|
||||
}
|
||||
|
||||
.assistant-badge--drop {
|
||||
background: linear-gradient(135deg, #d4d4d4 0%, #b6b6b6 100%);
|
||||
border: 1rpx solid rgba(120, 120, 120, 0.18);
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
/* ===== 最专一维度:助教服务明细表 ===== */
|
||||
.loyal-table {
|
||||
padding: 6rpx 0 4rpx 80rpx;
|
||||
border-left: 4rpx solid #eeeeee;
|
||||
margin-left: 80rpx;
|
||||
padding-left: 14rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.loyal-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
padding: 6rpx 0;
|
||||
}
|
||||
|
||||
.loyal-row--header {
|
||||
padding-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.loyal-row--header .loyal-col {
|
||||
font-size: 20rpx;
|
||||
color: #c5c5c5;
|
||||
}
|
||||
|
||||
.loyal-col {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.loyal-col--name {
|
||||
width: 140rpx;
|
||||
flex: none;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.loyal-coach-name {
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loyal-coach-name.assistant--assignee {
|
||||
color: #e34d59;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.loyal-coach-name.assistant--abandoned {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.loyal-coach-name.assistant--normal {
|
||||
color: #242424;
|
||||
}
|
||||
|
||||
.loyal-val {
|
||||
font-weight: 600;
|
||||
color: #393939;
|
||||
}
|
||||
|
||||
.loyal-val--primary {
|
||||
font-weight: 700;
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.loyal-val--gray {
|
||||
color: #8b8b8b;
|
||||
}
|
||||
|
||||
/* 最专一头部右侧 */
|
||||
.header-metrics--loyal {
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.loyal-top-name {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e34d59;
|
||||
}
|
||||
|
||||
.loyal-top-score {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
/* ===== 底部安全区 ===== */
|
||||
.safe-bottom {
|
||||
height: 200rpx;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
Reference in New Issue
Block a user