news 2026/10/1 18:54:53

微信小程序外卖实战:请求封装、分页加载与图片上传

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序外卖实战:请求封装、分页加载与图片上传

做外卖类小程序总有一个绕不开的节点,那就是从“能看页面”过渡到“能真正下单”。如果你跟的是苍穹外卖这套实战项目,到了 Day6 这个阶段,说明已经过了环境搭建和基础页面的坎,开始碰微信小程序里真正硬核的东西:请求封装、列表分页、图片上传,还有一堆绕不开的页面细节。这篇内容就围绕 Day6 这一天的实操展开,我会把跑通这套流程的经验和踩坑点都整理出来,适合正在跟苍穹外卖的学员,也适合所有第一次用原生微信小程序做电商类项目的开发者。

做项目这件事,有时候看起来功能都做完了,但用起来总差点意思,往往就是差在这些看不见的基础层里。这篇内容不会教你机械敲代码,而是把为什么要这样设计、哪些地方容易翻车讲明白,你照着走的时候能少走不少弯路。

1. 项目结构与整体设计思路

1.1 先厘清小程序端和后端的分工

苍穹外卖这个项目本身是前后端分离的,后端用 Spring Boot 提供接口,管理后台一般用 Vue 或者 React 开发,用户端跑在微信小程序里。Day6 之前的任务基本都在搭后端和后台管理页面,到了 Day6 才真正把用户端小程序串起来。

我刚拿到 Day6 需求的时候,第一反应是赶紧写页面,毕竟列表页、详情页的 UI 摆在那里,视觉上很有成就感。但实际操作下来才发现,Day6 真正的工作量不在页面,而在数据链路。小程序端必须想清楚三件事:接口怎么请求、数据怎么分页、用户操作怎么反馈。如果不先把这三件事定下来,后面很可能返工。

我遇到过不少同学在页面里直接写wx.request,一个接口调七八处,后端接口域名改个端口,全局搜出来几十处替换。这种写法在小项目里看着没问题,但苍穹外卖这种几十个接口的项目绝对不能这么干。所以 Day6 的第一步,我建议不是写按钮,而是先把项目的请求层设计出来。

1.2 目录划分和 app.json 全局配置

小程序的工程结构虽然官方给了规范,但实际项目里每个人组织方式都不一样。我在这个项目里用的目录结构大概是这样的:

  • pages:存放所有页面,每个页面一个目录,目录名和页面名保持一致
  • components:自定义组件,比如菜品卡片、数量选择器
  • utils:工具函数,包括 request 封装、格式化、常量
  • api:接口层,按业务模块拆成dish.js、cart.js、order.js
  • static:静态资源,图片、图标等

接口层单独拆出来是我个人非常推荐的做法。举个例子,首页要调菜品接口,在api/dish.js里写一个函数:

import { getRequest } from '../utils/request'; export function getDishList(params) { return getRequest('/dish/list', params); }

页面里只需要import { getDishList } from '../../api/dish',然后调用函数就行。接口路径、参数结构都和页面逻辑分离,后面如果后端改了接口名,你只需要改api层。

app.json 的全局配置同样不能轻视。你要在 app.json 里注册页面路径、配置window的导航栏标题和背景色、定义tabBar。外卖小程序用户端通常有“首页”“订单”“我的”几个底部 tab,建议 Day6 第一天就把 tabBar 的图标和页面路径规划好,不要等到页面写完了再补。否则改一次 tabBar,页面之间跳转的路径全部要核对一遍,特别容易漏。

页面级的.json文件要注意一个小细节:如果你想让某个页面用自定义导航栏(比如首页顶部放搜索框和定位),要在该页面的 .json 里配置"navigationStyle": "custom"。这个配置只对当前页面生效,不会影响其他页面。很多新手在这里踩坑是因为把它配到了 app.json 的 window 里,结果发现全局导航栏都没了,半天不知道怎么回事。

2. 请求封装:先把地基打好

2.1 为什么要统一封装而不是裸调 wx.request

微信小程序的wx.request是页面开发中最频繁使用的 API,但裸用问题很多。第一,每次都要写完整 URL,接口域名一换,页面全要改。第二,每个页面各自处理success和fail,代码重复度极高。第三,一旦需要统一的 token 注入、错误提示、加载等待,散落在各处的代码会让你改到崩溃。

