移动端开发篇(uni版)

一、项目构建与调试

项目构建分别为两种


1、基于和buildx构建


打开 hbuildx -> 新建 -> 项目 -> uni-app ->选择一个模板 (直接推荐 默认)
你会得到如下项目结构:

  • pages - 存放所有页面文件的目录,每个页面通常包含 .vue 文件
  • static - 静态资源目录,用于存放图片、字体等静态文件
  • App.vue - 应用的根组件,定义应用的全局生命周期和样式
  • main.js - 应用的入口文件,用于初始化 Vue 实例和全局配置
  • manifest.json - 应用的配置文件,包含应用名称、图标、版本等信息,特别是打包 App 时的配置
  • pages.json - 页面路由配置文件,定义页面路径、窗口样式、tabBar 等
  • shime-uni.d.ts - TypeScript 类型声明文件,为 uni-app API 提供类型定义
  • uni.scss - 全局样式变量文件,可定义全局使用的样式变量
     

2、使用npx 直接创建 (推荐)


npx degit dcloudio/uni-preset-vue#vite my-project
详细网址如下 uni-app官网
项目结构如下图所示:

npx构建的项目相对于hubuildx 创建的项目来说提供了更多便利
1、 package.json
     (uni的基础依赖)、
        vite.config(构建工具配置)、
       .gitignore自动集成到里面
2、可以使用其他代码编辑器直接开发
        直接用npm run dev:h5 就可以直接吧项目跑起来了相对hbuildx更方便
3、更适合复杂项目
        对于大型项目,命令行方式通常更灵活
        可以更好地管理复杂的依赖关系
4、更好的定制性   
        可以更方便地修改 Vite 配置  
        更容易集成第三方工具和库 
        更容易自定义构建流程.

界面预览&调试


1、H5与App端调试方法


hbuilder开发过程中选择运行 -> 然后可以选择内置浏览器 或者直接运行到浏览器

在npx构建项目中可以 直接 npm run dev:h5 但是具体看 package.json 的配置;
关于数据请求渲染页面相关在 开发配置项中介绍


2、微信小程序调试


微信小程序的界面调试相对来说麻烦点 当然可以用测试appId 

 关于appId 的申请 :
1、微信公众平台:微信公众平台 打开后账号分类选择“小程序”
2、点击注册按钮,进入小程序注册步骤 注册
3、进行信息登记 完善信息等等  
相关请参考

第一步appid
1、配置appId 用hbuilder 打开 manifest.json 选择微信小程序配置 
2、或者直接在manifest.json —— "mp-weixin"中填写

第二步 配置运行微信开发者工具的路径
hbuilder 中

npx 项目中

就可以了 

第三步 跑起来就完了

二、开发 配置项 注意事项

1、路由导航栏篇

 easycom: 组件自动引入规则

pages
        path: 页面路径
        style: 页面样式配置,包括导航栏、下拉刷新等
        app-plus->titleNView 可以配置头部导航栏按钮内容等


globalStyle:全局样式配置(导航栏文字颜色、背景色;页面背景色等)

tabBar:底部导航栏配置
        color: 未选中颜色
        selectedColor: 选中颜色
        list: 导航项配置

导航栏高度计算

其中可以稍微注意一下一些默认属性 比如在写移动端的时候 经常要计算内容区的高度 
iOS:状态栏高度为 44px(iPhone X 及以上机型为 88px,包含安全区域)
Android:状态栏高度通常为 24px-28px,但各厂商可能有差异
微信小程序:导航栏高度固定为 44px,状态栏高度需动态获取
H5:通常没有原生导航栏,可自定义高度

  // 获取导航栏高度
  const getNavBarHeight = () => {
    const systemInfo = uni.getSystemInfoSync();
    const statusBarHeight = systemInfo.statusBarHeight;
    
    // 导航栏高度
    let navBarHeight = 44; // 默认值
    
    // #ifdef MP-WEIXIN
    navBarHeight = 44; // 微信小程序固定值
    // #endif
    
    // #ifdef APP-PLUS
    navBarHeight = systemInfo.platform === 'ios' ? 44 : 48;
    // #endif
    
    // 总高度 = 状态栏 + 导航栏
    const totalHeight = statusBarHeight + navBarHeight;
    const tabBarHeight = 50; // 底部导航栏固定高度

    return {
      statusBarHeight,
      navBarHeight,
      tabBarHeight,
      totalHeight
    };
  };


2、页面篇

 html


页面代码相对没那么多注意的东西就正常的看成 .vue 文件就好了 
当然我们在开发过程中也要注意适配 
比如 在html中:div-> view 、 img->image 、 span->text 适当进行统一 这样在运行小程序或者打包的时候能避免大多数问题
图片方面 移动端尽量少使用本地图片 要想直接适配小程序 微信小程序主包限制为20MB,分包每个不超过2MB  所以图片能用请求就用请求  当然 不兼容小程序无所谓

推荐比较好用一点的form 表单组件 uni-form 个人觉得还行 经过全局引入之后可以直接使用
代码示例

//安装 npm install  @dcloudio/uni-ui

//全局注册  pages.json中

 "easycom": {
    "autoscan": true,
    "custom": {
      "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
    }
  }
