普通视图
type-challenges(ts类型体操): 16 - 排除最后一项
Vue-常用修饰符
前言
在 Vue 开发中,修饰符(Modifiers)是指令后的一个特殊后缀(以 . 开头),它能以极简的方式帮我们处理事件冒泡、键盘监听以及复杂的双向绑定逻辑。掌握它们,能让你的模板代码既优雅又高效。
一、 事件修饰符:精准控制交互行为
事件修饰符主要用于处理 DOM 事件的细节。
-
.stop:阻止事件冒泡(调用event.stopPropagation())。 -
.prevent:阻止事件的默认行为(调用event.preventDefault())。 -
.capture:在捕获模式下触发事件监听器。 -
.self:只有当事件是从触发元素本身触发时才触发回调。 -
.once:事件只触发一次,之后自动移除监听器。 -
.passive:滚动事件的性能优化,告诉浏览器不需要等待preventDefault。
二、 键盘与鼠标修饰符:语义化监听
1. 按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键,例如:<input @keyup.enter="submitForm" type="text" placeholder="按回车提交">。
-
.enter:回车键 -
.tab:Tab 键 -
.space:空格键 -
.delete:删除或退格键 -
.up/.down/.left/.right:方向键
2. 鼠标修饰符
用于限制处理程序仅响应特定的鼠标按键。
-
.left:点击鼠标左键触发。 -
.right:点击鼠标右键触发。 -
.middle:点击鼠标中键(滚轮点击)触发。
三、 v-model 修饰符:数据预处理
这些修饰符可以自动处理表单输入的数据格式。
-
.lazy: 将v-model的同步时机设置在change事件之后,一般为在输入框失去焦点时。 -
.number:自动将用户的输入值转为数值类型(内部使用parseFloat)。 -
.trim:自动过滤用户输入内容的首尾空白字符。
四、 双向绑定修饰符
这是 Vue 2 到 Vue 3 变化最大的部分。
1. Vue 2 时代的 .sync
在 Vue 2 中,.sync 是实现父子组件属性双向绑定的语法糖。
// 使用 .sync 的语法糖
<ChildComponent :title.sync="pageTitle" />
// 在子组件的方法中
this.$emit('update:title', newTitleValue);
2. Vue 3 的统一:v-model:prop
Vue 3 废弃了 .sync,将其功能合并到了 v-model 中。支持在同一个组件上绑定多个 v-model。
// 在父组件中
<ChildComponent v-model:title="pageTitle" />
// 子组件
<script setup>
defineProps(['title']);
const emit = defineEmits(['update:title']);
const updateTitle = (newVal) => {
emit('update:title', newVal);
};
</script>
3. Vue 3.4+ 的黑科技:defineModel
这是目前 Vue 3 最推荐的写法,极大简化了双向绑定的逻辑代码。
// 父组件
<ChildComponent v-model="inputValue" />
// 子组件
const inputValue = defineModel({
// inputValue为双向绑定输入框的值
type: [String],
// 默认值
default: ''
})
五、 总结
- 交互逻辑优先使用事件修饰符,减少组件内的非业务代码。
-
表单处理善用
.trim和.number,降低后端校验压力。 -
父子通信在 Vue 3 项目中全面拥抱
v-model:prop,如果是新项目(Vue 3.4+),请直接使用defineModel,它能让你的代码量减少 50% 以上。
Pagefind:为静态网站打造的极速搜索方案
跨平台框架怎么选:16 个框架全景对比(2026 版)
字符串拼接?使用 knitwork-x,更轻量的 TS 代码生成方案
拒绝造轮子!Quill 实现自定义标签功能的踩坑实录 🛠️
1. 《手写系列:面试官问我 new 的原理,我直接甩出三个版本》
JavaScript事件循环(下) - requestAnimationFrame与Web Workers
别再用 Web 思路搞 Node 服务打包了!这可能是你“地狱级”痛苦的根源
Vue3 props穿透(attrs)重大变化:$attrs 居然包含class/style了!
统一开发规范--Git hooks工具库——husky
学习Three.js--星环粒子(ShaderMaterial)
# Vue3 音频标注插件 wavesurfer
数据工程指南:指标平台选型避坑与 NoETL 语义编织技术解析
uni-app 小程序(兼容鸿蒙)多参数传递避坑:eventChannel 完胜 URL & 拼接
使用 Python 实现 Flutter 项目的自动化构建与发布
前言
作为公司唯一的移动端开发,我需要同时负责 5 个 Flutter App 的开发和维护工作。每个应用都需要支持 iOS 和 Android 双平台,这意味着每次发版我可能要进行多达 10 次的打包操作。如果每次都手动执行构建、上传、通知这些重复性工作,不仅耗时巨大,还极易出错。
为了从繁琐的重复劳动中解放出来,把更多精力投入到真正有价值的开发工作中,我使用 Python 编写了一套自动化脚本,实现了一键完成构建、上传和通知的完整流程。
本文将分享我在这个过程中的实践经验和代码实现。
项目背景
公司目前有 5 个移动端应用在同时运营,而移动端开发只有我一个人。每个应用都是基于 Flutter 开发的跨平台应用,需要同时支持 iOS 和 Android 平台。在日常开发中,存在以下痛点:
- 项目多、人手少:5 个 App × 2 个平台 = 10 个构建任务,一个人根本忙不过来
- 构建流程繁琐:每次打包都需要手动执行多个命令,切换项目、切换环境配置
- 上传步骤重复:构建完成后需要手动上传到测试平台(蒲公英),操作机械且耗时
- 通知不及时:需要手动通知测试人员新版本已就绪,容易遗漏
- iOS 构建环境问题:CocoaPods 缓存问题经常导致构建失败,排查费时费力
面对这样的工作强度,自动化不再是"锦上添花",而是"刚需"。
技术方案
我设计了以下几个 Python 脚本来解决这些问题:
python/
├── build_app.py # 主构建脚本(iOS + Android)
├── build_android_app.py # Android 单独构建脚本
├── clean_ios_build.py # iOS 构建环境清理
├── force_clean_ios.py # 强制清理脚本
├── bulk_email.py # 群发邮件工具类
├── send_email.py # 单封邮件发送
└── test_email_auth.py # 邮箱授权测试
核心实现
1. 自动构建脚本
构建脚本的核心功能是自动执行 Flutter 构建命令,并支持不同环境(开发/生产)的配置。
#!/usr/local/bin/python3
import os
import subprocess
# 获取当前脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
# Flutter项目根目录(python文件夹在项目根目录下)
flutter_root = os.path.dirname(script_dir)
# 获取用户输入的环境
env = input("请输入环境(dev/prod): ")
# 检查环境配置文件是否存在
env_file = os.path.join(flutter_root, f"{env}.json")
if not os.path.exists(env_file):
print(f"错误: 环境配置文件 {env}.json 不存在")
exit(1)
# 切换到Flutter项目根目录
os.chdir(flutter_root)
# 构建Android应用
def build_android(env):
print("正在构建Android应用...")
env_text = '生产' if env == 'prod' else '开发'
print(f"构建版本: {env_text}环境...")
# 构建命令,支持代码混淆
build_command = f'fvm flutter build apk --release --dart-define-from-file={env}.json --obfuscate --split-debug-info=./build/debug_info'
try:
process = subprocess.run(
build_command.split(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
print("构建输出:")
print(process.stdout)
print("Android构建成功!")
print("APK文件路径: build/app/outputs/flutter-apk/app-release.apk")
return True
except subprocess.CalledProcessError as e:
print(f"Android构建失败: {e}")
return False
# 构建iOS应用
def build_ios(env, upload_to_appstore=False):
print("正在构建iOS应用...")
env_text = '生产' if env == 'prod' else '开发'
# 根据是否上传App Store选择导出方法
export_method = "app-store" if upload_to_appstore else "development"
build_command = f"fvm flutter build ipa --release --export-method {export_method} --dart-define-from-file={env}.json --obfuscate --split-debug-info=./build/debug_info"
try:
process = subprocess.run(
build_command.split(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
print("构建输出:")
print(process.stdout)
print("iOS构建成功!")
return True
except subprocess.CalledProcessError as e:
print(f"iOS构建失败: {e}")
return False
2. 自动上传到蒲公英
构建完成后,自动将安装包上传到蒲公英测试平台:
import requests
def upload_to_pgyer(env, ipa_path, platform):
"""上传到蒲公英测试平台"""
print(f"正在上传到蒲公英...")
print(f"文件路径: {ipa_path}")
# 从配置文件或环境变量读取 API Key
api_key = os.environ.get('PGYER_API_KEY', 'your_api_key')
user_key = os.environ.get('PGYER_USER_KEY', 'your_user_key')
files = {"file": open(ipa_path, "rb")}
headers = {"enctype": "multipart/form-data"}
platform_text = "android" if platform == "android" else "ios"
payload = {
"uKey": user_key,
"_api_key": api_key,
"installType": 1,
"updateDescription": f"{platform_text}自动化打包"
}
try:
response = requests.post(
"https://www.pgyer.com/apiv2/app/upload",
data=payload,
files=files,
headers=headers
)
result = response.json()
# 获取构建信息
qr_code_url = result["data"]["buildQRCodeURL"]
version = result["data"]["buildVersion"]
version_no = result["data"]["buildVersionNo"]
build_name = result["data"]["buildName"]
print(f"上传成功!")
print(f"二维码地址: {qr_code_url}")
print(f"版本: {version} ({version_no})")
return {
"qr_code_url": qr_code_url,
"version": version,
"version_no": version_no,
"build_name": build_name
}
except Exception as e:
print(f"上传失败: {e}")
return None
3. 群发邮件通知
构建并上传成功后,自动发送邮件通知团队成员:
#!/usr/local/bin/python3
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
import time
from typing import List, Dict, Optional
class BulkEmailSender:
"""群发邮件发送器"""
def __init__(self, smtp_server: str, smtp_port: int,
sender_email: str, sender_password: str):
"""
初始化群发邮件发送器
Args:
smtp_server: SMTP服务器地址
smtp_port: SMTP端口
sender_email: 发送者邮箱
sender_password: 发送者邮箱密码或授权码
"""
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.sender_email = sender_email
self.sender_password = sender_password
def _create_connection(self):
"""创建SMTP连接"""
try:
if self.smtp_port == 465:
# 使用SSL连接(465端口)
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
else:
# 使用STARTTLS连接(587/25端口)
server = smtplib.SMTP(self.smtp_server, self.smtp_port)
server.starttls()
server.login(self.sender_email, self.sender_password)
return server
except Exception as e:
print(f"连接失败: {e}")
return None
def send_bulk_individual(self, recipients: List[str], subject: str,
body: str, html_body: Optional[str] = None,
attachment_path: Optional[str] = None,
delay: float = 1.0) -> Dict[str, bool]:
"""
逐个发送邮件(隐私保护最好,每个人只能看到自己的邮箱)
Args:
recipients: 收件人列表
subject: 邮件主题
body: 邮件内容(纯文本)
html_body: HTML邮件内容(可选)
attachment_path: 附件路径(可选)
delay: 发送间隔(秒),避免被服务器限制
"""
results = {}
server = self._create_connection()
if not server:
return {email: False for email in recipients}
try:
for i, recipient in enumerate(recipients):
try:
print(f"发送邮件 {i+1}/{len(recipients)} 到: {recipient}")
# 创建邮件
if html_body:
msg = MIMEMultipart('alternative')
msg.attach(MIMEText(body, 'plain', 'utf-8'))
msg.attach(MIMEText(html_body, 'html', 'utf-8'))
else:
msg = MIMEMultipart()
msg.attach(MIMEText(body, 'plain', 'utf-8'))
msg['From'] = self.sender_email
msg['To'] = recipient
msg['Subject'] = subject
# 添加附件
if attachment_path and os.path.exists(attachment_path):
self._add_attachment(msg, attachment_path)
# 发送邮件
server.sendmail(self.sender_email, [recipient], msg.as_string())
results[recipient] = True
print(f"✅ 发送成功: {recipient}")
# 延迟避免被限制
if i < len(recipients) - 1:
time.sleep(delay)
except Exception as e:
results[recipient] = False
print(f"❌ 发送失败 {recipient}: {e}")
finally:
server.quit()
return results
def send_bulk_bcc(self, recipients: List[str], subject: str, body: str,
html_body: Optional[str] = None, batch_size: int = 50) -> bool:
"""
使用BCC批量发送(隐私保护,收件人看不到其他人)
适合大批量发送通知邮件
"""
server = self._create_connection()
if not server:
return False
try:
# 分批发送,避免单次发送太多
for i in range(0, len(recipients), batch_size):
batch = recipients[i:i + batch_size]
print(f"发送批次 {i//batch_size + 1}: {len(batch)} 个收件人")
if html_body:
msg = MIMEMultipart('alternative')
msg.attach(MIMEText(body, 'plain', 'utf-8'))
msg.attach(MIMEText(html_body, 'html', 'utf-8'))
else:
msg = MIMEMultipart()
msg.attach(MIMEText(body, 'plain', 'utf-8'))
msg['From'] = self.sender_email
msg['To'] = self.sender_email # 显示发送者自己
msg['Bcc'] = ', '.join(batch) # 密送给所有收件人
msg['Subject'] = subject
all_recipients = [self.sender_email] + batch
server.sendmail(self.sender_email, all_recipients, msg.as_string())
print(f"✅ 批次发送成功: {len(batch)} 个收件人")
if i + batch_size < len(recipients):
time.sleep(2)
return True
except Exception as e:
print(f"❌ BCC群发失败: {e}")
return False
finally:
server.quit()
def _add_attachment(self, msg: MIMEMultipart, attachment_path: str):
"""添加附件到邮件"""
with open(attachment_path, "rb") as attachment:
part = MIMEBase('application', 'octet-stream')
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
f'attachment; filename= {os.path.basename(attachment_path)}'
)
msg.attach(part)
4. 发送构建通知邮件
将构建信息通过 HTML 邮件发送给团队:
def send_build_notification(build_info, env, platform, test_content=""):
"""发送构建通知邮件"""
env_text = '生产' if env == 'prod' else '开发'
platform_text = "Android" if platform == "android" else "iOS"
# 构建HTML邮件内容
html_body = f"""
<html>
<body>
<h2>项目构建通知</h2>
<p>构建状态: <span style="color: green;"><b>成功</b></span></p>
<ul>
<li>构建名称: {build_info['build_name']}</li>
<li>平台: {platform_text}</li>
<li>环境: {env_text}</li>
<li>版本: {build_info['version']}</li>
<li>版本号: {build_info['version_no']}</li>
<li>测试内容: {test_content}</li>
</ul>
<img src="{build_info['qr_code_url']}" alt="下载二维码">
<p>请扫描二维码下载安装测试。</p>
</body>
</html>
"""
# 从环境变量读取邮件配置
smtp_server = os.environ.get('SMTP_SERVER', 'smtp.exmail.qq.com')
smtp_port = int(os.environ.get('SMTP_PORT', '587'))
sender_email = os.environ.get('SENDER_EMAIL')
sender_password = os.environ.get('SENDER_PASSWORD')
bulk_sender = BulkEmailSender(
smtp_server=smtp_server,
smtp_port=smtp_port,
sender_email=sender_email,
sender_password=sender_password
)
# 收件人列表(从配置文件读取)
recipients = load_recipients_from_config()
subject = f"构建通知: {build_info['build_name']} - {platform_text} - {env_text}环境"
results = bulk_sender.send_bulk_individual(
recipients=recipients,
subject=subject,
body=f'{platform_text}打包通知',
html_body=html_body,
delay=0.5
)
return results
5. iOS 构建环境清理脚本
在 iOS 开发中,经常会遇到 CocoaPods 缓存导致的构建问题。这个脚本可以彻底清理构建环境:
#!/usr/bin/env python3
"""
iOS 构建环境清理脚本
用于解决 Firebase Crashlytics 模块化头文件等常见问题
"""
import os
import subprocess
import shutil
def run_command(command, description):
"""执行命令并打印结果"""
print(f"\n{description}...")
print(f"执行命令: {command}")
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.stdout:
print("输出:", result.stdout)
if result.returncode == 0:
print(f"✅ {description} 成功")
else:
print(f"❌ {description} 失败")
return result.returncode == 0
except Exception as e:
print(f"❌ {description} 异常: {e}")
return False
def force_clean_ios():
"""强制清理 iOS 构建环境"""
print("🧹 开始强制清理 iOS 构建环境...")
# 获取项目根目录
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ios_dir = os.path.join(project_root, "ios")
if not os.path.exists(ios_dir):
print(f"❌ iOS 目录不存在: {ios_dir}")
return False
os.chdir(project_root)
print(f"📁 当前工作目录: {os.getcwd()}")
# 1. 清理 Flutter 缓存
run_command("fvm flutter clean", "清理 Flutter 构建缓存")
# 2. 删除 pubspec.lock
pubspec_lock = os.path.join(project_root, "pubspec.lock")
if os.path.exists(pubspec_lock):
print(f"🗑️ 删除 pubspec.lock")
os.remove(pubspec_lock)
# 3. 删除 .dart_tool 目录
dart_tool_dir = os.path.join(project_root, ".dart_tool")
if os.path.exists(dart_tool_dir):
print(f"🗑️ 删除 .dart_tool 目录")
shutil.rmtree(dart_tool_dir)
# 4. 删除 iOS 构建目录
for dir_name in ["build", "Pods", ".symlinks"]:
dir_path = os.path.join(ios_dir, dir_name)
if os.path.exists(dir_path):
print(f"🗑️ 删除 {dir_name} 目录")
shutil.rmtree(dir_path)
# 5. 删除 Podfile.lock
podfile_lock = os.path.join(ios_dir, "Podfile.lock")
if os.path.exists(podfile_lock):
print(f"🗑️ 删除 Podfile.lock")
os.remove(podfile_lock)
# 6. 清理 CocoaPods 缓存
run_command("pod cache clean --all", "清理 CocoaPods 缓存")
# 7. 重新获取 Flutter 依赖
run_command("fvm flutter pub get", "重新获取 Flutter 依赖")
# 8. 重新安装 Pods
os.chdir(ios_dir)
run_command("pod install --repo-update", "重新安装 Pods")
print("\n🎉 强制清理完成!")
print("💡 现在可以重新尝试构建 iOS 应用了")
return True
if __name__ == "__main__":
force_clean_ios()
6. 邮箱授权测试工具
在配置邮件服务前,可以使用这个工具测试授权码是否正确:
#!/usr/local/bin/python3
import smtplib
def test_email_auth(smtp_server, smtp_port, email, auth_code):
"""
测试邮箱授权码是否正确
"""
try:
print(f"正在测试邮箱: {email}")
print(f"SMTP服务器: {smtp_server}:{smtp_port}")
# 连接SMTP服务器
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
# 尝试登录
server.login(email, auth_code)
server.quit()
print("✅ 授权码验证成功!")
return True
except smtplib.SMTPAuthenticationError:
print("❌ 授权码验证失败!请检查:")
print(" 1. 授权码是否正确")
print(" 2. 是否已开启SMTP服务")
print(" 3. 是否使用了邮箱密码而非授权码")
return False
except Exception as e:
print(f"❌ 连接失败: {e}")
return False
if __name__ == "__main__":
# 常用邮箱SMTP配置
email_configs = {
'qq': ('smtp.qq.com', 587),
'163': ('smtp.163.com', 25),
'gmail': ('smtp.gmail.com', 587),
'outlook': ('smtp-mail.outlook.com', 587),
'wechat': ('smtp.exmail.qq.com', 587)
}
print("=== 邮箱授权码测试工具 ===\n")
email_type = input("请选择邮箱类型 (qq/163/gmail/outlook/wechat): ").lower()
if email_type not in email_configs:
print("不支持的邮箱类型")
exit(1)
email = input("请输入邮箱地址: ")
auth_code = input("请输入授权码: ")
smtp_server, smtp_port = email_configs[email_type]
test_email_auth(smtp_server, smtp_port, email, auth_code)
使用方式
1. 环境准备
首先确保安装了必要的 Python 依赖:
pip install requests
2. 配置敏感信息
建议使用环境变量或配置文件管理敏感信息,不要硬编码在脚本中:
# 设置环境变量
export PGYER_API_KEY="your_api_key"
export PGYER_USER_KEY="your_user_key"
export SENDER_EMAIL="your_email@example.com"
export SENDER_PASSWORD="your_auth_code"
export SMTP_SERVER="smtp.exmail.qq.com"
export SMTP_PORT="587"
3. 执行构建
# 进入 python 脚本目录
cd python
# 执行构建脚本
python3 build_app.py
脚本会依次提示:
- 选择环境(dev/prod)
- 是否发送邮件通知
- 输入测试内容
- 是否上传到 App Store(仅生产环境)
4. 清理 iOS 构建环境
当遇到 iOS 构建问题时,执行:
python3 force_clean_ios.py
最佳实践
1. 敏感信息管理
- 使用环境变量存储 API Key、密码等敏感信息
- 不要将敏感信息提交到版本控制
- 可以使用
.env文件配合python-dotenv库
2. 错误处理
- 每个关键步骤都添加 try-except 处理
- 构建失败时输出详细错误信息
- 记录日志便于问题排查
3. 邮件发送策略
- 群发邮件时添加适当延迟,避免被服务器限制
- 使用 BCC 方式保护收件人隐私
- 分批发送大量邮件
4. 构建优化
- 使用
--obfuscate参数进行代码混淆 - 使用
--split-debug-info分离调试信息 - 根据环境使用不同的配置文件
总结
通过 Python 脚本实现 Flutter 项目的自动化构建,可以显著提高开发效率:
- 一键完成:构建、上传、通知全流程自动化
- 减少出错:避免手动操作带来的失误
- 节省时间:构建期间可以专注于其他工作
- 规范流程:统一的构建和发布流程
这套脚本已经在我使用了一段时间,效果良好。希望这篇文章对有类似需求的开发者有所帮助。
相关技术栈:
- Python 3.x
- Flutter + FVM
- 蒲公英测试平台
- SMTP 邮件服务
被拒 10 次后,我的首款开源鸿蒙应用终上架,真的坎坷~
经过近几个月的开发与打磨,我的首款鸿蒙应用👉 uViewPro(跨平台UI组件库)👈 点击体验 正式上线华为鸿蒙应用市场!这是一款基于 uni-app + uView Pro 开发的应用,主要面向开发者实践的应用,它不仅展示了 uView Pro 开源组件库的强大能力,更是一次跨平台开发的完美落地实践。
![]()
但是上线的过程可谓是坎坷不断...
一. 说一下心酸苦楚
我感觉鸿蒙应用上架可比其他平台上线严格多了,我反复修改提交了近10次才最终上架成功。
第一次提交申请后被拒的原因是:
- 功能交互简单,影响用户的总体体验。
- 横竖屏布局未适配问题,不符合鸿蒙应用UX设计规范。
- 未正常适配设备深色模式,不符合鸿蒙应用UX设计规范。
![]()
第2,3个问题都好解决,因为问题明确,解决方案明了:
- 第2个问题需要适配横竖屏切换的布局适配,布局不要错乱即可。
- 第3个问题需要将应用的所有页面适配暗色模式。
但官方也明确说了,这两个问题不是重点,不解决也可以通过审核。
最重要的卡着不让上线的是第1个原因,由于应用交互简单所以不能上线。我真是....,这太主观了,审核人员说你交互简单,那你就过不了!
果真,经过优化后再次提交,后面所有被拒绝的原因都为第一个,其他的问题都已解决,不管应用内容如何丰富,都被拒绝!(这根本就是不想给过啊...)
![]()
![]()
提交多次被审核驳回后,快到了放弃的边缘,再次经群友提点,提交申诉和工单可能会通过,让我又看到了曙光。
![]()
很快,几天后一盆冷水又浇来了,唉,工单也给驳回了。
![]()
难道这一次真到了要放弃的时候了?那不可能,已经付出了那么多,不能轻易放弃。我甚至怀疑 UI 组件演示库就根本不让上,但是我通过在鸿蒙应用商店搜索,别人做的那么简单的都能上架,我的应用说太简单了?
从 1.0.0 到 1.0.9,我增加了下述重要的功能:
- 加入引导系统:首次启动展示引导页,在复杂组件页面新增分步引导。
- 提高用户参与度:任务与体验值体系,每个组件配套任务(学习/实现类任务),完成后奖励经验值与成就。
- 互动反馈功能:加入点赞/收藏/评分功能,记录用户偏好并提供“常用组件”列表。
- 增强交互动效:为演示页加入真实交互,“在线模拟” 操作,体验动效。
- 支持API文档查询:各组件的演示+API手册,做一个APP全能大师
所以我不认可审核人员说的“交互简单”,我梳理一下思路,开始复盘,开始反思。最终,我整理提交了一大堆材料+万字说明文档,包括如下:
![]()
我把这一次当成绝地反击的最后一次,终于,之前的努力没有被化作泡影,通过了审核!
为什么会这么艰难?不止我感觉难,鸿蒙开发群里的小伙伴同样是这样,可能是与报名了鸿蒙应用激励计划有关!
![]()
下面来说说,我为什么一定要在鸿蒙系统上线这款应用?
二. 为什么要开发这款鸿蒙应用?
众所周知,我是👉 uView Pro 开源组件库 的作者,自从2025年8月份开源以来,目前已经有不少开发者使用,期间有许多小伙伴像我询问,支不支持鸿蒙?但由于我从未在鸿蒙系统上开发过应用,也没上架过鸿蒙应用,也不知道它的兼容性如何。
所以,我打算通过将这款应用真正上架到鸿蒙应用商店,来既验证uView Pro 在鸿蒙系统上的可行性,也为其他开发者提供了可参考的落地案例。
跨平台开发常见痛点:文档不直观、示例难落地、多端适配易踩坑。基于 uView Pro 组件库,因此我必须要做一个真实应用来解决这些问题,让开发者可以:
- 直观体验 真实场景下的组件表现
- 快速上手 通过交互 Demo 和任务系统掌握用法
- 验证可行性 在鸿蒙平台验证跨平台方案
- 提升效率 借助模板与工具加速开发
而我选择在鸿蒙上验证 uView Pro 组件库的可行性,主要是为了:
- 补齐版图:其他主流平台已兼容,鸿蒙是必选的一环
- 验证能力:确认 uni-app + Vue3 + uView Pro 在鸿蒙的兼容性、性能与体验
- 市场红利:华为对在2025年要求时间段上架的应用,会给开发者发放激励金
- 技术成长:学习鸿蒙特性、解决新问题、沉淀跨平台经验
最终不负众望,这次实践既验证了方案的可行性,也为其他开发者提供了可参考的落地案例。
三. 技术栈:为什么选择这些技术?
- 开发框架:uni-app(多端开发,提高生产力)
- 开发语言:Vue3 + TS(鸿蒙打包只支持Vue3)
- UI组件库:uView Pro(我自己的开源组件库)
最主要的是我想验证 uView Pro 开发鸿蒙应用的可行性。
1. uni-app:一次开发,多端运行
uni-app 作为老牌的多端开发的跨平台开发框架,它的核心优势在于:真正的跨平台能力。
uni-app 开发的应用,支持编译到多个平台:
- 移动端:Android、iOS、HarmonyOS
- 小程序:微信、支付宝、百度、头条、QQ
- Web:H5,PC
- 快应用:华为、小米等
这意味着,使用 uni-app 可以同时覆盖多个平台,会大大降低开发和维护成本。使用它可以充分利用它的跨平台能力,确保应用在各个平台上都能正常运行,特别是在鸿蒙平台上的表现,也完全达到了预期。
2. Vue 3 + TS:现代化的开发体验
Vue 3 作为当前最流行的前端框架之一,带来了:
- Composition API:更灵活的代码组织方式,逻辑复用更简单
- 性能提升:相比 Vue 2,性能提升 2-3 倍
- TypeScript 支持:完整的类型系统,只能提示校验,减少运行时错误
- 生态丰富:庞大的社区和丰富的插件生态
使用它可以充分利用 Vue 3 的 Composition API,将复杂的组件逻辑拆分成可复用的组合函数,代码更加清晰和易维护。不过,鸿蒙开发也只能用 Vue3,因为 uni-app 并不支持将 Vue2 的代码打包成鸿蒙应用。
// 示例:使用 Composition API 管理主题状态
<script setup lang="ts">
import type { DarkMode } from 'uview-pro/types/global'
import { useTheme } from 'uview-pro'
import { ref } from 'vue'
const { darkMode, currentTheme, setDarkMode, setTheme, getAvailableThemes } = useTheme()
const darkModes = ref<{ value: DarkMode, label: string }[]>(
[
{ value: 'auto', label: '自动' },
{ value: 'light', label: '亮色' },
{ value: 'dark', label: '深色' },
],
)
function handleThemeSelect(theme: string) {
// 切换到选定的主题
setTheme(theme)
}
function handleDarkModeSelect(mode: DarkMode) {
setDarkMode(mode)
}
</script>
TypeScript 的加入,会让整个项目更加健壮:
- 编译时类型检查,提前发现潜在问题
- 更好的 IDE 智能提示,提升开发效率
- 代码可读性更强,团队协作更顺畅
- 重构更安全,减少引入 Bug 的风险
![]()
3. uView Pro:强大的 UI 组件库
![]()
uView Pro 是我长期维护的开源 UI 组件库,它提供了:
(1). 丰富的组件生态
uView Pro 已包含 80+ 组件,覆盖了日常开发所需:
- 基础组件:Button、Input、Icon、Image 等
- 表单组件:Form、Checkbox、Radio、Picker 等
- 布局组件:Layout、Grid、Flex、Card 等
- 导航组件:Navbar、Tabbar、Tabs、Steps 等
- 数据展示:Table、List、Swiper、Waterfall 等
- 反馈组件:Toast、Modal、Loading、ActionSheet 等
- 其他组件:MessageInput、LazyLoad、Loadmore、Link 等
uView Pro 基于官方 uView UI 1.8.8 版本,完全使用 Vue3 + TypeScript 源码级重写,每个组件都经过精心重构优化,既保证了功能的完整性,又兼顾了易用性。
(2). 完善的文档和示例
uView Pro 的文档非常详细,同样进行了重构级优化,免费无广告:
- API 文档:每个组件的属性、事件、方法都有详细说明
- 示例代码:提供多种使用场景的示例
- 最佳实践:分享组件使用的最佳实践
- 常见问题:整理常见问题和解决方案
(3). 主题定制能力
uView Pro 支持完整的主题定制:
- 内置主题:提供多种预设主题
- 自定义主题:支持自定义颜色、字体等
- 暗黑模式:完整的暗黑模式支持
- 动态切换:支持运行时切换主题
可以充分利用 uView Pro 的主题系统,实现多主题切换和暗黑模式,用户体验非常流畅。不仅如此,你可以3分钟智能生成多种主题,主要是靠:
智能推断主题色工具: 通过设置某个主题色,可以阶梯生成其他色值。
![]()
随机生成主题色工具: 随机生成主题色阶梯色值。
![]()
生成后可在 main.ts 这样使用:
import uViewPro from '@/uni_modules/uview-pro';
// 主题列表,仅作演示,应单独提取出来统一维护
const themes = [
// 主题: 绿色
{
name: 'green',
label: '清翠绿',
color: {
// 明亮模式下的主题色
primary: '#059669',
error: '#dc2626',
warning: '#eab308',
success: '#16a34a',
info: '#78716c',
primaryLight: '#ecfdf5',
errorLight: '#fee2e2',
warningLight: '#fefce8',
successLight: '#dcfce7',
infoLight: '#fafaf9',
primaryDark: '#047857',
errorDark: '#b91c1c',
warningDark: '#ca8a04',
successDark: '#15803d',
infoDark: '#57534e',
primaryDisabled: '#6ee7b7',
errorDisabled: '#fca5a5',
warningDisabled: '#facc15',
successDisabled: '#86efac',
infoDisabled: '#e7e5e4'
},
darkColor: {
// 暗黑模式下的主题色
// 如未配置,系统会自动根据亮色生成暗黑色值
}
}
];
export function createApp() {
const app = createSSRApp(App);
// 引入uView Pro 主库
app.use(uViewPro, {
theme: {
themes: themes,
defaultTheme: 'green',
defaultDarkMode: 'light'
},
});
return {
app
};
}
(4). 多语言能力
uView Pro 所有内置组件均支持多语言,支持全局与组件级配置、响应式切换与持久化语言偏好。
核心特性如下:
-
内置语言: 默认包含
zh-CN与en-US。 - 配置灵活: 支持在应用入口全局配置或组件内覆盖局部语言包。
- 响应式切换: 切换语言时组件文案自动更新。
- 持久化: 用户选择会被保存以便下次恢复。
- 扩展友好: 可按需添加或覆盖语言包,支持按需加载。
在 main.ts 这样使用:
import uViewPro from 'uview-pro';
export function createApp() {
const app = createSSRApp(App);
// 引入uView Pro 主库,
app.use(uViewPro, {
locale: {
// 部分覆盖内置语言包
locales: [
{ name: 'zh-CN', uModal: { confirmText: '好的', cancelText: '算了' } },
{ name: 'en-US', uModal: { confirmText: 'OK', cancelText: 'Cancel' } }
],
defaultLocale: 'zh-CN'
}
});
return {
app
};
}
![]()
四. 核心优势:这款鸿蒙应用为什么值得体验?
1. 真正的跨平台体验
uView Pro 鸿蒙应用本身就是跨平台开发的最佳实践。通过这款应用,你可以:
- 验证跨平台能力:在鸿蒙设备上体验,验证 uni-app + uView Pro 的跨平台能力
- 学习最佳实践:了解如何在跨平台项目中组织代码、处理兼容性问题
- 参考实现方案:参考应用的实现方式,应用到自己的项目中
2. 新增游戏化学习机制
传统的组件库文档往往比较枯燥,而 uView Pro 鸿蒙应用引入了游戏化学习机制:
(1). 任务系统
每个组件 Demo 都配套一个或多个任务,例如:
- 表单验证任务:完成一个完整的表单验证流程
- 数据展示任务:使用 Table 组件展示数据列表
- 交互设计任务:实现特定的交互效果
完成任务后,会获得经验值奖励,让学习过程更有趣。
(2). 成就系统
达到一定经验值后,可以解锁成就和特权:
- 主题解锁:解锁更多主题选项
- 模板下载:增加模板下载次数
- 特殊标识:获得特殊的用户标识
(3). 体验地图
可视化展示学习进度:
- 已完成任务:清晰展示已掌握的内容
- 推荐任务:根据当前进度推荐下一步学习内容
- 成就展示:展示已解锁的成就
这种游戏化的学习方式,让学习组件库变得更加有趣和高效。
![]()
3. 丰富的功能模块
uView Pro 不仅仅是一个组件展示应用,更是一个完整的开发工具集合:
(1). 80+ 组件演示
每个组件都包含:
- 交互 Demo:可以直接操作,感受组件的实际效果
- 参数说明:详细的 API 文档
- 示例代码:多种使用场景的代码示例
- 最佳实践:组件使用的最佳实践建议
(2). 20+ 工具库
提供实用的开发工具:
- 颜色工具:颜色选择器、颜色转换、主题生成
- HTTP 工具:请求测试、接口调试
- 路由工具:路由跳转、参数解析
- 规则校验:表单验证、数据校验
- 其他工具:图标库、Mock 数据生成器等
这些工具都是日常开发中经常用到的,集成在应用中,方便随时使用。
(3). 10+ 业务模板
提供完整的业务页面模板,支持分享,一键下载业务模板源码:
- 登录界面:多种登录方式的设计
- 地址管理:地址列表、添加、编辑
- 评论列表:评论展示、回复、点赞
- 个人中心:用户信息、设置、订单等
- 设置页:应用设置、账号设置等
- ...
(4). 4 个实用场景实践
内置4个完整的业务场景,可以感受组件在实际应用中的使用:
- 待办事项:TODO 应用,记录任务,完成它们。
- 我的笔记:记录灵光乍现的想法,可随时查看。
- 数据统计:统计你的使用情况,了解你的使用习惯。
- 我的收藏:收藏喜欢的组件,快速查看。
![]()
4. 完善的用户体验
(1). 多主题系统
通过便捷的主题配置工具,3分钟即可生成多种主题,应用内置了5套主题,例如:
- 默认蓝:经典的蓝色主题
- 霞光紫:优雅的紫色主题
- 清翠绿:清新的绿色主题
- 暖阳橙:温暖的橙色主题
- 午夜蓝:深沉的蓝色主题
工具支持自定义主题,选择主色后可以预览效果,并保存为本地配置。
![]()
(2). 暗黑模式
完整的暗黑模式支持:
- 自动模式:跟随系统设置自动切换
- 手动模式:手动切换亮色/暗色
- 即时生效:切换后立即生效,无需重启
暗黑模式不仅覆盖了组件样式,还包括示例页、代码高亮、图表等,确保整个应用的视觉体验一致。
![]()
(3). 引导系统
首次使用应用时,会展示引导页:
- 应用定位:介绍应用的核心价值
- 功能速览:快速了解主要功能
- 使用指南:如何使用演示和任务系统
进入具体页面时,也会有分步引导,帮助用户快速上手复杂组件。
![]()
![]()
以上部分功能仅限在鸿蒙应用中体验!
五. 如何体验?
1. 通过华为应用市场
- 打开华为应用市场(AppGallery)
- 搜索 uViewPro 或 跨平台UI组件库
重要提示: 此应用仅在 HarmonyOS 5.0 及以上版本 设备的应用市场中提供,请确保您的设备系统版本满足要求后再进行下载。
2. 首次使用建议
- 完成引导:首次打开应用时,建议完成引导页,了解应用的核心功能
- 探索组件:从首页进入组件库,浏览感兴趣的组件
- 完成任务:尝试完成一些任务,体验游戏化学习机制
- 切换主题:尝试切换不同的主题,感受主题系统的强大
- 体验模板:查看业务模板,了解如何快速搭建页面
六. 总结
uView Pro 不仅仅是一款UI组件库,更是鸿蒙跨平台开发的一次实践。通过这款应用,我希望能够:
- 展示跨平台开发的可行性:证明 uni-app + uView Pro 可以在鸿蒙平台上完美运行
- 帮助开发者提升效率:通过丰富的组件和模板,帮助开发者快速开发应用
- 推动技术生态发展:为跨平台开发技术生态贡献一份力量
如果你是一名开发者,如果你对跨平台开发感兴趣,如果你想要体验鸿蒙跨平台的能力,那么,uView Pro 应用绝对值得你下载体验!
uView Pro 应用的代码全部开源,你可随时体验和使用!👇
相关资料
- 官方文档:uviewpro.cn
- GitHub:github.com/anyup/uView…
- Gitee:gitee.com/anyup/uView…
- 体验鸿蒙应用的能力:uviewpro.cn/zh/resource…
从Clawdbot到Moltbot再到OpenClaw,这只龙虾又双叒改名了
大家好,我是凌览。
- 个人网站:blog.code24.top
- 去水印下载鸭:nologo.code24.top
如果本文能给你提供启发或帮助,欢迎动动小手指,一键三连(点赞、评论、转发),给我一些支持和鼓励谢谢。
要说最近AI圈最折腾的项目,非这只"龙虾"莫属。 两个月前,它还叫Clawdbot,三天前改成了Moltbot,结果还没等大家念顺口,1月30日又宣布最终定名OpenClaw。
短短72小时内两度更名,GitHub上那个超过10万星标的开源项目,硬是把取名这件事演成了连续剧。
从一封律师函说起
事情从25年11月份说起,国外开发者Peter搞了个项目,最初叫"WhatsApp Relay"。
后来他觉得Claude Code那个龙虾形象挺酷,就给自己的项目起了个谐音梗名字——Clawdbot(龙虾叫Clawd),Logo也用了类似的红色龙虾形象。
![]()
项目意外爆火。一周200万访问量,GitHub星标蹭蹭往上涨,连Mac Mini都因为这玩意儿销量激增。
![]()
人红是非多,Anthropic的法务团队找上门了:Clawd跟Claude发音太像,涉嫌商标侵权。
"去掉d改成Clawbot也不行",面对AI巨头的压力,他最终还是妥协了。
第一次改名:Moltbot
1月27日,Clawdbot正式更名为Moltbot。新名字取自龙虾"蜕皮"(Molt)的生物学过程——龙虾必须蜕掉旧壳才能长大。Peter在公告里写:"同样的龙虾灵魂,换了一身新壳。"
![]()
吉祥物从Clawd改成了Molty,Logo也同步更新。社区对这个名字还算包容,毕竟寓意挺深刻。但麻烦接踵而至:GitHub在重命名时出了故障,Peter的个人账号一度报错;更离谱的是,X上的旧账号@clawdbot在改名后短短10秒内就被加密货币骗子抢注,随即开始炒作一款叫CLAWD的假代币,市值一度炒到1600万美元后崩盘。
Peter不得不连发数条推文澄清:这是个非营利项目,他永远不会发币,任何挂他名字的代币都是骗局。
![]()
第二次改名:OpenClaw
Moltbot这个名字还没捂热,三天后,Peter又宣布了最终名称:OpenClaw。
这次他学乖了。这个名字是凌晨5点Discord群里脑暴出来的,Peter提前做了功课——商标查询没问题,域名全部买断,迁移代码也写好了。
Open代表开源、开放、社区驱动;Claw代表龙虾 heritage,向起源致敬。Peter说,这精准概括了项目的精神内核。
改名背后的折腾
回头看这三次更名,简直像一场被迫的成长。
第一次是玩梗撞上了法律墙,第二次是应急方案不够完善,第三次才算真正站稳。这期间还夹杂着GitHub故障、账号被抢注、币圈骚扰、安全漏洞被研究人员点名——一个个人开发者的业余项目,在爆红后遭遇的连锁反应,比代码调试还让人头大。
现在它叫OpenClaw
不管名字怎么变,这个项目的核心没变:跑在你自己机器上的AI助手,支持WhatsApp、Telegram、飞书、钉钉等20多个平台,数据全本地,能操作文件、执行命令、调用API。你可以把它当成一个7×24小时待命的"数字员工",在聊天软件里@它一声,它就能帮你查数据库、整理会议纪要、甚至批量删除7.5万封邮件。
最新版本还增加了Twitch和Google Chat支持,集成了KIMI K2.5等模型,Web界面也能发图片了。
至于那只龙虾,还在。只是现在它叫OpenClaw,不叫Clawd,也不叫Molty了。