广州作为一线城市,体育场馆资源供需矛盾日益突出。传统电话预约方式效率低下,经常出现"空场无人用"和"想订订不到"的双输局面。去年我曾参与本地羽毛球协会的场地协调工作,亲眼目睹管理员每天要接听上百个咨询电话,手写登记本涂改得面目全非。这种背景下,我们团队开发了这套基于现代Web技术的场馆预定管理系统。
系统采用Vue+Node.js+ElementUI的技术组合,实现了从场地查询、在线预订到支付结算的全流程数字化管理。上线三个月后,天河体育中心分馆的场地利用率提升了27%,投诉率下降63%。最让我意外的是,许多中老年用户通过简单的操作培训后,也能熟练使用这套系统——这要归功于ElementUI优秀的交互设计。
选择Vue.js 2.x版本(现也已升级支持3.x)主要基于三点考虑:
实际开发中,我们特别优化了日期时间选择组件。体育场馆的预定往往以小时为单位,且需要区分工作日/节假日价格。通过改造el-date-picker组件,实现了如下特色功能:
vue复制<el-time-select
v-model="selectedHours"
:picker-options="{
start: '08:00',
step: '01:00',
end: '22:00',
disabledHours: this.getDisabledHours
}">
</el-time-select>
Node.js + Express的轻量级组合完美匹配场馆管理的业务特点:
数据库采用MySQL+Redis组合方案:
javascript复制// 位图操作示例
const setCourtStatus = (courtId, hour) => {
const key = `court:${courtId}:${dayString}`;
redis.setbit(key, hour, 1);
}
这是系统最关键的难点。我们采用三层校验机制:
重要提示:必须使用事务处理预定操作,伪代码如下:
sql复制START TRANSACTION;
SELECT status FROM courts WHERE id=1 FOR UPDATE;
-- 检查状态
UPDATE courts SET status='reserved' WHERE id=1;
INSERT INTO orders (...) VALUES (...);
COMMIT;
场馆在不同时段有不同定价策略,我们设计了规则引擎:
javascript复制// 价格规则配置示例
{
"courtType": "badminton",
"timeRanges": [
{
"days": [1,2,3,4,5], // 周一到周五
"hours": "9-12,14-17",
"price": 80
},
{
"days": [6,0], // 周末
"hours": "all",
"price": 120
}
]
}
基于ElementUI的Admin模板快速构建了管理后台,几个实用技巧:
vue复制<el-button v-permission="['admin','manager']">删除场地</el-button>
javascript复制app.get('/api/courts/status', cacheMiddleware(10), (req, res) => {
// 10秒缓存
});
初期有用户反映点击预定后长时间无响应。经排查发现:
javascript复制// 数据库配置调整
pool: {
max: 50,
acquireTimeout: 30000
}
ElementUI默认在移动端体验不佳,我们做了这些改进:
javascript复制const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
使用ECharts实现的关键指标看板:
一个实用的数据处理技巧:
javascript复制// 将数据库原始数据转换为ECharts需要的格式
const transformData = (raw) => {
return raw.map(item => ({
name: item.courtName,
value: [item.weekday, item.hour, item.usageRate]
}));
}
采用Docker-compose实现一键部署:
yaml复制version: '3'
services:
frontend:
build: ./frontend
ports:
- "8080:80"
backend:
build: ./backend
ports:
- "3000:3000"
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: password
在实现预定冲突检测功能时,有个细节值得分享:最初我们采用简单的时间段比对算法,后来发现需要处理各种边界情况,比如:
最终我们抽象出了状态检测的状态模式实现:
javascript复制class StatusChecker {
check() {
throw new Error('抽象方法');
}
}
class TimeRangeChecker extends StatusChecker {
// 实现具体检查逻辑
}
这套系统上线后最大的收获是:技术方案必须服从业务场景。比如最初我们设计了非常精确到分钟级的预定系统,但实际运营发现场馆清洁、设备维护都需要缓冲时间,最终调整为以小时为最小单位,反而提升了用户体验和运营效率。