☰
微信小程序+Spring Boot商城实战:高分毕设与MVP落地指南
2026/10/25 6:06:18 网站建设 项目流程

简介:这是一套完整的微信小程序购物商城全栈开发项目,面向Java后端与小程序前端初学者及课程设计学生,解决电商类应用从用户端到管理端的全流程开发实践需求。资源包含可本地编译运行的Java Spring Boot后端、WXML/WXSS/JS构成的小程序前端、MySQL数据库脚本及详细文档说明,覆盖商品浏览、购物车管理、订单提交、物流跟踪、评价反馈与后台商品/订单/用户地址管理等核心业务模块。压缩包共1539个文件,以176个Java后端逻辑文件、385个HTML页面模板、305个JS交互脚本、219个PNG与32个JPG图片资源为主,辅以CSS样式、JSON配置及SQL建库脚本,整体大小为10.27MB,结构清晰、模块分离明确,便于分层学习与调试。已有205人下载学习,配套文档详实、代码注释充分,评审得分达95分以上,适合作为高校课程设计、毕业设计参考或全栈开发入门实战范例。

1. 微信小程序购物商城 + Java 后端:为什么这个组合在毕业设计和中小商业项目里稳拿高分?

你手头这个.zip文件,不是“又一个练手 demo”,而是一套能跑通、能部署、能讲清楚技术链路的完整闭环系统:前端是微信原生小程序(非 uni-app 或 Taro),后端用 Spring Boot + MyBatis-Plus 写成标准 RESTful 接口,数据库用 MySQL 5.7+,配套有建表 SQL、接口文档 Markdown、前后端分离部署说明,甚至包含微信登录 + 手机号授权 + 订单状态机 + 支付回调模拟等真实业务模块。它之所以常被标注为“高分项目”,根本原因不是代码多炫酷,而是踩中了三个硬性得分点:一是微信小程序侧符合最新基础库要求(如wx.login+getPhoneNumber的双阶段鉴权流程),二是 Java 后端用了 MyBatis-Plus 的@TableName+@TableId注解驱动实体与数据库映射,避免手写 XML;三是数据库设计含外键约束、索引优化(比如订单表user_id和goods_id均建了 B+Tree 索引),不是纯 CRUD 堆砌。适合两类人:计算机专业本科生做毕设(答辩时能讲清「为什么选 MyBatis-Plus 而不是 JPA」「小程序如何规避wx.request并发限制」),以及小团队快速上线 MVP 商城(把application-prod.yml里的数据库地址一换,加个 Nginx 反向代理就能对外提供服务)。别被“高分”二字带偏——它真正的价值,在于所有模块都可验证、可替换、可压测。


2. 从 ZIP 解压到本地运行:四步走通全栈链路

2.1 解压后目录结构解析:看清哪些文件真有用,哪些只是占空间

拿到微信小程序购物商城app设计带Java后端+源代码+文档说明+数据库(高分项目).zip后,先别急着mvn clean install。用unzip -l xxx.zip | head -20快速扫一眼顶层结构(Windows 用户可用 7-Zip 查看),你会看到类似这样的关键路径:

├── docs/ # 文档目录(重点看 API.md 和 数据库设计说明.md) ├── frontend/ # 小程序源码(注意:不是 /miniprogram,而是 /frontend,说明是独立工程) │ ├── project.config.json # 小程序开发者工具配置(含 appid) │ ├── app.js # 全局逻辑(含 wx.login 拦截器) │ └── pages/ ├── backend/ # Java 后端(Spring Boot 2.7.x,非 3.x) │ ├── pom.xml # 关键:确认 <spring-boot.version> 是 2.7.18(兼容 JDK8) │ ├── src/main/java/com/example/mall/ │ │ ├── MallApplication.java # 启动类 │ │ ├── controller/ # REST 接口(如 GoodsController.java) │ │ ├── service/ # 业务逻辑(含 OrderService 的状态流转) │ │ └── mapper/ # MyBatis-Plus Mapper(带 @Mapper 注解) │ └── src/main/resources/ │ ├── application-dev.yml # 开发环境配置(数据库账号密码明文!) │ └── mapper/ # XML 映射文件(实际项目中可能为空,因用了注解式) └── db/ # 数据库脚本(重点看 mall_init.sql) └── mall_init.sql # 包含建库、建表、初始数据(admin 用户、测试商品)

