iOS高仿支付宝实战:从架构到UI还原的完整指南
2026/9/2 4:25:26 网站建设 项目流程

简介:这是一份完整的高仿支付宝iOS工程源码包,基于GitHub开源项目整理,适合具备Objective-C基础、希望深入研究复杂App界面架构与交互动效的开发者。压缩包共293个文件,整体仅343KB,涵盖Objective-C的.m/.h源码、JSON配置文件、XIB界面布局、plist属性列表以及85张PNG图片素材,各类型文件对应视图控制、数据解析、界面模板与图标资源,模块边界清楚,便于按需查阅。项目已实现支付宝Home页图标长按拖动排序、删除及持久化存储,集成二维码/条形码扫描、服务、财富和余额宝页面,其中余额宝收益数字采用动态滚动展示;同时包含手势解锁界面、其他详情页等待完善模块,可作为继续迭代的起点。目前已有3177人浏览学习,既可当作仿写真实App的练手项目,也能为课程设计或面试作品提供完整可运行的参考实现。

1. 为什么选支付宝当练手项目:拆解"高仿"背后的学习价值

早几年我就想找一个能把 iOS 开发里各个知识点串起来的项目练手,找来找去,最后锁定了"高仿支付宝"这个方向。说实话,网上一搜这个关键词能出来一大堆 Demo,但多数都是拿几个 Tab 页面拼一拼就算完事。我自己的体会是:支付宝这个 App 的界面复杂度、交互密度和信息层级,在整个移动互联网产品里都是数一数二的,你如果真能把它"高仿"到七八成像,那 iOS 开发里的大半边天你基本都摸过了。

先说清楚边界:我做这个东西的定位是学习型 Demo,目标是还原支付宝的 UI 结构、页面跳转逻辑、核心交互流程和本地数据展示,用于练手和面试作品展示。它不接真实支付、不碰真实账户体系,也绝对不涉及任何对支付宝品牌的冒名或误导性使用。做出来的东西是一份代码作品,不是一个可以上架的仿冒产品。这个性质你一定要想明白,否则后面所有技术选型都会跑偏。

为什么支付宝适合当练习对象?你拆开看就知道:它有复杂到极致的首页宫格区、有典型的"黄金八宫格"图文入口、有扫码和收付款这种高频交互、有账单和明细这类典型的列表页、有需要 H5 和原生混编的运营页、还有瀑布式信息流和搜索页。一个 App 里几乎囊括了 iOS 开发的所有基础组件使用场景:UITableView、UICollectionView、UINavigationController、UITabBarController、UIStackView、WKWebView、CoreData 或 SQLite、本地缓存、深链跳转。换句话说,把这个项目啃下来,你等于把 iOS 开发的常用兵器都过了一遍。

这篇文章我就把自己从零到一实现"ios-高仿支付宝"这个项目的过程、架构决策和踩过的坑全部整理出来,适合有基础 iOS 开发经验、想通过一个完整项目提升架构能力和 UI 还原能力的朋友。你要是刚开始学 iOS,也可以对照着一步步抄作业,但建议先把 UIKit 基础过一遍再来看。

2. 架构设计与工程组织:一个 Demo 也要有体面

2.1 分层思路:MVC 为主,MVVM 做局部增强

很多初学者拿到这个项目第一件事就是往 Main.storyboard 里拖控件。我劝你直接放弃这个念头。支付宝这种量级的界面,用 storyboard 做高仿基本是给自己挖坑,光首页一个页面的控件层级就够你拖到怀疑人生。我的做法是整个工程全部用代码布局,配合 Masonry 或 SnapKit 做 Auto Layout,这样每个页面的结构都清清楚楚,review 代码的时候也方便。

架构上我选了最稳妥的 MVC 分层,然后在"首页"和"账单列表"这两个页面引入 MVVM 的思路做局部增强。为什么不全上 MVVM?答案很简单:这个项目的本质是 UI 还原和交互复刻,业务逻辑并不复杂,强行上 RxSwift 或 Combine 反而增加学习负担。但首页那种数据来源多样(宫格入口、公告、Banner、推荐位)的页面,用 ViewModel 把数据组装逻辑从 Controller 里抽出来确实能减少 Massive View Controller 的问题。

工程的目录结构我按功能模块划分,而不是按技术类型划分:

