毕业设计如果继续做“XX管理系统”,大概率会在开题答辩时和七八个同学撞车。相比之下,以鸿蒙系统为平台、用 ArkTS 做原生开发的类小红书 App,从技术栈到产品形态都有足够区分度,而且它不是只做一个 demo 界面,而是把社交笔记、电商商城和 Web 后台串成一条完整业务链路。这类课题既能体现对新型平台的掌握,也能覆盖状态管理、网络请求、权限申请、数据持久化、接口设计和后台管理等常见工程能力,非常适合作为毕设选题,也可以作为简历上的完整项目。
这篇文章按一个可落地的毕设项目来拆解:先讲为什么选这个题、整体架构和技术栈怎么定,再给数据模型和接口约定,然后分别说明笔记信息流、商城模块、Web 后台的实现思路,最后补充真机调试排错路径、答辩演示路径和常见坑。文中代码用于说明工程思路,实际开发时要以你本地的 DevEco Studio 版本和 API 版本为准,不要照抄后不做版本验证。
1. 为什么这个选题值得做:从撞车选题里找区分度
1.1 毕设选题撞车的原因与破局思路
毕业设计撞车最多的通常是“图书管理系统”“学生选课系统”“企业员工管理”这类题目。撞车的原因很简单:技术栈固定、参考案例多、实现路径成熟。大家用同一个框架、同一套 CRUD,最后交上去的验收点高度相似,甚至答辩时被问的问题都一样。
破局思路不是把界面做得更花哨,而是换一个有平台属性的方向。鸿蒙系统是一个仍在快速迭代的操作系统,ArkTS 作为其应用开发的推荐语言,还没有被大量毕设项目覆盖。当别人还在写 Web 管理系统时,你手里是一个运行在真机或模拟器上的原生 App,带双列瀑布流、点赞收藏、购物车、下单流程和后台管理,这种完整度更容易在开题、中期和答辩时讲出东西。
这里要区分一个概念:鸿蒙系统并不只是“手机上的系统”。从通俗角度看,它面向手机、平板、车机、智慧屏等多设备协同场景;从技术角度看,应用层通过 ArkUI 声明式框架和 ArkTS 语言开发,底层系统负责调度、安全和分布式能力。毕设层面不需要讲得很底层,但理解这一点能帮助你在答辩时解释“为什么不用 WebView 套壳,而要选原生开发”。
1.2 ArkTS 原生开发在毕设中的优势
ArkTS 是在 TypeScript 基础上扩展出的声明式 UI 开发语言。它和传统命令式写界面的方式不同,开发者描述“界面应该长什么样”,框架负责在状态变化时更新界面。这个思路与 React、Vue 有相似之处,但 ArkTS 直接跑在鸿蒙应用框架上,能调用相机、相册、网络、权限管理等系统能力。
用 ArkTS 做原生开发,毕设里有三个实际收益:
- 权限申请、相机调用、相册选择这些能力可以作为独立功能点,答辩时容易展示系统级集成能力。
- 声明式 UI 的代码量相对少,页面结构和状态变化更直观,适合在有限时间内完成。
- 技术资料正在快速积累,但还远没有 JavaWeb 那么泛滥,写出来的项目有稀缺性。
需要注意,ArkTS 对类型约束比普通 JavaScript 严格,组件状态管理也有自己的规则。不要直接用写“自由脚本”的习惯去写 ArkTS,否则运行起来会遇到类型报错和状态不刷新问题。
1.3 课题拆解:社交笔记、电商商城、Web 后台三个模块的边界
这个题目可以拆成三个边界清晰的模块:
- 用户端 App:包含登录注册、首页双列笔记流、笔记详情、发布笔记、点赞收藏、商品列表、商品详情、购物车、订单提交、个人中心。
- 服务端接口:为 App 和 Web 后台提供 RESTful API,处理用户、笔记、商品、订单、评论、点赞等数据。
- Web 后台:管理员登录后,可以查看数据看板、管理用户、审核笔记、上下架商品、处理订单。
毕设的工作量主要落在 App 端。如果时间有限,可以砍掉评论、搜索、支付对接等非核心功能,用一个“模拟支付成功”来代替真实支付。后端接口数量控制在 20 个左右就能支撑整个演示流程,不要一开始就把表设计得过于复杂。
2. 项目整体架构与技术选型:先画清楚模块边界
2.1 客户端、服务端、Web 后台三层结构
项目建议采用前后端分离结构,分三部分:
- HarmonyOS App 客户端:负责用户交互,通过 HTTP 请求访问服务端接口。
- 服务端:负责业务逻辑、鉴权、数据校验和数据库读写,同时给 Web 后台提供接口。
- Web 后台:一个单独的管理端项目,只允许管理员登录,提供内容审核和运营管理功能。
数据流链路大致是:App 发起登录请求,服务端校验后返回 token;App 携带 token 获取笔记或商品列表;用户发布笔记,服务端写入待审核状态;管理员在后台审核通过,用户端才能看到笔记上线。
这种三层结构的好处是职责分离。App 端可以不关心 SQL 和业务规则,后台管理可以随时补充业务功能而不需要重新发版 App。答辩时也能清楚说明,这是一个“完整的前后端协作项目”,而不是单页面 Demo。
2.2 服务端与数据库选型
服务端技术栈可以根据你熟悉的方向选择。最常用的两组方案:
| 技术栈 | 优点 | 适合场景 |
|---|---|---|
| Java + Spring Boot + MySQL | 资料多,企业微服务常用,代码结构规范 | 熟悉 Java 或后续想找 Java 后端岗位 |
| Node.js + Express/Nest + MySQL | 语言与前端一致,开发迭代快 | 熟悉 TypeScript 或希望整体一套语言 |
| Python + Flask/FastAPI + MySQL | 代码轻量,适合快速原型 | 想快速跑通但需要后期补齐工程化能力 |
数据库层建议统一用 MySQL。毕设环境下表结构可控,MySQL 的 SQL 资料也多,排错成本低。如果你的电脑没有安装 MySQL,也可以先用 SQLite 或 H2 数据库开发,最后再迁移到 MySQL,但要注意 SQL 方言差异。
2.3 核心业务链路需要提前梳理
在写代码之前,先用文字把核心链路理清楚,后面就不会乱:
- 用户注册登录:注册时保存用户名和密码哈希,登录成功后服务端返回 token,App 后续请求在 Header 中携带。
- 笔记发布与审核:用户填写标题、内容、分类并选择图片,发布后状态为待审核;后台审核通过后,笔记在用户端可见。
- 商品与订单:用户浏览商品并加入购物车,确认后提交订单;服务端生成订单号,状态为待支付;模拟支付成功后状态变为已支付,后台可以进行发货操作。
- 后台管理:管理员登录后看到统计数字,对用户、笔记、商品、订单进行管理操作。
这些链路是后面设计数据库表结构的基础。先想清楚链路,再建表,不要一边写界面一边临时改表。
3. 环境准备与工程创建:先跑起来,再写业务
3.1 DevEco Studio、SDK 和 API 版本要对齐
HarmonyOS 应用开发的主力 IDE 是 DevEco Studio,安装后需要下载 HarmonyOS SDK。SDK 版本会直接影响 ArkTS 语法、组件能力和权限 API,所以开始项目前先确认三件事:
- DevEco Studio 安装完成且登录账号环境正常。
- SDK 已下载,且与工程目标 API 版本一致。
- 有可用的模拟器或真机。如果使用真机,要在设备上开启开发者模式,并允许 USB 调试。
API 版本是一个容易踩坑的地方。不同 API 版本中,权限申请接口、路由跳转方式、WaterFlow 等新组件的支持程度不同。教程中经常看到的一些写法可能来自不同版本,遇到 API 不存在或编译报错时,优先在官方 API 文档中确认当前版本签名。
如果实在没有真机,可以用模拟器完成大部分功能演示,但相机、相册、定位等系统能力在模拟器上的体验会受限。毕设演示时,能准备一台鸿蒙真机效果更好。
3.2 创建 ArkTS 工程并理解目录结构
在 DevEco Studio 中选择“Empty Ability”模板创建工程。一个典型工程的目录结构大致如下:
HarmonyNotesDemo/ ├── AppScope/ │ ├── app.json5 │ └── resources/ ├── entry/ │ ├── src/main/ │ │ ├── ets/ │ │ │ ├── entryability/ │ │ │ ├── pages/ │ │ │ │ ├── Index.ets │ │ │ │ ├── NoteDetailPage.ets │ │ │ │ ├── PublishNotePage.ets │ │ │ │ ├── ProductListPage.ets │ │ │ │ ├── CartPage.ets │ │ │ │ └── MinePage.ets │ │ │ ├── model/ │ │ │ └── service/ │ │ ├── resources/ │ │ └── module.json5 │ └── build-profile.json5 └── build-profile.json5module.json5是模块配置文件,里面注册 Ability、声明权限、配置页面入口。pages目录存放页面组件。model目录建议放数据模型和接口返回类型定义,service目录放网络请求封装和工具方法。不要在页面文件里写大量逻辑,否则后期维护会很痛苦。
创建完工程后,先运行一次默认 Hello World,确认开发环境可以编译、安装、启动。这一步能提前暴露 SDK、签名、模拟器连接等问题,不要在还没跑通基础工程时就开始写业务代码。
3.3 权限申请:从热门问题“arkts 权限申请”说起
权限申请是鸿蒙开发中很容易忽略的环节。应用访问网络、相机、相册等能力,需要先在module.json5中声明权限,然后在代码中请求动态权限。
基础网络权限配置如下:
{ "module": { "name": "entry", "requestPermissions": [ { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.CAMERA" }, { "name": "ohos.permission.READ_IMAGEVIDEO" }, { "name": "ohos.permission.WRITE_IMAGEVIDEO" } ] } }查询用户相册、访问网络、调用相机都属于需要“解释用途”的敏感权限。即使声明了权限,部分系统能力仍需要在运行时弹窗申请。常用的动态权限申请代码思路如下:
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; async function requestPermission(permission: Permissions) { const atManager = abilityAccessCtrl.createAtManager(); try { const result = await atManager.requestPermissionsFromUser(permission); if (result.authResults.length > 0 && result.authResults[0] === 0) { console.info('permission granted'); } else { console.error('permission denied'); } } catch (err) { console.error('request permission error: ' + JSON.stringify(err)); } }这里要特别提醒:不同 API 版本中requestPermissionsFromUser入参类型不一样,有的版本接受数组,有的版本接受单个权限对象,还有的版本要求传入context。写代码前必须以当前工程依赖的 API 版本为准,不能直接复制网上任意一段代码。
权限申请常见的坑是只做了声明、没有动态请求,或者声明了权限但用户拒绝后没有处理。正确做法是:在进入需要权限的功能前检查授权状态,未授权时弹窗说明用途并请求;用户拒绝后,界面要给出引导或降级处理,不能直接崩溃。
4. 数据库模型设计:先定义业务的数据基础
4.1 用户、笔记、商品、订单四张核心表
数据库表设计不用太多,但要能支撑完整业务链路。核心表先建四张:用户表、笔记表、商品表、订单表。
CREATE TABLE `user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password_hash` VARCHAR(255) NOT NULL, `nickname` VARCHAR(50), `avatar_url` VARCHAR(255), `role` TINYINT DEFAULT 0 COMMENT '0普通用户 1管理员', `status` TINYINT DEFAULT 1 COMMENT '1正常 0禁用', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE `note` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `title` VARCHAR(100), `content` TEXT, `cover_url` VARCHAR(255), `category` VARCHAR(30), `like_count` INT DEFAULT 0, `status` TINYINT DEFAULT 0 COMMENT '0待审核 1已上线 2已下线', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE `product` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `price` DECIMAL(10, 2) NOT NULL, `stock` INT DEFAULT 0, `cover_url` VARCHAR(255), `status` TINYINT DEFAULT 1 COMMENT '1上架 0下架', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE `order` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(64) NOT NULL UNIQUE, `user_id` BIGINT NOT NULL, `total_amount` DECIMAL(10, 2) NOT NULL, `status` TINYINT DEFAULT 0 COMMENT '0待支付 1已支付 2已发货 3已完成 4已取消', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP );可以看到,用户表加了role字段区分普通用户和管理员,笔记表加了status字段支撑后台审核,商品表用status控制上下架。这些字段并不复杂,但它们是 Web 后台能独立管理内容的基础。如果省掉状态字段,后面后台审核功能会无从下手。
4.2 点赞、收藏、评论、购物车等关联表
核心表确定后,再补充关联表,保存用户与内容之间的交互关系。
CREATE TABLE `favorite` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `note_id` BIGINT NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_note` (`user_id`, `note_id`) ); CREATE TABLE `comment` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `note_id` BIGINT NOT NULL, `content` VARCHAR(500) NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE `cart` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `product_id` BIGINT NOT NULL, `quantity` INT DEFAULT 1, `selected` TINYINT DEFAULT 1, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_product` (`user_id`, `product_id`) );关联表看起来简单,但有几处容易错:favorite表要加唯一键防止重复点赞;cart表要按用户和商品维度唯一,这样重复加入购物车时执行的是数量累加而不是插入新记录。这些细节在接口开发时都能遇到。
4.3 接口返回格式与分页约定
App 端、Web 后台和服务端之间统一约定接口返回格式,可以大幅减少联调成本。
{ "code": 0, "message": "ok", "data": { "list": [], "total": 100, "page": 1, "size": 10 } }分页接口统一约定page和size参数,返回结果带total。这样 App 端做下拉加载时,可以用page + 1请求下一页,直到列表长度等于total时停止。图片、头像、封面地址在数据库中建议存相对路径或完整 URL,服务端统一拼接域名,避免前端到处改 baseUrl。
5. 笔记信息流模块的 ArkTS 实现:双列瀑布流与交互状态
5.1 首页结构:搜索栏、Tab 栏与内容列表
首页是类小红书 App 的门面,结构上可以分为三块:顶部搜索栏、Tab 切换栏、笔记内容列表。Tab 可以切换“推荐”“穿搭”“美食”“美妆”等分类,按分类请求后端接口即可。
ArkTS 页面骨架如下:
@Entry @Component struct HomePage { @State noteList: NoteItem[] = []; @State currentCategory: string = '推荐'; build() { Column() { Row() { TextInput({ placeholder: '搜索笔记和商品' }) .layoutWeight(1) Button('搜索') .onClick(() => this.search()) } .padding(10) TabBar({ categories: ['推荐', '穿搭', '美食', '美妆'], current: this.currentCategory, onSelect: (category: string) => this.loadNotes(category) }) NoteList({ notes: this.noteList }) .layoutWeight(1) } } search() { // 跳转到搜索结果页 } loadNotes(category: string) { // 请求后端接口并刷新 noteList } }这里把搜索、Tab、列表拆成多个组件或 Builder,可以让页面结构更清晰。@State装饰的变量是状态源,ArkUI 会在状态变化时重新渲染界面。要特别注意的是,修改数组时直接this.noteList.push(item)不会触发刷新,正确做法是生成新数组重新赋值,例如this.noteList = [...this.noteList, ...newData]。
5.2 双列瀑布流的两种实现方案
瀑布流是类小红书 App 最核心的界面特征。实现上主要有两种思路。
第一种是使用 ArkUI 的WaterFlow组件。如果你的目标 API 版本支持,代码会简洁很多:
WaterFlow() { LazyForEach(this.noteList, (item: NoteItem) => { FlowItem() { NoteCard({ note: item }) } }, (item: NoteItem) => item.id) }LazyForEach会根据列表项在屏幕内的可见性按需创建组件,适合大量图片笔记的列表。如果目标版本中WaterFlow不可用,可以使用第二种方案:用Scroll包裹Row,左侧一个Column,右侧一个Column,把偶数项放进左列,奇数项放进右列。
Scroll(this.scroller) { Row() { this.leftColumn() this.rightColumn() } .alignItems(VerticalAlign.Top) } .scrollable(ScrollDirection.Vertical) .onReachEnd(() => this.loadMore())双列手动布局的原理是:左列负责偶数下标数据,右列负责奇数下标数据,两列各自垂直排列,实现类似瀑布流的效果。这个方案的缺点是左右两列高度可能不平衡,但通过不断加载新数据、保持两列数量接近,可以得到比较自然的展示效果。
5.3 笔记卡片、点赞收藏与详情页跳转
笔记卡片组件需要展示封面图、标题、作者头像、作者昵称和点赞数。用@Component定义卡片,接收NoteItem对象,然后点击卡片时跳转到详情页。
@Component export struct NoteCard { @Prop note: NoteItem; build() { Column({ space: 4 }) { Image(this.note.coverUrl) .width('100%') .aspectRatio(0.85) .objectFit(ImageFit.Cover) .borderRadius(8) Text(this.note.title) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row({ space: 4 }) { Text(this.note.authorName) .fontSize(12) Text(this.note.likeCount + ' 赞') .fontSize(12) } .width('100%') } .padding(6) .onClick(() => { router.pushUrl({ url: 'pages/NoteDetailPage', params: { id: this.note.id } }); }) } }@Prop表示单向数据传入,适合卡片展示场景。点赞按钮的状态变化通常要传递到父组件,可以用@Link双向绑定,或者通过回调函数通知父组件更新数据。点击卡片跳转详情页时,通过路由params传递笔记 id,详情页再根据 id 请求完整内容。
5.4 网络请求封装与图片加载兜底
App 端不直接在每个页面里写http请求,建议统一封装一个ApiClient服务。基本写法如下:
import { http } from '@kit.NetworkKit'; export class ApiClient { static baseUrl: string = 'http://192.168.1.100:8080/api'; static get<T>(path: string): Promise<T> { const request = http.createHttp(); return request.request(this.baseUrl + path, { method: http.RequestMethod.GET, connectTimeout: 10000, readTimeout: 10000, header: { 'Content-Type': 'application/json' } }).then((resp) => { if (resp.responseCode === 200) { return JSON.parse(resp.result as string) as T; } throw new Error('request failed: ' + resp.responseCode); }).finally(() => { request.destroy(); }); } }网络请求成功不等于业务成功。服务端通常会在 HTTP 200 下返回code字段,前端要先判断code === 0,再处理data。如果code非 0,要弹出错误提示。
图片加载方面,Image组件的src可以设置网络 URL,但是要确认三件事:图片域名是否在系统网络安全配置允许的范围内;URL 是http还是https,明文请求可能被拦截;图片加载失败时有没有兜底图。兜底写法如下:
Image(this.note.coverUrl) .alt($r('app.media.default_cover')) .objectFit(ImageFit.Cover).alt()参数会在图片加载失败或尚未加载完成时展示本地兜底图。生产环境中图片链接触发大量流量,要考虑服务端开启 CDN 或图片压缩;毕设阶段只要保证图片能正常加载、失败有兜底即可。
6. 商城模块的 ArkTS 实现:购物车数量逻辑和模拟下单
6.1 商品列表、商品详情与加入购物车
商城模块的商品列表结构和笔记列表类似,只是卡片信息不同,需要展示商品名称、价格和库存。商品卡片点击后进入商品详情页,详情页包含大图、价格、库存、数量选择和加入购物车按钮。
商品数据模型可以设计为:
export class ProductItem { id: string = ''; name: string = ''; price: number = 0; stock: number = 0; coverUrl: string = ''; status: number = 1; }加入购物车时调用后端接口,把userId、productId、quantity传给服务端。服务端做原子操作:库存足够时更新购物车记录并扣减临时数量,库存不足时返回错误码。不要在 App 端自行计算库存,因为客户端数据不可信,最终校验必须在服务端完成。
6.2 购物车数量管理、选中逻辑与总价计算
购物车页面是状态管理比较集中的地方,需要处理数量增加、数量减少、单选、全选、删除、总价计算等操作。ArkTS 中数组元素内部的字段变化通常不会自动触发界面刷新,因此更新数量后最好重新赋值整个数组。
核心逻辑示例:
@Component struct CartPage { @State cartItems: CartItem[] = []; updateQuantity(index: number, delta: number) { const item = this.cartItems[index]; item.quantity = Math.max(1, item.quantity + delta); this.cartItems = [...this.cartItems]; } toggleSelect(index: number) { this.cartItems[index].selected = !this.cartItems[index].selected; this.cartItems = [...this.cartItems]; } get totalPrice(): number { return this.cartItems .filter(item => item.selected) .reduce((sum, item) => sum + item.price * item.quantity, 0); } }这里的totalPrice是计算属性,每次界面渲染时会根据cartItems重新计算。数量最小值要限制为 1,最大值不能超过商品库存。全选逻辑判断当前选中数量是否等于列表长度,注意空购物车时要禁用“提交订单”按钮。
6.3 下单流程与模拟支付
从购物车提交订单时,只把选中的商品提交给服务端。订单创建接口返回订单号,前端生成一个支付确认页,用户点击“确认支付”后调用模拟支付接口,服务端把订单状态从待支付改为已支付。
async submitOrder() { const selectedItems = this.cartItems.filter(item => item.selected); if (selectedItems.length === 0) { return; } const order = await ApiClient.post<OrderResult>('/order/create', { items: selectedItems.map(item => ({ productId: item.productId, quantity: item.quantity })) }); // 跳转到支付确认页 router.pushUrl({ url: 'pages/PayPage', params: { orderNo: order.orderNo, amount: order.totalAmount } }); }模拟支付的代码量不大,但演示效果很重要。支付完成后,订单状态更新为已支付,购物车要清空已下单商品。后台管理员看到已支付订单后,可以执行发货操作,订单状态变为已发货。这样一条从 App 到后台再到 App 的完整业务闭环就成立了。
7. Web 后台快速搭建:内容审核和订单管理是关键
7.1 后台功能清单与页面拆分
Web 后台是区分“一个 App Demo”和“一个完整项目”的重要加分项。后台功能不需要做得非常华丽,但必须覆盖核心运营动作。
| 后台模块 | 核心功能 | 依赖数据表 | 对 App 的影响 |
|---|---|---|---|
| 登录 | 管理员账号登录 | user | 无 |
| 数据看板 | 今日新增用户、笔记数、订单数、销售额 | user/note/order | 无 |
| 用户管理 | 用户列表、禁用启用 | user | 被禁用用户无法登录 |
| 笔记管理 | 笔记列表、审核、下架 | note | 审核通过后用户端可见 |
| 商品管理 | 商品列表、上架、下架 | product | 下架商品用户端不可见 |
| 订单管理 | 订单列表、发货 | order | 发货后订单状态变化 |
从这张表可以看出,后台管理的每个操作几乎都会影响用户端数据。毕设答辩时,最有效的演示路径是:在后台把一条笔记从待审核改为通过,然后在 App 端刷新看到这条笔记上线。这比单纯展示一堆静态页面更有说服力。
7.2 后台技术选型:前后端分离还是服务端模板
Web 后台有两种常见做法。
第一种是前后端分离:后端提供接口,前端使用 Vue3 + Element Plus 或 React + Ant Design 开发。这种方式结构清晰,页面美观,适合有前端基础的同学,但需要同时维护两个工程的开发环境。
第二种是服务端模板渲染:后端直接提供 HTML 页面,管理界面用 Bootstrap 或原生 HTML 实现。这种方式工程简单,适合时间紧张、只想快速跑通后台功能的场景。
对比表如下:
| 方案 | 开发成本 | 展示效果 | 适合场景 |
|---|---|---|---|
| Vue3 + Element Plus 前后端分离 | 中等 | 较好 | 有前端基础,希望提升项目含金量 |
| 服务端模板渲染 | 低 | 一般 | 时间紧,只求后台可用 |
| React + Ant Design | 中等偏高 | 较好 | 熟悉 React 生态 |
7.3 后台列表页、审核操作和管理员登录
后台列表页的核心是“列表 + 条件筛选 + 操作按钮”。比如笔记管理页,需要展示用户昵称、笔记标题、分类、状态、创建时间,并提供“通过”“下架”按钮。状态字段在数据库中是数字,后台要转换成中文展示,例如0转成“待审核”、1转成“已上线”。
管理员登录需要单独设计,不能在后台使用普通用户的注册登录接口。简单做法是初始化一条role=1的管理员记录,后台登录时校验角色。生产环境还要加入验证码、单点登录、操作日志等机制,但毕设阶段可以先不做。
如果后台使用 Vue3 开发,基础登录流程是:输入账号密码,后端校验成功后返回 token,前端把 token 存到localStorage或pinia中,后续请求在Authorization头携带。这个流程和 App 端高度相似,可以在答辩时作为“多端接口复用”的例子来说明整个项目设计的一致性。
8. 真机调试与接口联调:从日志到链路的排查顺序
8.1 联调环境先确认,避免在错误前提下找问题
App 端和服务端联调时,最常遇到的就是环境问题。这里按优先级列出检查清单:
- 真机和电脑是否处于同一局域网。
- 服务端接口是否能从电脑浏览器直接访问。
- App 中配置的
baseUrl是否是电脑的局域网 IP,而不是localhost或127.0.0.1。 - 真机是否能 ping 通电脑 IP,电脑防火墙是否拦截了服务端端口。
- 网络权限是否已在
module.json5中声明。
localhost是一个高频错误。模拟器虽然运行在电脑上,但模拟器内部的网络环境和宿主机不同,写死localhost经常导致无法访问本机服务;真机上访问电脑服务也必须使用电脑的局域网 IP。建议把baseUrl单独放到配置文件中,方便调试时切换。
8.2 常见错误现象与排查路径
| 问题现象 | 可能原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 请求后台接口一直超时 | 真机和电脑不在同一网络,或端口被防火墙拦截 | 查看两台设备 IP,做 ping 测试 | 改用同一 WiFi,开放服务端端口或临时关闭防火墙 |
| 接口返回 404 | 后端路径和前端请求路径不一致 | 查看后端控制台日志,浏览器直接访问接口 | 统一接口路径拼接规则 |
| 接口返回 JSON 解析失败 | 返回格式不符合前端类型定义 | 查看返回原始字符串和model中字段 | 前后端共同维护接口文档 |
| 图片加载空白 | 图片 URL 使用 http,或域名未配置 | 浏览器打开图片地址,检查网络策略 | 使用 https,配置网络安全白名单,准备本地兜底 |
| 权限弹窗不出现,功能不可用 | 只在配置中声明,未动态申请 | 检查requestPermissionsFromUser调用 | 按系统要求完成动态申请流程 |
| 页面状态不刷新 | 直接修改数组内部字段 | 检查是否重新赋值@State数组 | 使用新数组替换旧数组触发渲染 |
排查顺序建议:先看服务端有没有收到请求,再看服务端返回了什么,最后看前端解析结果。把问题定位到“网络不通”“服务端异常”“前端解析异常”三个分段,比从界面开始盲目改代码要高效得多。
8.3 日志和真机调试的关键节点
鸿蒙开发中,日志通过 HiLog 输出,可以从 DevEco Studio 的 Log 窗口查看。页面里可以使用console.info输出关键变量。排查网络请求时,重点看三个时间点:
- 请求发出前:
baseUrl是否正确,参数是否完整。 - 服务端收到请求时:路径、Method、请求体是否与接口定义一致。
- 响应返回后:HTTP 状态码、
code字段、data内容是否能被解析。
如果真机连接不上 DevEco Studio,先检查 USB 调试是否打开、驱动是否安装、设备是否已在开发者选项中授权电脑。部分型号的设备在首次连接时需要确认“允许 USB 调试”弹窗,这是很容易忽略的一步。
9. 毕设验收与答辩准备:功能完整度要能自圆其说
9.1 按模块整理验收清单
答辩演示之前,建议按模块整理一份验收清单,逐项自测。
| 模块 | 必测功能 | 验收标准 |
|---|---|---|
| 用户端 | 登录注册、退出 | 使用数据库中的账号可登录,错误密码有提示 |
| 笔记 | 首页瀑布流、详情、点赞、收藏、发布 | 发布后后台可见,后台审核通过后用户端可查看 |
| 搜索 | 关键词搜索笔记或商品 | 输入关键词返回正确结果 |
| 商城 | 商品列表、详情、购物车、下单 | 购物车选中项正确,订单状态正确流转 |
| 个人中心 | 我的发布、我的收藏、订单列表 | 数据与用户关联正确 |
| 后台 | 登录、看板、管理操作 | 审核、上下架、发货状态能同步到用户端 |
清单里最容易漏的是“发布笔记后走后台审核再上线”这条链路。很多毕设只做了页面展示,没有把内容状态管理起来,答辩时被问“谁审核内容”就答不上来。这一条链路完整跑通,整个项目的业务价值会明显提升。
9.2 演示路径:先主链路,再功能点
演示不要从“登录”开始慢慢点,要先给评委一条清晰的主链路。
推荐演示顺序:
- 打开 App,展示首页双列瀑布流,截图展示界面效果。
- 登录账号,进入笔记详情,演示点赞和收藏。
- 点击发布按钮,上传一张图片并填写内容,提交笔记。
- 切换到 Web 后台,演示管理员登录,找到待审核笔记并审核通过。
- 回到 App,刷新首页,看到刚发布的笔记已经上线。
- 进入商城模块,把商品加入购物车,确认下单并模拟支付。
- 回到 Web 后台,找到新订单并执行发货。
- 回到 App,查看订单状态变为已发货。
这条路径把 App、服务端、Web 后台三个端都串了起来,每一步都有明确的因果验证。相比“这里能点赞、那里能加购”的碎片化演示,这条链路更能体现项目完整性。
9.3 答辩高频问题与准备方向
答辩时经常被问到四类问题:
- 为什么选择鸿蒙系统和 ArkTS,而不是 Android 或 Flutter。可以从“新平台、原生能力、声明式 UI、与 TypeScript 语法相近”几个角度回答,不要贬低其他技术,只讲选题差异。
- 状态管理是怎么做的。要能说出
@State、@Prop、@Link的区别,以及数组更新为什么需要重新赋值。 - 服务端接口如何保证安全。至少要说密码加密存储、登录 token、后台接口的角色校验。没有做 token 校验的项目在答辩时容易失分。
- 如果数据量变大,项目怎么优化。可以从分页、懒加载、图片缓存、服务端索引、Redis 缓存几个方向回答。前面代码里已经用了
LazyForEach和分页,这一点就能直接关联上。
提前把这些问题的答案写成文字稿,比临场组织语言要稳得多。
10. 最佳实践与扩展方向:从毕设项目升级成简历项目
10.1 工程化上最值得做的几个改进
如果时间和精力允许,建议在完成基础功能后加入以下工程化改进:
- 接口统一封装:所有请求都走一个
ApiClient,统一处理错误码、加载态和 token。 - 配置文件外置:
baseUrl、图片域名放到配置文件中,不要在页面里散落写死。 - 日志规范:App、服务端、Web 后台都保留运行日志,至少能查清请求失败发生在哪个环节。
- 异常分支处理:用户拒绝权限、网络超时、库存不足、订单重复提交,这些场景要有明确提示。
- 密码安全:用户密码在服务端使用
bcrypt或PBKDF2哈希再存储,不能明文入库。
这些点不一定都写在答辩 PPT 里,但在代码审查和系统演示中会体现出来。尤其是“库存不足”和“重复提交订单”的异常处理,是很多毕设项目欠缺的地方。
10.2 可以继续扩展的方向
这个题目可以从两个方向继续拔高。
第一个方向是多端协同。鸿蒙系统强调分布式能力,可以尝试在平板、手机之间接力演示,或者用分布式数据把自己的笔记同步到另一个设备。这一块不需要做很大,有一个小型场景演示即可,但能体现对鸿蒙平台特性的理解。
第二个方向是内容推荐和数据可视化。用户端可以记录用户的浏览、点赞行为,服务端做一个简单的标签推荐策略;Web 后台的数据看板可以加入图表,展示签到趋势、订单金额趋势。这块可以参考开源图表库或服务端统计函数实现。推荐策略不要求做成复杂算法,能解释清楚“基于内容标签的简单推荐思路”就已经超过大多数选题。