我自己带项目的时候,定过一个规矩:页面文件里不允许出现wx.request,必须走封装后的方法。这条规矩看着有点绝对,实际执行起来受益很大。请求一旦异常,你只需要在一个文件里排查;接口返回的数据结构不规范,也只需要在一个地方兼容。

封装的核心目标有四个:统一域名配置、统一请求头、统一处理状态码、统一错误提示。这四个统一做完,小程序的网络层才算站得住。

2.2 封装实践:Promise 化、header 注入与状态码判断

用原生语法封装一个 Promise 风格的 request,核心代码其实不长。我贴一段我在项目中使用的典型实现,细节都标在注释里:

const BASE_URL = 'https://api.example.com'; // 根据环境切换 const request = (url, method = 'GET', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}${url}`, method, data, timeout: 10000, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success: (res) => { // 第一层判断:HTTP 状态码 if (res.statusCode === 200) { const body = res.data; // 第二层判断:业务状态码 if (body.code === 1) { resolve(body); } else if (body.code === 401) { wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(body); } else { wx.showToast({ title: body.msg || '请求失败', icon: 'none' }); reject(body); } } else { wx.showToast({ title: '网络异常', icon: 'none' }); reject(res); } }, fail: (err) => { wx.showToast({ title: '网络请求失败', icon: 'none' }); reject(err); } }); }); }; export function getRequest(url, params) { return request(url, 'GET', params); } export function postRequest(url, data) { return request(url, 'POST', data); }

这里最容易被忽略的是两层状态码的判断。很多后端接口在 HTTP 正常返回 200 的情况下,业务 code 可能是 401 表示登录过期。如果只判断statusCode,用户就会在一个登录已过期的页面里继续看到一堆报错。我在项目里最重要的一次改造,就是加入了 401 自动清除登录态并跳转登录页的逻辑,从那以后线上反馈少了一大半。

2.3 封装之后的调用方式和边界情况

封装好了,页面里调用就变成了 Promise 的写法,非常清爽:

import { getDishList } from '../../api/dish'; getDishList({ categoryId: this.data.currentCategoryId }) .then((res) => { this.setData({ dishList: res.data }); }) .catch((err) => { console.error('获取菜品失败', err); });

这里有两个边界情况值得说。第一个是并发请求的 loading 处理。如果在封装里统一打开wx.showLoading,那么多个请求并发时,先完成的请求关闭 loading,后完成的还没结束,loading 就提前消失了。我的处理方式是维护一个计数器,每次请求创建时加一,结束时减一,归零才关闭 loading。当然,如果你更习惯在页面级别手动控制 loading,那封装里就不要统一加 loading 提示。

第二个是请求超时。wx.request默认超时在低版本基础库里没设,可以在封装里显式加timeout: 10000,尤其是提交订单这类操作,网络稍慢就容易让用户干等好几十秒没反应。设置超时后,fail回调会收到错误,你再根据errMsg判断是超时还是断网,提示语可以更精准。

3. 页面列表与“加载更多”的实现

3.1 分页参数的三个关键变量

外卖首页、订单列表这类长列表,后端基本都不会一次性把数据全返回,而是用分页。分页接口一般接受page和pageSize,返回数据里通常还有一个total或者hasMore。

我习惯在页面 data 里维护三个变量:list(当前已加载的数据数组)、page(下一页页码)、hasMore(是否还有更多数据)。每次请求成功后,把新返回的数组 concat 到 list 后面,page加一。这里有一个新手常犯的错误:页面下拉刷新时,忘记把page重置为 1,结果刷新后加载的是第二页数据。

下拉刷新和筛选切换都属于“重置场景”。只要数据源发生变化,page必须复位为 1,list必须清空,hasMore必须重置为 true。这三个变量是绑在一起的,改一个就要检查另外两个,不然列表数据一定乱。

3.2 触底加载:页面滚动和 scroll-view 的区别

页面列表的加载更多,触发时机有两种写法。

第一种是普通页面滚动配合onReachBottom。你什么都不用配置,只需要在页面.js里写onReachBottom() {}方法,页面滚动到底部附近就会触发。触发距离默认值是 50px,也可以在页面 .json 里用onReachBottomDistance调整。这种方式的优点是实现简单,缺点是你必须保证页面确实存在滚动区域,如果内容太少撑不满一屏,onReachBottom是不会触发的。