<template>
    <div class="edit-class-container">
        <uni-forms ref="form" :model="formData" :rules="rules">
            <uni-forms-item label="班次名称" name="className">
                <uni-easyinput v-model="formData.className" placeholder="请输入班次名称" />
            </uni-forms-item>

            <uni-forms-item label="上班时间" name="startTime">
                <view class="time-picker" @click="openTimePicker('start')">
                    <text>{{ formData.startTime || '请选择上班时间' }}</text>
                    <text class="picker-icon">></text>
                </view>
            </uni-forms-item>

            <uni-forms-item label="下班时间" name="endTime">
                <view class="time-picker" @click="openTimePicker('end')">
                    <text>{{ formData.endTime || '请选择下班时间' }}</text>
                    <text class="picker-icon">></text>
                </view>
            </uni-forms-item>
        </uni-forms>

        <button class="submit-btn" @click="handleSubmit">确定</button>
    </div>

    <!-- 时间选择器弹窗 -->
    <uni-popup ref="popup" type="bottom">
        <view class="picker-container">
            <view class="picker-header">
                <text @click="cancelPicker">取消</text>
                <text @click="confirmPicker">确定</text>
            </view>
            <picker-view class="picker-view" :value="timeArray" @change="handleTimeChange">
                <picker-view-column>
                    <view class="item" v-for="(hour, index) in hours" :key="index">{{ hour }}</view>
                </picker-view-column>
                <picker-view-column>
                    <view class="item" v-for="(minute, index) in minutes" :key="index">{{ minute }}</view>
                </picker-view-column>
            </picker-view>
        </view>
    </uni-popup>
</template>
const form = ref(null);
const popup = ref(null);
const formData = ref({
    className: '',
    startTime: '',
    endTime: ''
});

// ....


在样式上


尽量不要去使用 background-image这个东西(在小程序中没用)

其他样式基本上正常写

路由跳转

 uni.navigateTo(OBJECT)  :保留当前页面,跳转到应用内的某个页面
新页面会入栈,可以通过navigateBack返回
页面栈最多十层,超出会报错
保留当前页面状态

适用:详情页面跳转、表单页面跳转、需要返回的页面跳转

uni.redirectTo(OBJECT):关闭当前页面,跳转到应用内的某个页面

适用: 登录成功后跳转、完成某个流程后跳转、不需要返回的场景

uni.reLaunch(OBJECT):关闭所有页面,打开到应用内的某个页面

适用: 回到首页、切换用户身份后刷新应用、需要重置应用状态的场景

uni.switchTab(OBJECT):跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
适用场景:底部标签页切换、从非tabBar页面跳转到tabBar页面

uni.navigateBack(OBJECT):关闭当前页面,返回上一页面或多级页面

路由传参 不一一列举详细请看文档

// 发送参数
uni.navigateTo({
  url: 'pages/detail/detail?id=1&type=news&title=标题'
});

// 接收参数
export default {
  onLoad(options) {
    console.log(options.id); // 1
    console.log(options.type); // news
    console.log(options.title); // 标题
  }
}


生命周期 


onLoad  路由传参在接收 、初始化数据
onshow 一般用于更新页面数据等 开始动画
onHide 隐藏 结束动画 清除定时器 延时器
onUnload 页面被销毁,内存被回收
性能优化方面 
        使用onLoad缓存不常变化的数据
        在onHide中暂停不必要的操作
        使用onShow进行按需刷新而非全量刷新

3、数据请求篇

import {
  // toast,
  getStorageSync
  // toLoginPage
} from './utils.js';

import { HTTP_REQUEST_URL, TOKENNAME } from './config.js';

const baseRequest = async (
  url,
  method,
  data,
  header,
  enableChunked = false,
  loading = false,
  needToken = true
) => {
  let token = await uni.getStorageSync('TOKEN');

  // console.log("请求参数", url, method, data, header, token)
  let headerBase = header
    ? header
    : {
        'Content-Type': 'application/json'
      };

  if (token && needToken && !url.includes('/map-api')) {
    headerBase[TOKENNAME] = token;
  }

  return new Promise((reslove, reject) => {
    loading &&
      uni.showLoading({
        title: 'loading'
      });
    uni.request({
      url: url.includes('/map-api') ? url : HTTP_REQUEST_URL + url,
      method: method || 'GET',
      header: headerBase,
      timeout: 200000,
      data: data || {},
      enableChunked: enableChunked,
      success: (res) => {
        // console.log("res===", res)
        if (res.data.code === 401) {
          uni.showModal({
            title: '提示',
            content: '身份过期请重新登录',
            success: function (res) {
              if (res.confirm) {
                uni.clearStorageSync();

                uni.redirectTo({
                  url: '/pages/login/index'
                });
                reslove(res.data);
              } else if (res.cancel) {
                console.log('用户点击取消');
                reslove(res.data);
                // 用户点击了取消按钮的相关逻辑可以放在这里
              }
            }
          });
        } else {
          reslove(res.data);
        }
      },
      fail: (msg) => {
        reject(msg);
      },
      complete: () => {
        loading && uni.hideLoading();
      }
    });
  });
};

const request = {};

['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
  request[method] = (api, data, header, enableChunked, loading, needToken) =>
    baseRequest(api, method, data, header, enableChunked, loading, needToken);
});

export default request;

这份数据请求能应对绝大多数场景
请求域名必须在小程序管理后台配置白名单
默认不支持跨域请求,所有请求都需要HTTPS

在uni-app中,我们不需要引入fetch或axios等第三方库,因为uni.request已经提供了完善的网络请求功能。然而,对于stream流式请求这种特殊场景,uni.request需要额外的配置才能正确处理。


