「实用场景教程」如何用日程控件DHTMLX Scheduler制作酒店预订日历?(三)

dhtmlxScheduler是一个类似于Google日历的JavaScript日程安排控件,日历事件通过Ajax动态加载,支持通过拖放功能调整事件日期和时间,事件可以按天,周,月三个种视图显示。

DHTMLX Scheduler正式版下载

在本教程中,我们将使用两个强大的工具:DHTMLX Scheduler库和Angular框架来创建一个全面的酒店客房预订应用程序。在上文中(点击这里回顾>>)我们为大家介绍了提供保存数据中的数据加载、CRUD操作实现等,本文将继续介绍服务器配置。

Step 5 – 服务器配置

现在让我们继续为应用程序设置Node.js服务器,本教程使用Express框架和MySQL作为数据存储。

添加依赖项和安装模块

您应该设置MySQL服务器,或者可以使用其他服务,例如免费MySQL托管。

添加express、mysql和date-format-lite模块:

$ npm install express mysql date-format-lite

server.js被指定为上面的输入点,现在让我们在项目的根目录下创建server文件夹,并添加server.js文件,代码如下:

const express = require('express'); // use Express
const app = express(); // create application
const port = 3000; // port for listening
const cors = require('cors');
app.use(cors()); // enable CORS for all routes// MySQL will be used for db access and util to promisify queries
const util = require('util');
const mysql = require('mysql');// use your own parameters for database
const mysqlConfig = {
'connectionLimit': 10,
'host': 'localhost',
'user': 'root',
'password': '',
'database': 'room_reservation_node'
};app.use(express.json()); // Enable JSON body parsing
// return static pages from the './public' directory
app.use(express.static(__dirname + '/public'));// start server
app.listen(port, () = {
console.log('Server is running on port ' + port + '...');
});const router = require('./router');// open connection to mysql
const connectionPool = mysql.createPool(mysqlConfig);
connectionPool.query = util.promisify(connectionPool.query);// add listeners to basic CRUD requests
const DatabaseHandler = require('./databaseHandler');
const databaseHandler = new DatabaseHandler(connectionPool);
router.setRoutes(app, '/data', databaseHandler);

然后打开package.json文件夹,将start语句替换为:

"scripts": {
"ng": "ng",
"start": "concurrently \"node server/server.js\" \"ng serve\"",
…

我们将使用concurrent包来同时启动服务器和客户端应用程序,因此添加concurrent模块:

$ npm install concurrently

准备数据库

让我们将Scheduler连接到数据库,并定义在其中读取和写入项的方法。

  • 创建数据库:

首先我们需要一个数据库来工作,您可以使用自己喜欢的mysql-client或通过控制台创建数据库。

要使用mysql-client创建数据库,打开它并执行下面的代码,创建预订表:

CREATE TABLE `reservations` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`start_date` datetime NOT NULL,
`end_date` datetime NOT NULL,
`text` varchar(255) DEFAULT NULL,
`room` varchar(255) DEFAULT NULL,
`booking_status` varchar(255) DEFAULT NULL,
`is_paid` BOOLEAN DEFAULT NULL CHECK (is_paid IN (0, 1)),
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

让我们添加一些测试数据:

INSERT INTO `reservations` VALUES (2, '2023-08-01', '2023-08-11', 'RSV2023-08-01ABC124', 3, 4, true);
INSERT INTO `reservations` VALUES (3, '2023-08-07', '2023-08-17', 'RSV2023-08-07ABC126', 5, 3, true);
INSERT INTO `reservations` VALUES (4, '2023-08-04', '2023-08-16', 'RSV2023-08-04ABC125', 7, 4, false);
INSERT INTO `reservations` VALUES (13, '2023-07-28', '2023-08-14', 'RSV2023-07-28ABC123', 1, 4, true);
INSERT INTO `reservations` VALUES (14, '2023-08-14', '2023-08-27', 'RSV2023-08-14ABC129', 1, 3, false);
INSERT INTO `reservations` VALUES (15, '2023-08-19', '2023-08-29', 'new booking', 4, 1, false);
INSERT INTO `reservations` VALUES (16, '2023-08-24', '2023-08-31', 'new booking', 11, 1, false);
INSERT INTO `reservations` VALUES (17, '2023-08-17', '2023-08-26', 'RSV2023-08-17ABC135', 6, 2, false);
INSERT INTO `reservations` VALUES (18, '2023-08-18', '2023-08-31', 'RSV2023-08-18ABC139', 9, 2, false);
INSERT INTO `reservations` VALUES (19, '2023-08-02', '2023-08-12', 'RSV2023-08-02ABC127', 10, 4, true);
INSERT INTO `reservations` VALUES (20, '2023-08-12', '2023-08-21', 'RSV2023-08-12ABC130', 10, 3, false);

创建房间表:

CREATE TABLE `rooms` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`cleaning_status` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

添加一些测试数据:

INSERT INTO `rooms` VALUES ('1', '1', '101', '1', '1');
INSERT INTO `rooms` VALUES ('2', '2', '102', '1', '3');
INSERT INTO `rooms` VALUES ('3', '3', '103', '1', '2');
INSERT INTO `rooms` VALUES ('4', '4', '104', '1', '1');
INSERT INTO `rooms` VALUES ('5', '5', '105', '2', '1');
INSERT INTO `rooms` VALUES ('6', '6', '201', '2', '2');
INSERT INTO `rooms` VALUES ('7', '7', '202', '2', '1');
INSERT INTO `rooms` VALUES ('8', '8', '203', '3', '3');
INSERT INTO `rooms` VALUES ('9', '9', '204', '3', '3');
INSERT INTO `rooms` VALUES ('10', '10', '301', '4', '2');
INSERT INTO `rooms` VALUES ('11', '11', '302', '4', '2');
INSERT INTO `rooms` VALUES ('12', '12', '303', '1', '2');

创建roomTypes表:

CREATE TABLE `roomTypes` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

添加一些测试数据:

INSERT INTO `roomTypes` VALUES ('1', '1', '1 bed');
INSERT INTO `roomTypes` VALUES ('2', '2', '2 bed');
INSERT INTO `roomTypes` VALUES ('3', '3', '3 bed');
INSERT INTO `roomTypes` VALUES ('4', '4', '4 bed');

创建cleaningStatuses表:

CREATE TABLE `cleaningStatuses` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
`color` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

添加一些测试数据:

INSERT INTO `cleaningStatuses` VALUES ('1', '1', 'Ready', '#43a047');
INSERT INTO `cleaningStatuses` VALUES ('2', '2', 'Dirty', '#e53935');
INSERT INTO `cleaningStatuses` VALUES ('3', '3', 'Clean up', '#ffb300');

创建bookingStatuses表:

CREATE TABLE `bookingStatuses` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

添加一些测试数据:

INSERT INTO `bookingStatuses` VALUES ('1', '1', 'New');
INSERT INTO `bookingStatuses` VALUES ('2', '2', 'Confirmed');
INSERT INTO `bookingStatuses` VALUES ('3', '3', 'Arrived');
INSERT INTO `bookingStatuses` VALUES ('4', '4', 'Checked Out');
  • 实现数据访问:

所有的读/写逻辑都将在一个名为DatabaseHandler的单独模块中定义,它将使用mysql连接并在指定的表中执行简单的CRUD操作:读取所有项,插入新项,更新或删除现有项。为此创建databaseHandler.js文件,并将以下代码添加到其中:

require('date-format-lite'); // add date formatclass DatabaseHandler {
constructor(connection, table) {
this._db = connection;
this.table = 'reservations';
}/// ↓↓↓ reservations handler ↓↓↓
// get reservations, use dynamic loading if parameters sent
async getAllReservations(params) {
let query = 'SELECT * FROM ??';
let queryParams = [
this.table
];let result = await this._db.query(query, queryParams);result.forEach((entry) = {
// format date and time
entry.start_date = entry.start_date.format('YYYY-MM-DD hh:mm');
entry.end_date = entry.end_date.format('YYYY-MM-DD hh:mm');
});return result;
}// create new reservation
async insert(data) {
let result = await this._db.query(
'INSERT INTO ?? (`start_date`, `end_date`, `text`, `room`, `booking_status`, `is_paid`) VALUES (?,?,?,?,?,?)',
[this.table, data.start_date, data.end_date, data.text, data.room, data.booking_status, data.is_paid]);return {
action: 'inserted',
tid: result.insertId
}
}// update reservation
async update(id, data) {
await this._db.query(
'UPDATE ?? SET `start_date` = ?, `end_date` = ?, `text` = ?, `room` = ?, `booking_status` = ?, `is_paid` = ? WHERE id = ?',
[this.table, data.start_date, data.end_date, data.text, data.room, data.booking_status, data.is_paid, id]);return {
action: 'updated'
}
}// delete reservation
async delete(id) {
await this._db.query(
'DELETE FROM ?? WHERE `id`=? ;',
[this.table, id]);return {
action: 'deleted'
}
}
/// ↑↑↑ reservations handler ↑↑↑/// ↓↓↓ room cleanup status handler ↓↓↓
// get rooms
async getAllRooms(params) {
let query = 'SELECT * FROM ??';
let queryParams = [
'rooms'
];let result = await this._db.query(query, queryParams);return result;
}// update room cleanup status
async updateRoomCleaningStatus(id, data) {
await this._db.query(
'UPDATE ?? SET `value` = ?, `label` = ?, `type` = ?, `cleaning_status` = ? WHERE id = ?',
['rooms', data.key, data.label, data.type, data.cleaning_status, id]);return {
action: 'updated'
}
}
/// ↑↑↑ room cleanup status handler ↑↑↑/// ↓↓↓ get room types ↓↓↓
async getRoomTypes(params) {
let query = 'SELECT * FROM ??';
let queryParams = [
'roomTypes'
];let result = await this._db.query(query, queryParams);return result;
}
/// ↑↑↑ get room types ↑↑↑/// ↓↓↓ get cleaning statuses ↓↓↓
async getCleaningStatuses(params) {
let query = 'SELECT * FROM ??';
let queryParams = [
'cleaningStatuses'
];let result = await this._db.query(query, queryParams);return result;
}
/// ↑↑↑ get cleaning statuses ↑↑↑/// ↓↓↓ get booking statuses ↓↓↓
async getBookingStatuses(params) {
let query = 'SELECT * FROM ??';
let queryParams = [
'bookingStatuses'
];let result = await this._db.query(query, queryParams);return result;
}
/// ↑↑↑ get booking statuses ↑↑↑
}module.exports = DatabaseHandler;
路由