第二种是局部区域滚动,用scroll-view组件。这时触底事件用bindscrolltolower,但有一个关键前提:scroll-view必须有明确的高度,否则它的内容自适应,滚动事件永远不会触发。这是个很隐蔽的坑。外层样式写成height: 100vh,或者用 flex 分配页面剩余的高度,scroll-view才能正常工作。

两种方式不要混用。如果页面已经用了scroll-view做局部滚动,又在页面上写onReachBottom,两套逻辑会互相干扰,要么重复加载,要么完全不触发。我在项目里一般只用第一种,结构简单;只有商品分类切换那种“列表区域独立滚动”的页面,才用scroll-view。

3.3 加载锁、尾部状态和性能优化

触底事件在用户快速滑动的场景下,有可能在几百毫秒内连续触发两三次。如果不加锁,同一个page的请求会被发出去多次,列表里就出现重复数据。我自己加锁的方式很朴素,在 data 里放一个loadingMore:

onReachBottom() { if (this.data.loadingMore || !this.data.hasMore) return; this.setData({ loadingMore: true }); this.fetchDishList(); }

请求完成后把loadingMore设回 false。这个方法简单有效,足够覆盖绝大多数场景。

尾部状态提示建议做成列表的最后一个组件。常见文案是“加载中…”和“没有更多了”。如何决定显示哪一种:loadingMore为 true 时显示“加载中…”,hasMore为 false 时显示“没有更多了”。从用户角度来说,这是个很微妙的细节,但做好了,列表不会让人感觉“卡住”。

性能方面有一点值得提:列表数据量大了以后,setData的开销会变大,因为setData会把数据从逻辑层传到渲染层,数据越大传输越慢。如果订单列表已经几百条,每次 concat 之后 setData 整个列表,滚动会变得卡顿。解决办法之一是wx:for时加上wx:key,让组件能够复用;另一个是如果仍然直接 setData 整个数组,规模不大时也能接受。想深入研究的话,可以了解一下官方扩展库里的长列表组件方案,但 Day6 阶段不需要过早优化。

4. 本地图片上传的完整链路

4.1 选图接口:chooseMedia 与临时路径

在 Day6 里,图片上传最常见的场景是用户评价时上传图片,以及商家编辑菜品时上传菜品图。本地开发的第一步是让用户选图,官方接口现在推荐用wx.chooseMedia,它比旧的wx.chooseImage功能更完整,支持拍照和相册两种方式,count 参数可以限制数量。

选图成功回调里拿到的不是最终可用的图片地址,而是临时文件路径。这个临时路径的生命周期只存在于当前小程序进程,重启之后就会失效。所以正确做法是拿到临时路径后立刻上传,不要把它当作持久地址保存到后端。有一个开发中的常见困惑:开发者工具里看到的临时路径形如http://tmp/xxx.jpg,很多人误以为可以直接当正式图片 URL 使用,结果一发布发现图片全裂了。

4.2 上传方法:wx.uploadFile 的请求差异和 JSON 解析

图片上传使用的是wx.uploadFile,它和wx.request不是一套 API。wx.uploadFile会把文件以 multipart/form-data 的方式提交,你需要指定filePath和name。name是后端接收文件的字段名,这个必须和后端同事约定好。如果后端用 Spring Boot 接收,常见字段名是file或multipartFile,拿不准就去看后端接口代码里的参数注解。

上传时需要额外携带参数,比如用户 id 或者 token,就放在formData里。有些后端要求把 token 放在 header 的Authorization里,wx.uploadFile也支持传 header。你自己封装一个 upload 方法的时候,记得把 request 封装里那套 header 逻辑复用过来,不然就容易出现“普通接口能调通,上传接口一直 401”的怪现象。

还有一个细节是wx.uploadFile返回的res.data是字符串。很多后端会返回 JSON,但微信不会自动帮你解析,必须JSON.parse。我第一次遇到的时候直接在 success 回调里读res.data.code,拿到 undefined,懵了十几分钟才反应过来。后来我会在解析外面加一层 try/catch,避免后端万一返回非 JSON 导致整个页面报错。

4.3 压缩、进度条和九图上传

