HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

uni-app 上架 iOS 应用全流程 从云打包到开心上架(Appuploader)免 Mac 上传发布指南

iOS

'''随着 uni-app 成为跨平台应用开发的主流框架之一,越来越多的开发者开始将 Web 技术(Vue + JS)扩展至原生 App 开发领域。

uni-app 通过 HBuilderX 云打包服务 实现“一套代码,多端运行”,极大降低了 iOS 与 Android 应用的开发门槛。

然而,许多开发者在面对 iOS 应用上架时常遇到瓶颈:

没有 Mac、无法使用 Xcode 上传、App Store Connect 操作繁琐……

开心上架(Appuploader)为 uni-app 团队提供了方便的解决方案。
它支持在 Windows / Linux / macOS 环境下完成证书管理、IPA 上传、截图与多语言信息同步,让 uni-app 项目实现 全平台免 Mac 上架流程


一、uni-app 打包 iOS 应用的基本流程

uni-app 项目的构建和打包通常通过 HBuilderX 或 CLI 工具完成。

步骤 1️⃣:打包设置

在 HBuilderX 菜单栏中选择:

发行 → 原生 App-云打包 → iOS

配置内容包括:

  • 应用名称、Bundle ID(必须与 Apple Developer 一致);
  • 应用版本号与描述信息;
  • App 图标与启动图。

步骤 2️⃣:选择证书类型

HBuilderX 云打包提供两种证书方式:

选项 说明
上传自有证书 使用个人或企业 Apple 账号生成的证书
使用 DCloud 公共证书 测试用途,不能正式上架 App Store

建议使用自有证书,以保证应用可正式上架。

步骤 3️⃣:打包完成后下载 IPA

云打包完成后,系统会生成 .ipa 文件,可直接用于上传。

示例:

unpackage/release/ios/myapp.ipa

二、准备上架前的必要条件

在正式上传前,请确保具备以下条件:

条件 说明
Apple 开发者账号 注册 Apple Developer,年费 99 美元
App 专用密码 上传时使用,保护主账号安全
隐私政策链接 必填项,可托管在网站或 Github Pages
应用截图与描述 必须提供 6.5" 与 5.5" 设备截图

三、免Mac上架

开心上架(Appuploader) 是一款专为 iOS 上架设计的跨平台工具,支持图形界面与命令行两种操作方式,可在 Windows / Linux / macOS 上运行。

核心功能:

功能 说明
免 Mac 上传 IPA 不依赖 Xcode 与 Transporter
命令行支持 适配 CI/CD 自动化环境
证书管理 一键创建 iOS 开发/发布证书
多语言截图上传 支持 App Store Connect 多语言数据
通道切换 同时支持苹果新旧上传协议

特别适合使用 uni-app 的跨平台开发团队。


四、使用 Appuploader 上传 uni-app 生成的 iOS 应用

方式一:图形界面上传(适合初次上架)

打开 开心上架;
登录 Apple 开发者账号(使用 App 专用密码);
点击 “上传 IPA”;
选择 HBuilder 云打包生成的 .ipa 文件;
上传完成后在 App Store Connect 查看应用状态。
ipa上架


方式二:命令行上传(推荐开发者使用)

命令行版本支持全自动上传,可与构建流程整合。

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./unpackage/release/ios/uniapp.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f IPA 文件路径

执行命令后,Appuploader 会:

  • 验证包体信息;
  • 通过加密通道上传至 App Store Connect;
  • 输出上传日志与结果。

整个过程无需 Mac 环境。


五、App Store Connect 上架配置步骤

IPA 上传成功后,在 App Store Connect 中完成以下步骤:

填写应用基本信息(名称、描述、关键词);
上传截图与隐私政策;
配置应用分类与年龄评级;
添加版本号与构建文件;
点击 “提交审核” 等待审批。
app store connect

审核时间一般为 1~3 个工作日
含推送、支付功能的应用审核时间可能更长。


六、跨平台 uni-app 团队的发布实践

以一个典型的 uni-app 团队为例:

阶段 工具 功能
代码开发 HBuilderX / VSCode 使用 Vue 语法开发应用
云打包 HBuilder 云端 生成 iOS .ipa 文件
上传上架 开心上架(Appuploader) 免 Mac 上传 IPA 至 App Store
审核管理 App Store Connect 提交资料、监控审核状态

实际场景:
开发者在 Windows 环境中开发 uni-app 项目,
HBuilder 云打包生成 IPA,
然后使用以下命令上传:

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./ios_release/uniapp.ipa

整个上架过程无需 Mac 或 Xcode,
团队协作效率提升 60% 以上。


七、常见问题与解决方案

问题 原因 解决方案
上传失败(401) 密码错误 使用 App 专用密码而非 Apple ID 密码
“Invalid Bundle ID” Bundle ID 不匹配 与 Apple Developer 注册的 ID 保持一致
“Missing Provisioning Profile” 描述文件不正确 重新生成发布证书与配置文件
审核拒绝 隐私政策或截图问题 更新资料并重新提交
Transporter 报错但 CLI 成功 网络不稳定 使用 Appuploader 命令行通道 2 上传

八、进阶:自动化上架流程(CI/CD)

可将 Appuploader 命令集成进 Jenkins 或 GitLab CI 流水线:

#!/bin/bash  
# 自动上传 uni-app 打包生成的 iOS 包  
ipa_path="./unpackage/release/ios/uniapp.ipa"  
appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f "$ipa_path"

执行后 Jenkins 将自动:

  • 构建 uni-app;
  • 上传至 App Store;
  • 生成上架日志;
  • 推送通知至团队频道(如企业微信)。

九、uni-app 上架的优势总结

环节 工具 优势
代码开发 uni-app 跨平台、一套代码多端发布
打包生成 HBuilder 云打包 无需 Xcode,本地轻量化
上传发布 开心上架(Appuploader) 支持全平台上传、自动化命令行
团队协作 CI/CD + CLI 提高上架频率与可追溯性

对前端团队而言,uni-app + Appuploader 是最轻量高效的 iOS 发布方案。


uni-app 让前端开发者能够轻松构建移动应用,而 开心上架(Appuploader)则让他们能够不依赖 Mac,实现 “跨平台上架”。

从 HBuilder 打包到 App Store 发布,整个过程简洁、高效、安全,适合个人开发者与团队工程使用。

让开发专注于创造,让上架交给自动化。'''

继续阅读 »

'''随着 uni-app 成为跨平台应用开发的主流框架之一,越来越多的开发者开始将 Web 技术(Vue + JS)扩展至原生 App 开发领域。

uni-app 通过 HBuilderX 云打包服务 实现“一套代码,多端运行”,极大降低了 iOS 与 Android 应用的开发门槛。

然而,许多开发者在面对 iOS 应用上架时常遇到瓶颈:

没有 Mac、无法使用 Xcode 上传、App Store Connect 操作繁琐……

开心上架(Appuploader)为 uni-app 团队提供了方便的解决方案。
它支持在 Windows / Linux / macOS 环境下完成证书管理、IPA 上传、截图与多语言信息同步,让 uni-app 项目实现 全平台免 Mac 上架流程


一、uni-app 打包 iOS 应用的基本流程

uni-app 项目的构建和打包通常通过 HBuilderX 或 CLI 工具完成。

步骤 1️⃣:打包设置

在 HBuilderX 菜单栏中选择:

发行 → 原生 App-云打包 → iOS

配置内容包括:

  • 应用名称、Bundle ID(必须与 Apple Developer 一致);
  • 应用版本号与描述信息;
  • App 图标与启动图。

步骤 2️⃣:选择证书类型

HBuilderX 云打包提供两种证书方式:

选项 说明
上传自有证书 使用个人或企业 Apple 账号生成的证书
使用 DCloud 公共证书 测试用途,不能正式上架 App Store

建议使用自有证书,以保证应用可正式上架。

步骤 3️⃣:打包完成后下载 IPA

云打包完成后,系统会生成 .ipa 文件,可直接用于上传。

示例:

unpackage/release/ios/myapp.ipa

二、准备上架前的必要条件

在正式上传前,请确保具备以下条件:

条件 说明
Apple 开发者账号 注册 Apple Developer,年费 99 美元
App 专用密码 上传时使用,保护主账号安全
隐私政策链接 必填项,可托管在网站或 Github Pages
应用截图与描述 必须提供 6.5" 与 5.5" 设备截图

三、免Mac上架

开心上架(Appuploader) 是一款专为 iOS 上架设计的跨平台工具,支持图形界面与命令行两种操作方式,可在 Windows / Linux / macOS 上运行。

核心功能:

功能 说明
免 Mac 上传 IPA 不依赖 Xcode 与 Transporter
命令行支持 适配 CI/CD 自动化环境
证书管理 一键创建 iOS 开发/发布证书
多语言截图上传 支持 App Store Connect 多语言数据
通道切换 同时支持苹果新旧上传协议

特别适合使用 uni-app 的跨平台开发团队。


四、使用 Appuploader 上传 uni-app 生成的 iOS 应用

方式一:图形界面上传(适合初次上架)

打开 开心上架;
登录 Apple 开发者账号(使用 App 专用密码);
点击 “上传 IPA”;
选择 HBuilder 云打包生成的 .ipa 文件;
上传完成后在 App Store Connect 查看应用状态。
ipa上架


方式二:命令行上传(推荐开发者使用)

命令行版本支持全自动上传,可与构建流程整合。

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./unpackage/release/ios/uniapp.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f IPA 文件路径

执行命令后,Appuploader 会:

  • 验证包体信息;
  • 通过加密通道上传至 App Store Connect;
  • 输出上传日志与结果。

整个过程无需 Mac 环境。


五、App Store Connect 上架配置步骤

IPA 上传成功后,在 App Store Connect 中完成以下步骤:

填写应用基本信息(名称、描述、关键词);
上传截图与隐私政策;
配置应用分类与年龄评级;
添加版本号与构建文件;
点击 “提交审核” 等待审批。
app store connect

审核时间一般为 1~3 个工作日
含推送、支付功能的应用审核时间可能更长。


六、跨平台 uni-app 团队的发布实践

以一个典型的 uni-app 团队为例:

阶段 工具 功能
代码开发 HBuilderX / VSCode 使用 Vue 语法开发应用
云打包 HBuilder 云端 生成 iOS .ipa 文件
上传上架 开心上架(Appuploader) 免 Mac 上传 IPA 至 App Store
审核管理 App Store Connect 提交资料、监控审核状态

实际场景:
开发者在 Windows 环境中开发 uni-app 项目,
HBuilder 云打包生成 IPA,
然后使用以下命令上传:

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./ios_release/uniapp.ipa

整个上架过程无需 Mac 或 Xcode,
团队协作效率提升 60% 以上。


七、常见问题与解决方案

问题 原因 解决方案
上传失败(401) 密码错误 使用 App 专用密码而非 Apple ID 密码
“Invalid Bundle ID” Bundle ID 不匹配 与 Apple Developer 注册的 ID 保持一致
“Missing Provisioning Profile” 描述文件不正确 重新生成发布证书与配置文件
审核拒绝 隐私政策或截图问题 更新资料并重新提交
Transporter 报错但 CLI 成功 网络不稳定 使用 Appuploader 命令行通道 2 上传

八、进阶:自动化上架流程(CI/CD)

可将 Appuploader 命令集成进 Jenkins 或 GitLab CI 流水线:

#!/bin/bash  
# 自动上传 uni-app 打包生成的 iOS 包  
ipa_path="./unpackage/release/ios/uniapp.ipa"  
appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f "$ipa_path"

执行后 Jenkins 将自动:

  • 构建 uni-app;
  • 上传至 App Store;
  • 生成上架日志;
  • 推送通知至团队频道(如企业微信)。

九、uni-app 上架的优势总结

环节 工具 优势
代码开发 uni-app 跨平台、一套代码多端发布
打包生成 HBuilder 云打包 无需 Xcode,本地轻量化
上传发布 开心上架(Appuploader) 支持全平台上传、自动化命令行
团队协作 CI/CD + CLI 提高上架频率与可追溯性

对前端团队而言,uni-app + Appuploader 是最轻量高效的 iOS 发布方案。


uni-app 让前端开发者能够轻松构建移动应用,而 开心上架(Appuploader)则让他们能够不依赖 Mac,实现 “跨平台上架”。

从 HBuilder 打包到 App Store 发布,整个过程简洁、高效、安全,适合个人开发者与团队工程使用。

让开发专注于创造,让上架交给自动化。'''

收起阅读 »

动态创建web-view加载本地html 页面通讯

web_view

我们日常使用web-view最常见的方法是新建一个页面,然后放一个web-view并配置上我们的html页面地址

这里有另外一种动态创建web-view的方法,更加的灵活

  let wvPath = '/hybrid/html/webview.html'  
  let wv= new plus.webview.create(  
    wvPath,  
    'map-view',  
    {  
      'uni-app': 'none',  
      top: systemInfo.statusBarHeight,  
      left: 0,  
      width: systemInfo.screenWidth,  
      height: systemInfo.screenHeight - systemInfo.statusBarHeight,  
      background: '#ffffff',  
      // 启用手势返回  
      // popGesture: 'close',  
    },  
    {  
       // 这里携带web-view的额外参数  
       key  
    }  
  )  
// 一定要记得再不需要的时候 关闭掉动态创建的we-view  
wv.close()

监听动态创建的web-view发送的消息

  plus.globalEvent.addEventListener('plusMessage', (e) => {    
          console.log("网页消息", e);    
  })

竟然用到了plusMessage方法,本来直接使用的message监听发现怎么都不生效
从 plus.globalEvent看出监听的是全局的plusMessage方法
很多人的使用习惯,既然我监听了消息,那么在我不需要的时候是需要把这个监听移除掉的,否则不停的监听影响APP性能,于是写了下面的移除方法:

plus.globalEvent.removeEventListener('plusMessage',messagEvent)
但是出乎意料,移除后整个APP的所有事件都失效!点击哪里都没有反应,切记这个方法是不可以移除的

第二种 消息传输方式
plus.globalEvent监听uni.postMessage推送的消息会出现重复推送等问题,建议改为Webview url拦截的方式获取html文件数据。
// html中跳转自定义url,会被拦截,不会进行跳转
window.location.href = 'push?params=loading'

// vue页面wv拦截url变更
wv.overrideUrlLoading({mode:'reject'}, e => {
var params = decodeURI(e.url.split('push?params=')[1])
})

url拦截更实时,准确率更高,不会重复接收消息,只有App支持,H5+文档参考:https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.overrideUrlLoading

参考文章:
https://www.html5plus.org/doc/zh_cn/webview.html
https://ask.dcloud.net.cn/article/35083

继续阅读 »

我们日常使用web-view最常见的方法是新建一个页面,然后放一个web-view并配置上我们的html页面地址

这里有另外一种动态创建web-view的方法,更加的灵活

  let wvPath = '/hybrid/html/webview.html'  
  let wv= new plus.webview.create(  
    wvPath,  
    'map-view',  
    {  
      'uni-app': 'none',  
      top: systemInfo.statusBarHeight,  
      left: 0,  
      width: systemInfo.screenWidth,  
      height: systemInfo.screenHeight - systemInfo.statusBarHeight,  
      background: '#ffffff',  
      // 启用手势返回  
      // popGesture: 'close',  
    },  
    {  
       // 这里携带web-view的额外参数  
       key  
    }  
  )  
// 一定要记得再不需要的时候 关闭掉动态创建的we-view  
wv.close()

监听动态创建的web-view发送的消息

  plus.globalEvent.addEventListener('plusMessage', (e) => {    
          console.log("网页消息", e);    
  })

竟然用到了plusMessage方法,本来直接使用的message监听发现怎么都不生效
从 plus.globalEvent看出监听的是全局的plusMessage方法
很多人的使用习惯,既然我监听了消息,那么在我不需要的时候是需要把这个监听移除掉的,否则不停的监听影响APP性能,于是写了下面的移除方法:

plus.globalEvent.removeEventListener('plusMessage',messagEvent)
但是出乎意料,移除后整个APP的所有事件都失效!点击哪里都没有反应,切记这个方法是不可以移除的

第二种 消息传输方式
plus.globalEvent监听uni.postMessage推送的消息会出现重复推送等问题,建议改为Webview url拦截的方式获取html文件数据。
// html中跳转自定义url,会被拦截,不会进行跳转
window.location.href = 'push?params=loading'

// vue页面wv拦截url变更
wv.overrideUrlLoading({mode:'reject'}, e => {
var params = decodeURI(e.url.split('push?params=')[1])
})

url拦截更实时,准确率更高,不会重复接收消息,只有App支持,H5+文档参考:https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.overrideUrlLoading

参考文章:
https://www.html5plus.org/doc/zh_cn/webview.html
https://ask.dcloud.net.cn/article/35083

收起阅读 »

uni-vue3--专为 UniApp + Vue3 + UnoCSS 打造的开箱即用模板

uni-vue3

专为 UniApp + Vue3 + UnoCSS 打造的 starter template

源码:https://github.com/vue-rookie/uni-vue3

🚀 特性

  • ✅ Vue3 + Composition API
  • ✅ UnoCSS 原子化CSS
  • ✅ TypeScript 支持
  • ✅ Vite 构建工具

📦 快速开始

小程序预览

产品

建议手机模式预览
uni-vue3框架--------------------------------代码:feature/main
uni-vue3模仿抖音---------------------------代码:feature/douyin
uni-vue3模仿小红书------------------------代码:feature/xiaohongshu

