init: 项目初始提交 - NeoZQYY Monorepo 完整代码
This commit is contained in:
955
docs/h5_ui/pages/task-list.html
Normal file
955
docs/h5_ui/pages/task-list.html
Normal file
@@ -0,0 +1,955 @@
|
||||
<!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">
|
||||
<link href="../css/banner.css" 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: 70px;
|
||||
}
|
||||
/* 任务卡片边框颜色 */
|
||||
.task-card {
|
||||
position: relative;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
.task-card.high-priority {
|
||||
border-left-color: #f43f5e;
|
||||
}
|
||||
.task-card.priority {
|
||||
border-left-color: #f97316;
|
||||
}
|
||||
.task-card.relationship {
|
||||
border-left-color: #ec4899;
|
||||
}
|
||||
.task-card.callback {
|
||||
border-left-color: #14b8a6;
|
||||
}
|
||||
/* 标签颜色 - 圆角矩形 */
|
||||
.tag-high-priority {
|
||||
background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.tag-priority {
|
||||
background: linear-gradient(135deg, #ea580c 0%, #f97316 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.tag-relationship {
|
||||
background: linear-gradient(135deg, #db2777 0%, #ec4899 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.tag-callback {
|
||||
background: linear-gradient(135deg, #0d9488 0%, #14b8a6 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* 进度条动画 */
|
||||
.progress-bar {
|
||||
background: linear-gradient(90deg, #f59e0b 0%, #fbbf24 50%, #fcd34d 100%);
|
||||
transition: width 0.6s ease-out;
|
||||
}
|
||||
/* 6段档位进度条 */
|
||||
.tier-progress {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
height: 8px;
|
||||
}
|
||||
.tier-segment {
|
||||
border-radius: 2px;
|
||||
background: rgba(255,255,255,0.25);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tier-segment.completed {
|
||||
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
||||
}
|
||||
.tier-segment.current {
|
||||
background: linear-gradient(90deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.25) 50%, rgba(255,255,255,0.25) 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
.tier-segment.current .tier-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* 红戳样式 - 透明印章风格 */
|
||||
.red-stamp {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.stamp-badge {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -28px;
|
||||
transform: rotate(-12deg) scale(0);
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border: 3px solid rgb(239, 68, 68);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
box-shadow:
|
||||
inset 0 0 0 2px rgba(255, 255, 255, 0.95),
|
||||
inset 0 0 10px 2px rgba(255, 255, 255, 0.7),
|
||||
inset 0 0 20px 4px rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
/* 盖戳动画 */
|
||||
@keyframes stampDown {
|
||||
0% {
|
||||
transform: rotate(-12deg) scale(3);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: rotate(-12deg) scale(0.9);
|
||||
opacity: 0.9;
|
||||
}
|
||||
70% {
|
||||
transform: rotate(-12deg) scale(1.05);
|
||||
opacity: 0.85;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-12deg) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.stamp-badge.stamp-animate {
|
||||
animation: stampDown 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||
}
|
||||
.stamp-badge .thumb {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
.stamp-badge .stamp-text {
|
||||
font-size: 11px;
|
||||
color: rgb(220, 38, 38);
|
||||
font-weight: bold;
|
||||
margin-top: 1px;
|
||||
text-shadow:
|
||||
0 0 2px rgba(255,255,255,1),
|
||||
0 0 4px rgba(255,255,255,0.9),
|
||||
1px 1px 0 rgba(255,255,255,0.9),
|
||||
-1px -1px 0 rgba(255,255,255,0.9),
|
||||
1px -1px 0 rgba(255,255,255,0.9),
|
||||
-1px 1px 0 rgba(255,255,255,0.9);
|
||||
}
|
||||
/* 进度条按比例宽度:0-100(45.45%), 100-130(13.64%), 130-160(13.64%), 160-190(13.64%), 190-220(13.64%) */
|
||||
.tier-segment-0 { flex: 100; }
|
||||
.tier-segment-1 { flex: 30; }
|
||||
.tier-segment-2 { flex: 30; }
|
||||
.tier-segment-3 { flex: 30; }
|
||||
.tier-segment-4 { flex: 30; }
|
||||
/* 下降趋势样式 */
|
||||
.trend-down {
|
||||
color: rgba(255,255,255,0.5);
|
||||
font-size: 12px;
|
||||
}
|
||||
/* 业绩卡片文字样式 */
|
||||
.stat-value {
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
||||
}
|
||||
.stat-highlight {
|
||||
color: #6ee7b7;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
}
|
||||
.stat-label {
|
||||
color: rgba(255,255,255,0.9);
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.15);
|
||||
}
|
||||
.stat-secondary {
|
||||
color: rgba(255,255,255,0.7);
|
||||
}
|
||||
.stat-accent {
|
||||
color: #fcd34d;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
}
|
||||
/* 奖金金额突出样式 */
|
||||
.bonus-amount {
|
||||
text-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.35),
|
||||
0 0 12px rgba(251, 191, 36, 0.5);
|
||||
}
|
||||
/* === 任务分区 === */
|
||||
.section-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* 置顶卡片微亮边框 */
|
||||
.task-card.pinned {
|
||||
box-shadow: 0 1px 4px rgba(245, 158, 11, 0.12), 0 0 0 1px rgba(245, 158, 11, 0.08);
|
||||
}
|
||||
/* 放弃任务 */
|
||||
.task-card.abandoned {
|
||||
border-left-color: #d1d5db !important;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.task-card.abandoned .task-name {
|
||||
color: #9ca3af;
|
||||
}
|
||||
.task-card.abandoned .task-desc {
|
||||
color: #c5c5c5;
|
||||
}
|
||||
.task-card.abandoned .task-tag-wrap > span:first-child {
|
||||
background: #d1d5db !important;
|
||||
}
|
||||
/* 备注指示器 */
|
||||
.note-indicator {
|
||||
font-size: 12px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
/* === 长按上下文菜单 === */
|
||||
.context-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.context-overlay.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
|
||||
min-width: 192px;
|
||||
padding: 6px 0;
|
||||
opacity: 0;
|
||||
transform: scale(0.88);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
transform-origin: top left;
|
||||
}
|
||||
.context-menu.active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: all;
|
||||
}
|
||||
.ctx-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 13px 18px;
|
||||
font-size: 14px;
|
||||
color: #393939;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s;
|
||||
user-select: none;
|
||||
}
|
||||
.ctx-item:active {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
.ctx-item + .ctx-item {
|
||||
border-top: 1px solid #f3f3f3;
|
||||
}
|
||||
/* === 备注 / 放弃弹窗 === */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-top: 18vh;
|
||||
padding-bottom: 40vh;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.modal-overlay.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
.modal-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
width: 88%;
|
||||
max-width: 360px;
|
||||
padding: 24px 20px 20px;
|
||||
transform: translateY(24px);
|
||||
transition: transform 0.25s ease;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.modal-overlay.active .modal-card {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.modal-card textarea {
|
||||
width: 100%;
|
||||
border: 1.5px solid #e7e7e7;
|
||||
border-radius: 10px;
|
||||
padding: 12px 14px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
color: #2c2c2c;
|
||||
transition: border-color 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.modal-card textarea:focus {
|
||||
border-color: #0052d9;
|
||||
}
|
||||
.modal-card textarea.error-border {
|
||||
border-color: #e34d59;
|
||||
}
|
||||
.modal-error {
|
||||
color: #e34d59;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
display: none;
|
||||
}
|
||||
.modal-error.show {
|
||||
display: block;
|
||||
}
|
||||
.modal-submit-btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 14px;
|
||||
transition: background 0.2s, opacity 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.modal-submit-btn.primary {
|
||||
background: #0052d9;
|
||||
color: #fff;
|
||||
}
|
||||
.modal-submit-btn.primary:active {
|
||||
background: #003eb3;
|
||||
}
|
||||
.modal-submit-btn.danger {
|
||||
background: #e34d59;
|
||||
color: #fff;
|
||||
}
|
||||
.modal-submit-btn.danger:active {
|
||||
background: #c9363f;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-1 min-h-screen">
|
||||
<!-- 顶部区域 - 用户信息和业绩卡片 -->
|
||||
<div class="banner-bg theme-blue texture-aurora text-white pb-4">
|
||||
<!-- 用户信息 -->
|
||||
<div class="px-5 pt-10 pb-3">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg overflow-hidden">
|
||||
<img src="../img/zjtx.png" class="w-full h-full object-cover" alt="助教头像">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xl font-semibold">小燕</span>
|
||||
<span class="px-2 py-0.5 bg-white/20 rounded-full text-xs">助教</span>
|
||||
</div>
|
||||
<p class="text-white/70 text-sm">广州朗朗桌球</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 业绩进度卡片 -->
|
||||
<div class="mx-4">
|
||||
<div class="bg-white/15 backdrop-blur-md rounded-2xl px-4 py-3 border border-white/20">
|
||||
<!-- 第一层:标题行 -->
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="stat-label text-sm font-medium">距离100小时仅剩</span>
|
||||
<span class="stat-accent text-xl font-bold">12.5小时</span>
|
||||
</div>
|
||||
<a href="performance.html" class="stat-secondary text-xs flex items-center gap-1 hover:text-white transition-colors">
|
||||
查看详情
|
||||
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 第二层:5段档位进度条 + 边界小时数(按比例宽度) -->
|
||||
<div class="relative mb-6">
|
||||
<div class="tier-progress">
|
||||
<div class="tier-segment tier-segment-0 completed" title="0档 <100h"></div>
|
||||
<div class="tier-segment tier-segment-1 current" title="1档 100-130h">
|
||||
<div class="tier-fill" style="width: 58%"></div>
|
||||
</div>
|
||||
<div class="tier-segment tier-segment-2" title="2档 130-160h"></div>
|
||||
<div class="tier-segment tier-segment-3" title="3档 160-190h"></div>
|
||||
<div class="tier-segment tier-segment-4" title="4档 190-220h"></div>
|
||||
</div>
|
||||
<!-- 档位边界小时数(按比例定位:0, 100/220≈45.45%, 130/220≈59.09%, 160/220≈72.73%, 190/220≈86.36%, 220/220=100%) -->
|
||||
<div class="absolute w-full top-full mt-1.5 flex text-[9px]" style="left: 0;">
|
||||
<span class="text-white/60" style="position:absolute; left:0; transform:translateX(0);">0</span>
|
||||
<span class="text-white/60" style="position:absolute; left:45.45%; transform:translateX(-50%);">100</span>
|
||||
<span class="text-white/80 font-medium" style="position:absolute; left:59.09%; transform:translateX(-50%);">130</span>
|
||||
<span class="text-white/60" style="position:absolute; left:72.73%; transform:translateX(-50%);">160</span>
|
||||
<span class="text-white/60" style="position:absolute; left:86.36%; transform:translateX(-50%);">190</span>
|
||||
<span class="text-white/60" style="position:absolute; right:0; transform:translateX(0);">220</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第三层:核心数据 - 两列布局(左宽右窄) -->
|
||||
<div class="flex items-stretch mb-2.5">
|
||||
<!-- 左侧:课时数据 + 红戳(占60%) -->
|
||||
<div class="pr-4 border-r border-white/25 flex justify-center items-center" style="flex: 3;">
|
||||
<!-- 自动宽度容器:包含课时数据和红戳,居中显示 -->
|
||||
<div class="red-stamp inline-block relative" style="padding-right: 35px;">
|
||||
<div class="text-center">
|
||||
<div class="flex items-baseline justify-center gap-1.5">
|
||||
<span class="stat-highlight text-xl font-bold">77.5</span>
|
||||
<span class="stat-secondary text-sm">|</span>
|
||||
<span class="stat-accent text-xl font-bold">12</span>
|
||||
<span class="stat-secondary text-sm">|</span>
|
||||
<span class="stat-value text-xl font-bold">87.5</span>
|
||||
</div>
|
||||
<p class="stat-label text-xs mt-1.5">基础课 | 激励课 | 全部</p>
|
||||
</div>
|
||||
<!-- 红戳徽章 -->
|
||||
<div class="stamp-badge" style="right: -5px; top: 5px;">
|
||||
<span class="thumb">👍</span>
|
||||
<span class="stamp-text">已完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:奖金激励(占40%) -->
|
||||
<div class="pl-3 text-center flex flex-col justify-center" style="flex: 2;">
|
||||
<div class="bonus-amount">
|
||||
<span class="text-3xl font-bold text-amber-300">800</span>
|
||||
<span class="text-base text-amber-300/80">元</span>
|
||||
</div>
|
||||
<p class="stat-label text-xs mt-1.5">达100h即得</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第四层:预计收入 -->
|
||||
<div class="flex items-center justify-between pt-2 border-t border-white/25">
|
||||
<span class="stat-label text-xs">2月预计收入 | 比1月同期</span>
|
||||
<a href="performance.html" class="flex items-center gap-1.5 group">
|
||||
<span class="stat-value text-lg font-bold">¥6,206</span>
|
||||
<span class="trend-down">↓368</span>
|
||||
<svg class="w-3.5 h-3.5 stat-secondary group-hover:text-white transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待办任务列表 -->
|
||||
<div class="px-4 py-5">
|
||||
<!-- 标题 -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-base font-semibold text-gray-13">今日 客户维护</h2>
|
||||
<span class="text-sm text-gray-6">共 7 项</span>
|
||||
</div>
|
||||
|
||||
<!-- 📌 置顶区域 -->
|
||||
<div class="mb-5">
|
||||
<div class="flex items-center gap-1.5 mb-2.5">
|
||||
<span class="section-label text-amber-700 bg-amber-50">📌 置顶</span>
|
||||
<span class="text-sm text-gray-6">2项</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<!-- 置顶: 王先生 -->
|
||||
<div class="task-card high-priority pinned block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="1" data-task-name="王先生" data-task-type="high-priority">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-high-priority px-2 py-0.5 text-white text-xs font-medium">高优先召回</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">王先生</span>
|
||||
<span class="text-sm">💖</span>
|
||||
<span class="note-indicator" title="有备注">📝</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:15天前 · 余额:非常多</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">高流失风险,建议尽快联系</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 置顶: 李女士 -->
|
||||
<div class="task-card high-priority pinned block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="2" data-task-name="李女士" data-task-type="high-priority">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-high-priority px-2 py-0.5 text-white text-xs font-medium">高优先召回</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">李女士</span>
|
||||
<span class="text-sm">🧡</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:20天前 · 余额:非常多</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">VIP客户,储值余额较多</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 一般任务区域 -->
|
||||
<div class="mb-5">
|
||||
<div class="flex items-center gap-1.5 mb-2.5">
|
||||
<span class="section-label text-gray-9 bg-gray-2">一般任务</span>
|
||||
<span class="text-sm text-gray-6">3项</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<!-- 张先生 -->
|
||||
<div class="task-card priority block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="3" data-task-name="张先生" data-task-type="priority">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-priority px-2 py-0.5 text-white text-xs font-medium">优先召回</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">张先生</span>
|
||||
<span class="text-sm">💛</span>
|
||||
<span class="note-indicator" title="有备注">📝</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:10天前 · 余额:一般</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">消费频率下降,需关注</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 刘先生 -->
|
||||
<div class="task-card priority block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="4" data-task-name="刘先生" data-task-type="priority">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-priority px-2 py-0.5 text-white text-xs font-medium">优先召回</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">刘先生</span>
|
||||
<span class="text-sm">💙</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:8天前 · 余额:一般</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">偏好晚间时段,可推荐夜场套餐</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 陈先生 -->
|
||||
<div class="task-card relationship block bg-white rounded-xl p-4 shadow-sm cursor-pointer" data-task-id="5" data-task-name="陈先生" data-task-type="relationship">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-relationship px-2 py-0.5 text-white text-xs font-medium">关系构建</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">陈先生</span>
|
||||
<span class="text-sm">💙</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:5天前 · 余额:无</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">潜力客户,建议加强互动</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-5 flex-shrink-0 ml-2 mt-1" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ❌ 放弃的任务区域 -->
|
||||
<div class="mb-2">
|
||||
<div class="flex items-center gap-1.5 mb-2.5">
|
||||
<span class="section-label text-gray-6 bg-gray-2">已放弃</span>
|
||||
<span class="text-sm text-gray-6">2项</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<!-- 放弃: 赵女士 -->
|
||||
<div class="task-card callback abandoned block bg-white rounded-xl p-4 shadow-sm" data-task-id="6" data-task-name="赵女士" data-task-type="callback">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-callback px-2 py-0.5 text-white text-xs font-medium">客户回访</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">赵女士</span>
|
||||
<span class="text-sm">🧡</span>
|
||||
<span class="note-indicator" title="有备注">📝</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:3天前 · 余额:非常多</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">放弃原因:客户已转会至其他球房</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 放弃: 周先生 -->
|
||||
<div class="task-card callback abandoned block bg-white rounded-xl p-4 shadow-sm" data-task-id="7" data-task-name="周先生" data-task-type="callback">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1.5 task-tag-wrap">
|
||||
<span class="tag-callback px-2 py-0.5 text-white text-xs font-medium">客户回访</span>
|
||||
<span class="text-base font-semibold text-gray-13 task-name">周先生</span>
|
||||
<span class="text-sm">💛</span>
|
||||
<span class="note-indicator" title="有备注">📝</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-7 leading-relaxed task-desc">最近到店:5天前 · 余额:一般</p>
|
||||
<p class="text-sm text-gray-6 leading-relaxed task-desc">放弃原因:联系方式失效,无法触达</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 长按上下文菜单 -->
|
||||
<div class="context-overlay" id="contextOverlay"></div>
|
||||
<div class="context-menu" id="contextMenu">
|
||||
<div class="ctx-item" data-action="pin">
|
||||
<span>📌</span><span>置顶任务</span>
|
||||
</div>
|
||||
<div class="ctx-item" data-action="abandon">
|
||||
<span>❌</span><span>放弃任务</span>
|
||||
</div>
|
||||
<div class="ctx-item" data-action="ai">
|
||||
<span>🤖</span><span>问问AI助手</span>
|
||||
</div>
|
||||
<div class="ctx-item" data-action="remark">
|
||||
<span>📝</span><span>备注</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 备注 / 放弃 弹窗(可复用组件) -->
|
||||
<div class="modal-overlay" id="remarkModal">
|
||||
<div class="modal-card">
|
||||
<h3 class="text-base font-semibold text-gray-13 mb-1" id="modalTitle">添加备注</h3>
|
||||
<p class="text-xs text-gray-6 mb-3" id="modalSubtitle">为该客户的维护任务添加备注</p>
|
||||
<textarea id="remarkInput" rows="4" placeholder="请输入备注内容..."></textarea>
|
||||
<div class="modal-error" id="remarkError">请填写放弃原因,不能为空</div>
|
||||
<button class="modal-submit-btn primary" id="modalSubmitBtn">保存备注</button>
|
||||
<button class="mt-2 w-full text-center text-sm text-gray-6 py-2 bg-transparent border-0 cursor-pointer" id="modalCancelBtn">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 悬浮助手按钮 -->
|
||||
<script src="../js/ai-float-btn.js"></script>
|
||||
|
||||
<!-- 通用底部导航 -->
|
||||
<script src="../js/bottom-nav.js"></script>
|
||||
|
||||
<!-- 盖戳动画 -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 延迟一点触发盖戳动画,让页面先加载完
|
||||
setTimeout(function() {
|
||||
const stamp = document.querySelector('.stamp-badge');
|
||||
if (stamp) {
|
||||
stamp.classList.add('stamp-animate');
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 长按菜单 + 备注弹窗 交互 -->
|
||||
<script>
|
||||
(function() {
|
||||
/* ========== 导航映射 ========== */
|
||||
var NAV = {
|
||||
'high-priority': 'task-detail.html',
|
||||
'priority': 'task-detail-priority.html',
|
||||
'relationship': 'task-detail-relationship.html',
|
||||
'callback': 'task-detail-callback.html'
|
||||
};
|
||||
|
||||
/* ========== 长按检测 ========== */
|
||||
var pressTimer = null;
|
||||
var isLongPress = false;
|
||||
var isMoved = false;
|
||||
var startX = 0, startY = 0;
|
||||
var currentCard = null;
|
||||
var currentName = '';
|
||||
var LONG_PRESS_MS = 500;
|
||||
var MOVE_THRESHOLD = 8; // 超过8px判定为滑动,不触发点击
|
||||
|
||||
var overlay = document.getElementById('contextOverlay');
|
||||
var menu = document.getElementById('contextMenu');
|
||||
|
||||
document.querySelectorAll('.task-card').forEach(function(card) {
|
||||
/* --- touch --- */
|
||||
card.addEventListener('touchstart', function(e) {
|
||||
isLongPress = false;
|
||||
isMoved = false;
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
currentCard = this;
|
||||
var tx = startX, ty = startY, self = this;
|
||||
pressTimer = setTimeout(function() {
|
||||
isLongPress = true;
|
||||
showContextMenu(tx, ty, self);
|
||||
}, LONG_PRESS_MS);
|
||||
}, { passive: true });
|
||||
|
||||
card.addEventListener('touchmove', function(e) {
|
||||
var dx = Math.abs(e.touches[0].clientX - startX);
|
||||
var dy = Math.abs(e.touches[0].clientY - startY);
|
||||
if (dx > MOVE_THRESHOLD || dy > MOVE_THRESHOLD) {
|
||||
isMoved = true;
|
||||
clearTimeout(pressTimer);
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
card.addEventListener('touchend', function(e) {
|
||||
clearTimeout(pressTimer);
|
||||
if (!isLongPress && !isMoved && !this.classList.contains('abandoned')) {
|
||||
navigateCard(this);
|
||||
}
|
||||
});
|
||||
|
||||
card.addEventListener('touchcancel', function() {
|
||||
clearTimeout(pressTimer);
|
||||
isMoved = true;
|
||||
});
|
||||
|
||||
/* --- mouse (desktop preview) --- */
|
||||
card.addEventListener('mousedown', function(e) {
|
||||
if (e.button !== 0) return;
|
||||
isLongPress = false;
|
||||
isMoved = false;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
currentCard = this;
|
||||
var mx = e.clientX, my = e.clientY, self = this;
|
||||
pressTimer = setTimeout(function() {
|
||||
isLongPress = true;
|
||||
showContextMenu(mx, my, self);
|
||||
}, LONG_PRESS_MS);
|
||||
});
|
||||
|
||||
card.addEventListener('mousemove', function(e) {
|
||||
if (pressTimer && !isLongPress) {
|
||||
var dx = Math.abs(e.clientX - startX);
|
||||
var dy = Math.abs(e.clientY - startY);
|
||||
if (dx > MOVE_THRESHOLD || dy > MOVE_THRESHOLD) {
|
||||
isMoved = true;
|
||||
clearTimeout(pressTimer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
card.addEventListener('mouseup', function() {
|
||||
clearTimeout(pressTimer);
|
||||
if (!isLongPress && !isMoved && !this.classList.contains('abandoned')) {
|
||||
navigateCard(this);
|
||||
}
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
clearTimeout(pressTimer);
|
||||
});
|
||||
|
||||
/* 禁用原生右键菜单 */
|
||||
card.addEventListener('contextmenu', function(e) { e.preventDefault(); });
|
||||
});
|
||||
|
||||
function navigateCard(card) {
|
||||
var type = card.dataset.taskType;
|
||||
if (NAV[type]) window.location.href = NAV[type];
|
||||
}
|
||||
|
||||
/* ========== 上下文菜单 ========== */
|
||||
function showContextMenu(x, y, card) {
|
||||
currentCard = card;
|
||||
currentName = card.dataset.taskName || '';
|
||||
|
||||
var mw = 192, mh = menu.offsetHeight || 220;
|
||||
var left = x - mw / 2;
|
||||
var top = y - mh - 12;
|
||||
|
||||
if (left < 10) left = 10;
|
||||
if (left + mw > window.innerWidth - 10) left = window.innerWidth - mw - 10;
|
||||
if (top < 10) top = y + 20;
|
||||
|
||||
menu.style.left = left + 'px';
|
||||
menu.style.top = top + 'px';
|
||||
|
||||
overlay.classList.add('active');
|
||||
menu.classList.add('active');
|
||||
|
||||
if (navigator.vibrate) navigator.vibrate(30);
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
overlay.classList.remove('active');
|
||||
menu.classList.remove('active');
|
||||
}
|
||||
|
||||
overlay.addEventListener('click', closeContextMenu);
|
||||
|
||||
/* 菜单项点击 */
|
||||
menu.querySelectorAll('.ctx-item').forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
var action = this.dataset.action;
|
||||
closeContextMenu();
|
||||
switch (action) {
|
||||
case 'pin':
|
||||
showToast('已置顶「' + currentName + '」的任务');
|
||||
break;
|
||||
case 'abandon':
|
||||
modalMode = 'abandon';
|
||||
openRemarkModal();
|
||||
break;
|
||||
case 'ai':
|
||||
showToast('正在唤起AI助手…');
|
||||
break;
|
||||
case 'remark':
|
||||
modalMode = 'remark';
|
||||
openRemarkModal();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ========== 备注 / 放弃 弹窗 ========== */
|
||||
var modalMode = 'remark';
|
||||
var modalOverlay = document.getElementById('remarkModal');
|
||||
var modalTitle = document.getElementById('modalTitle');
|
||||
var modalSubtitle = document.getElementById('modalSubtitle');
|
||||
var modalInput = document.getElementById('remarkInput');
|
||||
var modalError = document.getElementById('remarkError');
|
||||
var modalBtn = document.getElementById('modalSubmitBtn');
|
||||
var modalCancel = document.getElementById('modalCancelBtn');
|
||||
|
||||
function openRemarkModal() {
|
||||
modalInput.value = '';
|
||||
modalError.classList.remove('show');
|
||||
modalInput.classList.remove('error-border');
|
||||
|
||||
if (modalMode === 'abandon') {
|
||||
modalTitle.textContent = '放弃任务 — ' + currentName;
|
||||
modalSubtitle.textContent = '请填写放弃该客户维护任务的原因(必填)';
|
||||
modalBtn.textContent = '确认原因 放弃该客户的维护';
|
||||
modalBtn.className = 'modal-submit-btn danger';
|
||||
modalInput.placeholder = '请输入放弃原因(必填)…';
|
||||
} else {
|
||||
modalTitle.textContent = '添加备注 — ' + currentName;
|
||||
modalSubtitle.textContent = '为该客户的维护任务添加备注';
|
||||
modalBtn.textContent = '保存备注';
|
||||
modalBtn.className = 'modal-submit-btn primary';
|
||||
modalInput.placeholder = '请输入备注内容…';
|
||||
}
|
||||
|
||||
modalOverlay.classList.add('active');
|
||||
setTimeout(function() { modalInput.focus(); }, 300);
|
||||
}
|
||||
|
||||
function closeRemarkModal() {
|
||||
modalOverlay.classList.remove('active');
|
||||
modalInput.blur();
|
||||
}
|
||||
|
||||
function submitRemark() {
|
||||
var value = modalInput.value.trim();
|
||||
|
||||
if (modalMode === 'abandon' && !value) {
|
||||
modalError.classList.add('show');
|
||||
modalInput.classList.add('error-border');
|
||||
modalInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
closeRemarkModal();
|
||||
|
||||
if (modalMode === 'abandon') {
|
||||
showToast('已放弃「' + currentName + '」的维护任务');
|
||||
} else {
|
||||
showToast('备注已保存');
|
||||
}
|
||||
}
|
||||
|
||||
modalBtn.addEventListener('click', submitRemark);
|
||||
modalCancel.addEventListener('click', closeRemarkModal);
|
||||
|
||||
/* 点击遮罩关闭 */
|
||||
modalOverlay.addEventListener('click', function(e) {
|
||||
if (e.target === modalOverlay) closeRemarkModal();
|
||||
});
|
||||
|
||||
/* 输入时清除错误提示 */
|
||||
modalInput.addEventListener('input', function() {
|
||||
if (this.value.trim()) {
|
||||
modalError.classList.remove('show');
|
||||
this.classList.remove('error-border');
|
||||
}
|
||||
});
|
||||
|
||||
/* ========== 轻提示 Toast ========== */
|
||||
function showToast(msg) {
|
||||
var t = document.getElementById('_toast');
|
||||
if (!t) {
|
||||
t = document.createElement('div');
|
||||
t.id = '_toast';
|
||||
t.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(0.9);' +
|
||||
'background:rgba(0,0,0,0.75);color:#fff;padding:12px 24px;border-radius:10px;' +
|
||||
'font-size:14px;z-index:9999;opacity:0;transition:opacity .2s,transform .2s;' +
|
||||
'pointer-events:none;text-align:center;max-width:80%;';
|
||||
document.body.appendChild(t);
|
||||
}
|
||||
t.textContent = msg;
|
||||
t.style.opacity = '1';
|
||||
t.style.transform = 'translate(-50%,-50%) scale(1)';
|
||||
setTimeout(function() {
|
||||
t.style.opacity = '0';
|
||||
t.style.transform = 'translate(-50%,-50%) scale(0.9)';
|
||||
}, 1800);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user