AlipayDemo/ ├── AppDelegate/ ├── Modules/ │ ├── Home/ │ ├── Money/ │ ├── Bills/ │ ├── Profile/ │ └── Common/ ├── Core/ │ ├── Network/ │ ├── Cache/ │ └── Router/ ├── Resources/ └── SupportingFiles/

这个结构与支付宝按业务线组织的思路是一致的。你以后接手任何大型项目,都会发现团队更倾向于按业务模块来划目录,而不是把所有的 View 丢进一个 Views 文件夹、所有的 Controller 丢进另一个 Controllers 文件夹。从一开始就按模块划,后续加功能、删功能都干净利落。

2.2 页面路由:用 URL 驱动的思想统一跳转逻辑

支付宝这类 App 一个显著特征就是页面跳转路径极其复杂:从首页点公告要跳 H5、点账单要跳列表、点扫码要跳相机页、收款码页面还要支持从后台推送直接拉起。如果你每个跳转都写pushViewController,代码会迅速腐化。我这里前期就用了一个轻量级 Router 来解决页面间通信和跳转问题。

Router 的核心思路是"页面即 URL"。每一页注册一个路径,比如:

Router.register("alipay://home") { params in let vc = HomeViewController() return vc } Router.register("alipay://bills/detail") { params in let vc = BillDetailViewController(billID: params["billID"]) return vc }

调用方只需要Router.open("alipay://bills/detail?billID=10086"),完全不用 import 目标页面的类。好处有三个:一是模块之间的耦合度大幅降低,二是后续如果要接深链(Universal Links)或者 App Clips 的拉起逻辑,Router 可以直接复用,三是测试的时候可以直接通过 URL 跳转到任意指定页面,配合 XCUITest 做自动化测试简直不要太方便。

2.3 为什么控制器需要"瘦身":从首页代码量说起

我自己写首页第一版时,HomeViewController 里堆了大约 800 行代码:代理方法、数据组装、下拉刷新、红点逻辑全在一起,改一个功能要翻半天。后来我做了两件事:一是把首页拆成三个子组件——顶部搜索与扫一扫区域、金刚区宫格、信息流列表,分别封装成独立的 UIView 子类;二是把数据组装逻辑抽到 HomeViewModel 里。

拆完之后效果立竿见影:HomeViewController 只剩下大约 200 行,核心只做子组件的拼接和事件转发。这里要特别说一下"事件转发",这是很多人在拆组件时容易踩的坑。子组件要处理点击事件,不能直接把 Target-Action 或闭包写死在子组件内部再去引用其他页面,正确做法是定义 delegate 或 closure 回调,由 Controller 统一处理跳转。比如金刚区的每一个宫格按钮,点击之后回调onTapGridItem(item: GridItemModel),再由 Controller 决定是 push 还是 present,这样子组件就可以完全复用。

3. UI 还原的核心难点:从布局细节到交互手感

3.1 首页宫格区:UIStackView 让布局代码减少一半

支付宝首页最上方那个"扫一扫、付钱、收钱、出行"四个大金刚图标,然后是"手机充值、信用卡还款、花呗"这类服务入口,这个区域是高仿的第一个硬骨头。它本质是一个固定行数、自适应列数的宫格布局。很多人在网上找 UICollectionView 的流水布局方案来做,但我觉得用 UIStackView 是最快的方案,而且代码量最少。

思路是这样的:外层一个纵向 UIStackView,每个 subview 是一行横向 UIStackView,每个横向 stack 里放若干个 gridItem 视图。每个 gridItem 内部又是一个纵向 UIStackView,上面 UIImageView、下面 UILabel。

private func createGridRow(items: [GridItemModel]) -> UIView { let rowStack = UIStackView() rowStack.axis = .horizontal rowStack.distribution = .fillEqually rowStack.spacing = 0 for item in items { let itemView = GridItemView(model: item) itemView.onTap = { [weak self] model in self?.handleGridTap(model) } rowStack.addArrangedSubview(itemView) } return rowStack }

为什么说 UIStackView 适合这种场景?因为它天生处理了"等分宽度"和"间距"这两个最啰嗦的约束。你不需要给每个 item 单独写 leading、trailing、width 约束,只要设置 distribution 和 spacing,系统自动帮你排。这在 iOS 9 之后就是官方推荐的布局方案,热词里的ios oc uistackview也说明大家确实在关注这个。