// http: stream流式请求
// #ifdef MP-WEIXIN || app
export const chatFetch = async (data) => {
	let token = await uni.getStorageSync("TOKEN");
	let memberToken = await uni.getStorageSync("MEMBER_TOKEN");

	let headerBase = {
		"Content-Type": 'application/json',
		"AppId": data.app_id
	}
	if (token) {
		let requsetToken = memberToken ? memberToken : token
		headerBase[TOKENNAME] = JSON.parse(requsetToken);
	}

	const task = uni.request({
		url: `${HTTP_CHATAPI}/chatStream/stream/chat_new`,
		method: "POST",
		// responseType:"text",
		enableChunked: true, //注意配置
		// enableHttp2:true,
		data: data,
		header: headerBase,
		success: (res) => {},
		fail: (err) => {},
		complete: () => {}
	})

	return task
}
// #endif

// #ifdef H5
export const chatFetch = async (data) => {
	let token = await uni.getStorageSync("TOKEN");
	let memberToken = await uni.getStorageSync("MEMBER_TOKEN");
	// 在页面中创建 XMLHttpRequest 对象
	var xhr = new XMLHttpRequest()
	// 设置 XMLHttpRequest 对象
	xhr.open('POST', `${HTTP_CHATAPI}/chatStream/stream/chat_new`)
	xhr.responseType = 'text'
	xhr.timeout = 0
	xhr.setRequestHeader("Content-Type", 'application/json');
	xhr.setRequestHeader("AppId", data.app_id);
	if (token) {
		let requsetToken = memberToken ? memberToken : token;
		xhr.setRequestHeader(`${TOKENNAME}`, JSON.parse(requsetToken));
	}
	let previousResponseText = null;
	let onChunkReceivedCb = null;
	xhr.onChunkReceived = function(fn) {
		onChunkReceivedCb = function() {
			fn.apply(this, arguments)
		}
		return onChunkReceivedCb
	}
	
	xhr.onreadystatechange = function () {
		if (xhr.readyState === XMLHttpRequest.LOADING || xhr.readyState === XMLHttpRequest.DONE) {
			// 处理 HTTP 数据块
			// console.log("xhr.responseText", xhr.responseText)
			if(xhr.onChunkReceived) {
				let resText = xhr.responseText;
				if(previousResponseText) {
					let index = xhr.responseText.indexOf(previousResponseText);
					if(index != -1) {
						resText = resText.substring(index + previousResponseText.length)
					}
				}
				previousResponseText = xhr.responseText;
				onChunkReceivedCb(resText)
			}
		}
	}
	
	xhr.send(JSON.stringify(data))
	return xhr
}
// #endif

// #ifdef H5 这是使用fetch请求 暂时没验证过仅供参考
export const chatFetch = async (data) => {
  let token = await uni.getStorageSync("TOKEN");
  let memberToken = await uni.getStorageSync("MEMBER_TOKEN");
  
  // 构建请求头
  const headers = {
    "Content-Type": 'application/json',
    "AppId": data.app_id
  };
  
  if (token) {
    let requestToken = memberToken ? memberToken : token;
    headers[TOKENNAME] = JSON.parse(requestToken);
  }
  
  // 使用Fetch API进行流式请求
  const response = await fetch(`${HTTP_CHATAPI}/chatStream/stream/chat_new`, {
    method: 'POST',
    headers: headers,
    body: JSON.stringify(data)
  });
  
  // 检查响应状态
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  // 获取响应的ReadableStream
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  
  // 创建一个控制器对象,用于管理流
  const controller = {
    reader,
    decoder,
    // 读取数据块的方法
    async read(callback) {
      try {
        while (true) {
          const { done, value } = await reader.read();
          if (done) {
            callback(null, true); // 传递null表示结束
            break;
          }
          // 解码二进制数据为文本
          const chunk = decoder.decode(value, { stream: true });
          callback(chunk, false);
        }
      } catch (error) {
        console.error('Stream reading error:', error);
        throw error;
      }
    },
    // 取消流的方法
    cancel() {
      reader.cancel();
    }
  };
  
  return controller;
};
// #endif


不大支持复杂的类型请求 比如formData 等要做适配 

