资讯详情

Bytebase 前端 React 迁移实践:Release 详情页 Strangler Fig 迁移计划全解析

发布时间:2026/9/16 23:25:11

500+
企业客户服务经验
120+
行业领域内容覆盖
3000+
原创页面设计沉淀
98%
客户满意度

Bytebase 前端 React 迁移实践:Release 详情页 Strangler Fig 迁移计划全解析

Bytebase 前端 React 迁移实践Release 详情页 Strangler Fig 迁移计划全解析【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase本文基于 Bytebase 仓库中的正式迁移计划文档 plan.md配套 definition.md、design.md 与 execution.md完整剖析一次从 Vue 到 React的页面级迁移是如何拆解为 8 个可独立验证的任务T1–T8并逐一下沉落地的。读完本文你将掌握Bytebase 前端 React 迁移的路线图式任务拆解方法Task Index 每任务的 Objective/Size/Files/Implementation/Boundaries/Dependencies/Validation 契约、Strangler Fig 迁移模式下路由层作为接缝、页面级原子替换的实操手法、useEffect race-guard 竞态防护与useVueState桥接 Pinia 状态的正确姿势以及一套可直接复用的 React 迁移任务模板与 CI 验证门禁清单。一、迁移背景为什么选择 Strangler Fig 模式Bytebase 前端正处于从 Vue 到 React 的渐进式迁移期仓库内置了专门的迁移 playbookdocs/plans/2026-04-08-react-migration-playbook.md。本次 Release 详情页迁移的设计design.md明确了三个直接塑造方案的参考模式路由级接缝、页面级替换。Strangler Fig绞杀藤迁移的核心是把旧页面逐步替换而非一次性重写先在旧系统中找出接缝替换其中一块其余部分共存迭代推进。本迁移的接缝就是路由注册表frontend/src/app/router/routes/dashboard.tsx中的PROJECT_V1_ROUTE_RELEASE_DETAIL条目——把component从 Vue 实现换成ReactPageMount.vuepage: ProjectReleaseDetailPage即可原子性地替换整个页面而ReleaseFileTable叶子组件继续为旧调用方服务。拒绝 Context改用自包含页面取值。Vue 侧通过provideReleaseDetailContext在四个组件间共享release、project、allowApply三个值React 官方指引明确 Context 不应该是首选三个值的浅层传递属于 props 范畴。React 页面自行 fetch release 与 project并以 props 下发给各局部组件。Effect 数据获取 清理旗标。每个路由参数对应单一资源projects/{projectId}/releases/{releaseId}useEffect配合ignore清理旗标即可处理竞态无需引入 TanStack Query 或 zustand——这与 playbook 默认不新增 TanStack Query 的规则一致。迁移的六个设计目标Design Goals按优先级排列且均可验证#设计目标可验证手段1与 Vue 页面行为对等归档横幅、应用抽屉创建 planrollout 并跳转、废弃确认、恢复、文件行点击打开 Monaco 详情、文档标题重置迁移前后对 Vue 树人工走查2除ReleaseFileTable.vue保留给CreateRevisionDrawer外被替换的 Vue 文件不再有存活调用方rg全仓搜索返回零命中3路由条目与兄弟路由PROJECT_V1_ROUTE_RELEASES对齐grep校验注册形式4不引入新的共享 React 状态抽象无createContext/ 新 zustand / 新 query 入口代码审查 缺失性验证5React 语言包 i18n 全覆盖pnpm --dir frontend check含check-react-i18n通过6前端 CI 门禁全绿fix/check/type-check/test四条命令退出码为 0二、任务总览一张 Task Index 看清全貌计划文档开篇用一行 Task Index 定义了任务粒度、依赖与规模S/M/L 表示体量S≈小、M≈中、L≈大Task Index: T1: ReactReleaseFileTable.tsx[M] — T2: ReactReleaseFileDetailPanel.tsx[M] — T3:ProjectReleaseDetailPage.tsx[L] — T4: Add missing React i18n keys [S] — T5: Switch route entry to React [S] — T6: Delete obsolete Vue files [S] — T7:ReleaseFileTable.test.tsxleaf test [M] — T8: Frontend CI gates [S]依赖关系T3 依赖 T1、T2T7 依赖 T1T1/T2 无依赖T4–T6、T8 各自独立。任务编排遵循先叶子、后页面、再接线、最后清理的递进顺序——先产出可复用的 React 叶子组件再组装页面切换路由删旧文件补测试跑门禁。三、T1ReactReleaseFileTable叶子组件 [M]目标Objective镜像 Vue 版ReleaseFileTable.vue产出 React 叶子组件使 React 页面可以渲染它并让CreateRevisionDrawer将来可以切换复用。规模一个新文件约 160 行包含中等复杂度逻辑选择同步、行点击模式。当前仓库中该组件已落地于 frontend/src/components/release/ReleaseFileTable.tsx。Props 契约与设计 §4 一致export interface ReleaseFileTableProps { files: Release_File[]; releaseType: Release_Type; showSelection?: boolean; // 默认 false rowClickable?: boolean; // 默认 true selectedFiles?: Release_File[]; // 默认 [] onRowClick?: (file: Release_File, e: React.MouseEvent) void; onSelectedFilesChange?: (files: Release_File[]) void; }实现要点用/components/ui/table下的Table、TableHeader、TableBody、TableRow、TableCell、TableHead原样移植 Vue 组件的表格结构。选择状态用本地useStateSetstring保存选中路径集合并通过useEffect从selectedFilesprop 同步对应源码中的useEffect(() { setSelectedPaths(new Set((selectedFiles ?? []).map((f) f.path))); }, [selectedFiles])对外变更通过onSelectedFilesChange回调把路径集合映射回Release_File[]数组applySelection中利用files.find((f) f.path path)重建对象列表。列布局选择列仅showSelection为 true 时显示、版本列、类型列、文件名列用w-*工具类宽度近似 Vue 的 160/64/128 三档宽度。选择列使用原生input typecheckboxReactui/组件库暂无Checkbox基元按 frontend/AGENTS.md 约定文本类输入仅强制Input复选框允许原生实现外层包td classNamepx-4 py-3并在onClick上阻止冒泡避免点击复选框触发行点击。行点击逻辑镜像ReleaseFileTable.vue:104-137选择模式下切换选中态非选择模式调用onRowClick。当rowClickable || showSelection为真时设置手型光标。实际源码中handleRowClick正是按此分支实现选择模式下先复制selectedPaths集合命中则删除、未命中则添加再调用applySelection。类型文本原样移植 Vue 的四分支 switch对应源码中useMemo内对Release_Type的判定且用releaseType satisfies never做穷尽性检查——DECLARATIVE 返回 SDLVERSIONED 返回t(issue.title.change-database)TYPE_UNSPECIFIED 返回空串。i18n通过useTranslation()取common.version、common.type、database.revision.filename、issue.title.change-database等键。边界Boundaries不得删除ReleaseFileTable.vue——CreateRevisionDrawer.vue:414仍是它的存活 Vue 调用方同一 PR 内两套实现共存这是 Strangler Fig 迁移容忍的共存态与 playbook 中BytebaseLogo/UserPassword的先例一致。不从 barrel 文件index.ts导出按路径直接 import。不得重写Release_Type的四分支 switch 逻辑必须原样移植。验证pnpm --dir frontend type-check零错误。四、T2ReactReleaseFileDetailPanel[M]目标镜像ReleaseFileDetailPanel.vue供页面内文件详情 sheet 使用。规模一个新文件约 90 行包含异步 sheet 获取与竞态防护。当前仓库中已落地于 frontend/src/components/release/ReleaseFileDetailPanel.tsx。实现要点Props 仅两个{ release: Release; releaseFile: Release_File }。状态两个statement: string与loading: boolean均用useState。异步获取与竞态防护核心useEffect监听releaseFile变化先置let cancelled false守卫然后调用sheetServiceClientConnect.getSheet({ name: releaseFile.sheet, raw: true })用new TextDecoder().decode(sheet.content)将内容解码进statement未取消时才写入cleanup 中置cancelled true。该模式与RevisionDetailPanel.tsx:92-135完全一致遵循 React 官方 You Might Not Need an Effect 中Effect 中做数据获取是合法用法但必须带清理旗标处理竞态的建议。渲染结构自 Vue 版移植头部行common.version{releaseFile.version}加粗元信息行database.revision.filename: {releaseFile.path}Hash: {releaseFile.sheetSha256.slice(0,8)}分隔线用/components/ui/separator的Separator /语句区标签 内联CopyButton将revision/RevisionDetailPanel.tsx:46-75的内联版CopyButton原样搬入本文件不共享——这是设计的显式 Non-GoalReadonlyMonaco/components/monacoclassNamerelative h-auto max-h-[480px] min-h-[120px]。加载指示loading为 true 时在 Monaco 上方渲染 lucide 的Loader2并加animate-spin。i18ncommon.version、common.statement、database.revision.filename。边界T6 之前不删除 Vue 版不导出共享 ReactCopyButton按设计 Non-Goal 内联不调用任何 Vue 的 Drawer/DrawerContent——sheet 外壳由页面持有。执行期路径修正见 execution.md计划中的 import/react/components/monaco实际无法解析该目录没有 barrel 文件改为按完整路径/react/components/monaco/ReadonlyMonaco导入与SchemaEditorLite/Panels/PreviewPane.tsx等既有调用方保持一致。五、T3ProjectReleaseDetailPage页面 [L]目标自包含的 React 页面替代ReleaseDetail.vue、BasicInfo.vue、NavBar/*.vue与context.ts四个 Vue 文件的组合。规模一个新文件约 500 行页面状态 头部 基础信息 应用到数据库 sheet 文件详情 sheet 内联DatabaseAndGroupSelector。当前仓库中已落地于 frontend/src/routes/project/ProjectReleaseDetailPage.tsx。导出签名export function ProjectReleaseDetailPage({ projectId, releaseId, }: { projectId: string; releaseId: string; })页面级状态第 2 步资源名内联构造自context.ts:42-44原样复制releaseName \${projectNamePrefix}${projectId}/releases/${releaseId}、projectName ${projectNamePrefix}${projectId}const release useVueState(() releaseStore.getReleaseByName(releaseName) ?? unknownRelease())const project useVueState(() projectV1Store.getProjectByName(projectName) ?? unknownProject())useEffect加ignore守卫依次调用projectV1Store.getOrFetchProjectByName(projectName)与releaseStore.fetchReleaseByName(releaseName)执行期实际改为直接使用 getter——store 内部已返回哨兵unknownRelease()/unknownProject()页面无需再兜底?? unknownX()allowApply hasPermissionToCreateChangeDatabaseIssueInProject(project)该纯 TS 工具函数可在 React 中直接调用每次渲染内联计算文档标题useEffect监听project.title调用setDocumentTitle(t(release.releases), project.title)——本页面挂载时路由必然是详情路由因此跳过 Vue 的路由名检查const [selectedReleaseFile, setSelectedReleaseFile] useStateRelease_File | undefined()驱动文件详情 sheet镜像ReleaseDetail.vue:46-53const [applyOpen, setApplyOpen] useState(false)驱动应用到数据库sheetconst [abandonOpen, setAbandonOpen] useState(false)驱动废弃确认弹窗。页面布局镜像ReleaseDetail.vue:1-11外层div classNameflex flex-col items-start gap-y-4 p-4 relative内部依次为局部组件ReleaseHeader、ReleaseBasicInfo、ReleaseFileTableonRowClick{(f) setSelectedReleaseFile(f)}。局部组件拆分设计 §3 表格文件内局部定义、不跨文件共享React 局部组件替代的 Vue 文件要点ReleaseHeaderNavBar.vueArchiveBanner条件渲染标题、ApplyToDatabaseButton、含废弃/恢复的DropdownMenu废弃流程用AlertDialog按 frontend/AGENTS.md Dialog vs Sheet 约定破坏性确认用 Dialog归档横幅内联渲染bg-warning/10 text-warning等不新建共享组件ReleaseBasicInfoBasicInfo.vue用HumanizeTs组件VCS 来源行内联渲染Link2图标 标签当前无 React VCS 图标内联a href{release.vcsSource.url} target_blank{beautifyUrl(...)}/a即可达到该表面的行为对等ApplyToDatabaseSheetApplyToDatabaseButton.vueApplyToDatabasePanel.vueSheet widthwideDatabaseAndGroupSelector以第四份内联副本的形式搬入本文件DatabaseSelectorDatabaseGroupSelector三函数一起从ProjectPlanDashboardPage.tsx:881-949复制执行期将会话 key 改为bb.release-apply-db-selector以避免与 plan 详情页实例冲突RPC 序列自ApplyToDatabasePanel.vue:103-154原样移植废弃/恢复流程废弃入口弹出AlertDialog标题t(bbkit.confirm-button.sure-to-abandon)、描述t(bbkit.confirm-button.can-undo)底部 Cancel/Confirm确认后调用releaseStore.deleteRelease(release.name)。恢复直接调用releaseStore.undeleteRelease(release.name)无确认弹窗镜像 Vue 行为。文件详情 sheetSheet open{!!selectedReleaseFile} onOpenChange{(o) !o setSelectedReleaseFile(undefined)}SheetContent widthwide标题优先使用已有键release.file否则用字面量 Release FileVue 原为字面文本内容为 T2 的ReleaseFileDetailPanel release{release} releaseFile{selectedReleaseFile!} /。创建计划回滚的 RPC 序列自ApplyToDatabasePanel.vue:103-154逐字移植不得改动先create(PlanSchema, { title, description, specs: [changeDatabaseConfig with release] })再planServiceClientConnect.createPlan随后rolloutServiceClientConnect.createRollout最后router.push(buildPlanDeployRoute({ projectId, planId }))。页面级边界不引入React.createContext或任何新 store——Pinia 是唯一数据源不抽取共享DatabaseAndGroupSelector内联三函数块不引入tanstack/query、zustand或任何新状态库废弃/恢复不加 Vue 之外的额外权限门禁不触碰 Monaco 内部实现。页面挂载约定页面解析器mount.ts的./pages/project/*.tsxglob 约定能自动解析page: ProjectReleaseDetailPage无需注册表修改。六、T4补齐 React i18n 键 [S]目标让 T1/T2/T3 消费的所有键在全部五个 React 语言包中可解析。文件五个语言包en-US、es-ES、ja-JP、vi-VN、zh-CN。每个键的值从frontend/src/locales/lang.json逐字复制不得翻译或改写Keyen-USes-ESja-JPvi-VNzh-CNcommon.versionVersionVersiónバージョンPhiên bản版本common.apply-to-databaseApply to databaseAplicar a la base de datosデータベースに適用Áp dụng cho cơ sở dữ liệu应用到数据库common.abandonAbandonAbandonar破棄Hủy bỏ废弃database.revision.filenameFilenameNombre del archivoファイル名Tên tệp文件名issue.title.change-databaseChange databaseModificar base de datosデータベースの変更Thay đổi cơ sở dữ liệu变更数据库bbkit.confirm-button.sure-to-abandonAre you sure to abandon?¿Estás seguro de abandonar?本当に破棄しますか?Bạn có chắc chắn muốn hủy bỏ?确定要废弃吗bbkit.confirm-button.can-undoYou can undo this action later.Podrás deshacer esta acción más tarde.この操作は後で元に戻すことができます。Bạn có thể hoàn tác hành động này sau.此操作可以撤销。注意common.restore、common.cancel、common.confirm、common.create、common.type、common.statement、common.copy、common.copied、common.close、common.archived、release.releases、release.files、database.selected-n-databases等键已存在不得重复添加。保持各语言包既有的字母序/分组序。执行日志显示本次共新增 45 行7 键 × 5 语言 结构修正。验证pnpm --dir frontend check零错误该命令会运行check-react-i18n由 scripts/check-react-i18n.mjs 与 locale 排序器共同把关。七、T5切换路由入口到 React [S]目标让PROJECT_V1_ROUTE_RELEASE_DETAIL挂载新的 React 页面。将路由定义整体替换为{ path: :releaseId, name: PROJECT_V1_ROUTE_RELEASE_DETAIL, meta: { requiredPermissionList: () [bb.releases.get], }, component: () import(/react/ReactPageMount.vue), props: (route: RouteLocationNormalized) ({ page: ProjectReleaseDetailPage, ...route.params, }), },这段配置与兄弟路由PROJECT_V1_ROUTE_RELEASES的注册形式完全对齐满足设计目标 3。关键点meta.requiredPermissionList保留[bb.releases.get]权限约束component换成ReactPageMount.vue后页面通过props传入page名称与路由参数projectId、releaseId——路由解析留在 router 边界React 页面保持自包含。执行期落点位于当前仓库的 frontend/src/app/router/routes/dashboard.tsx路由名常量注册于 frontend/src/app/router/handles.ts。验证pnpm --dir frontend type-check零错误rg Release/ReleaseDetail/\ frontend/src/router无输出路由不再 import 旧 Vue 树。八、T6删除废弃 Vue 文件 [S]目标删除无存活调用方的 Vue 文件同时保留仍被引用的ReleaseFileTable.vueCreateRevisionDrawer.vue:414仍 import 它。删除清单frontend/src/components/Release/ReleaseDetail/index.tsfrontend/src/components/Release/ReleaseDetail/ReleaseDetail.vuefrontend/src/components/Release/ReleaseDetail/context.tsfrontend/src/components/Release/ReleaseDetail/BasicInfo.vuefrontend/src/components/Release/ReleaseDetail/NavBar/index.tsfrontend/src/components/Release/ReleaseDetail/NavBar/NavBar.vuefrontend/src/components/Release/ReleaseDetail/NavBar/ApplyToDatabaseButton.vuefrontend/src/components/Release/ReleaseDetail/NavBar/ApplyToDatabasePanel.vuefrontend/src/components/Release/ReleaseDetail/ReleaseFileTable/ReleaseFileDetailPanel.vue保留清单frontend/src/components/Release/ReleaseDetail/ReleaseFileTable/index.tsfrontend/src/components/Release/ReleaseDetail/ReleaseFileTable/ReleaseFileTable.vue删除后的验证矩阵全部用rg扫frontend/srcrg Release/ReleaseDetail/index — 无命中 rg Release/ReleaseDetail/ReleaseDetail — 无命中 rg Release/ReleaseDetail/BasicInfo — 无命中 rg Release/ReleaseDetail/NavBar — 无命中 rg Release/ReleaseDetail/context — 无命中 rg ReleaseFileDetailPanel.vue — 无命中 rg ReleaseFileTable — 仅剩 CreateRevisionDrawer.vue:414Vue 新 React 文件 若有测试文件执行日志确认删除完成后rg ReleaseFileTable恰好命中 4 处保留的 Vue 文件与其 barrel、CreateRevisionDrawer.vue存活调用方、新的 React 文件。九、T7叶子契约测试——ReleaseFileTable.test.tsx[M]目标为 ReactReleaseFileTable编写契约测试设计 §8 的主叶子测试页面级与详情面板级测试按计划尾部约定不在本次范围。当前仓库中已落地于 frontend/src/components/release/ReleaseFileTable.test.tsx。实现要点环境设置IS_REACT_ACT_ENVIRONMENT true引入createElement、createRoot、act。Mockreact-i18next令useTranslation返回{ t: (k) k }——不做真实语言包加载测试聚焦组件契约。三个测试每个 30 行为每个Release_File渲染版本/类型/文件名单元格默认隐藏选择列showSelectiontrue时显示非选择模式下点击行触发onRowClick并携带该 file。测试夹具最小化两个Release_File对象仅填充path、version、sheetSha256、sheet字段releaseType Release_Type.VERSIONED。执行期改用create(Release_FileSchema, ...)构造合法 proto 实例与仓库其他测试夹具模式一致而非纯对象字面量。边界不 mockui/table内部渲染真实组件不测选择切换的边界情形Vue 版本无测试此测试是对等性 最低保障。验证pnpm --dir frontend test ReleaseFileTable.test— 3/3 通过。十、T8前端 CI 门禁 [S]目标确认fix、check、type-check、test全部通过。无文件改动fix可能自动编辑。按序执行pnpm --dir frontend fix— 格式化 lint import 排序仅自动修复pnpm --dir frontend check— CI 模式校验不做修改含 biome、react-i18n、locale 排序pnpm --dir frontend type-check— TypeScript / vue-tsc 类型检查pnpm --dir frontend test— 完整 vitest 套件。若fix修改了文件需再跑一遍check与type-check。验证标准四条命令退出码均为 0除main分支既有问题外无新增 lint 错误。十一、数据流与组件分解总结设计 §6 给出了迁移后的完整数据流——无 Context、全 props 直传、Pinia 作为唯一数据源、useVueState桥接响应式更新Vue router └─ ReactPageMount.vue (pageProjectReleaseDetailPage) └─ ProjectReleaseDetailPage (fetch, useState, useVueState) ├─ ReleaseHeader (release, project, allowApply, onArchive, onRestore) │ ├─ AlertDialog (abandon confirm) │ └─ ApplyToDatabaseSheet (release, project) ├─ ReleaseBasicInfo (release) ├─ ReleaseFileTable (files, releaseType, onRowClick) └─ Sheet (selectedReleaseFile) └─ ReleaseFileDetailPanel (release, releaseFile)这一架构同时回答了设计中的两个开放问题文件表放在react/components/release/独立目录而非内联进页面CreateRevisionDrawer迁移时可复用避免二次内联文件详情以Sheet widthwide承载。十二、范围外任务Out-of-Scope与执行结果验证计划明确排除的工作防止范围蔓延页面级测试ProjectReleaseDetailPage.test.tsx与ReleaseFileDetailPanel.test.tsx——需 mock 六个以上服务与五组 Vue 响应式 getter先例页面均无页面级测试改由设计目标 1 的人工走查 设计目标 6 的 CI 门禁双层覆盖抽取共享 ReactDatabaseAndGroupSelector、共享 ReactCopyButton均为显式 Non-Goal需要处各自内联移植VCSIcon.vue、ArchiveBanner.vue、HumanizeDate.vue、EllipsisText.vue及/components/v2的 Drawer/CopyButton 为可复用 React 基元迁移CreateRevisionDrawer.vue这正是 T6 保留ReleaseFileTable.vue的原因改动 apply RPC 序列、为废弃/恢复加权限门禁、重设计页面布局。执行结果见 execution.md 完成声明8 个任务全部完成React 页面挂载于PROJECT_V1_ROUTE_RELEASE_DETAIL新增两个叶子组件与一个 3/3 通过的测试删除九个 Vue 文件并保留ReleaseFileTable.vuei18n 新增 7 键 × 5 语言最终全量test通过 114 个测试文件 / 1420 个测试用例。从当前仓库结构看迁移成果已进一步演进合并页面落于 frontend/src/routes/project/ProjectReleaseDetailPage.tsx叶子组件与测试落于 frontend/src/components/release/路由注册位于 frontend/src/app/router/routes/dashboard.tsx。整个迁移以单个 PR、单一提交链、无功能开关的方式合入回滚只需还原路由条目并恢复叶子文件爆炸半径始终被限制在路由层。这份计划文档的价值在于它将一次页面级框架迁移固化为任务契约 验证矩阵 边界声明的标准化流程任务边界、依赖顺序、删除安全性与验收标准全部显式化是 Bytebase 后续 React 迁移批次release 列表页、plan 详情页等可以直接复用的操作模板。【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
热门专题