真实用户上传的图片体积通常不小,尤其是现在手机默认拍照动辄几 MB。不经过处理直接上传,一是上传时间特别长,二是多图场景容易触发内存问题。wx.chooseMedia里虽然有sizeType参数可以选压缩模式,但不同机型的压缩效果参差不齐。如果想要更可控的效果,可以选图之后再用wx.compressImage压缩一遍。压缩参数里的quality取值 0 到 100,外卖场景适合 70 左右,肉眼基本看不出差别,体积却能小不少。

上传进度用onProgressUpdate监听。上传接口支持监听进度回调,参数里有progress。简单的进度条显示在按钮上或者上传区域内就行。我见过不少项目完全忽略这个反馈,用户的图片超过 2MB 时,网络差的情况下会干等十几秒,你说他能不怀疑小程序卡死了吗。

多图上传还有一个顺序问题。如果用户一次选了 9 张图,简单地在 for 循环里发起 9 个异步上传,返回值顺序是不确定的,最后展示的图片顺序可能和选择顺序不一致。我的处理办法是记录原始索引,每个上传任务带上自己的 index,所有任务完成后按索引排好序再一起提交给后端。这个细节在“评价晒图”这类功能里几乎一定会被用户注意到。

5. 绕不开的页面细节:单选、导航栏和生命周期

5.1 单选和 SKU 选择的两种实现思路

外卖小程序的点餐页面经常出现规格选择,比如“辣度:微辣、中辣、特辣”“份量:标准、大份”。在原生小程序里,最简单的方案是radio-group嵌套radio。这个方案代码量最小,而且天然支持单选的互斥逻辑。但问题是原生的 radio 样式很难定制,圆点、颜色、尺寸都有限制,做出外卖 App 那种按钮组样式很费劲。

我更推荐的做法是用view自己模拟选中态。data 里定义selectedIndex,wxml 里用wx:for渲染选项,点击时更新selectedIndex,再根据selectedIndex === index给当前项添加active样式类。视觉上完全可控,交互逻辑也只有一行赋值,适合 SKU 选择。唯一要注意的是,多个规格组不能共用一个selectedIndex,每个规格组要有自己独立的选中状态变量。

5.2 顶部导航栏高度:不要用固定像素

自定义导航栏是外卖类小程序特别常见的需求,因为顶部要放定位、搜索框、购物车入口,原生导航栏展示不了这么丰富的内容。设置自定义导航栏之后,你需要手动处理安全区域的占位。

很多新手会直接写一个固定高度,比如网上流传的 64px 或 88px。但状态栏高度在不同手机上是不同的,iPhone 的刘海机型和安卓机型差距很大。正确做法是动态获取:

const windowInfo = wx.getWindowInfo(); const statusBarHeight = windowInfo.statusBarHeight; const menuButtonRect = wx.getMenuButtonBoundingClientRect();

用这两组数据就能算出导航栏的高度:menuButtonRect.height加上(menuButtonRect.top - statusBarHeight) * 2,然后再加状态栏高度,这就是自定义导航栏应有的总高度。我把这段计算封装成了一个工具函数,所有页面共用。还有一个相关细节是页面底部安全区,iPhone 的 home 指示条会挡住内容,需要给底部预留env(safe-area-inset-bottom)的距离,否则布局会显得局促。

5.3 生命周期:监听用户离开和回到小程序

“用户切走了”这件事,在网页里很难感知,但在小程序里有明确的生命周期。用户点手机 Home 键、切到其他 App、或者小程序从后台恢复,都会触发对应事件。

App 级别有onHide和onShow,页面级别也有onHide和onShow。如果你想在用户离开小程序时保存草稿、记录离开时间、断开某些连接,写在app.js的onHide里最合适。如果只是某个页面需要暂存输入内容,那就写在该页面的onHide里。需要注意,onHide里尽量别做太多事情,切后台之后小程序随时可能被系统销毁,所以用同步 API 写本地存储或者轻量埋点是更稳的。

回到小程序时触发onShow,这是刷新数据的好时机。比如购物车角标数量、订单状态,这类数据在用户离开期间可能已经变化,回到页面时应该重新拉取。我有个习惯:列表页的请求尽量放在onShow而不是onLoad,因为onLoad只在页面首次加载时触发一次,从二级页面返回时不会再触发,数据就可能是旧的。

