ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SAP B1与钉钉审批集成方案:从采购申请到ERP单据自动同步

SAP B1与钉钉审批集成方案:从采购申请到ERP单据自动同步 平时做SAP B1项目接触过不少中小企业客户最常被问到的需求除了财务月结就是能不能让钉钉上的审批流直接进ERP。说实话很多公司内部跑的都是两套系统员工日常审批在钉钉上完成流程确实快但单据永远只在手机里躺一个已通过的状态另一边财务和采购在SAP B1里还得手动把钉钉上的申请一笔一笔录进去。时间一长数据对不上、单据丢单、月底结账前疯狂补录都是常态。这篇主要讲的就是怎么把SAP B1里的采购申请单审批完整放到钉钉上来跑员工在钉钉上填一张采购申请单走完企业内部的审批流之后系统自动把数据写到SAP B1里生成正式的Purchase Request单同时把SAP B1的单号回推给钉钉。整个过程尽量少人工干预审批记录和ERP单据能一一对应。如果你正在做SAP B1的二次开发、准备接钉钉的开放平台接口或者所在公司在纠结要不要做OA审批和ERP的集成这篇文章可以作为一份选型和落地的参考。下面是我在实际项目里的完整方案包括架构设计、字段映射、接口调用、踩过的坑文末还有一份高频问题清单。1. 为什么采购申请审批一定要打通SAP B1与钉钉1.1 双轨办公下的数据孤岛问题采购申请这个场景特别典型。SAP B1在中小制造和贸易企业里覆盖率很高但绝大多数实际使用B1的公司一线员工并不是天天坐在电脑前对着ERP客户端操作的。销售要申请样机、车间要申请辅料、行政要申请办公用品这些场景天然发生在移动端所以钉钉几乎是标配。问题就出在这里员工在钉钉上提交审批审批通过之后这张申请单如果没人去B1里补录采购部门就没法在ERP里安排后续的采购订单。反之如果采购人员在B1里手动建了单钉钉上又显示审批已完成两边各记各的账月底一查同一笔采购到底批没批、ERP里有没有建单完全对不上。这就是典型的双系统数据割裂。我见过最夸张的情况是一家做电子元件的贸易公司采购申请全靠钉钉审批通过后截图发给采购采购再照着截图在B1里录单。截图里的品名规格和B1物料主数据不完全一致录单的人还得一个个去核对物料编码。一个月有300多张采购申请录错、录漏、重复录的至少占两成。1.2 传统方案的三条路为什么都不彻底在正式做接口集成之前大部分企业解决这个问题的思路无外乎三条路纯手工录单。审批走钉钉录单走B1两边靠人肉同步。优点是没有开发成本缺点是效率低、错误率高、过程不透明。人工录单还有另外一个问题就是延迟。审批通过后隔几天才录进系统等于采购周期被人为拉长了。截图/邮件审批。也有企业干脆不走钉钉审批而是让申请人把采购需求发邮件或者直接截图给采购部门采购在B1里建单后再用邮件回复确认。这种方式比第一种稍微集中一些但审批痕迹分散在个人邮箱里审计的时候非常被动。单独开发一套审批系统。有些企业选择了自建一套独立的采购申请系统开发周期长、成本高而且一线员工还要多学一套系统推广阻力很大。更麻烦的是这套自建系统还是得和B1做接口等于绕了一圈工作和成本一点没少。这三条路的共同问题是把审批过程和ERP数据当成两件事在管。而集成的核心思路恰恰相反审批过程可以留在钉钉但审批的结果必须直接变成ERP里的业务单据让钉钉成为ERP的移动端入口而不是一个孤立的审批工具。1.3 集成方案到底能带来什么从项目落地后的实际效果来看打通之后最直观的变化有几点单据生成时间从人工补录变成审批通过后自动同步采购申请到采购订单的周期能缩短1到2天物料编码、供应商编码、税率、仓库等主数据来自B1侧校验录单错误基本消除每张审批单都能在B1里找到对应的原始申请审计追溯时有完整的链路钉钉上完成审批的员工能看到最终生成的B1采购申请单号心理上的确定感完全不一样。这里要特意说明一点集成不是要把钉钉改造成B1也不是要把B1的完整业务流程搬到钉钉上。整个方案的服务边界是采购申请单这一步后面的采购订单、收货、发票校验仍然留在B1内部钉钉只负责触达和审批。2. 整体数据流从钉钉审批发起到SAP B1采购申请落库2.1 主数据流与单据状态定义这个方案里最简单的理解方式是钉钉管审批B1管账本中间要有一个翻译官。整体数据流大致如下申请人在钉钉的采购申请模板里填写申请单指定审批人提交钉钉审批流按照预设的规则进行审批一级审批、二级审批、条件审批等审批流结束后钉钉开放平台通过回调事件把审批结果同意/驳回/撤回推送给集成中间件中间件校验审批结果调用SAP B1的Service Layer接口在B1里创建采购申请单创建成功后中间件把B1生成的单据编号更新回钉钉的外部系统字段或者单独发一条通知给申请人如果写ERP失败中间件记录失败日志并触发重试同时通知IT管理员介入。整个流程里中间件是核心。它既要接收钉钉的回调又要调用B1的接口还要负责状态记录、异常处理、幂等控制和数据转换。建议在数据库里维护一张单据状态表把每条申报的运行状态记录下来。2.2 中间件的角色为什么需要一层翻译官有些朋友可能第一反应是能不能让钉钉直接调用SAP B1的Service Layer接口技术上不是完全不行但实际做的时候很容易被几个问题卡住Service Layer的鉴权方式和企业网络环境直接暴露给钉钉的服务器安全性很难保障钉钉审批回调是异步、事件驱动的如果B1接口响应超时或者不可用钉钉不会帮你做复杂的重试处理审批结果里的数据是钉钉表单的语义物料编码、供应商编码需要先映射成B1侧的主数据格式这个转换逻辑放在钉钉和B1之间的独立服务里更灵活。所以中间件最基本、最朴素的职责是当一个翻译官可靠投递员。它接收钉钉的JSON报文解析出需要的字段转换成Service Layer需要的JSON结构调用B1接口返回结果回写。中间件本身可以是一个小型的Spring Boot服务也可以是一套Python服务甚至用云函数实现取决于团队熟悉什么技术栈。我在项目里用的是Java Spring Boot MySQL部署在客户内网的一台Windows服务器上。选Java没有特别的宗教原因主要是客户内部有Java维护能力而且Spring Boot在定时任务、HTTP调用、日志管理这些方面都很成熟。如果你团队更熟Python用FastAPI或者Flask也完全可以核心是这层服务要足够简单、稳定、可监控。2.3 单据状态机设计这步容易被忽略但非常重要。建议在中间件里维护一张pr_sync_log表字段大致如下字段说明id自增主键process_instance_id钉钉审批实例IDform_data_json钉钉审批表单原始数据status0-待处理1-处理中2-成功3-失败4-已重试b1_doc_entrySAP B1生成的单据内部IDb1_doc_numSAP B1生成的单据编号error_msg失败原因retry_count重试次数create_time / update_time创建和更新时间状态流转其实就两种主线审批通过待处理 → 处理中 → 成功或失败 → 重试 → 成功/终态失败审批驳回/取消直接标记为终态不调用B1这张表不只是日志也是异常处理的基础。后期排查问题、统计成功率和响应耗时全都靠它。另外process_instance_id要加唯一索引这是做幂等控制的第一道保障后面会细说。3. 钉钉侧实现审批模板配置与回调机制3.1 采购申请表单字段设计钉钉这边的第一步是在钉钉管理后台创建一张采购申请单审批模板。表单字段的设计要结合B1采购申请单的核心字段来定不要原封照抄线下纸质单。建议的表单设计如下钉钉表单字段组件类型说明申请事由单行输入必填作为SAP B1单据的备注/事由供应商关联审批单/下拉单选建议用下拉数据从B1供应商主数据导入采购类别下拉单选区分原材料、办公用品、固定资产等申请日期日期默认当天写入B1的申请日期需求日期日期必填写入B1的需求日期预算中心/部门下拉单选用于B1里按部门核算和B1的利润中心对应明细列表明细组件物料编码、物料名称、数量、含税单价、备注明细组件是钉钉表单里比较常用的一个能力审批人可以在手机上直接打开明细逐行查看。明细默认有展开收起的功能行数多的申请单在手机上也不会太卡。关于物料编码这个字段不同企业差异很大。有的企业一线员工熟悉物料编码可以直接填有的企业员工只写品名规格需要采购后补编码。我建议如果物料主数据比较规范尽量在下拉/关联选择里限定范围避免审批通过后写B1时找不到物料号导致失败。3.2 审批流的组织架构匹配钉钉审批流的配置相对简单通常按照部门负责人、部门负责人采购负责人、金额分级审批等策略配置即可。这里要提醒的是钉钉审批流条件规则依赖组织架构和角色上线前要把审批人名单重新梳理一遍。常见的一个坑是按角色选审批人比如部门主管时钉钉会自动识别发起人的直接上级。但如果员工在组织架构里所在的部门是空的或者部门主管角色没有设定审批流会报无审批人错误流程会直接卡在发起环节。这类问题在集成方案里需要提前排查不然后续流程根本走不起来。3.3 审批结果回调订阅事件方式钉钉侧最关键的技术点是审批结果如何通知到中间件。目前主流的做法是使用钉钉开放平台的事件订阅能力。在钉钉开放平台的开发者后台创建一个企业内部应用申请以下权限审批实例的读取权限审批实例的详情通讯录只读权限获取用户信息用于用户映射然后在事件订阅里订阅这两个事件bpms_instance_change审批实例状态变更包括审批开始、审批结束等场景bpms_task_change任务状态变更一般用于跟踪每个审批节点的动作订阅事件时需要配置一个回调URL钉钉会往这个URL推送加密的JSON报文。这里有两个容易踩的细节加密方式。钉钉的事件回调默认使用AES加密需要你在开发者后台配置一个AES Key和Token。接收回调时要先解密再用Token做签名校验。如果加密解密配置不一致钉钉后台会一直显示订阅回调测试失败。响应超时。钉钉的回调推送有超时机制业务处理完要同步返回一个加密的success字符串。如果你在回调接口里同步调用B1接口创建单据万一Service Layer响应很慢超过钉钉的超时时间钉钉会认为推送失败并重复推送。所以回调接口里尽量只做接收事件落库异步处理这三件事把B1写入放到异步队列或者定时任务里去执行避免因为处理超时导致重复推送。3.4 多公司/多账套的标识问题做SAP B1项目的朋友都知道一套Service Layer可能对应多个账套/公司或者一个企业同时有多个独立核算的分公司。这种情况在钉钉侧怎么区分方案是在审批模板上增加一个不可见字段比如叫公司编码由系统通过钉钉的流程表单外部选项能力预填或者在模板里放一个下拉框由申请人选择公司。字段值会随着审批单数据一起回传中间件收到后根据这个字段决定调用哪个Service Layer实例。这个字段在表单上是否可见、是否允许修改要根据实际管理需求来设置。4. SAP B1侧写入Service Layer与DI API的选型对比及采购申请单核心字段映射4.1 Service Layer还是DI API怎么选SAP B1的集成接口历史上常用的是DI API和DI Server现在更推荐使用Service Layer。对比项DI APIService Layer技术栈依赖COM组件Windows环境REST API跨平台部署要求需要安装B1客户端/DLL需要安装Service Layer服务性能单条操作快但并发能力一般支持批量HTTP并发更好维护性版本升级容易出兼容问题接口版本由SAP管理社区/文档老资料多官方标准接口逐步成为主流如果你是在新项目里做集成优先用Service Layer。一方面它不需要在中间件服务器上安装B1客户端另一方面Service Layer在SAP HANA版本的B1上支持得最好。连接Service Layer的标准方式是安装B1的时候选择以公司数据库和单一公司模式启动Service Layer如果有多个账套也可以用管理公司模式。连接时用B1超级管理员账号或者专门创建的集成账号先调用登录接口获取SessionId之后每次请求Token放在Cookie里。登录接口的典型请求如下POST http://service-layer-host:50000/b1s/v1/Login Content-Type: application/json { CompanyDB: SBO_YourCompany, UserName: manager, Password: your_password, Language: 24 }请求成功后会返回一个SessionId后续接口调用时在请求头加上Cookie: B1SESSIONSessionId即可。注意SessionId是有有效期的默认是30分钟左右建议在中间件里做Session的缓存和自动重新登录不要每次请求都登录一次。4.2 采购申请单Purchase Request的核心字段映射SAP B1里采购申请单对应的对象是PurchaseRequest数据表底层是OPRQ主表和PRQ1明细行。Service Layer的请求体结构如下核心字段版{ DocType: dDocument_Items, DocDate: 2025-06-01, DocDueDate: 2025-06-05, ReqDate: 2025-06-01, ReqType: rntEmployee, Requester: 12, RequesterName: 张三, U_ApprovalProcessID: ding1234567890abcdef, U_DingTalkInstanceID: proc1234567890, Comments: 生产急需物料采购申请, PurchaseRequestLines: [ { ItemCode: A0001, Quantity: 100, Price: 12.5, TaxCode: VAT17, U_RequireDate: 2025-06-05, LineText: 用于产线A的紧急补充物料 } ] }逐字段解释下为什么这么设DocDate单据日期建议直接用钉钉表单里的申请日期不要用系统当天日期。因为有些公司要求按实际业务日期入账万一审批跨天、提交日期和审批日期不一致用提交日期更符合业务事实。DocDueDate到期日期。一般等于申请人填的需求日期采购部门拿到这个单子后需要在这个日期前完成采购订单的下达。ReqType / Requester申请类型和申请人ID。ReqType可以设置为员工rntEmployeeRequester对应B1内部的员工ID。更简单的做法是直接用RequesterName但为了报表统计准确性还是建议维护一张钉钉用户ID和B1员工ID的映射表。U_ApprovalProcessID / U_DingTalkInstanceID自定义字段存储钉钉的审批流ID和审批实例ID。这是整个集成追溯链路的桥梁字段建议加到B1的采购申请单界面上并创建索引后期按钉钉审批单号反查B1单号、按B1单号反查钉钉审批记录都靠这两个字段。PurchaseRequestLines明细行。关键字段是ItemCode物料编码、Quantity数量、Price价格、TaxCode税码。特别注意如果B1物料主数据里设置了税码通常会被带到单据行里但钉钉表单里如果允许申请人填含税单价需要转换或者直接由B1端的税码规则计算避免同一物料在不同单据上税率不一致。4.3 单据编号策略用B1自动编号还是自定义编号写采购申请单时另一个需要提前确定的是单据编号策略。SAP B1默认的采购申请单编号规则是OPRQ.DocNum由系统按序列自动生成。如果你不做任何干预直接POST请求后B1会返回一个系统单据编号。这里有一个常见的认知误区有人以为可以在Service Layer请求里自定义DocNum实际上Service Layer默认是不允许直接修改DocNum的因为编号规则被系统序列管控。如果你确实需要自定义编号比如希望编号里包含部门代号需要在B1的编号序列配置里做文章而不是在Service Layer请求里硬编码。我的建议是编号走B1系统序列不做自定义。原因很简单自定义编号的规则维护成本高而且一旦和钉钉侧的回写顺序冲突很容易出现编号重复或者空号。集成追溯靠的是U_DingTalkInstanceID不是靠单号格式。单号留给B1管钉钉侧通过回写字段展示。4.4 联系细节Service Layer 创建采购申请单的完整调用示例这里给出一个相对完整的创建采购申请单的代码示例以Java RestTemplate为例public String createPurchaseRequest(String jsonBody, String sessionId) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set(Cookie, B1SESSION sessionId); HttpEntityString request new HttpEntity(jsonBody, headers); String url https://service-layer-host:50000/b1s/v1/PurchaseRequest; try { ResponseEntityString response restTemplate.postForEntity(url, request, String.class); if (response.getStatusCode().is2xxSuccessful()) { JSONObject obj new JSONObject(response.getBody()); // obj里面会返回 DocEntry 和 DocNum return obj.getString(DocNum); } else { // 具体错误信息在response body的error字段中 throw new RuntimeException(创建采购申请失败: response.getBody()); } } catch (HttpStatusCodeException e) { // 错误码处理400通常是字段校验失败500通常是服务端异常 throw new RuntimeException(Service Layer调用异常, e); } }需要注意Service Layer 在返回400错误时响应体里的error.message会有非常详细的字段校验异常描述比如字段 XXX 必填、物料 A0001 不存在。中间件要把这些错误信息完整记录到日志表里这样排查问题时能直接定位到是哪一行的哪个字段出问题。5. 状态同步与边界情况驳回、撤回、离职审批人5.1 审批驳回时如何回写状态这是很多人做的时候容易漏的场景。钉钉审批通过后集成到B1大家都能想到。但驳回呢驳回后申请人在钉钉上修改一下表单再重新提交此时钉钉会创建一个全新的审批实例。如果你不做任何处理系统里同一个采购需求可能会有两条审批记录一条驳回、一条通过而B1只收到通过的这条表面上没问题但审计时容易说不清楚。建议的处理方式在中间件里维护一个关联申请ID的概念。钉钉表单里加一个隐藏字段U_OldInstanceID如果申请人是修改后重新提交通过钉钉的流程表单功能把上一版审批实例ID带过来。中间件收到新的审批通过事件后如果U_OldInstanceID有值先检查B1里是否已经有一张对应的采购申请单通过U_DingTalkInstanceID查询如果有可以选择先不删除旧单因为B1里的采购申请单可能已经被采购订单引用而是创建一个新单并在备注里写明此单为对原审批单XXX的替代申请。这一点对实施团队尤其重要。采购申请单不比草稿它在B1里如果已经关联了下游的采购订单就不能随便删除或者取消。重新提一张全新的单子在业务上反而是最安全的。5.2 审批撤回和修改再提交的双写问题钉钉审批流还有一个很坑的细节审批人如果在审批过程中点了退回并修改申请人的表单会回到草稿状态修改后可以重新提交生成的是一个全新的审批实例ID。此时如果中间件只认U_DingTalkInstanceID做唯一标识那么同一条采购需求就有多个审批实例IDB1里可能出现重复单据。解决思路有两种第一种主数据层面。把钉钉表单的申请事由申请人提交日期作为业务唯一键在中间件里做重复判断。但这种方式容易误伤比如同一个申请人同一天提交两笔完全相同的物料申请会被误判为重复。第二种用隐藏字段维护业务关联ID。在钉钉表单里加一个U_BizRequestID业务申请ID提交时如果该字段为空则自动生成一个UUID修改后重新提交时保留之前的UUID。中间件以U_BizRequestID作为业务唯一键判断重复确保同一个业务申请ID只在B1里创建一张单据。第一次创建成功后把B1单号回填到钉钉的单据编号字段如果后续又收到同一U_BizRequestID的审批通过事件直接更新已有单据或者忽略避免重复创建。我强烈建议采用第二种方案。业务唯一键的问题不要依赖自然字段拼凑单独维护一个业务申请ID字段逻辑清晰排查也方便。5.3 审批人离职或者组织架构变动这个坑在项目运维阶段最让人头疼。钉钉审批流经常配置的是按角色找审批人如果某个部门的主管离职了组织架构里没有及时更新新主管钉钉会认为审批人不存在整个流程卡住。更麻烦的是如果管理员在钉钉后台直接删除了离职员工的账号而该员工的审批单子还处于审批中状态钉钉会允许其他管理员代审但这个代审操作对审批流程的推进方式有要求配置错了会直接导致流程终止。应对手段上线前做一轮审批人和角色的梳理和HR系统对齐信息上线后配置一个钉钉审批流失败通知人通常是IT负责人一旦出现无审批人异常第一时间能在群里收到通知。这个看起来简单实际项目里有很多企业因为没做这一步流程卡了两三天都没人发现。5.4 失败补偿定时任务与手工修复通道再稳定的中间件也难免出现极端情况Service Layer临时不可用、密码过期、网络抖动。这种情况靠同步重试解决不了问题需要一套补偿机制。我在中间件里做了一个定时任务每5分钟扫描一次pr_sync_log表找出所有status3失败且retry_count小于5的记录重试一次。重试时先检查B1里是否已经存在相同U_DingTalkInstanceID的单据存在则直接标记为成功并回写单号不存在才真正发起创建。超过5次重试还是失败的单据状态变为终态失败系统自动发钉钉消息通知IT管理员。管理员登录中间件后台可以查看完整的请求和响应日志手动修正数据后点重新执行。这个手工修复通道在项目初期特别有用很多问题都是靠这一步兜底解决的。6. 实测中遇到的高频问题与处理清单最后把这些年在类似项目里真正踩过的坑梳理成一份清单每一类都附了解决思路后续实施时可以对照自检。时间字段时区差异钉钉和B1默认时区不同是高频问题。钉钉服务器用的UTC时间你拿到的日期时间字符串如果不做转换直接PUT给B1经常出现差8小时的情况。建议在中间件里统一用Asia/Shanghai时区解析钉钉回传的日期字段然后再转成B1需要的日期格式。解决方案在读取钉钉事件的createTime、表单日期字段时显式指定时区不要依赖服务器系统时区。金额精度不一致钉钉表单里的金额数字默认是浮点数传输B1的金额字段通常是两位小数库存币种。如果你把钉钉那头的含税金额原样传给Service Layer遇到长小数比如0.10.2这种很容易出现实际写入金额比申请金额多0.01元的问题。解决方案中间件解析时统一用BigDecimal或Decimal类型做两位小数四舍五入后再组装JSON不要在字符串阶段就直接拼接。钉钉用户与B1员工ID映射钉钉的userid形如uidRandomString和B1的员工ID没有任何天然对应关系必须建一张映射表。映射表的维护方式建议做成员工自维护管理员审核员工在钉钉上首次使用系统时填写B1员工编号管理员在后台审核绑定。审计比较严格的公司尽量做成和HR系统或AD打通自动同步。Service Layer并发锁冲突如果同一个B1账套同时被多个外部系统调用比如其他自建系统也在写B1Service Layer偶尔会返回Could not lock object之类的错误。这类错误通常是并发写同一类单据导致的。解决方案在中间件里对同一种单据的创建请求加一把分布式锁按账套粒度其他请求排队等待避免并发冲突。附件要不要传到B1钉钉审批单里经常有附件比如供应商报价单扫描件、技术图纸B1的采购申请单本身也支持附件管理Attachments2。但在实际项目里我不建议默认把钉钉的附件自动同步到B1。第一B1附件存的是附件路径/URL文件本身还是要在文件服务器或对象存储里放一份等于双份存储第二B1采购申请单的附件在后续单据流转中很少被下游真正读取默认同步反而增加接口复杂度和存储成本。我的建议是首次实施阶段不同步附件B1单据的备注里记录钉钉审批实例ID需要看附件时直接从钉钉审批详情打开。如果后续业务上真的有必须在B1里离线查看附件的需求再单独做一个附件下载同步功能。幂等控制必须做钉钉的事件订阅在某些场景下会重复推送同一事件网络超时后重试、后台手动重推等Service Layer的调用如果重复执行B1里就会出现两张完全相同的采购申请单。所以中间件在处理前必须先根据process_instance_id在pr_sync_log表里查重。如果已经存在且处理成功直接幂等返回不再调用B1。用户映射缺失时的临时处理钉钉上提交申请的员工如果在映射表里找不到对应的B1员工ID建议不要直接报错而是走单子先落进B1申请人字段留空后续由管理员补录的流程。因为采购申请往往对时效性要求高因为一个用户映射问题卡住整个业务不划算。这个宽进严出的思路在很多集成项目里都能少踩不少坑。最后分享一点心得这个项目做完之后我最深的体会是SAP B1和钉钉的集成技术上本身并没有多难真正难的是把业务边界想清楚。钉钉解决的是人怎么申请、怎么审批B1解决的是账怎么记、单怎么流转中间的桥怎么搭完全取决于企业的管理粒度。另外集成方案上线后一定要留一个星期到一个月的双人并行观察期。我见过不少项目接口上线第一天就急着把钉钉上的线下流程全部停掉结果发现B1里单据的字段映射有些地方不符合采购部门的实际习惯又灰溜溜地恢复线下。稳妥的做法是先并行跑半个月每天让采购人员在B1里抽查几张自动同步的单子确认无误后再彻底切换到新流程。最后再分享一个小技巧调钉钉接口和Service Layer的时候强烈建议在中间件里给每条外部请求和响应都打印全链路日志格式包含时间戳、审批实例ID、B1单据号、耗时。这个习惯在项目初期看起来有点繁琐但等系统上线几个月后任何一次数据对不上你只需要打开日志表点开一条记录就能知道这条单据走到哪一步、在哪一步出了问题省下的排查时间绝对是当初写日志代码的好几倍。
返回列表