移动端项目APP
创建项目
在 VSCode 中开发 UniApp(APP 端),需要进行一些环境配置,包括安装必要的扩展、配置 HBuilderX、运行调试等。下面是完整的步骤
通过 cli工具创建项目
npx degit dcloudio/uni-preset-vue#vite-ts cooling-app
cd cooling-app/
安装依赖
npm install
通过Hbuilder创建项目(通过这种方式创建的,默认项目中没有vite.config.ts文件)
如果你在 HBuilderX 中创建的项目没有初始化 npm,你首先需要初始化 npm。
打开 HBuilderX,进入你的项目文件夹
运行以下命令来初始化 npm
npm init -y
这将生成一个默认的 package.json 文件
模拟器抓包
打开 Fidder,Tools,HTTPS - 勾选 DecryptHttpsTraffic - 点击 Actions - 导出 Export 到桌面
把证书格式更改,把cer格式变为pem,
openssl x509 -in 当前路径.cer -inform DER -out 新文件路径.pem -outform PEM
openssl x509 -in ~/Desktop/123.cer -inform DER -out 123.pem -outform PEM
openssl x509 -inform DER -in Fiddler_Root_Certificate_Authority.cer -out cacert.pem
查看哈希值
openssl x509 -subject_hash_old -in pem文件
openssl x509 -in -subject_hash_old -in 123.pem
openssl x509 -inform PEM -subject_hash_old -in cacert.pem
$ openssl x509 -inform PEM -subject_hash_old -in 123.pem
269953fb
-----BEGIN CERTIFICATE-----
T6arzzah/dxqqmtE1uYxaezynHF/XEKh79jDhEKnRSbYFS28IcY=
-----END CERTIFICATE-----
将桌面上的 cacaert.pem 证书 修改为 0725b47c.0 ,名称为上一步操作的 hash 值,后缀为 0
启动一个Android 7 以上的模拟器,按照下图指示
启动模拟器
设置模拟器静态IP并且开启网桥模式
开启模拟器Root
修改完重启模拟器
修改模拟器WIFI代理为手动,设置为 Filder的IP地址和端口
打开夜神模拟器设备的 USB 调试选项
将0725b47c.0证书放入夜神模拟器系统证书目录
复制上图中的0725b47c.0文件,然后打开下图指示的根目录文件夹,点击右上角的粘贴按钮,即可将0725b47c.0证书放入系统证书目录 /system/etc/security/cacerts/
夜神模拟器 cmd 环境配置
这个文件夹内可以看到有个 nox_adb 应用程序
打开查看高级系统设置,进入下图页面,按照顺序进行配置环境变量地址即可,在系统变量的Path中添加 nox_adb的路径
D:\Program Files\Nox\bin
给 0725b47c.0 证书赋予权限
使用 nox_adb devices 命令查看设备连接状态
使用 nox_adb conncet 127.0.0.1:62025 命令建立 adb 连接
nox_adb connect 127.0.0.1:62001
夜神模拟器的端口是规律的,第一个模拟器端口是62001,第二个模拟器端口是62025,第三个是62025+1,以此类推
使用nox_adb -s 127.0.0.1:62025 shell 命令进入模拟器 shell 命令行
nox_adb -s 127.0.0.1:62001 shell
使用 mount -o remount -rw /system 命令给外层根目录中的 system 赋予权限
使用 cd system/etc/security/cacerts 命令到系统证书目录
使用 chmod 644 0725b47c.0 命令给证书赋予权限
chmod 644 269953fb.0
修改完成后,使用ls -l 命令查看权限是否启用,如下图所示
最后需要在把最外层system的权限改回去
cd ../../../..
mount -o remount -ro system
以下为OpenSSL安装:
去官网下载好以后傻瓜式安装一直下一步,然后部署环境变量即可。
部署完cmd命令行输入openssl显示以下页面表示安装成功
安装完证书后,浏览器可能会一直弹窗报错“该网站的完全证书有问题
浏览器 【设置】- 【隐私与安全】- 【显示安全警告】 ,取消勾选就行
Fiddler过滤抓包如何设置
配置fiddler允许监听到https
设置过滤规则: 在 Fiddler 中,过滤规则可以通过 Filters 面板来配置
在 Fiddler 界面上,点击上方的 Filters 标签。
在左侧面板中,你会看到一个名为 Use Filters 的选项,确保这个选项被勾选。
配置过滤条件: 你需要通过 Host 或 IP 地址来过滤特定的客户端流量。步骤如下
根据 IP 过滤:
在 Filters 面板中,找到 Request Filters 部分。
你可以在 Host 或 IP 地址框中输入需要过滤的客户端 IP 地址。
比如,你想要过滤 192.168.1.100 这个 IP 的流量,你可以在 Host 或 Request URL 中设置为该 IP。
选择 Only Show Traffic to/from...:
这可以帮助你进一步精确过滤,仅捕获目标 IP 与 Fiddler 代理之间的流量。
使用 FiddlerScript 进一步定制过滤条件(可选): 如果你需要更加复杂的过滤条件,Fiddler 允许你通过 FiddlerScript 脚本来设置过滤条件。
打开 FiddlerScript,点击 Rules -> Customize Rules。
在 OnBeforeRequest 或 OnBeforeResponse 函数中,可以编写自定义代码来检查流量的源 IP,过滤不需要的流量。你可以使用以下代码来判断请求的来源 IP:
if (oSession.remoteIP !== "192.168.1.100") {
oSession["ui-hide"] = "True"; // 隐藏非目标 IP 的请求
}
这样,只有来自 IP 192.168.1.100 的流量会被显示。
验证过滤: 过滤设置完成后,你可以进行相应的测试,确保 Fiddler 只显示来自指定客户端 IP 的流量。
安装插件
sass安装
npm install sass@1.77.5
uviewUI组件
安装
https://uviewui.com/
使用 Hbuilder 导入
UnoCss
基本使用
原子化CSS是一种CSS架构方式,其支持小型、单一用途的类,其名称基于视觉功能
安装
npm install -D unocss
vite配置
// vite.config.js
import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
UnoCSS(),
],
})
// webpack.config.js
const UnoCSS = require('@unocss/webpack').default
module.exports = {
plugins: [
UnoCSS(),
],
optimization: {
realContentHash: true,
},
}
在根目录创建一个uno.config.ts配置文件
用于编写用户想要的 unocss 配置
// uno.config.js
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})
全局引入
// main.js
import 'virtual:uno.css';
安装pinia
将 vue 版本更新到 3.5.13,以匹配 pinia
npm install vue@3.5.13
npm install pinia
新建 stora/index.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 启用持久化插件
export default pinia
在 main.js 中引入并使用 Pinia:
import { createSSRApp } from "vue";
import App from "./App.vue";
import pinia from './store'
export function createApp() {
const app = createSSRApp(App);
app.use(pinia)
return {
app,
pinia
};
}
创建 store 文件夹并定义状态管理模块
在项目根目录下创建一个 store 文件夹,然后在其中创建所需的状态管理模块
// store/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
});
在需要使用状态的页面中,引入并使用 useCounterStore
<script setup>
import { useCounterStore } from '@/store/counter.js';
const counterStore = useCounterStore();
</script>
======================
自定义存储和自定义数据序列化程序。
npm i pinia-plugin-persistedstate@4.2.0
将插件添加到你的 pinia 实例中:
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
在定义具体的 Store 时,使用 persist 选项来启用持久化功能
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: '',
}),
persist: true, // 启用持久化
});
在状态管理模块中启用持久化:
// store/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
persist: true, // 启用持久化
});
通过上述步骤,您可以在 uni-app 的 HBuilder X 项目中成功集成并使用 Pinia 进行状态管理
自定义持久化内容
如果您需要自定义持久化的行为,例如更改存储方式或指定需要持久化的状态字段,可以在 persist
选项中进行配置:
自定义持久化配置(可选)
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: '',
}),
persist: {
key: 'user_data', // 自定义存储的键名
storage: sessionStorage, // 使用 sessionStorage 而非 localStorage
paths: ['token'], // 仅持久化 token 字段
},
});
请求拦截器
src/interceptors/request.ts
// request拦截器
export const requestInterceptor = () => {
// 添加请求和响应拦截器
uni.addInterceptor('request', {
invoke(args) {
// 请求前的处理,如添加 token
const token = uni.getStorageSync('TOKEN');
if (token) {
args.header = {
...args.header,
Authorization: `Bearer ${token}`,
};
}
return args;
},
success(response) {
// 响应后的处理,如统一处理错误码
if (response.statusCode === 401) {
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
});
// 可以跳转到登录页面
uni.redirectTo({ url: '/pages/login/login' });
}
return response;
},
fail(err) {
// 请求失败的处理
uni.showToast({
title: '请求失败,请检查网络',
icon: 'none',
});
return Promise.reject(err);
},
});
}
路由拦截器
src/interceptors/route.ts
import { getToken } from "@/utils/system";
// 定义需要拦截的路由跳转方法
const methodsToIntercept = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'];
// navigate拦截器
const navigateToInterceptor = (args) => {
const WHITE_LIST = ['/pages/login/login']; // 定义白名单页面
// 获取目标页面的路径
const url = args.url.split('?')[0];
// 如果目标页面在白名单中,则直接放行
if (WHITE_LIST.includes(url)) {
return args;
}
// 获取token
const token = getToken();
if (!token) {
// 如果没有 Token,提示用户并跳转到登录页面
uni.showToast({ title: '请先登录', icon: 'none' });
uni.navigateTo({ url: '/pages/login/login' });
return false; // 阻止原始跳转
}
// Token 有效,允许跳转
return args;
}
export const routeInterceptor = {
install() {
methodsToIntercept.forEach((method) => {
uni.addInterceptor(method, {
invoke: navigateToInterceptor,
});
});
},
}
拦截器
src/interceptors/index.ts
export { requestInterceptor } from './request'
export { routeInterceptor } from './route'
在main.ts中挂载
import { createSSRApp } from "vue";
import App from "./App.vue";
import pinia from './store'
import { routeInterceptor ,requestInterceptor } from './interceptors/index'
export function createApp() {
const app = createSSRApp(App);
app.use(pinia)
app.use(routeInterceptor)
app.use(requestInterceptor)
return {
app,
pinia
};
}
src目录
在 uni-app 中,默认情况下 @ 符号不会指向 src 目录,而是需要手动配置。
在项目根目录的 vite.config.js 文件中,添加以下代码:
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'), // 将 @ 指向 src 目录
},
},
});
跨域配置
底部配置
在 pages.json 中配置
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/device/device",
"style": {
"navigationBarTitleText": "设备"
}
},
{
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "用户"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "通信基站机柜高效热管理平台",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#9799a5",
"selectedColor": "#3284FF",
"borderStyle": "white",
"list": [{
"pagePath": "pages/index/index",
"iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home-h.png",
"text": "首页"
},
{
"pagePath": "pages/device/device",
"iconPath": "static/images/tabbar/device.png",
"selectedIconPath": "static/images/tabbar/device-h.png",
"text": "设备"
},
{
"pagePath": "pages/user/user",
"iconPath": "static/images/tabbar/my.png",
"selectedIconPath": "static/images/tabbar/my-h.png",
"text": "我的"
}
]
},
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
"condition": { //模式配置,仅开发期间生效
"current": 0, //当前激活的模式(list 的索引项)
"list": [{
"name": "", //模式名称
"path": "", //启动页面,必选
"query": "" //启动参数,在页面的onLoad函数里面得到
}]
}
}
配置高德Key
配置 manifest.json 后再打自定义基座,再自定义基座确定好 包名 然后等待 云打包完成,可以登陆 uclioud后台看到应用的 SHA1和包名。然后在运行,使用自定义基座运行。
获取高德地图 API Key
访问 高德开放平台。
注册并登录后,进入「应用管理」->「创建新应用」。
选择 Web服务 类型,获取 API Key(key)。
云打包,使用云打包中的证书中的SHA1 5D:2B:50:B0:DF:5B:A5:EA:A5:9F:66
启用 逆地理编码(Geocode) 和 POI搜索 服务。
在 manifest.json 配置
在 HBuilderX 中,打开 manifest.json,找到 App -> SDK 配置 -> 地图,填入你的 API Key
{
"appid": "",
"uniStatistics": {},
"permissions": {
"Maps": {
"provider": "amap",
"key": {
"android": "你的高德API Key",
"ios": "你的高德API Key"
}
}
}
}
在 main.ts 中初始化地图
在 main.ts 文件中,确保 uni-app 知道你使用的是高德地图:
uni.setStorageSync('amap_key', '你的高德API Key');
使用腾讯地图SDK
uni 请求
在使用 uni.request 发起网络请求时,处理请求参数的方式与 axios 存在一些差异。在 axios 中,params 通常用于 GET 请求的查询参数,data 则用于 POST 请求的请求体。而在 uni.request 中,所有请求类型的参数都通过 data 字段传递
在 uni.request
中处理请求参数的建议
// 方法一:将查询参数直接添加到 URL 中
uni.request({
url: 'https://example.com/api/resource?param1=value1¶m2=value2',
method: 'GET',
success: (res) => {
// 处理响应
},
fail: (err) => {
// 处理错误
}
});
// 方法二:使用 data 字段传递查询参数
uni.request({
url: 'https://example.com/api/resource',
method: 'GET',
data: {
param1: 'value1',
param2: 'value2'
},
success: (res) => {
// 处理响应
},
fail: (err) => {
// 处理错误
}
});
**POST 请求:**将请求体参数放在 data
字段中
uni.request({
url: 'https://example.com/api/resource',
method: 'POST',
data: {
key1: 'value1',
key2: 'value2'
},
success: (res) => {
// 处理响应
},
fail: (err) => {
// 处理错误
}
});
封装 uni.request
以模拟 axios
的用法:
为了使 uni.request
的使用方式更接近 axios
,可以进行封装,使其支持类似的配置项。
const request = (options) => {
const { url, method = 'GET', params = {}, data = {}, header = {} } = options;
// 处理 GET 请求的查询参数
let fullUrl = url;
if (method.toUpperCase() === 'GET' && Object.keys(params).length > 0) {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
fullUrl = `${url}?${queryString}`;
}
// 发起请求
return new Promise((resolve, reject) => {
uni.request({
url: fullUrl,
method,
data: method.toUpperCase() === 'GET' ? {} : data,
header,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
}
});
});
};
// 使用示例
request({
url: 'https://example.com/api/resource',
method: 'GET',
params: {
param1: 'value1',
param2: 'value2'
}
}).then(response => {
// 处理响应
}).catch(error => {
// 处理错误
});
您可以使用 qs 插件将参数对象序列化为查询字符串,以处理类似 axios 的 params 参数。qs 是一个流行的查询字符串解析和序列化库,常用于将对象转换为 URL 查询字符串格式
npm install qs
在 uni.request 中使用 qs 处理查询参数
封装request
src/utils/request.ts
在使用 uni.request 发起 GET 请求时,您可以使用 qs.stringify() 方法将参数对象转换为查询字符串,并将其附加到 URL 中
import qs from 'qs';
//ES6类
class Service {
//原型对象的方法
API(options: any): any {
var queryString = '';
// 解构
const {params = {}, data = {}, header = {} } = options;
// 定义全局基础路由
const baseURL = 'http://jdgz.xwydl.com:8310';
//加载效果开启
uni.showLoading({
title: '加载中...'
})
//判断用户是否登录:如有token携带
const token = uni.getStorageSync('TOKEN');
// 定义请求头
header['Content-Type'] = 'application/json;charset=UTF-8'
// 如果token存在,则携带请求头
if (token) {
header.token = token;
}
// 如果存在 params
if(params){
// 序列化params
queryString = '?' + qs.stringify(params)
}else{
queryString = ''
}
// 返回一个promise对象
return new Promise((resolve, reject) => {
//uni-app 提供的API进行网络发请求
uni.request({
url: baseURL + options.url + queryString,// 请求的地址
method: options.method || 'GET',// 请求方式
data: options.data || {},// 携带的参数
header, // 请求头
success(res) {// 请求成功的回调
resolve(res.data); // 返回一个成功的Promise对象,同时简化数据
},
fail(error) { // 请求失败的回调
reject(error);
},
complete() { // 成功失败都执行
uni.hideLoading(); // 隐藏加载动画
}
})
})
}
// get请求方法
get(options: any) {
options.method = "GET";
return this.API(options);
}
// post请求方法
post(options: any) {
options.method = "POST";
return this.API(options);
}
// delete 请求方法
delete(options: any) {
options.method = "DELETE";
return this.API(options);
}
}
// 暴漏数据
export default Service;
封装API
src/api/home/home.ts
import Service from "@/utils/request";
class Request extends Service {
// 登陆请求
reqLogin(data:any){
return this.post({
url:'/api/mob/login',
data:data
})
}
// 获取地图数据
reqDeviceMapList(params:any){
return this.post({
url:'/api/core/device/map/list',
data:null,
params:params
})
}
}
export default new Request;
nvue
启用nvue格式,来支持原生SDK或API的支持
首先在pages.json文件中,把对应的页面添加上 app-plus;
{
"path" : "pages/map/map",
"style" :
{
"navigationBarTitleText" : "地图",
"navigationStyle": "custom",
"app-plus":{"nvue":true}
}
},
然后更改文件的后缀 map.vue => map.nvue
使用vue2的语法编写
map.nvue
坐标系
高德默认使用的gcj02(国内)坐标系得到的经纬度,一些第三方软件如 奥维地图获取的经纬度是 wsg(国际)坐标,两者相差500m左右。注意,根据需要进行转换
wsg转gcj02
// 坐标转换方法(WGS84 -> GCJ02)
function wgs84ToGcj02(lon, lat) {
const a = 6378245.0;
const ee = 0.00669342162296594323;
function outOfChina(lon, lat) {
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
function transformLat(x, y) {
let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y +
0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLon(x, y) {
let ret = 300.0 + x + 2.0 * y + 0.1 * x * x +
0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
}
if (outOfChina(lon, lat)) {
return [lon, lat];
}
const dLat = transformLat(lon - 105.0, lat - 35.0);
const dLon = transformLon(lon - 105.0, lat - 35.0);
const radLat = lat / 180.0 * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
const mgLat = lat + (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
const mgLon = lon + (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
return [mgLon, mgLat];
}