还有一个需要区分的是onUnload。它只在页面销毁时触发,比如关闭页面或跳到另一个 tab。很多新手把切后台误认为页面卸载,结果发现离开小程序后再回来,onLoad没有被执行。搞清楚onShow、onHide、onUnload三者的区别,很多生命周期相关的 bug 都能避免。

6. 调试、体验版与常见问题排查

6.1 把小程序发给别人试用

本地开发完成后,把代码发给别人试用最规范的方式是上传体验版。第一步,在微信公众平台注册一个小程序账号,拿到真实的 AppID,替换开发者工具里的测试号。第二步,在开发者工具的工具栏点“上传”,输入版本号和备注。第三步,到公众平台的“版本管理 - 开发版本”里,找到刚上传的版本,点击“选为体验版”。

体验版不是谁都能打开,只有添加到“体验成员”名单里的微信号才能扫码使用。你可以在公众平台的“成员管理 - 体验成员”里添加测试同学的微信号。在收集反馈时,我会让测试同学按照“手机型号 + 微信版本 + 操作步骤 + 现象描述 + 截图”这个模板来反馈。没有这个模板,你大概率只会收到“打不开”“很卡”这种没法定位的信息。

上线阶段还有一个提醒:小程序里调用接口必须使用已配置的合法域名,开发时可以在开发者工具右上角勾选“不校验合法域名”,但发布前需要在公众平台的“开发设置 - 服务器域名”里配置 request 和 uploadFile 的合法域名。域名必须是 HTTPS,而且不能带路径。

6.2 五个高频问题和定位方法

我把 Day6 阶段常见的报错和坑整理成了一个表,方便定位。

现象可能原因排查方向
请求返回成功但页面没数据setData 路径错误,或 this 指向问题打印 res,检查路径和箭头函数
图片在开发者工具里正常,真机不显示图片域名不是 HTTPS 或未配合法域名检查图片 URL 协议和公众平台域名配置
触底加载完全不触发内容不足一屏,或 scroll-view 没有高度确认滚动容器高度,确认使用 onReachBottom 还是 bindscrolltolower
uploadFile 返回数据里 code 为 undefinedres.data 是字符串,未 JSON.parse打印 res.data 查看真实结构
按钮连续点击发起多次请求异步请求期间按钮未禁用加 submitting 状态,绑定到按钮 disabled

这五个问题里,最常见、也最隐蔽的是第一个和第三个。setData 路径问题可以通过console.log很快定位,滚动类的坑则需要在开发工具里反复确认页面高度。我的经验是,遇到任何“事件没反应”的问题,先在控制台打一条日志确认事件到底有没有触发,再往下查。省得你对着代码猜半天,结果发现是容器高度没撑开。

6.3 调试工具和网络面板的使用习惯

开发者工具里的 Network 面板比很多人想象的更重要。它能看到每次请求的 URL、请求方法、请求头、响应体、耗时。接口报错但页面没报错时,第一时间开 Network 看返回的数据结构,比在业务代码里逐个 console.log 高效得多。

Console 面板也别浪费。我习惯在 request 封装的 success 和 fail 里加一个判断:开发环境下console.log('[request]', url, 'params:', data),上线前把这种日志统一关掉。这样定位问题时,请求参数一清二楚。还可以在 Network 面板里看请求详情中的响应内容,微信官方有时候会把响应体用树形展示,比看 JSON 字符串直观。

不要小看这些使用习惯,它们能帮你把定位问题的时间从半小时缩短到五分钟。真正到项目联调阶段,接口联调 70% 的时间都在看 Network 面板和 Console 日志。

7. 从 Day6 往后:购物车与订单联动需要注意什么

7.1 数据模型先想明白,页面就是套模板

Day6 做完,下一步一般就是购物车和订单。这两个模块的代码量不大,难点在数据关系。购物车里要保存菜品 id、数量、价格、规格信息,还能实时改数量。如果在页面局部维护一堆零散变量,到订单结算页就会开始混乱。我的建议是先把购物车的数据模型定下来,比如用对象以菜品 id 为 key 存购物车项,而不是用数组反复 find。