然后需要设置路由,以便放置在页面上的调度器可以访问存储。为此创建另一个helper模块,并将其命名为router.js:

function callMethod (method) {
return async (req, res) = {
let result;try {
result = await method(req, res);
} catch (e) {
result = {
action: 'error',
message: e.message
}
}res.send(result);
}
};module.exports = {
setRoutes (app, prefix, databaseHandler) {
/// ↓↓↓ reservations router ↓↓↓
app.get(`${prefix}/reservations`, callMethod((req) = {
return databaseHandler.getAllReservations(req.query);
}));app.post(`${prefix}/reservations`, callMethod((req) = {
return databaseHandler.insert(req.body);
}));app.put(`${prefix}/reservations/:id`, callMethod((req) = {
return databaseHandler.update(req.params.id, req.body);
}));app.delete(`${prefix}/reservations/:id`, callMethod((req) = {
return databaseHandler.delete(req.params.id);
}));
/// ↑↑↑ reservations router ↑↑↑/// ↓↓↓ rooms router ↓↓↓
app.get(`${prefix}/collections/rooms`, callMethod((req) = {
return databaseHandler.getAllRooms(req.query);
}));app.put(`${prefix}/collections/rooms/:id`, callMethod((req) = {
return databaseHandler.updateRoomCleaningStatus(req.params.id, req.body);
}));
/// ↑↑↑ rooms router ↑↑↑/// ↓↓↓ room types router ↓↓↓
app.get(`${prefix}/collections/roomTypes`, callMethod((req) = {
return databaseHandler.getRoomTypes(req.query);
}));
/// ↑↑↑ room types router ↑↑↑/// ↓↓↓ cleaning statuses router ↓↓↓
app.get(`${prefix}/collections/cleaningStatuses`, callMethod((req) = {
return databaseHandler.getCleaningStatuses(req.query);
}));
/// ↑↑↑ cleaning statuses router ↑↑↑/// ↓↓↓ booking statuses router ↓↓↓
app.get(`${prefix}/collections/bookingStatuses`, callMethod((req) = {
return databaseHandler.getBookingStatuses(req.query);
}));
/// ↑↑↑ booking statuses router ↑↑↑
}
};

