n/gi, '')
+ .replace(/<\/tr>n/gi, '')
+ .replace(/<\/span>n/gi, '');
}
function prepareEditorHtml(html) {
- return prepareWordHtml(html);
+ var content = String(html || '');
+ if (!content) return '';
+ if (isWordFormattedDoc(content)) {
+ content = sanitizeWordExportHtml(content);
+ content = ensureV266WordStyles(content);
+ content = fixWordPreviewHtml(content);
+ }
+ return content;
}
function ensureV266WordStyles(html) {
@@ -555,9 +640,9 @@ const Component = function () {
var DOCUMENT_SECTION_TABS = [
{ key: 'main', label: '合同主体' },
- { key: 'lease_order', label: '租赁订单', markers: ['1:租赁订单 ', '附件 1:租赁订单', '附件1:租赁订单', '附件 1:', '附件1:', '附件 1', '附件1'] },
- { key: 'vehicle_cost', label: '车辆产生的费用', markers: ['附件2:车辆产生的费用', '附件2:车辆产生的费用', '附件2:', '附件2:', '附件2'] },
- { key: 'safety_notice', label: '安全责任告知书', markers: ['附件3:安全责任告知书', '附件3:安全责任告知书', '附件3:', '附件3:', '附件3'] },
+ { key: 'lease_order', label: '租赁订单', markers: ['1:租赁订单 ', '附件 1:租赁订单', '附件1:租赁订单'] },
+ { key: 'vehicle_cost', label: '车辆产生的费用', markers: ['附件2:车辆产生的费用', '附件2:车辆产生的费用', '附件2:车辆产生的费用'] },
+ { key: 'safety_notice', label: '安全责任告知书', markers: ['附件3:安全责任告知书', '附件3:安全责任告知书', '附件3:安全责任告知书'] },
{ key: 'dynamic_supervision', label: '重型普通货运车辆租赁动态监管责任告知与确认书', markers: ['附件4:重型普通货运车辆租赁动态监管责任告知与确认书', '附件4:'] },
{ key: 'safety_commitment', label: '安全责任承诺书', markers: ['附件5:', '安全责任承诺书为严格落实', '附件5:', '附件5:'] },
{ key: 'vehicle_spec', label: '车辆使用规范和驾驶员要求', markers: ['附件6:车辆使用规范和驾驶员要求', '附件6:车辆使用规范和驾驶员要求', '附件6:'] },
@@ -565,17 +650,42 @@ const Component = function () {
{ key: 'safety_agreement', label: '安全责任协议', markers: ['data-risk-redline="1">安全责任协议
', '安全责任协议
甲方(出租方):', '安全责任协议
'] }
];
var SECTION_VISIBLE_TAB_COUNT = 4;
+ var LIST_EXPAND_COL_WIDTH = 48;
+ var LIST_TABLE_SCROLL_X = LIST_EXPAND_COL_WIDTH + 280 + 96 + 88 + 150 + 110 + 150 + 110 + 200;
function escapeAttr(value) {
return String(value || '').replace(/&/g, '&').replace(/"/g, '"');
}
+ function isValidSectionMarkerMatch(source, index, marker) {
+ if (index < 0) return false;
+ var text = String(source || '');
+ var before = text.slice(Math.max(0, index - 8), index);
+ var after = text.slice(index, index + String(marker || '').length + 12);
+ if (/[((]$/.test(before)) return false;
+ if (/、$/.test(before)) return false;
+ if (/^《/.test(after.slice(String(marker).length))) return false;
+ var nearby = text.slice(Math.max(0, index - 24), index + String(marker).length + 24);
+ if (/详见.{0,20}附件/.test(nearby)) return false;
+ if (/以附件/.test(nearby)) return false;
+ if (String(marker).length <= 10 && !//.test(nearby)) return false;
+ return true;
+ }
+
function findFirstMarker(source, markers) {
var text = String(source || '');
var best = -1;
(markers || []).forEach(function (marker) {
- var idx = text.indexOf(marker);
- if (idx >= 0 && (best < 0 || idx < best)) best = idx;
+ var from = 0;
+ while (from < text.length) {
+ var idx = text.indexOf(marker, from);
+ if (idx < 0) break;
+ if (isValidSectionMarkerMatch(text, idx, marker)) {
+ if (best < 0 || idx < best) best = idx;
+ break;
+ }
+ from = idx + 1;
+ }
});
return best;
}
@@ -586,8 +696,47 @@ const Component = function () {
return map;
}
- function splitMonolithicHtml(html) {
- var source = String(html || '');
+ function isWordBlockNode(node) {
+ if (!node) return false;
+ if (node.nodeType === 3) return !!String(node.textContent || '').trim();
+ return node.nodeType === 1 && node.nodeName !== 'STYLE';
+ }
+
+ function detectSectionStartKey(node) {
+ if (!node || node.nodeType !== 1) return null;
+ var html = node.outerHTML || '';
+ if (!html) return null;
+ for (var t = 1; t < DOCUMENT_SECTION_TABS.length; t++) {
+ var tab = DOCUMENT_SECTION_TABS[t];
+ var markers = tab.markers || [tab.label];
+ for (var m = 0; m < markers.length; m++) {
+ var marker = markers[m];
+ var idx = html.indexOf(marker);
+ if (idx >= 0 && isValidSectionMarkerMatch(html, idx, marker)) {
+ return tab.key;
+ }
+ }
+ }
+ return null;
+ }
+
+ function findSafeHtmlSplitIndex(source, index) {
+ if (index <= 0) return 0;
+ var before = String(source || '').slice(0, index);
+ var tags = ['', '
', ''];
+ var best = -1;
+ tags.forEach(function (tag) {
+ var idx = before.lastIndexOf(tag);
+ if (idx >= 0) {
+ var end = idx + tag.length;
+ if (end > best) best = end;
+ }
+ });
+ return best > 0 ? best : index;
+ }
+
+ function splitMonolithicHtmlByString(html) {
+ var source = sanitizeWordExportHtml(html);
var result = emptySectionMap();
if (!source.trim()) return result;
var splitPoints = [];
@@ -600,15 +749,58 @@ const Component = function () {
splitPoints.forEach(function (point) {
if (!deduped.length || deduped[deduped.length - 1].index !== point.index) deduped.push(point);
});
- result.main = deduped.length ? source.slice(0, deduped[0].index) : source;
+ result.main = deduped.length ? source.slice(0, findSafeHtmlSplitIndex(source, deduped[0].index)) : source;
for (var i = 0; i < deduped.length; i++) {
var start = deduped[i].index;
- var end = i + 1 < deduped.length ? deduped[i + 1].index : source.length;
+ var end = i + 1 < deduped.length ? findSafeHtmlSplitIndex(source, deduped[i + 1].index) : source.length;
result[deduped[i].key] = source.slice(start, end);
}
return result;
}
+ function splitMonolithicHtmlByDom(html) {
+ var source = sanitizeWordExportHtml(html);
+ var result = emptySectionMap();
+ if (!source.trim()) return result;
+ ensureV266WordStyles(source);
+ var div = document.createElement('div');
+ div.innerHTML = source.replace(/商用车试用协议
\n协议签订地:【浙江省嘉兴市平湖区乍浦镇】
\n协议编号:【LNZLHT 】
\n
\n甲方(车辆提供方):{{lessorName}}
\n送达地址: \t
\n联系人及联系电话:
\n电子邮箱:
\n
\n乙方(车辆使用方):{{customerName}}
\n送达地址:
\n联系人及联系电话:
\n电子邮箱:
\n
\n本协议经双方平等自愿协商,根据《中华人民共和国民法典》及相关法规,就氢燃料电池车的试用及租赁事宜,达成如下一致条款,以资共同遵守。
\n第一条 试用车辆、期限
\n\n\n\n| \n 品牌及车型 \n | \n\n 车牌号 \n | \n\n 保证金(元/辆) \n | \n\n 试用期间 \n租金(元/月/辆) \n | \n\n 试用期限 \n | \n\n 试用期后未及时还车租金(元/日/辆) \n | \n
\n\n| \n \n | \n\n \n | \n\n \n | \n\n \n | \n\n \n | \n\n \n | \n
\n\n| \n
\n | \n\n
\n | \n\n
\n | \n\n
\n | \n\n
\n | \n\n
\n | \n
\n\n
\n
\n试用期限届满后,若乙方仍需使用该车辆,在车辆实际退还甲方之日前,本协议的全部约定对双方持续有效,双方均应按照本协议约定全面履行各自义务(包括但不限于租金支付、车辆保管、维保责任等)。
\n1.1车辆交付地点: ,车辆交付时间: 年 月 日(若实际交车时间与本协议约定的交车时间不一致的,以实际交车时间为准),试用期起算以实际交车时间为准。
\n1.2试用车辆仅限 行政区域范围内使用,超出前述约定区域范围的,应事先征得甲方书面同意。
\n1.3 试用期内,甲方向乙方提供如下服务:
\n1.3.1 跟进试用车辆全过程;
\n1.3.2 组织车辆操作的培训、咨询解答、现场支持等;
\n1.3.3 提供试用过程中遇到问题和故障的实时响应;
\n1.3.4 提供氢能相关政策解读等;
\n1.3.5 车辆试用结果的整理和汇报;
\n1.3.6 车辆的“机动车交通事故责任强制险”、“机动车商业保险(含三者险和车损险)”由甲方购买。
\n第二条 费用与支付
\n2.1 租金先付后用:试用期内租金,乙方提车前2个工作日向甲方支付租金。超过约定试用期未归还车辆,也未签订正式《商用车租赁合同》的,按前述表格记载的日租金收取。
\n2.2 本协议签署后提车前2个工作日,乙方应向甲方支付保证金。
\n2.3 试用期间,氢气费及与氢气费相关的服务费(如有)由乙方承担,若需要甲方代付的,乙方应于提车前2个工作日内向甲方预交氢气费人民币 元;预付款余额低于一次加气费时,甲方通知乙方,乙方接到通知后1个工作日内,按甲方的要求预交氢气费;否则,甲方有权不予安排车辆加氢,由此所造成的一切损失由乙方自行承担,给甲方造成损失的,乙方还应承担相应的赔偿责任。
\n2.4 试用期结束后,乙方还车时车里剩余氢气量若低于交车时的氢气量,则按照 元/公斤向甲方支付氢气费,若高于交车时的氢气量,则乙方同意甲方不向乙方返还氢气费。
\n2.5 所有费用汇入甲方如下指定账户,否则视为未支付:
\n 户 名:【 】
\n开户行:【 】
\n账 号:【 】
\n2.6 如根据本协议约定,乙方应承担的用车费用未付的、应向甲方承担赔偿、违约金或其他责任时,甲方有权从保证金中扣除。保证金不足以抵扣的,乙方仍应支付或赔偿给甲方。
\n2.7 试用期届满后,乙方拒绝承租试用车辆的,乙方交还试用车辆后,双方在30日内对账结算,确认无误后,将剩余的保证金无息退还给乙方。
\n2.8 碳资产
\n本合同约定仅限于车辆使用,该车辆所产生的碳资产数据权益和碳资产收益归甲方所有,本合同报价不含减碳、碳资产管理费用等。
\n第三条 试用车辆的风险与管理
\n3.1 乙方负责提供氢燃料电池车试用场地,且在车辆运抵前确保场地满足车辆运行要求。
\n3.2 车辆自交付乙方之时起,车辆发生损毁、灭失、被盗、遗失等风险的,风险责任由乙方承担,乙方应赔偿甲方全部损失,但该损失是因车辆自身质量问题导致的除外。
\n3.3 车辆自交付乙方之时起,乙方负责车辆相关安全保管义务和日常管理,乙方使用车辆应符合国家、地方的相关法律法规、规章制度,遵守交通规则,禁止超速、超载,禁止装载易燃易爆、易腐蚀等危险物品及会在车中留下刺激性气味的物品。
\n3.4 乙方应为车辆配备合格的专职驾驶员,驾驶员必须参加甲方组织的培训,按照操作规范驾驶车辆。
\n3.5 车辆在试用期间产生的施救费、路桥费、人工费以及车辆发生损毁、灭失、出现故障和车辆事故以及乙方人员的人身损害、第三人财产损失和人身伤亡等由乙方承担,因此给甲方造成损失的,乙方应予赔偿,如进保险,乙方根据不同车型,需向甲方承担保险上浮费,18吨车型主责及以上责任为3000元/次/辆,其他责任为2000元/次/辆(因车辆本身质量问题发生的损毁、灭失除外);若保险实际上浮费超过前述费用的,以实际发生的保险上浮费为准。
\n3.6 试用期间,车辆损坏需要维修和保养的,乙方必须在甲方指定维修站对车辆进行维修、保养,将车辆修复至提车时的状态,费用由乙方全额承担,并在 3 个工作日内通知甲方,将维修、保养相关单据的原件交回甲方留存。如在指定维修站以外维修、保养车辆的,应征得甲方书面同意,否则属于乙方违约,乙方应按 2000 元/次的标准向甲方支付违约金。
\n3.7 汽车车上人员责任保险(驾驶员、车上其他人员)”由乙方购买:乙方应为使用该车辆的驾驶员及车上其他人员购买责任保险,车辆使用期间所发生的与乙方驾驶员及车上其他人员相关的一切财产、人身损失等由乙方承担,与甲方无任何关系,若给甲方造成损失的,乙方需向甲方承担赔偿责任。
\n3.8 试用期间,车辆若发生违章的,乙方必须在违章发生之日起15日内处理完毕,违章产生的费用由乙方承担,若未在此时间内处理完毕的,乙方除需承担实际罚款金额,还需向甲方承担代办费。同时,甲方有权按一条违章信息1200元的标准(广东区域内为2000元/条),向乙方收取违约金;违约金金额不足以覆盖甲方损失时,乙方还应承担损失赔偿责任。所有费用,甲方有权从保证金中直接扣除。
\n3.9 试用期间,乙方必须保持车辆原状,不得擅自更改车身外观、增加车身广告或改变车身颜色等,否则必须恢复车辆原状,同时向甲方承担 5000元/辆的修复费用,甲方有权从保证金中直接扣除。
\n第四条 试用车辆交还
\n4.1 乙方拒绝承租试用车辆的,最迟应在试用期限届满前3个工作日内通知甲方,双方约定还车的时间和还车地点,乙方负责将试用车辆送至约定的还车地点。
\n4.2 乙方还车时,应携带提车时签订的《车辆交接单》,经甲方检查车辆确认无误后,双方签署《车辆交接单》,确认还车。
\n4.3返还车辆的车容车貌、技术状况应符合国家、地方的有关标准和规定,除正常使用耗损外,应与公安机关登记信息、《车辆交接单》保持一致,无任何毁损,证照齐全,无未处理交通违章、交通事故等不存在未决纠纷争议可能影响甲方利益情形。否则,甲方有权拒绝接收车辆,乙方应在限期内整改,整改期内日租金按两倍日租金标准继续计算,直至车辆符合交还标准;如乙方拒绝或超过十五天仍未完成整改的,甲方有权强制收回车辆并送至修理厂进行修复或调整,因此产生的运输费、修理费等费用以及处理交通违章、事故等发生的一切费用,均由乙方全额承担。
\n第五条 试用车辆的承租
\n5.1 试用期限内,乙方确认承租的,应及时通知甲方,双方签订正式的《商用车租赁合同》,确认租金起算时间、租金费率等相关条款。
\n5.2 乙方确认承租的,双方不再办理试用车辆的交还手续,乙方继续使用该车辆,双方按照《商用车租赁合同》的条款履行,本协议项下的款项结清,权责了结后,本协议自动终止。
\n第六条 违约责任
\n6.1 本协议项下乙方应支付的费用及应承担的赔偿责任,乙方应及时支付。每逾期一天,应按逾期金额的千分之一向甲方支付逾期付款违约金,同时仍应履行付款义务。
\n6.2 乙方出现下列情形之一的,甲方有权立即解除或提前终止本协议并强制收回车辆,且乙方应向甲方支付违约金,违约金标准为前述表格中记载的日租金标准的2倍向甲方支付违约金:
\n6.2.1利用标的车辆从事违法犯罪活动的,或因涉嫌违法犯罪被采取强制措施或受到行政处罚的;
\n6.2.2未按期支付本协议项下的相应费用超过3个工作日的,经甲方催告后仍不支付的;
\n6.2.3涉酒涉毒驾车、交通事故逃逸、不及时抢救伤员保护现场、谎报或瞒报交通事故的;
\n6.2.4擅自将标的车辆转让、转租、转包,或设置抵押、留置、质押等负担,或恶意更换车辆零部件或拆撤、损毁、涂抹车辆设施、设备、标记、广告的;
\n6.2.5标的车辆技术状况经甲方累计3次检查不合格的,或经甲方检查不合格要求乙方修复,乙方在3日内拒不修复的;
\n6.2.6向甲方提供虚假证件或资料的;
\n6.2.7发生3次有责交通事故或单次有重大、特大交通事故的;
\n6.2.8利用标的车辆进行相关法律法规明令禁止的行为,或其他严重影响甲方权益或可能导致甲方损失的行为;
\n6.2.9乙方有违反本协议约定导致甲方解除或提前终止本协议的其他情形的。
\n6.3 任何一方有其他违反本协议情形的,应赔偿守约方全部损失。本协议中的全部损失包括但不限于对守约方所造成的直接损失、可得利益损失、守约方支付给第三方的赔偿费用/违约金/罚款/调查取证费用/公证费/鉴定费/诉讼仲裁费/保全费/保全保险费/律师费/维权费用以及其他合理费用。
\n第七条 争议的解决
\n7.1 凡因本协议引起的或与本协议有关的任何争议,由甲、乙双方友好协商解决。协商无法解决的,向协议签订地人民法院提起诉讼。
\n第八条 其他
\n8.1甲乙双方承诺保守商业机密和商业诚信,除双方已公开或披露信息外,未经双方事前书面同意,不得将机密信息泄露给其他方。
\n8.2关于送达:任何与本协议有关的由一方发送给其他方的通知或其他通讯往来(“通知”)应采用书面形式,按照本协议开头记载的通讯地址或通讯号码或电子邮箱送达被通知人均构成有效形式之通知,包括但不限于法院、仲裁机构的法律文书及双方的来往函件等。
\n8.3本协议所附附件是协议内容的一部分,与本协议具有同等法律效力,若附件内容与本协议内容不一致的,以附件约定的内容为准。
\n8.4本协议由双方法定代表人或授权代表签字并加盖公章之日起生效。
\n8.5本协议一式两份,甲乙双方各执壹份,具有同等法律效力。
\n8.6本协议未尽事宜,由甲乙双方另行友好协商,签订补充协议。
\n
\n---协议正文结束---
\n
\n以下为签署处:
\n
\n
\n甲方: (盖章) 乙方: (盖章)
\n
\n法定代表人(或授权代表)签名: 法定代表人(或授权代表)签名:
\n
\n签署日期: 年 月 日 签署日期: 年 月 日
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n附件1:安全责任告知书
\n一、各车辆使用人责任主体:
\n1、各租赁客户合作伙伴、挂靠我司车辆的用户,当您使用我司车辆期间必须承担安全责任,责任主体是:车辆使用人(公司或个人),挂靠车辆使用人(公司或个人)。
\n2、根据《中华人民共和国民法典》第一千二百零九条,因租赁、借用等情形机动车所有人、管理人与使用人不是同一人时,发生交通事故等造成损害,属于该机动车一方责任的,由机动车承租人和使用人承担该赔偿责任。
\n3、租赁车辆、挂靠车辆使用人,平时要做好出车检查,制订车辆日常检查记录表,对于:氢系统、电池、灯光、轮胎、车辆外观、随车配置物品等做好日常检查并记录,如有异常必须及时处理,车辆合格满足出车条件以后才能出车,并且必须保持车辆定位系统在线。
\n4、车辆、挂靠车辆使用人,要做好我司车辆的安全、事故台账,出事故必须登记在台账中,并且开展四不放过教育:①事故原因没有查清不放过,②事故责任者没有严肃处理不放过,③全体司机及车队管理人员没受到教育不放过,④防范措施没有落实不放过。
\n开展教育必须要有现场多张照片,所有司机及车队管理人员在教育签到表上签字,照片及签到表原件留档,我司安全部备查。
\n二、车辆使用安全注意事项:
\n1、所有驾驶员要有从业资格证,9.6米厢车与4.2米冷藏车,必须出示本人资格证,登记后才可以提车。
\n2、注意车辆高度,苏龙9.6米车高4米。跃进4.2米车高3.3米。集卡飞驰车头3.9米,挂车装载平箱4.2米左右。驾驶员一定注意车厢高度,当心限高杆、桥梁、树枝等物。
\n3、因为是氢用车,车内不得吸烟,车周边10米以内不得吸烟,车辆远离烟火(周边10米以上),不得将车停放在高压电线下。
\n4、进加氢站,必须无烟火,关闭总电源后加氢。服从加氢站管理,如:静电接地,不超35Mpa压力等等。
\n5、车辆遇到氢气泄露,不严重移至空旷处,关闭总电源!车辆旁边不得有闲杂人等,远离明火,联系运维人员:江浙沪沈帅15021428914;新疆赵波18639103977;广东邱陈佳17666025673;集卡车李泽琦13383480807;北京及其他地方赵小峰18868302409。
\n6、系统漏气并着火,火焰未烧至其他管路和车身;处理方式:立即熄火,关闭电源,马上灭火,立即打上述人员电话,在他们指导下灭火,以防电池等爆炸。如果火势大、控制难、立即拨打119,人员撤离至安全区域。
\n7、供氢系统漏气着火,或电池着火,火焰烧至其他管路或车身;处理方式:立即拨打119,人员撤离至安全区域。
\n8、车辆每日检查:轮胎完好、气压标准、灯光设施齐全、车辆不带病上路。
\n9、雨雾天气,降低速度。注意能见度,与前车保持足够安全距离。
\n10、行驶中电池高温冒烟,马上靠边,断开高压系统,马上联系运维。
\n11、车辆发生火情立即停车,用灭火器降温灭火,关电源,火势大要保证人身安全,离开车辆打119报警。
\n12、氢能车辆禁止电焊,周围不许任何焊接,此类维修通知专业人员来修理。
\n13、出交通事故,第一时间打安全经理王屹青电话:18116198188,告知实际情况,加此电话微信,拍现场照片:整体事故场景、我方车牌、对方车牌、我方及对方车辆受损部位。并发定位,安全经理会指导处理事故。\t
\n贵方上述安全告知已收到并熟悉,我方(被告知人)郑重承诺:无条件遵守上述安全操作事项。若因我方(被告知人)原因导致发生安全事故,我方(被告知人)自行承担一切法律责任。
\n
\n被告知人盖章:
\n日期: 年 月 日
\n
\n
\n
\n附件2:重型普通货运车辆租赁动态监管责任告知与确认书
\n为严格落实《中华人民共和国道路交通安全法》、《道路货物运输及站场管理规定》等相关法律法规及行业监管要求,规范重型普通货运车辆租赁期间的安全运营行为,强化驾驶员安全管控,防范道路交通安全风险,保障租赁车辆、人员及公共安全,现就甲方租赁给乙方车辆的动态监管违规行为及处置事宜,正式告知如下:
\n甲方将对租赁车辆实施24小时动态监管,重点监测驾驶员存在的超速行驶、超时驾驶(持续时间≥4小时)、接打手持电话、操作手机、抽烟、生理疲劳驾驶、未按规定系安全带、偏离驾驶位、佩戴红外阻断型墨镜遮挡视线、动态监控设备被遮挡或失效、双手脱离方向盘、不目视前方行驶、实线变道、前向碰撞、车道偏离、前向车距过近等各类违反道路交通安全法律法规及双方签订的《商用车租赁合同》约定的违规行为。本确认书所载违规行为的认定,以甲方提供的、由车辆合法装载的动态监控设备所记录的数据、影像为准。若乙方对相关数据、影像记录的违规事实有异议,应在收到甲方通知后48小时内书面提出,并提出理由。乙方驾驶员的上述违规行为,不仅会给乙方自身运营带来重大安全隐患与法律责任风险,还将对甲方造成合规、运营等多方面的不利影响及实际损失,具体如下:
\n\n- 一、合规经营风险:
\n- 甲方作为重型普通货运车辆出租主体,依法承担租赁车辆的动态监管主体责任,若乙方驾驶员的违规行为被交通运输、公安交管等监管部门查处,甲方将面临行政处罚、行业资质核查、信用评级下调等合规风险,情节严重的,将直接影响甲方道路运输相关业务的正常开展。若租赁车辆因乙方原因(包含但不限于乙方驾驶员屡次、严重违规或乙方安全管理缺失)导致租赁车辆被交通主管部门依法做出撤销/吊销/注销其道路运输证,导致车辆无法运营,相关损失由乙方承担。
\n- 二、运营资源消耗:
\n- 针对乙方驾驶员的违规行为,甲方需额外投入大量人力、物力开展违规核实、沟通对接、整改监督等工作,占用甲方动态监管、车辆维护、客户服务等核心运营资源,大幅降低整体租赁业务运营效率,严重影响甲方对其他合规客户的服务质量,造成甲方运营成本增加。
\n
\n针对监测发现的驾驶员违规行为,结合上述违规行为对甲方造成的各类影响与损失,甲方将采取分级处置措施,具体如下:
\n1、首次违规处置:自甲方首次向乙方反馈驾驶员违规行为之日起,乙方需在24小时内组织相关负责人对该驾驶员进行正式约谈,明确违规行为的危害、整改要求及后续纪律,留存约谈记录(含约谈人员、时间、内容、签字、约谈照片等),并将约谈记录原件及相关佐证材料提交甲方备案。若乙方未按要求完成整改,甲方将立即暂停该车辆相关服务,直至乙方完全落实约谈及备案要求,暂停服务期间车辆租金照常结算,且由此导致甲方产生的合理直接损失,均由乙方承担。
\n2、二次违规处置:若同一驾驶员在首次违规约谈备案后,再次出现任何违规行为,乙方须立即停止该驾驶员驾驶租赁车辆的资格,组织其参加安全警示教育学习(学习内容含交通安全法规、重型货运车辆安全操作规范、动态监管要求等),学习完成后,乙方需联合甲方对该驾驶员进行面谈教育,确认其整改意愿、整改措施,双方共同签订《安全警示教育确认书》。驾驶员经学习、面谈合格后,方可重新上岗驾驶租赁车辆;未合格的,不得安排其驾驶甲方租赁车辆。若乙方未按甲方要求完成整改,甲方将立即暂停该车辆相关服务,暂停服务期间车辆租金照常结算,且由此导致甲方产生的合理直接损失,均由乙方承担。
\n3、三次违规处置:若同一驾驶员在二次违规学习、面谈教育后,第三次出现任何违规行为,乙方在接到甲方通知后48小时内,应更换该租赁车辆的驾驶员。新驾驶员须具备合法有效的从业资格证书、无重大交通安全违规记录、无重大交通事故责任记录,且乙方需将新驾驶员的身份证、从业资格证、无违规记录证明等相关资质材料提交甲方审核备案,审核通过后方可安排其上岗。若乙方未按要求按时更换驾驶员或新驾驶员资质审核未通过,甲方将暂停该车辆相关服务。
\n4、特殊处置(强制收回车辆):若同一驾驶员出现第三次违规后,乙方未按甲方要求及时更换驾驶员,仍继续安排该驾驶员驾驶租赁车辆,视为乙方构成严重违约,甲方有权单方解除双方之间签订的《商用车租赁合同》。合同解除后,乙方除需按照《商用车租赁合同》约定向甲方支付违约金外,另甲方有权立即启动车辆强制收回程序,无条件收回租赁车辆,且不承担任何违约责任。车辆强制收回过程中产生的拖车费、保管费、运输费、场地占用费等一切相关费用,均由乙方承担,甲方有权直接从租赁保证金中扣除,不足部分有权向乙方追偿;同时,甲方将依据《商用车租赁合同》约定,追究乙方的违约责任,由此给甲方造成的一切损失(含但不限于行政罚款、赔偿金、律师费、诉讼费、保全费、差旅费等),均由乙方全额承担,甲方有权从租赁保证金中直接抵扣,不足部分有权向乙方追偿。
\n乙方需高度重视租赁车辆驾驶员的安全管控工作,切实履行承租方安全管理主体责任,加强对驾驶员的日常安全教育、培训及常态化管理,严格落实本确认书约定的分级处置措施,积极配合甲方的动态监管工作,及时整改各类违规行为。甲方将定期向乙方反馈车辆动态监管情况,对乙方未按本确认书要求落实处置措施、整改不到位或者拒不配合监管的行为,甲方将严格按照法律法规及双方签订的《租赁合同》约定处理。
\n本确认书自甲乙双方签字或盖章后生效,本确认书作为《商用车租赁合同》的附件,与《商用车租赁合同》具有同等法律效力。本确认书未尽事宜,按照双方签订的《商用车租赁合同》相关约定执行;若本确认书内容与相关法律法规冲突,按法律法规规定执行。
\n乙方确认:已认真阅读并充分全面理解本确认书全部内容,自愿严格遵照本确认书约定及相关法律法规要求执行,并承担因违反确认书约定及相关规定产生的全部责任。
\n(以下无正文)
\n
\n
\n
\n确认人盖章:
\n日期: 年 月 日
\n
\n
\n
\n
\n
\n
\n附件3:
\n安全责任承诺书
\n为严格落实《道路运输车辆动态监督管理办法》、《广东省道路运输条例》及粤标动态监控管理要求,防范因超速、疲劳驾驶、接打电话、抽烟、遮挡摄像头、屏蔽设备、离线等违规行为被交通运输主管部门连续通报,避免因此导致车辆被责令停运、营运证被暂扣或注销等严重后果,并就车辆粤标动态监控相关安全责任,作出如下郑重承诺:
\n1、严格遵守《道路运输车辆动态监督管理办法》、《广东省道路运输车辆智能监管系统应用管理办法》等国家及地方性法规,并已阅读、理解且承诺遵守贵公司提供的《粤标动态监控专项安全管理方案》(下称“管理方案”)相关规定,服从贵公司依据该方案及相关法规实施的动态监控管理。
\n2、负责督促我司承租司机规范驾驶,杜绝疲劳驾驶、超速、分心驾驶等违规行为,严禁破坏、屏蔽、断电、拆机等影响粤标监控设备正常运行的行为。
\n3、及时接收贵公司反馈的车辆违规信息,严格按照要求落实整改,配合贵公司开展司机安全教育、车辆停运等处置措施。
\n4、若因我司及我司承租司机违规行为,导致车辆被交管、运政部门通报、处罚或贵公司遭受其他损失的,我方同意承担由此产生的全部直接经济损失,包括但不限于政府部门处以的罚款、贵公司为处理此事支出的合理费用(如差旅费、律师费)以及车辆被责令停运期间的租金损失(按《商用车租赁合同》(下称”主合同“)约定标准计算)。
\n5、我司知悉,若因连续违规被主管部门通报3次及以上,导致贵公司营运证被暂扣或注销等严重后果的,自愿承担全部赔偿责任(包括但不限于车辆价值贬损、营运损失等)。同时,贵公司有权单方解除《商用车租赁合同》,且有权在5年内拒绝与我司及关联方开展任何商业合作。
\n6、本承诺书自签订之日起生效,有效期至《商用车租赁合同》约定租赁期满之日止。
\n7、本承诺书作为《商用车租赁合同》的有效附件,与主合同具有同等法律效力。如本承诺书内容与主合同内容约定不一致,则以更严格约束我司义务的条款为准。
\n(以下无正文)
\n
\n承诺人(盖章):
\n签订日期: 年 月 日
\n
\n
\n
\n
\n
\n附件4:乙方营业执照和法定代表人身份证复印件、道路运输经营许可证等
\n
\n
\n
\n";
diff --git a/src/prototypes/contract-template-management/trial-agreement-document.js b/src/prototypes/contract-template-management/trial-agreement-document.js
new file mode 100644
index 0000000..22ed96c
--- /dev/null
+++ b/src/prototypes/contract-template-management/trial-agreement-document.js
@@ -0,0 +1,2 @@
+// AUTO-GENERATED from 商用车试用协议.docx — do not edit by hand
+export var TRIAL_AGREEMENT_DOCUMENT_HTML = "商用车试用协议
\n
协议签订地:【浙江省嘉兴市平湖区乍浦镇】
\n
协议编号:【LNZLHT 】
\n
\n
甲方(车辆提供方):{{lessorName}}
\n
送达地址: \t
\n
联系人及联系电话:
\n
电子邮箱:
\n
\n
乙方(车辆使用方):{{customerName}}
\n
送达地址:
\n
联系人及联系电话:
\n
电子邮箱:
\n
\n
本协议经双方平等自愿协商,根据《中华人民共和国民法典》及相关法规,就氢燃料电池车的试用及租赁事宜,达成如下一致条款,以资共同遵守。
\n
第一条 试用车辆、期限
\n
\n\n\n| \n 品牌及车型 \n | \n\n 车牌号 \n | \n\n 保证金(元/辆) \n | \n\n 试用期间 \n租金(元/月/辆) \n | \n\n 试用期限 \n | \n\n 试用期后未及时还车租金(元/日/辆) \n | \n
\n\n| \n \n | \n\n \n | \n\n \n | \n\n \n | \n\n \n | \n\n \n | \n
\n\n| \n
\n | \n\n
\n | \n\n
\n | \n\n
\n | \n\n
\n | \n\n
\n | \n
\n\n
\n
\n
试用期限届满后,若乙方仍需使用该车辆,在车辆实际退还甲方之日前,本协议的全部约定对双方持续有效,双方均应按照本协议约定全面履行各自义务(包括但不限于租金支付、车辆保管、维保责任等)。
\n
1.1车辆交付地点: ,车辆交付时间: 年 月 日(若实际交车时间与本协议约定的交车时间不一致的,以实际交车时间为准),试用期起算以实际交车时间为准。
\n
1.2试用车辆仅限 行政区域范围内使用,超出前述约定区域范围的,应事先征得甲方书面同意。
\n
1.3 试用期内,甲方向乙方提供如下服务:
\n
1.3.1 跟进试用车辆全过程;
\n
1.3.2 组织车辆操作的培训、咨询解答、现场支持等;
\n
1.3.3 提供试用过程中遇到问题和故障的实时响应;
\n
1.3.4 提供氢能相关政策解读等;
\n
1.3.5 车辆试用结果的整理和汇报;
\n
1.3.6 车辆的“机动车交通事故责任强制险”、“机动车商业保险(含三者险和车损险)”由甲方购买。
\n
第二条 费用与支付
\n
2.1 租金先付后用:试用期内租金,乙方提车前2个工作日向甲方支付租金。超过约定试用期未归还车辆,也未签订正式《商用车租赁合同》的,按前述表格记载的日租金收取。
\n
2.2 本协议签署后提车前2个工作日,乙方应向甲方支付保证金。
\n
2.3 试用期间,氢气费及与氢气费相关的服务费(如有)由乙方承担,若需要甲方代付的,乙方应于提车前2个工作日内向甲方预交氢气费人民币 元;预付款余额低于一次加气费时,甲方通知乙方,乙方接到通知后1个工作日内,按甲方的要求预交氢气费;否则,甲方有权不予安排车辆加氢,由此所造成的一切损失由乙方自行承担,给甲方造成损失的,乙方还应承担相应的赔偿责任。
\n
2.4 试用期结束后,乙方还车时车里剩余氢气量若低于交车时的氢气量,则按照 元/公斤向甲方支付氢气费,若高于交车时的氢气量,则乙方同意甲方不向乙方返还氢气费。
\n
2.5 所有费用汇入甲方如下指定账户,否则视为未支付:
\n
户 名:【 】
\n
开户行:【 】
\n
账 号:【 】
\n
2.6 如根据本协议约定,乙方应承担的用车费用未付的、应向甲方承担赔偿、违约金或其他责任时,甲方有权从保证金中扣除。保证金不足以抵扣的,乙方仍应支付或赔偿给甲方。
\n
2.7 试用期届满后,乙方拒绝承租试用车辆的,乙方交还试用车辆后,双方在30日内对账结算,确认无误后,将剩余的保证金无息退还给乙方。
\n
2.8 碳资产
\n
本合同约定仅限于车辆使用,该车辆所产生的碳资产数据权益和碳资产收益归甲方所有,本合同报价不含减碳、碳资产管理费用等。
\n
第三条 试用车辆的风险与管理
\n
3.1 乙方负责提供氢燃料电池车试用场地,且在车辆运抵前确保场地满足车辆运行要求。
\n
3.2 车辆自交付乙方之时起,车辆发生损毁、灭失、被盗、遗失等风险的,风险责任由乙方承担,乙方应赔偿甲方全部损失,但该损失是因车辆自身质量问题导致的除外。
\n
3.3 车辆自交付乙方之时起,乙方负责车辆相关安全保管义务和日常管理,乙方使用车辆应符合国家、地方的相关法律法规、规章制度,遵守交通规则,禁止超速、超载,禁止装载易燃易爆、易腐蚀等危险物品及会在车中留下刺激性气味的物品。
\n
3.4 乙方应为车辆配备合格的专职驾驶员,驾驶员必须参加甲方组织的培训,按照操作规范驾驶车辆。
\n
3.5 车辆在试用期间产生的施救费、路桥费、人工费以及车辆发生损毁、灭失、出现故障和车辆事故以及乙方人员的人身损害、第三人财产损失和人身伤亡等由乙方承担,因此给甲方造成损失的,乙方应予赔偿,如进保险,乙方根据不同车型,需向甲方承担保险上浮费,4.5吨车型主责及以上责任为2000元/次/辆,其他责任为1000元/次/辆;18吨车型与49吨车型主责及以上责任为3000元/次/辆,其他责任为2000元/次/辆(因车辆本身质量问题发生的损毁、灭失除外);若保险实际上浮费超过前述费用的,以实际发生的保险上浮费为准。
\n
3.6 试用期间,车辆损坏需要维修和保养的,乙方必须在甲方指定维修站对车辆进行维修、保养,将车辆修复至提车时的状态,费用由乙方全额承担,并在 3 个工作日内通知甲方,将维修、保养相关单据的原件交回甲方留存。如在指定维修站以外维修、保养车辆的,应征得甲方书面同意,否则属于乙方违约,乙方应按 2000 元/次的标准向甲方支付违约金。
\n
3.7 汽车车上人员责任保险(驾驶员、车上其他人员)”由乙方购买:乙方应为使用该车辆的驾驶员及车上其他人员购买责任保险,车辆使用期间所发生的与乙方驾驶员及车上其他人员相关的一切财产、人身损失等由乙方承担,与甲方无任何关系,若给甲方造成损失的,乙方需向甲方承担赔偿责任。
\n
3.8 试用期间,车辆若发生违章的,乙方必须在违章发生之日起15日内处理完毕,违章产生的费用由乙方承担,若未在此时间内处理完毕的,乙方除需承担实际罚款金额,还需向甲方承担代办费。同时,甲方有权按一条违章信息1200元的标准(广东区域内为2000元/条),向乙方收取违约金;违约金金额不足以覆盖甲方损失时,乙方还应承担损失赔偿责任。所有费用,甲方有权从保证金中直接扣除。
\n
3.9 试用期间,乙方必须保持车辆原状,不得擅自更改车身外观、增加车身广告或改变车身颜色等,否则必须恢复车辆原状,同时向甲方承担 5000元/辆的修复费用,甲方有权从保证金中直接扣除。
\n
第四条 试用车辆交还
\n
4.1 乙方拒绝承租试用车辆的,最迟应在试用期限届满前3个工作日内通知甲方,双方约定还车的时间和还车地点,乙方负责将试用车辆送至约定的还车地点。
\n
4.2 乙方还车时,应携带提车时签订的《车辆交接单》,经甲方检查车辆确认无误后,双方签署《车辆交接单》,确认还车。
\n
4.3返还车辆的车容车貌、技术状况应符合国家、地方的有关标准和规定,除正常使用耗损外,应与公安机关登记信息、《车辆交接单》保持一致,无任何毁损,证照齐全,无未处理交通违章、交通事故等不存在未决纠纷争议可能影响甲方利益情形。否则,甲方有权拒绝接收车辆,乙方应在限期内整改,整改期内日租金按两倍日租金标准继续计算,直至车辆符合交还标准;如乙方拒绝或超过十五天仍未完成整改的,甲方有权强制收回车辆并送至修理厂进行修复或调整,因此产生的运输费、修理费等费用以及处理交通违章、事故等发生的一切费用,均由乙方全额承担。
\n
第五条 试用车辆的承租
\n
5.1 试用期限内,乙方确认承租的,应及时通知甲方,双方签订正式的《商用车租赁合同》,确认租金起算时间、租金费率等相关条款。
\n
5.2 乙方确认承租的,双方不再办理试用车辆的交还手续,乙方继续使用该车辆,双方按照《商用车租赁合同》的条款履行,本协议项下的款项结清,权责了结后,本协议自动终止。
\n
第六条 违约责任
\n
6.1 本协议项下乙方应支付的费用及应承担的赔偿责任,乙方应及时支付。每逾期一天,应按逾期金额的千分之一向甲方支付逾期付款违约金,同时仍应履行付款义务。
\n
6.2 乙方出现下列情形之一的,甲方有权立即解除或提前终止本协议并强制收回车辆,且乙方应向甲方支付违约金,违约金标准为前述表格中记载的日租金标准的2倍向甲方支付违约金:
\n
6.2.1利用标的车辆从事违法犯罪活动的,或因涉嫌违法犯罪被采取强制措施或受到行政处罚的;
\n
6.2.2未按期支付本协议项下的相应费用超过3个工作日的,经甲方催告后仍不支付的;
\n
6.2.3涉酒涉毒驾车、交通事故逃逸、不及时抢救伤员保护现场、谎报或瞒报交通事故的;
\n
6.2.4擅自将标的车辆转让、转租、转包,或设置抵押、留置、质押等负担,或恶意更换车辆零部件或拆撤、损毁、涂抹车辆设施、设备、标记、广告的;
\n
6.2.5标的车辆技术状况经甲方累计3次检查不合格的,或经甲方检查不合格要求乙方修复,乙方在3日内拒不修复的;
\n
6.2.6向甲方提供虚假证件或资料的;
\n
6.2.7发生3次有责交通事故或单次有重大、特大交通事故的;
\n
6.2.8利用标的车辆进行相关法律法规明令禁止的行为,或其他严重影响甲方权益或可能导致甲方损失的行为;
\n
6.2.9乙方有违反本协议约定导致甲方解除或提前终止本协议的其他情形的。
\n
6.3 任何一方有其他违反本协议情形的,应赔偿守约方全部损失。本协议中的全部损失包括但不限于对守约方所造成的直接损失、可得利益损失、守约方支付给第三方的赔偿费用/违约金/罚款/调查取证费用/公证费/鉴定费/诉讼仲裁费/保全费/保全保险费/律师费/维权费用以及其他合理费用。
\n
第七条 争议的解决
\n
7.1 凡因本协议引起的或与本协议有关的任何争议,由甲、乙双方友好协商解决。协商无法解决的,向协议签订地人民法院提起诉讼。
\n
第八条 其他
\n
8.1甲乙双方承诺保守商业机密和商业诚信,除双方已公开或披露信息外,未经双方事前书面同意,不得将机密信息泄露给其他方。
\n
8.2关于送达:任何与本协议有关的由一方发送给其他方的通知或其他通讯往来(“通知”)应采用书面形式,按照本协议开头记载的通讯地址或通讯号码或电子邮箱送达被通知人均构成有效形式之通知,包括但不限于法院、仲裁机构的法律文书及双方的来往函件等。
\n
8.3本协议所附附件是协议内容的一部分,与本协议具有同等法律效力,若附件内容与本协议内容不一致的,以附件约定的内容为准。
\n
8.4本协议由双方法定代表人或授权代表签字并加盖公章之日起生效。
\n
8.5本协议一式两份,甲乙双方各执壹份,具有同等法律效力。
\n
8.6本协议未尽事宜,由甲乙双方另行友好协商,签订补充协议。
\n
\n
---协议正文结束---
\n
\n
以下为签署处:
\n
\n
\n
甲方: (盖章) 乙方: (盖章)
\n
\n
法定代表人(或授权代表)签名: 法定代表人(或授权代表)签名:
\n
\n
签署日期: 年 月 日 签署日期: 年 月 日
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
附件1:安全责任告知书
\n
一、各车辆使用人责任主体:
\n
1、各租赁客户合作伙伴、挂靠我司车辆的用户,当您使用我司车辆期间必须承担安全责任,责任主体是:车辆使用人(公司或个人),挂靠车辆使用人(公司或个人)。
\n
2、根据《中华人民共和国民法典》第一千二百零九条,因租赁、借用等情形机动车所有人、管理人与使用人不是同一人时,发生交通事故等造成损害,属于该机动车一方责任的,由机动车承租人和使用人承担该赔偿责任。
\n
3、租赁车辆、挂靠车辆使用人,平时要做好出车检查,制订车辆日常检查记录表,对于:氢系统、电池、灯光、轮胎、车辆外观、随车配置物品等做好日常检查并记录,如有异常必须及时处理,车辆合格满足出车条件以后才能出车,并且必须保持车辆定位系统在线。
\n
4、车辆、挂靠车辆使用人,要做好我司车辆的安全、事故台账,出事故必须登记在台账中,并且开展四不放过教育:①事故原因没有查清不放过,②事故责任者没有严肃处理不放过,③全体司机及车队管理人员没受到教育不放过,④防范措施没有落实不放过。
\n
开展教育必须要有现场多张照片,所有司机及车队管理人员在教育签到表上签字,照片及签到表原件留档,我司安全部备查。
\n
二、车辆使用安全注意事项:
\n
1、所有驾驶员要有从业资格证,9.6米厢车与4.2米冷藏车,必须出示本人资格证,登记后才可以提车。
\n
2、注意车辆高度,苏龙9.6米车高4米。跃进4.2米车高3.3米。集卡飞驰车头3.9米,挂车装载平箱4.2米左右。驾驶员一定注意车厢高度,当心限高杆、桥梁、树枝等物。
\n
3、因为是氢用车,车内不得吸烟,车周边10米以内不得吸烟,车辆远离烟火(周边10米以上),不得将车停放在高压电线下。
\n
4、进加氢站,必须无烟火,关闭总电源后加氢。服从加氢站管理,如:静电接地,不超35Mpa压力等等。
\n
5、车辆遇到氢气泄露,不严重移至空旷处,关闭总电源!车辆旁边不得有闲杂人等,远离明火,联系运维人员:江浙沪沈帅15021428914;新疆赵波18639103977;广东邱陈佳17666025673;集卡车李泽琦13383480807;北京及其他地方赵小峰18868302409。
\n
6、系统漏气并着火,火焰未烧至其他管路和车身;处理方式:立即熄火,关闭电源,马上灭火,立即打上述人员电话,在他们指导下灭火,以防电池等爆炸。如果火势大、控制难、立即拨打119,人员撤离至安全区域。
\n
7、供氢系统漏气着火,或电池着火,火焰烧至其他管路或车身;处理方式:立即拨打119,人员撤离至安全区域。
\n
8、车辆每日检查:轮胎完好、气压标准、灯光设施齐全、车辆不带病上路。
\n
9、雨雾天气,降低速度。注意能见度,与前车保持足够安全距离。
\n
10、行驶中电池高温冒烟,马上靠边,断开高压系统,马上联系运维。
\n
11、车辆发生火情立即停车,用灭火器降温灭火,关电源,火势大要保证人身安全,离开车辆打119报警。
\n
12、氢能车辆禁止电焊,周围不许任何焊接,此类维修通知专业人员来修理。
\n
13、出交通事故,第一时间打安全经理王屹青电话:18116198188,告知实际情况,加此电话微信,拍现场照片:整体事故场景、我方车牌、对方车牌、我方及对方车辆受损部位。并发定位,安全经理会指导处理事故。\t
\n
贵方上述安全告知已收到并熟悉,我方(被告知人)郑重承诺:无条件遵守上述安全操作事项。若因我方(被告知人)原因导致发生安全事故,我方(被告知人)自行承担一切法律责任。
\n
\n
被告知人盖章:
\n
日期: 年 月 日
\n
\n
\n
\n
附件2:乙方营业执照和法定代表人身份证复印件、道路运输经营许可证等
\n
\n
\n
\n
";
diff --git a/src/prototypes/customer-management/.spec/requirements-prd-create.md b/src/prototypes/customer-management/.spec/requirements-prd-create.md
new file mode 100644
index 0000000..8204a8b
--- /dev/null
+++ b/src/prototypes/customer-management/.spec/requirements-prd-create.md
@@ -0,0 +1,60 @@
+# 客户管理 · 新增页产品需求说明(PRD)
+
+> 从列表页「新增客户」进入。保存为原型 Toast,不对接真实接口。
+
+---
+
+## 1. 页面框架
+
+| 项 | 说明 |
+|---|---|
+| 面包屑 | 业务管理 / 客户管理 / 新增 |
+| 布局 | 三组卡片表单 + 底部 sticky 操作栏 |
+| 操作 | 重置(清空表单)、提交(校验后 Toast 成功并返回列表) |
+
+---
+
+## 2. 客户信息
+
+| 字段 | 必填 | 控件 | 说明 |
+|---|---|---|---|
+| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |
+| 客户分级 | 否 | 下拉 | A / B / C |
+| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |
+| 客户简称 | 否 | 文本 | 选填 |
+| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |
+| 通讯地址 | 否 | 文本 | |
+| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |
+| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |
+| 备注 | 否 | 多行文本 | |
+
+---
+
+## 3. 联系人信息
+
+| 字段 | 必填 | 控件 |
+|---|---|---|
+| 姓名 | 否 | 文本 |
+| 手机号 | 否 | 文本 |
+| 职位 | 否 | 文本 |
+
+---
+
+## 4. 付款及开票信息
+
+| 字段 | 必填 | 控件 | 说明 |
+|---|---|---|---|
+| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |
+| 纳税人识别号 | 是 | 文本 | |
+| 注册地址 | 否 | 文本 | |
+| 注册电话 | 否 | 文本 | |
+| 开户银行 | 是 | 文本 | |
+| 账号 | 否 | 文本 | |
+
+---
+
+## 5. 校验与提交
+
+- 提交前校验所有必填项
+- 通过后 Toast「客户已保存(原型演示)」并返回列表
+- 重置清空全部字段,客户类型默认「企业」
diff --git a/src/prototypes/customer-management/.spec/requirements-prd-list.md b/src/prototypes/customer-management/.spec/requirements-prd-list.md
new file mode 100644
index 0000000..21a3668
--- /dev/null
+++ b/src/prototypes/customer-management/.spec/requirements-prd-list.md
@@ -0,0 +1,96 @@
+# 客户管理 · 列表页产品需求说明(PRD)
+
+> 本页为客户主数据台账列表。标注「原型演示」的跳转、导入、导出仅 Toast 提示。
+
+---
+
+## 1. 模块定位
+
+| 项 | 说明 |
+|---|---|
+| 模块名称 | 客户管理 — 列表页 |
+| 目标用户 | 销售、业务负责人、运营、风控 |
+| 核心任务 | 按多维度筛选客户台账,查看标签与状态,新增客户或进入详情/编辑 |
+| 页面结构 | 面包屑 + 筛选区 + 工具栏 + 表格 + 分页 |
+
+---
+
+## 2. 客户编号规则(集团多子公司)
+
+- 总长度 7,仅含大写字母 A–Z 与数字 0–9
+- 结构:第 1 位 = 签约/归属子公司代码;第 2–7 位 = 6 位数字流水号(000001–999999)
+- 流水号在「同一子公司代码」下递增
+- 校验正则:`/^[A-Z][0-9]{6}$/`
+- 示例:A000001(华东)、B000001(华南)、C000001(华北)等
+
+---
+
+## 3. 数据来源
+
+| 数据 | 来源 | 用途 |
+|---|---|---|
+| 客户台账 | `data/customers.json`(20 条样例) | 列表、筛选选项 |
+| 客户标签 | `CUSTOMER_LABEL_OPTIONS` 常量 | 筛选与列表展示 |
+| 部门-负责人 | 由台账聚合 + `DEPT_MANAGER_MAP` | 筛选联动 |
+
+---
+
+## 4. 筛选区
+
+默认展示前 4 项(一行),点击「更多筛选」显示全部。
+
+| 字段 | 控件 | 匹配规则 |
+|---|---|---|
+| 客户编号 | 可搜索单选 | 精确匹配 `code` |
+| 客户名称 | 可搜索单选 | 精确匹配 `name` |
+| 标签 | 多选 | 客户 `labels` 包含任一选中标签 |
+| 区域 | 可搜索单选 | 精确匹配 `region` |
+| 客户状态 | 多选 | `status` 在选中集合内 |
+| 业务部门 | 多选 | `department` 在选中集合内 |
+| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |
+| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |
+
+### 4.1 操作逻辑
+
+| 操作 | 行为 |
+|---|---|
+| **查询** | 将当前筛选项写入已应用条件,页码重置为 1 |
+| **重置** | 清空待应用与已应用条件,页码重置为 1 |
+| 修改筛选项未点查询 | 不影响当前列表 |
+
+---
+
+## 5. 列表工具栏
+
+| 按钮 | 行为 |
+|---|---|
+| 新增客户 | 进入新增客户页(分组表单) |
+| 批量导入 | Toast:批量导入(原型演示) |
+| 批量导出 | Toast:导出当前列表条数 |
+
+---
+
+## 6. 列表字段说明
+
+| 列 | 说明 |
+|---|---|
+| 客户编号 | 左固定列 |
+| 客户名称 | 超长省略 |
+| 标签 | 仅展示第一个标签;多于 1 个显示「+N」,悬浮展示全部 |
+| 客户区域 | 大区名称 |
+| 客户状态 | 绿点「正常」/ 红点「黑名单」 |
+| 业务部门 / 业务负责人 | 文本 |
+| 客户类型 / 所属行业 | 文本 |
+| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |
+| 详细地址 / 创建时间 | 文本;创建时间不换行 |
+| 操作 | 详情、编辑、删除(原型 Toast) |
+
+支持行多选(checkbox);横向滚动;操作列右固定。
+
+---
+
+## 7. 客户标签枚举
+
+逾期预警、严重拖欠、频繁改账期、资质存疑、涉诉/仲裁、制裁/高风险地区、频繁争议、车辆/资产高风险、信息不实、开票异常、多头签约。
+
+标签样式:白底 + 橙色描边(`#f77234`)。
diff --git a/src/prototypes/customer-management/annotation-source.json b/src/prototypes/customer-management/annotation-source.json
new file mode 100644
index 0000000..13f2dae
--- /dev/null
+++ b/src/prototypes/customer-management/annotation-source.json
@@ -0,0 +1,283 @@
+{
+ "documentVersion": 1,
+ "format": "axhub-annotation-source",
+ "data": {
+ "version": 2,
+ "prototypeName": "customer-management",
+ "pageId": "list",
+ "updatedAt": 1782697342692,
+ "nodes": [
+ {
+ "id": "cm-list-filter",
+ "index": 1,
+ "title": "筛选条件",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-list-filter\"]"
+ ],
+ "fingerprint": "cm-list-filter",
+ "path": []
+ },
+ "aiPrompt": "列表筛选区字段与查询逻辑。",
+ "annotationText": "默认展示 4 项(一行),点击「更多筛选」显示全部。",
+ "hasMarkdown": true,
+ "color": "#2563eb",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ },
+ {
+ "id": "cm-list-toolbar",
+ "index": 2,
+ "title": "列表工具栏",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-list-toolbar\"]"
+ ],
+ "fingerprint": "cm-list-toolbar",
+ "path": []
+ },
+ "aiPrompt": "新增、导入、导出入口。",
+ "annotationText": "「新增客户」进入分组表单页。",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ },
+ {
+ "id": "cm-list-table",
+ "index": 3,
+ "title": "客户台账列表",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-list-table\"]"
+ ],
+ "fingerprint": "cm-list-table",
+ "path": []
+ },
+ "aiPrompt": "客户台账表格列与行操作。",
+ "annotationText": "",
+ "hasMarkdown": true,
+ "color": "#2563eb",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ },
+ {
+ "id": "cm-create-base",
+ "index": 4,
+ "title": "客户信息",
+ "pageId": "create",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-create-base\"]"
+ ],
+ "fingerprint": "cm-create-base",
+ "path": []
+ },
+ "aiPrompt": "客户类型、分级、全称、城市、部门负责人等。",
+ "annotationText": "客户全称变更时同步发票抬头。",
+ "hasMarkdown": true,
+ "color": "#2563eb",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ },
+ {
+ "id": "cm-create-contact",
+ "index": 5,
+ "title": "联系人信息",
+ "pageId": "create",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-create-contact\"]"
+ ],
+ "fingerprint": "cm-create-contact",
+ "path": []
+ },
+ "aiPrompt": "联系人姓名、手机、职位。",
+ "annotationText": "均为选填。",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ },
+ {
+ "id": "cm-create-invoice",
+ "index": 6,
+ "title": "付款及开票信息",
+ "pageId": "create",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-create-invoice\"]"
+ ],
+ "fingerprint": "cm-create-invoice",
+ "path": []
+ },
+ "aiPrompt": "发票抬头、税号、银行账号等。",
+ "annotationText": "发票抬头只读,与客户全称一致。",
+ "hasMarkdown": true,
+ "color": "#d97706",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ },
+ {
+ "id": "cm-create-footer",
+ "index": 7,
+ "title": "底部操作栏",
+ "pageId": "create",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"cm-create-footer\"]"
+ ],
+ "fingerprint": "cm-create-footer",
+ "path": []
+ },
+ "aiPrompt": "重置与提交。",
+ "annotationText": "提交校验必填项后 Toast 并返回列表。",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000
+ }
+ ]
+ },
+ "markdownMap": {
+ "cm-list-filter": "## 4. 筛选区\n\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\n\n| 字段 | 控件 | 匹配规则 |\n|---|---|---|\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签 |\n| 区域 | 可搜索单选 | 精确匹配 `region` |\n| 客户状态 | 多选 | `status` 在选中集合内 |\n| 业务部门 | 多选 | `department` 在选中集合内 |\n| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\n\n### 4.1 操作逻辑\n\n| 操作 | 行为 |\n|---|---|\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1 |\n| **重置** | 清空待应用与已应用条件,页码重置为 1 |\n| 修改筛选项未点查询 | 不影响当前列表 |\n\n---",
+ "cm-list-toolbar": "## 5. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增客户 | 进入新增客户页(分组表单) |\n| 批量导入 | Toast:批量导入(原型演示) |\n| 批量导出 | Toast:导出当前列表条数 |\n\n---",
+ "cm-list-table": "## 6. 列表字段说明\n\n| 列 | 说明 |\n|---|---|\n| 客户编号 | 左固定列 |\n| 客户名称 | 超长省略 |\n| 标签 | 仅展示第一个标签;多于 1 个显示「+N」,悬浮展示全部 |\n| 客户区域 | 大区名称 |\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\n| 业务部门 / 业务负责人 | 文本 |\n| 客户类型 / 所属行业 | 文本 |\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\n| 操作 | 详情、编辑、删除(原型 Toast) |\n\n支持行多选(checkbox);横向滚动;操作列右固定。\n\n---",
+ "cm-create-base": "## 2. 客户信息\n\n| 字段 | 必填 | 控件 | 说明 |\n|---|---|---|---|\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\n| 客户分级 | 否 | 下拉 | A / B / C |\n| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |\n| 客户简称 | 否 | 文本 | 选填 |\n| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |\n| 通讯地址 | 否 | 文本 | |\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\n| 备注 | 否 | 多行文本 | |\n\n---",
+ "cm-create-contact": "## 3. 联系人信息\n\n| 字段 | 必填 | 控件 |\n|---|---|---|\n| 姓名 | 否 | 文本 |\n| 手机号 | 否 | 文本 |\n| 职位 | 否 | 文本 |\n\n---",
+ "cm-create-invoice": "## 4. 付款及开票信息\n\n| 字段 | 必填 | 控件 | 说明 |\n|---|---|---|---|\n| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |\n| 纳税人识别号 | 是 | 文本 | |\n| 注册地址 | 否 | 文本 | |\n| 注册电话 | 否 | 文本 | |\n| 开户银行 | 是 | 文本 | |\n| 账号 | 否 | 文本 | |\n\n---",
+ "cm-create-footer": "## 5. 校验与提交\n\n- 提交前校验所有必填项\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\n- 重置清空全部字段,客户类型默认「企业」"
+ },
+ "assetMap": {},
+ "directory": {
+ "nodes": [
+ {
+ "type": "folder",
+ "id": "cm-doc-root",
+ "title": "客户管理说明",
+ "defaultExpanded": true,
+ "children": [
+ {
+ "type": "markdown",
+ "id": "cm-doc-overview",
+ "title": "模块总览",
+ "markdown": "## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 客户管理 — 列表页 |\n| 目标用户 | 销售、业务负责人、运营、风控 |\n| 核心任务 | 按多维度筛选客户台账,查看标签与状态,新增客户或进入详情/编辑 |\n| 页面结构 | 面包屑 + 筛选区 + 工具栏 + 表格 + 分页 |\n\n---"
+ },
+ {
+ "type": "link",
+ "id": "cm-link-self",
+ "title": "客户管理原型",
+ "href": "/prototypes/customer-management",
+ "target": "self"
+ },
+ {
+ "type": "folder",
+ "id": "cm-doc-prd",
+ "title": "PRD 全文",
+ "defaultExpanded": false,
+ "children": [
+ {
+ "type": "markdown",
+ "id": "cm-doc-prd-list",
+ "title": "列表页 PRD",
+ "markdown": "# 客户管理 · 列表页产品需求说明(PRD)\n\n> 本页为客户主数据台账列表。标注「原型演示」的跳转、导入、导出仅 Toast 提示。\n\n---\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 客户管理 — 列表页 |\n| 目标用户 | 销售、业务负责人、运营、风控 |\n| 核心任务 | 按多维度筛选客户台账,查看标签与状态,新增客户或进入详情/编辑 |\n| 页面结构 | 面包屑 + 筛选区 + 工具栏 + 表格 + 分页 |\n\n---\n\n## 2. 客户编号规则(集团多子公司)\n\n- 总长度 7,仅含大写字母 A–Z 与数字 0–9\n- 结构:第 1 位 = 签约/归属子公司代码;第 2–7 位 = 6 位数字流水号(000001–999999)\n- 流水号在「同一子公司代码」下递增\n- 校验正则:`/^[A-Z][0-9]{6}$/`\n- 示例:A000001(华东)、B000001(华南)、C000001(华北)等\n\n---\n\n## 3. 数据来源\n\n| 数据 | 来源 | 用途 |\n|---|---|---|\n| 客户台账 | `data/customers.json`(20 条样例) | 列表、筛选选项 |\n| 客户标签 | `CUSTOMER_LABEL_OPTIONS` 常量 | 筛选与列表展示 |\n| 部门-负责人 | 由台账聚合 + `DEPT_MANAGER_MAP` | 筛选联动 |\n\n---\n\n## 4. 筛选区\n\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\n\n| 字段 | 控件 | 匹配规则 |\n|---|---|---|\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签 |\n| 区域 | 可搜索单选 | 精确匹配 `region` |\n| 客户状态 | 多选 | `status` 在选中集合内 |\n| 业务部门 | 多选 | `department` 在选中集合内 |\n| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\n\n### 4.1 操作逻辑\n\n| 操作 | 行为 |\n|---|---|\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1 |\n| **重置** | 清空待应用与已应用条件,页码重置为 1 |\n| 修改筛选项未点查询 | 不影响当前列表 |\n\n---\n\n## 5. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增客户 | 进入新增客户页(分组表单) |\n| 批量导入 | Toast:批量导入(原型演示) |\n| 批量导出 | Toast:导出当前列表条数 |\n\n---\n\n## 6. 列表字段说明\n\n| 列 | 说明 |\n|---|---|\n| 客户编号 | 左固定列 |\n| 客户名称 | 超长省略 |\n| 标签 | 仅展示第一个标签;多于 1 个显示「+N」,悬浮展示全部 |\n| 客户区域 | 大区名称 |\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\n| 业务部门 / 业务负责人 | 文本 |\n| 客户类型 / 所属行业 | 文本 |\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\n| 操作 | 详情、编辑、删除(原型 Toast) |\n\n支持行多选(checkbox);横向滚动;操作列右固定。\n\n---\n\n## 7. 客户标签枚举\n\n逾期预警、严重拖欠、频繁改账期、资质存疑、涉诉/仲裁、制裁/高风险地区、频繁争议、车辆/资产高风险、信息不实、开票异常、多头签约。\n\n标签样式:白底 + 橙色描边(`#f77234`)。\n"
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-prd-create",
+ "title": "新增页 PRD",
+ "markdown": "# 客户管理 · 新增页产品需求说明(PRD)\n\n> 从列表页「新增客户」进入。保存为原型 Toast,不对接真实接口。\n\n---\n\n## 1. 页面框架\n\n| 项 | 说明 |\n|---|---|\n| 面包屑 | 业务管理 / 客户管理 / 新增 |\n| 布局 | 三组卡片表单 + 底部 sticky 操作栏 |\n| 操作 | 重置(清空表单)、提交(校验后 Toast 成功并返回列表) |\n\n---\n\n## 2. 客户信息\n\n| 字段 | 必填 | 控件 | 说明 |\n|---|---|---|---|\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\n| 客户分级 | 否 | 下拉 | A / B / C |\n| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |\n| 客户简称 | 否 | 文本 | 选填 |\n| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |\n| 通讯地址 | 否 | 文本 | |\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\n| 备注 | 否 | 多行文本 | |\n\n---\n\n## 3. 联系人信息\n\n| 字段 | 必填 | 控件 |\n|---|---|---|\n| 姓名 | 否 | 文本 |\n| 手机号 | 否 | 文本 |\n| 职位 | 否 | 文本 |\n\n---\n\n## 4. 付款及开票信息\n\n| 字段 | 必填 | 控件 | 说明 |\n|---|---|---|---|\n| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |\n| 纳税人识别号 | 是 | 文本 | |\n| 注册地址 | 否 | 文本 | |\n| 注册电话 | 否 | 文本 | |\n| 开户银行 | 是 | 文本 | |\n| 账号 | 否 | 文本 | |\n\n---\n\n## 5. 校验与提交\n\n- 提交前校验所有必填项\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\n- 重置清空全部字段,客户类型默认「企业」\n"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "id": "cm-doc-list",
+ "title": "列表页模块",
+ "defaultExpanded": true,
+ "children": [
+ {
+ "type": "route",
+ "id": "cm-route-list",
+ "title": "打开列表页",
+ "route": "customer-management:list",
+ "payload": {
+ "pageId": "list"
+ }
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-list-filter",
+ "title": "筛选条件",
+ "markdown": "## 4. 筛选区\n\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\n\n| 字段 | 控件 | 匹配规则 |\n|---|---|---|\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签 |\n| 区域 | 可搜索单选 | 精确匹配 `region` |\n| 客户状态 | 多选 | `status` 在选中集合内 |\n| 业务部门 | 多选 | `department` 在选中集合内 |\n| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\n\n### 4.1 操作逻辑\n\n| 操作 | 行为 |\n|---|---|\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1 |\n| **重置** | 清空待应用与已应用条件,页码重置为 1 |\n| 修改筛选项未点查询 | 不影响当前列表 |\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-list-toolbar",
+ "title": "列表工具栏",
+ "markdown": "## 5. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增客户 | 进入新增客户页(分组表单) |\n| 批量导入 | Toast:批量导入(原型演示) |\n| 批量导出 | Toast:导出当前列表条数 |\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-list-table",
+ "title": "客户台账列表",
+ "markdown": "## 6. 列表字段说明\n\n| 列 | 说明 |\n|---|---|\n| 客户编号 | 左固定列 |\n| 客户名称 | 超长省略 |\n| 标签 | 仅展示第一个标签;多于 1 个显示「+N」,悬浮展示全部 |\n| 客户区域 | 大区名称 |\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\n| 业务部门 / 业务负责人 | 文本 |\n| 客户类型 / 所属行业 | 文本 |\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\n| 操作 | 详情、编辑、删除(原型 Toast) |\n\n支持行多选(checkbox);横向滚动;操作列右固定。\n\n---"
+ }
+ ]
+ },
+ {
+ "type": "folder",
+ "id": "cm-doc-create",
+ "title": "新增页模块",
+ "defaultExpanded": true,
+ "children": [
+ {
+ "type": "route",
+ "id": "cm-route-create",
+ "title": "打开新增页",
+ "route": "customer-management:create",
+ "payload": {
+ "pageId": "create"
+ }
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-create-base",
+ "title": "客户信息",
+ "markdown": "## 2. 客户信息\n\n| 字段 | 必填 | 控件 | 说明 |\n|---|---|---|---|\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\n| 客户分级 | 否 | 下拉 | A / B / C |\n| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |\n| 客户简称 | 否 | 文本 | 选填 |\n| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |\n| 通讯地址 | 否 | 文本 | |\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\n| 备注 | 否 | 多行文本 | |\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-create-contact",
+ "title": "联系人信息",
+ "markdown": "## 3. 联系人信息\n\n| 字段 | 必填 | 控件 |\n|---|---|---|\n| 姓名 | 否 | 文本 |\n| 手机号 | 否 | 文本 |\n| 职位 | 否 | 文本 |\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-create-invoice",
+ "title": "付款及开票信息",
+ "markdown": "## 4. 付款及开票信息\n\n| 字段 | 必填 | 控件 | 说明 |\n|---|---|---|---|\n| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |\n| 纳税人识别号 | 是 | 文本 | |\n| 注册地址 | 否 | 文本 | |\n| 注册电话 | 否 | 文本 | |\n| 开户银行 | 是 | 文本 | |\n| 账号 | 否 | 文本 | |\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "cm-doc-node-cm-create-footer",
+ "title": "底部操作栏",
+ "markdown": "## 5. 校验与提交\n\n- 提交前校验所有必填项\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\n- 重置清空全部字段,客户类型默认「企业」"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/src/prototypes/customer-management/components/CustomerTable.tsx b/src/prototypes/customer-management/components/CustomerTable.tsx
new file mode 100644
index 0000000..ba53e61
--- /dev/null
+++ b/src/prototypes/customer-management/components/CustomerTable.tsx
@@ -0,0 +1,113 @@
+import React, { useState } from 'react';
+import type { CustomerRecord } from '../types';
+import { LabelTags } from './LabelTags';
+import { StatusBadge } from './StatusBadge';
+
+interface CustomerTableProps {
+ records: CustomerRecord[];
+ selectedIds: Set;
+ onToggleRow: (id: string, checked: boolean) => void;
+ onToggleAll: (checked: boolean) => void;
+ onView: (record: CustomerRecord) => void;
+ onEdit: (record: CustomerRecord) => void;
+ onDelete: (record: CustomerRecord) => void;
+}
+
+export function CustomerTable({
+ records,
+ selectedIds,
+ onToggleRow,
+ onToggleAll,
+ onView,
+ onEdit,
+ onDelete,
+}: CustomerTableProps) {
+ const allSelected = records.length > 0 && records.every((record) => selectedIds.has(record.id));
+ const [hoverId, setHoverId] = useState(null);
+
+ if (records.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/prototypes/customer-management/components/FilterPanel.tsx b/src/prototypes/customer-management/components/FilterPanel.tsx
new file mode 100644
index 0000000..dda9be3
--- /dev/null
+++ b/src/prototypes/customer-management/components/FilterPanel.tsx
@@ -0,0 +1,211 @@
+import React, { useMemo, useState } from 'react';
+import { ChevronDown, Search } from 'lucide-react';
+import type { CustomerFilters, CustomerRecord } from '../types';
+import { FilterPickerField } from '../../vehicle-management/components/FilterPickerField';
+import {
+ CUSTOMER_LABEL_OPTIONS,
+ CUSTOMER_STATUS_OPTIONS,
+ REGION_OPTIONS,
+} from '../utils/constants';
+import {
+ buildCodeOptions,
+ buildDeptManagerMap,
+ buildOptions,
+ getManagerOptions,
+} from '../utils/filters';
+import { MultiSelectField } from './MultiSelectField';
+
+const DEFAULT_VISIBLE_COUNT = 4;
+
+interface FilterPanelProps {
+ records: CustomerRecord[];
+ filters: CustomerFilters;
+ onChange: (patch: Partial) => void;
+ onReset: () => void;
+ onSearch: () => void;
+}
+
+export function FilterPanel({
+ records,
+ filters,
+ onChange,
+ onReset,
+ onSearch,
+}: FilterPanelProps) {
+ const [expanded, setExpanded] = useState(false);
+
+ const codeOptions = useMemo(() => buildCodeOptions(records), [records]);
+ const nameOptions = useMemo(() => buildOptions(records, 'name'), [records]);
+ const departmentOptions = useMemo(() => buildOptions(records, 'department'), [records]);
+ const deptManagerMap = useMemo(() => buildDeptManagerMap(records), [records]);
+ const managerOptions = useMemo(
+ () => getManagerOptions(deptManagerMap, filters.departments),
+ [deptManagerMap, filters.departments],
+ );
+
+ const codePickerOptions = codeOptions.map((item) => item.label);
+ const codeLabelToValue = useMemo(
+ () => Object.fromEntries(codeOptions.map((item) => [item.label, item.value])),
+ [codeOptions],
+ );
+ const codeValueToLabel = useMemo(
+ () => Object.fromEntries(codeOptions.map((item) => [item.value, item.label])),
+ [codeOptions],
+ );
+
+ const handleDeptChange = (departments: string[]) => {
+ const allowed = getManagerOptions(deptManagerMap, departments);
+ onChange({
+ departments,
+ managers: filters.managers.filter((manager) => allowed.includes(manager)),
+ });
+ };
+
+ const fields = [
+ {
+ label: '客户编号',
+ control: (
+ onChange({ code: codeLabelToValue[label] ?? '' })}
+ />
+ ),
+ },
+ {
+ label: '客户名称',
+ control: (
+ onChange({ name })}
+ />
+ ),
+ },
+ {
+ label: '标签',
+ control: (
+ onChange({ labels })}
+ />
+ ),
+ },
+ {
+ label: '区域',
+ control: (
+ onChange({ region })}
+ />
+ ),
+ },
+ {
+ label: '客户状态',
+ control: (
+ onChange({ statuses: statuses as CustomerFilters['statuses'] })}
+ />
+ ),
+ },
+ {
+ label: '业务部门',
+ control: (
+
+ ),
+ },
+ {
+ label: '业务负责人',
+ control: (
+ onChange({ managers })}
+ />
+ ),
+ },
+ {
+ label: '创建时间',
+ control: (
+
+ onChange({ createFrom: event.target.value })}
+ aria-label="创建开始日期"
+ />
+ —
+ onChange({ createTo: event.target.value })}
+ aria-label="创建结束日期"
+ />
+
+ ),
+ },
+ ];
+
+ const visibleFields = expanded ? fields : fields.slice(0, DEFAULT_VISIBLE_COUNT);
+
+ return (
+
+
+
+ {visibleFields.map((field) => (
+
+ ))}
+
+
+ {fields.length > DEFAULT_VISIBLE_COUNT && (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/prototypes/customer-management/components/LabelTags.tsx b/src/prototypes/customer-management/components/LabelTags.tsx
new file mode 100644
index 0000000..ed01419
--- /dev/null
+++ b/src/prototypes/customer-management/components/LabelTags.tsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react';
+
+interface LabelTagsProps {
+ tags: string[];
+}
+
+const labelStyle: React.CSSProperties = {
+ display: 'inline-flex',
+ alignItems: 'center',
+ padding: '0 7px',
+ lineHeight: '20px',
+ borderRadius: 4,
+ background: '#fff',
+ border: '1px solid #f77234',
+ color: '#1d2129',
+ fontSize: 12,
+ whiteSpace: 'nowrap',
+};
+
+const countStyle: React.CSSProperties = {
+ ...labelStyle,
+ borderColor: '#c9cdd4',
+ color: '#4e5969',
+ cursor: 'default',
+};
+
+export function LabelTags({ tags }: LabelTagsProps) {
+ const [open, setOpen] = useState(false);
+
+ if (!tags.length) {
+ return —;
+ }
+
+ if (tags.length === 1) {
+ return {tags[0]};
+ }
+
+ return (
+
+ {tags[0]}
+ setOpen(true)}
+ onMouseLeave={() => setOpen(false)}
+ onFocus={() => setOpen(true)}
+ onBlur={() => setOpen(false)}
+ tabIndex={0}
+ role="button"
+ aria-label={`还有 ${tags.length - 1} 个标签`}
+ >
+ +{tags.length - 1}
+ {open && (
+
+ {tags.map((tag) => (
+ {tag}
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/prototypes/customer-management/components/MultiSelectField.tsx b/src/prototypes/customer-management/components/MultiSelectField.tsx
new file mode 100644
index 0000000..82644ac
--- /dev/null
+++ b/src/prototypes/customer-management/components/MultiSelectField.tsx
@@ -0,0 +1,81 @@
+import React, { useCallback, useMemo, useRef, useState } from 'react';
+import { ChevronDown, X } from 'lucide-react';
+import { useDismissOnFocusOutside } from '../../vehicle-management/components/useDismissOnFocusOutside';
+
+interface MultiSelectFieldProps {
+ values: string[];
+ options: string[];
+ placeholder?: string;
+ ariaLabel: string;
+ onChange: (values: string[]) => void;
+}
+
+export function MultiSelectField({
+ values,
+ options,
+ placeholder = '请选择',
+ ariaLabel,
+ onChange,
+}: MultiSelectFieldProps) {
+ const [open, setOpen] = useState(false);
+ const fieldRef = useRef(null);
+
+ const dismiss = useCallback(() => setOpen(false), []);
+ useDismissOnFocusOutside(open, fieldRef, dismiss);
+
+ const summary = useMemo(() => {
+ if (values.length === 0) return '';
+ if (values.length <= 2) return values.join('、');
+ return `已选 ${values.length} 项`;
+ }, [values]);
+
+ const toggle = (option: string) => {
+ if (values.includes(option)) {
+ onChange(values.filter((item) => item !== option));
+ } else {
+ onChange([...values, option]);
+ }
+ };
+
+ return (
+
+
+ {values.length > 0 && (
+
+ )}
+ {open && (
+
+ {options.map((option) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/prototypes/customer-management/components/StatusBadge.tsx b/src/prototypes/customer-management/components/StatusBadge.tsx
new file mode 100644
index 0000000..8f4b12f
--- /dev/null
+++ b/src/prototypes/customer-management/components/StatusBadge.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import type { CustomerStatus } from '../types';
+
+interface StatusBadgeProps {
+ status: CustomerStatus;
+}
+
+export function StatusBadge({ status }: StatusBadgeProps) {
+ const dotColor = status === '正常' ? '#00b42a' : status === '黑名单' ? '#f53f3f' : '#86909c';
+ return (
+
+
+ {status}
+
+ );
+}
diff --git a/src/prototypes/customer-management/data/customers.json b/src/prototypes/customer-management/data/customers.json
new file mode 100644
index 0000000..44089df
--- /dev/null
+++ b/src/prototypes/customer-management/data/customers.json
@@ -0,0 +1,22 @@
+[
+ { "id": "1", "code": "C000001", "name": "北京华宇科技有限公司", "labels": ["资质存疑"], "region": "华北地区", "status": "正常", "department": "销售一部", "manager": "张伟", "customerType": "企业客户", "industry": "互联网", "creditCode": "91110108MA01D2XY89", "legalPerson": "李明", "regCapital": "1000万元", "operateStatus": "在业", "address": "北京市海淀区中关村大街18号科创大厦8层", "createTime": "2023-10-25 14:30:00" },
+ { "id": "2", "code": "A000001", "name": "上海诚新贸易有限公司", "labels": ["频繁改账期"], "region": "华东地区", "status": "正常", "department": "销售一部", "manager": "王芳", "customerType": "企业客户", "industry": "零售", "creditCode": "91310115MA1K3NBC12", "legalPerson": "陈强", "regCapital": "500万元", "operateStatus": "在业", "address": "上海市浦东新区张江路1288号科技园3号楼", "createTime": "2023-11-02 09:15:00" },
+ { "id": "3", "code": "B000001", "name": "深圳智联信息技术有限公司", "labels": [], "region": "华南地区", "status": "正常", "department": "市场开拓部", "manager": "刘洋", "customerType": "企业客户", "industry": "互联网", "creditCode": "91440300MA5F8QWE45", "legalPerson": "赵敏", "regCapital": "2000万元", "operateStatus": "在业", "address": "广东省深圳市南山区科技园南区科苑路15号", "createTime": "2023-11-18 16:20:00" },
+ { "id": "4", "code": "A000002", "name": "杭州云帆智能制造股份有限公司", "labels": ["逾期预警"], "region": "华东地区", "status": "正常", "department": "大客户部", "manager": "李强", "customerType": "企业客户", "industry": "制造业", "creditCode": "91330106MA2B7HJK01", "legalPerson": "周杰", "regCapital": "5000万元", "operateStatus": "存续", "address": "浙江省杭州市滨江区物联网街451号", "createTime": "2023-12-05 11:00:00" },
+ { "id": "5", "code": "D000001", "name": "成都天府医疗健康集团", "labels": ["信息不实"], "region": "西南地区", "status": "正常", "department": "大客户部", "manager": "陈静", "customerType": "企业客户", "industry": "医疗保健", "creditCode": "91510100MA6C9RST78", "legalPerson": "吴磊", "regCapital": "3000万元", "operateStatus": "在业", "address": "四川省成都市高新区天府大道北段966号", "createTime": "2024-01-08 10:45:00" },
+ { "id": "6", "code": "B000002", "name": "广州粤海金融服务有限公司", "labels": ["严重拖欠", "多头签约"], "region": "华南地区", "status": "黑名单", "department": "销售二部", "manager": "孙丽", "customerType": "企业客户", "industry": "金融", "creditCode": "91440101MA5D1UVW23", "legalPerson": "郑华", "regCapital": "8000万元", "operateStatus": "在业", "address": "广东省广州市天河区珠江东路30号金融中心16层", "createTime": "2024-01-22 13:10:00" },
+ { "id": "7", "code": "E000001", "name": "武汉长江物流供应链有限公司", "labels": [], "region": "华中地区", "status": "正常", "department": "销售一部", "manager": "张伟", "customerType": "企业客户", "industry": "物流", "creditCode": "91420100MA4K2PQR56", "legalPerson": "冯涛", "regCapital": "1500万元", "operateStatus": "在业", "address": "湖北省武汉市东西湖区物流大道88号", "createTime": "2024-02-01 08:50:00" },
+ { "id": "8", "code": "A000003", "name": "南京金陵软件科技有限公司", "labels": [], "region": "华东地区", "status": "正常", "department": "市场开拓部", "manager": "王芳", "customerType": "企业客户", "industry": "互联网", "creditCode": "91320105MA1P5XYZ99", "legalPerson": "韩雪", "regCapital": "800万元", "operateStatus": "在业", "address": "江苏省南京市雨花台区软件大道101号", "createTime": "2024-02-14 15:25:00" },
+ { "id": "9", "code": "C000002", "name": "天津市滨海新区港口设备制造厂", "labels": ["涉诉/仲裁"], "region": "华北地区", "status": "正常", "department": "销售二部", "manager": "刘洋", "customerType": "企业客户", "industry": "制造业", "creditCode": "91120116MA07NMLM34", "legalPerson": "曹阳", "regCapital": "2200万元", "operateStatus": "存续", "address": "天津市滨海新区临港经济区渤海路36号", "createTime": "2024-03-03 09:40:00" },
+ { "id": "10", "code": "B000003", "name": "厦门海峡跨境电商有限公司", "labels": [], "region": "华南地区", "status": "正常", "department": "市场开拓部", "manager": "赵敏", "customerType": "企业客户", "industry": "零售", "creditCode": "91350200MA8F3CDE77", "legalPerson": "许晴", "regCapital": "600万元", "operateStatus": "在业", "address": "福建省厦门市湖里区象屿路97号国际航运中心", "createTime": "2024-03-20 14:05:00" },
+ { "id": "11", "code": "F000001", "name": "西安市教育局机关服务中心", "labels": [], "region": "西北地区", "status": "正常", "department": "大客户部", "manager": "李强", "customerType": "政府机构", "industry": "公共服务", "creditCode": "91610103MB1T2ABC88", "legalPerson": "何伟", "regCapital": "—", "operateStatus": "在业", "address": "陕西省西安市碑林区盐店街28号", "createTime": "2024-04-02 11:30:00" },
+ { "id": "12", "code": "A000004", "name": "青岛海洋生物医药研究院有限公司", "labels": ["开票异常"], "region": "华东地区", "status": "正常", "department": "销售一部", "manager": "陈静", "customerType": "企业客户", "industry": "医疗保健", "creditCode": "91370212MA3U5FGH22", "legalPerson": "罗斌", "regCapital": "3500万元", "operateStatus": "在业", "address": "山东省青岛市崂山区松岭路238号", "createTime": "2024-04-18 16:00:00" },
+ { "id": "13", "code": "E000002", "name": "郑州中原新能源汽车销售有限公司", "labels": ["严重拖欠", "涉诉/仲裁", "多头签约"], "region": "华中地区", "status": "黑名单", "department": "销售二部", "manager": "孙丽", "customerType": "企业客户", "industry": "制造业", "creditCode": "91410105MA9L6NOP44", "legalPerson": "丁凯", "regCapital": "1200万元", "operateStatus": "注销", "address": "河南省郑州市金水区经三路66号(已迁出)", "createTime": "2024-05-06 10:15:00" },
+ { "id": "14", "code": "A000005", "name": "个体工商户·林晓峰五金经营部", "labels": [], "region": "华东地区", "status": "正常", "department": "销售一部", "manager": "周杰", "customerType": "个人客户", "industry": "零售", "creditCode": "92330102MA2HY67890", "legalPerson": "林晓峰", "regCapital": "50万元", "operateStatus": "在业", "address": "浙江省温州市鹿城区车站大道789号", "createTime": "2024-05-22 09:20:00" },
+ { "id": "15", "code": "D000002", "name": "重庆山城火锅餐饮管理有限公司", "labels": [], "region": "西南地区", "status": "正常", "department": "市场开拓部", "manager": "吴磊", "customerType": "企业客户", "industry": "零售", "creditCode": "91500105MA60QRS111", "legalPerson": "黄丽", "regCapital": "300万元", "operateStatus": "在业", "address": "重庆市渝中区解放碑民族路188号", "createTime": "2024-06-01 12:45:00" },
+ { "id": "16", "code": "A000006", "name": "苏州工业园区精密模具厂", "labels": ["车辆/资产高风险", "制裁/高风险地区"], "region": "华东地区", "status": "黑名单", "department": "大客户部", "manager": "郑华", "customerType": "企业客户", "industry": "制造业", "creditCode": "91320594MA1W2TUV55", "legalPerson": "马军", "regCapital": "1800万元", "operateStatus": "在业", "address": "江苏省苏州市工业园区星龙街428号", "createTime": "2024-06-15 15:50:00" },
+ { "id": "17", "code": "E000003", "name": "长沙湘江科创投资基金(有限合伙)", "labels": ["频繁争议"], "region": "华中地区", "status": "正常", "department": "大客户部", "manager": "冯涛", "customerType": "企业客户", "industry": "金融", "creditCode": "91430100MA4L8XYZ66", "legalPerson": "谢文", "regCapital": "10000万元", "operateStatus": "在业", "address": "湖南省长沙市岳麓区麓谷大道658号", "createTime": "2024-07-03 08:30:00" },
+ { "id": "18", "code": "C000003", "name": "石家庄市桥西区智慧城市运营中心", "labels": [], "region": "华北地区", "status": "正常", "department": "市场开拓部", "manager": "韩雪", "customerType": "政府机构", "industry": "互联网", "creditCode": "91130104MB0X9DEF12", "legalPerson": "邓伟", "regCapital": "—", "operateStatus": "在业", "address": "河北省石家庄市桥西区中华南大街172号", "createTime": "2024-07-20 14:10:00" },
+ { "id": "19", "code": "A000007", "name": "合肥徽风电子元器件有限公司", "labels": ["频繁改账期"], "region": "华东地区", "status": "正常", "department": "销售二部", "manager": "曹阳", "customerType": "企业客户", "industry": "制造业", "creditCode": "91340100MA2U1BCD33", "legalPerson": "蒋欣", "regCapital": "900万元", "operateStatus": "在业", "address": "安徽省合肥市高新区望江西路800号", "createTime": "2024-08-08 11:55:00" },
+ { "id": "20", "code": "D000003", "name": "昆明滇池生态旅游开发有限公司", "labels": ["制裁/高风险地区", "多头签约", "开票异常"], "region": "西南地区", "status": "正常", "department": "销售一部", "manager": "许晴", "customerType": "企业客户", "industry": "公共服务", "creditCode": "91530100MA6N7KLM88", "legalPerson": "潘越", "regCapital": "4500万元", "operateStatus": "存续", "address": "云南省昆明市西山区滇池路1318号", "createTime": "2024-08-25 17:00:00" }
+]
diff --git a/src/prototypes/customer-management/index.tsx b/src/prototypes/customer-management/index.tsx
new file mode 100644
index 0000000..0dc6b09
--- /dev/null
+++ b/src/prototypes/customer-management/index.tsx
@@ -0,0 +1,243 @@
+/**
+ * @name 客户管理
+ */
+
+import '../vehicle-management/style.css';
+import './styles/index.css';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
+import { Download, FileUp, Plus } from 'lucide-react';
+import {
+ AnnotationViewer,
+ type AnnotationDirectoryRouteNode,
+ type AnnotationSourceDocument,
+ type AnnotationViewerOptions,
+} from '@axhub/annotation';
+import seedCustomers from './data/customers.json';
+import { FilterPanel } from './components/FilterPanel';
+import { CustomerTable } from './components/CustomerTable';
+import { CreatePage } from './pages/CreatePage';
+import { TablePagination } from '../vehicle-management/components/TablePagination';
+import {
+ EMPTY_CREATE_FORM,
+ EMPTY_FILTERS,
+ type CustomerCreateForm,
+ type CustomerFilters,
+ type CustomerPageId,
+ type CustomerRecord,
+} from './types';
+import { applyFilters } from './utils/filters';
+import annotationSourceDocument from './annotation-source.json';
+
+const DEFAULT_PAGE_SIZE = 10;
+
+type NavigationRef = {
+ openCreate?: () => void;
+ backToList?: () => void;
+};
+
+function validateCreateForm(form: CustomerCreateForm): string | null {
+ if (!form.customerType) return '请选择客户类型';
+ if (!form.customerName.trim()) return '请输入客户全称';
+ if (!form.cityProvince || !form.cityName) return '请选择所属城市';
+ if (!form.department) return '请选择业务负责部门';
+ if (!form.manager) return '请选择业务负责人员';
+ if (!form.invoiceTitle.trim()) return '请填写发票抬头';
+ if (!form.taxId.trim()) return '请输入纳税人识别号';
+ if (!form.bankName.trim()) return '请输入开户银行';
+ return null;
+}
+
+export default function CustomerManagementApp() {
+ const [records] = useState(seedCustomers as CustomerRecord[]);
+ const [view, setView] = useState('list');
+ const [pendingFilters, setPendingFilters] = useState(EMPTY_FILTERS);
+ const [appliedFilters, setAppliedFilters] = useState(EMPTY_FILTERS);
+ const [page, setPage] = useState(1);
+ const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
+ const [selectedIds, setSelectedIds] = useState>(new Set());
+ const [createForm, setCreateForm] = useState(EMPTY_CREATE_FORM);
+ const [toast, setToast] = useState('');
+ const navigationRef = useRef({});
+
+ const showToast = useCallback((msg: string) => {
+ setToast(msg);
+ window.setTimeout(() => setToast(''), 3000);
+ }, []);
+
+ navigationRef.current = {
+ openCreate: () => {
+ setCreateForm(EMPTY_CREATE_FORM);
+ setView('create');
+ },
+ backToList: () => setView('list'),
+ };
+
+ const filtered = useMemo(
+ () => applyFilters(records, appliedFilters),
+ [records, appliedFilters],
+ );
+
+ const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
+ const paged = useMemo(() => {
+ const start = (page - 1) * pageSize;
+ return filtered.slice(start, start + pageSize);
+ }, [filtered, page, pageSize]);
+
+ const handleSearch = () => {
+ setAppliedFilters(pendingFilters);
+ setPage(1);
+ setSelectedIds(new Set());
+ };
+
+ const handleReset = () => {
+ setPendingFilters(EMPTY_FILTERS);
+ setAppliedFilters(EMPTY_FILTERS);
+ setPage(1);
+ setSelectedIds(new Set());
+ };
+
+ const handlePageSizeChange = (size: number) => {
+ setPageSize(size);
+ setPage(1);
+ };
+
+ const handleToggleRow = (id: string, checked: boolean) => {
+ setSelectedIds((prev) => {
+ const next = new Set(prev);
+ if (checked) next.add(id);
+ else next.delete(id);
+ return next;
+ });
+ };
+
+ const handleToggleAll = (checked: boolean) => {
+ if (!checked) {
+ setSelectedIds(new Set());
+ return;
+ }
+ setSelectedIds(new Set(paged.map((record) => record.id)));
+ };
+
+ const handleOpenCreate = () => {
+ setCreateForm(EMPTY_CREATE_FORM);
+ setView('create');
+ };
+
+ const handleSubmitCreate = () => {
+ const error = validateCreateForm(createForm);
+ if (error) {
+ showToast(error);
+ return;
+ }
+ showToast('客户已保存(原型演示)');
+ setView('list');
+ };
+
+ const handleDirectoryRoute = useCallback((node: AnnotationDirectoryRouteNode) => {
+ const payload = node.payload as { pageId?: string } | undefined;
+ const pageId: CustomerPageId = payload?.pageId === 'create' ? 'create' : 'list';
+ if (pageId === 'create') {
+ navigationRef.current.openCreate?.();
+ } else {
+ navigationRef.current.backToList?.();
+ }
+ }, []);
+
+ const annotationOptions = useMemo(
+ () => ({
+ showToolbar: true,
+ showThemeToggle: true,
+ showColorFilter: true,
+ emptyWhenNoData: false,
+ toolbarEdge: 'right',
+ currentPageId: view,
+ onDirectoryRoute: handleDirectoryRoute,
+ }),
+ [view, handleDirectoryRoute],
+ );
+
+ return (
+ <>
+
+ {view === 'create' ? (
+