- 这一课学什么
- 为什么不把图片存进网络微数据库
- 准备
- 第一步:界面(这一课新增)
- 第二步:选图、压缩、上传
- 第三步:拿到地址,登记到相册
- 第四步:连不上服务端时
- 第五步:列表里直接显示网络图片
- 跑起来看看
- 常见问题
- 动手改一改
- 下一课
- 用到的组件
- 验证范围
第 4 课 图片上传:从相册选一张图,写一句说明,上传后大家都能在列表里看到。
这一课学什么
- 图片和数据分开存:图片文件交给服务端保存,换回一个图片地址;网络微数据库里只存这个地址。
- 上传前压缩:手机照片动辄好几 MB,压缩后只有几百 KB,上传快、看的人加载也快。
- 显示网络图片:图像组件的「图片」、列表显示框的图片,都可以直接填
http://…地址。


图为由最终 .aia 执行真实积木、连真实服务端生成的布局预览(列表缩略图是从服务端取回的图片),不是真机截图。
为什么不把图片存进网络微数据库
网络微数据库存的是文本,想存图片只能把图片转成很长的 Base64 文字,体积还会再大三分之一,读写都慢,很多服务对单个值也有大小限制。
更好的分工是:
手机 ──图片文件──▶ 服务端 /upload ──返回──▶ 图片地址 http://…/files/xxxx.jpg
手机 ──地址 + 说明──▶ 网络微数据库(标签 photos)
看的人 ──取 photos──▶ 拿到地址 ──▶ 图像组件直接显示
本专题的服务端已经带了上传功能(服务端教程里下载的就是这个版本):只收 jpg、png、gif、webp 图片,单张不超过 5MB。
准备
服务端照常运行。下载 PhotoShare.aia 导入,填服务地址。
第一步:界面(这一课新增)
| 组件 | 名字 | 要设置的属性 |
|---|---|---|
| 图像选择器 | 图像选择器_选图 | 文本:从相册选图 |
| 图像 | 图像_预览 | 高度 220,勾选「自动缩放图片」 |
| 文本输入框 | 文本输入框_说明 | |
| 按钮 | 按钮_上传 | 先设为不可用,选了图再启用 |
| 列表显示框 | 列表显示框_相册 | 布局:图片 + 主文字 + 副文字(布局 4),图像宽高 72 |
| Web 客户端 | Web客户端1 | 超时时间 30000 |
| 图片处理 | 图片处理1 |
第二步:选图、压缩、上传
global 选中图片 = ""
when 图像选择器_选图.AfterPicking() {
选中图片 = 图像选择器_选图.Selection
图像_预览.Picture = 选中图片
按钮_上传.Enabled = true
}
when 按钮_上传.Click() {
var 地址 = 微数据库1.GetValue("serviceUrl", "")
if 地址 == "" {
底部提示条1.Show("先在上面填好服务地址并点保存")
} else {
// 最长边缩到 1280、质量 80,一般只剩几百 KB
var 小图 = 图片处理1.Compress(选中图片, 1280, 80)
if 小图 == "" {
底部提示条1.Show("这张图片处理不了,换一张试试")
} else {
按钮_上传.Enabled = false
标签_上传状态.Text = join("正在上传(", round(图片处理1.FileSize(小图) / 1024), " KB)……")
Web客户端1.Url = join(地址, "/upload")
Web客户端1.PostFile(小图)
}
}
}
-
PostFile把整个文件作为请求内容发给服务地址/upload,服务端存好后回一段 JSON:{"status": "OK", "url": "http://…/files/xxxx.jpg"}。 本课使用原始文件请求体上传,服务端专门读取请求内容。它与浏览器表单的multipart/form-data不同:依赖 PHP$_FILES的普通上传接口或要求 multipart 的图床,不能直接替换本课/upload地址。仅手动设置 Content-Type 也不会生成表单分隔符、文件字段和附加字段。对象存储应以具体接口的上传协议为准,例如预签名 PUT 与 multipart POST 是两种不同流程。现有 PHP 文件上传教程 的旧示例也是读取原始请求体。对要求表单上传的接口,可以改用 Web 客户端原生的 执行POST表单文件请求,设置接口要求的文件字段名、附加字段与请求头。 -
压缩返回的是新文件的路径;返回空文本表示这张图处理失败(比如选到的其实不是图片)。
中途验证:选一张照片点上传,状态显示「正在上传(xxx KB)……」;电脑浏览器打开服务地址页面,标题里「图片 1 张」。
第三步:拿到地址,登记到相册
global 待保存 = ""
when Web客户端1.GotText(url, responseCode, responseType, responseContent) {
if responseCode == 200 {
var 结果 = Web客户端1.JsonTextDecodeWithDictionaries(responseContent)
var 图片地址 = dictget(结果, "url", "")
图像_预览.Picture = 图片地址
待保存 = {"url": 图片地址, "note": trim(文本输入框_说明.Text), "time": 计时器1.FormatDateTime(计时器1.Now(), "MM-dd HH:mm")}
网络微数据库1.GetValue("photos")
} else {
按钮_上传.Enabled = true
if responseCode == 413 {
标签_上传状态.Text = "上传失败:图片超过 5MB"
} else if responseCode == 400 {
标签_上传状态.Text = "上传失败:只支持 jpg、png、gif、webp 图片"
} else if responseCode == 404 {
标签_上传状态.Text = "上传失败:服务地址不对(口令那一段抄错了?)"
} else {
标签_上传状态.Text = join("上传失败,服务器返回 ", responseCode)
}
}
}
拿到地址后把预览图换成服务器上的那张——能显示出来,就说明上传真的成功了,地址也能访问。接下来和第 2 课的留言一样:先取 photos 列表,追加 待保存,再存回去(.aia 里还把列表限制在最近 50 张)。
中途验证:电脑浏览器打开状态里那张图片的地址(可以在服务地址页面的 photos 里找到),能看到压缩后的图片。
第四步:连不上服务端时
when Web客户端1.TimedOut(url) {
按钮_上传.Enabled = true
标签_上传状态.Text = "上传超时:网络太慢,过一会儿再试"
}
when Screen1.ErrorOccurred(component, functionName, errorNumber, message) {
if functionName == "PostFile" {
按钮_上传.Enabled = true
标签_上传状态.Text = "上传失败:连不上服务端。检查服务端窗口还开着吗、手机和电脑是同一个 WiFi 吗?"
} else {
底部提示条1.Show(message)
}
}
这里有个容易漏的坑:服务端没开、地址连不上时,Web 客户端不会触发「请求超时时」,而是报到屏幕的「出现错误时」(错误号 1104)。只写了超时事件的话,按钮会一直是灰的。
第五步:列表里直接显示网络图片
global 显示顺序 = []
procedure 显示(列表) {
显示顺序 = []
var 项 = []
for i = length(列表) to 1 step -1 {
var 条 = select(列表, i)
listAdd(显示顺序, 条)
var 说明 = dictget(条, "note", "")
listAdd(项, 列表显示框_相册.CreateElement(choose(说明 == "", "(没写说明)", 说明), dictget(条, "time", ""), dictget(条, "url", "")))
}
列表显示框_相册.Elements = 项
}
when 列表显示框_相册.AfterPicking() {
var 条 = select(显示顺序, 列表显示框_相册.SelectionIndex)
图像_预览.Picture = dictget(条, "url", "")
}
- 「创建列表元素」的第三个参数就是图片,直接给网络地址;列表显示框会自己下载、缓存。
- 列表是倒序显示的,点击时的「选中项索引」对应的是倒序后的顺序,所以另存一份
显示顺序来取。
跑起来看看
- 上传完成:上面的大图换成服务器上的图,提示「上传成功」,说明清空,等下一张。
- 另一台手机打开:刷新后列表里有这张图和说明。
- 点列表里的一张:上面显示大图。
- 关掉服务端再上传:提示连不上,按钮恢复;开回服务端再点上传就成功了。
常见问题
| 现象 | 原因与办法 |
|---|---|
| 一直「正在上传」,按钮是灰的 | 缺了「出现错误时」的处理,服务端连不上时没有事件恢复按钮。照第四步补上 |
| 提示服务地址不对 | 口令抄错,服务端返回 404 |
| 提示图片超过 5MB | 没压缩就上传了。确认传给 PostFile 的是压缩后的路径 |
| 列表里有文字没图片 | 地址打不开:手机能打开服务地址页面吗?服务端换了电脑或 IP 后,旧图片地址里的 IP 也就失效了 |
| 图片方向是横的 | 压缩会按照片里的方向信息自动摆正;直接上传原图才可能出现 |
| 服务端重启后图片还在吗 | 在。图片存在服务端文件夹的 files 里,备份时连同 tinywebdb.sqlite3 一起备份 |
动手改一改
- 在压缩前显示原图大小、压缩后显示新大小,看看压缩省了多少。
- 上传前加一个「拍照」按钮(照相机组件),拍完直接上传。
下一课
第 5 课 带图商品墙:把前四课串起来——发布带图片的商品、搜索、看详情、下架自己的商品。
用到的组件
图像选择器 · 图片处理 · Web 客户端 · 图像 · 列表显示框 · 网络微数据库
验证范围
最终 .aia 已通过正式编辑器载入与保存重开(0 错误)、后台导入和 Android APK 编译;连真实服务端执行积木检查 19 项(按 Web 客户端的分块方式真实上传、取回的图片是压缩后的 1280×960 JPEG、竖图缩到 720×1280、口令错误 404、压缩失败、服务端关掉时的出现错误时、重试成功等),见验证记录。真机相册选图、真机运行与 AI 伴侣联调尚未测试。
扫码添加客服咨询