首页 / 资讯中心 / 文章详情

旅游管理系统(兼后台)完整版开发实战与避坑指南

旅游管理系统(兼后台)完整版开发实战与避坑指南 ★ FEATURED ARTICLE
简介完整的旅游管理系统源码包基于ASP.NET WebForms技术栈开发集前台展示与后台管理于一体适合.NET方向学习者、毕业设计答辩或需要快速搭建业务系统的开发者参考。资源内置数据库文件完整覆盖线路管理、景点介绍、订单处理等典型旅游业务模块后台还封装了公共用户控件与文件上传、缩略图处理等通用能力便于直接运行或二次扩展。包内共910个文件以aspx页面、cs后台逻辑、ashx一般处理程序、ascx用户控件为主体辅以css/js前端样式脚本、SQL数据库脚本及sln/csproj工程文件整体约22.79MB目录结构清晰代码组织规范便于定位业务代码与配置。目前已有3739人浏览学习适合希望系统掌握WebForm开发模式、理解企业级后台组织方式的读者通过完整示例快速积累实战经验并复用其中的通用模块作为新项目起点。1. 旅游管理系统完整版兼后台先想清楚这个项目到底在解决什么问题很多第一次接触旅游管理系统的人以为难点是前端页面做得够不够漂亮——轮播图、线路卡片、旅游攻略排版。但真正动手做一遍就会发现这类系统最难的不是展示而是“用户端看到的线路信息、价格、库存”和“后台管理员维护的数据”能不能保持一致。线路在后台改了价格前端立刻生效用户下单成功后台订单列表能查到对应记录管理员关闭一条线路前端检索里就搜不到它。这些看似基础的要求拼起来才是“完整版兼后台”真正的分量。这篇文章面向两类人一类是课程设计、毕业设计需要交付完整项目的人另一类是刚工作不久、想把前后端分离的 Web 开发流程完整走一遍的初级开发者。我按自己平时做一个类似系统时的顺序来讲先定数据模型再定后台管理端的技术骨架然后补前台用户端最后聊部署和最常见的几个翻车现场。你不需要提前准备好任何代码库跟着章节往下走每一步都能落到自己的工程里。2. 数据模型先行旅游管理系统的四张核心表与两个关键设计决策平时拿到这类标题我的习惯是先打开数据库设计工具而不是先建前端项目。原因很简单旅游管理系统兼后台的业务闭环是“管理员维护线路 → 用户浏览下单 → 后台处理订单”整个闭环的可靠性全部压在数据模型上。页面可以改接口可以调表结构设计错了后期返工成本最高。2.1 用户与管理员分开建表不要共用一个 users 表常见做法是把“前台注册登录的游客”和“后台登录的管理员”分成两张表member 和 admin_user。很多人图省事把两类账号塞进同一张用户表靠 role 字段区分。短期看确实少写一张表但很快会出现问题前台登录接口要查用户表后台登录接口也查用户表每次都要带条件区分角色更麻烦的是如果后续要给管理员加“操作日志”“角色权限”要么在用户表里堆一堆前台用户用不到的字段要么再拆表。前台会员的属性昵称、头像、消费积分和后台管理员的属性真实姓名、手机号、最后登录时间几乎没有交集拆开更干净。会员表 member 的核心字段如下id、username、password加密存储、nickname、phone、avatar、status1 正常 0 禁用、create_time。管理员表 admin_user 则是id、username、password、real_name、rolesuper / operator、last_login_time、create_time。密码一律用 bcrypt 或 PBKDF2 这类不可逆加密算法不要用 MD5 明文存储。2.2 线路表与订单表订单要存线路快照而不是只存线路外键这是旅游管理系统里最容易踩坑的表设计决策。线路表 travel_route 保存的是当前有效的线路信息标题、摘要、详情、出发地、目的地、价格、成团人数上限、封面图、状态上架 / 下架、创建时间。订单表 travel_order 记录每一次下单订单号、会员 id、线路 id、下单时的价格、出行人数、联系方式、状态、创建时间。关键点在于订单表里除了线路 id一定要冗余一份“下单时的线路快照”。因为线路价格会变库存会变甚至线路会被下架。如果订单只记录线路外键用户下单后管理员改了价格订单详情页显示的是新价格财务对账就会出问题。我一般用一个 route_snapshot 字段存 JSON把下单那一刻的线路标题、价格、图片、出行日期都存进去。订单详情页优先展示快照内容后台管理员的订单列表也能看到“用户当时买的是什么”。订单状态字段建议用整数类型存储状态机0 待支付、1 已支付待出行、2 已完成、3 已取消、4 退款中。不要用字符串存“已支付”“已完成”因为对账和统计时字符串容易写错整数配合代码里的枚举常量最稳妥。2.3 建表 SQL最小可用版本与字段注释下面给出一套最小可用的建表语句用 MySQL 8.0 语法。为了篇幅我把索引和注释一并写在 SQL 里方便直接复用。-- 会员表 CREATE TABLE member ( id INT UNSIGNED AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT bcrypt加密后的密码, nickname VARCHAR(50) DEFAULT COMMENT 昵称, phone VARCHAR(20) DEFAULT COMMENT 手机号, avatar VARCHAR(255) DEFAULT COMMENT 头像路径, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT前台会员表; -- 管理员表 CREATE TABLE admin_user ( id INT UNSIGNED AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 管理员账号, password VARCHAR(100) NOT NULL COMMENT bcrypt加密后的密码, real_name VARCHAR(50) DEFAULT COMMENT 真实姓名, role VARCHAR(20) NOT NULL DEFAULT operator COMMENT super超级管理员 operator普通运营, last_login_time DATETIME DEFAULT NULL COMMENT 最后登录时间, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_admin_name (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT后台管理员表; -- 线路表 CREATE TABLE travel_route ( id INT UNSIGNED AUTO_INCREMENT COMMENT 主键, title VARCHAR(100) NOT NULL COMMENT 线路标题, summary VARCHAR(255) DEFAULT COMMENT 线路摘要, detail TEXT COMMENT 线路详情, departure VARCHAR(50) NOT NULL COMMENT 出发地, destination VARCHAR(50) NOT NULL COMMENT 目的地, price DECIMAL(10,2) NOT NULL COMMENT 单人价格, max_people INT NOT NULL DEFAULT 20 COMMENT 成团人数上限, cover_image VARCHAR(255) DEFAULT COMMENT 封面图路径, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_destination (destination), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT旅游线路表; -- 订单表 CREATE TABLE travel_order ( id INT UNSIGNED AUTO_INCREMENT COMMENT 主键, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号, member_id INT UNSIGNED NOT NULL COMMENT 下单会员id, route_id INT UNSIGNED NOT NULL COMMENT 线路id, route_snapshot JSON COMMENT 下单时线路快照, people_count INT NOT NULL DEFAULT 1 COMMENT 出行人数, amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, contact_name VARCHAR(50) NOT NULL COMMENT 联系人姓名, contact_phone VARCHAR(20) NOT NULL COMMENT 联系人手机号, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已完成 3已取消 4退款中, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_member_id (member_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT旅游订单表;上面这段 SQL 里route_snapshot 用了 JSON 类型这是 MySQL 5.7 以后才支持的如果你用的是 5.6需要改成 TEXT 类型手动序列化。字段类型上我刻意控制了长度订单金额用 DECIMAL(10,2) 而不用 FLOAT因为浮点数在计算和比较时会出精度问题涉及钱一律用定点数。索引设计方面member 表的 username 建唯一索引保证账号不重复travel_order 表的 member_id 和 status 各建普通索引因为最常见的查询是“某会员的订单列表”和“按状态筛订单”。不需要一上来就把所有可能的查询条件都加索引等真实业务跑起来再补也可以但 username 和 order_no 这种天生唯一值的字段唯一索引一定要有。3. 后台管理端怎么做vue3 后台管理系统的主流方案与权限控制后台管理端是“兼后台”三个字的核心。按照当下的主力技术栈vue3 后台管理系统是最常见的选型。配合 Element Plus 组件库和 Pinia 状态管理可以在很短的时间内拼出一个像模像样的管理界面。我一般不会自己从零手写上传组件、分页表格、富文本编辑器这些基础设施直接用现成组件把精力放在业务逻辑上。3.1 后台登录JWT 令牌方案与 axios 拦截器后台登录流程看起来简单实际上有一个容易出问题的地方登录成功之后前端如何判断“当前用户有权限访问哪些页面”。我用的方案是登录接口返回 JWT 令牌前端把令牌存到 localStorageaxios 请求拦截器统一在 Header 里携带 Authorization 字段。后端接口用过滤器校验令牌校验不通过返回 401前端响应拦截器捕捉到 401 后跳回登录页。JWT 相比传统的 Session 方案好处是后台管理端如果拆成多个服务订单服务、线路服务、用户服务同一个令牌可以共享不需要做 Session 同步。代价是令牌过期前无法主动失效所以令牌有效期一般设短一点2 到 4 小时配合前端定时刷新或者重新登录。登录请求的 axios 封装如下// request.js 统一封装请求实例 import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, // 所有接口走同一前缀方便后端做统一路由 timeout: 10000 // 10 秒超时防止长时间等待 }) // 请求拦截器自动附加令牌 request.interceptors.request.use(config { const token localStorage.getItem(admin_token) if (token) { config.headers.Authorization Bearer ${token} } return config }, error Promise.reject(error)) // 响应拦截器统一处理业务码和登录失效 request.interceptors.response.use(response { const res response.data // 约定后端返回 { code: 0, data: ..., message: ok }code 0 为成功 if (res.code ! 0) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(admin_token) router.replace(/login) ElMessage.warning(登录已过期请重新登录) } return Promise.reject(error) }) export default request这段代码里最关键的是响应拦截器里对 401 的处理。很多管理系统开发到一半会出现一个怪现象用户登录后放着不动过几个小时再操作页面一直报错刷新又回到登录页。原因就是后端返回 401 后前端没有统一跳转业务组件里的请求持续报错。统一在这里处理所有接口的登录失效行为就一致了。baseURL 在开发环境一般配合前端代理使用。Vite 配置文件里设置/api代理到后端服务地址避免开发时跨域困扰生产环境则让后端把接口部署在同一个域名下。3.2 路由权限动态路由与菜单过滤的落地写法后台管理端通常有不同角色超级管理员能看到“管理员管理”菜单普通运营只能看到线路和订单模块。如果直接把所有路由写死在静态路由表里普通运营手动输入 URL 也能访问管理页权限形同虚设。我惯用的做法是路由表分成两份constantRoutes 和 asyncRoutes。前者是登录页、404 页这种任何角色都能访问的页面后者是业务管理页每个路由的 meta 字段里标记 allowedRoles例如[super]或[super, operator]。用户登录后后端返回当前管理员的角色前端根据角色过滤 asyncRoutes再调用router.addRoute动态注册。路由权限判定的核心方法如下// permission.js 动态注册路由 import router from /router import { constantRoutes, asyncRoutes } from /router/routes function hasPermission(roles, route) { if (route.meta route.meta.allowedRoles) { return roles.some(role route.meta.allowedRoles.includes(role)) } return true // 未标记 allowedRoles 的路由视为公共路由 } export function setupDynamicRoutes(roles) { const accessibleRoutes asyncRoutes.filter(route hasPermission(roles, route)) accessibleRoutes.forEach(route { router.addRoute(route) }) // 再挂一个兜底路由未匹配时跳 404 router.addRoute({ path: /:pathMatch(.*)*, redirect: /404 }) }参数说明roles 是当前登录管理员的角色数组比如[operator]asyncRoutes 中每个路由对象的 meta.allowedRoles 是允许访问的角色列表。做到这一步菜单显示和路由访问都受同一份角色列表约束普通用户手动输入管理页 URL 会被 404 兜底路由拦住。有人会问前端做路由过滤够不够不够。前端限制只是用户体验层面真正的权限校验必须后端接口再做一次。后端的做法是管理端接口统一走“管理员认证 角色鉴权”两个步骤比如线路新增接口要求管理员角色是 super 或 operator管理员管理接口只允许 super。前后端双重校验才能防止有人直接调接口绕过页面限制。3.3 后台界面的布局规划仿内部管理后台的骨架后台界面不需要设计感多强要的是信息层级清晰、操作路径短。我自己常用的布局是左侧菜单栏、顶部导航条、右侧内容区三段式。左侧菜单按模块分组线路管理、订单管理、会员管理、公告管理、系统设置。顶部放当前管理员昵称、退出登录按钮。内容区默认进入“线路管理”列表页。如果你想让界面看起来更像规范的内部管理后台注意三个细节列表页统一用筛选区 表格 分页组件表单页统一用卡片包裹表单项每个表单项要么带说明文字要么带必填标识操作按钮统一放在表格行的最右侧鼠标移入时显示避免行内按钮过多挤占空间。这些细节不需要额外引入组件库利用 Element Plus 的表格和表单组件默认行为就能实现。4. 前台用户端的核心流程线路检索、详情展示与下单支付前台用户端面向的是普通游客不强制登录就能浏览线路但下单前必须登录。整个流程可以切成三段检索列表、详情展示、提交订单。每一段都有值得注意的边界情况。4.1 线路检索页筛选条件与分页接口的参数设计检索页一般提供几个筛选项目的地、出发地、价格区间、是否只显示可预订线路。筛选条件传到后端时分页参数 pageNum 和 pageSize 必不可少。一个容易忽略的问题是价格筛选应该由后端处理而不是前端把全量数据拉到本地再 filter。前几版我图省事前端拿到全量线路后自己做价格过滤结果线路数据到 200 条时页面明显变卡切换筛选条件要等一两秒。正确的做法是后端接口接收 priceMin 和 priceMax 参数在 SQL 里用 WHERE 条件过滤数据库本身对这类查询有优化。后端接口的逻辑可以概括为四步接收查询参数拼接动态 WHERE 条件执行 COUNT 查询得到总数再执行分页数据查询。动态 SQL 拼接时注意参数化查询不要把用户传入的价格值直接拼接进 SQL避免注入风险。前端拿到结果后渲染列表加载状态和空状态都要处理——没有符合条件的线路时显示“暂无相关线路”而不是白屏。4.2 详情页与下单确认订单提交时校验“还能不能买”线路详情页展示标题、图片、价格、行程介绍然后是一个“立即预订”按钮。点击后进入下单确认页确认日期、出行人数、联系人信息提交时调用订单接口。这段流程里最容易翻车的是库存校验。用户 A 打开详情页看到还剩 8 个名额犹豫了 10 分钟这 10 分钟里用户 B 已经订走 6 个名额。A 点击提交时如果后端不做校验订单会创建成功但实际出行人数超过成团上限。我这里的处理方式是在后端创建订单的接口里加一个“剩余名额校验”查出线路当前已确认订单的出行人数总和加上本次下单人数超过上限就返回“该线路余位不足”。后端代码示意如下// 下单接口核心逻辑伪代码 public OrderResult createOrder(CreateOrderParam param) { TravelRoute route routeMapper.selectById(param.getRouteId()); if (route null || route.getStatus() ! 1) { return OrderResult.fail(线路不存在或已下架); } // 统计已确认订单的出行总人数排除取消和退款订单 Integer bookedPeople orderMapper.sumPeopleByRouteIdAndStatus( param.getRouteId(), List.of(1, 2)); // 状态1已支付 状态2已完成 int remaining route.getMaxPeople() - (bookedPeople null ? 0 : bookedPeople); if (param.getPeopleCount() remaining) { return OrderResult.fail(余位不足当前仅剩 remaining 个名额); } // 生成订单并写入线路快照 // ... }这个接口是典型的“先查后做”逻辑在高并发场景下单会有超卖风险但旅游管理系统属于典型的中低并发业务加一个数据库层面的条件更新可以进一步兜底日常够用。我一般在线上继续用事务包裹整个下单过程保证名额统计和订单插入要么同时成功、要么同时回滚。另外下单确认页要把联系人姓名和手机号做成必填校验。手机号做一次格式校验正则表达式覆盖 11 位数字即可过度校验反而会把正常用户挡在门外。4.3 支付状态没有真实支付渠道时的模拟策略严格来说真实支付需要商户号和支付牌照绝大多数学习项目不可能接入真实渠道。常见做法是接入支付宝沙箱环境或者做一个本地模拟支付。我建议优先用沙箱环境因为支付回调的完整链路下单、支付、异步通知验签、修改订单状态和真实业务几乎一致跑一遍沙箱能帮你理解整个支付闭环。如果沙箱没申请下来再用本地模拟支付订单状态为待支付时提供一个“模拟支付成功”的按钮点击后调用后端接口把订单状态改为已支付。不管是哪种方式订单状态流转的逻辑要统一订单创建后是待支付收到支付成功通知后改为已支付。不要把“订单创建成功”和“订单已支付”混为一谈很多项目改来改去最后订单状态乱掉根源就是这里没分清楚。5. 打包部署与联调避坑旅游管理系统真实运行的 5 个常见问题到了部署和联调阶段很多问题不是写业务代码时遇到的而是环境切换、请求路径、数据一致性这些“非业务”环节冒出来的。这一章集中写我见过最多、也最值得提前防住的 5 个问题。这一切都是从实际项目里踩出来的按“现象 → 原因 → 解决”的方式整理。5.1 后台登录成功但跳转后接口全部 401现象管理员输入账号密码登录成功页面跳转到首页但首页所有的统计接口都返回 401。原因登录接口返回的令牌是存在 localStorage 里的但 axios 请求拦截器读取的 key 和登录时写入的 key 不一致。比如登录页面写的是 admin_token请求拦截器读的是 token自然取不到。解决把令牌的存储 key 收敛到一个常量文件里统一管理。类似这种问题看似低级但多人协作时很容易出现写登录模块的人和维护请求封装的人各写各的字符串。建议全项目只允许通过 auth.js 工具类读写令牌任何地方不要直接操作 localStorage。5.2 图片上传成功但前端访问裂图现象后台管理端上传线路封面图提示上传成功前端页面上图片地址无法访问显示裂图。原因这类问题八成是路径不对。上传接口通常返回相对路径比如/uploads/route/20250101.jpg前端拼接的域名或基础路径和后端文件服务的实际路径不一致。开发环境里后端服务挂在 8080图片路径拼接成 localhost:8080/uploads 可以访问生产环境里静态文件服务可能由 Nginx 代理路径前缀变成了 /static/没同步改配置就会 404。解决图片路径不要在前端写死拼接逻辑后端返回完整的可访问 URL。后端在返回图片路径时根据当前环境拼接好域名前缀。这样前端拿到什么就显示什么部署到任何环境都不容易出路径问题。5.3 后台修改线路价格后历史订单金额跟着变动现象管理员把某条线路的价格从 1999 改成 2299结果之前已经下单的订单详情页金额也显示成了 2299。原因订单表只存了线路 id详情页实时联表查询线路表获取价格。线路价格变历史订单展示的价格就变。这是设计缺陷修改成本最高。解决使用第二章提到的线路快照方案。订单创建时把线路标题、价格、封面图存进订单表详情页不查线路表直接取快照数据。这里也能看出数据模型设计阶段多做一步冗余设计比后期打补丁省力得多。5.4 前端 npm run dev 能跑npm run build 后页面白屏现象开发环境一切正常打包后部署到服务器访问首页白屏控制台报资源 404。原因Vite 打包默认资源路径是绝对路径/assets/xxx.js部署在域名子路径下比如https://example.com/travel/时找不到资源。很多后台管理系统部署在 Nginx 的某个 location 下不是直接部署根路径。解决在 vite.config 里设置base: ./这样打包后的资源路径变成相对路径。不过相对路径也有自己的坑如果前端路由用的 history 模式刷新页面时 Nginx 需要配置 try_files 回退到 index.html否则刷新到子路由会 404。history 模式和 base 相对路径的组合需要你按实际部署方式做一次测试。5.5 后台管理进程一关就没了现象把后端服务用npm start或java -jar直接跑在服务器上关掉终端窗口服务就停。原因默认启动方式在终端退出时接收 SIGHUP 信号并终止进程。解决用 nohup 让进程脱离终端运行比如nohup java -jar travel-admin.jar app.log 21 。这条命令把标准输出和错误输出写到 app.log后台运行不随终端退出。日志文件要定期处理我习惯用 logrotate 按天切割避免单个日志文件膨胀到几个 GB 后难以排查问题。如果是容器化部署则交给 docker restart 策略或 K8s 管理不需要 nohup。6. 验收维度与一个实用技巧订单号生成与交付前的自检清单系统做到最后不要急着打包交付。你先要有一份像样的验收标准告诉对方“做到什么程度算完整”。这里说的完整不是功能全部堆砌而是核心链路数据一致、边界情况有处理。6.1 版本交付前的验收清单我在收尾一个模拟项目X时会按下面的清单过一遍每一项都不难测但漏掉任意一项都可能让对方觉得系统“不完整”。验收项操作方式预期结果用户注册登录注册新账号退出后重新登录密码加密存储错误密码登录失败线路上架下架后台下架某线路前台检索页不再出现该线路库存扣减设置上限 5 人下单 5 人后继续下单第 6 单提示余位不足价格快照下单后修改线路价格历史订单价格不变订单状态流转下单→模拟支付→后台标记完成状态字段按 0→1→2 变化后台权限控制用普通运营账号访问管理员管理页路由跳转 404接口返回 403数据一致性删除一条线路历史订单不受影响订单快照可见这个清单本质上是把“业务闭环”翻译成了可执行的测试步骤。每一条都对应一个容易出现问题的环节跑通之后系统才算真正能拿得出手。6.2 一个实用技巧订单号生成策略订单号的设计容易被忽视但它直接影响财务对账和问题排查。我见过有人直接用自增 id 当订单号也见过用时间戳拼接随机数后者在并发时可能重复前者会把订单量暴露给竞争对手。推荐方案日期时间 随机序列 后缀校验。格式为 yyyyMMddHHmmss 4 位随机数字 2 位随机字母例如2025061415304592AB。这个长度在 16 位左右可读性和唯一性兼顾。生成后先查一次数据库确认未重复重复则重新生成。如果并发量更大可以引入号段模式由发号器统一分配但旅游管理系统用不到这个复杂度。-- 订单号唯一索引兜底防止程序并发时生成重复订单号 ALTER TABLE travel_order ADD UNIQUE KEY uk_order_no (order_no);配合唯一索引即使生成逻辑出现极端情况数据库也会拦住重复订单号不会产生两条相同订单号的数据。项目做到这一步核心链路算真正闭合了。最后说一个我的习惯每次改完一个模块我都会把涉及的表结构变更和生产环境的初始化数据同步整理到项目根目录的 init.sql 文件里而不是只改本地数据库。这样做的好处是换一台机器、换一个人接手只要执行一遍 init.sql 就能得到完整的可用环境。我有一次赶进度直接改了生产库没记录后来需要重建测试环境时对不上结构花了大半天才补齐。从那之后任何表的字段变更我都先改 init.sql再动本地库。这个习惯看似不起眼但当你需要把系统部署到第二台机器、第三台机器时会发现它是完整的定义。希望这些内容对你有切实帮助。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站