尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
移动端购物车实战:三层数据结构与价格计算全解析
1. 购物车这个小功能到底难在哪移动端购物车乍一看就是个列表加勾选框很多刚入行的朋友会觉得这东西两天就能撸完。但我带过几个类似项目之后发现真正把它做到能用、好用、经得起折腾工作量远超预期。它牵扯到的东西特别杂本地状态和远端数据的同步、勾选逻辑的联动、价格计算的精度、优惠券和满减的叠加、库存校验、失效商品的兜底、编辑态和结算态的切换还有各种边界情况。任何一个环节没想清楚上线之后都会被用户骂。这篇文章我打算把模仿移动端淘宝购物车这个项目从头到尾拆一遍。核心关键词就是购物车、移动端、状态管理、价格计算、勾选联动、本地持久化、结算流程。我会讲清楚整体设计思路、核心模块怎么拆、关键代码怎么写、参数怎么算以及我在实际做的时候踩过的那些坑。适合谁看前端有一定基础、想做一个完整电商类交互模块的同学也适合做后端、想理解前端购物车数据结构的同学。哪怕你只是想搞懂为什么购物车的价格总是算不对这篇也能给你答案。先说清楚一件事我这里讲的模仿指的是交互逻辑和数据结构的复刻不涉及任何真实平台的私有接口、加密协议或者商业数据。所有实现都基于公开可见的通用交互模式用自己搭的模拟数据来跑。这一点先摆明后面就不重复了。购物车本质上是一个**临时订单容器。用户把想买的东西丢进去改数量、挑规格、勾选一部分最后结算。它同时承担了三个角色一是暂存区**二是决策辅助工具帮用户对比、凑单三是价格计算器。这三个角色决定了它的数据结构必须同时满足易修改易展示易计算三个要求。很多人做不好就是因为只把它当成一个数组来存没考虑后面两个角色。我见过最常见的错误做法是把购物车数据直接塞进一个扁平数组每个元素就是商品对象勾选状态、数量、规格全混在一起。这样写前期很爽等到要做按店铺分组跨店满减选中商品总价的时候代码就开始到处打补丁最后变成一坨。正确的思路是分层商品层、店铺层、购物车层每一层管自己的事。这个分层思想是整篇文章的主线后面所有细节都围绕它展开。2. 整体架构与数据结构设计2.1 为什么一定要做三层结构移动端购物车和 PC 端最大的区别在于屏幕窄、操作靠手指、信息密度要求高。用户希望一眼看到哪些店的东西、每样多少钱、一共多少钱。如果数据结构是扁平的渲染的时候就得在模板里现算分组性能差不说逻辑还容易乱。三层结构是这样分的商品层Item单个 SKU 的信息包括商品 ID、规格、单价、数量、库存、是否选中、是否失效。店铺层Shop一个店铺下的所有商品加上店铺级的选中状态、店铺优惠信息。购物车层Cart所有店铺的集合加上全局的选中状态、总价、总数量、优惠汇总。这么分的好处是每一层的全选逻辑都能独立处理。店铺的全选只影响自己店内的商品购物车的全选影响所有店铺。计算总价的时候从商品层往上汇总逻辑清晰不会串。我用一个简化的 TypeScript 接口来描述这个结构方便你直接抄interface CartItem { itemId: string; skuId: string; title: string; spec: string; price: number; // 单价单位分 quantity: number; stock: number; selected: boolean; invalid: boolean; // 是否失效 invalidReason?: string; } interface CartShop { shopId: string; shopName: string; items: CartItem[]; selected: boolean; // 店铺级选中 promotions: Promotion[]; } interface Cart { shops: CartShop[]; selected: boolean; // 全局选中 totalPrice: number; totalCount: number; }注意价格我用的是分做单位不是元。这是血泪教训。浮点数在 JavaScript 里做加减会出精度问题0.1 0.2 ! 0.3这个经典问题在购物车里会直接导致用户看到合计 99.99999999 元。所有价格在存储和计算时都用整数分只在展示的时候除以 100 并格式化。这个原则后面还会反复提到。2.2 状态管理方案怎么选移动端购物车的状态管理常见的有三种方案组件内部 state、全局状态库如 Redux、Pinia、Zustand、以及本地持久化加内存缓存。我的建议是购物车数据必须走全局状态并且做本地持久化。原因很简单购物车数据会被多个页面用到——商品详情页的加入购物车、购物车页本身、结算页、甚至首页的购物车角标。如果只放在购物车页的组件里其他页面拿不到就得靠事件总线或者层层传参维护成本极高。具体选哪个库看你的技术栈。Vue 用 PiniaReact 用 Zustand 或 Redux Toolkit 都行。核心是把这个 store 设计好暴露几个关键 actionaddItem(item)加入商品已存在则数量累加。removeItem(itemId)删除商品。updateQuantity(itemId, quantity)改数量带库存和上下限校验。toggleItem(itemId)切换单个商品选中。toggleShop(shopId)切换店铺全选。toggleAll()切换全局全选。clearInvalid()一键清理失效商品。本地持久化用localStorage或移动端的 storage API把整个 cart 序列化存进去。但要注意持久化的只是用户意图不是最终数据。比如价格、库存这些会变的信息本地存的只是快照进入购物车页时必须重新拉一次最新数据做校验。这个点很多人忽略导致用户看到的价格和结算时不一致投诉就来了。2.3 数据同步的时机与策略购物车的数据同步是个容易被低估的难点。用户在多个设备上操作、或者本地改了数量但没提交这些情况都要处理。我的策略是**本地优先异步同步冲突以服务端为准**。具体来说用户任何操作先改本地状态界面立即响应保证流畅。同时发起一个异步请求把变更同步到服务端。进入购物车页时先展示本地缓存同时拉取服务端最新数据。服务端数据回来后做一次合并校验价格更新、库存不足的标记失效、已下架的标记失效。如果本地有未同步的变更以服务端为准但要给用户提示。这套策略的核心是不阻塞用户操作。移动端用户对卡顿极其敏感任何等待超过 300ms 的操作都会让人觉得这 App 好卡。所以本地先改、界面先动是最基本的体验保障。3. 核心交互逻辑的拆解与实现3.1 勾选联动的三种状态勾选联动是购物车最核心也最容易写错的逻辑。它有三层单个商品、店铺、全局。三者之间的关系是单个商品全选 → 店铺选中。所有店铺选中 → 全局选中。全局选中 → 所有店铺和商品都选中。这里有个细节店铺的选中状态有三种不是两种。全选、全不选、部分选中半选。半选状态在 UI 上通常显示为一个横杠或者特殊图标。很多人只做了布尔值结果半选状态显示不出来用户看着别扭。实现上店铺的selected可以是个派生值不直接存。每次商品勾选变化后重新计算店铺状态function computeShopSelected(items) { const validItems items.filter(i !i.invalid); if (validItems.length 0) return false; const selectedCount validItems.filter(i i.selected).length; if (selectedCount 0) return false; if (selectedCount validItems.length) return true; return indeterminate; // 半选 }全局状态同理遍历所有店铺计算。这里要注意失效商品不参与全选计算。如果用户有一件失效商品没勾其他都勾了全局应该显示全选而不是半选。这个细节直接影响用户对全选的预期。3.2 数量修改的边界处理数量修改看着简单其实边界特别多最小值是 1减到 1 就不能再减或者弹出删除确认。最大值是库存超过库存要提示库存不足。输入框要限制只能输数字且不能有前导零。用户手动输入时可能输入空、输入超大数、输入小数都要处理。快速连点加减号要做防抖避免请求风暴。我一般会封装一个clampQuantity函数统一处理function clampQuantity(input, stock) { let n parseInt(input, 10); if (isNaN(n) || n 1) n 1; if (n stock) n stock; return n; }输入框的失焦事件里调用这个函数把用户输入规整成合法值。加减按钮则直接基于当前值加减再 clamp 一次。提示数量修改后不要立即发请求用 300ms 防抖。用户连点五下加号只发最后一次请求能省掉大量无效通信。3.3 价格计算的精度与优惠叠加价格计算是购物车的心脏。前面说了用分做单位这里展开讲计算流程。单个商品的小计 单价 × 数量。这个简单。复杂的是优惠叠加。常见的优惠有店铺满减满 100 减 10满 200 减 30。店铺优惠券满 150 减 20。平台满减跨店满 300 减 50。单品折扣某商品打 8 折。这些优惠的叠加顺序和计算基数都有讲究。一般来说单品折扣先算然后是店铺级优惠最后是平台级优惠。因为平台满减通常基于店铺优惠后的金额来算。计算流程我整理成一张表方便对照步骤计算内容基数说明1单品小计单价 × 数量用分计算2单品折扣单品小计如 8 折则 × 0.83店铺小计店内选中商品求和折扣后金额4店铺满减店铺小计满足门槛才减5店铺优惠券店铺小计与满减可叠加6平台满减所有店铺优惠后求和跨店计算7最终应付平台优惠后金额不低于 0每一步都要用整数分计算折扣用乘法后取整一般向下取整对用户有利。取整方式要和后端对齐否则会出现前端显示 99.99、后端算出 100.00 的尴尬。注意优惠计算建议前端只做预估展示最终以服务端返回为准。前端算错顶多显示不对服务端算错就是资损。所以结算页一定要用服务端返回的金额覆盖前端计算值。3.4 失效商品的兜底策略失效商品包括已下架、已售罄、已删除、价格变动过大等。这些商品不能参与结算但也不能直接从购物车删掉因为用户可能还想看看。我的处理方式是失效商品置底显示和有效商品用分割线隔开。失效商品默认不勾选且不可勾选。失效商品不参与任何价格计算和全选逻辑。提供一键清理失效商品按钮。失效商品上标注失效原因比如已售罄已下架。这个设计的关键是让用户感知到失效但不打扰用户。如果失效商品混在有效商品里用户全选的时候会一脸懵为什么总价对不上置底加不可选就解决了这个问题。4. 完整实操流程与关键代码4.1 项目初始化与目录结构假设你用 Vue 3 Pinia Vite 来做初始化命令npm create vitelatest cart-demo -- --template vue-ts cd cart-demo npm install pinia目录结构我建议这样组织src/ stores/ cart.ts # 购物车 store utils/ price.ts # 价格计算工具 storage.ts # 本地持久化 components/ CartItem.vue # 单个商品 CartShop.vue # 店铺分组 CartFooter.vue # 底部结算栏 views/ Cart.vue # 购物车页 mock/ cartData.ts # 模拟数据这个结构的好处是职责清晰。store 管数据utils 管纯函数components 管展示。价格计算这种纯逻辑抽到 utils 里方便写单元测试也方便复用。4.2 购物车 store 的完整实现store 是整个购物车的核心我把它写完整import { defineStore } from pinia; import { clampQuantity } from /utils/price; import { saveCart, loadCart } from /utils/storage; export const useCartStore defineStore(cart, { state: () ({ shops: loadCart() || [], }), getters: { // 所有有效商品 validItems(state) { return state.shops.flatMap(s s.items.filter(i !i.invalid)); }, // 选中的有效商品 selectedItems(): any[] { return this.validItems.filter((i: any) i.selected); }, // 总数量 totalCount(): number { return this.selectedItems.reduce((sum: number, i: any) sum i.quantity, 0); }, // 总价分 totalPrice(): number { return this.selectedItems.reduce( (sum: number, i: any) sum i.price * i.quantity, 0 ); }, // 全局选中状态 allSelected(state): boolean | string { const valid state.shops.flatMap(s s.items.filter(i !i.invalid)); if (valid.length 0) return false; const sel valid.filter(i i.selected).length; if (sel 0) return false; if (sel valid.length) return true; return indeterminate; }, }, actions: { toggleItem(itemId: string) { for (const shop of this.shops) { const item shop.items.find(i i.itemId itemId); if (item !item.invalid) { item.selected !item.selected; break; } } this.syncShopSelected(); saveCart(this.shops); }, toggleShop(shopId: string) { const shop this.shops.find(s s.shopId shopId); if (!shop) return; const valid shop.items.filter(i !i.invalid); const allSel valid.every(i i.selected); valid.forEach(i (i.selected !allSel)); saveCart(this.shops); }, toggleAll() { const target this.allSelected ! true; for (const shop of this.shops) { shop.items.forEach(i { if (!i.invalid) i.selected target; }); } saveCart(this.shops); }, updateQuantity(itemId: string, qty: number) { for (const shop of this.shops) { const item shop.items.find(i i.itemId itemId); if (item) { item.quantity clampQuantity(qty, item.stock); break; } } saveCart(this.shops); }, removeItem(itemId: string) { this.shops this.shops .map(shop ({ ...shop, items: shop.items.filter(i i.itemId ! itemId), })) .filter(shop shop.items.length 0); saveCart(this.shops); }, clearInvalid() { this.shops this.shops .map(shop ({ ...shop, items: shop.items.filter(i !i.invalid), })) .filter(shop shop.items.length 0); saveCart(this.shops); }, syncShopSelected() { for (const shop of this.shops) { const valid shop.items.filter(i !i.invalid); shop.selected valid.length 0 valid.every(i i.selected); } }, }, });这段代码有几个关键点值得说。第一toggleItem之后调用了syncShopSelected保证店铺状态跟着更新。第二removeItem之后如果店铺空了把整个店铺也删掉避免出现空店铺。第三所有修改操作最后都调saveCart做持久化。第四getter 里用flatMap把嵌套结构拍平方便计算。4.3 价格计算工具的实现价格计算单独抽出来写成纯函数// 单位分 export function calcItemSubtotal(price: number, quantity: number): number { return price * quantity; } export function calcShopSubtotal(items: any[]): number { return items .filter(i i.selected !i.invalid) .reduce((sum, i) sum i.price * i.quantity, 0); } export function applyShopPromotion(subtotal: number, promotions: any[]): number { let result subtotal; for (const p of promotions) { if (p.type fullReduce subtotal p.threshold) { result - p.reduce; } if (p.type coupon subtotal p.threshold) { result - p.amount; } } return Math.max(result, 0); } export function applyPlatformPromotion(total: number, rules: any[]): number { let result total; for (const r of rules) { if (total r.threshold) { result - r.reduce; } } return Math.max(result, 0); } export function formatPrice(cents: number): string { return (cents / 100).toFixed(2); }注意Math.max(result, 0)防止优惠叠加后算出负数。虽然理论上不该出现但防御性编程能避免线上事故。4.4 底部结算栏的联动底部结算栏要展示全选按钮、总价、优惠信息、结算按钮。它和 store 的联动是自动的因为用的是 getter。template div classcart-footer label classselect-all input typecheckbox :checkedcart.allSelected true :indeterminatecart.allSelected indeterminate changecart.toggleAll() / 全选 /label div classtotal span合计/span span classprice¥{{ formatPrice(cart.totalPrice) }}/span /div button classcheckout :disabledcart.totalCount 0 clickgoCheckout 结算({{ cart.totalCount }}) /button /div /template这里:indeterminate是原生 checkbox 的属性用来显示半选状态。注意它只能通过 JS 设置不能通过 HTML 属性设置所以要用:indeterminate绑定。结算按钮的禁用逻辑是totalCount 0也就是没有选中任何有效商品时禁用。这个细节很重要否则用户点进去结算页发现是空的体验很差。4.5 本地持久化的实现持久化用 localStorage但要处理序列化和版本问题const CART_KEY cart_v1; export function saveCart(shops: any[]) { try { localStorage.setItem(CART_KEY, JSON.stringify(shops)); } catch (e) { console.warn(购物车持久化失败, e); } } export function loadCart(): any[] | null { try { const raw localStorage.getItem(CART_KEY); if (!raw) return null; const data JSON.parse(raw); if (!Array.isArray(data)) return null; return data; } catch (e) { return null; } }key 里带版本号v1是为了将来数据结构变了能平滑升级。如果直接叫cart将来改结构时老数据会解析失败用户购物车就丢了。带版本号的话新版本读cart_v2读不到再尝试迁移cart_v1就安全多了。提示localStorage 有 5MB 左右的限制购物车数据一般不会超。但如果你的商品对象特别大比如存了长描述、多张图片 URL要注意精简只存必要字段。5. 常见问题与排查技巧实录5.1 价格算不对的排查思路价格算不对是最常见的问题我总结了一套排查顺序先看单位是不是有的地方用元、有的地方用分混用必错。再看浮点有没有直接用0.1 0.2这种运算有就改成整数分。看取整折扣计算后有没有取整取整是向上还是向下前后端是否一致看筛选计算时有没有正确过滤掉未选中和失效商品看叠加顺序优惠是不是按正确顺序叠加的我遇到过一次前端显示 199.99后端返回 200.00查了半天发现是前端折扣计算用了Math.round后端用了Math.floor。统一成Math.floor就好了。这种问题一定要前后端对齐取整规则最好写进接口文档。5.2 全选状态错乱的定位方法全选状态错乱通常是这几个原因失效商品参与了全选计算。店铺状态没有在商品变化后同步更新。半选状态没处理只用了布尔值。异步数据回来后没有重新计算状态。排查的时候我会在toggleItem、toggleShop、toggleAll三个 action 里都打日志打印当前所有商品的选中状态和计算出的全局状态。跑一遍操作看哪一步状态对不上问题就定位了。5.3 数量修改的防抖与竞态数量修改如果每次都发请求快速连点会产生竞态先发的请求后回来把后发的覆盖了。解决办法有两个防抖300ms 内的连续操作只发最后一次。请求序号每次请求带一个自增序号回来时只接受最新序号的响应。我一般两个都用。防抖减少请求量序号保证顺序正确。代码大概这样let seq 0; async function syncQuantity(itemId, qty) { const mySeq seq; const res await api.updateQuantity(itemId, qty); if (mySeq ! seq) return; // 过期响应丢弃 // 处理结果 }5.4 常见问题速查表问题现象可能原因解决方向总价有小数尾巴浮点运算改用整数分全选后总价对不上失效商品参与计算过滤失效商品半选状态不显示只用了布尔值引入 indeterminate数量能减到 0没做下限校验clamp 到 1快速点击数量错乱请求竞态防抖 序号刷新后购物车丢失没持久化localStorage价格与结算页不一致本地快照过期进页面重新拉取删除后店铺还在没清理空店铺filter 空店铺这张表我建议直接贴在工位上遇到问题先对照一遍能省不少时间。5.5 几个我踩过的坑第一个坑是库存校验的时机。我一开始只在加入购物车时校验库存结果用户加完之后商品被抢光了结算时才报错。后来改成进入购物车页和进入结算页都校验一次库存不足的直接标记失效体验好很多。第二个坑是规格变更。同一个商品不同规格其实是不同的 SKU价格和库存都可能不同。我一开始用商品 ID 做唯一键结果用户改了规格购物车里出现两条一样的记录。后来改成用skuId做唯一键加入时如果 skuId 已存在就累加数量问题解决。第三个坑是优惠券的互斥。有些优惠券不能叠加有些可以。我一开始没做互斥判断用户选了两张互斥的券价格算出来是负的。后来加了互斥规则表选券时动态禁用互斥的选项才正常。第四个坑是大数量下的性能。购物车商品超过 50 件时每次勾选都全量重算界面会卡。后来把计算拆成按店铺缓存小计只有变化的店铺才重算性能提升明显。6. 性能优化与体验细节6.1 长列表的渲染优化购物车商品多的时候长列表渲染是性能瓶颈。移动端尤其明显因为设备性能参差不齐。我的做法是虚拟列表 分组渲染。虚拟列表只渲染可视区域内的商品滚动时动态替换。分组渲染则是按店铺分组每个店铺作为一个渲染单元减少 DOM 节点数量。如果不想引入虚拟列表库至少要做到列表项组件化 key 稳定。key 用skuId而不是索引这样 Vue 或 React 的 diff 才能正确复用节点。用索引做 key 的话删除中间一项会导致后面所有项都重新渲染性能差还容易出 bug。6.2 图片懒加载与占位购物车里的商品图不少全量加载会拖慢首屏。用loadinglazy或者 Intersection Observer 做懒加载图片进入视口才加载。同时给图片容器设固定宽高比避免加载完成后布局抖动。占位图我一般用一个浅灰色背景加商品名首字比空白好看也比转圈图标省事。6.3 交互反馈的细节移动端交互反馈特别重要用户手指点下去要有回应。几个细节勾选框点击有缩放动画150ms 左右。数量加减按钮按下有背景色变化。删除操作有确认弹窗或者左滑删除加撤销提示。结算按钮点击后立即置灰并显示 loading防止重复提交。这些细节单个看都很小但加起来就是这个 App 好不好用的分水岭。6.4 无障碍与适配购物车的勾选框要用原生input[typecheckbox]或者加rolecheckbox和aria-checked方便读屏软件识别。按钮要有足够的点击区域移动端建议不小于 44×44 像素。适配方面用rem或vw做弹性布局字号用clamp限制范围避免小屏字太小、大屏字太大。底部结算栏要处理安全区域用env(safe-area-inset-bottom)给全面屏留出空间。7. 一些延伸思考购物车这个模块做完之后其实可以延伸出不少东西。比如购物车推荐根据用户加购的商品推荐相似或互补商品比如凑单提示当用户差一点就能满减时提示再买 XX 元可减 XX再比如价格监控用户加购的商品降价了给个提醒。这些延伸功能的共同点是都建立在购物车已有的数据结构之上。这也是为什么我一开始强调数据结构要设计好——结构对了后面加功能就是水到渠成结构错了每加一个功能都要重构一遍。我个人在实际操作中的体会是购物车这种看起来简单的模块最能体现一个开发者的工程素养。它不难在某个技术点难在把几十个细节都考虑到、都处理好。写完能跑是一回事上线不出问题是另一回事。多花时间在数据结构和边界处理上比事后打补丁划算得多。最后再分享一个小技巧做购物车的时候我会专门建一个边界用例清单把能想到的异常情况都列进去——空购物车、全失效、库存为 1、数量为 0、优惠叠加到负数、网络断开、本地存储满……每实现一个功能就对着清单过一遍。这个习惯帮我挡掉了大量线上问题你也可以试试。
RELATED

相关推荐

分形盒维数计算实战:一维到三维的代码实现与避坑指南

分形盒维数计算实战:一维到三维的代码实现与避坑指南

简介:这套MATLAB工具资源专门面向需要掌握分形几何与盒维数计算的科研人员和工程师,提供1D、2D、3D三种空间下的分形盒维数计算实现,覆盖科赫曲线、曼德勃罗集、Sierpinski海绵等典型分形对象。包内共27个文件,以.m脚本、png结果图…

📅 2026/10/13 11:43:59
Agent Skills技能化拆分:破解长提示词难题的工程实践

Agent Skills技能化拆分:破解长提示词难题的工程实践

从去年开始,凡是认真搞过Agent应用的人,应该都体会过同一件头疼事:指令越写越长,几百行提示词堆在系统提示里,改一处就要连带改三处,模型还时不时把后面的规则忘了。后来我在不少项目里尝试给智能体做“技能…

📅 2026/10/13 11:38:58
Windows下Emacs+Python开发环境配置实战

Windows下Emacs+Python开发环境配置实战

1. 项目概述:为什么在Windows上配好Emacs的Python开发环境,比想象中更值得投入时间Emacs Python 在 Windows 上长期被默认为“小众中的小众”——很多人第一反应是“何必自找麻烦”,转头就去用 PyCharm 或 VS Code。但如果你真花三天时间把这…

📅 2026/10/13 11:38:58
MORE NEWS

更多资讯

📰

C++运算符重载实战:从零实现健壮可靠的Date日期类

看到这个题目,我第一反应是:经典。只要是学 C 运算符重载,日期类几乎是绕不开的标配练习,课程作业里有它,笔试面试的机考题里也常有它的变体。A同学前几天把题目拍给我看,要求就是标题里那几句:…

📰

STM32+WiFi智能插座定时功能实战:本地RTC与云端同步避坑指南

简介:这份资源面向嵌入式开发初学者与智能家居DIY爱好者,提供基于STM32单片机实现WiFi控制家电插座开关及定时功能的完整工程方案,帮助读者理解从硬件选型到固件开发的整体流程。压缩包共224个文件,约101.37MB,包含32个…

📰

PHP实现weixin://跳转:APP唤起微信的完整方案

简介:APP跳转神器是一套基于PHP的微信跳转源码,核心解决从手机APP或浏览器端唤起微信并跳转到指定网页的需求,适用于公众号文章推广、营销二维码落地页、微信内打开任意网址等场景。使用者只需点击链接即可唤起微信,无需复制粘贴&…

📰

纯原生HTML5音乐播放器:零依赖、可嵌入、真可用

1. 项目概述:一个真正能用、能改、能嵌入的轻量级音乐播放器 “HTML简单音乐播放器”这八个字,乍看平平无奇,但在我过去十年带过的几十个前端入门项目里,它几乎是新手跨过“静态页面”门槛、第一次触摸“交互逻辑”的关键跳板。不…

📰

Docker Compose 一键部署教程:从单体到微服务完整实践

微服务 / 单体项目 Docker Compose 一键部署教程(完整版) 1. 这方案到底解决什么问题 先聊个场景。你手里有一个单体项目,或者一套微服务集群,本地跑得正欢,但要交付给同事联调、部署到测试服务器、或者换一台新机器&…

📰

微信小程序图片视频上传Flask后端存储完整demo与避坑指南

简介:面向微信小程序开发者与Flask后端初学者,这份demo完整演示了图片和视频从前端采集、预览到后端接收存储的链路。前端是不需要复杂配置的微信小程序工程,可直接用微信开发者工具打开,涵盖照片/视频选择、预览与上传交互&#…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