它所做的就是设置应用程序来侦听调度器可以发送的请求url,并调用存储的适当方法。请注意,所有方法都包装在try-catch块中,以便能够捕获任何错误并向客户机返回适当的错误响应。

还要注意,异常消息是直接写入API响应的。这在开发过程中非常方便,但在生产环境中,对客户端隐藏这些消息可能是一个好主意,因为到达那里的原始mysql异常可能包含敏感数据。

现在如果您打开应用程序页面,可以看到一个带有预订的调度程序。可以在调度程序中创建、删除和修改项,即使重新加载页面,您所做的任何更改也将保留。

如何用日程控件DHTMLX Scheduler制作酒店预订日历

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/199701.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

t-SNE方法:

使用t-SNE时,除了指定你想要降维的维度(参数n_components),另一个重要的参数是困惑度(Perplexity,参数perplexity) 困惑度: 困惑度大致表示如何在局部或者全局位面上平衡关注点&am…

BearPi Std 板从入门到放弃 - 后天篇(2)(I2C1读写EEPROM)

简介 基于 BearPi Std 板从入门到放弃 - 后天篇(1)(I2C1 读取 光照强度), 使用同一个I2C接口访问EEPROM, 同时读取光照亮度 主芯片: STM32L431RCT6 LED : PC13 \ 推挽输出即可 \ 高电平点亮 串口: Usart1 I2C : I2C1 光照强度传感器&#xf…

金蝶云星空和管易云接口打通对接实战

金蝶云星空和管易云接口打通对接实战 对接系统金蝶云星空 金蝶K/3Cloud结合当今先进管理理论和数十万家国内客户最佳应用实践,面向事业部制、多地点、多工厂等运营协同与管控型企业及集团公司,提供一个通用的ERP服务平台。K/3Cloud支持的协同应用包括但不…

在python的Scikit-learn库中,可以使用train_test_split函数来划分训练集和测试集。

文章目录 一、在Scikit-learn库中,可以使用train_test_split函数来划分训练集和测试集总结 一、在Scikit-learn库中,可以使用train_test_split函数来划分训练集和测试集 在Scikit-learn库中,可以使用train_test_split函数来划分训练集和测试…

【yolov8】与yolov5的区别及改进详解

图像识别技术在物联网、智能监控等领域广泛应用。而深度学习中的目标检测技术,能够帮助我们对图像中的目标进行识别,进而实现自动化控制。目前,Yolov8和Yolov5是目标检测领域热门的模型。 yolo目标检测原理yolov5详解yolov8yolov8结构图Conv模…

智能优化算法应用:基于学校优化算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于学校优化算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于学校优化算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.学校优化算法4.实验参数设定5.算法结果6.参考…

Linux中shell的运行原理

在Linux中,每次输入命令时,前面都会出现一串字母,我们称之为命令行提示符 实际上,命令行提示符是一种外壳程序 外壳程序的概念: 前面我们提到过,在Linux中,一切皆文件,所谓的命令就…

UE Windows平台下Linux的交叉编译项目打包

UE Windows平台下Linux的交叉编译项目打包 交叉编译(Cross-compilation) 使得在以Windows为中心的工作流程中工作的游戏开发者能够以Linux为目标对项目进行打包。目前,只有Windows支持交叉编译。 交叉编译支持的平台 Windows | Linux-x86_…

每日3道PWN(第二天)

ciscn_2019_n_1 参考: [BUUCTF-pwn]——ciscn_2019_n_1-CSDN博客 [BUUCTF]PWN5——ciscn_2019_n_1_ciscn_2019_n_4-CSDN博客 BUUCTF—ciscn_2019_n_1 1-CSDN博客 checksec一下 64位栈溢出 按f5查看main函数,双击可疑函数 发现含有命令执行的且发现fl…

