feat:引入 TDesign 以及前端初稿

This commit is contained in:
Neo
2026-01-24 22:56:57 +08:00
parent 6147924889
commit c972c52e04
80 changed files with 35245 additions and 0 deletions

View File

@@ -0,0 +1,524 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>看板-客户 - 球房运营助手</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#0052d9',
'primary-light': '#ecf2fe',
success: '#00a870',
warning: '#ed7b2f',
error: '#e34d59',
'gray-1': '#f3f3f3',
'gray-2': '#eeeeee',
'gray-3': '#e7e7e7',
'gray-4': '#dcdcdc',
'gray-5': '#c5c5c5',
'gray-6': '#a6a6a6',
'gray-7': '#8b8b8b',
'gray-8': '#777777',
'gray-9': '#5e5e5e',
'gray-10': '#4b4b4b',
'gray-11': '#393939',
'gray-12': '#2c2c2c',
'gray-13': '#242424',
},
fontFamily: {
sans: ['Noto Sans SC', 'sans-serif'],
}
}
}
}
</script>
<style>
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
padding-bottom: 80px;
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 44px);
}
.tab-active {
color: #0052d9;
position: relative;
}
.tab-active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 24px;
height: 3px;
background: linear-gradient(90deg, #0052d9, #5b9cf8);
border-radius: 2px;
}
.filter-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.filter-overlay.show {
display: block;
}
.filter-dropdown {
display: none;
position: fixed;
left: 0;
right: 0;
background: white;
border-radius: 0 0 16px 16px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
z-index: 1001;
max-height: 60vh;
overflow-y: auto;
}
.filter-dropdown.show {
display: block;
}
/* 二级筛选栏层级与动效 */
#filterBar {
overflow: hidden;
max-height: 120px;
transition: transform 220ms ease, opacity 220ms ease, max-height 220ms ease;
will-change: transform, opacity;
}
/* 仅用于“首次进入页面”的下滑出现(用 transition 触发一次) */
#filterBar.filter-bar-enter {
transform: translateY(-16px);
opacity: 0;
}
#filterBar.filter-bar-hidden {
opacity: 0;
transform: translateY(-110%);
pointer-events: none;
}
@keyframes filterBarDrop {
from { transform: translateY(-16px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
#filterBar { animation: none; transition: none; }
}
.customer-card {
transition: all 0.2s ease;
}
.customer-card:active {
transform: scale(0.98);
}
</style>
</head>
<body class="bg-gray-1 min-h-screen">
<!-- 顶部导航 -->
<div class="safe-area-top bg-white sticky top-0 z-20 shadow-sm">
<div class="h-11 flex items-center justify-center relative border-b border-gray-2">
<h1 class="text-base font-medium text-gray-13">看板</h1>
</div>
<!-- 一级 Tab -->
<div class="flex">
<a href="board-finance.html" class="flex-1 py-3 text-center text-sm font-medium text-gray-7">财务</a>
<a href="board-customer.html" class="flex-1 py-3 text-center text-sm font-medium tab-active">客户</a>
<a href="board-coach.html" class="flex-1 py-3 text-center text-sm font-medium text-gray-7">助教</a>
</div>
</div>
<!-- 筛选区域(二级) -->
<div id="filterBar" class="bg-gray-1 sticky top-[88px] z-10 px-4 py-2 border-b border-gray-2">
<div class="bg-white rounded-2xl p-1.5 flex gap-2 shadow-sm border border-gray-2">
<button onclick="toggleFilter('type')" class="flex-1 px-3 py-2 bg-gray-50 rounded-lg text-sm text-gray-10 flex items-center justify-center gap-1 border border-gray-100">
<span id="typeLabel">最应召回</span>
<svg class="w-4 h-4 text-gray-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<button onclick="toggleFilter('project')" class="flex-1 px-3 py-2 bg-gray-50 rounded-lg text-sm text-gray-10 flex items-center justify-center gap-1 border border-gray-100">
<span id="projectLabel">不限</span>
<svg class="w-4 h-4 text-gray-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
</div>
</div>
<!-- 遮罩层 -->
<div id="filterOverlay" class="filter-overlay" onclick="closeAllFilters()"></div>
<!-- 类型筛选弹窗 -->
<div id="typeDropdown" class="filter-dropdown">
<div class="p-4 space-y-2">
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最近到店')">最近到店</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最应召回')">最应召回</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最近充值')">最近充值</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最高消费')">最高消费</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最高余额')">最高余额</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最频繁')">最频繁</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('潜力股')">潜力股</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectType('最专一')">最专一</div>
</div>
</div>
<!-- 项目筛选弹窗 -->
<div id="projectDropdown" class="filter-dropdown">
<div class="p-4 space-y-2">
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectProject('不限')">不限</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectProject('中式/追分')">中式/追分</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectProject('斯诺克')">斯诺克</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectProject('麻将')">麻将</div>
<div class="px-4 py-3 text-sm text-gray-10 hover:bg-primary/5 rounded-lg cursor-pointer" onclick="selectProject('团建')">团建</div>
</div>
</div>
<!-- 客户列表 -->
<div class="p-4 space-y-3">
<!-- 客户卡片 1 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">王先生</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded">VIP</span>
</div>
<p class="text-xs text-gray-6">最近15天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.92</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💖 小燕</span>
<span>💛 泡芙</span>
</div>
<span class="text-gray-6">30天到店5次</span>
</div>
</a>
<!-- 客户卡片 2 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">李女士</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded">VIP</span>
</div>
<p class="text-xs text-gray-6">最近20天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.88</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💖 Amy</span>
<span>💖 小燕</span>
</div>
<span class="text-gray-6">余额8000元</span>
</div>
</a>
<!-- 客户卡片 3 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">张先生</span>
</div>
<p class="text-xs text-gray-6">最近10天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.85</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💖 小燕</span>
</div>
<span class="text-gray-6">高频客户</span>
</div>
</a>
<!-- 客户卡片 4 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">刘先生</span>
</div>
<p class="text-xs text-gray-6">最近8天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.78</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💛 泡芙</span>
<span>💛 Amy</span>
</div>
<span class="text-gray-6">偏好斯诺克</span>
</div>
</a>
<!-- 客户卡片 5 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-400 to-violet-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">陈先生</span>
</div>
<p class="text-xs text-gray-6">最近12天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.72</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💛 小燕</span>
</div>
<span class="text-gray-6">潜力客户</span>
</div>
</a>
<!-- 客户卡片 6 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-rose-400 to-pink-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">赵女士</span>
<span class="px-1.5 py-0.5 bg-error/10 text-error text-xs rounded">VIP</span>
</div>
<p class="text-xs text-gray-6">最近5天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.68</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💖 泡芙</span>
<span>💖 小燕</span>
<span>💛 Amy</span>
</div>
<span class="text-gray-6">余额12000元</span>
</div>
</a>
<!-- 客户卡片 7 -->
<a href="customer-detail.html" class="block bg-white rounded-2xl p-4 shadow-sm customer-card">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-400 to-teal-500 flex items-center justify-center">
<span class="text-white font-semibold"></span>
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold text-gray-13">周先生</span>
</div>
<p class="text-xs text-gray-6">最近7天前</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-primary font-semibold">0.65</p>
<p class="text-xs text-gray-6">召回因子</p>
</div>
</div>
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2 text-gray-6">
<span>💛 Amy</span>
</div>
<span class="text-gray-6">新客户</span>
</div>
</a>
</div>
<!-- 悬浮助手按钮 -->
<script src="../js/ai-float-btn.js"></script>
<!-- 通用底部导航 -->
<script src="../js/bottom-nav.js"></script>
<script>
let currentFilter = null;
function positionDropdown(dropdownEl) {
const bar = document.getElementById('filterBar');
if (!bar || !dropdownEl) return;
const rect = bar.getBoundingClientRect();
dropdownEl.style.top = `${rect.bottom}px`;
}
function toggleFilter(type) {
const overlay = document.getElementById('filterOverlay');
const typeDropdown = document.getElementById('typeDropdown');
const projectDropdown = document.getElementById('projectDropdown');
if (currentFilter === type) {
closeAllFilters();
return;
}
closeAllFilters();
currentFilter = type;
overlay.classList.add('show');
if (type === 'type') {
positionDropdown(typeDropdown);
typeDropdown.classList.add('show');
} else if (type === 'project') {
positionDropdown(projectDropdown);
projectDropdown.classList.add('show');
}
}
function closeAllFilters() {
currentFilter = null;
document.getElementById('filterOverlay').classList.remove('show');
document.getElementById('typeDropdown').classList.remove('show');
document.getElementById('projectDropdown').classList.remove('show');
}
function selectType(value) {
document.getElementById('typeLabel').textContent = value;
closeAllFilters();
}
function selectProject(value) {
document.getElementById('projectLabel').textContent = value;
closeAllFilters();
}
// 滚动时:下滑隐藏筛选栏,上滑显示(单次滚动手势结束后判断一次,避免抖动)
(function initFilterBarScrollBehavior() {
const bar = document.getElementById('filterBar');
if (!bar) return;
const TRIGGER_DISTANCE_DOWN = 24;
const TRIGGER_DISTANCE_UP = 12;
const JITTER_THRESHOLD = 2;
let lastY = window.scrollY || 0;
let lastDir = null; // 'down' | 'up'
let ticking = false;
let acc = 0;
let lockedDir = null; // 方向锁:同方向只触发一次,直到方向反转
// 首次进入:用 transition 做一次从上到下出现
bar.classList.add('filter-bar-enter');
window.requestAnimationFrame(() => {
bar.classList.remove('filter-bar-enter');
});
function setVisible(visible) {
if (visible) {
bar.classList.remove('filter-bar-hidden');
} else {
if (!bar.classList.contains('filter-bar-hidden')) closeAllFilters();
bar.classList.add('filter-bar-hidden');
}
}
function resetGesture(y) {
lastY = y;
lastDir = null;
acc = 0;
lockedDir = null;
}
function onScrollFrame() {
const y = window.scrollY || 0;
if (y <= 8) {
setVisible(true);
resetGesture(y);
return;
}
const delta = y - lastY;
if (Math.abs(delta) <= JITTER_THRESHOLD) {
lastY = y;
return;
}
const dir = delta > 0 ? 'down' : 'up';
if (lastDir === null) {
lastDir = dir;
} else if (dir !== lastDir) {
lastDir = dir;
lockedDir = null;
acc = 0;
}
lastY = y;
// 同方向只触发一次:锁住直到方向反转
if (lockedDir === dir) return;
acc += Math.abs(delta);
const threshold = dir === 'up' ? TRIGGER_DISTANCE_UP : TRIGGER_DISTANCE_DOWN;
if (acc < threshold) return;
setVisible(dir === 'up');
lockedDir = dir;
acc = 0;
}
window.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(() => {
onScrollFrame();
ticking = false;
});
}, { passive: true });
})();
</script>
</body>
</html>