但这里有一个细节必须注意:UIStackView 的 spacing 只能设置统一间距,而支付宝的宫格是"图标之间距大、图标与文字之间距小"这种复杂嵌套。我的处理是每个 GridItemView 内部自己负责图标和文字之间的布局,外部 rowStack 只管行内各 item 的等分。嵌套使用 UIStackView 的时候,一定要记得给内部 stack 设置isLayoutMarginsRelativeArrangement或在 item 内部用约束控制内边距,否则你会发现在不同尺寸的模拟器上,宫格间距表现不一致。

3.2 底部 TabBar 与自定义中间按钮:别用现成的 UITabBarController

支付宝底部的 TabBar 只有四个入口:首页、理财、口碑、我的,而且这四个入口的图标和间距非常紧凑。直接用系统 UITabBarController 会出现一个尴尬的问题:系统的 UITabBar 并不好做"选中态图标切换 + 数字角标 + 未读红点"这种组合效果。所以我的方案是自定义一个 TabBar 容器。

自定义 TabBar 的架构是:用一个根 ContainerViewController 持有四个子 Controller,自建一个 TabBarView 放在底部,用 Auto Layout 把 TabBarView 的高度约束在 49pt(不含安全区)加上安全区底部高度。点击 TabBar 按钮时切换当前显示的子 Controller:

class MainContainerViewController: UIViewController { private let tabBarView = MainTabBarView() private var currentChild: UIViewController? override func viewDidLoad() { super.viewDidLoad() // 添加子控制器、布局、初始化 tabBarView } func switchTo(index: Int) { // 移除当前 child,添加新的 child // 同步 tabBarView 的选中态 } }

这里容易踩的坑是:如果你直接在 Container 里addChild/removeFromParent,子页面的viewWillAppear等生命周期方法可能不会被正确调用。解决方式是使用transition(from:to:duration:options:animations:completion:)方法,或者在切换之后手动调用生命周期方法。我自己实测下来最简单的做法是给 Container 加一个当前 child 的引用,在切换时依次调用oldChild.willMove(toParent: nil)oldChild.view.removeFromSuperview()oldChild.removeFromParent(),再newChild.willMove(toParent: self)addChild(newChild)view.addSubview(newChild.view)newChild.didMove(toParent: self)。顺序不能乱,乱了就等着看生命周期异常吧。

3.3 富文本与长文本分页:账单详情页的排版方案

搜索热词里有一条ios ui文字分页排版demo,这个确实是我做账单详情页时遇到的难题。账单页有一个"详情描述"区域,需要展示一段较长的文字,支付宝的排版是首屏只显示前几行,超过部分折叠,点击"展开"才全部展示。这个需求如果用 UILabel 的 numberOfLines 来做,只是"截断",不是"分页"。

我实现折叠效果时用的方案是:先设置 UILabel 的 numberOfLines = 3,然后用sizeThatFits计算完整文本的高度,再和 3 行的高度对比,如果超出就显示"展开"button。展开后把 numberOfLines 设为 0,重新布局。关键代码如下:

let fullHeight = label.sizeThatFits(CGSize(width: maxWidth, height: .greatestFiniteMagnitude)).height let threeLineHeight = label.font.lineHeight * 3 let isTruncated = fullHeight > threeLineHeight + 1 button.isHidden = !isTruncated label.numberOfLines = isExpanded ? 0 : 3

如果你的需求是真正的"分页",比如每页固定展示 N 行、左右翻页,那就需要用到UITextView+NSLayoutManager或者自定义页码排版。我当时为了模拟"账单按月分页"的效果,用了一个 UIScrollView 横向分页容器,每一页一个 UILabel,每页固定展示 5 条账单记录。这种方式操作起来很直观,但要注意处理页面切换时滚动位置的恢复,否则用户从详情页返回再进入时,滚动位置会丢失。解决方法是记录当前页码,在viewWillAppearscrollView.setContentOffset恢复位置。

3.4 弹层与 Popup 在 iOS 上的显示异常

我在做"付款成功"和"账单筛选"这两个弹层时,遇到了类似热词里van-popup 两个在ios显示异常的情况。当时我有两个弹窗组件:一个是底部弹出的安全键盘提示,一个是居中的加载动画。在部分 iOS 版本上,两个弹窗叠加时会出现层级错乱,后弹出的反而显示在下面。

查了一圈,根因是 iOS 的 keyWindow 和 UIWindowScene 在 iOS 13 之后发生了变化。很多第三方弹窗库用的是UIApplication.shared.keyWindow来获取 window,但这个方法在 iOS 13 之后已经废弃,返回的可能是 nil 或者错误的 window,导致弹窗加到了错误层级。正确做法是使用UIApplication.shared.connectedScenes找到活动的UIWindowScene再取 keyWindow,或者直接在当前控制器的 view 上添加弹窗视图。

我最后的稳妥方案是:做一个全局的 OverlayManager,它持有一个专门的 UIWindow 实例,所有弹窗都加到这一个 window 上,通过windowLevel控制层级。这样不管页面怎么 push、present,弹窗永远在最上层,省去了一堆层级判断的麻烦。

4. 数据层与本地化方案:不依赖服务器也能"活"起来

4.1 数据模型设计:把"假数据"做成"真结构"

高仿项目最常见的问题就是数据写死、页面写死,看起来像 PPT。我的建议是把数据模型完整地建出来,哪怕数据是本地 Mock 的。比如账单模块,我建了BillModelBillSectionModelBillDetailModel三个模型,分别对应单笔账单、按月份分组的账单列表、账单详情。每个模型都包含 ID、Title、Amount、Category、Date、Status 等字段。

struct BillModel { let billID: String let title: String let amount: Double let category: BillCategory let date: Date let status: BillStatus } enum BillCategory: String { case food, transport, shopping, entertainment, finance }

Mock 数据用 Json 文件放在 Bundle 里,启动时读取并解码成模型数组。为什么用 JSON 而不是直接代码里写死?因为 JSON 更接近真实项目的工作流:后端返回 JSON、App 解码成 Model。你从第一天起就按这个套路来,后面接入真实接口只是替换数据源的问题,UI 代码完全不用改。

4.2 持久化选型:UserDefaults、文件、还是 SQLite?

这个项目里有几个数据需要持久化:登录状态、首页配置信息、账单搜索历史。我的选择是:登录状态用 UserDefaults(本身设计就是存轻量键值),搜索历史用文件存储(一个 JSON 数组写入沙盒 Library 目录),账单数据如果有大量增删改查再考虑 SQLite。

这里说一个纯前端的替代方案:如果你不想写任何原生的持久化代码,也有不少人选择把 Mock 数据做成 JSON 文件直接打进包,每次启动从 Bundle 读取。这种方式对 Demo 来说完全够用,唯一的缺点是用户修改的数据不会保留。我建议至少把"搜索历史"做成真持久化,这样你在面试演示的时候可以很自信地说"我这里做了本地持久化",而不是被一问就露怯。

4.3 Web 组件混编:WKWebView 加载 H5 页面

支付宝的很多运营位和二级页面都是 H5,高仿也得把这个混编机制做出来。iOS 上承载 H5 的组件是 WKWebView,它取代了早期 UIWebView。我封装了一个WebContainerViewController,可以接收一个 URL 字符串,内部创建 WKWebView 加载。

坑点有两个:一是 WKWebView 的 cookie 不与系统 Safari 共享,如果 H5 需要登录态,你要通过WKWebsiteDataStore注入 cookie 或者用 JS bridge 传递 token。Demo 阶段无所谓,但要知道这个机制。二是 iOS 14 之后的 App 如果要从 H5 跳回原生页面,需要配置WKURLSchemeHandler或者通过 JS 注入协议,我用的方案是注册一个自定义 scheme,H5 里跳alipaydemo://billDetail?id=xxxWKWebView的 decidePolicyFor 拦截到之后交给 Router 处理。这个机制的底层逻辑跟热词里ios浏览器唤起安装app是一致的,本质都是通过 scheme 做 App 唤起。

5. 踩坑实录:iOS 开发里绕不过去的那些问题

5.1 跨页面传值丢失:一套必须避开的时序坑

开发中遇到过wx.navigatebackminiprogram ios 拿不到extradata类似的问题——从 A 页跳 B 页,B 页返回 A 页时带不回数据。原生场景的对应问题是用闭包或 delegate 回传数据时,self 已经被释放,导致回调不生效。

我的建议是:如果页面间需要回传数据,优先用 delegate 或者闭包,并且闭包里用[weak self]防止循环引用。如果是非常复杂、跨越多个层级的传值,考虑引入一个轻量级的消息总线或者直接使用 NotificationCenter。但 NotificationCenter 有个缺点:它不区分发起者,收到通知的页面可能来自任意源。所以在 Demo 里我推荐一个更简单的方式:把需要传值的目标数据放进一个单例 Session,然后 target 页面在 viewWillAppear 时主动去读。

5.2 模拟器与真机的差异:那些"模拟器没问题、真机就翻车"的瞬间

热词里有ios设备模拟,说明很多人关注模拟器。但我必须说一句:模拟器永远不能替代真机。我做这个项目时最典型的一次翻车是字体渲染:模拟器上中文显示正常,到了真机上某些系统字体(比如苹方)的渲染行高与模拟器不同,导致 UILabel 撑出多余的高度,底部 TabBar 被顶到安全区之外,露出大片空白。

这类问题怎么防?一是所有约束不要写死数值(比如 label 高度 40pt),而是用>=这种不等式约束,给系统渲染留出余地。二是关键页面必须做真机适配,特别是 iPhone SE(小屏)和 iPhone Pro Max(大屏)这两端都要看。三是使用系统字体时,用UIFont.preferredFont(forTextStyle:)配合 Dynamic Type,这样系统调整字体大小时你的布局不会崩。

5.3 屏幕适配:分屏与多尺寸的布局策略

热词里有ios分屏,这个在 iPad 上很常见。虽然高仿支付宝主要针对 iPhone 的屏幕尺寸,但如果你在 iPad 上跑这个项目,会暴露一个布局问题:如果用固定宽度约束做宫格,iPad 上会变得很宽,行数错乱。我最后的做法是给宫格区域设置一个最大宽度,并且用NSLayoutConstraintconstant根据屏幕宽度动态计算每行 item 数。

let numberOfItemsPerRow = UIDevice.current.userInterfaceIdiom == .pad ? 6 : 4

这个看似粗糙的判断在 Demo 里非常实用。当然,如果你要做到像支付宝那样在不同屏幕尺寸上自适应换行,那就得用 UICollectionView 的流式布局了,每次布局时根据宽度计算列数。对 UIStackView 方案来说,一个折中方案是把每行固定的 item 数量做成配置项,启动时根据屏幕宽度动态决定。这个我也做了,实测下来在分屏场景下表现基本能接受。

5.4 启动图与 LaunchScreen 的版本适配

还有一个很低级但容易忘的坑:如果你的项目没有配置 LaunchScreen 文件,App 只能在"兼容模式"下运行,无法使用完整的屏幕尺寸。做高仿项目时需要特别注意启动图和启动屏的尺寸配置,否则在 iPhone 15 这类新机型上,App 会被自动放大,界面模糊。当时我这个工程是直接从旧项目拷过来的,默认用的 LaunchImage,后来在真机上发现页面被拉伸。换成 LaunchScreen.storyboard 之后一切正常。建议所有新工程都直接用 LaunchScreen.storyboard 方式,彻底告别逐尺寸适配。

6. 自动化测试与抓包验证:让 Demo 真正"能跑能验"

6.1 用 XCUITest 做一套回归冒烟测试

热词里有ios自动化,这块确实值得做。高仿项目的页面非常多,每次改一个 UI 细节都可能把另一个页面弄坏。手工回归一遍所有页面太痛苦,我的做法是用 XCUITest 写一套冒烟测试,覆盖关键路径:启动 App → 首页加载完成 → 点击扫一扫 → 相机页出现 → 返回 → 点击账单 → 账单列表出现 → 点击某一条账单 → 详情页出现 → 返回。

func testHomeToBillDetailFlow() { let app = XCUIApplication() app.launch() let homeTab = app.tabBars.buttons["首页"] XCTAssertTrue(homeTab.exists) homeTab.tap() app.buttons["账单"].tap() let firstCell = app.cells.firstMatch XCTAssertTrue(firstCell.waitForExistence(timeout: 5)) firstCell.tap() XCTAssertTrue(app.staticTexts["账单详情"].exists) }

这套测试跑下来大约 30 秒,每次改完代码跑一遍,能非常快地发现页面崩没崩、关键按钮能不能点。XCUITest 的定位测试有时候不稳,如果按钮找不到,建议先看看 accessibility label 是否设置。支付宝这种强 UI 项目,给关键控件设置 accessibilityIdentifier 是很好的习惯,既方便测试又能提高可访问性。

6.2 用抓包工具验证数据流

热词里有fiddler手机抓包ios教程,这个对应的是网络调试场景。我在这个项目里虽然主要用本地 Mock 数据,但为了模拟真实的网络加载链路,也加了一个简单的 NetworkManager,用 URLSession 发起请求,数据源指向本地 Bundle 的 JSON 文件。为了验证请求逻辑正确,我配合 Charles 做了抓包确认每次请求确实发出去了、返回的数据也确实是预期的内容。

真机抓包的步骤其实很简单:电脑上装好 Charles,开启 SSL Proxying,手机 Wi-Fi 代理指向电脑 IP(端口 8888),然后在手机上访问chls.pro/ssl安装 Charles 的根证书,信任证书后就能解开 HTTPS 流量。这个操作在 iOS 开发调试里非常常用,做任何网络请求相关的调试都离不开。

6.3 模拟弱网与异常情况

高仿项目的一层进阶是模拟弱网。支付宝首页会有一个 Loading 状态、网络超时会有重试页、图片加载失败会有一个占位图。如果你把这些状态都做出来,面试时展示出来的成熟度是完全不一样的。Xcode 自带的 Network Link Conditioner 可以模拟不同网络环境,比如设置成 3G、高延迟、丢包率 10%,然后观察你的页面表现。我实测下来,很多同学做的 Demo 在弱网环境下一片空白或者闪退,就是因为没有处理加载和失败状态。把这些状态补上,你的项目就从"能看"变成了"能用"。

7. 从高仿到进阶:App Clips 与上架准备的延伸思考

7.1 从"仿"到"做":App Clips 能给你什么启发

热词里有ios app clips开发,这其实是个很好的进阶方向。支付宝的"扫码即用"体验,跟 App Clips 的"无需安装、扫码即用"理念非常像。你在做高仿项目时积累的组件化能力——Router 路由、页面即 URL、本地数据快速加载——恰好是 App Clips 开发的核心基础。如果你的高仿项目已经把功能模块拆得很干净,可以尝试用 App Clips 做一个"扫码后直接展示某账单详情"的轻量体验,这比从零开始学 App Clips 要轻松得多,而且面试时这是一个非常亮眼的加分项。

7.2 开发者证书与上架模拟:提前熟悉签名机制

很多人做完了项目想装到真机上,结果卡在签名这一关。这个项目虽然不打算上架,但真机调试是必须的。你需要一个 Apple ID 登录 Xcode,在 Signing & Capabilities 里选择 Team 为 Personal Team,Xcode 会自动帮你生成开发证书。如果是公司项目或者你需要给非越狱设备做分发,就需要申请开发者账号并生成.p12证书文件。热词里的ios开发者app证书更新ios证书p12免费生成说的就是这个流程。

这里提醒一个常见坑:免费的 Personal Team 签名的 App 只有 7 天有效期,到期后需要重新签名才能继续运行。如果你像我一样用这个 Demo 做长期维护,建议还是花点钱买个个人开发者账号,一年 99 美元,省去反复签名的麻烦。而且只有付费开发者账号才能开启推送、App Clips 和 TestFlight 内的多人测试。如果你计划把作品发到 TestFlight 给朋友体验,这一步绕不过去。

7.3 下一步往哪走

做完这个高仿项目之后,我最大的变化是看任何 App 的第一反应都不再是"这个界面好看",而是下意识去拆解它的页面层级、数据流和组件复用方式。我建议你做完之后也尝试做一个完全自己的项目——哪怕只是一个记账工具或者一个小工具类 App,把从高仿里练出来的架构能力和 UI 还原能力用自己的想法重新组合一遍。只有走到这一步,这个项目才算真正消化完了。

最后分享一个我自己的小习惯:每次在模拟器里跑通一个新页面,我都截图保存下来,和真机上的目标 App 做对比,把差异写进项目的 README 文件里。这样等过几个月再回头看,你能清清楚楚看到自己哪些地方做到了八成、哪些地方还差得远。这种"差距清单"比任何学习笔记都有用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询