// import mimeMap from '@/utils/mimeMap.js'
const mimeMap = {
	"0.001": "application/x-001",
	"0.323": "text/h323",
	"0.907": "drawing/907",
	".acp": "audio/x-mei-aac",
	".aif": "audio/aiff",
	".aiff": "audio/aiff",
	".asa": "text/asa",
	".asp": "text/asp",
	".au": "audio/basic",
	".awf": "application/vnd.adobe.workflow",
	".bmp": "application/x-bmp",
	".c4t": "application/x-c4t",
	".cal": "application/x-cals",
	".cdf": "application/x-netcdf",
	".cel": "application/x-cel",
	".cg4": "application/x-g4",
	".cit": "application/x-cit",
	".cml": "text/xml",
	".cmx": "application/x-cmx",
	".crl": "application/pkix-crl",
	".csi": "application/x-csi",
	".cut": "application/x-cut",
	".dbm": "application/x-dbm",
	".dcd": "text/xml",
	".der": "application/x-x509-ca-cert",
	".dib": "application/x-dib",
	".doc": "application/msword",
	".drw": "application/x-drw",
	".dwf": "Model/vnd.dwf",
	".dwg": "application/x-dwg",
	".dxf": "application/x-dxf",
	".emf": "application/x-emf",
	".ent": "text/xml",
	".eps": "application/x-ps",
	".etd": "application/x-ebx",
	".fax": "image/fax",
	".fif": "application/fractals",
	".frm": "application/x-frm",
	".gbr": "application/x-gbr",
	".gif": "image/gif",
	".gp4": "application/x-gp4",
	".hmr": "application/x-hmr",
	".hpl": "application/x-hpl",
	".hrf": "application/x-hrf",
	".htc": "text/x-component",
	".html": "text/html",
	".htx": "text/html",
	".ico": "image/x-icon",
	".iff": "application/x-iff",
	".igs": "application/x-igs",
	".img": "application/x-img",
	".isp": "application/x-internet-signup",
	".java": "java/*",
	".jpe": "image/jpeg",
	".jpeg": "image/jpeg",
	".jpg": "application/x-jpg",
	".jsp": "text/html",
	".lar": "application/x-laplayer-reg",
	".lavs": "audio/x-liquid-secure",
	".lmsff": "audio/x-la-lms",
	".ltr": "application/x-ltr",
	".m2v": "video/x-mpeg",
	".m4e": "video/mpeg4",
	".man": "application/x-troff-man",
	".mdb": "application/msaccess",
	".mfp": "application/x-shockwave-flash",
	".mhtml": "message/rfc822",
	".mid": "audio/mid",
	".mil": "application/x-mil",
	".mnd": "audio/x-musicnet-download",
	".mocha": "application/x-javascript",
	".mp1": "audio/mp1",
	".mp2v": "video/mpeg",
	".mp4": "video/mpeg4",
	".mpd": "application/vnd.ms-project",
	".mpeg": "video/mpg",
	".mpga": "audio/rn-mpeg",
	".mps": "video/x-mpeg",
	".mpv": "video/mpg",
	".mpw": "application/vnd.ms-project",
	".mtx": "text/xml",
	".net": "image/pnetvue",
	".nws": "message/rfc822",
	".out": "application/x-out",
	".p12": "application/x-pkcs12",
	".p7c": "application/pkcs7-mime",
	".p7r": "application/x-pkcs7-certreqresp",
	".pc5": "application/x-pc5",
	".pcl": "application/x-pcl",
	".pdf": "application/pdf",
	".pdx": "application/vnd.adobe.pdx",
	".pgl": "application/x-pgl",
	".pko": "application/vnd.ms-pki.pko",
	".plg": "text/html",
	".plt": "application/x-plt",
	".png": "application/x-png",
	".ppa": "application/vnd.ms-powerpoint",
	".pps": "application/vnd.ms-powerpoint",
	".ppt": "application/x-ppt",
	".prf": "application/pics-rules",
	".prt": "application/x-prt",
	".ps": "application/postscript",
	".pwz": "application/vnd.ms-powerpoint",
	".ra": "audio/vnd.rn-realaudio",
	".ras": "application/x-ras",
	".rdf": "text/xml",
	".red": "application/x-red",
	".rjs": "application/vnd.rn-realsystem-rjs",
	".rlc": "application/x-rlc",
	".rm": "application/vnd.rn-realmedia",
	".rmi": "audio/mid",
	".rmm": "audio/x-pn-realaudio",
	".rms": "application/vnd.rn-realmedia-secure",
	".rmx": "application/vnd.rn-realsystem-rmx",
	".rp": "image/vnd.rn-realpix",
	".rsml": "application/vnd.rn-rsml",
	".rtf": "application/msword",
	".rv": "video/vnd.rn-realvideo",
	".sat": "application/x-sat",
	".sdw": "application/x-sdw",
	".slb": "application/x-slb",
	".slk": "drawing/x-slk",
	".smil": "application/smil",
	".snd": "audio/basic",
	".sor": "text/plain",
	".spl": "application/futuresplash",
	".ssm": "application/streamingmedia",
	".stl": "application/vnd.ms-pki.stl",
	".sty": "application/x-sty",
	".swf": "application/x-shockwave-flash",
	".tg4": "application/x-tg4",
	".tif": "image/tiff",
	".tiff": "image/tiff",
	".top": "drawing/x-top",
	".tsd": "text/xml",
	".uin": "application/x-icq",
	".vcf": "text/x-vcard",
	".vdx": "application/vnd.visio",
	".vpg": "application/x-vpeg005",
	".vsd": "application/x-vsd",
	".vst": "application/vnd.visio",
	".vsw": "application/vnd.visio",
	".vtx": "application/vnd.visio",
	".wav": "audio/wav",
	".wb1": "application/x-wb1",
	".wb3": "application/x-wb3",
	".wiz": "application/msword",
	".wk4": "application/x-wk4",
	".wks": "application/x-wks",
	".wma": "audio/x-ms-wma",
	".wmf": "application/x-wmf",
	".wmv": "video/x-ms-wmv",
	".wmz": "application/x-ms-wmz",
	".wpd": "application/x-wpd",
	".wpl": "application/vnd.ms-wpl",
	".wr1": "application/x-wr1",
	".wrk": "application/x-wrk",
	".ws2": "application/x-ws",
	".wsdl": "text/xml",
	".xdp": "application/vnd.adobe.xdp",
	".xfd": "application/vnd.adobe.xfd",
	".xhtml": "text/html",
	".xls": "application/x-xls",
	".xml": "text/xml",
	".xq": "text/xml",
	".xquery": "text/xml",
	".xsl": "text/xml",
	".xwd": "application/x-xwd",
	".sis": "application/vnd.symbian.install",
	".x_t": "application/x-x_t",
	".apk": "application/vnd.android.package-archive",
	"0.301": "application/x-301",
	"0.906": "application/x-906",
	".a11": "application/x-a11",
	".ai": "application/postscript",
	".aifc": "audio/aiff",
	".anv": "application/x-anv",
	".asf": "video/x-ms-asf",
	".asx": "video/x-ms-asf",
	".avi": "video/avi",
	".biz": "text/xml",
	".bot": "application/x-bot",
	".c90": "application/x-c90",
	".cat": "application/vnd.ms-pki.seccat",
	".cdr": "application/x-cdr",
	".cer": "application/x-x509-ca-cert",
	".cgm": "application/x-cgm",
	".class": "java/*",
	".cmp": "application/x-cmp",
	".cot": "application/x-cot",
	".crt": "application/x-x509-ca-cert",
	".css": "text/css",
	".dbf": "application/x-dbf",
	".dbx": "application/x-dbx",
	".dcx": "application/x-dcx",
	".dgn": "application/x-dgn",
	".dll": "application/x-msdownload",
	".dot": "application/msword",
	".dtd": "text/xml",
	".dwf": "application/x-dwf",
	".dxb": "application/x-dxb",
	".edn": "application/vnd.adobe.edn",
	".eml": "message/rfc822",
	".epi": "application/x-epi",
	".eps": "application/postscript",
	".exe": "application/x-msdownload",
	".fdf": "application/vnd.fdf",
	".fo": "text/xml",
	".g4": "application/x-g4",
	".tif": "image/tiff",
	".gl2": "application/x-gl2",
	".hgl": "application/x-hgl",
	".hpg": "application/x-hpgl",
	".hqx": "application/mac-binhex40",
	".hta": "application/hta",
	".htm": "text/html",
	".htt": "text/webviewhtml",
	".icb": "application/x-icb",
	".ico": "application/x-ico",
	".ig4": "application/x-g4",
	".iii": "application/x-iphone",
	".ins": "application/x-internet-signup",
	".IVF": "video/x-ivf",
	".jfif": "image/jpeg",
	".jpe": "application/x-jpe",
	".jpg": "image/jpeg",
	".js": "application/x-javascript",
	".la1": "audio/x-liquid-file",
	".latex": "application/x-latex",
	".lbm": "application/x-lbm",
	".ls": "application/x-javascript",
	".m1v": "video/x-mpeg",
	".m3u": "audio/mpegurl",
	".mac": "application/x-mac",
	".math": "text/xml",
	".mdb": "application/x-mdb",
	".mht": "message/rfc822",
	".mi": "application/x-mi",
	".midi": "audio/mid",
	".mml": "text/xml",
	".mns": "audio/x-musicnet-stream",
	".movie": "video/x-sgi-movie",
	".mp2": "audio/mp2",
	".mp3": "audio/mp3",
	".mpa": "video/x-mpg",
	".mpe": "video/x-mpeg",
	".mpg": "video/mpg",
	".mpp": "application/vnd.ms-project",
	".mpt": "application/vnd.ms-project",
	".mpv2": "video/mpeg",
	".mpx": "application/vnd.ms-project",
	".mxp": "application/x-mmxp",
	".nrf": "application/x-nrf",
	".odc": "text/x-ms-odc",
	".p10": "application/pkcs10",
	".p7b": "application/x-pkcs7-certificates",
	".p7m": "application/pkcs7-mime",
	".p7s": "application/pkcs7-signature",
	".pci": "application/x-pci",
	".pcx": "application/x-pcx",
	".pdf": "application/pdf",
	".pfx": "application/x-pkcs12",
	".pic": "application/x-pic",
	".pl": "application/x-perl",
	".pls": "audio/scpls",
	".png": "image/png",
	".pot": "application/vnd.ms-powerpoint",
	".ppm": "application/x-ppm",
	".ppt": "application/vnd.ms-powerpoint",
	".pr": "application/x-pr",
	".prn": "application/x-prn",
	".ps": "application/x-ps",
	".ptn": "application/x-ptn",
	".r3t": "text/vnd.rn-realtext3d",
	".ram": "audio/x-pn-realaudio",
	".rat": "application/rat-file",
	".rec": "application/vnd.rn-recording",
	".rgb": "application/x-rgb",
	".rjt": "application/vnd.rn-realsystem-rjt",
	".rle": "application/x-rle",
	".rmf": "application/vnd.adobe.rmf",
	".rmj": "application/vnd.rn-realsystem-rmj",
	".rmp": "application/vnd.rn-rn_music_package",
	".rmvb": "application/vnd.rn-realmedia-vbr",
	".rnx": "application/vnd.rn-realplayer",
	".rpm": "audio/x-pn-realaudio-plugin",
	".rt": "text/vnd.rn-realtext",
	".rtf": "application/x-rtf",
	".sam": "application/x-sam",
	".sdp": "application/sdp",
	".sit": "application/x-stuffit",
	".sld": "application/x-sld",
	".smi": "application/smil",
	".smk": "application/x-smk",
	".sol": "text/plain",
	".spc": "application/x-pkcs7-certificates",
	".spp": "text/xml",
	".sst": "application/vnd.ms-pki.certstore",
	".stm": "text/html",
	".svg": "text/xml",
	".tdf": "application/x-tdf",
	".tga": "application/x-tga",
	".tif": "application/x-tif",
	".tld": "text/xml",
	".torrent": "application/x-bittorrent",
	".txt": "text/plain",
	".uls": "text/iuls",
	".vda": "application/x-vda",
	".vml": "text/xml",
	".vsd": "application/vnd.visio",
	".vss": "application/vnd.visio",
	".vst": "application/x-vst",
	".vsx": "application/vnd.visio",
	".vxml": "text/xml",
	".wax": "audio/x-ms-wax",
	".wb2": "application/x-wb2",
	".wbmp": "image/vnd.wap.wbmp",
	".wk3": "application/x-wk3",
	".wkq": "application/x-wkq",
	".wm": "video/x-ms-wm",
	".wmd": "application/x-ms-wmd",
	".wml": "text/vnd.wap.wml",
	".wmx": "video/x-ms-wmx",
	".wp6": "application/x-wp6",
	".wpg": "application/x-wpg",
	".wq1": "application/x-wq1",
	".wri": "application/x-wri",
	".ws": "application/x-ws",
	".wsc": "text/scriptlet",
	".wvx": "video/x-ms-wvx",
	".xdr": "text/xml",
	".xfdf": "application/vnd.adobe.xfdf",
	".xls": "application/vnd.ms-excel",
	".xlw": "application/x-xlw",
	".xpl": "audio/scpls",
	".xql": "text/xml",
	".xsd": "text/xml",
	".xslt": "text/xml",
	".x_b": "application/x-x_b",
	".sisx": "application/vnd.symbian.install",
	".ipa": "application/vnd.iphone",
	".xap": "application/x-silverlight-app",
	".zip": "application/x-zip-compressed",
}

