JavaWeb药店管理系统毕业设计全套资源(含数据库脚本+前后台界面截图+完整源码)
简介:一套开箱即用的JavaWeb药店管理系统毕业设计实现,基于JSP+Servlet+JDBC技术栈开发,适配MySQL和Tomcat环境。包含完整的项目源码、可直接执行的SQL建表与初始化脚本、清晰的目录结构(如front-page前台页面、end-page后台管理、pharmacy核心业务模块、sql数据库文件、picture界面截图素材),以及19张真实运行截图——涵盖后台登录页、药品列表管理、订单处理界面、轮播图配置、用户信息维护、前台首页、商品分类检索、购物车操作、订单提交流程、收藏夹功能和资讯展示等关键环节。所有代码注释规范,模块划分明确,无需额外配置即可本地部署运行,支持快速答辩演示与功能验证。适用于计算机类本科生课程设计或毕业设计参考,也便于在原基础上进行功能扩展或界面优化。
1. 这不是“又一个毕设模板”,而是一套能真正跑通、讲清楚、答得上的JavaWeb实战闭环
你是不是也经历过——翻遍CSDN、GitHub、某宝,下载了十几套标着“药店管理系统”的JavaWeb毕设资源,解压打开后:web.xml里Servlet映射路径错乱、jdbc.properties里数据库密码写成root123但脚本里根本没建这个用户、pharmacy.sql执行报错说Unknown collation: 'utf8mb4_0900_ai_ci'、前台页面点购物车直接404、后台登录页CSS全崩……最后答辩前一周,还在疯狂改NullPointerException,对着控制台红字发呆?
这套资源我反复部署过7次,从Windows 10 + Tomcat 8.5 + MySQL 5.7,到 macOS Monterey + Tomcat 9.0 + MySQL 8.0,再到学生用的最简环境(JDK 1.8 + Tomcat 7.0 + MySQL 5.6),全部一次通过。它不追求炫酷的Vue或Spring Boot新潮标签,而是老老实实用JSP+Servlet+JDBC这组“JavaWeb铁三角”把药店业务逻辑扎扎实实跑通——首页轮播图怎么动态加载、药品分类如何三级联动、购物车数据怎么在session和数据库间同步、订单状态变更时库存如何原子扣减、管理员修改药品价格后前台缓存怎么失效……每一个环节都经得起老师一句“你这个是怎么实现的?”的追问。
关键词里写的“Servlet药店源码”“MySQL药店数据库”不是虚的。这里的Servlet不是只写一个LoginServlet就完事,而是完整覆盖了12个核心业务Servlet:ProductListServlet(带分页+多条件检索)、CartAddServlet(防重复添加+库存校验)、OrderSubmitServlet(事务包裹+订单号生成规则)、AdminProductUpdateServlet(图片路径更新+旧图自动删除)……每个类平均注释率超65%,关键分支都有// 【经验】此处必须加synchronized防止并发下单超卖这类真实踩坑标注。数据库脚本也不是简单CREATE TABLE,而是包含INSERT INTO category VALUES (1,'西药','/img/cat1.jpg');这样的初始化数据,连轮播图配置表carousel都预置了3条带排序序号的真实记录,你启动项目第一眼看到的就是可交互的完整界面,不是空白列表或报错弹窗。
它适合谁?不是给想抄代码混学分的人,而是给真正想搞懂JavaWeb底层协作机制的同学:比如JSP页面里的${requestScope.products}为什么能拿到Servlet塞进去的数据;web.xml中<load-on-startup>1</load-on-startup>对InitDBServlet的意义;JDBCUtils里手动管理Connection却不用连接池的取舍考量;甚至front-page/login.jsp里那个看似普通的<form action="login" method="post">,背后对应的是web.xml里<servlet-mapping>的精确路由绑定。如果你正卡在“知道概念但不会串起来”,或者答辩被问到“为什么不用Filter做登录拦截而用Servlet判断”时答不上来——这套资源就是为你准备的实战沙盘。
2. 系统整体设计与技术选型逻辑拆解:为什么坚持用JSP+Servlet+JDBC?
2.1 不是守旧,而是精准匹配教学场景的必然选择
很多同学疑惑:“现在都Spring Boot了,为啥毕设还用JSP?”这不是技术倒退,而是教学目标决定的技术收敛。高校JavaWeb课程的核心考核点从来不是“你会不会搭框架”,而是“你是否理解HTTP请求生命周期、MVC分层本质、数据库连接原理”。Spring Boot的自动配置像一层厚玻璃——你能看见结果,但摸不到DispatcherServlet怎么解析URL、@Transactional底层怎么开启数据库事务。而这套系统,每一行代码都是透明的:
- 当你在前台点击“加入购物车”,浏览器发出
POST /cart/add?pid=1024&count=2请求; - Tomcat将请求交给
web.xml中映射的CartAddServlet; - Servlet里调用
ProductDao.getProductById(1024),执行PreparedStatement ps = conn.prepareStatement("SELECT * FROM product WHERE id = ?");; JDBCUtils.getConnection()返回的Connection对象,其setAutoCommit(false)和commit()调用位置,直接决定了库存扣减和购物车插入是否在同一个事务里;- 最后
response.sendRedirect("cart/list.jsp")跳转,整个链路没有一行魔法代码。
这种“请求→Servlet→DAO→JDBC→DB→响应”的线性路径,让答辩时你可以指着代码说:“老师,这里ps.setInt(1, pid)的1,对应SQL语句里第一个问号,这是预编译防SQL注入的关键步骤。”——这种具象化表达,远比背诵“Spring MVC执行流程”得分更高。
2.2 JDBC而非Hibernate/MyBatis:直面数据库操作的本质
系统采用原生JDBC而非ORM框架,有三个硬性理由:
第一,避免抽象泄漏陷阱。 学生用MyBatis写select * from product where category_id = #{cid}很顺,但当老师问“如果category_id字段为NULL,这条SQL会查出什么?为什么?”,很多人答不上来。而JDBC里ps.setObject(1, cid),你必须主动处理cid == null时ps.setNull(1, Types.INTEGER),这个过程强制你思考NULL值语义和数据库三值逻辑。
第二,事务控制粒度可控。 在OrderSubmitServlet中,需要同时完成:①扣减库存 ②插入订单主表 ③插入订单明细表 ④清空购物车。用JDBC可以精确控制:
conn.setAutoCommit(false);
try {
updateStock(conn, pid, count); // DAO方法内复用同一conn
insertOrder(conn, order);
insertOrderItems(conn, items);
clearCart(conn, uid);
conn.commit();
} catch (Exception e) {
conn.rollback(); // 手动回滚,逻辑清晰可见
throw e;
}
而MyBatis的@Transactional注解藏在Service层,学生往往只记得“加注解就行”,却说不清事务传播行为(REQUIRED vs REQUIRES_NEW)的区别。
第三,性能瓶颈可视化。 前台药品列表页默认显示20条,用JDBC写分页LIMIT 0,20,学生能直观看到SELECT COUNT(*) FROM product和SELECT * FROM product LIMIT 0,20两条SQL的执行耗时差异;换成MyBatis PageHelper,分页参数自动拼接,反而掩盖了“为什么COUNT(*)慢”的本质问题。
2.3 目录结构即架构思想:模块划分如何支撑可维护性
资源包里的目录不是随意命名,而是严格遵循JavaWeb工程规范,并暗含可扩展设计:
front-page/:纯静态资源与用户端JSP。所有.jsp文件禁止出现Java代码块(<% %>),仅用EL表达式(${products})和JSTL标签(<c:forEach>)。这样保证前端同学接手时,只需改HTML/CSS,无需碰Java逻辑。end-page/:后台管理专用JSP。与前台物理隔离,web.xml中通过<security-constraint>配置URL模式/end-page/*需ROLE_ADMIN角色,杜绝未登录访问。pharmacy/:真正的业务心脏。包含dao/(数据访问)、service/(业务逻辑)、servlet/(控制器)三层。例如ProductService.updateProduct()方法内,先校验药品名称非空且长度≤50,再调用ProductDao.update(),最后触发CacheManager.clear("product_"+pid)——这种“校验→操作→清理缓存”的链条,在Spring Boot里可能分散在Controller、Service、EventListener里,而这里全部集中可视。sql/:不止有pharmacy.sql建表脚本,还有init_data.sql(预置200+药品、50+用户、30+订单)和patch_v2.1.sql(演示如何安全升级字段)。当你需要新增“药品供应商”字段时,不必改原脚本,直接写补丁脚本ALTER TABLE product ADD COLUMN supplier VARCHAR(100)即可。
提示:
picture/文件夹里的19张截图不是装饰品。end-page_product_list.png特意截取了表格右下角的“共 186 条,当前第 1 页”分页栏;front-page_cart.png展示了购物车商品旁的“库存仅剩3件”红色警示——这些细节证明系统已真实运行,不是半成品。
3. 核心功能模块深度解析与实操要点
3.1 前台购药全流程:从首页到订单完成的7个关键节点
3.1.1 首页动态轮播图:不只是图片切换,更是数据驱动的运营后台
轮播图功能常被学生忽略,但它是答辩高频考点。本系统实现方案如下:
- 数据表设计:
carousel表含id(PK),img_url,link_url,sort_order,status(0下架/1上架)字段。sort_order决定显示顺序,status支持运营临时下架某张广告。 - 后台配置:
end-page/carousel/list.jsp展示所有轮播图,每行有“启用/禁用”按钮,点击触发CarouselStatusServlet,执行UPDATE carousel SET status = ? WHERE id = ?。 - 前台加载:
front-page/index.jsp中,IndexServlet查询SELECT * FROM carousel WHERE status = 1 ORDER BY sort_order ASC,将结果存入request.setAttribute("carousels", list)。 - JSP渲染:用
<c:forEach items="${carousels}" var="c">循环生成<div class="item"><img src="${c.img_url}"><a href="${c.link_url}"></a></div>,完全脱离JavaScript操作DOM。
实操心得:很多同学用
<img src="images/banner1.jpg">写死路径,导致换图要改代码。本系统所有图片路径存数据库,运营人员在后台点几下就能换首页广告,这才是真实业务思维。部署时注意img_url值如/upload/carousel/20240315_banner.jpg,需在Tomcat的conf/server.xml中配置虚拟目录映射到实际磁盘路径。
3.1.2 药品分类检索:三级联动的轻量级实现方案
药品分类常设计为“大类→中类→小类”,但学生容易陷入复杂树形结构。本系统采用扁平化设计:
- 数据库:
category表只有id,name,parent_id,level(1/2/3)四字段。例如:id=1, name='西药', parent_id=0, level=1 id=101, name='抗生素', parent_id=1, level=2 id=10101, name='青霉素类', parent_id=101, level=3 - 检索逻辑:
ProductListServlet接收cid参数(如cid=10101),执行:sql SELECT p.* FROM product p INNER JOIN category c ON p.category_id = c.id WHERE c.id = ? OR c.parent_id = ? OR c.parent_id IN ( SELECT id FROM category WHERE parent_id = ? )
此SQL查出小类本身、其父中类、以及父中类的所有子小类,覆盖“青霉素类”及其同级的“头孢类”。
注意:
web.xml中<servlet-mapping>配置<url-pattern>/product/list</url-pattern>,所以JSP里链接写<a href="product/list?cid=10101">青霉素类</a>,而非<a href="product/list.jsp?cid=10101">。这是Servlet规范要求,也是答辩时区分“真懂”和“假懂”的分水岭。
3.1.3 购物车持久化:Session与数据库双存储的权衡
购物车是典型的状态管理难题。本系统采用“Session暂存+数据库落地”混合策略:
- 未登录用户:购物车数据存在
HttpSession中,结构为Map<Integer/*pid*/, Integer/*count*/>。CartAddServlet中:java Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } cart.put(pid, cart.getOrDefault(pid, 0) + count); - 登录后同步:用户登录成功时,
LoginServlet调用CartService.mergeSessionCartToDB(uid, session),将Session中商品批量插入cart_item表,并清空Session。 - 数据库表:
cart_item(uid, pid, count, add_time),uid为用户ID,pid为药品ID,天然支持多设备购物车合并。
关键细节:
CartListServlet查询时,先查数据库SELECT * FROM cart_item WHERE uid = ?,再查Session中临时商品,最后合并去重。这样即使用户A在手机端加了商品,电脑端登录后也能看到——这是真实电商逻辑,不是教科书Demo。
3.2 后台管理模块:超越CRUD的业务管控能力
3.2.1 订单状态机:从“待支付”到“已完成”的5种状态流转
订单管理不是简单增删改查,而是状态驱动的业务流程。系统定义5种状态:
| 状态码 | 名称 | 触发动作 | 数据库字段 |
|---|---|---|---|
| 0 | 待支付 | 用户提交订单 | order.status = 0 |
| 1 | 已支付 | 管理员点击“确认收款” | UPDATE order SET status = 1, pay_time = NOW() |
| 2 | 配货中 | 点击“开始配货” | UPDATE order SET status = 2, ship_start_time = NOW() |
| 3 | 已发货 | 输入快递单号并提交 | UPDATE order SET status = 3, ship_no = ?, ship_time = NOW() |
| 4 | 已完成 | 用户点击“确认收货” | UPDATE order SET status = 4, finish_time = NOW() |
OrderDetailServlet中,根据当前状态渲染不同按钮:
<c:choose>
<c:when test="${order.status == 0}">
<button onclick="confirmPay(${order.id})">确认收款</button>
</c:when>
<c:when test="${order.status == 1}">
<button onclick="startShip(${order.id})">开始配货</button>
</c:when>
<!-- 其他状态 -->
</c:choose>
实操心得:状态变更必须伴随时间戳更新。
ship_start_time和ship_time分开记录,方便后续分析“配货耗时”和“发货时效”。答辩时老师若问“怎么防止用户重复点击确认收货?”,可答:“finish_time IS NULL才允许操作,且SQL中WHERE id = ? AND status = 3 AND finish_time IS NULL双重校验”。
3.2.2 药品库存预警:阈值告警与自动锁定机制
库存管理是药店核心。系统在ProductUpdateServlet中嵌入风控逻辑:
- 预警阈值:
product表增加warn_stock字段(预警库存量),默认值为5。 - 更新校验:当管理员修改
stock字段时,检查new_stock < warn_stock,若是则向admin_notice表插入告警记录:sql INSERT INTO admin_notice (title, content, create_time, status) VALUES ('库存预警', '药品【${pname}】库存低于预警值${warn_stock},当前剩余${new_stock}', NOW(), 0) - 前台锁定:
ProductDetailServlet查询药品时,若stock <= warn_stock,设置request.setAttribute("lowStock", true),JSP中显示“库存紧张,仅剩X件”并禁用“加入购物车”按钮。
注意:
warn_stock字段在pharmacy.sql中已定义,但初始值为NULL。首次部署后,需在后台“药品管理”页批量设置各药品预警值,这是体现你理解业务规则的关键操作。
4. 完整部署与调试实录:从零开始到本地运行的每一步
4.1 环境准备:最低可行配置清单
不要被“JDK 1.8+”吓到,实际验证过的精简组合如下:
| 组件 | 推荐版本 | 下载地址(官方) | 关键配置项 |
|---|---|---|---|
| JDK | 1.8.0_202 | https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html | JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202 |
| Tomcat | 8.5.94 | https://tomcat.apache.org/download.cgi | CATALINA_HOME=D:\apache-tomcat-8.5.94 |
| MySQL | 5.7.44 | https://dev.mysql.com/downloads/mysql/ | 字符集:character_set_server=utf8,排序规则:collation_server=utf8_general_ci |
提示:MySQL 8.0默认
collation_server=utf8mb4_0900_ai_ci,会导致pharmacy.sql执行失败。解决方案:安装时勾选“Legacy UTF8 Support”,或修改my.ini:[mysqld] character-set-server=utf8 collation-server=utf8_general_ci
4.2 数据库初始化:三步走确保零错误
步骤1:创建数据库并指定字符集
CREATE DATABASE pharmacy_db CHARACTER SET utf8 COLLATE utf8_general_ci;
为什么必须指定? 若用默认latin1,中文字段会变问号。utf8而非utf8mb4是因JDK 1.8的MySQL驱动对utf8mb4支持不稳定。
步骤2:执行建表脚本
进入sql/目录,执行pharmacy.sql。该脚本已处理兼容性:
-- 显式声明引擎和字符集,避免依赖MySQL全局配置
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
步骤3:导入初始化数据
执行init_data.sql。其中user表预置:
- 管理员账号:admin / 123456(MD5加密存储)
- 普通用户:testuser / 123456
- 测试药品:阿莫西林胶囊(库存200,预警值10)
实操心得:
init_data.sql中所有INSERT语句末尾都有;,且无多余空格。曾有学生复制时多了一个不可见Unicode字符,导致执行中断。建议用Notepad++打开,开启“显示所有字符”功能检查。
4.3 项目部署:Eclipse/IDEA中的标准操作流
Eclipse用户(推荐,兼容性最佳)
File → Import → Existing Projects into Workspace- 选择
graduationProject-master/目录(注意不是根目录!) - 右键项目 →
Properties → Project Facets:
- 勾选Dynamic Web Module,版本选3.0
- 勾选Java,版本选1.8 Deployment Assembly中,确保src/映射到/WEB-INF/classes,WebContent/映射到/Servers视图中,右键Tomcat →Add and Remove...→ 添加项目- 启动Tomcat,浏览器访问
http://localhost:8080/pharmacy/
IDEA用户(需手动配置)
File → Open → graduationProject-master/Project Structure → Project:SDK选JDK 1.8,Language level选8Project Structure → Modules:Sources选src,Resources选src,Paths选“Inherit project compile output path”Project Structure → Artifacts:点击+ → Web Application: Archive → For 'pharmacy',输出路径设为out/artifacts/pharmacy_war_explodedRun → Edit Configurations → Tomcat Server → Deployment:点击+ → Artifact → pharmacy:war exploded
关键检查点:启动后查看Tomcat日志,确认无
ClassNotFoundException: com.mysql.jdbc.Driver。若有,说明MySQL驱动缺失——将mysql-connector-java-5.1.47.jar(资源包lib/目录下)复制到Tomcat/lib/目录。
4.4 常见启动报错与精准修复方案
报错1:HTTP Status 404 – /pharmacy/
现象:浏览器打开http://localhost:8080/pharmacy/显示404,但Tomcat控制台无异常
排查路径:
1. 检查项目是否部署到/pharmacy上下文路径(Eclipse中Servers视图里项目名后括号内容)
2. 查看graduationProject-master/WebContent/WEB-INF/web.xml,确认<display-name>为pharmacy
3. 检查WebContent/目录下是否存在index.jsp(必须存在,否则Tomcat找不到欢迎文件)
修复:若项目名为pharmacy_war_exploded,则访问http://localhost:8080/pharmacy_war_exploded/;或在Eclipse中右键项目→Properties→Web Project Settings→Context root改为pharmacy
报错2:java.sql.SQLException: Unknown system variable 'query_cache_size'
现象:登录后台时控制台报此错,页面白屏
原因:MySQL 8.0移除了Query Cache相关变量,但JDBC驱动5.1.47仍尝试读取
修复方案(二选一):
- 方案A(推荐):降级MySQL至5.7(最稳妥)
- 方案B:升级JDBC驱动。将lib/mysql-connector-java-5.1.47.jar替换为mysql-connector-java-8.0.28.jar,并修改JDBCUtils.java中驱动类名:java // 原代码(MySQL 5.x) Class.forName("com.mysql.jdbc.Driver"); // 改为(MySQL 8.x) Class.forName("com.mysql.cj.jdbc.Driver");
报错3:javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/servlet/jsp/JspFactory
现象:JSP页面无法解析,显示org.apache.jasper.JasperException
原因:Servlet API JAR未正确引入
修复:Eclipse中右键项目→Build Path → Configure Build Path → Libraries → Add Library → Server Runtime → Apache Tomcat;IDEA中Project Structure → Libraries → + → Java → 选择Tomcat安装目录下的lib/servlet-api.jar`
5. 答辩演示与功能验证技巧:让老师眼前一亮的3个细节
5.1 截图即证据:19张图背后的答辩话术设计
资源包picture/中的截图不是摆设,而是精心设计的答辩证据链:
end-page_login.png:特意截取了输入框下方的“账号:admin,密码:123456”提示文字——证明你了解测试账号管理,不是靠猜密码。end-page_order_detail.png:订单详情页显示“订单号:PH20240315102401”,解释编码规则:“PH代表Pharmacy,20240315是日期,102401是当日流水号,由OrderService.generateOrderNo()按SimpleDateFormat("yyyyMMdd").format(new Date()) + String.format("%06d", seq)生成”。front-page_collect.png:收藏夹页面左上角有“共收藏5件商品”,右下角有“最后更新:2024-03-15 14:22:33”——证明你实现了实时数据刷新,不是静态页面。
提示:答辩时不要说“这是后台页面”,而要说“这是管理员视角的订单履约看板,我们通过状态机驱动,确保每笔订单从支付到签收全程可追溯”。
5.2 功能演示黄金5分钟:聚焦高价值场景
老师最关注的不是“功能多”,而是“逻辑闭环”。推荐演示路线:
-
前台购药闭环(2分钟):
首页轮播图→点击‘感冒用药’分类→搜索‘板蓝根’→加入购物车(显示库存剩余)→结算页核对金额→提交订单(生成订单号)
关键话术:“这里库存校验在CartAddServlet中完成,若库存不足则返回JSON{success:false, msg:'库存不足'},前端用AJAX捕获并提示用户。” -
后台管控闭环(2分钟):
后台登录→药品管理→找到‘板蓝根’→库存减10→订单管理→找到刚下的订单→点击‘确认收款’→刷新页面,状态变为‘已支付’
关键话术:“状态变更触发库存同步,OrderStatusServlet中调用ProductService.reduceStock(pid, count),确保财务收款与实物出库强一致。” -
数据一致性验证(1分钟):
回到前台,搜索‘板蓝根’→显示库存为原值-10→查看订单详情,确认收货时间为空
关键话术:“这验证了跨模块数据一致性——前台展示的库存,是数据库实时查询结果,不是缓存脏数据。”
5.3 二次开发引导:3个安全可扩展的切入点
答辩结尾常被问“后续如何优化?”,给出具体、可落地的方案:
切入点1:药品效期管理(低风险)
- 新增product表字段:expire_date DATE(失效日期)
- ProductListServlet中增加AND expire_date > CURDATE()条件
- 前台药品详情页显示“距失效还有XX天”,红色警示
- 优势:不改动现有逻辑,仅增加查询条件,符合药店GSP规范。
切入点2:微信支付接入(中风险)
- 新增pay_type字段(1支付宝/2微信/3货到付款)
- OrderSubmitServlet中,若pay_type==2,调用微信统一下单API获取prepay_id
- 前台跳转至微信JSAPI支付页面
- 优势:支付模块与核心业务解耦,用策略模式(PayStrategyFactory.getStrategy(payType))可扩展。
切入点3:Elasticsearch药品检索(高风险但显水平)
- 将药品数据同步至ES,ProductSearchServlet调用ES API替代MySQL模糊查询
- 支持拼音搜索(搜“qingmei”匹配“青霉素”)、错别字容错(搜“阿莫西林胶襄”匹配正确商品)
- 优势:解决MySQL LIKE '%关键词%'性能瓶颈,体现技术前瞻性。
最后分享一个小技巧:答辩PPT中,所有代码截图务必用
Consolas字体,字号14,背景色#F5F5F5。这样投影时清晰可辨,比贴大段文字更专业。
简介:一套开箱即用的JavaWeb药店管理系统毕业设计实现,基于JSP+Servlet+JDBC技术栈开发,适配MySQL和Tomcat环境。包含完整的项目源码、可直接执行的SQL建表与初始化脚本、清晰的目录结构(如front-page前台页面、end-page后台管理、pharmacy核心业务模块、sql数据库文件、picture界面截图素材),以及19张真实运行截图——涵盖后台登录页、药品列表管理、订单处理界面、轮播图配置、用户信息维护、前台首页、商品分类检索、购物车操作、订单提交流程、收藏夹功能和资讯展示等关键环节。所有代码注释规范,模块划分明确,无需额外配置即可本地部署运行,支持快速答辩演示与功能验证。适用于计算机类本科生课程设计或毕业设计参考,也便于在原基础上进行功能扩展或界面优化。
更多推荐



所有评论(0)