Hi工大Pro项目前端技术细节解析

介绍

Hi工大Pro 是一款服务于大学生的校园生活平台,使用人数达到数万人,用户粘性较高,深受广大用户喜爱。该项目包含论坛社交、今日课表、查课表、查成绩、查空教室、失物招领、寻物启事、校园天气查询等一系列功能,并全局适配暗黑主题,支持更换主题配色。可以扫码体验,包含微信和QQ小程序。

微信小程序

QQ小程序

技术相关

本人作为该项目团队技术负责人,负责该项目技术栈架构选型、前后端开发、项目部署、代码规范约束、项目UI设计等工作。其中该项目80%前端开发及95%后端开发均由本人独立完成。

前端技术架构

版本迭代

V1.x ~ V2.x (2020.3 ~ 2021.9)

该版本前端技术栈采用的是原生小程序,通过Gitee平台进行项目代码管理,有代码相应规范,但未采取统一的集成部署,仍是手动打包上传至微信再手动提交审核。

该版本迭代优化后,包含今日课表、查课表、查成绩、查空教室、绩点预警、等级考试、竞赛通知、校园天气、社团组织、校车校历、失物招领、寻物启事、趣玩(趣定向打卡功能)、趣跑(实现了小程序端的在线跑步,包含运动轨迹地图、速度卡路里标注、在线排行榜)等相应功能。

V3.x (2021.9 ~ 至今)

后续应项目需要,为实现多平台部署,降低开发成本(如打包为Android和iOS应用)。遂采取UniApp框架对项目进行重构优化(可以实现多平台部署),并加入了 node_modules包管理Jenkins集成部署。该项目于2021年9月底大部分重构完毕后,统一部署至微信和QQ小程序端。

该版本主要加入了论坛社交模块,支持手机号、微信、QQ登录,且支持多校园多系统(后端实现了多个教务系统的爬虫)适配,并迭代掉了非必要的社团组织、等级考试、竞赛通知、趣玩、趣跑等功能。

项目结构

项目结构

该项目使用 Yarn 作为包管理工具。其中最重要的是 src 目录下的一系列文件,如:

  • assets、static: 静态文件,如 css 及 图片 等
  • ci: 包含微信及QQ小程序的集成部署相关操作
  • components: 项目中引入的组件,包含自定义封装组件与外部组件库等。
  • pages: 项目页面相关源代码
  • private-key: 继承部署验证需要的私钥文件
  • utils: 项目需要的一些工具类库等

技术细节

如何实现的暗黑主题(多配色)适配?

一般来说,暗黑主题适配有两种方案 :

  • 根据 css 中的 媒体查询,@media(prefers-color-scheme
    : dark )
  • 通过调用API获取系统中的当前主题再做出相应css修改。

我们这个项目采取的是第二种方法,那么为什么不采取第一种方案呢?不是更方便一些吗,甚至都不需要写JS层面的代码。

首先我们应该知道如果通过媒体查询进行适配暗黑主题色,那么当前应用的主题色只能和系统保持一致,而无法被用户通过应用内的设置做出相应的修改。

而如果当我们采取API获取系统主题方式,可以让用户选择是否跟随系统主题,获取应用内独立设置当前主题配色。