function FormData() {

	let fileManager = uni.getFileSystemManager();
	let data = {};
	let files = [];

	this.append = (name, value) => {
		data[name] = value;
		return true;
	}

	this.appendFile = (name, path) => {
		let buffer = fileManager.readFileSync(path);
		if (Object.prototype.toString.call(buffer).indexOf("ArrayBuffer") < 0) {
			return false;
		}
		files.push({
			name: name,
			buffer: buffer,
			fileName: getFileNameFromPath(path)
		});
		return true;
	}


	this.batchAppendFile = (fileArr, keyName = "name", pathName = "path") => {
		// [{name: "file", path: filePath }]
		for (let i = 0; i < fileArr.length; i++) {
			let name = fileArr[i][keyName],
				path = fileArr[i][pathName];
			let buffer = fileManager.readFileSync(path);
			if (Object.prototype.toString.call(buffer).indexOf("ArrayBuffer") < 0) {
				return false;
			}
			files.push({
				name: name,
				buffer: buffer,
				fileName: getFileNameFromPath(path)
			});
		}
		return true;
	}

	this.getData = () => convert(data, files)
}

function getFileNameFromPath(path) {
	let idx = path.lastIndexOf("/");
	return path.substr(idx + 1);
}

function convert(data, files) {
	let boundaryKey = 'wxmpFormBoundary' + randString(); // 数据分割符,一般是随机的字符串
	let boundary = '--' + boundaryKey;
	let endBoundary = boundary + '--';

	let postArray = [];
	//拼接参数
	if (data && Object.prototype.toString.call(data) == "[object Object]") {
		for (let key in data) {
			postArray = postArray.concat(formDataArray(boundary, key, data[key]));
		}
	}
	//拼接文件
	if (files && Object.prototype.toString.call(files) == "[object Array]") {
		for (let i in files) {
			let file = files[i];
			postArray = postArray.concat(formDataArray(boundary, file.name, file.buffer, file.fileName));
		}
	}
	//结尾
	let endBoundaryArray = [];
	for (var i = 0; i < endBoundary.length; i++) { // 最后取出结束boundary的charCode
		endBoundaryArray.push(...endBoundary.utf8CodeAt(i));
	}
	postArray = postArray.concat(endBoundaryArray);
	return {
		contentType: 'multipart/form-data; boundary=' + boundaryKey,
		buffer: new Uint8Array(postArray).buffer
	}
}

