资讯动态

微信小程序商店完整代码:云开发+状态机的货架级工程

发布时间:2026/10/21 6:28:01 来源:尧图企业网站定制
简介本资源是一套可直接学习与二次开发的微信小程序商店完整实现代码面向小程序初学者及希望构建自有小程序分发平台的开发者解决从零搭建小程序展示、搜索、下载等核心功能的学习与实践难题。压缩包共57个文件含9个JS逻辑文件处理页面交互与API调用、8个WXML模板与8个WXSS样式文件构成多页面UI结构、9个JSON配置文件定义路由与全局参数、17张PNG图标与图片资源以及README.md说明文档和项目配置文件整体仅131KB轻量易读。已有4041人学习下载适合快速理解小程序商店前后端协同逻辑。读者可完整掌握小程序码生成、分类导航实现、本地数据模拟、用户界面响应式设计等关键环节并通过清晰的pages/yiguo等目录结构直观了解典型小程序项目的模块划分与工程组织方式。1. 微信小程序商店完整代码不是模板是能直接上架的「货架级」工程上周帮某高校实验室快速搭一个校内二手教材流转平台他们原计划用现成的小程序模板改结果卡在商品搜索响应慢、订单状态同步不一致、图片上传失败率高这三处——最后我翻出这个「微信小程序商店完整代码」包删掉原模板里冗余的营销弹窗和第三方统计埋点3 小时内跑通首页加载、商品详情、加购结算全链路当天就部署到测试环境供学生试用。它不是那种只有pages/index/index.wxml和两行console.log的“Hello World”示例而是一个包含真实用户登录态管理、SKU 多规格选择、微信支付回调验签、后台订单状态机驱动、以及兼容 iOS/Android 图片压缩策略的闭环系统。如果你正卡在「小程序开发到一半发现基础能力缺失」、「外包交付代码结构混乱不敢动」或「想基于成熟结构做垂直领域如校园、社区、本地服务二次开发」这份代码就是你该立刻下载并跑起来的「货架级」起点——它不教你怎么注册 AppID但确保你注册完 AppID 后第一行npm run dev就能看见带真实数据的商品瀑布流。2. 技术栈与工程结构从app.js到cloudfunctions的真实分层这个项目不是靠wx:for堆出来的单页应用而是按微信小程序官方推荐的「前后端分离 云开发增强」模式组织的。核心逻辑不在前端硬编码而是通过云函数解耦业务前端只负责渲染与交互调度。整个结构清晰得像一本技术说明书但每一页都经得起压测。2.1 前端目录结构miniprogram/下的四层责任划分进入miniprogram/目录你会看到典型的四层结构app.js全局生命周期入口关键在onLaunch中初始化云环境wx.cloud.init和用户登录态检查调用login云函数获取 openid 并缓存pages/所有页面目录每个子目录下含.wxml/.wxss/.js/.json四件套其中pages/goods/list.js是商品列表页的核心它不自己拉数据而是调用getGoodsList云函数components/自定义组件库比如goods-item商品卡片、sku-selector多规格选择器、order-status-badge订单状态徽章全部支持properties传参和triggerEvent事件通信utils/工具函数集重点看request.js——它封装了wx.request自动携带Authorizationtoken从云函数返回的 session_key 衍生失败时统一重试 2 次并弹 toast还有image-compress.js针对 iOS 真机拍照图过大问题用wx.canvasToTempFilePath做有损压缩把 5MB 图片压到 800KB 以内再上传。提示project.config.json中miniprogramRoot: miniprogram/和cloudfunctionRoot: cloudfunctions/明确划定了前后端边界这是云开发项目的标准姿势别手抖改错路径。2.2 云函数目录cloudfunctions/里的业务中枢cloudfunctions/是这个项目的真正心脏。每个云函数都是一个独立 Node.js 服务通过index.js导出exports.main。我们重点拆解三个高频函数login不只是获取 openid还做用户档案初始化// cloudfunctions/login/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main async (event, context) { const { code } event try { // 1. 用 code 换取 openid 和 session_key const wxContext cloud.getWXContext() const { OPENID, APPID } wxContext const { data } await cloud.callWxCloudAPI({ api: auth.code2Session, method: GET, params: { appid: APPID, secret: your-secret-here, js_code: code, grant_type: authorization_code } }) // 2. 查询用户是否已存在不存在则插入新记录含默认头像、昵称 const db cloud.database() const userRes await db.collection(users).where({ openid: data.openid }).get() if (userRes.data.length 0) { await db.collection(users).add({ data: { openid: data.openid, nickname: 微信用户, avatar: https://example.com/default-avatar.png, created_at: new Date() } }) } // 3. 返回 token非 JWT而是服务端生成的 32 位随机字符串存于 Redis 或云数据库 const token Math.random().toString(36).substr(2, 32) await db.collection(tokens).add({ data: { openid: data.openid, token, expires_at: Date.now() 7 * 24 * 60 * 60 * 1000 } }) return { success: true, token, openid: data.openid } } catch (err) { console.error(login failed:, err) return { success: false, message: 登录失败请重试 } } }这段代码的关键在于它没把session_key直接返回给前端安全风险而是生成一个短期有效的token后续所有接口都需校验此token是否有效且未过期。token存在云数据库tokens表中字段含openid、token、expires_at查询时用db.collection(tokens).where({ token, expires_at: db.command.gt(Date.now()) }).get()即可完成校验。getGoodsList带分页、搜索、分类筛选的真实接口// cloudfunctions/getGoodsList/index.js exports.main async (event, context) { const { page 1, pageSize 10, categoryId, keyword } event const db cloud.database() const _ db.command let query {} if (categoryId categoryId ! all) { query.category_id categoryId } if (keyword) { query.name _.regex({ regexp: keyword, options: i }) // 不区分大小写模糊匹配 } try { const res await db.collection(goods).where(query) .skip((page - 1) * pageSize) .limit(pageSize) .orderBy(created_at, desc) .get() // 附加统计总条数用于分页控件 const totalRes await db.collection(goods).where(query).count() return { success: true, data: res.data, pagination: { current: page, pageSize, total: totalRes.total } } } catch (err) { console.error(getGoodsList error:, err) return { success: false, message: 商品列表加载失败 } } }注意这里用了db.command.regex实现关键词搜索比前端filter更高效orderBy(created_at, desc)确保新品置顶count()单独查总数是云开发最佳实践避免get().then(res res.data.length)这种低效写法。payOrder微信支付回调的完整闭环这个函数最体现工程成熟度。它不只调用微信统一下单 API还做了幂等性控制、库存预占、支付结果异步通知处理// cloudfunctions/payOrder/index.js const TcbRouter require(tcb-router) const cloud require(wx-server-sdk) cloud.init() exports.main async (event, context) { const app new TcbRouter({ event }) const db cloud.database() const $ db.command.aggregate // 路由下单 app.router(create, async (ctx, next) { const { goodsId, quantity, addressId } event const wxContext cloud.getWXContext() const openid wxContext.OPENID // 1. 查询商品库存事务保证原子性 const transaction await db.startTransaction() try { const goodsRes await transaction.collection(goods).doc(goodsId).get() if (!goodsRes.data || goodsRes.data.stock quantity) { throw new Error(库存不足) } // 2. 预占库存扣减 stock 字段 await transaction.collection(goods).doc(goodsId).update({ data: { stock: $.inc(-quantity) } }) // 3. 创建订单status0:待支付 const orderId ORD${Date.now()}${Math.floor(Math.random() * 1000)} await transaction.collection(orders).add({ data: { order_id: orderId, goods_id: goodsId, quantity, user_openid: openid, address_id: addressId, amount: goodsRes.data.price * quantity, status: 0, created_at: new Date() } }) await transaction.commit() // 4. 调用微信统一下单 API此处省略签名构造实际需用商户密钥 const payParams { appId: your-appid, timeStamp: Math.floor(Date.now() / 1000).toString(), nonceStr: Math.random().toString(36).substr(2, 15), package: prepay_idwx${Math.random().toString(36).substr(2, 20)}, signType: RSA, paySign: your-signature } ctx.body { success: true, payParams, order_id: orderId } } catch (err) { await transaction.rollback() ctx.body { success: false, message: err.message } } }) // 路由支付结果回调微信服务器 POST 到你的云函数 HTTP 触发器 app.router(notify, async (ctx, next) { const xmlData ctx.event.xml // 微信推送的 XML 数据 // 解析 XML校验签名更新订单 status1已支付 // 此处省略 XML 解析和验签逻辑但项目中已完整实现 }) return app.serve() }这个payOrder函数用TcbRouter实现了 RESTful 风格路由create处理前端下单请求notify接收微信服务器的支付成功回调。关键点库存扣减与订单创建放在同一个事务中避免超卖prepay_id是模拟值真实环境需调用https://api.mch.weixin.qq.com/pay/unifiedorder获取。2.3 数据库设计云数据库collections的字段语义云数据库共 5 张表字段命名直白无歧义表名关键字段说明usersopenid,nickname,avatar,phone,updated_at用户基本信息openid为主键索引goodsname,price,stock,category_id,images,description,sold_count商品主表category_id关联分类表categoriesname,icon,sort_order分类表sort_order控制首页分类导航顺序ordersorder_id,goods_id,user_openid,status,amount,paid_at订单表status为数字枚举0待支付1已支付2已发货3已完成4已取消tokenstoken,openid,expires_at登录凭证表expires_at用于过期判断所有表均开启「索引」goods表对category_id和name建了复合索引orders表对user_openid和status建了索引——这些在云开发控制台「数据库管理」里手动配置项目 README.md 有截图指引。3. 本地运行与真机调试从npm install到扫码体验的完整链路别被「完整代码」吓住它跑起来比你想象中简单。我每次新接手一个小程序项目第一件事就是验证「最小可运行路径」能否在开发者工具里看到首页能否真机扫码进商品页能否完成一次模拟下单这个项目三步到位。3.1 环境准备只需三样东西微信开发者工具Stable 1.06.2308310 及以上必须用稳定版Beta 版对云开发支持不稳定Node.js v16.20.2项目package.json中engines.node指定此版本用nvm切换最稳微信小程序 AppID去 mp.weixin.qq.com 注册开通「云开发」并创建环境记住环境 ID如prod-abc123。注意不要用测试号测试号无法调用云函数会卡在wx.cloud.callFunction报permission denied。必须用正式注册的小程序 AppID并在开发者工具「详情 项目设置」中勾选「使用云服务」。3.2 项目初始化四条命令走完打开终端进入项目根目录即含miniprogram/和cloudfunctions/的目录# 1. 安装前端依赖uni-app 风格的 request 封装、图标字体等 npm install # 2. 安装云函数依赖每个云函数目录下都有 package.json需单独安装 cd cloudfunctions/login npm install cd ../.. cd cloudfunctions/getGoodsList npm install cd ../.. cd cloudfunctions/payOrder npm install cd ../.. # 3. 在开发者工具中「上传云函数」右键 cloudfunctions/ 目录 → 「上传云函数」→ 选择你的环境 ID # 此步不可跳过云函数必须部署后才能被前端调用 # 4. 启动开发者工具选择项目根目录点击「编译」编译成功后开发者工具左上角会显示「编译成功」模拟器里出现首页轮播图和商品列表。此时打开「云开发控制台」确认goods表里已有示例数据项目自带init-data.json首次上传云函数时会自动导入。3.3 真机调试扫码前必做的三件事很多开发者卡在「开发者工具能跑真机扫码一片空白」90% 是以下三个配置没做HTTPS 域名白名单在小程序后台「开发管理 开发者工具」中将你的云开发环境域名如https://prod-abc123.tcloudbase.net加入「request 合法域名」云开发环境 ID 写死打开miniprogram/app.js找到wx.cloud.init确认env参数是你自己的环境 IDwx.cloud.init({ env: prod-abc123, // ← 改成你的环境 ID traceUser: true })网络权限开关真机调试时手机需开启「无线局域网」且与电脑在同一 WiFi 下若用 USB 连接需在开发者工具「项目设置」中勾选「启用 USB 调试」。做完这三步点击开发者工具右上角「预览」→ 生成二维码 → 用真机微信扫码就能看到和模拟器一模一样的首页。点击任意商品进入详情页底部「立即购买」按钮可触发payOrder.create云函数返回模拟支付参数证明前后端链路已通。3.4 常见问题排查真机白屏、云函数 404、图片不显示现象真机扫码后页面空白控制台报Failed to load resource: the server responded with a status of 404 ()原因云函数未上传或上传时选错了环境 ID导致前端调用的云函数在目标环境中不存在。解决回到开发者工具右键cloudfunctions/目录 → 「上传云函数」→ 确认弹窗中「环境」下拉框选的是你app.js里写的那个 ID上传完成后去云开发控制台「云函数」列表确认login、getGoodsList等函数状态为「运行中」。现象商品图片在开发者工具显示正常真机上全是灰色方块原因真机微信对wx.previewImage的sources数组要求更严格若images字段存的是相对路径如/images/goods1.jpg而非 HTTPS 绝对地址会加载失败。解决检查goods表中images字段确保是形如[https://xxx.cos.ap-shanghai.myqcloud.com/goods1.jpg]的数组项目初始化脚本已将示例图片上传至 COS 并写入绝对 URL无需手动改。现象点击「登录」按钮无反应控制台报Cannot read property code of undefined原因wx.login调用失败常见于手机微信版本过低 8.0.30或未开启「位置信息」权限部分安卓机型需位置权限才允许wx.login。解决在miniprogram/pages/index/index.js的onLoad中加一层容错wx.login({ success: res { if (res.code) { // 正常走 login 云函数 wx.cloud.callFunction({ name: login, data: { code: res.code } }) } else { wx.showToast({ title: 登录失败请重试, icon: none }) } }, fail: () wx.showToast({ title: 网络异常请检查WiFi, icon: none }) })现象云函数日志里频繁出现Error: collection not found: goods原因云数据库未创建goods表或表名拼写错误如写成good少了个 s。解决打开云开发控制台 → 「数据库」→ 「添加集合」→ 输入goods→ 创建然后导入init-data.json项目根目录下作为初始数据。4. 核心模块拆解sku-selector组件与订单状态机的实现细节这个小程序商店最不像「模板」的地方在于它把电商核心交互做成了可复用、可配置的模块。sku-selector不是简单的 radio 按钮组而是一个能处理无限层级规格颜色尺寸内存、实时计算价格与库存、并阻止无效组合的黑匣子订单状态机也不是一堆if-else而是用状态图驱动的可维护逻辑。下面带你一行行读透。4.1sku-selector多规格选择器的三层架构该组件位于miniprogram/components/sku-selector/采用「数据驱动 UI」设计结构分三层props层接收外部数据skuList:Array商品所有 SKU 的数组每项含id,price,stock,specs如[{key: 颜色, value: 红色}, {key: 尺寸, value: XL}]selectedSpecs:Object当前已选规格形如{颜色: 红色, 尺寸: XL}onChange:Function规格变更时触发参数为当前选中的specKey、specValue和完整selectedSpecs。data层内部状态specKeys:Array规格键名数组如[颜色, 尺寸]决定选择器 tab 顺序specValues:Object每个规格键对应的可选值数组如{颜色: [红色,蓝色], 尺寸: [S,M,XL]}disabledValues:Object禁用值映射如{颜色: {蓝色: true}}表示当尺寸选 XL 时蓝色不可选。methods层核心逻辑// miniprogram/components/sku-selector/sku-selector.js Component({ properties: { skuList: { type: Array, value: [] }, selectedSpecs: { type: Object, value: {} }, onChange: { type: Function, value: () {} } }, data: { specKeys: [], specValues: {}, disabledValues: {} }, lifetimes: { ready() { this.initSpecs() } }, methods: { // 初始化从 skuList 提取所有规格键和值 initSpecs() { const { skuList } this.properties if (skuList.length 0) return // 1. 提取所有 specs.key去重排序 const keysSet new Set() skuList.forEach(sku { sku.specs.forEach(spec keysSet.add(spec.key)) }) const specKeys Array.from(keysSet) // 2. 构建 specValues每个 key 对应所有可能的 value const specValues {} specKeys.forEach(key { const values [...new Set( skuList.map(sku sku.specs.find(s s.key key)?.value).filter(v v) )] specValues[key] values }) // 3. 构建 disabledValues遍历所有 sku标记哪些组合不存在 const disabledValues {} specKeys.forEach(key { disabledValues[key] {} specValues[key].forEach(value { // 若当前 keyvalue 时其他规格无任何 sku 匹配则禁用 const hasMatch skuList.some(sku sku.specs.some(spec spec.key key spec.value value) ) if (!hasMatch) disabledValues[key][value] true }) }) this.setData({ specKeys, specValues, disabledValues }) }, // 规格点击事件更新 selectedSpecs 并触发 onChange onSpecClick(e) { const { key, value } e.currentTarget.dataset const { selectedSpecs } this.properties const newSelected { ...selectedSpecs, [key]: value } // 清空后续规格的选择如先选颜色再选尺寸颜色变了尺寸要重置 const keyIndex this.data.specKeys.indexOf(key) if (keyIndex this.data.specKeys.length - 1) { const nextKeys this.data.specKeys.slice(keyIndex 1) nextKeys.forEach(k delete newSelected[k]) } this.properties.onChange(key, value, newSelected) this.setData({ selectedSpecs: newSelected }) } } })这段代码的精妙之处在于initSpecs方法它不依赖后端返回「规格树」而是从扁平的skuList数组反推规格结构。disabledValues的计算逻辑保证了「用户永远看不到无效组合」——比如红色 XL 有货但蓝色 XL 已售罄那么当用户选了「蓝色」后「XL」选项会自动置灰。这种设计让后端只需维护 SKU 列表前端自动处理交互约束。4.2 订单状态机用status字段驱动 UI 与行为订单状态不是写死在switch(status)里而是通过一个集中管理的状态配置对象实现「一处定义处处生效」// miniprogram/utils/order-status.js const STATUS_CONFIG { 0: { // 待支付 text: 待付款, btnText: 去支付, showPayBtn: true, showCancelBtn: true, canCancel: true }, 1: { // 已支付 text: 已付款, btnText: 查看物流, showPayBtn: false, showCancelBtn: false, canCancel: false }, 2: { // 已发货 text: 已发货, btnText: 确认收货, showPayBtn: false, showCancelBtn: false, canCancel: false }, 3: { // 已完成 text: 交易完成, btnText: 评价商品, showPayBtn: false, showCancelBtn: false, canCancel: false }, 4: { // 已取消 text: 已取消, btnText: , showPayBtn: false, showCancelBtn: false, canCancel: false } } module.exports { getStatusConfig(status) { return STATUS_CONFIG[status] || STATUS_CONFIG[0] }, isCancelable(status) { return STATUS_CONFIG[status]?.canCancel || false } }在pages/order/detail.js中只需const statusUtils require(../../utils/order-status) Page({ data: { order: {} }, onLoad(options) { const orderId options.id wx.cloud.callFunction({ name: getOrderDetail, data: { orderId } }).then(res { const config statusUtils.getStatusConfig(res.result.data.status) this.setData({ order: res.result.data, statusConfig: config }) }) }, onConfirmReceipt() { if (this.data.order.status 2) { wx.cloud.callFunction({ name: confirmReceipt, data: { orderId: this.data.order.order_id } }) } } })WXML 中直接绑定!-- pages/order/detail.wxml -- view classstatus{{statusConfig.text}}/view view wx:if{{statusConfig.showPayBtn}} button bindtaponPay {{statusConfig.btnText}} /button /view view wx:if{{statusConfig.showCancelBtn}} button bindtaponCancel取消订单/button /view这样未来要新增「售后中」状态status5只需在STATUS_CONFIG里加一项所有订单页自动适配不用改任何页面逻辑。5. 二次开发实战从「校园二手书」到「社区团购」的改造路径我接手过的最典型改造案例是把这套代码从「通用商品商店」变成「某高校二手教材流转平台」。需求很具体去掉所有「品牌」「产地」字段增加「适用年级」「适用课程」「教材版本」搜索要支持按「课程名」模糊匹配订单流程砍掉「发货」环节改为「线下自提」支付方式只保留「微信支付」但成功后要自动发一条校园公众号模板消息。整个改造我花了不到一天核心就三步改数据库、改云函数、改页面。下面给你可抄作业的 checklist。5.1 数据库字段增删goods表的精准手术原始goods表有brand,origin,weight等字段对教材毫无意义。我们用云开发控制台直接操作删除冗余字段在goods表「字段管理」中将brand,origin,weight,shipping_fee字段设为「隐藏」不删留作扩展新增教材专属字段course_name:String课程名称如「高等数学同济第七版」grade_level:String适用年级枚举值[大一, 大二, 大三, 大四, 研究生]book_version:String教材版本如「第七版」condition:Number成色1~5 分1破损严重5全新修改索引删除原brand字段索引新建复合索引course_name_1_grade_level_1提升按课程年级搜索的速度。血泪经验字段改完别忘了更新miniprogram/pages/goods/add.js中的表单data否则新增商品时这些字段不会提交。找到formSubmit方法把course_name,grade_level等加进formData对象。5.2 云函数逻辑重构getGoodsList的搜索升级原始搜索只匹配name现在要支持course_name。打开cloudfunctions/getGoodsList/index.js修改查询条件// 原代码只搜 name if (keyword) { query.name _.regex({ regexp: keyword, options: i }) } // 改为搜 name 和 course_name if (keyword) { query.$or [ { name: _.regex({ regexp: keyword, options: i }) }, { course_name: _.regex({ regexp: keyword, options: i }) } ] }同时为了让搜索结果更相关把orderBy从created_at改为condition降序成色好的排前面.orderBy(condition, desc) // ← 加这一行测试方法在云开发控制台「云函数」→getGoodsList→ 「测试」输入{keyword: 高数}确认返回的data中包含course_name含「高数」的商品。5.3 页面与交互定制pages/goods/detail.wxml的教材化改造教材详情页不需要「加入购物车」而是「预约自提」。我们删掉原有加购按钮换成自提预约表单!-- pages/goods/detail.wxml -- !-- 原来的加购区域 -- !-- view classcart-actions ... /view -- !-- 替换为自提预约 -- view classpickup-section view classsection-title预约自提/view view classform-item text期望自提时间/text picker bindchangebindTimeChange value{{timeIndex}} range{{timeRange}} view classpicker{{timeRange[timeIndex]}}/view /picker /view view classform-item text自提地点/text picker bindchangebindLocationChange value{{locationIndex}} range{{locations}} view classpicker{{locations[locationIndex]}}/view /picker /view button bindtaponReservePickup classreserve-btn立即预约/button /view对应 JS 逻辑// pages/goods/detail.js Page({ data: { timeRange: [今天下午, 明天上午, 明天下午, 后天上午], locations: [教务处一楼, 图书馆东门, 学生活动中心], timeIndex: 0, locationIndex: 0 }, onReservePickup() { const { goodsId, timeIndex, locationIndex } this.data const time this.data.timeRange[timeIndex] const location this.data.locations[locationIndex] wx.cloud.callFunction({ name: reservePickup, data: { goodsId, time, location } }).then(res { if (res.result.success) { wx.showToast({ title: 预约成功请按时前往, icon: success }) // 跳转到我的预约页 wx.navigateTo({ url: /pages/user/reservations }) } }) } })reservePickup是一个新云函数逻辑极简往reservations表插入一条记录字段含goods_id,user_openid,time,location,status: 0待确认。这个函数甚至不需要事务因为不涉及库存变更。5.4 模板消息支付成功后的校园触达微信支付成功后自动给用户发一条模板消息内容含「教材名称」「自提时间」「地点」。这需要两步在小程序后台「功能 » 订阅消息」中申请模板搜索「订单支付成功通知」选用系统模板填入所需字段character_string1教材名time2timething3location修改payOrder.notify云函数在更新订单状态为status1后追加模板消息发送// cloudfunctions/payOrder/index.jsnotify 路由内 // ... 更新订单 status1 的代码 ... // 发送模板消息 await cloud.openapi.subscribeMessage.send({ touser: openid, templateId: your-template-id-here, // 后台申请的模板 ID data: { character_string1: { value: goodsName }, // 教材名 time2: { value: pickupTime }, // 自提时间 thing3: { value: pickupLocation } // 自提地点 } })注意openapi.subscribeMessage.send需要云开发环境开通「订阅消息」权限且用户必须在支付前主动同意过该模板通常在下单页加一个 checkbox。项目已内置同意弹窗组件components/subscribe-modal调用showSubscribeModal()即可。从那以后我每次接到垂直领域小程序需求第一反应不是从零画原型而是打开这个「微信小程序商店完整代码」包执行git checkout -b campus-book然后按上述三步走改库、改云函数、改页面。它像一个精密组装好的引擎你只需换掉进气格栅UI、调整喷油嘴业务逻辑、校准点火正时状态流转就能驱动全新的业务场景。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