SOCKET、TCP、HTTP之间的区别与联系

SOCKET、TCP、HTTP之间的区别与联系 一、 Socket 1、什么是socket2、为什么需要socket3、建立socket连接 二、HTTP(基于TCP) 1、HTTP的概念2、HTTP连接的特点 连接请求:一次连接连接请求:短连接(socket是长连接) 三、TCP/IP协议簇 四、HTTP、Socket…

30个Python小游戏,小白练手,我都能玩一天【内附源码】

给大家带来30个 Python 小游戏,一定要收藏! 文末获取完整代码 有手就行 1、吃金币 import os import cfg import sys import pygame import random from modules import *游戏初始化 def initGame():# 初始化pygame, 设置展示窗口pygame.init()screen…

C/C++---------------LeetCode第118. 杨辉三角

杨辉三角 题目及要求动态规划在mian内使用 题目及要求 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]] 提示: 1 < numRow…

ActiveMQ 反序列化漏洞(CVE-2015-5254)

ActiveMQ 反序列化漏洞 Apache ActiveMQ是一种开源的消息代理&#xff08;message broker&#xff09;&#xff0c;被广泛用于应用程序之间的消息传递。它提供可靠的消息传递模式&#xff0c;如发布/订阅、点对点和请求/响应&#xff0c;非常适合构建分布式系统和应用程序集成…

1_控制系统总体结构

1、总体结构 控制系统结构图&#xff1a; 黑色块为参数、黄色块为计算模块 1.1 其中参数含义 车辆属性参数&#xff1a; 参数含义 C α f C_{\alpha f} Cαf​自行车模型总轮胎侧偏刚度&#xff08;前轮&#xff09; C α r C_{\alpha r} Cαr​自行车模型总轮胎侧偏刚度&a…

客户案例:SMC2威胁感知升级,保障金融行业邮件安全

客户背景 某基金公司是一家在业界享有广泛声誉的综合型资产管理公司&#xff0c;总部位于广州&#xff0c;在北京、上海、香港等地区均设有公司&#xff0c;业务范围遍布全球&#xff0c;凭借其卓越的投资业绩和专业的基金管理服务&#xff0c;赢得了广大投资者的高度认可。 该…

数据结构第二次作业——递归、树、图【考点罗列//错题正解//题目解析】

目录 一、选择题 ——递归—— 1.【单选题】 ——递归的相关知识点 2.【单选题】——递归的应用 3.【单选题】——递归的实现结构 4.【单选题】——递归的执行与实现 5.【单选题】 ——递归算法 ——树—— 6.【单选题】 ——树的结构 *7.【单选题】——树的知识点 …

【Maven】依赖管理

1. 依赖管理 1.1 依赖配置 依赖&#xff1a;指当前项目运行所需要的jar包。一个项目中可以引入多个依赖。 依赖引入步骤&#xff1a;在pom.xml中编写标签&#xff0c;在标签中使用引入坐标&#xff0c;定义坐标的 groupId、artifactId、version&#xff0c;最后点击刷新&…

17. 电话号码的字母组合 经典回溯组合题目

17. 电话号码的字母组合 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;错误经验吸取 原题链接&#xff1a; 17. 电话号码的字母组合 https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/ 完成情况&…

c题目16:写一个递归函数,计算N阶乘

每日小语 一生中&#xff0c;最光辉的一天并非功成名就的那一天&#xff0c;而是从悲叹与绝望中产生对人生挑战与勇敢迈向意志的那一天。——福楼拜 自己思考 这个小语呢&#xff0c;我目前还达不到&#xff0c;只是顺其自然&#xff0c;很多东西做起来很有动力&#xff0c;…

《opencv实用探索·十》opencv双边滤波的简单理解

1、引言 OpenCV中的双边滤波&#xff08;Bilateral Filtering&#xff09;是一种保持边缘清晰的滤波方法&#xff0c;它考虑像素的空间关系和像素值之间的差异。双边滤波对于去除噪声的同时保持图像的边缘非常有效&#xff0c;它也是一种非线性滤波。 双边滤波采用了两个高斯滤…