function randString() {
	let res = "";
	for (let i = 0; i < 17; i++) {
		let n = parseInt(Math.random() * 62);
		if (n <= 9) {
			res += n;
		} else if (n <= 35) {
			res += String.fromCharCode(n + 55);
		} else {
			res += String.fromCharCode(n + 61);
		}
	}
	return res;
}

function formDataArray(boundary, name, value, fileName) {
	let dataString = '';
	let isFile = !!fileName;

	dataString += boundary + '\r\n';
	dataString += 'Content-Disposition: form-data; name="' + name + '"';
	if (isFile) {
		dataString += '; filename="' + fileName + '"' + '\r\n';
		dataString += 'Content-Type: ' + getFileMime(fileName) + '\r\n\r\n';
	} else {
		dataString += '\r\n\r\n';
		dataString += value;
	}

	var dataArray = [];
	for (var i = 0; i < dataString.length; i++) { // 取出文本的charCode(10进制)
		dataArray.push(...dataString.utf8CodeAt(i));
	}

	if (isFile) {
		let fileArray = new Uint8Array(value);
		dataArray = dataArray.concat(Array.prototype.slice.call(fileArray));
	}
	dataArray.push(..."\r".utf8CodeAt());
	dataArray.push(..."\n".utf8CodeAt());

	return dataArray;
}