时间允许的话,把购物车数据持久化到本地存储,用户退出页面再次进来时能恢复。注意,放在 storage 里的购物车数据在提交订单成功后要记得清除,否则用户会看到“下单成功但购物车还是满的”这种 bug。我在初期就踩过一次,后来在支付成功回调里统一清除购物车本地数据才解决。

7.2 提交订单的幂等和重复提交

提交订单和上传图片不太一样,它涉及服务端创建资源,最怕用户手抖点了两下,结果下了两单。解决重复提交的办法和列表加载锁差不多,页面 data 里加一个submitting,请求期间为 true,按钮禁用。还有后端幂等设计,通常会用唯一请求号,小程序端生成一个 uuid 放在请求体里,后端靠它去重。作为一个完整的实战项目,做到订单接口时会遇到这个点,提前了解它的原理,联调时会顺手很多。

7.3 这一天的实操给我留下的体会

真要我总结这一天,其实一句话就够了:请求层写好了,后面怎么都是顺的。Day6 之前,我也觉得写页面更出成果,但做完了才发现,小程序的体验问题几乎都出在数据请求和交互反馈这些不起眼的地方。加载更多、图片上传、导航栏适配,每一个单独拎出来都不复杂,可它们组合在一起,就是一个外卖小程序能不能被称为“产品”的分水岭。

我自己在带项目的时候反复强调一件事:练 Day6 一定不要只照着代码敲,要主动把网络层封装的思路讲给自己听,把分页触底的触发条件画出来,搞清楚哪些 API 是同步、哪些是异步。这些基本功扎实了,后面接订单、支付、评价的时候,你就会发现自己已经能独立处理问题,而不是每遇到一个报错都要去问一圈。拿今天这套代码多折腾几遍,你会慢慢找到那种“知其然也知其所以然”的感觉,这比把页面跑通本身更有价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 18:51:15

苏州生鲜配送APP开发公司哪家好?

摘要:苏州生鲜配送APP开发公司好不好,关键看它能不能处理生鲜的非标难题:实时库存、称重多规格、冷链温控、时段配送、损耗售后和多种履约模式。本文给出一套可直接对照的判断标准。生鲜电商和普通电商的差别很大。生鲜商品易腐、非标、需要称…

作者头像 李华
网站建设 2026/10/1 18:51:02

RevCol重构YOLOv7 backbone实现头盔小目标检测

简介:本资源是一个基于Reversible-Column-Networks(RCN)改进YOLOv7的电动车头盔佩戴检测系统,面向计算机、电子信息、人工智能等专业本科生及研究生,适用于课程设计、期末大作业与毕业设计等实践场景,聚焦于…

作者头像 李华
网站建设 2026/10/1 18:50:20

Redis接入AI实战:MCP协议与Skill生态让AI直接操作Redis

1. 当 Redis 开始“长脑子”:这次接入到底改变了什么Redis 这个名字,做后端的人基本都绕不开。缓存、分布式锁、排行榜、消息队列、会话存储,几乎每个稍微有点规模的项目里都能看到它的身影。但过去很长一段时间里,Redis 在大家心…

作者头像 李华
网站建设 2026/10/1 18:49:06

哑巴模型Jev实战:从部署到工作流集成

1. 先搞清楚Jev到底是个什么东西 第一次听到“哑巴模型Jev”这个叫法,我估计不少人和我一样,脑子里冒出一堆问号:这又是什么新出的AI玩具?跟市面上那些聊天助手有啥区别?为什么偏偏叫“哑巴”? 我最早接触…

作者头像 李华
网站建设 2026/10/1 18:49:01

小米便签系统化精读:功能拆解、整理流与备份迁移

我的手机里常年装着七八十个App,真正每天打开三次以上的,只有小米便签。它的界面朴素到有点“性冷淡”——一个方方正正的图标,点进去就是一页白纸,没有开屏引导,没有模板商城,甚至没有一句多余的话。很多人…

作者头像 李华
网站建设 2026/10/1 18:47:13

VGG-16图像检索系统实战:Python实现以图搜图与特征提取

简介:这是一套基于Python与VGG-16深度学习模型构建的图像检索系统开发资源,面向计算机、人工智能、通信工程等专业的高校学生、教师及科研从业者,可用于毕业设计、课程设计、项目立项演示或自学进阶。压缩包共255个文件,约41.25MB…

作者头像 李华