Channel管理.html 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>Channel管理 - 管理后台</title>
  7. <script src="https://unpkg.com/lucide@latest"></script>
  8. <style>
  9. * { box-sizing: border-box; }
  10. body {
  11. margin: 0;
  12. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Arial, sans-serif;
  13. background: #f3f5f8;
  14. color: #1f2d3d;
  15. font-size: 14px;
  16. }
  17. .page {
  18. min-height: 100vh;
  19. display: flex;
  20. background: #f3f5f8;
  21. overflow: hidden;
  22. }
  23. /* === 侧边栏 === */
  24. .sidebar {
  25. width: 286px;
  26. min-height: 100vh;
  27. background: #2d3a4b;
  28. color: #c6d0df;
  29. flex-shrink: 0;
  30. display: flex;
  31. flex-direction: column;
  32. }
  33. .logo {
  34. height: 78px;
  35. display: flex;
  36. align-items: center;
  37. padding-left: 66px;
  38. gap: 12px;
  39. color: #fff;
  40. font-size: 24px;
  41. font-weight: 700;
  42. background: #263445;
  43. border-bottom: 1px solid rgba(255,255,255,.06);
  44. flex-shrink: 0;
  45. }
  46. .logo-icon {
  47. width: 24px;
  48. height: 18px;
  49. border: 2px solid #fff;
  50. border-radius: 2px;
  51. position: relative;
  52. opacity: .92;
  53. }
  54. .logo-icon::after {
  55. content: "";
  56. position: absolute;
  57. left: 7px;
  58. bottom: -7px;
  59. width: 9px;
  60. height: 5px;
  61. border-bottom: 2px solid #fff;
  62. }
  63. .menu {
  64. padding-top: 28px;
  65. flex: 1;
  66. overflow-y: auto;
  67. }
  68. .menu-section-title,
  69. .menu-item {
  70. height: 46px;
  71. display: flex;
  72. align-items: center;
  73. padding: 0 24px;
  74. color: #d5deeb;
  75. font-size: 14px;
  76. letter-spacing: .2px;
  77. cursor: pointer;
  78. transition: all 0.2s;
  79. }
  80. .menu-section-title:hover, .menu-item:hover {
  81. color: #fff;
  82. }
  83. .menu-section-title .left-icon {
  84. width: 15px;
  85. margin-right: 16px;
  86. opacity: .95;
  87. }
  88. .menu-section-title .arrow {
  89. margin-left: auto;
  90. font-size: 14px;
  91. color: #b6c1d3;
  92. transition: transform 0.2s;
  93. }
  94. .bank-title {
  95. margin-top: 8px;
  96. color: #fff;
  97. }
  98. .submenu { padding-bottom: 12px; }
  99. .submenu .menu-item {
  100. height: 46px;
  101. padding-left: 76px;
  102. color: #bdc8d8;
  103. font-size: 14px;
  104. }
  105. .submenu .menu-item.active {
  106. background: rgba(23, 36, 54, .45);
  107. color: #409eff;
  108. font-weight: 500;
  109. }
  110. .rights-title { margin-top: 2px; }
  111. /* === 主内容区 === */
  112. .main {
  113. flex: 1;
  114. min-width: 0;
  115. display: flex;
  116. flex-direction: column;
  117. height: 100vh;
  118. }
  119. .topbar {
  120. height: 78px;
  121. background: #fff;
  122. border-bottom: 1px solid #e4e8ef;
  123. box-shadow: 0 1px 4px rgba(30,45,70,.04);
  124. display: flex;
  125. align-items: center;
  126. padding: 0 30px;
  127. color: #2b3442;
  128. font-size: 14px;
  129. flex-shrink: 0;
  130. }
  131. .breadcrumb span {
  132. margin: 0 11px;
  133. color: #596579;
  134. }
  135. .content {
  136. padding: 38px 30px 30px 30px;
  137. max-width: 100%;
  138. flex: 1;
  139. overflow-y: auto;
  140. }
  141. .view-section { display: none; }
  142. .view-section.active { display: block; }
  143. .search-card {
  144. background: #fff;
  145. border: 1px solid #e2e7ef;
  146. border-radius: 4px;
  147. padding: 24px 24px 22px 24px;
  148. box-shadow: 0 1px 4px rgba(30,45,70,.04);
  149. width: 100%;
  150. margin-bottom: 31px;
  151. }
  152. .form-grid {
  153. display: grid;
  154. grid-template-columns: repeat(3, minmax(260px, 1fr));
  155. column-gap: 36px;
  156. row-gap: 18px;
  157. align-items: center;
  158. margin-bottom: 24px;
  159. }
  160. /* 允许 label 根据需要设定特定的列宽 */
  161. .form-item {
  162. display: flex;
  163. align-items: center;
  164. min-width: 0;
  165. }
  166. /* 默认给 Channel 设置的标签宽度 */
  167. .form-item label {
  168. width: 106px;
  169. flex-shrink: 0;
  170. text-align: right;
  171. padding-right: 14px;
  172. color: #1f2937;
  173. font-size: 14px;
  174. white-space: nowrap;
  175. }
  176. /* 针对 Campaign 长标签的扩展样式 */
  177. .form-item.long-label label {
  178. width: 130px;
  179. }
  180. input,
  181. select {
  182. flex: 1;
  183. width: 100%;
  184. height: 40px;
  185. border: 1px solid #dfe4ec;
  186. border-radius: 4px;
  187. background: #fff;
  188. color: #303a4b;
  189. padding: 0 12px;
  190. font-size: 14px;
  191. outline: none;
  192. transition: border-color 0.2s;
  193. }
  194. input:focus, select:focus {
  195. border-color: #409eff;
  196. }
  197. input::placeholder { color: #9ba6b8; }
  198. select:disabled,
  199. input:disabled {
  200. background: #f8fafc;
  201. color: #a0a8b5;
  202. cursor: not-allowed;
  203. }
  204. .divider {
  205. height: 1px;
  206. background: #e8edf3;
  207. margin: 0 0 20px 0;
  208. }
  209. .toolbar {
  210. display: flex;
  211. justify-content: space-between;
  212. align-items: center;
  213. }
  214. .toolbar-left,
  215. .toolbar-right {
  216. display: flex;
  217. gap: 14px;
  218. align-items: center;
  219. }
  220. .btn {
  221. height: 40px;
  222. padding: 0 24px;
  223. border-radius: 4px;
  224. border: 1px solid #d8dde6;
  225. background: #fff;
  226. color: #334155;
  227. font-size: 14px;
  228. cursor: pointer;
  229. display: inline-flex;
  230. align-items: center;
  231. justify-content: center;
  232. gap: 8px;
  233. transition: all 0.2s;
  234. }
  235. .btn:hover { opacity: .92; border-color: #c0c4cc; color: #409eff; }
  236. .btn-primary {
  237. background: #2463e8;
  238. border-color: #2463e8;
  239. color: #fff;
  240. min-width: 152px;
  241. }
  242. .btn-primary:hover { border-color: #409eff; color: #fff; }
  243. .btn-purple {
  244. background: #5655e9;
  245. border-color: #5655e9;
  246. color: #fff;
  247. min-width: 82px;
  248. }
  249. .btn-purple:hover { border-color: #7b7af0; color: #fff; }
  250. .hide { display: none !important; }
  251. /* === 表格区 === */
  252. .table-card {
  253. background: #fff;
  254. border: 1px solid #e3e7ef;
  255. border-radius: 1px;
  256. overflow: auto;
  257. box-shadow: 0 1px 3px rgba(30,45,70,.04);
  258. max-width: 100%;
  259. }
  260. table {
  261. width: max-content;
  262. min-width: 100%;
  263. border-collapse: separate;
  264. border-spacing: 0;
  265. table-layout: fixed;
  266. }
  267. thead th {
  268. height: 55px;
  269. background: #fbfcfe;
  270. border-bottom: 1px solid #e5e9f1;
  271. border-right: 1px solid #e5e9f1;
  272. color: #344154;
  273. font-size: 14px;
  274. font-weight: 500;
  275. text-align: center;
  276. padding: 0 18px;
  277. white-space: nowrap;
  278. }
  279. tbody td {
  280. height: 58px;
  281. border-bottom: 1px solid #edf0f5;
  282. border-right: 1px solid #edf0f5;
  283. color: #2f3c4e;
  284. font-size: 14px;
  285. padding: 0 18px;
  286. vertical-align: middle;
  287. text-align: center;
  288. word-break: break-word;
  289. }
  290. tbody tr:last-child td { border-bottom: none; }
  291. tbody tr:hover { background-color: #f5f7fa; }
  292. /* 列宽 */
  293. .col-action { width: 190px; }
  294. .col-id { width: 120px; }
  295. .col-type { width: 150px; }
  296. .col-category { width: 190px; }
  297. .col-name { width: 190px; }
  298. .col-sub { width: 160px; }
  299. .col-owner { width: 120px; }
  300. .col-time { width: 205px; }
  301. th.col-action,
  302. td.action-links {
  303. position: sticky;
  304. left: 0;
  305. z-index: 3;
  306. background: #fff;
  307. box-shadow: 1px 0 0 #e5e9f1;
  308. white-space: nowrap;
  309. }
  310. tbody tr:hover td.action-links {
  311. background-color: #f5f7fa;
  312. }
  313. th.col-action {
  314. z-index: 4;
  315. background: #fbfcfe;
  316. }
  317. .action-links a {
  318. text-decoration: none;
  319. margin-right: 12px;
  320. font-size: 14px;
  321. cursor: pointer;
  322. }
  323. .action-links .edit,
  324. .action-links .view { color: #2f7cf6; }
  325. .action-links .delete { color: #ff4d4f; }
  326. .empty-tip { color: #9aa4b4; }
  327. /* === 弹窗 === */
  328. .modal-mask {
  329. position: fixed;
  330. inset: 0;
  331. background: rgba(15, 23, 42, .48);
  332. display: none;
  333. align-items: center;
  334. justify-content: center;
  335. padding: 0;
  336. z-index: 50;
  337. }
  338. .modal-mask.show { display: flex; }
  339. .modal {
  340. width: 560px;
  341. background: #fff;
  342. border-radius: 4px;
  343. box-shadow: 0 10px 28px rgba(0,0,0,.20);
  344. overflow: hidden;
  345. display: flex;
  346. flex-direction: column;
  347. max-height: 90vh;
  348. }
  349. .modal-header {
  350. height: 56px;
  351. display: flex;
  352. align-items: center;
  353. justify-content: space-between;
  354. padding: 0 20px;
  355. border-bottom: 1px solid #e5e7eb;
  356. font-size: 16px;
  357. font-weight: 500;
  358. color: #172033;
  359. background: #fff;
  360. flex-shrink: 0;
  361. }
  362. .modal-close {
  363. color: #9099a8;
  364. font-size: 22px;
  365. line-height: 1;
  366. cursor: pointer;
  367. transition: color 0.2s;
  368. }
  369. .modal-close:hover { color: #ff4d4f; }
  370. .modal-body {
  371. padding: 22px 24px 18px 20px;
  372. overflow-y: auto;
  373. flex: 1;
  374. }
  375. .modal-row {
  376. display: flex;
  377. align-items: center;
  378. margin-bottom: 14px;
  379. }
  380. .modal-row > label {
  381. width: 128px;
  382. flex-shrink: 0;
  383. text-align: right;
  384. padding-right: 16px;
  385. color: #1f2937;
  386. font-size: 14px;
  387. white-space: nowrap;
  388. }
  389. .modal-row > input, .modal-row > .inline-field {
  390. flex: 1;
  391. min-width: 0;
  392. }
  393. .required {
  394. color: #f5222d;
  395. margin-right: 5px;
  396. }
  397. .inline-field {
  398. display: flex;
  399. gap: 6px;
  400. align-items: center;
  401. }
  402. .inline-field select,
  403. .inline-field input { flex: 1; }
  404. .small-btn {
  405. width: 52px;
  406. height: 34px;
  407. border: 1px solid #2463e8;
  408. background: #f7fbff;
  409. color: #2463e8;
  410. border-radius: 4px;
  411. cursor: pointer;
  412. font-size: 14px;
  413. flex-shrink: 0;
  414. transition: all 0.2s;
  415. }
  416. .small-btn:hover { background: #eaf1fb; }
  417. .small-btn.edit-current {
  418. border-color: #d8dde6;
  419. color: #334155;
  420. background: #fff;
  421. }
  422. .small-btn.edit-current:hover { border-color: #c0c4cc; color: #409eff; }
  423. .small-btn:disabled {
  424. border-color: #e5e7eb;
  425. color: #b4bdca;
  426. background: #f8fafc;
  427. cursor: not-allowed;
  428. }
  429. .quick-panel {
  430. margin: -4px 0 14px 128px;
  431. padding: 10px;
  432. border: 1px solid #dbeafe;
  433. border-radius: 4px;
  434. background: #f8fbff;
  435. display: none;
  436. grid-template-columns: 1fr auto auto;
  437. gap: 8px;
  438. align-items: center;
  439. }
  440. .quick-panel.show { display: grid; }
  441. .quick-panel .btn {
  442. height: 34px;
  443. padding: 0 14px;
  444. font-size: 14px;
  445. min-width: 62px;
  446. }
  447. .modal-footer {
  448. height: 58px;
  449. border-top: 1px solid #e5e7eb;
  450. background: #f8fafc;
  451. display: flex;
  452. justify-content: flex-end;
  453. align-items: center;
  454. gap: 10px;
  455. padding: 0 18px;
  456. flex-shrink: 0;
  457. }
  458. .modal-footer .btn {
  459. min-width: 72px;
  460. height: 38px;
  461. padding: 0 20px;
  462. }
  463. .toast {
  464. position: fixed;
  465. top: 88px;
  466. left: 50%;
  467. transform: translateX(-50%);
  468. background: rgba(15,23,42,.9);
  469. color: #fff;
  470. padding: 10px 16px;
  471. border-radius: 4px;
  472. font-size: 14px;
  473. display: none;
  474. z-index: 100;
  475. }
  476. .toast.show { display: block; }
  477. @media (max-width: 1400px) {
  478. .sidebar { width: 260px; }
  479. .logo { padding-left: 50px; }
  480. .form-grid {
  481. grid-template-columns: repeat(2, minmax(260px, 1fr));
  482. column-gap: 24px;
  483. }
  484. }
  485. @media (max-width: 1100px) {
  486. .form-grid {
  487. grid-template-columns: 1fr;
  488. }
  489. }
  490. </style>
  491. </head>
  492. <body>
  493. <div class="page">
  494. <!-- 侧边栏 -->
  495. <aside class="sidebar">
  496. <div class="logo"><div class="logo-icon"></div><div>管理后台</div></div>
  497. <nav class="menu">
  498. <div class="menu-section-title"><i data-lucide="settings" class="left-icon"></i><span>定制功能</span><i data-lucide="chevron-down" class="arrow"></i></div>
  499. <div class="menu-section-title bank-title"><span>汇丰银行</span><i data-lucide="chevron-up" class="arrow"></i></div>
  500. <div class="submenu">
  501. <div class="menu-item">页面管理</div>
  502. <div class="menu-item">权益介绍页</div>
  503. <div class="menu-item">权益主页</div>
  504. <div class="menu-item">组装页</div>
  505. <div class="menu-item active" id="menu-channel" onclick="switchView('channel')">Channel管理</div>
  506. <div class="menu-item" id="menu-campaign" onclick="switchView('campaign')">Campaign管理</div>
  507. </div>
  508. <div class="menu-section-title rights-title"><span>权益管理</span><i data-lucide="chevron-up" class="arrow"></i></div>
  509. <div class="submenu">
  510. <div class="menu-item">借记卡权益</div>
  511. <div class="menu-item">信用卡权益</div>
  512. <div class="menu-item">人群管理</div>
  513. </div>
  514. </nav>
  515. </aside>
  516. <!-- 主区域 -->
  517. <main class="main">
  518. <header class="topbar">
  519. <div class="breadcrumb" id="breadcrumb">汇丰银行 <span>&gt;</span> 页面管理 <span>&gt;</span> Channel管理</div>
  520. </header>
  521. <!-- 视图:Channel -->
  522. <section class="content view-section active" id="view-channel">
  523. <div class="search-card">
  524. <div class="form-grid">
  525. <div class="form-item"><label>Channel ID</label><input type="text" id="s-ch-id" placeholder="支持精确查询" /></div>
  526. <div class="form-item">
  527. <label>Channel 类型</label>
  528. <select id="s-ch-type"><option value="">全部</option></select>
  529. </div>
  530. <div class="form-item"><label>Channel 类别</label><input type="text" id="s-ch-category" placeholder="关键字" /></div>
  531. <div class="form-item"><label>Channel 名称</label><input type="text" id="s-ch-name" placeholder="关键字" /></div>
  532. <div class="form-item"><label>Channel 子名称</label><input type="text" id="s-ch-subname" placeholder="关键字" /></div>
  533. <div class="form-item"><label>跟进员工</label><input type="text" id="s-ch-staff" placeholder="员工姓名" /></div>
  534. </div>
  535. <div class="divider"></div>
  536. <div class="toolbar">
  537. <div class="toolbar-left">
  538. <button class="btn btn-primary" onclick="openChannelModal('create')">
  539. <i data-lucide="plus" style="width: 16px; height: 16px;"></i> 新建 Channel
  540. </button>
  541. </div>
  542. <div class="toolbar-right">
  543. <button class="btn btn-purple" onclick="renderChannelTable()">查询</button>
  544. <button class="btn" onclick="resetSearch('channel')">重置</button>
  545. </div>
  546. </div>
  547. </div>
  548. <div class="table-card">
  549. <table>
  550. <thead>
  551. <tr>
  552. <th class="col-action">操作</th>
  553. <th class="col-id">Channel ID</th>
  554. <th class="col-type">类型</th>
  555. <th class="col-category">类别</th>
  556. <th class="col-name">名称</th>
  557. <th class="col-sub">子名称</th>
  558. <th class="col-owner">跟进员工</th>
  559. <th class="col-time">创建时间</th>
  560. <th class="col-time">更新时间</th>
  561. </tr>
  562. </thead>
  563. <tbody id="channelTableBody"></tbody>
  564. </table>
  565. </div>
  566. </section>
  567. <!-- 视图:Campaign -->
  568. <section class="content view-section" id="view-campaign">
  569. <div class="search-card">
  570. <div class="form-grid">
  571. <div class="form-item long-label"><label>Campaign ID</label><input type="text" id="s-cp-id" placeholder="支持精确查询" /></div>
  572. <div class="form-item long-label"><label>Campaign 名称</label><input type="text" id="s-cp-name" placeholder="模糊查询" /></div>
  573. <div class="form-item long-label"><label>Campaign 负责人</label><input type="text" id="s-cp-owner" placeholder="关键字" /></div>
  574. <div class="form-item long-label"><label>跟进员工</label><input type="text" id="s-cp-staff" placeholder="员工姓名" /></div>
  575. </div>
  576. <div class="divider"></div>
  577. <div class="toolbar">
  578. <div class="toolbar-left">
  579. <button class="btn btn-primary" onclick="openCampaignModal('create')">
  580. <i data-lucide="plus" style="width: 16px; height: 16px;"></i> 新建 Campaign
  581. </button>
  582. </div>
  583. <div class="toolbar-right">
  584. <button class="btn btn-purple" onclick="renderCampaignTable()">查询</button>
  585. <button class="btn" onclick="resetSearch('campaign')">重置</button>
  586. </div>
  587. </div>
  588. </div>
  589. <div class="table-card">
  590. <table>
  591. <thead>
  592. <tr>
  593. <th class="col-action" style="width: 150px;">操作</th>
  594. <th class="col-id" style="width: 150px;">Campaign ID</th>
  595. <th class="col-name">Campaign 名称</th>
  596. <th class="col-owner" style="width: 150px;">Campaign 负责人</th>
  597. <th class="col-owner" style="width: 150px;">跟进员工</th>
  598. <th class="col-time">创建时间</th>
  599. <th class="col-time">更新时间</th>
  600. </tr>
  601. </thead>
  602. <tbody id="campaignTableBody"></tbody>
  603. </table>
  604. </div>
  605. </section>
  606. </main>
  607. </div>
  608. <!-- 弹窗:新建/编辑 Channel -->
  609. <div class="modal-mask" id="channelModal">
  610. <div class="modal">
  611. <div class="modal-header">
  612. <div id="modalTitle">新建 Channel</div>
  613. <div class="modal-close" onclick="closeModal('channelModal')">×</div>
  614. </div>
  615. <div class="modal-body">
  616. <div class="modal-row hide" id="chIdRow">
  617. <label>Channel ID</label>
  618. <input type="text" id="f-ch-id" disabled />
  619. </div>
  620. <div class="modal-row">
  621. <label><span class="required">*</span>Channel 类型</label>
  622. <div class="inline-field">
  623. <select id="channelType">
  624. <option value="">请选择类型</option>
  625. </select>
  626. <button class="small-btn ch-btn" type="button" data-target="type">新增</button>
  627. <button class="small-btn edit-current ch-btn" type="button" data-target="type">编辑</button>
  628. </div>
  629. </div>
  630. <div class="quick-panel" id="typePanel">
  631. <input id="typeInput" type="text" placeholder="请输入类型" />
  632. <button class="btn btn-primary" style="min-width: 62px;" type="button" data-save="type">保存</button>
  633. <button class="btn" type="button" data-cancel="type">取消</button>
  634. </div>
  635. <div class="modal-row">
  636. <label><span class="required">*</span>Channel 类别</label>
  637. <div class="inline-field">
  638. <select id="channelCategory" disabled>
  639. <option value="">请先选择类型</option>
  640. </select>
  641. <button class="small-btn ch-btn" type="button" data-target="category">新增</button>
  642. <button class="small-btn edit-current ch-btn" type="button" data-target="category">编辑</button>
  643. </div>
  644. </div>
  645. <div class="quick-panel" id="categoryPanel">
  646. <input id="categoryInput" type="text" placeholder="请输入类别" />
  647. <button class="btn btn-primary" style="min-width: 62px;" type="button" data-save="category">保存</button>
  648. <button class="btn" type="button" data-cancel="category">取消</button>
  649. </div>
  650. <div class="modal-row">
  651. <label><span class="required">*</span>Channel 名称</label>
  652. <div class="inline-field">
  653. <select id="channelName" disabled>
  654. <option value="">请先选择类别</option>
  655. </select>
  656. <button class="small-btn ch-btn" type="button" data-target="name">新增</button>
  657. <button class="small-btn edit-current ch-btn" type="button" data-target="name">编辑</button>
  658. </div>
  659. </div>
  660. <div class="quick-panel" id="namePanel">
  661. <input id="nameInput" type="text" placeholder="请输入名称" />
  662. <button class="btn btn-primary" style="min-width: 62px;" type="button" data-save="name">保存</button>
  663. <button class="btn" type="button" data-cancel="name">取消</button>
  664. </div>
  665. <div class="modal-row">
  666. <label>Channel 子名称</label>
  667. <input id="subNameInput" type="text" placeholder="请输入Channel 子名称" />
  668. </div>
  669. <div class="modal-row">
  670. <label>跟进员工</label>
  671. <input id="ownerInput" type="text" placeholder="请输入跟进员工姓名" />
  672. </div>
  673. </div>
  674. <div class="modal-footer" id="modal-ch-footer">
  675. <button class="btn" onclick="closeModal('channelModal')">取消</button>
  676. <button class="btn btn-primary" id="submitChannelBtn">确定</button>
  677. </div>
  678. </div>
  679. </div>
  680. <!-- 弹窗:新建/编辑 Campaign -->
  681. <div class="modal-mask" id="campaignModal">
  682. <div class="modal" style="width: 500px;">
  683. <div class="modal-header">
  684. <div id="modalCpTitle">新建 Campaign</div>
  685. <div class="modal-close" onclick="closeModal('campaignModal')">×</div>
  686. </div>
  687. <div class="modal-body">
  688. <div class="modal-row hide" id="cpIdRow">
  689. <label style="width: 140px; padding-right: 12px;">Campaign ID</label>
  690. <input type="text" id="f-cp-id" disabled />
  691. </div>
  692. <div class="modal-row">
  693. <label style="width: 140px; padding-right: 12px;"><span class="required">*</span>Campaign 名称</label>
  694. <input type="text" id="f-cp-name" placeholder="请输入名称" />
  695. </div>
  696. <div class="modal-row">
  697. <label style="width: 140px; padding-right: 12px;"><span class="required">*</span>Campaign 负责人</label>
  698. <input type="text" id="f-cp-owner" placeholder="请输入负责人" />
  699. </div>
  700. <div class="modal-row">
  701. <label style="width: 140px; padding-right: 12px;">跟进员工 (可选)</label>
  702. <input type="text" id="f-cp-staff" placeholder="请输入员工姓名" />
  703. </div>
  704. </div>
  705. <div class="modal-footer" id="modal-cp-footer">
  706. <button class="btn" onclick="closeModal('campaignModal')">取消</button>
  707. <button class="btn btn-primary" onclick="submitCampaignModal()">确定</button>
  708. </div>
  709. </div>
  710. </div>
  711. <!-- 弹窗:确认删除 -->
  712. <div class="modal-mask" id="confirmModal">
  713. <div class="modal" style="width: 380px;">
  714. <div class="modal-header" style="border-bottom: none; padding-top: 24px;">
  715. <div style="font-weight: 600; font-size: 16px; color: #172033; display: flex; align-items: center;">
  716. <i data-lucide="alert-circle" style="color: #ff4d4f; margin-right: 8px;"></i> 确认删除
  717. </div>
  718. <div class="modal-close" onclick="closeModal('confirmModal')">×</div>
  719. </div>
  720. <div class="modal-body" style="padding: 16px 24px 32px 50px; font-size: 14px; color: #596579;">
  721. 您确定要删除此条数据吗?删除后将无法恢复。
  722. </div>
  723. <div class="modal-footer" style="padding: 12px 24px; justify-content: flex-end;">
  724. <button class="btn" style="height: 32px; padding: 0 16px; min-width: auto;" onclick="closeModal('confirmModal')">取消</button>
  725. <button class="btn btn-primary" style="height: 32px; padding: 0 16px; min-width: auto; background: #ff4d4f; border-color: #ff4d4f;" id="btn-confirm-delete">确认删除</button>
  726. </div>
  727. </div>
  728. </div>
  729. <div class="toast" id="toast"></div>
  730. <script>
  731. lucide.createIcons();
  732. // ================= 数据初始化 =================
  733. const channelDict = {
  734. "Human-assisted": {
  735. "汇丰中国客户服务号": ["汇丰中国客户服务号"],
  736. "MGM": ["MGM - Mortgage", "MGM - Premier"],
  737. "CCSS": ["CCSS SMS"],
  738. "Referral": ["GPB Referral", "HK referral", "Non WPB staff Referral", "Pinnacle Referral", "RBB Refferal"],
  739. "New staff": ["New staff"],
  740. "IBC": ["IBC referral staff code"],
  741. "Group Acct": ["HTC", "NIVIDA"]
  742. },
  743. "Unassisted": {
  744. "汇丰中国APP": ["Mobile X"],
  745. "汇丰中国官网": ["PWS site"],
  746. "汇丰中国小程序": ["汇丰中国小程序"],
  747. "汇丰中国订阅号": ["汇丰中国订阅号", "汇丰中国订阅号推文", "推文尾部入口"],
  748. "Chatbot": ["Chatbot"],
  749. "SMS": ["SMS"],
  750. "Paid Media": ["汇丰中国订阅号推文", "others"],
  751. "Others": ["others", "River APP"]
  752. }
  753. };
  754. let channelRows = [
  755. { id: "CH0001", type: "Human-assisted", category: "汇丰中国客户服务号", name: "汇丰中国客户服务号", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  756. { id: "CH0002", type: "Human-assisted", category: "MGM", name: "MGM - Mortgage", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  757. { id: "CH0003", type: "Human-assisted", category: "MGM", name: "MGM - Premier", subName: "双十一特惠", owner: "张经理", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-21 14:30:00" },
  758. { id: "CH0004", type: "Human-assisted", category: "CCSS", name: "CCSS SMS", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  759. { id: "CH0005", type: "Human-assisted", category: "Referral", name: "GPB Referral", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  760. { id: "CH0006", type: "Unassisted", category: "汇丰中国APP", name: "Mobile X", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  761. { id: "CH0007", type: "Unassisted", category: "汇丰中国官网", name: "PWS site", subName: "私行沙龙引导", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  762. { id: "CH0008", type: "Unassisted", category: "汇丰中国订阅号", name: "汇丰中国订阅号", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" },
  763. { id: "CH0009", type: "Unassisted", category: "汇丰中国订阅号", name: "推文尾部入口", subName: "", owner: "", createTime: "2025-11-20 09:15:00", updateTime: "2025-11-20 09:15:00" }
  764. ];
  765. let campaigns = [
  766. { id: 'CP9001', name: 'Q3 大客户回馈', owner: 'Market Team', staff: '王专员', createTime: '2025-10-01 10:00:00', updateTime: '2025-10-15 14:30:00' },
  767. { id: 'CP9002', name: '新春开户活动', owner: 'Retail Team', staff: '李专员', createTime: '2025-11-20 09:15:00', updateTime: '2025-11-20 09:15:00' }
  768. ];
  769. let editingChannelId = null;
  770. let editingCampaignId = null;
  771. let panelMode = { type: "add", category: "add", name: "add" };
  772. let deleteTarget = { type: null, id: null };
  773. // ================= 工具 =================
  774. function getNowString() {
  775. const d = new Date();
  776. const pad = n => n < 10 ? '0' + n : n;
  777. return d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
  778. }
  779. const toast = document.getElementById("toast");
  780. function showToast(text) {
  781. toast.textContent = text;
  782. toast.classList.add("show");
  783. setTimeout(() => toast.classList.remove("show"), 1600);
  784. }
  785. function closeModal(id) {
  786. document.getElementById(id).classList.remove("show");
  787. if(id === 'channelModal') {
  788. editingChannelId = null;
  789. closeAllPanels();
  790. }
  791. }
  792. // ================= 视图切换 =================
  793. function switchView(view) {
  794. document.getElementById('menu-channel').classList.remove('active');
  795. document.getElementById('menu-campaign').classList.remove('active');
  796. document.getElementById('view-channel').classList.remove('active');
  797. document.getElementById('view-campaign').classList.remove('active');
  798. if(view === 'channel') {
  799. document.getElementById('menu-channel').classList.add('active');
  800. document.getElementById('view-channel').classList.add('active');
  801. document.getElementById('breadcrumb').innerHTML = '汇丰银行 <span>&gt;</span> 页面管理 <span>&gt;</span> Channel管理';
  802. updateSearchDropdowns();
  803. renderChannelTable();
  804. } else {
  805. document.getElementById('menu-campaign').classList.add('active');
  806. document.getElementById('view-campaign').classList.add('active');
  807. document.getElementById('breadcrumb').innerHTML = '汇丰银行 <span>&gt;</span> 页面管理 <span>&gt;</span> Campaign管理';
  808. renderCampaignTable();
  809. }
  810. }
  811. // ================= Channel 逻辑 =================
  812. const typeSelect = document.getElementById("channelType");
  813. const categorySelect = document.getElementById("channelCategory");
  814. const nameSelect = document.getElementById("channelName");
  815. const subNameInput = document.getElementById("subNameInput");
  816. const ownerInput = document.getElementById("ownerInput");
  817. function updateSearchDropdowns() {
  818. const types = Object.keys(channelDict);
  819. const selType = document.getElementById('s-ch-type');
  820. selType.innerHTML = '<option value="">全部</option>' + types.map(t => `<option value="${t}">${t}</option>`).join('');
  821. }
  822. function renderChannelTable() {
  823. const sId = document.getElementById('s-ch-id').value.trim().toLowerCase();
  824. const sType = document.getElementById('s-ch-type').value;
  825. const sCategory = document.getElementById('s-ch-category').value.trim().toLowerCase();
  826. const sName = document.getElementById('s-ch-name').value.trim().toLowerCase();
  827. const sSubname = document.getElementById('s-ch-subname').value.trim().toLowerCase();
  828. const sStaff = document.getElementById('s-ch-staff').value.trim().toLowerCase();
  829. const tbody = document.getElementById("channelTableBody");
  830. const filtered = channelRows.filter(c => {
  831. return (!sId || c.id.toLowerCase().includes(sId)) &&
  832. (!sType || c.type === sType) &&
  833. (!sCategory || c.category.toLowerCase().includes(sCategory)) &&
  834. (!sName || c.name.toLowerCase().includes(sName)) &&
  835. (!sSubname || c.subName.toLowerCase().includes(sSubname)) &&
  836. (!sStaff || c.owner.toLowerCase().includes(sStaff));
  837. });
  838. if (filtered.length === 0) {
  839. tbody.innerHTML = '<tr><td colspan="9" class="text-center empty-tip" style="height:100px;">暂无数据</td></tr>';
  840. return;
  841. }
  842. tbody.innerHTML = filtered.map(row => `
  843. <tr>
  844. <td class="action-links">
  845. <a class="edit" onclick="openChannelModal('edit', '${row.id}')">编辑</a>
  846. <a class="view" onclick="openChannelModal('view', '${row.id}')">查看</a>
  847. <a class="delete" onclick="confirmDelete('channel', '${row.id}')">删除</a>
  848. </td>
  849. <td>${row.id}</td>
  850. <td>${row.type}</td>
  851. <td>${row.category}</td>
  852. <td>${row.name}</td>
  853. <td class="${row.subName ? "" : "empty-tip"}">${row.subName || "-"}</td>
  854. <td class="${row.owner ? "" : "empty-tip"}">${row.owner || "-"}</td>
  855. <td>${row.createTime}</td>
  856. <td>${row.updateTime}</td>
  857. </tr>
  858. `).join("");
  859. }
  860. function fillSelect(select, placeholder, list, value = "") {
  861. select.innerHTML = `<option value="">${placeholder}</option>`;
  862. list.forEach(item => {
  863. const option = document.createElement("option");
  864. option.value = item;
  865. option.textContent = item;
  866. select.appendChild(option);
  867. });
  868. select.disabled = list.length === 0;
  869. select.value = value;
  870. }
  871. function refreshTypes(value = "") {
  872. fillSelect(typeSelect, "请选择类型", Object.keys(channelDict), value);
  873. }
  874. function refreshCategories(type, value = "") {
  875. if (!type || !channelDict[type]) {
  876. fillSelect(categorySelect, "请先选择类型", []);
  877. fillSelect(nameSelect, "请先选择类别", []);
  878. return;
  879. }
  880. fillSelect(categorySelect, "请选择类别", Object.keys(channelDict[type]), value);
  881. refreshNames(type, value, "");
  882. }
  883. function refreshNames(type, category, value = "") {
  884. if (!type || !category || !channelDict[type]?.[category]) {
  885. fillSelect(nameSelect, "请先选择类别", []);
  886. return;
  887. }
  888. fillSelect(nameSelect, "请选择名称", channelDict[type][category], value);
  889. }
  890. typeSelect.addEventListener("change", () => refreshCategories(typeSelect.value));
  891. categorySelect.addEventListener("change", () => refreshNames(typeSelect.value, categorySelect.value));
  892. function closeAllPanels() {
  893. document.querySelectorAll(".quick-panel").forEach(p => p.classList.remove("show"));
  894. document.querySelectorAll(".quick-panel input").forEach(i => i.value = "");
  895. }
  896. function openChannelModal(mode, id = null) {
  897. editingChannelId = id;
  898. closeAllPanels();
  899. subNameInput.value = "";
  900. ownerInput.value = "";
  901. document.querySelectorAll(".ch-btn").forEach(btn => btn.classList.remove("hide"));
  902. document.getElementById('modal-ch-footer').classList.remove("hide");
  903. typeSelect.disabled = false;
  904. subNameInput.disabled = false;
  905. ownerInput.disabled = false;
  906. if (mode === "edit" || mode === "view") {
  907. const row = channelRows.find(r => r.id === id);
  908. document.getElementById('modalTitle').innerText = mode === "edit" ? "编辑 Channel" : "查看 Channel";
  909. document.getElementById('chIdRow').classList.remove('hide');
  910. document.getElementById('f-ch-id').value = row.id;
  911. refreshTypes(row.type);
  912. refreshCategories(row.type, row.category);
  913. refreshNames(row.type, row.category, row.name);
  914. subNameInput.value = row.subName;
  915. ownerInput.value = row.owner;
  916. if(mode === "view") {
  917. document.querySelectorAll(".ch-btn").forEach(btn => btn.classList.add("hide"));
  918. document.getElementById('modal-ch-footer').classList.add("hide");
  919. typeSelect.disabled = true;
  920. categorySelect.disabled = true;
  921. nameSelect.disabled = true;
  922. subNameInput.disabled = true;
  923. ownerInput.disabled = true;
  924. }
  925. } else {
  926. document.getElementById('modalTitle').innerText = "新建 Channel";
  927. document.getElementById('chIdRow').classList.add('hide');
  928. refreshTypes();
  929. fillSelect(categorySelect, "请先选择类型", []);
  930. fillSelect(nameSelect, "请先选择类别", []);
  931. }
  932. document.getElementById("channelModal").classList.add("show");
  933. }
  934. function openPanel(target, mode) {
  935. const type = typeSelect.value;
  936. const category = categorySelect.value;
  937. if (target === "category" && !type) return showToast("请先选择 Channel 类型");
  938. if (target === "name" && (!type || !category)) return showToast("请先选择 Channel 类型和类别");
  939. if (mode === "edit") {
  940. if (target === "type" && !type) return showToast("请先选择要编辑的类型");
  941. if (target === "category" && !category) return showToast("请先选择要编辑的类别");
  942. if (target === "name" && !nameSelect.value) return showToast("请先选择要编辑的名称");
  943. }
  944. closeAllPanels();
  945. panelMode[target] = mode;
  946. const panel = document.getElementById(`${target}Panel`);
  947. const input = document.getElementById(`${target}Input`);
  948. input.value = mode === "edit" ? (target === "type" ? type : target === "category" ? category : nameSelect.value) : "";
  949. input.placeholder = mode === "edit" ? "请输入新的内容" : `请输入新的${target === 'type' ? '类型' : target === 'category' ? '类别' : '名称'}`;
  950. panel.classList.add("show");
  951. input.focus();
  952. }
  953. document.querySelectorAll(".ch-btn[data-target]").forEach(btn => {
  954. btn.addEventListener("click", () => openPanel(btn.dataset.target, btn.classList.contains("edit-current") ? "edit" : "add"));
  955. });
  956. function saveDict(target) {
  957. const type = typeSelect.value;
  958. const category = categorySelect.value;
  959. const name = nameSelect.value;
  960. const value = document.getElementById(`${target}Input`).value.trim();
  961. const mode = panelMode[target];
  962. if (!value) return showToast("请输入内容");
  963. if (target === "type") {
  964. if (mode === "add") {
  965. if (channelDict[value]) return showToast("该类型已存在");
  966. channelDict[value] = {};
  967. } else {
  968. if (value !== type && channelDict[value]) return showToast("该类型已存在");
  969. channelDict[value] = channelDict[type];
  970. delete channelDict[type];
  971. channelRows.forEach(row => { if (row.type === type) row.type = value; });
  972. }
  973. refreshTypes(value);
  974. refreshCategories(value);
  975. }
  976. if (target === "category") {
  977. if (mode === "add") {
  978. if (channelDict[type][value]) return showToast("该类别已存在");
  979. channelDict[type][value] = [];
  980. } else {
  981. if (value !== category && channelDict[type][value]) return showToast("该类别已存在");
  982. channelDict[type][value] = channelDict[type][category];
  983. delete channelDict[type][category];
  984. channelRows.forEach(row => { if (row.type === type && row.category === category) row.category = value; });
  985. }
  986. refreshCategories(type, value);
  987. }
  988. if (target === "name") {
  989. const list = channelDict[type][category];
  990. if (mode === "add") {
  991. if (list.includes(value)) return showToast("该名称已存在");
  992. list.push(value);
  993. } else {
  994. if (value !== name && list.includes(value)) return showToast("该名称已存在");
  995. const index = list.indexOf(name);
  996. if (index >= 0) list[index] = value;
  997. channelRows.forEach(row => { if (row.type === type && row.category === category && row.name === name) row.name = value; });
  998. }
  999. refreshNames(type, category, value);
  1000. }
  1001. closeAllPanels();
  1002. updateSearchDropdowns();
  1003. renderChannelTable();
  1004. showToast(mode === "add" ? "新增成功" : "编辑成功");
  1005. }
  1006. document.querySelectorAll("[data-save]").forEach(btn => btn.addEventListener("click", () => saveDict(btn.dataset.save)));
  1007. document.querySelectorAll("[data-cancel]").forEach(btn => btn.addEventListener("click", closeAllPanels));
  1008. document.getElementById("submitChannelBtn").addEventListener("click", () => {
  1009. const type = typeSelect.value;
  1010. const category = categorySelect.value;
  1011. const name = nameSelect.value;
  1012. if (!type || !category || !name) return showToast("请完整选择类型、类别和名称");
  1013. const now = getNowString();
  1014. if (editingChannelId !== null) {
  1015. const idx = channelRows.findIndex(r => r.id === editingChannelId);
  1016. channelRows[idx] = {
  1017. ...channelRows[idx],
  1018. type, category, name,
  1019. subName: subNameInput.value.trim(),
  1020. owner: ownerInput.value.trim(),
  1021. updateTime: now
  1022. };
  1023. showToast("保存成功");
  1024. } else {
  1025. const nextId = `CH${String(channelRows.length + 1).padStart(4, "0")}`;
  1026. channelRows.unshift({
  1027. id: nextId,
  1028. type, category, name,
  1029. subName: subNameInput.value.trim(),
  1030. owner: ownerInput.value.trim(),
  1031. createTime: now,
  1032. updateTime: now
  1033. });
  1034. showToast("新建成功");
  1035. }
  1036. renderChannelTable();
  1037. closeModal('channelModal');
  1038. });
  1039. // ================= Campaign 逻辑 =================
  1040. function renderCampaignTable() {
  1041. const sId = document.getElementById('s-cp-id').value.trim().toLowerCase();
  1042. const sName = document.getElementById('s-cp-name').value.trim().toLowerCase();
  1043. const sOwner = document.getElementById('s-cp-owner').value.trim().toLowerCase();
  1044. const sStaff = document.getElementById('s-cp-staff').value.trim().toLowerCase();
  1045. const tbody = document.getElementById('campaignTableBody');
  1046. const filtered = campaigns.filter(c => {
  1047. return (!sId || c.id.toLowerCase().includes(sId)) &&
  1048. (!sName || c.name.toLowerCase().includes(sName)) &&
  1049. (!sOwner || c.owner.toLowerCase().includes(sOwner)) &&
  1050. (!sStaff || c.staff.toLowerCase().includes(sStaff));
  1051. });
  1052. if (filtered.length === 0) {
  1053. tbody.innerHTML = '<tr><td colspan="7" class="text-center empty-tip" style="height:100px;">暂无数据</td></tr>';
  1054. return;
  1055. }
  1056. tbody.innerHTML = filtered.map(row => `
  1057. <tr>
  1058. <td class="action-links">
  1059. <a class="edit" onclick="openCampaignModal('edit', '${row.id}')">编辑</a>
  1060. <a class="view" onclick="openCampaignModal('view', '${row.id}')">查看</a>
  1061. <a class="delete" onclick="confirmDelete('campaign', '${row.id}')">删除</a>
  1062. </td>
  1063. <td>${row.id}</td>
  1064. <td>${row.name}</td>
  1065. <td>${row.owner}</td>
  1066. <td class="${row.staff ? "" : "empty-tip"}">${row.staff || "-"}</td>
  1067. <td>${row.createTime}</td>
  1068. <td>${row.updateTime}</td>
  1069. </tr>
  1070. `).join("");
  1071. }
  1072. function openCampaignModal(mode, id = null) {
  1073. editingCampaignId = id;
  1074. const nameInp = document.getElementById('f-cp-name');
  1075. const ownerInp = document.getElementById('f-cp-owner');
  1076. const staffInp = document.getElementById('f-cp-staff');
  1077. nameInp.disabled = false;
  1078. ownerInp.disabled = false;
  1079. staffInp.disabled = false;
  1080. document.getElementById('modal-cp-footer').classList.remove('hide');
  1081. if (mode === 'edit' || mode === 'view') {
  1082. const c = campaigns.find(x => x.id === id);
  1083. document.getElementById('modalCpTitle').innerText = mode === 'edit' ? '编辑 Campaign' : '查看 Campaign';
  1084. document.getElementById('cpIdRow').classList.remove('hide');
  1085. document.getElementById('f-cp-id').value = c.id;
  1086. nameInp.value = c.name;
  1087. ownerInp.value = c.owner;
  1088. staffInp.value = c.staff;
  1089. if (mode === 'view') {
  1090. nameInp.disabled = true;
  1091. ownerInp.disabled = true;
  1092. staffInp.disabled = true;
  1093. document.getElementById('modal-cp-footer').classList.add('hide');
  1094. }
  1095. } else {
  1096. document.getElementById('modalCpTitle').innerText = '新建 Campaign';
  1097. document.getElementById('cpIdRow').classList.add('hide');
  1098. nameInp.value = '';
  1099. ownerInp.value = '';
  1100. staffInp.value = '';
  1101. }
  1102. document.getElementById('campaignModal').classList.add('show');
  1103. }
  1104. function submitCampaignModal() {
  1105. const name = document.getElementById('f-cp-name').value.trim();
  1106. const owner = document.getElementById('f-cp-owner').value.trim();
  1107. const staff = document.getElementById('f-cp-staff').value.trim();
  1108. const now = getNowString();
  1109. if(!name || !owner) return showToast("名称和负责人是必填项");
  1110. if (editingCampaignId) {
  1111. const idx = campaigns.findIndex(x => x.id === editingCampaignId);
  1112. campaigns[idx] = { ...campaigns[idx], name, owner, staff, updateTime: now };
  1113. showToast("保存成功");
  1114. } else {
  1115. const newId = `CP${String(campaigns.length + 1).padStart(4, "0")}`;
  1116. campaigns.unshift({ id: newId, name, owner, staff, createTime: now, updateTime: now });
  1117. showToast("新建成功");
  1118. }
  1119. closeModal('campaignModal');
  1120. renderCampaignTable();
  1121. }
  1122. // ================= 通用功能 =================
  1123. function resetSearch(view) {
  1124. if (view === 'channel') {
  1125. document.getElementById('s-ch-id').value = '';
  1126. document.getElementById('s-ch-type').value = '';
  1127. document.getElementById('s-ch-category').value = '';
  1128. document.getElementById('s-ch-name').value = '';
  1129. document.getElementById('s-ch-subname').value = '';
  1130. document.getElementById('s-ch-staff').value = '';
  1131. renderChannelTable();
  1132. } else {
  1133. document.getElementById('s-cp-id').value = '';
  1134. document.getElementById('s-cp-name').value = '';
  1135. document.getElementById('s-cp-owner').value = '';
  1136. document.getElementById('s-cp-staff').value = '';
  1137. renderCampaignTable();
  1138. }
  1139. }
  1140. function confirmDelete(type, id) {
  1141. deleteTarget = { type, id };
  1142. document.getElementById('confirmModal').classList.add('show');
  1143. }
  1144. document.getElementById('btn-confirm-delete').onclick = function() {
  1145. if (deleteTarget.type === 'channel') {
  1146. channelRows = channelRows.filter(c => c.id !== deleteTarget.id);
  1147. renderChannelTable();
  1148. } else {
  1149. campaigns = campaigns.filter(c => c.id !== deleteTarget.id);
  1150. renderCampaignTable();
  1151. }
  1152. closeModal('confirmModal');
  1153. showToast("删除成功");
  1154. }
  1155. // 初始化
  1156. window.onload = function() {
  1157. switchView('channel');
  1158. };
  1159. </script>
  1160. </body>
  1161. </html>