🚀 技术栈

  • 核心框架:Vue 3.4
  • 构建工具:Vite 5.0
  • 开发语言:TypeScript 5.0
  • 状态管理:Pinia 2.0
  • 样式方案:UnoCSS
  • 跨端框架:UniApp 3.0

自定义主题样式(超级自由简单的样式配置):

组件样式全部采用 tailwindcss 封装,您无需写任何 令人烦躁的 css 代码。

只需要修改对应 uno.config.ts 配置文件中的 theme 下的的色值号即可,其他无需任何更改

🪝 自定义 Hooks

项目中精心封装了丰富的自定义 Hooks,大幅提升开发效率和代码质量,所有 Hooks 支持 TypeScript 类型推导。

UI 交互类

useModal - 对话框管理

提供统一的弹窗交互解决方案,包括确认框、消息提示、加载状态等。

import { useModal } from "@/hooks"  

// 在组件中使用  
const {  
  showToast,  
  showSuccess,  
  showError,  
  showConfirm,  
  showLoading,  
  hideLoading,  
  showActionSheet,  
} = useModal()  

// 显示确认对话框  
await showConfirm({  
  title: "操作确认",  
  content: "确定要执行此操作吗?",  
})  

// 显示成功提示  
showSuccess("操作成功")  

// 显示加载状态  
const loading = showLoading()  
try {  
  // 执行异步操作  
} finally {  
  loading.hide() // 隐藏加载  
}  

// 显示底部操作菜单  
const selectedIndex = await showActionSheet({  
  itemList: ["选项一", "选项二", "选项三"],  
})

数据处理类

useStorage - 本地存储

增强版本地存储,支持过期时间、类型安全、自动序列化/反序列化、响应式存储。

import { useStorage } from "@/hooks"  

const { setStorage, getStorage, removeStorage, clearStorage, createReactiveStorage } = useStorage()  

// 存储数据,30天过期  
await setStorage("user-info", { id: 1, name: "Admin" }, 30 * 24 * 60 * 60 * 1000)  

// 读取数据  
const userInfo = await getStorage("user-info")  

// 创建响应式存储  
const count = createReactiveStorage("visit-count", 0)  
count.value++ // 自动同步到存储

useRequest - 网络请求

强大的请求管理 Hook,自动处理加载状态、错误处理、请求缓存等。

import { useRequest } from "@/hooks"  

const request = useRequest({  
  baseURL: "https://api.example.com",  
  autoHandleError: true, // 自动处理错误  
  autoLoading: true, // 自动显示加载状态  
})  

// GET 请求  
const { data } = await request.get("/users", { page: 1 })  

// POST 请求  
await request.post("/articles", { title, content })  

// 文件上传  
await request.upload("/upload", {  
  filePath: tempFilePath,  
  name: "file",  
  formData: { type: "avatar" },  
})

useInputLimit - 输入限制

提供各类输入限制函数,轻松实现各种格式检查和输入控制。

import { useInputDataLimit } from "@/hooks/useInputLimit"  

const {  
  limitNumber, // 仅限数字  
  limitLetter, // 仅限字母  
  limitNumberAndLetter, // 仅限数字和字母  
  limitToPositiveTwoDecimals, // 仅限两位小数的正数  
  limitNoChinese, // 不允许中文  
} = useInputDataLimit()  

// 在输入事件中使用  
const handleInput = (e) => {  
  const rawValue = e.detail.value  
  const formattedValue = limitToPositiveTwoDecimals(rawValue)  
  // 更新表单值  
}

设备能力类

useLocation - 位置服务

封装位置获取、地址解析、坐标转换等功能,支持高精度定位和后台定位。

import { useLocation } from "@/hooks"  

const { getLocation, startLocationUpdate, stopLocationUpdate, chooseLocation, openLocation } =  
  useLocation()  

// 获取当前位置  
const position = await getLocation({  
  type: "gcj02", // 坐标系类型  
  isHighAccuracy: true, // 高精度定位  
})  

// 打开地图选择位置  
const location = await chooseLocation()  

// 在地图上查看位置  
await openLocation({  
  latitude: 39.9087,  
  longitude: 116.3975,  
  name: "目的地",  
  address: "详细地址信息",  
  scale: 18,  
})

useCamera - 相机功能

相机相关功能封装,包括拍照、录像、选择相册等功能。

import { useCamera } from "@/hooks"  

const { takePhoto, chooseImage, chooseVideo, previewImage, compressImage } = useCamera()  

// 拍照或从相册选择  
const filePath = await takePhoto({  
  sourceType: ["camera", "album"],  
})  

// 选择多张图片  
const images = await chooseImage({  
  count: 9,  
  sizeType: ["original", "compressed"],  
})  

// 压缩图片  
const compressedPath = await compressImage(filePath, {  
  quality: 80, // 压缩质量  
})

useSystem - 系统信息

获取系统信息、设备信息、网络状态等功能。

import { useSystem } from "@/hooks"  

const { getSystemInfo, getNetworkType, onNetworkStatusChange, getDeviceInfo, vibrateShort } =  
  useSystem()  

// 获取系统信息  
const systemInfo = getSystemInfo()  
console.log(`运行平台: ${systemInfo.platform}, 系统: ${systemInfo.system}`)  

// 获取网络状态  
const networkType = await getNetworkType()  

// 监听网络变化  
onNetworkStatusChange((res) => {  
  console.log(`网络变更: ${res.networkType}, 是否连接: ${res.isConnected}`)  
})

其他实用 Hooks

useShare - 分享功能

统一的分享接口,支持小程序分享、系统分享、自定义分享等。

import { useShare } from "@/hooks"  

const { share, shareWithSystem, configMiniProgramShare } = useShare()  

// 配置页面分享参数  
configMiniProgramShare({  
  title: "分享标题",  
  path: "/pages/index/index",  
  imageUrl: "/static/share.png",  
  onShareSuccess: () => {  
    console.log("分享成功")  
  },  
})  

// 系统分享  
shareWithSystem({  
  title: "分享内容",  
  summary: "内容摘要",  
  href: "https://example.com",  
  imageUrl: "/static/share.png",  
})

useValidation - 表单验证

强大的表单验证系统,内置多种常用验证规则,支持自定义验证。

import { useValidation } from "@/hooks"  

const validation = useValidation()  

