<!doctype html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="utf-8" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<title>销售客户管理系统体验版</title>
|
<style>
|
:root {
|
--bg: #f6f7f9;
|
--panel: #ffffff;
|
--line: #dfe4ea;
|
--text: #18202b;
|
--muted: #647084;
|
--blue: #2563eb;
|
--green: #16845b;
|
--amber: #b7791f;
|
--red: #c24136;
|
--indigo: #4f46e5;
|
--shadow: 0 8px 24px rgba(15, 23, 42, .08);
|
}
|
|
* { box-sizing: border-box; }
|
body {
|
margin: 0;
|
background: var(--bg);
|
color: var(--text);
|
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
|
font-size: 14px;
|
}
|
.app {
|
min-height: 100vh;
|
display: grid;
|
grid-template-columns: 232px 1fr;
|
}
|
aside {
|
background: #111827;
|
color: #e5e7eb;
|
padding: 18px 14px;
|
position: sticky;
|
top: 0;
|
height: 100vh;
|
}
|
.brand {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
padding: 6px 8px 18px;
|
border-bottom: 1px solid rgba(255,255,255,.12);
|
margin-bottom: 14px;
|
}
|
.logo {
|
width: 34px;
|
height: 34px;
|
border-radius: 8px;
|
background: #22c55e;
|
display: grid;
|
place-items: center;
|
color: #07110c;
|
font-weight: 800;
|
}
|
.brand strong { display: block; font-size: 15px; }
|
.brand span { color: #9ca3af; font-size: 12px; }
|
.nav button {
|
width: 100%;
|
border: 0;
|
background: transparent;
|
color: #cbd5e1;
|
text-align: left;
|
padding: 11px 10px;
|
border-radius: 8px;
|
cursor: pointer;
|
display: flex;
|
gap: 10px;
|
align-items: center;
|
margin: 2px 0;
|
font: inherit;
|
}
|
.nav button.active, .nav button:hover { background: rgba(255,255,255,.1); color: #fff; }
|
.icon {
|
width: 18px;
|
height: 18px;
|
flex: 0 0 18px;
|
display: inline-grid;
|
place-items: center;
|
}
|
main { min-width: 0; }
|
header {
|
height: 66px;
|
background: var(--panel);
|
border-bottom: 1px solid var(--line);
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 0 24px;
|
position: sticky;
|
top: 0;
|
z-index: 2;
|
}
|
h1 {
|
font-size: 20px;
|
margin: 0 0 4px;
|
letter-spacing: 0;
|
}
|
.sub { color: var(--muted); font-size: 12px; }
|
.role-switch {
|
display: flex;
|
background: #eef2f7;
|
border: 1px solid var(--line);
|
border-radius: 8px;
|
padding: 3px;
|
gap: 3px;
|
}
|
.role-switch button {
|
border: 0;
|
border-radius: 6px;
|
padding: 8px 13px;
|
cursor: pointer;
|
background: transparent;
|
color: var(--muted);
|
font: inherit;
|
}
|
.role-switch button.active {
|
background: var(--panel);
|
color: var(--text);
|
box-shadow: 0 1px 4px rgba(15, 23, 42, .12);
|
}
|
.content { padding: 22px 24px 34px; }
|
.stats {
|
display: grid;
|
grid-template-columns: repeat(4, minmax(150px, 1fr));
|
gap: 14px;
|
margin-bottom: 18px;
|
}
|
.stat, .panel {
|
background: var(--panel);
|
border: 1px solid var(--line);
|
border-radius: 8px;
|
box-shadow: var(--shadow);
|
}
|
.stat { padding: 16px; min-height: 92px; }
|
.stat .label { color: var(--muted); font-size: 12px; }
|
.stat .value { font-size: 28px; font-weight: 800; margin-top: 8px; }
|
.stat .hint { color: var(--muted); font-size: 12px; margin-top: 5px; }
|
.grid {
|
display: grid;
|
grid-template-columns: 1.25fr .75fr;
|
gap: 16px;
|
}
|
.panel { overflow: hidden; }
|
.panel-head {
|
padding: 14px 16px;
|
border-bottom: 1px solid var(--line);
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 10px;
|
}
|
.panel-head strong { font-size: 15px; }
|
.toolbar {
|
display: flex;
|
gap: 8px;
|
align-items: center;
|
flex-wrap: wrap;
|
}
|
input, select, textarea {
|
border: 1px solid var(--line);
|
border-radius: 8px;
|
padding: 9px 10px;
|
font: inherit;
|
background: #fff;
|
min-width: 0;
|
}
|
textarea { width: 100%; min-height: 88px; resize: vertical; }
|
.btn {
|
border: 1px solid var(--line);
|
background: #fff;
|
color: var(--text);
|
border-radius: 8px;
|
padding: 9px 12px;
|
cursor: pointer;
|
font: inherit;
|
display: inline-flex;
|
align-items: center;
|
gap: 7px;
|
white-space: nowrap;
|
}
|
.btn.primary { background: var(--blue); color: #fff; border-color: var(--blue); }
|
.btn.green { background: var(--green); color: #fff; border-color: var(--green); }
|
.btn.danger { background: var(--red); color: #fff; border-color: var(--red); }
|
.btn:disabled { opacity: .55; cursor: not-allowed; }
|
table {
|
width: 100%;
|
border-collapse: collapse;
|
}
|
th, td {
|
text-align: left;
|
padding: 12px 14px;
|
border-bottom: 1px solid var(--line);
|
vertical-align: middle;
|
}
|
th {
|
color: var(--muted);
|
font-size: 12px;
|
background: #fafbfc;
|
font-weight: 600;
|
}
|
tr:hover td { background: #f8fafc; }
|
.tag {
|
display: inline-flex;
|
align-items: center;
|
border-radius: 999px;
|
padding: 4px 8px;
|
font-size: 12px;
|
line-height: 1;
|
border: 1px solid transparent;
|
}
|
.tag.blue { color: #1d4ed8; background: #eaf1ff; border-color: #cfe0ff; }
|
.tag.green { color: #166534; background: #eaf8f0; border-color: #ccebd8; }
|
.tag.amber { color: #92400e; background: #fff7e6; border-color: #fde4ad; }
|
.tag.red { color: #991b1b; background: #feecec; border-color: #fecaca; }
|
.tag.gray { color: #475569; background: #eef2f7; border-color: #dce3ed; }
|
.list { padding: 8px 0; }
|
.item {
|
padding: 12px 16px;
|
border-bottom: 1px solid var(--line);
|
display: flex;
|
justify-content: space-between;
|
gap: 14px;
|
}
|
.item:last-child { border-bottom: 0; }
|
.item strong { display: block; margin-bottom: 5px; }
|
.muted { color: var(--muted); }
|
.view { display: none; }
|
.view.active { display: block; }
|
.drawer {
|
position: fixed;
|
inset: 0;
|
display: none;
|
z-index: 5;
|
}
|
.drawer.active { display: block; }
|
.scrim {
|
position: absolute;
|
inset: 0;
|
background: rgba(15, 23, 42, .38);
|
}
|
.drawer-card {
|
position: absolute;
|
right: 0;
|
top: 0;
|
width: min(560px, 100%);
|
height: 100%;
|
background: var(--panel);
|
box-shadow: -16px 0 32px rgba(15,23,42,.18);
|
display: flex;
|
flex-direction: column;
|
}
|
.drawer-head {
|
padding: 18px;
|
border-bottom: 1px solid var(--line);
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
gap: 12px;
|
}
|
.drawer-body { padding: 18px; overflow: auto; }
|
.form-grid {
|
display: grid;
|
grid-template-columns: 1fr 1fr;
|
gap: 12px;
|
margin-bottom: 12px;
|
}
|
.field label {
|
display: block;
|
color: var(--muted);
|
font-size: 12px;
|
margin-bottom: 6px;
|
}
|
.field input, .field select { width: 100%; }
|
.timeline { margin-top: 16px; border-top: 1px solid var(--line); }
|
.tl {
|
padding: 13px 0;
|
border-bottom: 1px solid var(--line);
|
}
|
.tl strong { display: block; margin-bottom: 5px; }
|
.notice {
|
background: #fff7e6;
|
border: 1px solid #fde4ad;
|
color: #8a5300;
|
padding: 10px 12px;
|
border-radius: 8px;
|
margin-bottom: 14px;
|
}
|
.toast {
|
position: fixed;
|
left: 50%;
|
bottom: 26px;
|
transform: translateX(-50%);
|
background: #111827;
|
color: #fff;
|
padding: 10px 14px;
|
border-radius: 8px;
|
box-shadow: var(--shadow);
|
display: none;
|
z-index: 8;
|
}
|
.toast.show { display: block; }
|
|
@media (max-width: 980px) {
|
.app { grid-template-columns: 1fr; }
|
aside { position: static; height: auto; }
|
.stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.grid { grid-template-columns: 1fr; }
|
header { height: auto; padding: 14px 16px; gap: 12px; align-items: flex-start; flex-direction: column; }
|
.content { padding: 16px; }
|
}
|
@media (max-width: 620px) {
|
.stats { grid-template-columns: 1fr; }
|
.form-grid { grid-template-columns: 1fr; }
|
table { min-width: 760px; }
|
.panel { overflow-x: auto; }
|
}
|
</style>
|
</head>
|
<body>
|
<div class="app">
|
<aside>
|
<div class="brand">
|
<div class="logo">CRM</div>
|
<div>
|
<strong>客户管理体验版</strong>
|
<span>规则提醒 + 公海流转</span>
|
</div>
|
</div>
|
<nav class="nav">
|
<button class="active" data-view="dashboard"><span class="icon">⌂</span>工作台</button>
|
<button data-view="customers"><span class="icon">▦</span>客户列表</button>
|
<button data-view="deals"><span class="icon">¥</span>成交客户</button>
|
<button data-view="reminders"><span class="icon">!</span>今日提醒</button>
|
<button data-view="pool"><span class="icon">↻</span>公共客户池</button>
|
<button data-view="projects"><span class="icon">✓</span>项目审批</button>
|
</nav>
|
</aside>
|
|
<main>
|
<header>
|
<div>
|
<h1 id="pageTitle">销售工作台</h1>
|
<div class="sub" id="pageSub">当前账号:销售一。可切换主管视角体验权限差异。</div>
|
</div>
|
<div class="role-switch" aria-label="角色切换">
|
<button class="active" data-role="sales">员工</button>
|
<button data-role="supervisor">主管</button>
|
</div>
|
</header>
|
|
<section class="content">
|
<div id="dashboard" class="view active">
|
<div class="stats" id="stats"></div>
|
<div class="grid">
|
<div class="panel">
|
<div class="panel-head">
|
<strong>待处理客户</strong>
|
<button class="btn primary" id="addCustomerBtn"><span>+</span>新增客户</button>
|
</div>
|
<table>
|
<thead>
|
<tr>
|
<th>客户</th><th>负责人</th><th>状态</th><th>提醒</th><th>操作</th>
|
</tr>
|
</thead>
|
<tbody id="priorityRows"></tbody>
|
</table>
|
</div>
|
<div class="panel">
|
<div class="panel-head">
|
<strong>系统自动提醒</strong>
|
<button class="btn" id="runRulesBtn"><span>⟳</span>模拟每日检查</button>
|
</div>
|
<div class="list" id="reminderList"></div>
|
</div>
|
</div>
|
</div>
|
|
<div id="customers" class="view">
|
<div class="panel">
|
<div class="panel-head">
|
<strong>客户列表</strong>
|
<div class="toolbar">
|
<input id="searchInput" placeholder="搜索客户、公司、电话" />
|
<select id="stageFilter">
|
<option value="">全部阶段</option>
|
<option>初询</option>
|
<option>报价</option>
|
<option>审批</option>
|
<option>成交</option>
|
<option>暂缓</option>
|
</select>
|
<button class="btn primary" id="addCustomerBtn2"><span>+</span>新增客户</button>
|
</div>
|
</div>
|
<table>
|
<thead>
|
<tr>
|
<th>客户</th><th>联系方式</th><th>来源</th><th>负责人</th><th>阶段</th><th>成交状态</th><th>保护</th><th>下次跟进</th><th>操作</th>
|
</tr>
|
</thead>
|
<tbody id="customerRows"></tbody>
|
</table>
|
</div>
|
</div>
|
|
<div id="reminders" class="view">
|
<div class="panel">
|
<div class="panel-head">
|
<strong>今日提醒</strong>
|
<button class="btn" id="finishAllBtn"><span>✓</span>标记已读</button>
|
</div>
|
<div class="list" id="allReminders"></div>
|
</div>
|
</div>
|
|
<div id="deals" class="view">
|
<div class="panel">
|
<div class="panel-head">
|
<strong>成交客户</strong>
|
<span class="muted">单独管理成交状态、回访和复购机会</span>
|
</div>
|
<table>
|
<thead>
|
<tr>
|
<th>客户</th><th>成交状态</th><th>成交信息</th><th>负责人</th><th>首次录入人</th><th>下次成交回访</th><th>操作</th>
|
</tr>
|
</thead>
|
<tbody id="dealRows"></tbody>
|
</table>
|
</div>
|
</div>
|
|
<div id="pool" class="view">
|
<div class="panel">
|
<div class="panel-head">
|
<strong>公共客户池</strong>
|
<span class="muted" id="poolHint"></span>
|
</div>
|
<table>
|
<thead>
|
<tr>
|
<th>客户</th><th>首次录入人</th><th>原负责人</th><th>进入原因</th><th>最近有效跟进</th><th>操作</th>
|
</tr>
|
</thead>
|
<tbody id="poolRows"></tbody>
|
</table>
|
</div>
|
</div>
|
|
<div id="projects" class="view">
|
<div class="panel">
|
<div class="panel-head">
|
<strong>项目备案审批</strong>
|
<span class="muted">员工可提交,主管可审批</span>
|
</div>
|
<table>
|
<thead>
|
<tr>
|
<th>项目</th><th>客户</th><th>申请人</th><th>阶段</th><th>到期</th><th>状态</th><th>操作</th>
|
</tr>
|
</thead>
|
<tbody id="projectRows"></tbody>
|
</table>
|
</div>
|
</div>
|
</section>
|
</main>
|
</div>
|
|
<div class="drawer" id="drawer">
|
<div class="scrim" id="closeDrawer"></div>
|
<div class="drawer-card">
|
<div class="drawer-head">
|
<div>
|
<h1 id="drawerTitle">客户详情</h1>
|
<div class="sub" id="drawerSub"></div>
|
</div>
|
<button class="btn" id="closeDrawerBtn">关闭</button>
|
</div>
|
<div class="drawer-body" id="drawerBody"></div>
|
</div>
|
</div>
|
|
<div class="toast" id="toast"></div>
|
|
<script>
|
const today = new Date("2026-07-06T09:00:00");
|
let role = "sales";
|
let activeView = "dashboard";
|
|
const customers = [
|
{
|
id: 1, name: "王经理", company: "杭州佳源食品厂", phone: "138****9021", wechat: "wangjy",
|
source: "百度", owner: "销售一", intention: "A", level: "A", stage: "报价",
|
firstReceiver: "销售一", firstInputAt: "2026-06-18 10:20", dealStatus: "未成交", funnel: "S5 已报价",
|
dealAt: "", dealAmount: "", dealProduct: "", nextDealRevisit: "",
|
protect: "即将到期", protectEnd: "2026-07-08", next: "2026-07-06", last: "2026-06-27",
|
pool: "正常", scene: "食品车间空间消毒", demand: "臭氧消毒设备", params: "面积 800㎡,层高 4m",
|
remark: "客户关注设备稳定性和售后响应。", reminder: "今日需跟进,保护期 2 天后到期"
|
},
|
{
|
id: 2, name: "李工", company: "苏州清源水处理", phone: "136****1188", wechat: "liwater",
|
source: "爱采购", owner: "销售一", intention: "B", level: "B", stage: "选型",
|
firstReceiver: "销售一", firstInputAt: "2026-06-24 15:35", dealStatus: "未成交", funnel: "S4 选型方案",
|
dealAt: "", dealAmount: "", dealProduct: "", nextDealRevisit: "",
|
protect: "保护中", protectEnd: "2026-07-18", next: "2026-07-01", last: "2026-06-25",
|
pool: "正常", scene: "水处理", demand: "杀菌设备", params: "水量 30T/h",
|
remark: "需要确认现场水质和安装空间。", reminder: "已超期 5 天未跟进"
|
},
|
{
|
id: 3, name: "赵总", company: "山东铭泰工程", phone: "139****6502", wechat: "zhaomt",
|
source: "老客户转介绍", owner: "销售二", intention: "A", level: "A", stage: "审批",
|
firstReceiver: "销售二", firstInputAt: "2026-06-12 09:10", dealStatus: "未成交", funnel: "S6 比价/审批",
|
dealAt: "", dealAmount: "", dealProduct: "", nextDealRevisit: "",
|
protect: "保护中", protectEnd: "2026-08-20", next: "2026-07-05", last: "2026-06-18",
|
pool: "正常", scene: "工程项目配套", demand: "大型空间消毒", params: "项目预算审批中",
|
remark: "项目涉及总包和终端单位,报价需主管统一审核。", reminder: "高意向客户 18 天无有效跟进"
|
},
|
{
|
id: 4, name: "陈女士", company: "宁波海润养殖", phone: "135****7786", wechat: "chenhr",
|
source: "抖音", owner: "销售三", intention: "C", level: "C", stage: "暂缓",
|
firstReceiver: "销售三", firstInputAt: "2026-03-28 14:02", dealStatus: "未成交", funnel: "S8 暂缓/失败",
|
dealAt: "", dealAmount: "", dealProduct: "", nextDealRevisit: "",
|
protect: "已过期", protectEnd: "2026-06-10", next: "2026-06-08", last: "2026-04-01",
|
pool: "公共客户池", scene: "养殖场除味", demand: "除味杀菌", params: "2 个养殖棚",
|
remark: "客户预算低,后续如有补贴政策可再激活。", reminder: "保护期已过且无有效推进"
|
},
|
{
|
id: 5, name: "周主任", company: "常州三院实验室", phone: "137****2190", wechat: "zhoulab",
|
source: "官网", owner: "销售一", intention: "B", level: "B", stage: "成交",
|
firstReceiver: "销售一", firstInputAt: "2026-04-16 11:48", dealStatus: "已交付", funnel: "S7 成交/执行",
|
dealAt: "2026-05-20", dealAmount: "¥86,000", dealProduct: "实验室消毒设备 X2", nextDealRevisit: "2026-07-20",
|
protect: "保护中", protectEnd: "2026-09-20", next: "2026-07-09", last: "2026-06-20",
|
pool: "预公海", scene: "实验室消毒", demand: "维护和耗材", params: "复购可能",
|
remark: "已成交客户,需要跟进耗材复购和设备维护。", reminder: "成交客户需要回访,已进入预公海提醒"
|
}
|
];
|
|
const projects = [
|
{ id: 1, name: "南通冷库消毒改造", customer: "杭州佳源食品厂", applicant: "销售一", stage: "报价", end: "2026-07-07", status: "待审批" },
|
{ id: 2, name: "政府水厂投标项目", customer: "苏州清源水处理", applicant: "销售一", stage: "投标", end: "2026-07-05", status: "待延期审批" },
|
{ id: 3, name: "山东工程配套项目", customer: "山东铭泰工程", applicant: "销售二", stage: "审批", end: "2026-08-20", status: "已通过" }
|
];
|
|
function visibleCustomers() {
|
return role === "supervisor" ? customers : customers.filter(c => c.owner === "销售一" || c.pool === "公共客户池");
|
}
|
|
function daysBetween(dateText) {
|
const d = new Date(dateText + "T09:00:00");
|
return Math.round((d - today) / 86400000);
|
}
|
|
function tag(text, type) {
|
return `<span class="tag ${type}">${text}</span>`;
|
}
|
|
function tagForProtect(text) {
|
if (text === "保护中") return tag(text, "green");
|
if (text === "即将到期") return tag(text, "amber");
|
if (text === "已过期") return tag(text, "red");
|
return tag(text, "gray");
|
}
|
|
function showToast(text) {
|
const el = document.getElementById("toast");
|
el.textContent = text;
|
el.classList.add("show");
|
setTimeout(() => el.classList.remove("show"), 1800);
|
}
|
|
function setView(view) {
|
activeView = view;
|
document.querySelectorAll(".view").forEach(v => v.classList.toggle("active", v.id === view));
|
document.querySelectorAll(".nav button").forEach(b => b.classList.toggle("active", b.dataset.view === view));
|
const titles = { dashboard: role === "supervisor" ? "主管工作台" : "销售工作台", customers: "客户列表", deals: "成交客户", reminders: "今日提醒", pool: "公共客户池", projects: "项目审批" };
|
document.getElementById("pageTitle").textContent = titles[view];
|
render();
|
}
|
|
function setRole(nextRole) {
|
role = nextRole;
|
document.querySelectorAll(".role-switch button").forEach(b => b.classList.toggle("active", b.dataset.role === role));
|
document.getElementById("pageSub").textContent = role === "supervisor"
|
? "当前账号:主管。可查看全部客户、审批、分配和释放公海。"
|
: "当前账号:销售一。只能处理自己负责的客户和待办。";
|
setView(activeView);
|
}
|
|
function buildReminders() {
|
const list = [];
|
visibleCustomers().forEach(c => {
|
if (c.next <= "2026-07-06") list.push({ customer: c.name, title: c.next === "2026-07-06" ? "今日待跟进" : "超期未跟进", text: c.reminder, level: c.next === "2026-07-06" ? "blue" : "red" });
|
if (daysBetween(c.protectEnd) <= 3 && daysBetween(c.protectEnd) >= 0) list.push({ customer: c.name, title: "保护期即将到期", text: `保护期剩余 ${daysBetween(c.protectEnd)} 天`, level: "amber" });
|
if (c.pool === "预公海") list.push({ customer: c.name, title: "预公海提醒", text: "需要补充有效回访,否则将进入公共客户池", level: "amber" });
|
});
|
if (role === "supervisor") {
|
list.push({ customer: "赵总", title: "高意向客户无有效跟进", text: "A 类客户 16 天无有效跟进,可重新分配", level: "red" });
|
projects.filter(p => p.status !== "已通过").forEach(p => list.push({ customer: p.customer, title: p.status, text: `${p.name} 到期日 ${p.end}`, level: "blue" }));
|
}
|
return list;
|
}
|
|
function renderStats() {
|
const data = visibleCustomers();
|
const reminders = buildReminders();
|
const stats = role === "supervisor"
|
? [
|
["全部客户", customers.length, "含各销售名下客户"],
|
["今日提醒", reminders.length, "自动规则生成"],
|
["公海客户", customers.filter(c => c.pool === "公共客户池").length, "等待主管分配"],
|
["待审批", projects.filter(p => p.status !== "已通过").length, "项目备案/延期"]
|
]
|
: [
|
["我的客户", data.filter(c => c.owner === "销售一").length, "当前负责客户"],
|
["今日待跟进", data.filter(c => c.next === "2026-07-06").length, "今天必须处理"],
|
["超期未跟进", data.filter(c => c.next < "2026-07-06").length, "需要尽快补跟"],
|
["预公海", data.filter(c => c.pool === "预公海").length, "存在释放风险"]
|
];
|
document.getElementById("stats").innerHTML = stats.map(s => `
|
<div class="stat"><div class="label">${s[0]}</div><div class="value">${s[1]}</div><div class="hint">${s[2]}</div></div>
|
`).join("");
|
}
|
|
function renderDashboard() {
|
const priority = visibleCustomers().filter(c => c.next <= "2026-07-05" || c.protect !== "保护中" || c.pool === "预公海").slice(0, 6);
|
document.getElementById("priorityRows").innerHTML = priority.map(c => `
|
<tr>
|
<td><strong>${c.name}</strong><div class="muted">${c.company}</div></td>
|
<td>${c.owner}</td>
|
<td>${tag(c.stage, c.stage === "成交" ? "green" : "blue")}</td>
|
<td>${c.reminder}</td>
|
<td><button class="btn" onclick="openCustomer(${c.id})">查看</button></td>
|
</tr>
|
`).join("");
|
renderReminderList("reminderList", buildReminders().slice(0, 5));
|
}
|
|
function renderReminderList(target, list) {
|
const el = document.getElementById(target);
|
el.innerHTML = list.length ? list.map(r => `
|
<div class="item">
|
<div><strong>${r.title}:${r.customer}</strong><div class="muted">${r.text}</div></div>
|
${tag("未处理", r.level)}
|
</div>
|
`).join("") : `<div class="item"><div><strong>暂无提醒</strong><div class="muted">今天没有必须处理的事项</div></div></div>`;
|
}
|
|
function renderCustomers() {
|
const keyword = document.getElementById("searchInput")?.value.trim() || "";
|
const stage = document.getElementById("stageFilter")?.value || "";
|
let data = visibleCustomers().filter(c => c.pool !== "公共客户池" || role === "supervisor");
|
if (keyword) data = data.filter(c => `${c.name}${c.company}${c.phone}${c.wechat}`.includes(keyword));
|
if (stage) data = data.filter(c => c.stage === stage);
|
document.getElementById("customerRows").innerHTML = data.map(c => `
|
<tr>
|
<td><strong>${c.name}</strong><div class="muted">${c.company}</div></td>
|
<td>${c.phone}<div class="muted">${c.wechat}</div></td>
|
<td>${c.source}</td>
|
<td>${c.owner}</td>
|
<td>${tag(c.funnel, c.intention === "A" ? "red" : c.intention === "B" ? "amber" : "gray")}</td>
|
<td>${tag(c.dealStatus, c.dealStatus === "未成交" ? "gray" : "green")}</td>
|
<td>${tagForProtect(c.protect)}<div class="muted">${c.protectEnd}</div></td>
|
<td>${c.next}</td>
|
<td><button class="btn" onclick="openCustomer(${c.id})">详情</button></td>
|
</tr>
|
`).join("");
|
}
|
|
function renderPool() {
|
const rows = customers.filter(c => c.pool === "公共客户池");
|
document.getElementById("poolHint").textContent = role === "supervisor" ? "主管可重新分配" : "员工不可私自领取";
|
document.getElementById("poolRows").innerHTML = rows.map(c => `
|
<tr>
|
<td><strong>${c.name}</strong><div class="muted">${c.company}</div></td>
|
<td>${c.firstReceiver}<div class="muted">${c.firstInputAt}</div></td>
|
<td>${c.owner}</td>
|
<td>${c.reminder}</td>
|
<td>${c.last}</td>
|
<td>
|
${role === "supervisor"
|
? `<button class="btn primary" onclick="assignCustomer(${c.id})">分配给销售一</button>`
|
: `<button class="btn" disabled>等待主管分配</button>`}
|
</td>
|
</tr>
|
`).join("");
|
}
|
|
function renderDeals() {
|
const rows = visibleCustomers().filter(c => c.dealStatus !== "未成交");
|
document.getElementById("dealRows").innerHTML = rows.length ? rows.map(c => `
|
<tr>
|
<td><strong>${c.name}</strong><div class="muted">${c.company}</div></td>
|
<td>${tag(c.dealStatus, "green")}</td>
|
<td>${c.dealProduct}<div class="muted">${c.dealAt} | ${c.dealAmount}</div></td>
|
<td>${c.owner}</td>
|
<td>${c.firstReceiver}<div class="muted">${c.firstInputAt}</div></td>
|
<td>${c.nextDealRevisit || "未设置"}</td>
|
<td><button class="btn" onclick="openCustomer(${c.id})">回访记录</button></td>
|
</tr>
|
`).join("") : `
|
<tr><td colspan="7"><span class="muted">当前视角暂无成交客户</span></td></tr>
|
`;
|
}
|
|
function renderProjects() {
|
const rows = role === "supervisor" ? projects : projects.filter(p => p.applicant === "销售一");
|
document.getElementById("projectRows").innerHTML = rows.map(p => `
|
<tr>
|
<td><strong>${p.name}</strong></td>
|
<td>${p.customer}</td>
|
<td>${p.applicant}</td>
|
<td>${p.stage}</td>
|
<td>${p.end}</td>
|
<td>${tag(p.status, p.status === "已通过" ? "green" : "amber")}</td>
|
<td>
|
${role === "supervisor" && p.status !== "已通过"
|
? `<button class="btn green" onclick="approveProject(${p.id})">通过</button>`
|
: `<button class="btn" onclick="showToast('已打开备案详情')">查看</button>`}
|
</td>
|
</tr>
|
`).join("");
|
}
|
|
function openCustomer(id) {
|
const c = customers.find(x => x.id === id);
|
document.getElementById("drawerTitle").textContent = `${c.name} · ${c.company}`;
|
document.getElementById("drawerSub").textContent = `负责人:${c.owner} | 来源:${c.source} | 当前阶段:${c.stage}`;
|
const limited = role !== "supervisor" && c.owner !== "销售一";
|
document.getElementById("drawerBody").innerHTML = `
|
${limited ? `<div class="notice">当前是员工视角,只能查看被分配客户的完整资料。</div>` : ""}
|
<div class="form-grid">
|
<div class="field"><label>联系方式</label><input value="${limited ? "需主管分配后查看" : c.phone + " / " + c.wechat}" readonly></div>
|
<div class="field"><label>保护状态</label><input value="${c.protect},到期 ${c.protectEnd}" readonly></div>
|
<div class="field"><label>使用场景</label><input value="${c.scene}" readonly></div>
|
<div class="field"><label>关键参数</label><input value="${c.params}" readonly></div>
|
<div class="field"><label>阶段漏斗</label><input value="${c.funnel}" readonly></div>
|
<div class="field"><label>成交状态</label><input value="${c.dealStatus}" readonly></div>
|
<div class="field"><label>首次录入</label><input value="${c.firstReceiver} | ${c.firstInputAt}" readonly></div>
|
<div class="field"><label>下次跟进</label><input value="${c.next}" readonly></div>
|
</div>
|
<div class="field"><label>需求类型</label><textarea readonly>${c.demand}</textarea></div>
|
<div class="field" style="margin-top:12px;"><label>备注</label><textarea readonly>${c.remark}</textarea></div>
|
<div class="toolbar" style="margin:14px 0;">
|
<button class="btn primary" onclick="showToast('已新增一条跟进记录(体验模拟)')">新增跟进</button>
|
<button class="btn" onclick="showToast('已新增成交回访记录(体验模拟)')">成交回访</button>
|
<button class="btn" onclick="showToast('已提交项目备案申请(体验模拟)')">申请项目备案</button>
|
${role === "supervisor" ? `<button class="btn danger" onclick="releaseCustomer(${c.id})">释放到公海</button>` : ""}
|
</div>
|
<div class="timeline">
|
${c.dealStatus !== "未成交" ? `<div class="tl"><strong>${c.nextDealRevisit} 成交客户回访计划</strong><div class="muted">跟进使用情况、耗材维护、复购机会和售后问题。</div></div>` : ""}
|
<div class="tl"><strong>客户执行记录</strong><div class="muted">系统已记录:创建客户、阶段变更、报价、跟进、${c.pool === "公共客户池" ? "释放到公海" : "保护期刷新"}。</div></div>
|
<div class="tl"><strong>2026-06-27 有效跟进</strong><div class="muted">客户确认车间面积和层高,要求重新核算型号。</div></div>
|
<div class="tl"><strong>2026-06-21 报价记录</strong><div class="muted">已按 800㎡ 食品车间方案报价,等待客户内部审批。</div></div>
|
<div class="tl"><strong>2026-06-18 首次有效咨询</strong><div class="muted">来源 ${c.source},已录入场景、需求类型和下次跟进时间。</div></div>
|
</div>
|
`;
|
document.getElementById("drawer").classList.add("active");
|
}
|
|
function assignCustomer(id) {
|
const c = customers.find(x => x.id === id);
|
c.owner = "销售一";
|
c.pool = "正常";
|
c.protect = "保护中";
|
c.next = "2026-07-05";
|
showToast("已分配给销售一");
|
render();
|
}
|
|
function releaseCustomer(id) {
|
const c = customers.find(x => x.id === id);
|
c.pool = "公共客户池";
|
c.protect = "已过期";
|
document.getElementById("drawer").classList.remove("active");
|
showToast("已释放到公共客户池");
|
render();
|
}
|
|
function approveProject(id) {
|
const p = projects.find(x => x.id === id);
|
p.status = "已通过";
|
showToast("审批已通过");
|
render();
|
}
|
|
function openNewCustomer() {
|
document.getElementById("drawerTitle").textContent = "新增客户";
|
document.getElementById("drawerSub").textContent = "体验版会模拟必填校验和重复客户提醒";
|
document.getElementById("drawerBody").innerHTML = `
|
<div class="notice">输入手机号或公司名称时,系统会检查疑似重复客户。正式版会根据电话、微信、公司、平台账号等多字段判断。</div>
|
<div class="form-grid">
|
<div class="field"><label>客户姓名/称呼</label><input placeholder="例如:王经理"></div>
|
<div class="field"><label>手机号/微信/平台账号</label><input placeholder="至少填写一项" oninput="document.getElementById('dupTip').style.display='block'"></div>
|
<div class="field"><label>公司名称</label><input placeholder="能获取则必须填写"></div>
|
<div class="field"><label>来源平台</label><select><option>百度</option><option>爱采购</option><option>淘宝</option><option>抖音</option><option>官网</option></select></div>
|
<div class="field"><label>首次咨询时间</label><input type="datetime-local" value="2026-07-06T09:00"></div>
|
<div class="field"><label>首次接待人</label><input value="销售一" readonly></div>
|
<div class="field"><label>使用场景</label><input placeholder="食品厂、冷库、水处理等"></div>
|
<div class="field"><label>意向等级</label><select><option>A</option><option>B</option><option>C</option><option>D</option></select></div>
|
<div class="field"><label>需求类型</label><input placeholder="空间消毒、水处理、除味等"></div>
|
<div class="field"><label>成交状态</label><select><option>未成交</option><option>已成交</option><option>复购中</option><option>售后维护中</option><option>已流失</option></select></div>
|
<div class="field"><label>阶段漏斗</label><select><option>S1 新线索</option><option>S2 已联系</option><option>S3 需求确认</option><option>S4 选型方案</option><option>S5 已报价</option><option>S6 比价/审批</option><option>S7 成交/执行</option><option>S8 暂缓/失败</option></select></div>
|
<div class="field"><label>下次跟进时间</label><input type="date" value="2026-07-05"></div>
|
</div>
|
<div class="field" style="margin-bottom:12px;"><label>备注</label><textarea placeholder="记录客户特殊要求、预算、沟通注意事项等"></textarea></div>
|
<div id="dupTip" class="notice" style="display:none;">发现 1 条疑似重复客户:杭州佳源食品厂。需要主管确认后才能改变归属。</div>
|
<button class="btn primary" onclick="showToast('已保存客户(体验模拟)')">保存客户</button>
|
`;
|
document.getElementById("drawer").classList.add("active");
|
}
|
|
function render() {
|
renderStats();
|
renderDashboard();
|
renderCustomers();
|
renderDeals();
|
renderReminderList("allReminders", buildReminders());
|
renderPool();
|
renderProjects();
|
}
|
|
document.querySelectorAll(".nav button").forEach(btn => btn.addEventListener("click", () => setView(btn.dataset.view)));
|
document.querySelectorAll(".role-switch button").forEach(btn => btn.addEventListener("click", () => setRole(btn.dataset.role)));
|
document.getElementById("closeDrawer").addEventListener("click", () => document.getElementById("drawer").classList.remove("active"));
|
document.getElementById("closeDrawerBtn").addEventListener("click", () => document.getElementById("drawer").classList.remove("active"));
|
document.getElementById("addCustomerBtn").addEventListener("click", openNewCustomer);
|
document.getElementById("addCustomerBtn2").addEventListener("click", openNewCustomer);
|
document.getElementById("runRulesBtn").addEventListener("click", () => showToast("已按 2026-07-04 早上 8:30 规则生成提醒"));
|
document.getElementById("finishAllBtn").addEventListener("click", () => showToast("提醒已标记为已读(体验模拟)"));
|
document.getElementById("searchInput").addEventListener("input", renderCustomers);
|
document.getElementById("stageFilter").addEventListener("change", renderCustomers);
|
render();
|
</script>
|
</body>
|
</html>
|