巴拉巴拉~~ 2025-12-16 16:39:09 发布引言
跨平台开发一直是开发者关注的焦点,传统跨平台方案(如Flutter、React Native)存在“原生体验差”“与系统API适配复杂”等问题。HarmonyOS推出的ArkUI-X跨平台框架,实现了“一次开发、多端部署”到HarmonyOS、Android、iOS三大平台,且能完美复用ArkUI组件和鸿蒙生态能力。很多开发者想通过ArkUI-X实现跨平台落地,但对“环境搭建”“跨平台适配”“现有项目迁移”等关键环节不熟悉。本文从ArkUI-X核心原理入手,详解开发环境搭建、跨平台应用开发、Android项目迁移全流程,结合实战案例助力开发者快速掌握跨平台能力。
一、ArkUI-X核心原理与优势
1.1 核心原理:“一套代码,多端编译”
ArkUI-X采用“编译时多端适配”架构,区别于传统跨平台的“运行时解释”,核心流程为:
1. 代码编写:开发者使用ArkTS语言和ArkUI组件编写一套代码,无需区分平台;
2. 编译适配:通过ArkUI-X编译器,根据目标平台(HarmonyOS/Android/iOS)将代码编译为对应平台的原生渲染代码;
3. 原生运行:编译后的代码调用目标平台原生API(如Android的View体系、iOS的UIKit),实现原生级运行性能。
1.2 核心优势:鸿蒙生态复用+原生体验
- 鸿蒙生态无缝复用:直接复用HarmonyOS的ArkUI组件、状态管理、分布式能力,无需重新适配;
- 原生级性能:编译后生成原生代码,运行帧率接近原生应用,解决传统跨平台“卡顿”问题;
- 低迁移成本:现有HarmonyOS应用可通过ArkUI-X快速迁移至Android/iOS,无需重构;
- 多平台API统一:ArkUI-X封装了统一的跨平台API,如网络请求、数据存储,开发者无需关注平台差异。
1.3 支持范围与限制
当前ArkUI-X 1.0版本支持:1. 目标平台:HarmonyOS 3.0+、Android 10+、iOS 14+;2. 开发语言:ArkTS;3. 组件支持:ArkUI基础组件(Text、Button、List等)、布局组件(Column、Row等);4. 限制:暂不支持部分鸿蒙特色能力(如分布式数据管理)在非鸿蒙平台使用,需通过“平台差异化代码”适配。
二、开发环境搭建:Windows+Android/iOS
2.1 基础环境要求
- 操作系统:开发Android可使用Windows/macOS;开发iOS需使用macOS(依赖Xcode);
- 开发工具:DevEco Studio 4.1及以上版本;
- 依赖工具:Android Studio(用于Android模拟器/真机调试)、Xcode 14及以上(用于iOS开发)。
2.2 详细搭建步骤
2.2.1 安装ArkUI-X插件
1. 打开DevEco Studio,进入“Settings > Plugins > Marketplace”,搜索“ArkUI-X”并安装;2. 重启DevEco Studio,插件生效。
2.2.2 配置Android开发环境
1. 安装Android Studio,下载Android SDK(API 29及以上);2. 在DevEco Studio中配置Android SDK路径:“File > Project Structure > SDK Locations > Android SDK”;3. 创建Android模拟器或连接真机,开启开发者模式。
2.2.3 配置iOS开发环境(macOS)
1. 安装Xcode 14及以上,通过Xcode下载iOS SDK;2. 在DevEco Studio中配置Xcode路径:“File > Project Structure > SDK Locations > iOS SDK”;3. 创建iOS模拟器(Xcode中“Window > Devices and Simulators”)。
2.2.4 验证环境
创建ArkUI-X项目,选择“Cross-Platform Application”,分别选择Android和iOS模拟器运行,若能正常启动则环境配置成功。
三、实战案例:跨平台待办APP开发
3.1 需求定义
开发一款跨平台待办APP,支持HarmonyOS、Android、iOS运行,核心需求:1. 待办事项的增删改查;2. 待办事项状态切换(未完成/已完成);3. 数据持久化存储;4. 跨平台适配(如Android的返回键、iOS的导航栏样式)。
3.2 核心代码实现
步骤1:项目创建与配置
1. 在DevEco Studio中创建“Cross-Platform Application”,项目名称为“TodoApp”;2. 项目结构与HarmonyOS项目一致,新增“main_pages.json”配置页面路由:
{
"src": [
"pages/TodoListPage",
"pages/TodoEditPage"
]
}
步骤2:数据模型与存储服务(跨平台兼容)
import { Preference } from '@ohos.data.preferences';
import { Observed, ObjectLink } from '@ohos.ui.components';
// 待办事项模型
@Observed
export class TodoItem {
id: string;
content: string;
isCompleted: boolean;
createTime: string;
constructor(content: string) {
this.id = Date.now().toString();
this.content = content;
this.isCompleted = false;
this.createTime = new Date().toLocaleString();
}
}
// 跨平台数据存储服务(使用ArkUI-X统一API)
export class TodoStorageService {
private static PREF_NAME = 'todo_storage';
private static KEY_TODO_LIST = 'todo_list';
// 初始化存储
private static async getPreferences(): Promise {
return await Preference.getPreferences(this.PREF_NAME);
}
// 保存待办列表
static async saveTodoList(list: TodoItem[]): Promise {
const pref = await this.getPreferences();
await pref.putString(this.KEY_TODO_LIST, JSON.stringify(list));
await pref.flush();
}
// 获取待办列表
static async getTodoList(): Promise {
const pref = await this.getPreferences();
const listStr = await pref.getString(this.KEY_TODO_LIST, '[]');
return JSON.parse(listStr).map((item: any) => {
const todo = new TodoItem(item.content);
todo.id = item.id;
todo.isCompleted = item.isCompleted;
todo.createTime = item.createTime;
return todo;
});
}
}
步骤3:待办列表页面(跨平台适配)
import { TodoItem, TodoStorageService } from '../service/TodoStorageService';
import { ObjectLink } from '@ohos.ui.components';
import router from '@ohos.router';
import { platform } from '@ohos.system';
@Entry
@Component
struct TodoListPage {
@State todoList: TodoItem[] = [];
@State isLoading: boolean = true;
// 获取当前平台(用于差异化适配)
private currentPlatform = platform.os; // 补充完整:获取操作系统类型(android/ios/harmonyos)
async aboutToAppear() {
// 加载待办列表数据
await this.loadTodoList();
}
// 加载待办列表
private async loadTodoList() {
this.isLoading = true;
try {
this.todoList = await TodoStorageService.getTodoList();
} catch (err) {
promptAction.showToast({ message: "数据加载失败" });
console.error("加载待办列表失败:", err);
} finally {
this.isLoading = false;
}
}
// 删除待办事项
private async deleteTodo(id: string) {
const newList = this.todoList.filter(item => item.id !== id);
await TodoStorageService.saveTodoList(newList);
this.todoList = newList;
}
// 切换待办完成状态
private async toggleTodoStatus(item: TodoItem) {
item.isCompleted = !item.isCompleted;
await TodoStorageService.saveTodoList(this.todoList);
}
build() {
Column({ space: 0 }) {
// 导航栏(平台差异化适配)
this.buildNavbar();
// 待办列表
if (this.isLoading) {
Progress()
.width(80)
.height(80)
.margin({ top: 100 });
} else if (this.todoList.length === 0) {
Column({ space: 15, alignItems: ItemAlign.Center })
.margin({ top: 100 }) {
Image($r('app.media.empty_todo'))
.width(120)
.height(120);
Text("暂无待办事项,点击下方按钮添加")
.fontSize(16)
.color('#999999');
}
} else {
List({ space: 10 }) {
ForEach(this.todoList, (item: TodoItem) => {
ListItem() {
Row({ space: 15, alignItems: ItemAlign.Center })
.padding(20)
.backgroundColor('#ffffff')
.borderRadius(12) {
// 勾选框
Checkbox({ checked: item.isCompleted })
.onChange(() => this.toggleTodoStatus(item))
.width(24)
.height(24);
// 待办内容与时间
Column({ space: 5, flexGrow: 1 }) {
Text(item.content)
.fontSize(18)
.textDecoration(item.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None)
.color(item.isCompleted ? '#999999' : '#333333');
Text(item.createTime)
.fontSize(14)
.color('#999999');
}
// 删除按钮
Button('删除')
.width(60)
.height(35)
.backgroundColor('#ff3b30')
.fontColor('#ffffff')
.borderRadius(17.5)
.onClick(() => this.deleteTodo(item.id));
}
}
}, (item) => item.id);
}
.padding({ left: 20, right: 20, top: 10 })
.backgroundColor('#f5f5f5')
.flexGrow(1);
}
// 添加待办按钮(固定在底部)
Button('+ 添加待办')
.width('90%')
.height(55)
.backgroundColor('#007aff')
.fontColor('#ffffff')
.borderRadius(30)
.margin({ bottom: this.currentPlatform === 'ios' ? 30 : 20, top: 20 })
.onClick(() => {
// 跳转到编辑页面(新增模式)
router.pushUrl({
url: 'pages/TodoEditPage',
params: { type: 'add' }
}).then(() => {
// 编辑页面返回后刷新列表
this.loadTodoList();
});
});
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
// Android返回键适配:返回时退出应用
.onBackPress(() => {
if (this.currentPlatform === 'android') {
app.terminate();
return true; // 拦截默认返回行为
}
return false;
});
}
// 构建导航栏(平台差异化样式)
@Builder
buildNavbar() {
Row({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center })
.width('100%')
.height(this.currentPlatform === 'ios' ? 64 : 56)
.backgroundColor('#007aff')
.padding({ top: this.currentPlatform === 'ios' ? 20 : 0 }) {
Text('跨平台待办APP')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.color('#ffffff');
}
}
}
步骤4:待办编辑页面(新增/修改功能)
import { TodoItem, TodoStorageService } from '../service/TodoStorageService';
import router from '@ohos.router';
import { platform } from '@ohos.system';
@Entry
@Component
struct TodoEditPage {
@State content: string = '';
@State type: 'add' | 'edit' = 'add';
@State editItem: TodoItem | null = null;
private currentPlatform = platform.os;
async aboutToAppear() {
// 获取路由参数,判断是新增还是修改
const params = router.getParams() as { type: 'add' | 'edit', item?: TodoItem };
this.type = params.type;
if (this.type === 'edit' && params.item) {
this.editItem = params.item;
this.content = params.item.content; // 回显待办内容
}
}
// 保存待办事项
private async saveTodo() {
if (this.content.trim() === '') {
promptAction.showToast({ message: "待办内容不能为空" });
return;
}
const todoList = await TodoStorageService.getTodoList();
if (this.type === 'add') {
// 新增待办
const newTodo = new TodoItem(this.content.trim());
todoList.push(newTodo);
} else if (this.type === 'edit' && this.editItem) {
// 修改待办
const index = todoList.findIndex(item => item.id === this.editItem?.id);
if (index !== -1) {
todoList[index].content = this.content.trim();
}
}
// 保存并返回列表页面
await TodoStorageService.saveTodoList(todoList);
router.back();
}
build() {
Column({ space: 20 }) {
// 导航栏
Row({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center })
.width('100%')
.height(this.currentPlatform === 'ios' ? 64 : 56)
.backgroundColor('#007aff')
.padding({ top: this.currentPlatform === 'ios' ? 20 : 0, left: 20, right: 20 }) {
// 返回按钮
Button('返回')
.backgroundColor('transparent')
.fontColor('#ffffff')
.fontSize(16)
.onClick(() => router.back());
// 标题
Text(this.type === 'add' ? '新增待办' : '修改待办')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.color('#ffffff');
// 占位按钮(保持导航栏平衡)
Button('')
.backgroundColor('transparent')
.width(60);
}
// 输入框
TextInput({
placeholder: '请输入待办内容',
text: this.content
})
.width('90%')
.height(50)
.padding({ left: 15, right: 15 })
.backgroundColor('#ffffff')
.borderRadius(10)
.onChange((value) => this.content = value);
// 保存按钮
Button('保存')
.width('90%')
.height(55)
.backgroundColor('#007aff')
.fontColor('#ffffff')
.borderRadius(30)
.margin({ top: 50 })
.onClick(() => this.saveTodo());
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5');
}
}
步骤5:平台差异化适配核心实现
ArkUI-X通过“条件编译”和“平台API调用”实现差异化适配,核心场景及代码示例:
import { platform } from '@ohos.system';
import { abilityAccessCtrl, bundleManager } from '@ohos.abilityAccessCtrl';
@Component
struct PlatformAdaptComponent {
private currentPlatform = platform.os;
build() {
Column({ space: 15 }) {
// 1. 布局差异化:iOS导航栏高度更高,需适配顶部间距
Text('平台差异化布局示例')
.padding({ top: this.currentPlatform === 'ios' ? 30 : 20 });
// 2. 组件样式差异化:Android按钮圆角更小
Button('平台专属样式')
.width(150)
.height(45)
.borderRadius(this.currentPlatform === 'android' ? 8 : 22)
.backgroundColor(this.currentPlatform === 'android' ? '#ff6700' : '#007aff');
// 3. 功能差异化:调用平台专属API(如Android获取应用列表)
Button(`获取${this.currentPlatform}专属信息`)
.onClick(() => this.getPlatformInfo());
}
}
// 平台专属功能实现
private async getPlatformInfo() {
try {
if (this.currentPlatform === 'android') {
// Android:获取已安装应用列表(需申请权限)
const ac = abilityAccessCtrl.createAtManager();
const token = await ac.getTokenId(this.context.abilityInfo.bundleName, this.context.abilityInfo.uid);
const bundleMgr = bundleManager.getBundleManager();
const apps = await bundleMgr.getInstalledApplications(bundleManager.GetInstalledApplicationsFlag.GET_INSTALLED_APPLICATIONS_ALL);
promptAction.showToast({ message: `Android已安装应用数:${apps.length}` });
} else if (this.currentPlatform === 'ios') {
// iOS:获取设备型号(ArkUI-X封装的iOS专属API)
const deviceModel = await platform.deviceModel;
promptAction.showToast({ message: `iOS设备型号:${deviceModel}` });
} else if (this.currentPlatform === 'harmonyos') {
// HarmonyOS:获取分布式设备信息
const devices = await device.getConnectedDevices();
promptAction.showToast({ message: `鸿蒙分布式设备数:${devices.length}` });
}
} catch (err) {
console.error("获取平台信息失败:", err);
}
}
}
四、Android项目迁移实战:从原生到ArkUI-X
4.1 迁移前提与评估
迁移前需明确适配范围与成本,核心评估点:
- 项目规模:小型项目(<10个页面)可全量迁移,大型项目建议分模块迁移(如先迁移登录、首页等核心模块);
- 依赖库兼容性:Android原生依赖库(如Retrofit、Glide)需替换为ArkUI-X兼容方案(如网络请求用@ohos.net.http,图片加载用Image组件原生支持);
- 平台专属功能:如Android的Service、BroadcastReceiver,需替换为ArkUI-X的Ability、事件通知机制。
迁移核心原则:“UI层重写、业务逻辑复用、原生API替换”,避免直接复制Android原生代码。
4.2 分步迁移流程(以Android原生登录页面为例)
步骤1:迁移准备
1. 创建ArkUI-X项目,确保包名与Android原生项目一致(便于后续应用替换);
2. 梳理Android登录页面核心功能:账号密码输入、验证码获取、登录接口调用、记住密码功能;
3. 替换依赖库:Android的Retrofit替换为ArkUI-X的@ohos.net.http,SharedPreferences替换为Preference存储。
步骤2:UI层迁移(XML → ArkTS)
Android原生XML布局(login.xml)迁移为ArkUI-X的Component布局,核心对应关系:
Android原生组件
ArkUI-X组件
适配要点
TextView
Text
通过fontSize、fontWeight属性适配文字样式
EditText
TextInput
通过placeholder、inputType属性适配输入规则
Button
Button
通过backgroundColor、borderRadius适配样式
LinearLayout(垂直)
Column
通过space属性控制子组件间距
// 迁移后的ArkUI-X登录页面
@Entry
@Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
@State isRemember: boolean = false;
@State isLoading: boolean = false;
// 页面初始化时读取记住的密码
async aboutToAppear() {
const pref = await Preference.getPreferences('login_storage');
this.isRemember = await pref.getBoolean('is_remember', false);
if (this.isRemember) {
this.username = await pref.getString('username', '');
this.password = await pref.getString('password', '');
}
}
// 登录逻辑(替换Android的LoginPresenter)
private async login() {
if (this.username.trim() === '' || this.password.trim() === '') {
promptAction.showToast({ message: "账号或密码不能为空" });
return;
}
this.isLoading = true;
try {
// 替换Android的Retrofit请求:使用ArkUI-X网络API
const request = http.createHttp();
const response = await request.request(
'https://api.example.com/login',
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify({ username: this.username, password: this.password })
}
);
if (response.responseCode === 200) {
// 保存登录状态与记住密码
const pref = await Preference.getPreferences('login_storage');
await pref.putBoolean('is_remember', this.isRemember);
if (this.isRemember) {
await pref.putString('username', this.username);
await pref.putString('password', this.password);
} else {
await pref.delete('username');
await pref.delete('password');
}
await pref.flush();
// 登录成功跳转首页
router.replaceUrl({ url: 'pages/HomePage' });
} else {
promptAction.showToast({ message: "登录失败:" + response.result });
}
} catch (err) {
promptAction.showToast({ message: "网络请求失败" });
console.error("登录失败:", err);
} finally {
this.isLoading = false;
}
}
build() {
Column({ space: 25, alignItems: ItemAlign.Center })
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
.padding({ left: 30, right: 30 }) {
// 标题
Text('用户登录')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.margin({ top: 80 });
// 账号输入框
TextInput({ placeholder: '请输入账号', text: this.username })
.width('100%')
.height(55)
.padding({ left: 20, right: 20 })
.backgroundColor('#ffffff')
.borderRadius(12)
.onChange((value) => this.username = value);
// 密码输入框
TextInput({ placeholder: '请输入密码', text: this.password, type: InputType.Password })
.width('100%')
.height(55)
.padding({ left: 20, right: 20 })
.backgroundColor('#ffffff')
.borderRadius(12)
.onChange((value) => this.password = value);
// 记住密码
Row({ space: 10, alignItems: ItemAlign.Center })
.width('100%') {
Checkbox({ checked: this.isRemember })
.onChange((value) => this.isRemember = value);
Text('记住密码')
.fontSize(16)
.color('#666666');
}
// 登录按钮
Button(this.isLoading ? '登录中...' : '登录')
.width('100%')
.height(55)
.backgroundColor('#007aff')
.fontColor('#ffffff')
.borderRadius(30)
.margin({ top: 20 })
.disabled(this.isLoading)
.onClick(() => this.login());
}
}
}
步骤3:业务逻辑迁移(Java → ArkTS)
Android原生业务逻辑(如登录验证、数据解析)迁移为ArkTS工具类,核心要点:
- 单例模式替换:Android的单例(如LoginManager)替换为ArkTS的导出类+静态方法;
- 异步逻辑适配:Android的回调(Callback)替换为ArkTS的Promise/async-await;
- 数据模型复用:Android的Java Bean替换为ArkTS的class,保持字段名一致。
// 迁移后的登录工具类(替换Android的LoginManager)
export class LoginService {
// 单例实现(类似Android的static instance)
private static instance: LoginService;
private prefName = 'login_storage';
static getInstance(): LoginService {
if (!this.instance) {
this.instance = new LoginService();
}
return this.instance;
}
// 验证账号密码格式(复用Android的验证逻辑)
validateAccount(username: string, password: string): boolean {
// 手机号验证正则(与Android保持一致)
const phoneReg = /^1[3-9]\d{9}$/;
// 密码验证:6-16位字母数字组合
const pwdReg = /^[a-zA-Z0-9]{6,16}$/;
return phoneReg.test(username) && pwdReg.test(password);
}
// 保存登录状态(替换Android的SharedPreferences工具类)
async saveLoginState(username: string, password: string, isRemember: boolean) {
const pref = await Preference.getPreferences(this.prefName);
await pref.putBoolean('is_login', true);
await pref.putString('current_user', username);
if (isRemember) {
await pref.putString('username', username);
await pref.putString('password', password);
}
await pref.flush();
}
// 获取当前登录用户
async getCurrentUser(): Promise {
const pref = await Preference.getPreferences(this.prefName);
const isLogin = await pref.getBoolean('is_login', false);
if (!isLogin) return null;
return await pref.getString('current_user', null);
}
// 退出登录
async logout() {
const pref = await Preference.getPreferences(this.prefName);
await pref.putBoolean('is_login', false);
await pref.flush();
}
}
步骤4:测试与兼容优化
1. 多平台测试:在Android模拟器(API 30+)、鸿蒙真机、iOS模拟器中分别测试,重点验证:UI布局一致性(如按钮位置、文字大小);
2. 功能完整性(如登录、记住密码功能);
3. 性能指标(启动时间<3秒,无明显卡顿)。
4. 兼容性修复:针对不同平台问题优化,如:
Android:解决低版本系统(Android 10)中TextInput光标错位问题,通过设置固定高度修复;
5. iOS:解决键盘弹出时遮挡输入框问题,通过监听键盘高度调整布局。
五、ArkUI-X开发常见问题与解决方案
5.1 环境配置问题
问题现象
解决方案
Android模拟器启动失败,提示“SDK版本不匹配”
1. 确认Android SDK版本≥API 29;2. 在DevEco Studio中重新配置SDK路径,同步项目依赖;3. 重建模拟器并选择“Android 10+”系统镜像。
iOS模拟器无法启动,提示“Xcode路径未配置”
1. 确认Xcode版本≥14.0;2. 在DevEco Studio中进入“File > Project Structure”,配置Xcode路径为“/Applications/Xcode.app”;3. 重启DevEco Studio。
5.2 跨平台兼容性问题
问题现象
解决方案
Image组件在iOS中无法加载网络图片
1. 确认图片URL支持HTTPS(iOS默认禁止HTTP请求);2. 若需支持HTTP,在iOS项目的Info.plist中添加“NSAppTransportSecurity”配置,允许不安全连接。
Android返回键无法退出应用
在根组件中添加onBackPress事件监听,调用app.terminate()手动退出,如:.onBackPress(() => { app.terminate(); return true; })
5.3 性能问题
问题现象
解决方案
列表滑动卡顿(数据量>100条)
1. 开启List组件的懒加载:List({ space: 10, initialIndex: 0, lazyLoad: true });2. 拆分列表项为独立子组件,减少父组件刷新范围;3. 避免在列表项中使用复杂计算。
应用启动时间过长(>5秒)
1. 减少启动页初始化操作,将耗时任务(如数据加载)延迟到aboutToAppear后执行;2. 优化资源大小,压缩图片、减少不必要的依赖库;3. 开启编译优化,在build.gradle中配置minifyEnabled true。
六、总结
本文通过跨平台待办APP开发和Android项目迁移实战,详解了ArkUI-X的核心开发流程与适配技巧。ArkUI-X的“编译时多端适配”架构解决了传统跨平台方案的“原生体验差”痛点,且能无缝复用鸿蒙生态能力,大幅降低跨平台开发与迁移成本。
开发核心要点:1. 环境搭建需区分平台配置,确保Android SDK与Xcode版本适配;2. 跨平台适配优先采用“统一代码+条件编译”,减少平台专属代码;3. Android项目迁移遵循“UI重写、逻辑复用、API替换”原则,分模块逐步迁移降低风险。
随着ArkUI-X版本迭代,其对iOS平台的适配能力和API覆盖度将持续提升。开发者可重点关注鸿蒙智联设备与跨平台的联动场景,通过“一次开发、多端部署”抢占多设备生态红利。
相关推荐
云上修代码
2171
0
快乐编译者
1168
0
2030
0
老李的控制台
1202
0
1361
0
巴拉巴拉~~
我还没有写个人简介......
帖子
提问
粉丝
纯血鸿蒙HarmonyOS NEXT学习路线——从入门到企业级开发
2025-12-23 14:37:48 发布鸿蒙ArkTS开发规范实战指南——从规范到高效编码
2025-12-23 14:37:10 发布