// 创建表单数据和规则  
const { formData, rules, validate, errors, resetValidation } = validation.createForm(  
  {  
    username: "",  
    password: "",  
    email: "",  
    phone: "",  
  },  
  {  
    username: [  
      { required: true, message: "用户名不能为空" },  
      { min: 3, max: 20, message: "长度在3到20个字符" },  
    ],  
    password: [  
      { required: true, message: "密码不能为空" },  
      { min: 6, message: "密码长度不能小于6位" },  
      { pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, message: "密码必须包含大小写字母和数字" },  
    ],  
    email: [{ type: "email", message: "请输入正确的邮箱格式" }],  
    phone: [{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" }],  
  },  
)  

// 提交表单  
const handleSubmit = async () => {  
  const valid = await validate()  
  if (valid) {  
    // 表单验证通过,提交数据  
    console.log("表单数据:", formData)  
  } else {  
    // 表单验证失败  
    console.log("验证错误:", errors.value)  
  }  
}

usePageScroll - 页面滚动

页面滚动相关功能,包括滚动到指定位置、监听滚动事件等。

import { usePageScroll } from "@/hooks"  

const { scrollTo, scrollToTop, scrollToSelector, onPageScroll, getScrollPosition } = usePageScroll()  

// 滚动到顶部  
scrollToTop()  

// 滚动到指定位置  
scrollTo(0, 200, true) // 带动画效果滚动到 200px 位置  

// 滚动到指定元素  
scrollToSelector(".news-item-5", {  
  offset: -20, // 偏移量  
  duration: 300, // 动画时长  
})  

// 监听页面滚动  
onPageScroll((scrollTop) => {  
  if (scrollTop > 100) {  
    // 显示返回顶部按钮  
  }  
})

📋 环境要求

  • Node.js >= 18.0.0
  • pnpm >= 7.0.0
  • 微信开发者工具(开发小程序时使用)

🛠️ 快速开始

# 安装依赖  
pnpm install  

# 开发环境运行  
pnpm dev:mp-weixin  

# 生产环境构建  
pnpm build:mp-weixin

📱 开发指南

微信小程序开发

  1. 开发环境配置

    • 运行 pnpm dev:mp-weixin 生成开发环境代码
    • 使用微信开发者工具导入 dist/dev/mp-weixin 目录
    • 开启"不校验合法域名"选项(开发环境)
  2. 生产环境发布

    • 执行 pnpm build:mp-weixin 生成生产环境代码
    • 使用微信开发者工具导入 dist/build/mp-weixin 目录
    • 点击"上传"按钮发布小程序

项目规范

  1. 组件开发规范

    • 全局组件统一放置在 uni-module 目录下
    • 遵循 UniApp 的 easycom 组件规范
    • 组件命名采用 PascalCase 命名法
  2. 类型定义规范

    • 业务类型定义统一放在对应页面的 type.ts 文件中
    • 公共类型定义放在 types 目录下
    • 类型命名采用 PascalCase 命名法
  3. Hooks 使用规范

    • 公共 hooks 统一放在 hooks 目录下
    • 业务相关 hooks 放在对应页面目录
    • hooks 命名采用 camelCase 命名法,以 use 开头

🚀 性能优化

构建优化

  1. 代码分割

    • 使用 manualChunks 实现代码分割
    • 第三方依赖独立打包,提高缓存效率
    • 路由组件按需加载
  2. 资源优化

    • 图片资源自动压缩
    • CSS 代码压缩和优化
    • 静态资源 CDN 加速
  3. 编译优化

    • 使用 lightningcss 进行 CSS 处理
    • 配置合理的 assetsInlineLimit
    • 优化 Sass 编译配置

运行时优化

  1. 渲染优化

    • 图片懒加载
    • 虚拟列表
    • 条件渲染优化
  2. 性能监控

    • 页面加载性能监控
    • 组件渲染性能分析
    • 内存使用监控

📦 项目结构

├── src  
│   ├── pages              # 页面文件  
│   ├── pages-sub          # 子页面  
│   ├── hooks              # 自定义hooks  
│   ├── static             # 静态资源  
│   ├── types              # 类型定义  
│   ├── utils              # 工具函数  
│   └── uni-module         # 全局组件  
├── vite.config.ts         # Vite 配置  
└── package.json           # 项目配置

源码

stars
forks
watchers
license

📄 开源协议

本项目采用 MIT 协议开源,详情请查看 LICENSE 文件。

致谢

感谢 DCloud 官方,旗下出品的 uni-appuni-app-xuniClouduni-app 小程序 等多平台、多元化的技术体系。

继续阅读 »

uni-vue3

专为 UniApp + Vue3 + UnoCSS 打造的 starter template

源码:https://github.com/vue-rookie/uni-vue3

🚀 特性

  • ✅ Vue3 + Composition API
  • ✅ UnoCSS 原子化CSS
  • ✅ TypeScript 支持
  • ✅ Vite 构建工具

📦 快速开始

小程序预览

产品

建议手机模式预览
uni-vue3框架--------------------------------代码:feature/main
uni-vue3模仿抖音---------------------------代码:feature/douyin
uni-vue3模仿小红书------------------------代码:feature/xiaohongshu

🚀 技术栈

  • 核心框架:Vue 3.4
  • 构建工具:Vite 5.0
  • 开发语言:TypeScript 5.0
  • 状态管理:Pinia 2.0
  • 样式方案:UnoCSS
  • 跨端框架:UniApp 3.0

自定义主题样式(超级自由简单的样式配置):

组件样式全部采用 tailwindcss 封装,您无需写任何 令人烦躁的 css 代码。

只需要修改对应 uno.config.ts 配置文件中的 theme 下的的色值号即可,其他无需任何更改

🪝 自定义 Hooks

项目中精心封装了丰富的自定义 Hooks,大幅提升开发效率和代码质量,所有 Hooks 支持 TypeScript 类型推导。

UI 交互类

useModal - 对话框管理

提供统一的弹窗交互解决方案,包括确认框、消息提示、加载状态等。

import { useModal } from "@/hooks"  

// 在组件中使用  
const {  
  showToast,  
  showSuccess,  
  showError,  
  showConfirm,  
  showLoading,  
  hideLoading,  
  showActionSheet,  
} = useModal()  

// 显示确认对话框  
await showConfirm({  
  title: "操作确认",  
  content: "确定要执行此操作吗?",  
})  

// 显示成功提示  
showSuccess("操作成功")  

// 显示加载状态  
const loading = showLoading()  
try {  
  // 执行异步操作  
} finally {  
  loading.hide() // 隐藏加载  
}  

// 显示底部操作菜单  
const selectedIndex = await showActionSheet({  
  itemList: ["选项一", "选项二", "选项三"],  
})

数据处理类

useStorage - 本地存储

增强版本地存储,支持过期时间、类型安全、自动序列化/反序列化、响应式存储。

import { useStorage } from "@/hooks"  

const { setStorage, getStorage, removeStorage, clearStorage, createReactiveStorage } = useStorage()  

// 存储数据,30天过期  
await setStorage("user-info", { id: 1, name: "Admin" }, 30 * 24 * 60 * 60 * 1000)  

// 读取数据  
const userInfo = await getStorage("user-info")  

// 创建响应式存储  
const count = createReactiveStorage("visit-count", 0)  
count.value++ // 自动同步到存储

useRequest - 网络请求

强大的请求管理 Hook,自动处理加载状态、错误处理、请求缓存等。

import { useRequest } from "@/hooks"  

const request = useRequest({  
  baseURL: "https://api.example.com",  
  autoHandleError: true, // 自动处理错误  
  autoLoading: true, // 自动显示加载状态  
})  

// GET 请求  
const { data } = await request.get("/users", { page: 1 })  

// POST 请求  
await request.post("/articles", { title, content })  

// 文件上传  
await request.upload("/upload", {  
  filePath: tempFilePath,  
  name: "file",  
  formData: { type: "avatar" },  
})

useInputLimit - 输入限制

提供各类输入限制函数,轻松实现各种格式检查和输入控制。

import { useInputDataLimit } from "@/hooks/useInputLimit"  

const {  
  limitNumber, // 仅限数字  
  limitLetter, // 仅限字母  
  limitNumberAndLetter, // 仅限数字和字母  
  limitToPositiveTwoDecimals, // 仅限两位小数的正数  
  limitNoChinese, // 不允许中文  
} = useInputDataLimit()  

// 在输入事件中使用  
const handleInput = (e) => {  
  const rawValue = e.detail.value  
  const formattedValue = limitToPositiveTwoDecimals(rawValue)  
  // 更新表单值  
}

设备能力类

useLocation - 位置服务

封装位置获取、地址解析、坐标转换等功能,支持高精度定位和后台定位。

import { useLocation } from "@/hooks"  

const { getLocation, startLocationUpdate, stopLocationUpdate, chooseLocation, openLocation } =  
  useLocation()  

// 获取当前位置  
const position = await getLocation({  
  type: "gcj02", // 坐标系类型  
  isHighAccuracy: true, // 高精度定位  
})  

// 打开地图选择位置  
const location = await chooseLocation()  

// 在地图上查看位置  
await openLocation({  
  latitude: 39.9087,  
  longitude: 116.3975,  
  name: "目的地",  
  address: "详细地址信息",  
  scale: 18,  
})

useCamera - 相机功能

相机相关功能封装,包括拍照、录像、选择相册等功能。

import { useCamera } from "@/hooks"  

const { takePhoto, chooseImage, chooseVideo, previewImage, compressImage } = useCamera()  

// 拍照或从相册选择  
const filePath = await takePhoto({  
  sourceType: ["camera", "album"],  
})  

// 选择多张图片  
const images = await chooseImage({  
  count: 9,  
  sizeType: ["original", "compressed"],  
})  

// 压缩图片  
const compressedPath = await compressImage(filePath, {  
  quality: 80, // 压缩质量  
})

useSystem - 系统信息

获取系统信息、设备信息、网络状态等功能。

import { useSystem } from "@/hooks"  

const { getSystemInfo, getNetworkType, onNetworkStatusChange, getDeviceInfo, vibrateShort } =  
  useSystem()  

// 获取系统信息  
const systemInfo = getSystemInfo()  
console.log(`运行平台: ${systemInfo.platform}, 系统: ${systemInfo.system}`)  

// 获取网络状态  
const networkType = await getNetworkType()  

// 监听网络变化  
onNetworkStatusChange((res) => {  
  console.log(`网络变更: ${res.networkType}, 是否连接: ${res.isConnected}`)  
})

其他实用 Hooks

useShare - 分享功能

统一的分享接口,支持小程序分享、系统分享、自定义分享等。

import { useShare } from "@/hooks"  

const { share, shareWithSystem, configMiniProgramShare } = useShare()  

// 配置页面分享参数  
configMiniProgramShare({  
  title: "分享标题",  
  path: "/pages/index/index",  
  imageUrl: "/static/share.png",  
  onShareSuccess: () => {  
    console.log("分享成功")  
  },  
})  

// 系统分享  
shareWithSystem({  
  title: "分享内容",  
  summary: "内容摘要",  
  href: "https://example.com",  
  imageUrl: "/static/share.png",  
})

useValidation - 表单验证

强大的表单验证系统,内置多种常用验证规则,支持自定义验证。

import { useValidation } from "@/hooks"  

const validation = useValidation()  

// 创建表单数据和规则  
const { formData, rules, validate, errors, resetValidation } = validation.createForm(  
  {  
    username: "",  
    password: "",  
    email: "",  
    phone: "",  
  },  
  {  
    username: [  
      { required: true, message: "用户名不能为空" },  
      { min: 3, max: 20, message: "长度在3到20个字符" },  
    ],  
    password: [  
      { required: true, message: "密码不能为空" },  
      { min: 6, message: "密码长度不能小于6位" },  
      { pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, message: "密码必须包含大小写字母和数字" },  
    ],  
    email: [{ type: "email", message: "请输入正确的邮箱格式" }],  
    phone: [{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" }],  
  },  
)  

// 提交表单  
const handleSubmit = async () => {  
  const valid = await validate()  
  if (valid) {  
    // 表单验证通过,提交数据  
    console.log("表单数据:", formData)  
  } else {  
    // 表单验证失败  
    console.log("验证错误:", errors.value)  
  }  
}

usePageScroll - 页面滚动

页面滚动相关功能,包括滚动到指定位置、监听滚动事件等。

import { usePageScroll } from "@/hooks"  

const { scrollTo, scrollToTop, scrollToSelector, onPageScroll, getScrollPosition } = usePageScroll()  

// 滚动到顶部  
scrollToTop()  

// 滚动到指定位置  
scrollTo(0, 200, true) // 带动画效果滚动到 200px 位置  

// 滚动到指定元素  
scrollToSelector(".news-item-5", {  
  offset: -20, // 偏移量  
  duration: 300, // 动画时长  
})  

// 监听页面滚动  
onPageScroll((scrollTop) => {  
  if (scrollTop > 100) {  
    // 显示返回顶部按钮  
  }  
})

📋 环境要求

  • Node.js >= 18.0.0
  • pnpm >= 7.0.0
  • 微信开发者工具(开发小程序时使用)

🛠️ 快速开始

# 安装依赖  
pnpm install  

# 开发环境运行  
pnpm dev:mp-weixin  

# 生产环境构建  
pnpm build:mp-weixin

📱 开发指南

微信小程序开发

  1. 开发环境配置

    • 运行 pnpm dev:mp-weixin 生成开发环境代码
    • 使用微信开发者工具导入 dist/dev/mp-weixin 目录
    • 开启"不校验合法域名"选项(开发环境)
  2. 生产环境发布

    • 执行 pnpm build:mp-weixin 生成生产环境代码
    • 使用微信开发者工具导入 dist/build/mp-weixin 目录
    • 点击"上传"按钮发布小程序

项目规范

  1. 组件开发规范

    • 全局组件统一放置在 uni-module 目录下
    • 遵循 UniApp 的 easycom 组件规范
    • 组件命名采用 PascalCase 命名法
  2. 类型定义规范

    • 业务类型定义统一放在对应页面的 type.ts 文件中
    • 公共类型定义放在 types 目录下
    • 类型命名采用 PascalCase 命名法
  3. Hooks 使用规范

    • 公共 hooks 统一放在 hooks 目录下
    • 业务相关 hooks 放在对应页面目录
    • hooks 命名采用 camelCase 命名法,以 use 开头

🚀 性能优化

构建优化

  1. 代码分割

    • 使用 manualChunks 实现代码分割
    • 第三方依赖独立打包,提高缓存效率
    • 路由组件按需加载
  2. 资源优化

    • 图片资源自动压缩
    • CSS 代码压缩和优化
    • 静态资源 CDN 加速
  3. 编译优化

    • 使用 lightningcss 进行 CSS 处理
    • 配置合理的 assetsInlineLimit
    • 优化 Sass 编译配置

运行时优化

  1. 渲染优化

    • 图片懒加载
    • 虚拟列表
    • 条件渲染优化
  2. 性能监控

    • 页面加载性能监控
    • 组件渲染性能分析
    • 内存使用监控

📦 项目结构

├── src  
│   ├── pages              # 页面文件  
│   ├── pages-sub          # 子页面  
│   ├── hooks              # 自定义hooks  
│   ├── static             # 静态资源  
│   ├── types              # 类型定义  
│   ├── utils              # 工具函数  
│   └── uni-module         # 全局组件  
├── vite.config.ts         # Vite 配置  
└── package.json           # 项目配置

源码

stars
forks
watchers
license

📄 开源协议

本项目采用 MIT 协议开源,详情请查看 LICENSE 文件。

致谢

感谢 DCloud 官方,旗下出品的 uni-appuni-app-xuniClouduni-app 小程序 等多平台、多元化的技术体系。

收起阅读 »

委屈人情味人情味撒

无反应

委屈人情味人情味撒
m.zezcs5d.cn/ArTicLe/213962974.SHtml
m.zezcs5d.cn/ArTicLe/002874329.SHtml
m.zezcs5d.cn/ArTicLe/879975563.SHtml
m.zezcs5d.cn/ArTicLe/585432221.SHtml
m.zezcs5d.cn/ArTicLe/170874229.SHtml
m.zezcs5d.cn/ArTicLe/946542232.SHtml
m.zezcs5d.cn/ArTicLe/595654433.SHtml
m.zezcs5d.cn/ArTicLe/346290991.SHtml
m.zezcs5d.cn/ArTicLe/280876631.SHtml
m.zezcs5d.cn/ArTicLe/807654544.SHtml
m.zezcs5d.cn/ArTicLe/479853229.SHtml
m.zezcs5d.cn/ArTicLe/591098888.SHtml
m.zezcs5d.cn/ArTicLe/502190091.SHtml
m.zezcs5d.cn/ArTicLe/252008767.SHtml
m.zezcs5d.cn/ArTicLe/279876666.SHtml
m.zezcs5d.cn/ArTicLe/179866998.SHtml
m.zezcs5d.cn/ArTicLe/279653433.SHtml
m.zezcs5d.cn/ArTicLe/380986776.SHtml
m.zezcs5d.cn/ArTicLe/068645596.SHtml
m.zezcs5d.cn/ArTicLe/402098776.SHtml
m.zezcs5d.cn/ArTicLe/735331211.SHtml
m.zezcs5d.cn/ArTicLe/742785689.SHtml
m.zezcs5d.cn/ArTicLe/046331111.SHtml
m.zezcs5d.cn/ArTicLe/933473233.SHtml
m.zezcs5d.cn/ArTicLe/402083311.SHtml
m.zezcs5d.cn/ArTicLe/268875818.SHtml
m.zezcs5d.cn/ArTicLe/168654166.SHtml
m.zezcs5d.cn/ArTicLe/846417766.SHtml
m.zezcs5d.cn/ArTicLe/602009904.SHtml
m.zezcs5d.cn/ArTicLe/179875140.SHtml
m.zezcs5d.cn/ArTicLe/139654444.SHtml
m.zezcs5d.cn/ArTicLe/368897566.SHtml
m.zezcs5d.cn/ArTicLe/607505555.SHtml
m.zezcs5d.cn/ArTicLe/042862197.SHtml
m.zezcs5d.cn/ArTicLe/846661767.SHtml
m.zezcs5d.cn/ArTicLe/713310077.SHtml
m.zezcs5d.cn/ArTicLe/320966677.SHtml
m.zezcs5d.cn/ArTicLe/944167531.SHtml
m.zezcs5d.cn/ArTicLe/480786773.SHtml
m.zezcs5d.cn/ArTicLe/265321222.SHtml
m.zezcs5d.cn/ArTicLe/568301253.SHtml
m.zezcs5d.cn/ArTicLe/861541133.SHtml
m.zezcs5d.cn/ArTicLe/508380474.SHtml
m.zezcs5d.cn/ArTicLe/545468808.SHtml
m.zezcs5d.cn/ArTicLe/055433354.SHtml
m.zezcs5d.cn/ArTicLe/971075688.SHtml
m.zezcs5d.cn/ArTicLe/593804975.SHtml
m.zezcs5d.cn/ArTicLe/401322759.SHtml
m.zezcs5d.cn/ArTicLe/323097899.SHtml
m.zezcs5d.cn/ArTicLe/502101022.SHtml
m.zezcs5d.cn/ArTicLe/523839864.SHtml
m.zezcs5d.cn/ArTicLe/068665687.SHtml
m.zezcs5d.cn/ArTicLe/205308111.SHtml
m.zezcs5d.cn/ArTicLe/822832691.SHtml
m.zezcs5d.cn/ArTicLe/168667680.SHtml
m.zezcs5d.cn/ArTicLe/761876677.SHtml
m.zezcs5d.cn/ArTicLe/168667688.SHtml
m.zezcs5d.cn/ArTicLe/845197185.SHtml
m.zezcs5d.cn/ArTicLe/858121454.SHtml
m.zezcs5d.cn/ArTicLe/068484751.SHtml
m.zezcs5d.cn/ArTicLe/755074286.SHtml
m.zezcs5d.cn/ArTicLe/502100022.SHtml
m.zezcs5d.cn/ArTicLe/336533343.SHtml
m.zezcs5d.cn/ArTicLe/280887820.SHtml
m.zezcs5d.cn/ArTicLe/290508413.SHtml
m.zezcs5d.cn/ArTicLe/216421232.SHtml
m.zezcs5d.cn/ArTicLe/391998919.SHtml
m.zezcs5d.cn/ArTicLe/488406331.SHtml
m.zezcs5d.cn/ArTicLe/268767688.SHtml
m.zezcs5d.cn/ArTicLe/982186862.SHtml
m.zezcs5d.cn/ArTicLe/269776709.SHtml
m.zezcs5d.cn/ArTicLe/877385296.SHtml
m.zezcs5d.cn/ArTicLe/325418454.SHtml
m.zezcs5d.cn/ArTicLe/513110131.SHtml
m.zezcs5d.cn/ArTicLe/987174118.SHtml
m.zezcs5d.cn/ArTicLe/824423879.SHtml
m.zezcs5d.cn/ArTicLe/115498887.SHtml
m.zezcs5d.cn/ArTicLe/846665688.SHtml
m.zezcs5d.cn/ArTicLe/770863466.SHtml
m.zezcs5d.cn/ArTicLe/068665676.SHtml
m.zezcs5d.cn/ArTicLe/168867680.SHtml
m.zezcs5d.cn/ArTicLe/993110111.SHtml
m.zezcs5d.cn/ArTicLe/068665609.SHtml
m.zezcs5d.cn/ArTicLe/057578799.SHtml
m.zezcs5d.cn/ArTicLe/904210010.SHtml
m.zezcs5d.cn/ArTicLe/212730751.SHtml
m.zezcs5d.cn/ArTicLe/047776108.SHtml
m.zezcs5d.cn/ArTicLe/004308891.SHtml
m.zezcs5d.cn/ArTicLe/502009022.SHtml
m.zezcs5d.cn/ArTicLe/401839742.SHtml
m.zezcs5d.cn/ArTicLe/380101022.SHtml
m.zezcs5d.cn/ArTicLe/004309000.SHtml
m.zezcs5d.cn/ArTicLe/724323244.SHtml
m.zezcs5d.cn/ArTicLe/071076777.SHtml
m.zezcs5d.cn/ArTicLe/613221132.SHtml
m.zezcs5d.cn/ArTicLe/946554465.SHtml
m.zezcs5d.cn/ArTicLe/871078999.SHtml
m.zezcs5d.cn/ArTicLe/279887790.SHtml
m.zezcs5d.cn/ArTicLe/888495428.SHtml
m.zezcs5d.cn/ArTicLe/391992133.SHtml
m.zezcs5d.cn/ArTicLe/871076777.SHtml
m.zezcs5d.cn/ArTicLe/377961084.SHtml
m.zezcs5d.cn/ArTicLe/358496414.SHtml
m.zezcs5d.cn/ArTicLe/759632985.SHtml
m.zezcs5d.cn/ArTicLe/112830644.SHtml
m.zezcs5d.cn/ArTicLe/244394180.SHtml
m.zezcs5d.cn/ArTicLe/982107874.SHtml
m.zezcs5d.cn/ArTicLe/391742971.SHtml
m.zezcs5d.cn/ArTicLe/364321121.SHtml
m.zezcs5d.cn/ArTicLe/006272393.SHtml
m.zezcs5d.cn/ArTicLe/381276767.SHtml
m.zezcs5d.cn/ArTicLe/626443322.SHtml
m.zezcs5d.cn/ArTicLe/771927520.SHtml
m.zezcs5d.cn/ArTicLe/284958629.SHtml
m.zezcs5d.cn/ArTicLe/447211011.SHtml
m.zezcs5d.cn/ArTicLe/112849742.SHtml
m.zezcs5d.cn/ArTicLe/170887774.SHtml
m.zezcs5d.cn/ArTicLe/846443454.SHtml
m.zezcs5d.cn/ArTicLe/390728539.SHtml
m.zezcs5d.cn/ArTicLe/240988787.SHtml
m.zezcs5d.cn/ArTicLe/068394419.SHtml
m.zezcs5d.cn/ArTicLe/842755656.SHtml
m.zezcs5d.cn/ArTicLe/333946607.SHtml
m.zezcs5d.cn/ArTicLe/725422223.SHtml
m.zezcs5d.cn/ArTicLe/099517420.SHtml
m.zezcs5d.cn/ArTicLe/956143339.SHtml
m.zezcs5d.cn/ArTicLe/604211011.SHtml
m.zezcs5d.cn/ArTicLe/319140773.SHtml
m.zezcs5d.cn/ArTicLe/836443345.SHtml
m.zezcs5d.cn/ArTicLe/958339740.SHtml
m.zezcs5d.cn/ArTicLe/479055587.SHtml
m.zezcs5d.cn/ArTicLe/966174118.SHtml
m.zezcs5d.cn/ArTicLe/392949852.SHtml
m.zezcs5d.cn/ArTicLe/622200909.SHtml
m.zezcs5d.cn/ArTicLe/270887999.SHtml
m.zezcs5d.cn/ArTicLe/404649737.SHtml
m.zezcs5d.cn/ArTicLe/502000000.SHtml
m.zezcs5d.cn/ArTicLe/991022233.SHtml
m.zezcs5d.cn/ArTicLe/292839740.SHtml
m.zezcs5d.cn/ArTicLe/539138533.SHtml
m.zezcs5d.cn/ArTicLe/579978787.SHtml
m.zezcs5d.cn/ArTicLe/709977788.SHtml
m.zezcs5d.cn/ArTicLe/077560075.SHtml
m.zezcs5d.cn/ArTicLe/946443343.SHtml
m.zezcs5d.cn/ArTicLe/600999899.SHtml
m.zezcs5d.cn/ArTicLe/956274196.SHtml
m.zezcs5d.cn/ArTicLe/169879898.SHtml
m.zezcs5d.cn/ArTicLe/067543454.SHtml
m.zezcs5d.cn/ArTicLe/734052974.SHtml
m.zezcs5d.cn/ArTicLe/413322122.SHtml
m.zezcs5d.cn/ArTicLe/624421132.SHtml
m.zezcs5d.cn/ArTicLe/280503873.SHtml
m.zezcs5d.cn/ArTicLe/310643444.SHtml
m.zezcs5d.cn/ArTicLe/513119000.SHtml
m.zezcs5d.cn/ArTicLe/734966509.SHtml
m.zezcs5d.cn/ArTicLe/992209000.SHtml
m.zezcs5d.cn/ArTicLe/835331232.SHtml
m.zezcs5d.cn/ArTicLe/745053262.SHtml
m.zezcs5d.cn/ArTicLe/928866767.SHtml
m.zezcs5d.cn/ArTicLe/513210020.SHtml
m.zezcs5d.cn/ArTicLe/189528810.SHtml
m.zezcs5d.cn/ArTicLe/244352112.SHtml
m.zezcs5d.cn/ArTicLe/168305574.SHtml
m.zezcs5d.cn/ArTicLe/390989998.SHtml
m.zezcs5d.cn/ArTicLe/575332332.SHtml
m.zezcs5d.cn/ArTicLe/189417520.SHtml
m.zezcs5d.cn/ArTicLe/396098899.SHtml
m.zezcs5d.cn/ArTicLe/179496317.SHtml
m.zezcs5d.cn/ArTicLe/481211121.SHtml
m.zezcs5d.cn/ArTicLe/170643345.SHtml
m.zezcs5d.cn/ArTicLe/301616539.SHtml
m.zezcs5d.cn/ArTicLe/649500009.SHtml
m.zezcs5d.cn/ArTicLe/955272973.SHtml
m.zezcs5d.cn/ArTicLe/168861578.SHtml
m.zezcs5d.cn/ArTicLe/747887887.SHtml
m.zezcs5d.cn/ArTicLe/956305207.SHtml
m.zezcs5d.cn/ArTicLe/302231024.SHtml
m.zezcs5d.cn/ArTicLe/170616317.SHtml
m.zezcs5d.cn/ArTicLe/068666103.SHtml
m.zezcs5d.cn/ArTicLe/956673344.SHtml
m.zezcs5d.cn/ArTicLe/776161964.SHtml
m.zezcs5d.cn/ArTicLe/254211011.SHtml
m.zezcs5d.cn/ArTicLe/734949856.SHtml
m.zezcs5d.cn/ArTicLe/602113780.SHtml
m.zezcs5d.cn/ArTicLe/916495677.SHtml
m.zezcs5d.cn/ArTicLe/926162075.SHtml
m.zezcs5d.cn/ArTicLe/616020132.SHtml
m.zezcs5d.cn/ArTicLe/312727529.SHtml
m.zezcs5d.cn/ArTicLe/271009633.SHtml
m.zezcs5d.cn/ArTicLe/650953456.SHtml
m.zezcs5d.cn/ArTicLe/390617441.SHtml
m.zezcs5d.cn/ArTicLe/043544344.SHtml
m.zezcs5d.cn/ArTicLe/523838852.SHtml
m.zezcs5d.cn/ArTicLe/347653403.SHtml
m.zezcs5d.cn/ArTicLe/381447767.SHtml
m.zezcs5d.cn/ArTicLe/746161984.SHtml
m.zezcs5d.cn/ArTicLe/457554465.SHtml
m.zezcs5d.cn/ArTicLe/966161069.SHtml
m.zezcs5d.cn/ArTicLe/402199093.SHtml
m.zezcs5d.cn/ArTicLe/930872809.SHtml
m.zezcs5d.cn/ArTicLe/902740853.SHtml
m.zezcs5d.cn/ArTicLe/613110002.SHtml
m.zezcs5d.cn/ArTicLe/299638539.SHtml
m.zezcs5d.cn/ArTicLe/925342345.SHtml
m.zezcs5d.cn/ArTicLe/728093899.SHtml
m.zezcs5d.cn/ArTicLe/967194328.SHtml
m.zezcs5d.cn/ArTicLe/960887788.SHtml
m.zezcs5d.cn/ArTicLe/179773622.SHtml
m.zezcs5d.cn/ArTicLe/845050995.SHtml
m.zezcs5d.cn/ArTicLe/845367809.SHtml
m.zezcs5d.cn/ArTicLe/602201003.SHtml
m.zezcs5d.cn/ArTicLe/112727642.SHtml
m.zezcs5d.cn/ArTicLe/907315243.SHtml
m.zezcs5d.cn/ArTicLe/935454545.SHtml
m.zezcs5d.cn/ArTicLe/000616433.SHtml
m.zezcs5d.cn/ArTicLe/624355688.SHtml
m.zezcs5d.cn/ArTicLe/289594497.SHtml
m.zezcs5d.cn/ArTicLe/391988988.SHtml
m.zezcs5d.cn/ArTicLe/615146617.SHtml
m.zezcs5d.cn/ArTicLe/606340777.SHtml
m.zezcs5d.cn/ArTicLe/481099898.SHtml
m.zezcs5d.cn/ArTicLe/957373590.SHtml
m.zezcs5d.cn/ArTicLe/935543533.SHtml
m.zezcs5d.cn/ArTicLe/480988287.SHtml
m.zezcs5d.cn/ArTicLe/955193651.SHtml
m.zezcs5d.cn/ArTicLe/624213244.SHtml
m.zezcs5d.cn/ArTicLe/404949636.SHtml
m.zezcs5d.cn/ArTicLe/513109665.SHtml
m.zezcs5d.cn/ArTicLe/658775666.SHtml
m.zezcs5d.cn/ArTicLe/400766608.SHtml
m.zezcs5d.cn/ArTicLe/280987887.SHtml
m.zezcs5d.cn/ArTicLe/593320123.SHtml
m.zezcs5d.cn/ArTicLe/089492696.SHtml
m.zezcs5d.cn/ArTicLe/179776118.SHtml
m.zezcs5d.cn/ArTicLe/613220132.SHtml
m.zezcs5d.cn/ArTicLe/845949973.SHtml
m.zezcs5d.cn/ArTicLe/392322231.SHtml
m.zezcs5d.cn/ArTicLe/956283937.SHtml
m.zezcs5d.cn/ArTicLe/957664556.SHtml
m.zezcs5d.cn/ArTicLe/395161881.SHtml
m.zezcs5d.cn/ArTicLe/411461503.SHtml
m.zezcs5d.cn/ArTicLe/408757800.SHtml
m.zezcs5d.cn/ArTicLe/068153186.SHtml
m.zezcs5d.cn/ArTicLe/402103093.SHtml
m.zezcs5d.cn/ArTicLe/724344355.SHtml
m.zezcs5d.cn/ArTicLe/068493821.SHtml
m.zezcs5d.cn/ArTicLe/513007756.SHtml
m.zezcs5d.cn/ArTicLe/613310021.SHtml
m.zezcs5d.cn/ArTicLe/401838560.SHtml
m.zezcs5d.cn/ArTicLe/624430555.SHtml
m.zezcs5d.cn/ArTicLe/068654466.SHtml
m.zezcs5d.cn/ArTicLe/411749859.SHtml
m.zezcs5d.cn/ArTicLe/835431766.SHtml
m.zezcs5d.cn/ArTicLe/592108923.SHtml
m.zezcs5d.cn/ArTicLe/070616062.SHtml
m.zezcs5d.cn/ArTicLe/623217121.SHtml
m.zezcs5d.cn/ArTicLe/957383755.SHtml
m.zezcs5d.cn/ArTicLe/261097800.SHtml
m.zezcs5d.cn/ArTicLe/380988817.SHtml
m.zezcs5d.cn/ArTicLe/545273191.SHtml
m.zezcs5d.cn/ArTicLe/280876688.SHtml
m.zezcs5d.cn/ArTicLe/755161959.SHtml
m.zezcs5d.cn/ArTicLe/626443412.SHtml
m.zezcs5d.cn/ArTicLe/168766522.SHtml
m.zezcs5d.cn/ArTicLe/290610462.SHtml
m.zezcs5d.cn/ArTicLe/491110111.SHtml
m.zezcs5d.cn/ArTicLe/201838370.SHtml
m.zezcs5d.cn/ArTicLe/957654462.SHtml
m.zezcs5d.cn/ArTicLe/402099022.SHtml
m.zezcs5d.cn/ArTicLe/512847163.SHtml
m.zezcs5d.cn/ArTicLe/390988799.SHtml
m.zezcs5d.cn/ArTicLe/067296380.SHtml
m.zezcs5d.cn/ArTicLe/724311292.SHtml
m.zezcs5d.cn/ArTicLe/068765679.SHtml
m.zezcs5d.cn/ArTicLe/401832605.SHtml
m.zezcs5d.cn/ArTicLe/936544343.SHtml
m.zezcs5d.cn/ArTicLe/735050914.SHtml
m.zezcs5d.cn/ArTicLe/157765572.SHtml
m.zezcs5d.cn/ArTicLe/279879894.SHtml
m.zezcs5d.cn/ArTicLe/966480382.SHtml
m.zezcs5d.cn/ArTicLe/602209910.SHtml
m.zezcs5d.cn/ArTicLe/866272075.SHtml
m.zezcs5d.cn/ArTicLe/067655555.SHtml
m.zezcs5d.cn/ArTicLe/179775603.SHtml
m.zezcs5d.cn/ArTicLe/048051862.SHtml
m.zezcs5d.cn/ArTicLe/481099888.SHtml
m.zezcs5d.cn/ArTicLe/067494106.SHtml
m.zezcs5d.cn/ArTicLe/635105460.SHtml
m.zezcs5d.cn/ArTicLe/593210101.SHtml
m.zezcs5d.cn/ArTicLe/970507833.SHtml
m.zezcs5d.cn/ArTicLe/946553465.SHtml
m.zezcs5d.cn/ArTicLe/633969815.SHtml
m.zezcs5d.cn/ArTicLe/179766566.SHtml
m.zezcs5d.cn/ArTicLe/490020122.SHtml
m.zezcs5d.cn/ArTicLe/699838275.SHtml
m.zezcs5d.cn/ArTicLe/046554454.SHtml
m.zezcs5d.cn/ArTicLe/755161990.SHtml
m.zezcs5d.cn/ArTicLe/179776687.SHtml
m.zezcs5d.cn/ArTicLe/302209222.SHtml
m.zezcs5d.cn/ArTicLe/512962462.SHtml

继续阅读 »

委屈人情味人情味撒
m.zezcs5d.cn/ArTicLe/213962974.SHtml
m.zezcs5d.cn/ArTicLe/002874329.SHtml
m.zezcs5d.cn/ArTicLe/879975563.SHtml
m.zezcs5d.cn/ArTicLe/585432221.SHtml
m.zezcs5d.cn/ArTicLe/170874229.SHtml
m.zezcs5d.cn/ArTicLe/946542232.SHtml
m.zezcs5d.cn/ArTicLe/595654433.SHtml
m.zezcs5d.cn/ArTicLe/346290991.SHtml
m.zezcs5d.cn/ArTicLe/280876631.SHtml
m.zezcs5d.cn/ArTicLe/807654544.SHtml
m.zezcs5d.cn/ArTicLe/479853229.SHtml
m.zezcs5d.cn/ArTicLe/591098888.SHtml
m.zezcs5d.cn/ArTicLe/502190091.SHtml
m.zezcs5d.cn/ArTicLe/252008767.SHtml
m.zezcs5d.cn/ArTicLe/279876666.SHtml
m.zezcs5d.cn/ArTicLe/179866998.SHtml
m.zezcs5d.cn/ArTicLe/279653433.SHtml
m.zezcs5d.cn/ArTicLe/380986776.SHtml
m.zezcs5d.cn/ArTicLe/068645596.SHtml
m.zezcs5d.cn/ArTicLe/402098776.SHtml
m.zezcs5d.cn/ArTicLe/735331211.SHtml
m.zezcs5d.cn/ArTicLe/742785689.SHtml
m.zezcs5d.cn/ArTicLe/046331111.SHtml
m.zezcs5d.cn/ArTicLe/933473233.SHtml
m.zezcs5d.cn/ArTicLe/402083311.SHtml
m.zezcs5d.cn/ArTicLe/268875818.SHtml
m.zezcs5d.cn/ArTicLe/168654166.SHtml
m.zezcs5d.cn/ArTicLe/846417766.SHtml
m.zezcs5d.cn/ArTicLe/602009904.SHtml
m.zezcs5d.cn/ArTicLe/179875140.SHtml
m.zezcs5d.cn/ArTicLe/139654444.SHtml
m.zezcs5d.cn/ArTicLe/368897566.SHtml
m.zezcs5d.cn/ArTicLe/607505555.SHtml
m.zezcs5d.cn/ArTicLe/042862197.SHtml
m.zezcs5d.cn/ArTicLe/846661767.SHtml
m.zezcs5d.cn/ArTicLe/713310077.SHtml
m.zezcs5d.cn/ArTicLe/320966677.SHtml
m.zezcs5d.cn/ArTicLe/944167531.SHtml
m.zezcs5d.cn/ArTicLe/480786773.SHtml
m.zezcs5d.cn/ArTicLe/265321222.SHtml
m.zezcs5d.cn/ArTicLe/568301253.SHtml
m.zezcs5d.cn/ArTicLe/861541133.SHtml
m.zezcs5d.cn/ArTicLe/508380474.SHtml
m.zezcs5d.cn/ArTicLe/545468808.SHtml
m.zezcs5d.cn/ArTicLe/055433354.SHtml
m.zezcs5d.cn/ArTicLe/971075688.SHtml
m.zezcs5d.cn/ArTicLe/593804975.SHtml
m.zezcs5d.cn/ArTicLe/401322759.SHtml
m.zezcs5d.cn/ArTicLe/323097899.SHtml
m.zezcs5d.cn/ArTicLe/502101022.SHtml
m.zezcs5d.cn/ArTicLe/523839864.SHtml
m.zezcs5d.cn/ArTicLe/068665687.SHtml
m.zezcs5d.cn/ArTicLe/205308111.SHtml
m.zezcs5d.cn/ArTicLe/822832691.SHtml
m.zezcs5d.cn/ArTicLe/168667680.SHtml
m.zezcs5d.cn/ArTicLe/761876677.SHtml
m.zezcs5d.cn/ArTicLe/168667688.SHtml
m.zezcs5d.cn/ArTicLe/845197185.SHtml
m.zezcs5d.cn/ArTicLe/858121454.SHtml
m.zezcs5d.cn/ArTicLe/068484751.SHtml
m.zezcs5d.cn/ArTicLe/755074286.SHtml
m.zezcs5d.cn/ArTicLe/502100022.SHtml
m.zezcs5d.cn/ArTicLe/336533343.SHtml
m.zezcs5d.cn/ArTicLe/280887820.SHtml
m.zezcs5d.cn/ArTicLe/290508413.SHtml
m.zezcs5d.cn/ArTicLe/216421232.SHtml
m.zezcs5d.cn/ArTicLe/391998919.SHtml
m.zezcs5d.cn/ArTicLe/488406331.SHtml
m.zezcs5d.cn/ArTicLe/268767688.SHtml
m.zezcs5d.cn/ArTicLe/982186862.SHtml
m.zezcs5d.cn/ArTicLe/269776709.SHtml
m.zezcs5d.cn/ArTicLe/877385296.SHtml
m.zezcs5d.cn/ArTicLe/325418454.SHtml
m.zezcs5d.cn/ArTicLe/513110131.SHtml
m.zezcs5d.cn/ArTicLe/987174118.SHtml
m.zezcs5d.cn/ArTicLe/824423879.SHtml
m.zezcs5d.cn/ArTicLe/115498887.SHtml
m.zezcs5d.cn/ArTicLe/846665688.SHtml
m.zezcs5d.cn/ArTicLe/770863466.SHtml
m.zezcs5d.cn/ArTicLe/068665676.SHtml
m.zezcs5d.cn/ArTicLe/168867680.SHtml
m.zezcs5d.cn/ArTicLe/993110111.SHtml
m.zezcs5d.cn/ArTicLe/068665609.SHtml
m.zezcs5d.cn/ArTicLe/057578799.SHtml
m.zezcs5d.cn/ArTicLe/904210010.SHtml
m.zezcs5d.cn/ArTicLe/212730751.SHtml
m.zezcs5d.cn/ArTicLe/047776108.SHtml
m.zezcs5d.cn/ArTicLe/004308891.SHtml
m.zezcs5d.cn/ArTicLe/502009022.SHtml
m.zezcs5d.cn/ArTicLe/401839742.SHtml
m.zezcs5d.cn/ArTicLe/380101022.SHtml
m.zezcs5d.cn/ArTicLe/004309000.SHtml
m.zezcs5d.cn/ArTicLe/724323244.SHtml
m.zezcs5d.cn/ArTicLe/071076777.SHtml
m.zezcs5d.cn/ArTicLe/613221132.SHtml
m.zezcs5d.cn/ArTicLe/946554465.SHtml
m.zezcs5d.cn/ArTicLe/871078999.SHtml
m.zezcs5d.cn/ArTicLe/279887790.SHtml
m.zezcs5d.cn/ArTicLe/888495428.SHtml
m.zezcs5d.cn/ArTicLe/391992133.SHtml
m.zezcs5d.cn/ArTicLe/871076777.SHtml
m.zezcs5d.cn/ArTicLe/377961084.SHtml
m.zezcs5d.cn/ArTicLe/358496414.SHtml
m.zezcs5d.cn/ArTicLe/759632985.SHtml
m.zezcs5d.cn/ArTicLe/112830644.SHtml
m.zezcs5d.cn/ArTicLe/244394180.SHtml
m.zezcs5d.cn/ArTicLe/982107874.SHtml
m.zezcs5d.cn/ArTicLe/391742971.SHtml
m.zezcs5d.cn/ArTicLe/364321121.SHtml
m.zezcs5d.cn/ArTicLe/006272393.SHtml
m.zezcs5d.cn/ArTicLe/381276767.SHtml
m.zezcs5d.cn/ArTicLe/626443322.SHtml
m.zezcs5d.cn/ArTicLe/771927520.SHtml
m.zezcs5d.cn/ArTicLe/284958629.SHtml
m.zezcs5d.cn/ArTicLe/447211011.SHtml
m.zezcs5d.cn/ArTicLe/112849742.SHtml
m.zezcs5d.cn/ArTicLe/170887774.SHtml
m.zezcs5d.cn/ArTicLe/846443454.SHtml
m.zezcs5d.cn/ArTicLe/390728539.SHtml
m.zezcs5d.cn/ArTicLe/240988787.SHtml
m.zezcs5d.cn/ArTicLe/068394419.SHtml
m.zezcs5d.cn/ArTicLe/842755656.SHtml
m.zezcs5d.cn/ArTicLe/333946607.SHtml
m.zezcs5d.cn/ArTicLe/725422223.SHtml
m.zezcs5d.cn/ArTicLe/099517420.SHtml
m.zezcs5d.cn/ArTicLe/956143339.SHtml
m.zezcs5d.cn/ArTicLe/604211011.SHtml
m.zezcs5d.cn/ArTicLe/319140773.SHtml
m.zezcs5d.cn/ArTicLe/836443345.SHtml
m.zezcs5d.cn/ArTicLe/958339740.SHtml
m.zezcs5d.cn/ArTicLe/479055587.SHtml
m.zezcs5d.cn/ArTicLe/966174118.SHtml
m.zezcs5d.cn/ArTicLe/392949852.SHtml
m.zezcs5d.cn/ArTicLe/622200909.SHtml
m.zezcs5d.cn/ArTicLe/270887999.SHtml
m.zezcs5d.cn/ArTicLe/404649737.SHtml
m.zezcs5d.cn/ArTicLe/502000000.SHtml
m.zezcs5d.cn/ArTicLe/991022233.SHtml
m.zezcs5d.cn/ArTicLe/292839740.SHtml
m.zezcs5d.cn/ArTicLe/539138533.SHtml
m.zezcs5d.cn/ArTicLe/579978787.SHtml
m.zezcs5d.cn/ArTicLe/709977788.SHtml
m.zezcs5d.cn/ArTicLe/077560075.SHtml
m.zezcs5d.cn/ArTicLe/946443343.SHtml
m.zezcs5d.cn/ArTicLe/600999899.SHtml
m.zezcs5d.cn/ArTicLe/956274196.SHtml
m.zezcs5d.cn/ArTicLe/169879898.SHtml
m.zezcs5d.cn/ArTicLe/067543454.SHtml
m.zezcs5d.cn/ArTicLe/734052974.SHtml
m.zezcs5d.cn/ArTicLe/413322122.SHtml
m.zezcs5d.cn/ArTicLe/624421132.SHtml
m.zezcs5d.cn/ArTicLe/280503873.SHtml
m.zezcs5d.cn/ArTicLe/310643444.SHtml
m.zezcs5d.cn/ArTicLe/513119000.SHtml
m.zezcs5d.cn/ArTicLe/734966509.SHtml
m.zezcs5d.cn/ArTicLe/992209000.SHtml
m.zezcs5d.cn/ArTicLe/835331232.SHtml
m.zezcs5d.cn/ArTicLe/745053262.SHtml
m.zezcs5d.cn/ArTicLe/928866767.SHtml
m.zezcs5d.cn/ArTicLe/513210020.SHtml
m.zezcs5d.cn/ArTicLe/189528810.SHtml
m.zezcs5d.cn/ArTicLe/244352112.SHtml
m.zezcs5d.cn/ArTicLe/168305574.SHtml
m.zezcs5d.cn/ArTicLe/390989998.SHtml
m.zezcs5d.cn/ArTicLe/575332332.SHtml
m.zezcs5d.cn/ArTicLe/189417520.SHtml
m.zezcs5d.cn/ArTicLe/396098899.SHtml
m.zezcs5d.cn/ArTicLe/179496317.SHtml
m.zezcs5d.cn/ArTicLe/481211121.SHtml
m.zezcs5d.cn/ArTicLe/170643345.SHtml
m.zezcs5d.cn/ArTicLe/301616539.SHtml
m.zezcs5d.cn/ArTicLe/649500009.SHtml
m.zezcs5d.cn/ArTicLe/955272973.SHtml
m.zezcs5d.cn/ArTicLe/168861578.SHtml
m.zezcs5d.cn/ArTicLe/747887887.SHtml
m.zezcs5d.cn/ArTicLe/956305207.SHtml
m.zezcs5d.cn/ArTicLe/302231024.SHtml
m.zezcs5d.cn/ArTicLe/170616317.SHtml
m.zezcs5d.cn/ArTicLe/068666103.SHtml
m.zezcs5d.cn/ArTicLe/956673344.SHtml
m.zezcs5d.cn/ArTicLe/776161964.SHtml
m.zezcs5d.cn/ArTicLe/254211011.SHtml
m.zezcs5d.cn/ArTicLe/734949856.SHtml
m.zezcs5d.cn/ArTicLe/602113780.SHtml
m.zezcs5d.cn/ArTicLe/916495677.SHtml
m.zezcs5d.cn/ArTicLe/926162075.SHtml
m.zezcs5d.cn/ArTicLe/616020132.SHtml
m.zezcs5d.cn/ArTicLe/312727529.SHtml
m.zezcs5d.cn/ArTicLe/271009633.SHtml
m.zezcs5d.cn/ArTicLe/650953456.SHtml
m.zezcs5d.cn/ArTicLe/390617441.SHtml
m.zezcs5d.cn/ArTicLe/043544344.SHtml
m.zezcs5d.cn/ArTicLe/523838852.SHtml
m.zezcs5d.cn/ArTicLe/347653403.SHtml
m.zezcs5d.cn/ArTicLe/381447767.SHtml
m.zezcs5d.cn/ArTicLe/746161984.SHtml
m.zezcs5d.cn/ArTicLe/457554465.SHtml
m.zezcs5d.cn/ArTicLe/966161069.SHtml
m.zezcs5d.cn/ArTicLe/402199093.SHtml
m.zezcs5d.cn/ArTicLe/930872809.SHtml
m.zezcs5d.cn/ArTicLe/902740853.SHtml
m.zezcs5d.cn/ArTicLe/613110002.SHtml
m.zezcs5d.cn/ArTicLe/299638539.SHtml
m.zezcs5d.cn/ArTicLe/925342345.SHtml
m.zezcs5d.cn/ArTicLe/728093899.SHtml
m.zezcs5d.cn/ArTicLe/967194328.SHtml
m.zezcs5d.cn/ArTicLe/960887788.SHtml
m.zezcs5d.cn/ArTicLe/179773622.SHtml
m.zezcs5d.cn/ArTicLe/845050995.SHtml
m.zezcs5d.cn/ArTicLe/845367809.SHtml
m.zezcs5d.cn/ArTicLe/602201003.SHtml
m.zezcs5d.cn/ArTicLe/112727642.SHtml
m.zezcs5d.cn/ArTicLe/907315243.SHtml
m.zezcs5d.cn/ArTicLe/935454545.SHtml
m.zezcs5d.cn/ArTicLe/000616433.SHtml
m.zezcs5d.cn/ArTicLe/624355688.SHtml
m.zezcs5d.cn/ArTicLe/289594497.SHtml
m.zezcs5d.cn/ArTicLe/391988988.SHtml
m.zezcs5d.cn/ArTicLe/615146617.SHtml
m.zezcs5d.cn/ArTicLe/606340777.SHtml
m.zezcs5d.cn/ArTicLe/481099898.SHtml
m.zezcs5d.cn/ArTicLe/957373590.SHtml
m.zezcs5d.cn/ArTicLe/935543533.SHtml
m.zezcs5d.cn/ArTicLe/480988287.SHtml
m.zezcs5d.cn/ArTicLe/955193651.SHtml
m.zezcs5d.cn/ArTicLe/624213244.SHtml
m.zezcs5d.cn/ArTicLe/404949636.SHtml
m.zezcs5d.cn/ArTicLe/513109665.SHtml
m.zezcs5d.cn/ArTicLe/658775666.SHtml
m.zezcs5d.cn/ArTicLe/400766608.SHtml
m.zezcs5d.cn/ArTicLe/280987887.SHtml
m.zezcs5d.cn/ArTicLe/593320123.SHtml
m.zezcs5d.cn/ArTicLe/089492696.SHtml
m.zezcs5d.cn/ArTicLe/179776118.SHtml
m.zezcs5d.cn/ArTicLe/613220132.SHtml
m.zezcs5d.cn/ArTicLe/845949973.SHtml
m.zezcs5d.cn/ArTicLe/392322231.SHtml
m.zezcs5d.cn/ArTicLe/956283937.SHtml
m.zezcs5d.cn/ArTicLe/957664556.SHtml
m.zezcs5d.cn/ArTicLe/395161881.SHtml
m.zezcs5d.cn/ArTicLe/411461503.SHtml
m.zezcs5d.cn/ArTicLe/408757800.SHtml
m.zezcs5d.cn/ArTicLe/068153186.SHtml
m.zezcs5d.cn/ArTicLe/402103093.SHtml
m.zezcs5d.cn/ArTicLe/724344355.SHtml
m.zezcs5d.cn/ArTicLe/068493821.SHtml
m.zezcs5d.cn/ArTicLe/513007756.SHtml
m.zezcs5d.cn/ArTicLe/613310021.SHtml
m.zezcs5d.cn/ArTicLe/401838560.SHtml
m.zezcs5d.cn/ArTicLe/624430555.SHtml
m.zezcs5d.cn/ArTicLe/068654466.SHtml
m.zezcs5d.cn/ArTicLe/411749859.SHtml
m.zezcs5d.cn/ArTicLe/835431766.SHtml
m.zezcs5d.cn/ArTicLe/592108923.SHtml
m.zezcs5d.cn/ArTicLe/070616062.SHtml
m.zezcs5d.cn/ArTicLe/623217121.SHtml
m.zezcs5d.cn/ArTicLe/957383755.SHtml
m.zezcs5d.cn/ArTicLe/261097800.SHtml
m.zezcs5d.cn/ArTicLe/380988817.SHtml
m.zezcs5d.cn/ArTicLe/545273191.SHtml
m.zezcs5d.cn/ArTicLe/280876688.SHtml
m.zezcs5d.cn/ArTicLe/755161959.SHtml
m.zezcs5d.cn/ArTicLe/626443412.SHtml
m.zezcs5d.cn/ArTicLe/168766522.SHtml
m.zezcs5d.cn/ArTicLe/290610462.SHtml
m.zezcs5d.cn/ArTicLe/491110111.SHtml
m.zezcs5d.cn/ArTicLe/201838370.SHtml
m.zezcs5d.cn/ArTicLe/957654462.SHtml
m.zezcs5d.cn/ArTicLe/402099022.SHtml
m.zezcs5d.cn/ArTicLe/512847163.SHtml
m.zezcs5d.cn/ArTicLe/390988799.SHtml
m.zezcs5d.cn/ArTicLe/067296380.SHtml
m.zezcs5d.cn/ArTicLe/724311292.SHtml
m.zezcs5d.cn/ArTicLe/068765679.SHtml
m.zezcs5d.cn/ArTicLe/401832605.SHtml
m.zezcs5d.cn/ArTicLe/936544343.SHtml
m.zezcs5d.cn/ArTicLe/735050914.SHtml
m.zezcs5d.cn/ArTicLe/157765572.SHtml
m.zezcs5d.cn/ArTicLe/279879894.SHtml
m.zezcs5d.cn/ArTicLe/966480382.SHtml
m.zezcs5d.cn/ArTicLe/602209910.SHtml
m.zezcs5d.cn/ArTicLe/866272075.SHtml
m.zezcs5d.cn/ArTicLe/067655555.SHtml
m.zezcs5d.cn/ArTicLe/179775603.SHtml
m.zezcs5d.cn/ArTicLe/048051862.SHtml
m.zezcs5d.cn/ArTicLe/481099888.SHtml
m.zezcs5d.cn/ArTicLe/067494106.SHtml
m.zezcs5d.cn/ArTicLe/635105460.SHtml
m.zezcs5d.cn/ArTicLe/593210101.SHtml
m.zezcs5d.cn/ArTicLe/970507833.SHtml
m.zezcs5d.cn/ArTicLe/946553465.SHtml
m.zezcs5d.cn/ArTicLe/633969815.SHtml
m.zezcs5d.cn/ArTicLe/179766566.SHtml
m.zezcs5d.cn/ArTicLe/490020122.SHtml
m.zezcs5d.cn/ArTicLe/699838275.SHtml
m.zezcs5d.cn/ArTicLe/046554454.SHtml
m.zezcs5d.cn/ArTicLe/755161990.SHtml
m.zezcs5d.cn/ArTicLe/179776687.SHtml
m.zezcs5d.cn/ArTicLe/302209222.SHtml
m.zezcs5d.cn/ArTicLe/512962462.SHtml

收起阅读 »

【鸿蒙征文】星光不负,码向未来:uni-app + uniCloud 赋能社区管理系统的高效适配与生态融合实践

鸿蒙征文

前言

公司前段时间接了一个社区管理的项目,看到本次 鸿蒙征文 活动,正好做一次总结。

我们基于 uni-app 跨端框架和 uniCloud Serverless服务,开发“CommunityHub 社区管理系统”。该系统旨在连接社区物业与居民。

我会在本文中重点阐述了如何利用 uni-app 实现应用在鸿蒙 OS 上的快速适配、多设备响应式布局、已经App和鸿蒙元服务共享代码。

一、技术选型与项目概述

1.1 社区数字化面临的挑战

接到需求后,我们分析,主要有两个挑战:

  • 多端适配: 社区服务需要覆盖居民的手机、平板乃至管理员的PC,且在当下中国,鸿蒙的兼容是必须考虑的平台,维护多套代码成本极高。
  • 后端运维: 社区系统,日常请求量并不高,但需满足特殊情况下的高并发处理,需要稳定且易运维的后端支持。

1.2 uni-app + uniCloud:理想的解决方案

我们选择了 uni-app + uniCloud 这一黄金组合作为 CommunityHub 的技术底座,侧重于开发效率和后端弹性:

  • uni-app:高效跨端开发,一次编码,多端发行,开发效率高。基于 Web 技术的快速适配和高效迭代能力,能够迅速发布应用,并确保基础体验。

  • uniCloud:Serverless 后端,免运维、弹性伸缩,提供数据安全保障。极简 API 调用,为前端提供了统一、稳定的数据接口。

二、鸿蒙 OS 适配与生态融合策略

要使 CommunityHub 在鸿蒙生态中获得成功,我们采取了“高效适配先行,生态融合跟进”的策略。

2.1 策略一:统一的响应式布局,适配多设备形态

作为社区管理系统,CommunityHub 需兼顾居民的手机端和管理员的 PC/平板大屏端体验,充分利用了 uni-app 强大的响应式能力。

设计理念: 利用 uni-app 的条件编译和 CSS 媒体查询功能,配合简洁的组件化设计,实现 UI 的自适应重构。

  • 手机小屏(<768px): 采用底部 TabBar 导航,信息流以列表卡片形式展示,突出点击和触控体验。
  • PC/平板大屏(>1200px): 通过leftWindow切换为侧边栏导航和分栏布局。例如“报修管理”页面,在大屏上可以同时显示报修列表、详情和派单操作区,极大提升了管理员的效率。

这种“一套代码,两种形态”的响应式开发策略,最大限度地复用了代码,为未来应用在鸿蒙多终端上的部署打下了坚实基础。

2.2 策略二:面向鸿蒙 NEXT 生态的融合与升级路径

1. 鸿蒙元服务的支持

优势: 得益于 uni-app 对鸿蒙生态的深度支持,标准 uni-app 项目可以直接发行到鸿蒙元服务。

这极大地简化了开发流程,实现了主 App 与元服务代码基础的统一。

  • 解决方案: 我们采用 “uni-app 快速编译 + uniCloud 数据驱动” 的DCloud全家桶策略。
  • 元服务开发: 在 uni-app 项目中,使用条件编译或单独页面,针对性地开发轻量级的“快捷报修提交”功能模块。
  • 快速部署与统一代码: 将该模块通过 uni-app 编译部署为鸿蒙元服务,确保其 UI 和逻辑与主 App 保持高度一致,并享受 uni-app 的快速迭代优势。
  • 效果: 实现了“一次编码,多端复用”在 App 和元服务上的实践,确保了“即用即走”的用户体验,同时将复杂数据和业务逻辑留在了 uniCloud 统一后端,极大提升了社区服务的便捷性和开发效率。

2. 紧急公告卡片(服务卡片)设想:

对于“停水通知”等紧急公告,我们规划利用鸿蒙的服务卡片能力,通过原生卡片实时拉取 uniCloud 的最新公告数据,直接在用户桌面上显示公告摘要,确保紧急消息的触达率。

参考资料:鸿蒙元服务专题

三、关键功能实现与 uniCloud 赋能

3.1 高效报修与权限控制

核心逻辑:

  • 权限与通知: 通过 uni-id 的角色体系(居民/物业/维修人员)校验身份,并将任务自动派发给对应角色的物业管理员。
  • 进度跟踪: 利用 uni-push,居民端能实时获取报修单状态(已派单、处理中、已完成)的变更,保证了用户体验的实时性。

3.2 邻里交流与资源共享的数据安全

“邻里圈”和“资源共享”模块利用 uniCloud 强大的安全能力:

我们使用 uniCloud 的 云存储 存储用户上传的动态图片和视频,利用 权限规则 确保居民之间数据隔离。

对于“私信联系”功能,基于 uni-id 实现了安全的用户身份认证和通信机制,保护用户隐私。

3.3 技术特点

本系统全部基于uni-app内置组件和uni-ui扩展组件实现,尽可能使用图标代替图片,减少资源包大小,提升小程序的冷启动时间。

如下是对tabbar的一个配置,我们使用了uniicons.ttf,没有使用任何额外图片。

"tabBar": {  
        "color": "#7A7E83",  
        "selectedColor": "#007AFF",  
        "borderStyle": "black",  
        "backgroundColor": "#FFFFFF",  
        "iconfontSrc": "uni_modules/uni-icons/components/uni-icons/uniicons.ttf",  
        "list": [{  
            "pagePath": "pages/home/home",  
            "iconfont": {  
                "text": "\ue662",  
                "selectedText": "\ue663",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "首页"  
        }, {  
            "pagePath": "pages/community/post-list",  
            "iconfont": {  
                "text": "\ue682",  
                "selectedText": "\ue682",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "邻里圈"  
        }, {  
            "pagePath": "pages/message/message-list",  
            "iconfont": {  
                "text": "\ue6a6",  
                "selectedText": "\ue6c1",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "消息"  
        }, {  
            "pagePath": "pages/ucenter/ucenter",  
            "iconfont": {  
                "text": "\ue699",  
                "selectedText": "\ue69d",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "我的"  
        }]  
    }

四、总结与展望

感谢DCloud提供的uni-app + uniCloud纯前端方案,我们公司2个前端工程师就把整个项目上线了,系统开发周期大幅缩短,并保证了多端体验的统一性。

展望未来: 我们正积极关注 uni-app x 对鸿蒙 NEXT 的支持进度,并计划将 CommunityHub 逐步迁移至 uni-app x,彻底实现原生渲染,以达到最终的性能目标。

同时,我们也在打磨该系统的标准化,希望发布到 DCloud插件市场,供更多社区使用,或者开源出来。

继续阅读 »

前言

公司前段时间接了一个社区管理的项目,看到本次 鸿蒙征文 活动,正好做一次总结。

我们基于 uni-app 跨端框架和 uniCloud Serverless服务,开发“CommunityHub 社区管理系统”。该系统旨在连接社区物业与居民。

我会在本文中重点阐述了如何利用 uni-app 实现应用在鸿蒙 OS 上的快速适配、多设备响应式布局、已经App和鸿蒙元服务共享代码。

一、技术选型与项目概述

1.1 社区数字化面临的挑战

接到需求后,我们分析,主要有两个挑战:

  • 多端适配: 社区服务需要覆盖居民的手机、平板乃至管理员的PC,且在当下中国,鸿蒙的兼容是必须考虑的平台,维护多套代码成本极高。
  • 后端运维: 社区系统,日常请求量并不高,但需满足特殊情况下的高并发处理,需要稳定且易运维的后端支持。

1.2 uni-app + uniCloud:理想的解决方案

我们选择了 uni-app + uniCloud 这一黄金组合作为 CommunityHub 的技术底座,侧重于开发效率和后端弹性:

  • uni-app:高效跨端开发,一次编码,多端发行,开发效率高。基于 Web 技术的快速适配和高效迭代能力,能够迅速发布应用,并确保基础体验。

  • uniCloud:Serverless 后端,免运维、弹性伸缩,提供数据安全保障。极简 API 调用,为前端提供了统一、稳定的数据接口。

二、鸿蒙 OS 适配与生态融合策略

要使 CommunityHub 在鸿蒙生态中获得成功,我们采取了“高效适配先行,生态融合跟进”的策略。

2.1 策略一:统一的响应式布局,适配多设备形态

作为社区管理系统,CommunityHub 需兼顾居民的手机端和管理员的 PC/平板大屏端体验,充分利用了 uni-app 强大的响应式能力。

设计理念: 利用 uni-app 的条件编译和 CSS 媒体查询功能,配合简洁的组件化设计,实现 UI 的自适应重构。

  • 手机小屏(<768px): 采用底部 TabBar 导航,信息流以列表卡片形式展示,突出点击和触控体验。
  • PC/平板大屏(>1200px): 通过leftWindow切换为侧边栏导航和分栏布局。例如“报修管理”页面,在大屏上可以同时显示报修列表、详情和派单操作区,极大提升了管理员的效率。

这种“一套代码,两种形态”的响应式开发策略,最大限度地复用了代码,为未来应用在鸿蒙多终端上的部署打下了坚实基础。

2.2 策略二:面向鸿蒙 NEXT 生态的融合与升级路径

1. 鸿蒙元服务的支持

优势: 得益于 uni-app 对鸿蒙生态的深度支持,标准 uni-app 项目可以直接发行到鸿蒙元服务。

这极大地简化了开发流程,实现了主 App 与元服务代码基础的统一。

  • 解决方案: 我们采用 “uni-app 快速编译 + uniCloud 数据驱动” 的DCloud全家桶策略。
  • 元服务开发: 在 uni-app 项目中,使用条件编译或单独页面,针对性地开发轻量级的“快捷报修提交”功能模块。
  • 快速部署与统一代码: 将该模块通过 uni-app 编译部署为鸿蒙元服务,确保其 UI 和逻辑与主 App 保持高度一致,并享受 uni-app 的快速迭代优势。
  • 效果: 实现了“一次编码,多端复用”在 App 和元服务上的实践,确保了“即用即走”的用户体验,同时将复杂数据和业务逻辑留在了 uniCloud 统一后端,极大提升了社区服务的便捷性和开发效率。

2. 紧急公告卡片(服务卡片)设想:

对于“停水通知”等紧急公告,我们规划利用鸿蒙的服务卡片能力,通过原生卡片实时拉取 uniCloud 的最新公告数据,直接在用户桌面上显示公告摘要,确保紧急消息的触达率。

参考资料:鸿蒙元服务专题

三、关键功能实现与 uniCloud 赋能

3.1 高效报修与权限控制

核心逻辑:

  • 权限与通知: 通过 uni-id 的角色体系(居民/物业/维修人员)校验身份,并将任务自动派发给对应角色的物业管理员。
  • 进度跟踪: 利用 uni-push,居民端能实时获取报修单状态(已派单、处理中、已完成)的变更,保证了用户体验的实时性。

3.2 邻里交流与资源共享的数据安全

“邻里圈”和“资源共享”模块利用 uniCloud 强大的安全能力:

我们使用 uniCloud 的 云存储 存储用户上传的动态图片和视频,利用 权限规则 确保居民之间数据隔离。

对于“私信联系”功能,基于 uni-id 实现了安全的用户身份认证和通信机制,保护用户隐私。

3.3 技术特点

本系统全部基于uni-app内置组件和uni-ui扩展组件实现,尽可能使用图标代替图片,减少资源包大小,提升小程序的冷启动时间。

如下是对tabbar的一个配置,我们使用了uniicons.ttf,没有使用任何额外图片。

"tabBar": {  
        "color": "#7A7E83",  
        "selectedColor": "#007AFF",  
        "borderStyle": "black",  
        "backgroundColor": "#FFFFFF",  
        "iconfontSrc": "uni_modules/uni-icons/components/uni-icons/uniicons.ttf",  
        "list": [{  
            "pagePath": "pages/home/home",  
            "iconfont": {  
                "text": "\ue662",  
                "selectedText": "\ue663",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "首页"  
        }, {  
            "pagePath": "pages/community/post-list",  
            "iconfont": {  
                "text": "\ue682",  
                "selectedText": "\ue682",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "邻里圈"  
        }, {  
            "pagePath": "pages/message/message-list",  
            "iconfont": {  
                "text": "\ue6a6",  
                "selectedText": "\ue6c1",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "消息"  
        }, {  
            "pagePath": "pages/ucenter/ucenter",  
            "iconfont": {  
                "text": "\ue699",  
                "selectedText": "\ue69d",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "我的"  
        }]  
    }

四、总结与展望

感谢DCloud提供的uni-app + uniCloud纯前端方案,我们公司2个前端工程师就把整个项目上线了,系统开发周期大幅缩短,并保证了多端体验的统一性。

展望未来: 我们正积极关注 uni-app x 对鸿蒙 NEXT 的支持进度,并计划将 CommunityHub 逐步迁移至 uni-app x,彻底实现原生渲染,以达到最终的性能目标。

同时,我们也在打磨该系统的标准化,希望发布到 DCloud插件市场,供更多社区使用,或者开源出来。

收起阅读 »

ffmpeg 16KB问题

16KB

最近google市场要求16KB内存对齐的问题,市场上大多数的ffmpeg包都不支持;
我找到了一个,请在android studio中的build.gradle中增加
implementation 'com.moizhassan.ffmpeg:ffmpeg-kit-16kb:6.0.0'
通过google脚本测试,该包已修复16KB问题;

附件是我用ffmpeg写好的音视频处理页面;

继续阅读 »

最近google市场要求16KB内存对齐的问题,市场上大多数的ffmpeg包都不支持;
我找到了一个,请在android studio中的build.gradle中增加
implementation 'com.moizhassan.ffmpeg:ffmpeg-kit-16kb:6.0.0'
通过google脚本测试,该包已修复16KB问题;

附件是我用ffmpeg写好的音视频处理页面;

收起阅读 »

HBuilder 上架 iOS 应用全流程指南:从云打包到开心上架(Appuploader)上传的跨平台发布实践

iOS

'''随着 uni-app 与 HBuilderX 的普及,越来越多的前端开发者开始进入移动应用开发领域。
借助 HBuilder 的云打包服务,开发者可以在不使用 Xcode 的情况下,快速生成 iOS 的 .ipa 包。

但问题随之而来:许多团队没有 Mac 电脑,也无法使用 Xcode 或 Transporter 完成 App Store 上传。
开心上架(Appuploader)能在 Windows / Linux / macOS 系统中直接上传 IPA,并支持证书创建、描述文件管理和多语言信息批量提交。

本文将演示:从 HBuilder 打包到 iOS 应用上架的全流程,并介绍如何通过 Appuploader 实现免 Mac 跨平台上架。


一、为什么选择 HBuilder 打包 iOS 应用?

HBuilder 是 DCloud 推出的跨平台开发工具,支持 HTML5、Vue、uni-app 等多框架项目,
通过 云打包服务 自动生成 Android APK 与 iOS IPA 包。

优势总结:

特点 说明
无需本地 Xcode 环境 由云端完成编译与签名
跨平台开发 前端技术栈(Vue + JS)快速上手
支持插件扩展 可集成本地 SDK 与原生模块
App Store 上架兼容 云打包输出的 IPA 可直接提交审核

对前端开发者而言,HBuilder 是通往原生应用开发与上架的理想桥梁。


二、HBuilder 云打包生成 IPA 文件

步骤 1:配置应用信息

在 HBuilderX 中打开项目,点击顶部菜单:

发行 → 云打包 → iOS 应用

填写以下信息:

  • 应用名称、Bundle ID(需与 Apple Developer 保持一致);
  • 图标、启动图;
  • 版本号、应用描述。

步骤 2:选择证书模式

HBuilder 支持两种方式:
使用自己的苹果证书(需上传 .p12 与描述文件);
使用 DCloud 提供的公用证书(仅用于测试,不建议用于正式上架)。

步骤 3:打包完成后下载 .ipa 文件

系统会生成一个可安装或上架的 iOS 安装包。

示例文件路径:

./unpackage/release/ios/APP_NAME.ipa

三、准备 App Store 上传所需条件

要将 IPA 上架到 App Store,需要以下三项内容:

项目 说明
Apple 开发者账号 年费 99 美元(个人或企业)
App 专用密码 上传时使用,保护主账号安全
应用元数据 名称、简介、截图、隐私政策等

若没有 Mac,可完全依靠开心上架(Appuploader)进行后续操作。


四、开心上架(Appuploader)简介与核心功能

新版 开心上架(Appuploader) 是一款跨平台的 iOS 应用上架工具,
可替代 Application Loader、Transporter 等官方工具,支持 GUI 与命令行双模式。
首页

核心特性:

功能 说明
跨平台支持 兼容 Windows、Linux、macOS
上传 IPA 直接将 IPA 文件提交 App Store Connect
证书生成与管理 支持开发、发布、推送证书一键生成
多语言与截图上传 批量上传多语言描述与截图
命令行模式 适合自动化部署与持续集成

五、使用开心上架上传 HBuilder 生成的 IPA 文件

图形界面方式(推荐给新手):

打开 开心上架;
登录 Apple 开发者账号;
点击「上传 IPA」;
选择打包生成的 .ipa 文件;
等待上传完成后,即可在 App Store Connect 中看到应用信息。
ipa上传

命令行方式(适合开发者):

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./unpackage/release/ios/myapp.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 要上传的 IPA 文件路径

执行结果:

  • 自动建立连接;
  • 上传并验证包体信息;
  • 输出上传日志与状态报告。

六、App Store Connect 审核流程简述

IPA 上传成功后,需在 App Store Connect 填写以下内容:

项目 说明
应用名称 上架显示名称
隐私政策 必填链接
截图 支持多设备尺寸上传
关键词与描述 提高搜索曝光
审核提交 点击“提交审核”按钮

审核时间:

  • 一般应用: 1–3 天;
  • 含支付、推送等功能: 3–7 天。

七、跨平台上架实践案例

某 uni-app 团队在 Windows 环境中使用以下流程完成 iOS 上架:

使用 HBuilder 云打包生成 .ipa
在 Appuploader 中创建 iOS 发布证书;
执行上传命令:

appuploader_cli -u ios@team.com -p xxxx-xxxx-xxxx -c 2 -f ./unpackage/release/ios/teamapp.ipa

登录 App Store Connect 填写资料并提交审核。

全流程无需 Mac,整个过程耗时不足两小时。


八、常见问题与解决方案

问题 原因 解决方案
上传报错 “Invalid Credentials” 密码错误 使用 App 专用密码
IPA 无法识别 打包方式不正确 使用正式证书重新打包
上传超时 网络不稳定 切换上传通道 -c 1/2
审核被拒 应用隐私不合规 补充隐私说明与权限用途
证书过期 签名证书已失效 在 Appuploader 中重新生成

九、结合 Fastlane 与 Appuploader 实现自动上架

对于团队项目,可进一步将 Appuploader 集成到 Fastlane 或 Jenkins CI 流程中,实现自动化上架。

示例命令:

fastlane gym --scheme "MyApp"  
appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyApp.ipa

优势:

  • 自动构建 + 上传;
  • 支持多版本号与自动日志记录;
  • 适用于 Windows 与 Linux CI 环境。

通过 HBuilder + 开心上架(Appuploader) 的组合,前端开发者与跨平台团队无需 Mac 电脑,也能高效完成 iOS 应用上架流程。

HBuilder 负责高效云打包,Appuploader 负责证书管理与上传发布,两者协同,构建出真正的 “跨系统、全自动化上架方案”。

从写代码到上架 App Store,你只需一台电脑,不必是 Mac。'''

继续阅读 »

'''随着 uni-app 与 HBuilderX 的普及,越来越多的前端开发者开始进入移动应用开发领域。
借助 HBuilder 的云打包服务,开发者可以在不使用 Xcode 的情况下,快速生成 iOS 的 .ipa 包。

但问题随之而来:许多团队没有 Mac 电脑,也无法使用 Xcode 或 Transporter 完成 App Store 上传。
开心上架(Appuploader)能在 Windows / Linux / macOS 系统中直接上传 IPA,并支持证书创建、描述文件管理和多语言信息批量提交。

本文将演示:从 HBuilder 打包到 iOS 应用上架的全流程,并介绍如何通过 Appuploader 实现免 Mac 跨平台上架。


一、为什么选择 HBuilder 打包 iOS 应用?

HBuilder 是 DCloud 推出的跨平台开发工具,支持 HTML5、Vue、uni-app 等多框架项目,
通过 云打包服务 自动生成 Android APK 与 iOS IPA 包。

优势总结:

特点 说明
无需本地 Xcode 环境 由云端完成编译与签名
跨平台开发 前端技术栈(Vue + JS)快速上手
支持插件扩展 可集成本地 SDK 与原生模块
App Store 上架兼容 云打包输出的 IPA 可直接提交审核

对前端开发者而言,HBuilder 是通往原生应用开发与上架的理想桥梁。


二、HBuilder 云打包生成 IPA 文件

步骤 1:配置应用信息

在 HBuilderX 中打开项目,点击顶部菜单:

发行 → 云打包 → iOS 应用

填写以下信息:

  • 应用名称、Bundle ID(需与 Apple Developer 保持一致);
  • 图标、启动图;
  • 版本号、应用描述。

步骤 2:选择证书模式

HBuilder 支持两种方式:
使用自己的苹果证书(需上传 .p12 与描述文件);
使用 DCloud 提供的公用证书(仅用于测试,不建议用于正式上架)。

步骤 3:打包完成后下载 .ipa 文件

系统会生成一个可安装或上架的 iOS 安装包。

示例文件路径:

./unpackage/release/ios/APP_NAME.ipa

三、准备 App Store 上传所需条件

要将 IPA 上架到 App Store,需要以下三项内容:

项目 说明
Apple 开发者账号 年费 99 美元(个人或企业)
App 专用密码 上传时使用,保护主账号安全
应用元数据 名称、简介、截图、隐私政策等

若没有 Mac,可完全依靠开心上架(Appuploader)进行后续操作。


四、开心上架(Appuploader)简介与核心功能

新版 开心上架(Appuploader) 是一款跨平台的 iOS 应用上架工具,
可替代 Application Loader、Transporter 等官方工具,支持 GUI 与命令行双模式。
首页

核心特性:

功能 说明
跨平台支持 兼容 Windows、Linux、macOS
上传 IPA 直接将 IPA 文件提交 App Store Connect
证书生成与管理 支持开发、发布、推送证书一键生成
多语言与截图上传 批量上传多语言描述与截图
命令行模式 适合自动化部署与持续集成

五、使用开心上架上传 HBuilder 生成的 IPA 文件

图形界面方式(推荐给新手):

打开 开心上架;
登录 Apple 开发者账号;
点击「上传 IPA」;
选择打包生成的 .ipa 文件;
等待上传完成后,即可在 App Store Connect 中看到应用信息。
ipa上传

命令行方式(适合开发者):

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./unpackage/release/ios/myapp.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 要上传的 IPA 文件路径

执行结果:

  • 自动建立连接;
  • 上传并验证包体信息;
  • 输出上传日志与状态报告。

六、App Store Connect 审核流程简述

IPA 上传成功后,需在 App Store Connect 填写以下内容:

项目 说明
应用名称 上架显示名称
隐私政策 必填链接
截图 支持多设备尺寸上传
关键词与描述 提高搜索曝光
审核提交 点击“提交审核”按钮

审核时间:

  • 一般应用: 1–3 天;
  • 含支付、推送等功能: 3–7 天。

七、跨平台上架实践案例

某 uni-app 团队在 Windows 环境中使用以下流程完成 iOS 上架:

使用 HBuilder 云打包生成 .ipa
在 Appuploader 中创建 iOS 发布证书;
执行上传命令:

appuploader_cli -u ios@team.com -p xxxx-xxxx-xxxx -c 2 -f ./unpackage/release/ios/teamapp.ipa

登录 App Store Connect 填写资料并提交审核。

全流程无需 Mac,整个过程耗时不足两小时。


八、常见问题与解决方案

问题 原因 解决方案
上传报错 “Invalid Credentials” 密码错误 使用 App 专用密码
IPA 无法识别 打包方式不正确 使用正式证书重新打包
上传超时 网络不稳定 切换上传通道 -c 1/2
审核被拒 应用隐私不合规 补充隐私说明与权限用途
证书过期 签名证书已失效 在 Appuploader 中重新生成

九、结合 Fastlane 与 Appuploader 实现自动上架

对于团队项目,可进一步将 Appuploader 集成到 Fastlane 或 Jenkins CI 流程中,实现自动化上架。

示例命令:

fastlane gym --scheme "MyApp"  
appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyApp.ipa

优势:

  • 自动构建 + 上传;
  • 支持多版本号与自动日志记录;
  • 适用于 Windows 与 Linux CI 环境。

通过 HBuilder + 开心上架(Appuploader) 的组合,前端开发者与跨平台团队无需 Mac 电脑,也能高效完成 iOS 应用上架流程。

HBuilder 负责高效云打包,Appuploader 负责证书管理与上传发布,两者协同,构建出真正的 “跨系统、全自动化上架方案”。

从写代码到上架 App Store,你只需一台电脑,不必是 Mac。'''

收起阅读 »

关于使用Map组件截图技术方案

截图 地图 map

看网上好多技术方案都是使用集成原生插件,然后在原生插件里面生成截图,而且uniapp插件库里面的好几个这样的插件都需要收费。
如果大家对图片要求不高的话可以关注一些“天地图“的api,他们有根据坐标生成静态图片的接口,下面是链接地址
http://lbs.tianditu.gov.cn/staticapi/static.html
本人亲测可用

继续阅读 »

看网上好多技术方案都是使用集成原生插件,然后在原生插件里面生成截图,而且uniapp插件库里面的好几个这样的插件都需要收费。
如果大家对图片要求不高的话可以关注一些“天地图“的api,他们有根据坐标生成静态图片的接口,下面是链接地址
http://lbs.tianditu.gov.cn/staticapi/static.html
本人亲测可用

收起阅读 »

【报Bug】uni @touchmove 事件与 下拉刷新事件冲突

touchmove

【报Bug】uni @touchmove 事件与 下拉刷新事件冲突

这么多年的bug了为啥不解决呢, 很影响体验啊。

【报Bug】uni @touchmove 事件与 下拉刷新事件冲突

这么多年的bug了为啥不解决呢, 很影响体验啊。

UniApp开发鸿蒙应用实践:从底部小白条到代理提醒的完整解决方案

鸿蒙next 鸿蒙征文

前言

随着鸿蒙生态的快速发展,越来越多的开发者开始关注如何快速将现有应用适配到鸿蒙平台。UniApp作为一个成熟的跨平台开发框架,已经支持了鸿蒙应用的开发。然而在实际开发过程中,我们会遇到一些平台特有的问题和挑战。本文将基于一个服药提醒应用的开发实践,详细介绍如何解决鸿蒙平台的底部安全区适配问题,以及如何使用UTS插件实现代理提醒功能。

一、底部小白条问题的解决方案(SafeArea适配)

1.1 问题背景

在鸿蒙设备上,特别是全面屏设备,底部通常会有一个用于手势导航的"小白条"区域。这个区域被称为安全区域(Safe Area),如果应用界面没有正确处理这个区域,会导致底部内容被遮挡,影响用户体验。

1.2 SafeArea配置方案

UniApp为鸿蒙平台提供了完善的安全区域配置方案。我们需要在manifest.json文件中进行配置:

{  
  "app-harmony": {  
    "safearea": {  
      "background": "#ffffff",  
      "backgroundDark": "#2f0508",  
      "bottom": {  
        "offset": "none"  
      }  
    }  
  }  
}

配置说明:

  • background: 浅色模式下安全区域的背景色,这里设置为白色#ffffff,与应用主题保持一致
  • backgroundDark: 深色模式下安全区域的背景色,设置为深红色#2f0508,适配暗黑主题
  • bottom.offset: 底部区域占位策略,设置为"none"表示在没有TabBar时不需要占位

我们成功解决了底部小白条的适配问题,确保应用在各种鸿蒙设备上都能正常显示和交互。

二、代理提醒功能的UTS插件实现

2.1 代理提醒简介

代理提醒(Reminder Agent)是鸿蒙系统提供的一项重要能力,允许应用在后台设置定时提醒,即使应用被关闭也能准时触发。这对于服药提醒、日程管理等场景至关重要。

鸿蒙系统的代理提醒支持三种类型:

  • 闹钟提醒(ALARM): 基于时间的周期性提醒,适合每天固定时间的场景
  • 日历提醒(CALENDAR): 基于日期的事件提醒,适合特定日期的事件
  • 倒计时提醒(TIMER): 基于时长的一次性提醒,适合短期计时场景

2.2 为什么需要UTS插件

UniApp虽然支持鸿蒙平台,但并未内置代理提醒的API。要使用鸿蒙的原生能力,我们需要通过UTS(UniApp TypeScript)插件来调用鸿蒙的原生API。

UTS插件的优势:

  • 类型安全: 基于TypeScript,提供完整的类型检查
  • 性能优秀: 编译为原生代码,性能接近原生开发
  • 开发便捷: 语法接近TypeScript,学习成本低
  • 跨平台: 可以为不同平台编写不同的实现

2.3 UTS插件项目结构

我们创建了一个名为uni-reminder的UTS插件,项目结构如下:

uni_modules/  
└── uni-reminder/  
    ├── utssdk/  
    │   ├── app-harmony/        # 鸿蒙平台实现  
    │   │   └── index.uts       # 主要实现文件  
    │   └── interface.uts       # 接口定义文件  
    ├── package.json  
    └── readme.md

关键文件说明:

  • interface.uts: 定义插件的接口类型,包括所有方法的参数和回调类型
  • app-harmony/index.uts: 鸿蒙平台的具体实现,调用鸿蒙原生API

2.4 接口定义(interface.uts)

首先,我们需要定义清晰的接口类型。以闹钟提醒为例:

// 闹钟提醒选项  
export type PublishAlarmReminderOptions = {  
  hour: number                    // 闹钟小时数(0-23)  
  minute: number                  // 闹钟分钟数(0-59)  
  daysOfWeek?: Array<number> | null  // 重复日期,周日=0  
  title?: string | null           // 提醒标题  
  content?: string | null         // 提醒内容  
  success?: PublishAlarmReminderSuccessCallback | null  
  fail?: PublishAlarmReminderFailCallback | null  
  complete?: PublishAlarmReminderCompleteCallback | null  
}  

// 成功回调返回值  
export type PublishAlarmReminderSuccess = {  
  errMsg: string  
  reminderId: number  // 提醒ID,用于后续取消  
}  

// 定义uni对象上的方法  
export interface Uni {  
  publishAlarmReminder(options: PublishAlarmReminderOptions): void  
  publishCalendarReminder(options: PublishCalendarReminderOptions): void  
  publishTimerReminder(options: PublishTimerReminderOptions): void  
  cancelReminder(options: CancelReminderOptions): void  
  cancelAllReminders(options: CancelAllRemindersOptions): void  
  getValidReminders(options: GetValidRemindersOptions): void  
}

这种接口定义方式遵循了UniApp的API设计规范,使用success/fail/complete回调模式,保持了与UniApp其他API的一致性。

2.5 鸿蒙平台核心实现

2.5.1 导入鸿蒙原生API

import reminderAgentManager from '@ohos.reminderAgentManager'  
import { BusinessError } from '@kit.BasicServicesKit'
  • reminderAgentManager: 鸿蒙的代理提醒管理器
  • BusinessError: 鸿蒙的业务错误类型

2.5.2 实现闹钟提醒

export function publishAlarmReminder(options: PublishAlarmReminderOptions): void {  
  try {  
    // 构建闹钟提醒请求对象  
    const requestObj = {  
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,  
      hour: options.hour,  
      minute: options.minute,  
      title: options.title || '闹钟提醒',  
      content: options.content || '该起床了',  
      wantAgent: {  
        pkgName: 'com.liudd1.anshichiyao',  
        abilityName: 'EntryAbility'  
      },  
      actionButton: [{  
        title: '关闭',  
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE  
      }]  
    } as UTSJSONObject  

    // 设置重复日期  
    if (options.daysOfWeek && options.daysOfWeek.length > 0) {  
      requestObj['daysOfWeek'] = options.daysOfWeek  
    }  

    // 发布提醒  
    reminderAgentManager.publishReminder(  
      requestObj as unknown as reminderAgentManager.ReminderRequest  
    )  
      .then((reminderId: number) => {  
        options?.success?.({  
          errMsg: 'publishAlarmReminder:ok',  
          reminderId: reminderId  
        })  
        options?.complete?.({ errMsg: 'publishAlarmReminder:ok' })  
      })  
      .catch((err: BusinessError) => {  
        options?.fail?.({ errMsg: `publishAlarmReminder:fail ${err.message}` })  
        options?.complete?.({ errMsg: `publishAlarmReminder:fail ${err.message}` })  
      })  
  } catch (err) {  
    const error = err as BusinessError  
    options?.fail?.({ errMsg: `publishAlarmReminder:fail ${error.message}` })  
    options?.complete?.({ errMsg: `publishAlarmReminder:fail ${error.message}` })  
  }  
}

实现要点:

  1. reminderType: 指定提醒类型为闹钟
  2. wantAgent: 配置点击提醒后要启动的应用和页面
  3. actionButton: 添加操作按钮
  4. daysOfWeek: 可选的重复日期数组
  5. 错误处理: 使用try-catch和Promise.catch双重错误处理

2.5.3 实现日历提醒

export function publishCalendarReminder(options: PublishCalendarReminderOptions): void {  
  try {  
    const date = new Date(options.dateTime)  

    const requestObj = {  
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,  
      dateTime: {  
        year: date.getFullYear(),  
        month: date.getMonth() + 1,  
        day: date.getDate(),  
        hour: date.getHours(),  
        minute: date.getMinutes()  
      },  
      title: options.title || '日历提醒',  
      content: options.content || '事件提醒',  
      wantAgent: {  
        pkgName: 'com.liudd1.anshichiyao',  
        abilityName: 'EntryAbility'  
      },  
      actionButton: [{  
        title: '关闭',  
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE  
      }]  
    } as UTSJSONObject  

    // 设置重复月份和日期  
    if (options.repeatMonths && options.repeatMonths.length > 0) {  
      requestObj['repeatMonths'] = options.repeatMonths  
    }  
    if (options.repeatDays && options.repeatDays.length > 0) {  
      requestObj['repeatDays'] = options.repeatDays  
    }  

    // 发布提醒(Promise处理逻辑同闹钟提醒)  
    // ...  
  } catch (err) {  
    // 错误处理  
  }  
}

2.5.4 实现倒计时提醒

export function publishTimerReminder(options: PublishTimerReminderOptions): void {  
  try {  
    const requestObj = {  
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER,  
      triggerTimeInSeconds: options.triggerTimeInSeconds,  
      title: options.title || '倒计时提醒',  
      content: options.content || '倒计时已到',  
      wantAgent: {  
        pkgName: 'com.liudd1.anshichiyao',  
        abilityName: 'EntryAbility'  
      },  
      actionButton: [{  
        title: '关闭',  
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE  
      }]  
    } as UTSJSONObject  

    // 发布提醒  
    // ...  
  } catch (err) {  
    // 错误处理  
  }  
}

2.6 权限配置

使用代理提醒功能需要在manifest.json中声明权限:

{  
  "app-harmony": {  
    "permissions": [  
      "ohos.permission.PUBLISH_AGENT_REMINDER"  
    ]  
  }  
}

同时配合通知权限插件请求用户授权:

uni.requestNotification({  
  success: (res) => {  
    if (res.granted) {  
      console.log('通知权限已获取')  
    }  
  }  
})

2.7 在应用中使用插件

插件开发完成后,在应用中的使用非常简单:

export default {  
  data() {  
    return {  
      reminderIds: []  
    }  
  },  
  methods: {  
    // 设置每天早上8点的服药提醒  
    setMedicineReminder() {  
      uni.publishAlarmReminder({  
        hour: 8,  
        minute: 0,  
        daysOfWeek: [1, 2, 3, 4, 5, 6, 0],  
        title: '服药提醒',  
        content: '该吃药了,记得按时服药哦!',  
        success: (res) => {  
          console.log('提醒设置成功,ID:', res.reminderId)  
          this.reminderIds.push(res.reminderId)  
          uni.showToast({ title: '提醒设置成功' })  
        },  
        fail: (err) => {  
          console.error('提醒设置失败:', err.errMsg)  
        }  
      })  
    },  

    // 设置30分钟后的倒计时提醒  
    setTimerReminder() {  
      uni.publishTimerReminder({  
        triggerTimeInSeconds: 30 * 60,  
        title: '服药倒计时',  
        content: '30分钟已到,该服药了',  
        success: (res) => {  
          this.reminderIds.push(res.reminderId)  
        }  
      })  
    },  

    // 取消所有提醒  
    cancelAllReminders() {  
      uni.cancelAllReminders({  
        success: () => {  
          this.reminderIds = []  
          uni.showToast({ title: '已清空所有提醒' })  
        }  
      })  
    }  
  }  
}

希望本文的分享能够帮助到正在开发鸿蒙应用的开发者们,让我们一起为鸿蒙生态的繁荣贡献力量!

继续阅读 »

前言

随着鸿蒙生态的快速发展,越来越多的开发者开始关注如何快速将现有应用适配到鸿蒙平台。UniApp作为一个成熟的跨平台开发框架,已经支持了鸿蒙应用的开发。然而在实际开发过程中,我们会遇到一些平台特有的问题和挑战。本文将基于一个服药提醒应用的开发实践,详细介绍如何解决鸿蒙平台的底部安全区适配问题,以及如何使用UTS插件实现代理提醒功能。

一、底部小白条问题的解决方案(SafeArea适配)

1.1 问题背景

在鸿蒙设备上,特别是全面屏设备,底部通常会有一个用于手势导航的"小白条"区域。这个区域被称为安全区域(Safe Area),如果应用界面没有正确处理这个区域,会导致底部内容被遮挡,影响用户体验。

1.2 SafeArea配置方案

UniApp为鸿蒙平台提供了完善的安全区域配置方案。我们需要在manifest.json文件中进行配置:

{  
  "app-harmony": {  
    "safearea": {  
      "background": "#ffffff",  
      "backgroundDark": "#2f0508",  
      "bottom": {  
        "offset": "none"  
      }  
    }  
  }  
}

配置说明:

  • background: 浅色模式下安全区域的背景色,这里设置为白色#ffffff,与应用主题保持一致
  • backgroundDark: 深色模式下安全区域的背景色,设置为深红色#2f0508,适配暗黑主题
  • bottom.offset: 底部区域占位策略,设置为"none"表示在没有TabBar时不需要占位

我们成功解决了底部小白条的适配问题,确保应用在各种鸿蒙设备上都能正常显示和交互。

二、代理提醒功能的UTS插件实现

2.1 代理提醒简介

代理提醒(Reminder Agent)是鸿蒙系统提供的一项重要能力,允许应用在后台设置定时提醒,即使应用被关闭也能准时触发。这对于服药提醒、日程管理等场景至关重要。

鸿蒙系统的代理提醒支持三种类型:

  • 闹钟提醒(ALARM): 基于时间的周期性提醒,适合每天固定时间的场景
  • 日历提醒(CALENDAR): 基于日期的事件提醒,适合特定日期的事件
  • 倒计时提醒(TIMER): 基于时长的一次性提醒,适合短期计时场景

2.2 为什么需要UTS插件

UniApp虽然支持鸿蒙平台,但并未内置代理提醒的API。要使用鸿蒙的原生能力,我们需要通过UTS(UniApp TypeScript)插件来调用鸿蒙的原生API。

UTS插件的优势:

  • 类型安全: 基于TypeScript,提供完整的类型检查
  • 性能优秀: 编译为原生代码,性能接近原生开发
  • 开发便捷: 语法接近TypeScript,学习成本低
  • 跨平台: 可以为不同平台编写不同的实现

2.3 UTS插件项目结构

我们创建了一个名为uni-reminder的UTS插件,项目结构如下:

uni_modules/  
└── uni-reminder/  
    ├── utssdk/  
    │   ├── app-harmony/        # 鸿蒙平台实现  
    │   │   └── index.uts       # 主要实现文件  
    │   └── interface.uts       # 接口定义文件  
    ├── package.json  
    └── readme.md

关键文件说明:

  • interface.uts: 定义插件的接口类型,包括所有方法的参数和回调类型
  • app-harmony/index.uts: 鸿蒙平台的具体实现,调用鸿蒙原生API

2.4 接口定义(interface.uts)

首先,我们需要定义清晰的接口类型。以闹钟提醒为例:

// 闹钟提醒选项  
export type PublishAlarmReminderOptions = {  
  hour: number                    // 闹钟小时数(0-23)  
  minute: number                  // 闹钟分钟数(0-59)  
  daysOfWeek?: Array<number> | null  // 重复日期,周日=0  
  title?: string | null           // 提醒标题  
  content?: string | null         // 提醒内容  
  success?: PublishAlarmReminderSuccessCallback | null  
  fail?: PublishAlarmReminderFailCallback | null  
  complete?: PublishAlarmReminderCompleteCallback | null  
}  

// 成功回调返回值  
export type PublishAlarmReminderSuccess = {  
  errMsg: string  
  reminderId: number  // 提醒ID,用于后续取消  
}  

// 定义uni对象上的方法  
export interface Uni {  
  publishAlarmReminder(options: PublishAlarmReminderOptions): void  
  publishCalendarReminder(options: PublishCalendarReminderOptions): void  
  publishTimerReminder(options: PublishTimerReminderOptions): void  
  cancelReminder(options: CancelReminderOptions): void  
  cancelAllReminders(options: CancelAllRemindersOptions): void  
  getValidReminders(options: GetValidRemindersOptions): void  
}

这种接口定义方式遵循了UniApp的API设计规范,使用success/fail/complete回调模式,保持了与UniApp其他API的一致性。

2.5 鸿蒙平台核心实现

2.5.1 导入鸿蒙原生API

import reminderAgentManager from '@ohos.reminderAgentManager'  
import { BusinessError } from '@kit.BasicServicesKit'
  • reminderAgentManager: 鸿蒙的代理提醒管理器
  • BusinessError: 鸿蒙的业务错误类型

2.5.2 实现闹钟提醒

export function publishAlarmReminder(options: PublishAlarmReminderOptions): void {  
  try {  
    // 构建闹钟提醒请求对象  
    const requestObj = {  
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,  
      hour: options.hour,  
      minute: options.minute,  
      title: options.title || '闹钟提醒',  
      content: options.content || '该起床了',  
      wantAgent: {  
        pkgName: 'com.liudd1.anshichiyao',  
        abilityName: 'EntryAbility'  
      },  
      actionButton: [{  
        title: '关闭',  
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE  
      }]  
    } as UTSJSONObject  

    // 设置重复日期  
    if (options.daysOfWeek && options.daysOfWeek.length > 0) {  
      requestObj['daysOfWeek'] = options.daysOfWeek  
    }  

    // 发布提醒  
    reminderAgentManager.publishReminder(  
      requestObj as unknown as reminderAgentManager.ReminderRequest  
    )  
      .then((reminderId: number) => {  
        options?.success?.({  
          errMsg: 'publishAlarmReminder:ok',  
          reminderId: reminderId  
        })  
        options?.complete?.({ errMsg: 'publishAlarmReminder:ok' })  
      })  
      .catch((err: BusinessError) => {  
        options?.fail?.({ errMsg: `publishAlarmReminder:fail ${err.message}` })  
        options?.complete?.({ errMsg: `publishAlarmReminder:fail ${err.message}` })  
      })  
  } catch (err) {  
    const error = err as BusinessError  
    options?.fail?.({ errMsg: `publishAlarmReminder:fail ${error.message}` })  
    options?.complete?.({ errMsg: `publishAlarmReminder:fail ${error.message}` })  
  }  
}

实现要点:

  1. reminderType: 指定提醒类型为闹钟
  2. wantAgent: 配置点击提醒后要启动的应用和页面
  3. actionButton: 添加操作按钮
  4. daysOfWeek: 可选的重复日期数组
  5. 错误处理: 使用try-catch和Promise.catch双重错误处理

2.5.3 实现日历提醒

export function publishCalendarReminder(options: PublishCalendarReminderOptions): void {  
  try {  
    const date = new Date(options.dateTime)  

    const requestObj = {  
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,  
      dateTime: {  
        year: date.getFullYear(),  
        month: date.getMonth() + 1,  
        day: date.getDate(),  
        hour: date.getHours(),  
        minute: date.getMinutes()  
      },  
      title: options.title || '日历提醒',  
      content: options.content || '事件提醒',  
      wantAgent: {  
        pkgName: 'com.liudd1.anshichiyao',  
        abilityName: 'EntryAbility'  
      },  
      actionButton: [{  
        title: '关闭',  
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE  
      }]  
    } as UTSJSONObject  

    // 设置重复月份和日期  
    if (options.repeatMonths && options.repeatMonths.length > 0) {  
      requestObj['repeatMonths'] = options.repeatMonths  
    }  
    if (options.repeatDays && options.repeatDays.length > 0) {  
      requestObj['repeatDays'] = options.repeatDays  
    }  

    // 发布提醒(Promise处理逻辑同闹钟提醒)  
    // ...  
  } catch (err) {  
    // 错误处理  
  }  
}

2.5.4 实现倒计时提醒

export function publishTimerReminder(options: PublishTimerReminderOptions): void {  
  try {  
    const requestObj = {  
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER,  
      triggerTimeInSeconds: options.triggerTimeInSeconds,  
      title: options.title || '倒计时提醒',  
      content: options.content || '倒计时已到',  
      wantAgent: {  
        pkgName: 'com.liudd1.anshichiyao',  
        abilityName: 'EntryAbility'  
      },  
      actionButton: [{  
        title: '关闭',  
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE  
      }]  
    } as UTSJSONObject  

    // 发布提醒  
    // ...  
  } catch (err) {  
    // 错误处理  
  }  
}

2.6 权限配置

使用代理提醒功能需要在manifest.json中声明权限:

{  
  "app-harmony": {  
    "permissions": [  
      "ohos.permission.PUBLISH_AGENT_REMINDER"  
    ]  
  }  
}

同时配合通知权限插件请求用户授权:

uni.requestNotification({  
  success: (res) => {  
    if (res.granted) {  
      console.log('通知权限已获取')  
    }  
  }  
})

2.7 在应用中使用插件

插件开发完成后,在应用中的使用非常简单:

export default {  
  data() {  
    return {  
      reminderIds: []  
    }  
  },  
  methods: {  
    // 设置每天早上8点的服药提醒  
    setMedicineReminder() {  
      uni.publishAlarmReminder({  
        hour: 8,  
        minute: 0,  
        daysOfWeek: [1, 2, 3, 4, 5, 6, 0],  
        title: '服药提醒',  
        content: '该吃药了,记得按时服药哦!',  
        success: (res) => {  
          console.log('提醒设置成功,ID:', res.reminderId)  
          this.reminderIds.push(res.reminderId)  
          uni.showToast({ title: '提醒设置成功' })  
        },  
        fail: (err) => {  
          console.error('提醒设置失败:', err.errMsg)  
        }  
      })  
    },  

    // 设置30分钟后的倒计时提醒  
    setTimerReminder() {  
      uni.publishTimerReminder({  
        triggerTimeInSeconds: 30 * 60,  
        title: '服药倒计时',  
        content: '30分钟已到,该服药了',  
        success: (res) => {  
          this.reminderIds.push(res.reminderId)  
        }  
      })  
    },  

    // 取消所有提醒  
    cancelAllReminders() {  
      uni.cancelAllReminders({  
        success: () => {  
          this.reminderIds = []  
          uni.showToast({ title: '已清空所有提醒' })  
        }  
      })  
    }  
  }  
}

希望本文的分享能够帮助到正在开发鸿蒙应用的开发者们,让我们一起为鸿蒙生态的繁荣贡献力量!

收起阅读 »

解决uniapp打包h5刷新页面无法返回上一级页面的问题

h5环境直接拦截默认的返回方法,使用router自带的返回

拦截默认的pageHead的返回方法,判断整个页面的history

import Vue from 'vue'  
if (process.env.VUE_APP_PLATFORM === 'h5') {  
  // 替代默认的返回api  
  uni.navigateBack = function (params: any) {  
    let canBack = true  
    const pages = getCurrentPages()  
    let delta = params?.delta  
    if (typeof delta !== 'number') delta = 1  

    const router = getApp().$router  
    if (pages.length) {  
      const from = 'navigateBack'  
      function hasLifecycleHook(options: any, hook: string) {  
        return Array.isArray(options[hook]) && options[hook].length  
      }  
      const page = pages[pages.length - 1] as any  
      if (  
        hasLifecycleHook(page.$options, 'onBackPress') &&  
        page.__call_hook('onBackPress', {  
          from,  
        }) === true  
      ) {  
        canBack = false  
      }  
    }  
    if (canBack) {  
      if (delta > 1) {  
        router._$delta = delta  
      }  
      router.go(-delta, {  
        animationType: '',  
        animationDuration: '',  
      })  
    }  
  }  
  // 修复 自带的pageHead 刷新点击右上角回到首页  
  const Page = Vue.component('Page')  
  const PageHead = Page.component('PageHead')  
  // @ts-ignore  
  PageHead.methods._back = function () {  
    if (history.length === 1) {  
      return uni.reLaunch({  
        url: '/',  
      })  
    } else {  
      return uni.navigateBack({  
        // @ts-ignore  
        from: 'backbutton',  
      })  
    }  
  }  
}  
继续阅读 »

h5环境直接拦截默认的返回方法,使用router自带的返回

拦截默认的pageHead的返回方法,判断整个页面的history

import Vue from 'vue'  
if (process.env.VUE_APP_PLATFORM === 'h5') {  
  // 替代默认的返回api  
  uni.navigateBack = function (params: any) {  
    let canBack = true  
    const pages = getCurrentPages()  
    let delta = params?.delta  
    if (typeof delta !== 'number') delta = 1  

    const router = getApp().$router  
    if (pages.length) {  
      const from = 'navigateBack'  
      function hasLifecycleHook(options: any, hook: string) {  
        return Array.isArray(options[hook]) && options[hook].length  
      }  
      const page = pages[pages.length - 1] as any  
      if (  
        hasLifecycleHook(page.$options, 'onBackPress') &&  
        page.__call_hook('onBackPress', {  
          from,  
        }) === true  
      ) {  
        canBack = false  
      }  
    }  
    if (canBack) {  
      if (delta > 1) {  
        router._$delta = delta  
      }  
      router.go(-delta, {  
        animationType: '',  
        animationDuration: '',  
      })  
    }  
  }  
  // 修复 自带的pageHead 刷新点击右上角回到首页  
  const Page = Vue.component('Page')  
  const PageHead = Page.component('PageHead')  
  // @ts-ignore  
  PageHead.methods._back = function () {  
    if (history.length === 1) {  
      return uni.reLaunch({  
        url: '/',  
      })  
    } else {  
      return uni.navigateBack({  
        // @ts-ignore  
        from: 'backbutton',  
      })  
    }  
  }  
}  
收起阅读 »

分片上传、文件分片,使用Html5Plus API。测试对比MD5。

兄弟们,我在看掘金的时候找到分片上传失败的原因了,就很小的一个点:

html5Plus 的文档写的不太清楚出了,很多地方让人猜。就比如File.slice(start,end) ,在js中slice截取不包含end,但在Html5Plus File.slice包含end,导致合并文件后总是比原文件大一点

也就是使用Html5Plus的File.slice方法:每个分片end-1
已经在安卓模拟器上测试,并对比MD5值。

因为没有 readAsArrayBuffer 方法,只能通过 Base64 转 ArrayBuffer,需要注意去除dataUrl头部
关键代码

const readFileChunk = (file, start, end) => {  
    return new Promise((resolve, reject) => {  
        try {  
            const reader = new plus.io.FileReader();  
            reader.onload = () => {  
                try {  
                    // 排除base64头部  
                    const base64 = reader.result.substring(reader.result.indexOf(',')   1);  
                    resolve(uni.base64ToArrayBuffer(base64));  
                } catch (e) {  
                    reject(e)  
                }  
            };  
            reader.onerror = reject;  

            // 使用slice方法读取指定范围, 需要注意: end 包含  
            const blob = file.slice(start, end);  
            reader.readAsDataURL(blob); // HTML5 需要转换为base64读取  
        } catch (e) {  
            reject(e)  
        }  
    });  
};

完整的分片上传

    async uploadMultipart(filePath, chunkSize = 1024 * 1024 * 5) {  

        // 通过文件路径获取File对象(Html5Plus的File)  
        const file = await getFile(filePath);  
        const uploadId = await multipartUploadApi.startMultipart(file.name)  
        // console.log('uploadId', uploadId)  

        const partList = []  
        const totalSize = file.size;  
        for (let i = 0; i < totalSize / chunkSize; i  ) {  
            const start = i * chunkSize;  
            const end = Math.min((i   1) * chunkSize - 1, totalSize);  
            const chunkBuffer = await readFileChunk(file, start, end);  
            const partItem = await multipartUploadApi.uploadPart(uploadId, i   1, chunkBuffer)  
            partList.push(partItem)  
        }  
        const result = await multipartUploadApi.complete(uploadId, partList)  

        return result  
    },  

    getFile(filePath) {  
        return new Promise((resolve, reject) => {  
            plus.io.resolveLocalFileSystemURL(filePath, (entry) => {  
                if (!entry.isFile) {  
                    reject(new Error(`不是文件:${filePath}`))  
                    return  
                }  
                entry.file((file) => {  
                    resolve(file)  
                }, (err) => {  
                    // console.log('无法获取文件')  
                    reject(err)  
                })  
            }, (error) => {  
                // console.log('文件不存在', error)  
                reject(error)  
            })  
        })  
    },  
  readFileChunk (file, start, end) {  
    return new Promise((resolve, reject) => {  
        try {  
            const reader = new plus.io.FileReader();  
            reader.onload = () => {  
                try {  
                    // 排除base64头部  
                    const base64 = reader.result.substring(reader.result.indexOf(',')   1);  
                    resolve(uni.base64ToArrayBuffer(base64));  
                } catch (e) {  
                    reject(e)  
                }  
            };  
            reader.onerror = reject;  

            // 使用slice方法读取指定范围, 需要注意: end 包含  
            const blob = file.slice(start, end);  
            reader.readAsDataURL(blob); // HTML5 需要转换为base64读取  
        } catch (e) {  
            reject(e)  
        }  
    });  
};

参考的掘金文章:
解决Uniapp中文件切片问题
作者:Synmbrf
链接:https://juejin.cn/post/7493783786707058726
来源:稀土掘金

继续阅读 »

兄弟们,我在看掘金的时候找到分片上传失败的原因了,就很小的一个点:

html5Plus 的文档写的不太清楚出了,很多地方让人猜。就比如File.slice(start,end) ,在js中slice截取不包含end,但在Html5Plus File.slice包含end,导致合并文件后总是比原文件大一点

也就是使用Html5Plus的File.slice方法:每个分片end-1
已经在安卓模拟器上测试,并对比MD5值。

因为没有 readAsArrayBuffer 方法,只能通过 Base64 转 ArrayBuffer,需要注意去除dataUrl头部
关键代码

const readFileChunk = (file, start, end) => {  
    return new Promise((resolve, reject) => {  
        try {  
            const reader = new plus.io.FileReader();  
            reader.onload = () => {  
                try {  
                    // 排除base64头部  
                    const base64 = reader.result.substring(reader.result.indexOf(',')   1);  
                    resolve(uni.base64ToArrayBuffer(base64));  
                } catch (e) {  
                    reject(e)  
                }  
            };  
            reader.onerror = reject;  

            // 使用slice方法读取指定范围, 需要注意: end 包含  
            const blob = file.slice(start, end);  
            reader.readAsDataURL(blob); // HTML5 需要转换为base64读取  
        } catch (e) {  
            reject(e)  
        }  
    });  
};

完整的分片上传

    async uploadMultipart(filePath, chunkSize = 1024 * 1024 * 5) {  

        // 通过文件路径获取File对象(Html5Plus的File)  
        const file = await getFile(filePath);  
        const uploadId = await multipartUploadApi.startMultipart(file.name)  
        // console.log('uploadId', uploadId)  

        const partList = []  
        const totalSize = file.size;  
        for (let i = 0; i < totalSize / chunkSize; i  ) {  
            const start = i * chunkSize;  
            const end = Math.min((i   1) * chunkSize - 1, totalSize);  
            const chunkBuffer = await readFileChunk(file, start, end);  
            const partItem = await multipartUploadApi.uploadPart(uploadId, i   1, chunkBuffer)  
            partList.push(partItem)  
        }  
        const result = await multipartUploadApi.complete(uploadId, partList)  

        return result  
    },  

    getFile(filePath) {  
        return new Promise((resolve, reject) => {  
            plus.io.resolveLocalFileSystemURL(filePath, (entry) => {  
                if (!entry.isFile) {  
                    reject(new Error(`不是文件:${filePath}`))  
                    return  
                }  
                entry.file((file) => {  
                    resolve(file)  
                }, (err) => {  
                    // console.log('无法获取文件')  
                    reject(err)  
                })  
            }, (error) => {  
                // console.log('文件不存在', error)  
                reject(error)  
            })  
        })  
    },  
  readFileChunk (file, start, end) {  
    return new Promise((resolve, reject) => {  
        try {  
            const reader = new plus.io.FileReader();  
            reader.onload = () => {  
                try {  
                    // 排除base64头部  
                    const base64 = reader.result.substring(reader.result.indexOf(',')   1);  
                    resolve(uni.base64ToArrayBuffer(base64));  
                } catch (e) {  
                    reject(e)  
                }  
            };  
            reader.onerror = reject;  

            // 使用slice方法读取指定范围, 需要注意: end 包含  
            const blob = file.slice(start, end);  
            reader.readAsDataURL(blob); // HTML5 需要转换为base64读取  
        } catch (e) {  
            reject(e)  
        }  
    });  
};

参考的掘金文章:
解决Uniapp中文件切片问题
作者:Synmbrf
链接:https://juejin.cn/post/7493783786707058726
来源:稀土掘金

收起阅读 »