继续阅读更多专题内容

围绕企业服务、数字化转型与官网运营的常青话题,持续输出深度内容

企业官网建设指南 企业托管服务模式 财税政策与解读 企业数字化转型 官网SEO与获客 网站安全与运维
配套服务

读完这篇文章,了解更多服务

从整站搭建到SEO布局,17项核心服务助您打造高转化的企业官网

01

企业托管整站搭建

从信息架构到栏目预留,搭建可生长的企业站点骨架,每个页面独立原创设计。...

了解详情
02

规整可信网页设计

雪地靴温暖风原创设计,金属铜线条贯穿全页,拒绝通用模板与AI流水线。...

了解详情
03

企业服务SEO布局

关键词体系与语义化结构,从建站源头为搜索排名而生。...

了解详情
04

业务预约咨询表单

多场景表单与线索收集体系,把访问流量转化为可追踪的销售线索。...

了解详情
05

企业服务站点运维

安全巡检、数据备份与内容更新支持,全年守护网站稳定运行。...

了解详情
06

全终端商务适配

电脑、平板、手机一致呈现,移动端体验与转化同样出色。...

了解详情
需要专业建议?

让专业顾问为您解读行业趋势

关于企业官网建设、SEO获客与数字化转型的任何疑问,欢迎一对一咨询我们的专业顾问。