function getFileMime(fileName) {
	let idx = fileName.lastIndexOf(".");
	let mime = mimeMap[fileName.substr(idx)];
	return mime ? mime : "application/octet-stream"
}

String.prototype.utf8CodeAt = function(i) {
	var str = this;
	var out = [],
		p = 0;
	var c = str.charCodeAt(i);
	if (c < 128) {
		out[p++] = c;
	} else if (c < 2048) {
		out[p++] = (c >> 6) | 192;
		out[p++] = (c & 63) | 128;
	} else if (
		((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
		((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
		// Surrogate Pair
		c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
		out[p++] = (c >> 18) | 240;
		out[p++] = ((c >> 12) & 63) | 128;
		out[p++] = ((c >> 6) & 63) | 128;
		out[p++] = (c & 63) | 128;
	} else {
		out[p++] = (c >> 12) | 224;
		out[p++] = ((c >> 6) & 63) | 128;
		out[p++] = (c & 63) | 128;
	}
	return out;
};

export default FormData;

当移动端要用到的时候拿过来 然后new FormData()即可

当然h5 是可以使用webApi的直接new 就行


3.2、微信小程序域名白名单配置指南

1. 登录微信公众平台

首先,访问微信公众平台官网 https://mp.weixin.qq.com,使用小程序管理员账号登录

2. 进入小程序后台

登录后,选择您要配置的小程序项目。

3. 配置服务器域名

  • 在左侧菜单栏中,点击【开发】→【开发管理】→【开发设置】
  • 向下滚动找到【服务器域名】部分

4. 添加域名白名单

在服务器域名部分,您会看到以下几种类型的域名配置:

  • request合法域名:用于wx.request等网络请求接口
  • socket合法域名:用于wx.connectSocket等WebSocket接口
  • uploadFile合法域名:用于wx.uploadFile上传文件接口
  • downloadFile合法域名:用于wx.downloadFile下载文件接口

根据您的需求,在相应的输入框中添加您的API域名。

5. 域名要求

添加域名时需要注意以下几点:

  • 域名必须经过ICP备案
  • 域名必须是HTTPS(SSL证书有效)
  • 不支持IP地址(包括本地IP和公网IP)
  • 不支持localhost
  • 域名不能使用端口号(如www.example.com:8080)
  • 每个类型最多可配置20个域名

6. 保存配置

添加完域名后,点击【保存】按钮。

7. 验证域名所有权(如需要)

首次添加域名时,可能需要验证域名所有权:

  • 下载校验文件

2. 将校验文件上传到域名根目录

  • 确保可以通过 https://您的域名/.well-known/wechat-verification-file.txt 访问该文件
  • 点击【确认】完成验证

8. 开发环境设置

在开发阶段,可以临时关闭域名校验:

  • 在开发工具中,点击右上角【详情】
  • 勾选【不校验合法域名...】选项

注意:此设置仅在开发环境有效,上线版本必须配置合法域名。

9. 常见问题

  • 配置后不生效:域名配置有一定的生效延迟,一般在10分钟内
  • 测试号限制:测试号最多只能配置5个域名
  • 子域名问题:需要分别添加子域名,如添加了api.example.com不代表自动支持example.com

10. 特殊情况

如果您使用的是云开发或第三方平台,可能有特殊的域名配置要求,请参考相关文档。


三、权限处理


要注意权限处理 比如
用户授权
位置、相机、相册等需要用户授权 使用uni.authorize()或相关API获取权限

正常来说相机 和相册的拉起并没有那么复杂并且 有https的情况下正常使用uni.choose的api即可
但是相对于位置来说比较麻烦点

我这里举例一下位置授权配置  

1、位置权限

manifest.json 中 

"h5": {
    "template": "index.html",
    "router": {
      "mode": "hash",
      "base": "/h5/"
    },
    "publicPath": "/h5/",
    "sdkConfigs": {
      "maps": {
        "tencent": {
          "key": "J7DBZ-QQCKX-R2L4Z-T5YVE-QMS4J-NLBVP"
        },
        "qqmap": {
          "key": "J7DBZ-QQCKX-R2L4Z-T5YVE-QMS4J-NLBVP"
        },
        "geolocation": {
          "type": "tencent",
          "coordinate": "gcj02"
        }
      }
    },
    "domain": "https://anneng.xaxcsz.com",
    "devServer": {
      "https": false,
      "port": 8080,
      "disableHostCheck": true
    }
  },

页面中

const getPosition = () => {
  return new Promise((resolve, reject) => {
    uni.getLocation({
      type: 'gcj02', // 使用国测局坐标系(火星坐标系)
      success: async (res) => {
        try {
          // 获取位置成功
          const locationInfo = {
            latitude: res.latitude,
            longitude: res.longitude,
            accuracy: res.accuracy || 1000
          };

          resolve(locationInfo);
        } catch (error) {
          reject(new Error(`处理位置信息失败: ${error.message || '未知错误'}`));
        }
      },
      fail: (err) => {
        uni.showToast({
          title: '获取位置失败',
          icon: 'none',
          duration: 2000
        });
        reject(new Error(`获取位置失败: ${err.errMsg || '未知错误'}`));
      }
    });
  });
};

// 修改权限检查和位置获取逻辑
const getPersonalLocation = async () => {
  // 直接尝试获取位置,不使用 uni.authorize
  const position = await getPosition();
  console.log('position1', position);
  personalLocation.value = {
    ...position,
    latitude: position.latitude,
    longitude: position.longitude,
    address: position.address || position.province + position.city + (position.district || '')
  };
  position.value = personalLocation.value.address;
....
};

2、麦克风权限 相关拾音的比较麻烦一点

 const initAuthorize = (callback, isAction = true) => {
	// 向用户发起授权请求
	// #ifdef MP-WEIXIN
	uni.authorize({
		scope: 'scope.record', // 获取录音功能,也就是麦克风权限
		success: (res) => {
			// 用户授权使用麦克风权限调用语音搜索事件函数
			isAuth.value = true

			callback && callback()
		},
		// 用户没有授权使用麦克风权限执行以下代码
		fail(res) {
			isAuth.value = false
			// 显示模态弹窗提示用户没有开启麦克风权限
			uni.showModal({
				content: '检测到您未开启麦克风权限,请保持麦克风权限为开启状态',
				confirmText: '去开启',
				confirmColor: '#01B3FD',
				cancelText: '取消',
				showCancel: true,
				success: (res) => {
					if (res.confirm) {
						// 调起客户端小程序设置界面,返回用户设置的操作结果
						uni.openSetting({
							success: (res) => {
								if (res.authSetting['scope.record'] == true) {
									uni.showToast({
										title: '授权成功',
										icon: 'none'
									});
									isAuth.value = true;
									isAuth.value && callback &&
										callback()
								} else if (res.authSetting['scope.record'] ==
									false) {
									uni.showToast({
										title: '授权失败',
										icon: 'none'
									});
									isAuth.value = false
								}
							}
						})
					}
				}
			})
		},
		complete() {
			console.log("authorize['scope.record'] complete")
		}
	})
	
}

4、手机号 微信授权登录

	<button class="wx-login-btn" 
      open-type="getPhoneNumber" 
      @getphonenumber="getPhoneNumber" 
      plain="true">
		手机号一键登录
	</button>
// 微信获取手机号码登录
	// 3、调用getPhoneNumber
	const getPhoneNumber = async (e) => {
		// console.log(agree.value)
		if (agree.value instanceof Array && agree.value.length === 0) {
			uni.showToast({
				title: '请先勾选用户协议',
				icon: 'none'
			})
			return false;
		}

		// console.log('获取手机号码======code:', e, e.detail.code)
		let phoneCode = e.detail.code;
	
		if (agree.value.length === 0) {
			uni.showToast({
				title: '请先勾选用户协议',
				icon: 'none'
			})
			return false;
		}

		if (phoneCode) {
			console.log("有")
			uni.login({
				provider: 'weixin', //使用微信登录
				success: async function(res) {
					console.log("获取微信id===>", res.code);
					let wechatCode = res.code
					let loginRes = await login({
						loginType: 2,
						phoneCode: phoneCode,
						wechatCode: wechatCode
					})

				  //....
				}
			});
		} else {
			console.log("没有")
			loginPop.value.close()
		}
		// 注意:如果获取不到code 确认下开发者工具调试基础库版本是否为 2.21.2 及以上
	}


四、打包

h5


更具配置项npm run build:h5 就好了(推荐)没有什么流程 简单方便
或者直接在 hbuilder 点击发行  pc或者h5 即可 其他的按照流程来 

微信小程序

这个相对简单,申请好appId 准备好域名 接口白名单 之类的东西  写好代码 
在微信开发者工具中有发行和版本管理

app(目前我只试过安卓apk)

首先生成证书
安装JRE环境 Java Downloads | Oracle  选择对应操作系统


选择自己要的版本 然后注册或者直接登录

在命令行中执行如下步骤
配置环境变量

d:  
set PATH=%PATH%;"C:\Program Files\Java\jre1.8.0_201\bin"

 生成签名证书

keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore
Enter keystore password:  //输入证书文件密码,输入完成回车  
Re-enter new password:   //再次输入证书文件密码,输入完成回车  
What is your first and last name?  
  [Unknown]:  //输入名字和姓氏,输入完成回车  
What is the name of your organizational unit?  
  [Unknown]:  //输入组织单位名称,输入完成回车  
What is the name of your organization?  
  [Unknown]:  //输入组织名称,输入完成回车  
What is the name of your City or Locality?  
  [Unknown]:  //输入城市或区域名称,输入完成回车  
What is the name of your State or Province?  
  [Unknown]:  //输入省/市/自治区名称,输入完成回车  
What is the two-letter country code for this unit?  
  [Unknown]:  //输入国家/地区代号(两个字母),中国为CN,输入完成回车  
Is CN=XX, OU=XX, O=XX, L=XX, ST=XX, C=XX correct?  
  [no]:  //确认上面输入的内容是否正确,输入y,回车  

Enter key password for <testalias>  
        (RETURN if same as keystore password):  //确认证书密码与证书文件密码一样(HBuilder|HBuilderX要求这两个密码一致),直接回车就可以

查看证书信息 

keytool -list -v -keystore test.keystore  
Enter keystore password: //输入密码,回车

 详情请参考 Android平台签名证书(.keystore)生成指南 - DCloud问答

第二步  点击发行 选择云打包 然后 填入证书信息 就ok了 
Android 自定义渠道包 Android 自定义渠道包 | uni-app官网

我也在慢慢学习中 后续更新.... 


六、补充篇原生微信小程序
 

原生微信小程序由四种文件组成:

  • .js:脚本文件
  • .wxml:模板文件(类似HTML)
  • .wxss:样式文件(类似CSS)
  • .json:配置文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值