提示:frontend/下没有node_modules,说明小程序代码是纯原生开发(WXML + WXSS + JS),不依赖 npm 构建;backend/pom.xml中若出现<artifactId>spring-boot-starter-web</artifactId>且无spring-boot-starter-reactor-netty,基本可断定是 Tomcat 嵌入式容器,不是 WebFlux。

2.2 后端启动:绕过 Maven 镜像坑,用最简命令跑通接口

很多同学卡在第一步:mvn spring-boot:run报错Could not resolve dependencies。这不是代码问题,而是国内 Maven 镜像源失效或pom.xml里写了已下线的仓库地址(如https://repo.spring.io/release在 Spring Boot 2.7 后已停用)。正确做法是跳过 Maven,直接用 IDE 启动:

# 进入 backend 目录 cd backend # 方式一:IntelliJ IDEA(推荐) # 1. File → Open → 选择 backend/pom.xml # 2. 等待 Maven 自动导入(右下角提示 "Importing Maven project...") # 3. 找到 MallApplication.java → 右键 → Run 'MallApplication' # 4. 控制台输出 "Started MallApplication in X.XXX seconds" 即成功 # 方式二:命令行(需确保 JAVA_HOME 指向 JDK8 或 JDK11) # 先清理旧 target(避免 classpath 冲突) rm -rf target/ # 用 Maven Wrapper 启动(比全局 mvn 更可靠) ./mvnw spring-boot:run -Dspring.profiles.active=dev

启动成功后,访问http://localhost:8080/swagger-ui.html(如果pom.xml引入了springfox-swagger2)或http://localhost:8080/doc.html(若用的是knife4j),能看到/api/goods/list、/api/user/login等接口列表。此时不要急着调小程序,先用 curl 测通基础接口:

# 测试商品列表(GET) curl "http://localhost:8080/api/goods/list?page=1&size=10" # 测试用户登录(POST,模拟小程序 wx.login code) curl -X POST http://localhost:8080/api/user/login \ -H "Content-Type: application/json" \ -d '{"code":"mock_code_123"}'

参数说明:code字段是模拟值,真实场景下由小程序wx.login()获取,后端用该 code 向微信服务器换取openid。项目中通常在UserController.login()方法里调用微信sns/jscode2session接口,返回{"openid":"xxx","session_key":"yyy"},再存入 Redis 缓存(key 为login:xxx,过期时间 2 小时)。若你发现接口返回{"code":500,"msg":"微信服务器请求失败"},大概率是application-dev.yml里的wechat.appid和wechat.secret未填——这两个值必须去 微信公众平台 的「开发管理 → 开发者ID」页获取。

2.3 小程序端配置与真机调试:解决「开发者工具能跑,手机打不开」的玄学问题

小程序代码在frontend/目录,但直接用微信开发者工具打开会报错project.config.json 中 appid 无效。这是因为project.config.json里的"appid"是占位符(如"wx1234567890abcdef"),必须替换成你自己的测试号 AppID:

// frontend/project.config.json { "description": "项目配置文件", "packOptions": { "ignore": [] }, "setting": { "urlCheck": false, "es6": true, "enhance": true }, "appid": "wx1234567890abcdef", // ← 替换为你在 mp.weixin.qq.com 申请的测试号 AppID "projectname": "mall-miniapp", "libVersion": "2.28.0", // ← 必须 ≥ 2.25.0,否则 getPhoneNumber 会报错 "condition": { ... } }

注意:微信小程序顶部导航栏高度在不同机型差异极大(iPhone X 系列是 88px,安卓全面屏是 64px),项目中通常用wx.getSystemInfoSync().statusBarHeight动态计算,但frontend/app.js里若写死padding-top: 88rpx,真机就会错位。检查app.wxss是否含.nav-bar { padding-top: env(safe-area-inset-top) }—— 这才是适配刘海屏的正解。

启动开发者工具后,点击「预览」生成二维码,用自己手机微信扫码。此时若页面空白或报request:fail url not in domain list,说明app.js里的baseUrl还指向http://localhost:8080(开发者工具允许,真机禁止)。必须改成局域网 IP:

// frontend/app.js App({ globalData: { baseUrl: 'http://192.168.1.100:8080', // ← 改成你电脑的局域网 IP(非 127.0.0.1!) // ... } })

然后重启小程序。如果仍报错,打开手机微信「设置 → 通用 → 辅助功能 → 微信内网页」,关闭「网页版文件传输助手」——这个开关会劫持所有http://请求,导致接口 404。


3. 数据库初始化与 MyBatis-Plus 实体映射:让增删改查不靠猜

3.1 用mall_init.sql初始化 MySQL:避开字符集和时区两大雷区

db/mall_init.sql通常是建库 + 建表 + 插入测试数据的一体化脚本。但直接mysql -u root -p < mall_init.sql很可能失败,原因就两个:

  1. 字符集不匹配:脚本开头若没写CREATE DATABASE IF NOT EXISTS mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,而你的 MySQL 默认是latin1,中文字段会变??;
  2. 时区错误:datetime字段插入'2024-01-01 00:00:00'时,若 MySQL 时区是SYSTEM(即服务器本地时区),而 Java 读取时用GMT+8解析,时间会差 8 小时。

安全做法是手动执行建库语句:

-- 步骤 1:创建数据库(显式指定字符集) CREATE DATABASE IF NOT EXISTS mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 步骤 2:切换数据库 USE mall; -- 步骤 3:执行建表语句(从 mall_init.sql 复制 CREATE TABLE 部分) -- 注意:检查每个表是否含 ENGINE=InnoDB(MyBatis-Plus 要求事务支持) -- 示例 goods 表 CREATE TABLE `goods` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '商品名称', `price` decimal(10,2) NOT NULL COMMENT '价格', `stock` int NOT NULL DEFAULT '0' COMMENT '库存', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

提示:utf8mb4是必须的,因为微信昵称、商品描述可能含 emoji(如 🐱),utf8在 MySQL 中实际只支持 3 字节,无法存 🐱(需 4 字节)。

3.2 MyBatis-Plus 实体类与数据库字段映射:为什么@TableId(type = IdType.AUTO)比@TableId(type = IdType.INPUT)更安全

后端src/main/java/com/example/mall/entity/Goods.java是核心实体类。它的字段与goods表一一对应,但关键在于@TableId和@TableField注解的用法:

package com.example.mall.entity; import com.baomidou.mybatisplus.annotation.*; @TableName("goods") // 显式指定表名,避免驼峰转下划线出错 public class Goods { @TableId(type = IdType.AUTO) // 主键由数据库自增(推荐!) private Long id; @TableField("name") // 显式指定字段名,避免 name → name_id 错误映射 private String name; @TableField("price") private BigDecimal price; @TableField("stock") private Integer stock; // getter/setter 省略 }

为什么IdType.AUTO比IdType.INPUT安全?

  • IdType.INPUT要求前端传id,但小程序端不可能知道下一个 ID 是多少,极易冲突;
  • IdType.AUTO由 MySQLAUTO_INCREMENT生成,后端goodsMapper.insert(goods)后,goods.getId()会自动回填新 ID,方便后续关联操作(如插入订单项时用此 ID)。

参数说明:@TableField(fill = FieldFill.INSERT)可用于create_time字段,配合MetaObjectHandler自动填充;但本项目若没实现,就别乱加,否则插入时报Field 'create_time' doesn't have a default value。

3.3 用 MyBatis-Plus 生成建表 SQL:告别手写 DDL 的血泪经验

如果你要基于现有实体类反向生成建表语句(比如新增coupon表),别手写 SQL。MyBatis-Plus 提供MybatisPlusGenerator工具类,但更简单的是用MyBatis-Plus的AutoGenerator(需额外引入mybatis-plus-generator依赖):

<!-- backend/pom.xml --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> <!-- 注意:必须与 mybatis-plus-boot-starter 版本一致 --> </dependency>

然后写一个CodeGenerator.java(放在test/java下):

public class CodeGenerator { public static void main(String[] args) { AutoGenerator generator = new AutoGenerator(); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); generator.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("coupon"); // 模块名 pc.setParent("com.example.mall"); generator.setPackageInfo(pc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); // 下划线转驼峰 strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); strategy.setInclude("coupon"); // 只生成 coupon 表 generator.setStrategy(strategy); generator.execute(); } }

运行后,会在src/main/java下生成Coupon.java实体类,并在src/main/resources/mapper/下生成CouponMapper.xml。但注意:XML 文件里<insert>标签默认不含useGeneratedKeys="true",必须手动加上,否则insert后getId()返回 null:

<!-- src/main/resources/mapper/CouponMapper.xml --> <insert id="insert" parameterType="com.example.mall.entity.Coupon" useGeneratedKeys="true" keyProperty="id"> INSERT INTO coupon (name, discount, start_time, end_time) VALUES (#{name}, #{discount}, #{startTime}, #{endTime}) </insert>

4. 微信小程序登录与手机号获取:两阶段鉴权的硬核落地细节

4.1wx.login()+code2Session:为什么不能省掉后端校验这一步

小程序登录看似简单:wx.login()获取code,传给后端,后端用code换openid。但很多同学直接在小程序端用wx.request({url: 'http://localhost:8080/api/user/login', data: {code}}),结果被微信拦截——因为wx.request的域名必须在「小程序后台 → 开发管理 → 业务域名」里备案(HTTP 域名已不支持,必须 HTTPS)。正确链路是:小程序 → 自己后端 → 微信服务器:

// frontend/pages/login/login.js onLoad() { wx.login({ success: res => { // 1. 将 code 发给自己的后端(HTTPS 域名) wx.request({ url: getApp().globalData.baseUrl + '/api/user/login', method: 'POST', data: { code: res.code }, // 仅传 code success: resp => { if (resp.data.code === 200) { // 2. 后端返回 session_token(非 openid!) wx.setStorageSync('token', resp.data.data.token); wx.switchTab({ url: '/pages/index/index' }); } } }) } }) }

后端UserController.login()接收code,拼接微信 URL:

// backend/src/main/java/com/example/mall/controller/UserController.java @PostMapping("/login") public Result login(@RequestBody Map<String, String> params) { String code = params.get("code"); // 拼微信接口 URL(注意:必须用 https) String url = "https://api.weixin.qq.com/sns/jscode2session?" + "appid=" + wechatConfig.getAppid() + "&secret=" + wechatConfig.getSecret() + "&js_code=" + code + "&grant_type=authorization_code"; // 用 RestTemplate 调用微信(别用 HttpClient,太重) String result = restTemplate.getForObject(url, String.class); JSONObject json = JSONObject.parseObject(result); if (json.containsKey("openid")) { String openid = json.getString("openid"); String sessionKey = json.getString("session_key"); // 3. 用 openid 查库,不存在则 insert,返回自定义 token String token = JWTUtil.generateToken(openid); return Result.success(token); } else { return Result.fail("微信登录失败:" + json.getString("errmsg")); } }

关键点:session_key必须和服务端token绑定存储(如 Redis),后续解密手机号时要用它。千万别把session_key返回给小程序端!

4.2getPhoneNumber获取手机号:为什么必须用button open-type="getPhoneNumber"而不是wx.getPhoneNumber

微信在 2023 年底强制要求:获取手机号必须用<button open-type="getPhoneNumber">组件,wx.getPhoneNumberAPI 已废弃。项目中frontend/pages/user/bind-phone/bind-phone.wxml应类似:

<!-- 绑定手机号页面 --> <button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" class="bind-btn"> 一键绑定手机号 </button>

对应的 JS 逻辑:

// frontend/pages/user/bind-phone/bind-phone.js onGetPhoneNumber(e) { if (e.detail.code) { // 1. 将加密数据传给后端 wx.request({ url: getApp().globalData.baseUrl + '/api/user/bind-phone', method: 'POST', data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv, code: e.detail.code // 注意:这是微信回调的 code,不是 login 的 code }, header: { 'Authorization': wx.getStorageSync('token') // 携带登录 token }, success: res => { if (res.data.code === 200) { wx.showToast({ title: '绑定成功' }); setTimeout(() => wx.navigateBack(), 1000); } } }) } }

后端解密逻辑(UserController.bindPhone()):

@PostMapping("/bind-phone") public Result bindPhone(@RequestBody Map<String, String> params, @RequestHeader("Authorization") String token) { String encryptedData = params.get("encryptedData"); String iv = params.get("iv"); String code = params.get("code"); // 1. 用 token 查出 openid(从 Redis 取 session_key) String openid = JWTUtil.getOpenid(token); String sessionKey = redisTemplate.opsForValue().get("login:" + openid); // 2. AES-128-CBC 解密(微信官方 SDK 提供 WXBizDataCrypt) WXBizDataCrypt crypt = new WXBizDataCrypt(wechatConfig.getAppid(), sessionKey); JSONObject phoneObj = crypt.decrypt(encryptedData, iv); if (phoneObj.containsKey("phoneNumber")) { String phoneNumber = phoneObj.getString("phoneNumber"); // 3. 更新 user 表 phone 字段 userService.updatePhone(openid, phoneNumber); return Result.success(); } else { return Result.fail("手机号解密失败"); } }

注意:WXBizDataCrypt类需自行实现(微信官方 GitHub 有 Java 版),核心是Cipher.getInstance("AES/CBC/PKCS5Padding")。若解密报javax.crypto.BadPaddingException: Given final block not properly padded,99% 是session_key过期或encryptedData被前端二次 JSON.stringify。


5. 高频避坑指南:那些让项目卡在交付前夜的致命细节

5.1 现象:小程序真机预览白屏,开发者工具正常

原因:project.config.json中"libVersion"低于真机微信基础库版本(如设为2.20.0,而用户微信是2.29.0),导致getPhoneNumber等新 API 不识别。
解决:将"libVersion"改为"2.28.0"(当前兼容性最好的版本),并勾选开发者工具「详情 → 本地设置 → 调试基础库」为2.28.0。

5.2 现象:后端insert成功但getId()返回null

原因:MySQL 表主键未设AUTO_INCREMENT,或 MyBatis-Plus 实体类@TableId注解漏写type = IdType.AUTO。
解决:执行SHOW CREATE TABLE goods,确认id字段含AUTO_INCREMENT;检查实体类@TableId(type = IdType.AUTO)是否存在。

5.3 现象:curl http://localhost:8080/api/goods/list返回 404

原因:pom.xml中spring-boot-starter-web版本与 Spring Boot 不匹配(如 Spring Boot 2.7.x 配了 3.x 的 starter),导致@RestController扫描失效。
解决:检查pom.xml,确保<artifactId>spring-boot-starter-web</artifactId>无<version>标签(由 parent 统一管理),且parent的spring-boot.version为2.7.18。

5.4 现象:微信登录返回{"errcode":40029,"errmsg":"invalid code"}

原因:code有效期仅 5 分钟,且一个code只能用一次;或后端请求微信jscode2session时appid/secret填错。
解决:打印后端日志,确认code是否被重复使用;登录 mp.weixin.qq.com 核对「开发管理 → 开发者ID」页的 AppID 和 AppSecret。

5.5 现象:数据库中文显示为??,但SELECT查询正常

原因:JDBC 连接字符串未加characterEncoding=utf8mb4,如jdbc:mysql://localhost:3306/mall缺少参数。
解决:修改application-dev.yml中的spring.datasource.url:

spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8

6. 进阶技巧:用 MyBatis-Plus 的QueryWrapper实现动态条件查询,替代手写 SQL

毕业答辩时,老师常问:“如果我要查‘价格在 100~500 且库存大于 0 的商品’,后端怎么写?”——这时候千万别答“写个 SQL 用WHERE price BETWEEN ? AND ? AND stock > 0”,而要亮出 MyBatis-Plus 的QueryWrapper,体现工程化思维。

6.1 商品列表接口的动态查询:一行代码搞定多条件组合

GoodsController.list()方法接收page、size、minPrice、maxPrice、keyword等参数,传统做法是拼 SQL 字符串,易 SQL 注入。用QueryWrapper则安全又清晰:

@GetMapping("/list") public Result list( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) BigDecimal minPrice, @RequestParam(required = false) BigDecimal maxPrice, @RequestParam(required = false) String keyword) { Page<Goods> goodsPage = new Page<>(page, size); QueryWrapper<Goods> wrapper = new QueryWrapper<>(); // 价格区间(自动忽略 null 值) if (minPrice != null) wrapper.ge("price", minPrice); if (maxPrice != null) wrapper.le("price", maxPrice); // 模糊搜索(SQL LIKE) if (StringUtils.isNotBlank(keyword)) { wrapper.like("name", keyword); } // 库存大于 0(上架商品) wrapper.gt("stock", 0); // 执行查询 IPage<Goods> result = goodsService.page(goodsPage, wrapper); return Result.success(result); }

逻辑说明:wrapper.ge("price", minPrice)生成WHERE price >= ?,wrapper.like("name", keyword)生成WHERE name LIKE '%xxx%'。MyBatis-Plus 会自动处理null参数,不会生成AND price >= NULL这种无效 SQL。

6.2 多表关联查询:用@TableName+@TableField避免手写 JOIN

订单列表需显示商品名称、用户昵称,传统做法是写SELECT o.*, g.name as goods_name, u.nickname as user_nickname FROM order o LEFT JOIN goods g ON o.goods_id = g.id LEFT JOIN user u ON o.user_id = u.id。但 MyBatis-Plus 提供@TableName映射后,可用LambdaQueryWrapper+select指定字段:

// Order 实体类(含 goodsName 和 nickname 字段) @TableName("order") public class Order { @TableId private Long id; @TableField("goods_id") private Long goodsId; @TableField("user_id") private Long userId; // 非数据库字段,用于接收关联查询结果 @TableField(exist = false) private String goodsName; @TableField(exist = false) private String nickname; // getter/setter... } // OrderController.listWithDetail() @GetMapping("/list-detail") public Result listWithDetail(@RequestParam Integer page, @RequestParam Integer size) { Page<Order> pageObj = new Page<>(page, size); // 关联查询(MyBatis-Plus 3.4+ 支持) QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("o.*", "g.name as goods_name", "u.nickname as nickname") .from("order o") .leftJoin("goods g ON o.goods_id = g.id") .leftJoin("user u ON o.user_id = u.id"); IPage<Order> result = orderService.page(pageObj, wrapper); return Result.success(result); }

6.3 数据库同步技巧:用mysqldump导出结构 + 数据,避免INSERT丢失自增 ID

部署到生产环境时,db/mall_init.sql只含建表语句,没测试数据。若用INSERT INTO goods VALUES (1,'iPhone',5999.00,100),上线后新插入商品 ID 会从 1 开始,导致冲突。正确做法是导出含AUTO_INCREMENT的完整表:

# 导出 mall 库(含数据 + 结构),保留自增起始值 mysqldump -u root -p --databases mall > mall_full.sql # 生产环境导入(会自动重建 AUTO_INCREMENT) mysql -u root -p < mall_full.sql

我的习惯是:本地开发用mall_init.sql(干净建库),上线前用mysqldump导出一次全量备份,存到docs/deploy/目录。这样即使客户要求“恢复到某天数据”,也能秒级还原。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询