可以看下项目中该部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const loadTheme = (that, isSetFrontColor = true, setTabbar = false) =>  {
// 其中 that 指向的是调用loadTheme函数的指定应用实例
// isSetFrontColor 表示是否需要设置前景色
// setTabbar 表示是否需要修改tabbar的主题色
uni.setBackgroundColor({
backgroundColor: this.globalData.theme.navbarColor,
});
if (isSetFrontColor || this.globalData.theme.type == "dark") {
uni.setNavigationBarColor({
frontColor: this.globalData.theme.statusColor,
backgroundColor: "#ffffff",
});
} else {
if (this.globalData.theme.type == "light") {
uni.setNavigationBarColor({
frontColor: "#000000",
backgroundColor: "#ffffff",
});
}
}
if (typeof this.globalData.theme.navbarColor === "undefined") {
this.globalData.theme.navbarColor =
this.globalData.theme.backgroundColor;
}
let themeType = this.globalData.theme.type;
let currentType = this.globalData.tabbar.current;

if (setTabbar) {
let list = [];
let length = list.length;
this.globalData.tabbar.current = themeType;
if (themeType === "dark") {
uni.setTabBarStyle(this.globalData.tabbar.dark);
list = this.globalData.tabbar.dark.list;
length = list.length;
} else {
uni.setTabBarStyle(this.globalData.tabbar.light);
list = this.globalData.tabbar.light.list;
length = list.length;
}
for (let i = 0; i < length; i++) {
// 对 tabbar中的每一个 item 进行遍历,并调用 setTabBarItem API进行相应修改
uni.setTabBarItem({
index: i,
...list[i],
});
}
}
// 此处主题色保存在全局遍历 theme 中
(that.themeType = this.globalData.theme.type),
(that.statusColor = this.globalData.theme.statusColor),
(that.navFontColor = this.globalData.theme.navFontColor),
(that.fontColor = this.globalData.theme.fontColor),
(that.navbarColor = this.globalData.theme.navbarColor),
(that.bgColorTop = this.globalData.theme.navbarColor),
(that.bgImage = this.globalData.theme.bgImage),
(that.scheduleBgImage = this.globalData.theme.scheduleBgImage),
(that.homeBgImage = this.globalData.theme.homeBgImage),
(that.appImages = this.globalData.appImages);
},

然后在指定页面实例中(如pages/index/index.vue),我们会在 onLoad 这个Hook函数中调用全局提供的 loadTheme 函数,如下:

1
2
3
4
5
6
7
8
9
10
11
// 此处 app 通过 getApp() 获取的,指向的是全局实例。
if (app.globalData.theme.ready) {
app.loadTheme(this, app.globalData.theme.type == "dark", true);
} else {
// 为什么需要这个 callback 回调呢?
// 因为主题色配置 app.globalData.theme 是在 localstorage 中读取的,而 onLoad 钩子函数触发的事件不一定在配置内容读取完毕之后发生。
// 所以需要一个ready的判断,如果未完成读取,则增加一个回调,等待读取完毕后直接调用
app.themeReadyCallback = res => {
app.loadTheme(this, app.globalData.theme.type == "dark", true);
}
}

那么 loadTheme 调用完毕后,页面上会发生什么呢? -> 主题会被改变

主题是如何被改变的呢,这里可以参考以下内容:

1
2
3
4
5
6
7
<template>
<view :class="'theme-' + themeType">
所有的pages页面最外层均会包含一个 themeType主题类型的变量。
通过修改这个变量再结合Vue的响应式原理,通知到使用到这个变量的地方进行相应修改。
然后根据不同的 class 类选择器,页面则会渲染不同的样式。
</view>
</template>

公共暗黑主题样式(assets/css/theme-dark.less)

1
2
3
4
5
6
.theme-dark{
input,textarea{
color:var(--theme-dark-font)!important;
}
// ...
}

这里其实还涉及到主题配置项读取的函数及主题配色修改页面的一些函数,但内容过多,就不过于赘述了。

项目图片懒加载是如何实现的?

这个项目中的绝大部分图片(例如论坛动态中的图片、头像等)都用组件进行封装,并加入了图片懒加载,避免一次性加载完毕,缓解CDN的压力。

有的可能看到就觉得,图片懒加载还不简单,我倒着都能写!

其实差不多是这样,技术含量不高,但存在让我们思考的细节。

未完待续


Hi工大Pro项目前端技术细节解析
https://toflying.com/2022/09/16/11-hi-miniprogram/
作者
KingChen
发布于
2022年9月16日
许可协议