如何搭建会议室预订系统

文将介绍如何快速通过魔笔平台搭建一个简易的企业内部会议室预订场景的数据管理和展示应用。

方案概览

会议室预订平台的应用是企业中后台场景中一个重要的业务场景。在现代企业中,会议室的管理和预订不仅仅是为了方便员工安排会议,更是为了提高资源利用率、优化会议安排、减少冲突和浪费。魔笔平台凭借其强大的低代码开发能力,为企业提供了一套高效、智能的会议室预订解决方案,使得开发者能够快速构建出符合企业需求的应用。

image

一个简易的会议室预订管理系统应用的功能大致如下图所示:

image

对于一个简易版的会议室预订平台,所需实现的功能大致如下:

  • 会议室管理:会议室管理员可以进行会议室创建和相关管理功能。

  • 会议信息统计报表:会议室管理员可以查看日粒度会议室预订和会议时长的统计报表

  • 会议室预订:员工可以发起创建指定日期、指定时间段的会议室预订单

  • 预订信息管理:员工可以查看自身全部历史预订单信息,并可对未开始会议的预订单进行取消处理。

经过需求分析和功能分解后,使用魔笔平台搭建一个会议室预订管理系统应用,只需 5 步:

  1. 数据模型和数据库创建:我们将设计本次搭建应用的实体关系模型并通过创建 RDS MySQL 实例初始化我们的数据库表。

  2. 集成资源引入和创建:我们将在魔笔控制台引入之前创建完成的数据库表,并创建相应的集成资源。

  3. 创建应用和集成操作:我们通过绑定魔笔集成操作将之前创建的集成资源与应用绑定。

  4. 页面视图及逻辑设计:我们将通过魔笔内置系统组件和相关功能,快速搭建和构造会议室管理系统的前端页面视图和逻辑,并将之前创建的集成操作与页面视图绑定以驱动页面数据和响应页面行为。

  5. 开发预览:将设计和集成好的应用发布到生产环境进行测试使用。

除此之外,我们还将为会议室预订增加一个简易的 AI 助手功能来丰富应用体验。

方案详情

数据模型和数据库创建

本节将通过创建 RDS MySQL 实例初始化我们的数据库表,若您初次使用阿里云 RDS,请先了解 RDS 使用流程

实体关系设计

在通过 RDS 创建 MySQL 实例和连接账号后,我们需要进行会议室预订场景的领域实体关系模型设计,设计如下所示:

image

其中,meeting_room 实体抽象了会议室信息,reservation 实体抽象了预定单信息。一个会议室可以对应多个预订单,一个用户可以创建多个预订单。

数据表创建

接下来,我们通过 DDL 将实体关系模型转换为 RDS MySQL 数据库中的表结构,您可以通过 DMS 或 MySQL branch 连接数据库来执行初始化。具体内容可以参考通过 DMS 登录 RDS MySQL通过客户端、命令行连接 RDS MySQL 实例。连接数据库后,可参考执行的 MySQL DDL 如下:

CREATE TABLE meeting_room (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    location VARCHAR(255),
    capacity INT,
    description TEXT,
    imageUrl VARCHAR(255),
    status VARCHAR(50)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

CREATE TABLE reservation (
    id INT PRIMARY KEY AUTO_INCREMENT,
    room_id INT,
    mobi_user_id VARCHAR(255),
    subject VARCHAR(255) NOT NULL,
    date DATE,
    start DECIMAL(4, 2),
    end DECIMAL(4, 2),
    status VARCHAR(50),
    FOREIGN KEY (room_id) REFERENCES meeting_room(id)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

通过执行这段 SQL,我们可以得到实体关系设计对应的数据库表:

  1. meeting_room表包含会议室的详细信息,如名称、位置、容量、描述、图片 URL 和状态。

  2. reservation表包含预约单的详细信息,如预约的房间 ID、创建者 ID、主题、日期、开始时间、结束时间和状态。room_id是外键,引用meeting_room表的id。其中,startend字段定义为DECIMAL(4, 2)类型,以存储如 8、8.5、9、9.5 这样的数字,mobi_user_id储存创建者的魔笔登录用户 ID。

说明

请设置表的字符集及约束为 utf8mb4 和 utf8mb4_general_ci 或其他 utf8 的配置项,否则可能会导致插入数据转化失败。

集成资源引入和创建

本节我们将把上一节中创建好的 MySQL 数据库作为集成资源引入魔笔平台。

集成资源创建

首先我们登录魔笔控制台,并选择「资源」标签,进行资源管理页面。在资源管理页面选择「集成」子项进入集成资源管理页面

image

接下来,我们点击「创建集成」按钮,进入集成添加页面。然后在「选择类型」的表单中选中「MySQL」资源类型后点击下一步。

image

image

然后,我们在添加 MySQL 集成资源的表单中填写「集成名称」、「集成描述」以及 MySQL 数据资源的「Host」、「Port」、「数据库名称」、「用户名」和「密码」等信息。上述表单主要内容的含义及相关获取方式如下:

  • 集成名称:必填字段,表示集成资源的名称,用于在应用和资源管理中作为集成资源管理和引入的标识。

  • 集成描述:非必填字段,表示该集成资源的描述信息,通常是用户自定义的描述性文档内容。

  • Host:必填字段,表示该数据库资源的连接地址。可以选择 RDS 中数据库实例的外网连接地址进行填写。连接地址的具体获取方式,请参考查看和管理实例连接地址和端口

  • Port:必填字段,表示该数据库资源的连接端口。可以选择 RDS 中数据库实例的外网连接端口进行填写。连接端口的具体获取方式,请参考查看和管理实例连接地址和端口

  • 数据库名称:必填字段,表示该资源所映射和绑定的数据库名称,如果您还没有创建数据库,请参考管理数据库,并在创建数据库后填写该数据库的名称。

  • 用户名:必填字段,表示连接数据库所需要的管理账号用户名,如果您还没有创建数据库连接账号,请参考创建账号

  • 密码:必填字段,表示连接数据库所需要的管理账号密码,如果您还没有创建数据库连接账号,请参考创建账号

image

重要

如果您使用的是 RDS 数据库实例,请务必在 Host 中填写外网地址,如何获取外网地址请参考申请或释放外网地址

说明

在开发环境资源配置完成后,您也可以按照实际搭建系统的业务需求,配置生产环境的集成资源。

创建应用和集成操作

本节我们将进行应用的创建和集成操作的引入。

应用创建

首先,我们在魔笔控制台创建一个新的应用,进入魔笔控制台后点击「创建应用」按钮,填写「名称」并选择「Icon」图标后,点击「确定」创建应用。

image

image

集成操作创建

我们首先根据功能需求进行数据操作拆分和对应的集成操作创建。根据之前拆解的需求,我们可以将应用涉及到的数据操作可以抽象为以下操作:

  • 查询会议室

    • 查询全部会议室

  • 查询预订单

    • 查询全部预订单

    • 根据会议室名称和日期查询预订单

  • 操作会议室

    • 创建会议室

    • 删除会议室

    • 修改会议室名称、人数

  • 操作预订单

    • 创建预订单

    • 修改预订单状态

  • 统计会议信息

    • 分组查询各会议室使用情况

    • 分组查询会议时长情况

    • 月粒度统计预订次数前 10 的会议室

    • 月粒度统计开会时长前 10 的会议室

说明

为了简化搭建设计,本次部分查询相关的操作均采用前端分页、搜索和筛选。

根据这些抽象的操作及其类别,我们在魔笔设计器中创建分组来管理和维护不同类别的集成操作。在页面设计器左侧面板中,选择「代码」边栏,然后在全局分栏中点击「+」按钮添加分组,在已添加的分组名称处双击可进行名称设置。可参考的分组名称如下图所示:

image

接下来,我们根据业务需求创建和编写集成逻辑,以「查询所有预订单」集成操作为例。在页面设计器左侧面板中,选择「代码」边栏,然后在全局分栏的「查询预订单」右侧下拉菜单中选择「新建集成操作」按钮添加集成操作:

image

接下来,我们配置集成操作,在底部面板的「集成资源」选单中选择先前创建好的 MySQL 集成资源,然后在 SQL 语句面板中编写业务 SQL 语句,最后再根据业务需求选择是否「自动运行」,一个查询全部预订单的集成操作如下图所示:

image

对于其他部分的集成操作搭建,可参考下表:

集成名

参数

SQL语句

描述

createMeetingRoom

  • name

  • location

  • capacity

  • description

  • imageUrl

  • status


INSERT INTO meeting_room 
  (name, 
   location, 
   capacity, 
   description, 
   imageUrl, 
   status)
VALUES 
   ({{name}}, 
   {{location}}, 
   {{ Number(capacity) }}, 
   {{description}}, 
   {{imageUrl}}, 
   {{status}});

创建会议室

deleteMeetingRoom

  • id

DELETE FROM 
  meeting_room 
WHERE id = {{id}};

删除会议室

updateMeetingRoom

  • status

  • id

UPDATE 
  meeting_room 
SET 
  status = {{status}} 
WHERE id = {{id}};

更新会议室

createReservation

  • roomId

  • userId

  • subject

  • date

  • start

  • end

  • status

INSERT INTO reservation 
  (room_id, 
   mobi_user_id, 
   subject, 
   date, 
   start, 
   end, 
   status) 
VALUES 
  ({{roomId}}, 
   {{userId}}, 
   {{subject}}, 
   {{date}}, 
   {{start}}, 
   {{end}}, 
   {{status}});

创建预订单

updateReservationStatus

  • id

  • status

UPDATE 
  reservation 
SET 
  status = {{status}} 
WHERE id = {{id}}

更新预订单状态

queryAllMeetingRoom

SELECT * FROM meeting_room;

查询会议室(自动执行)

queryAllReservation

SELECT 
    r.id,
    r.mobi_user_id AS userId,
    r.subject,
    r.date,
    r.start,
    r.end,
    r.status,
    mr.name AS roomName,
    mr.id AS roomId,
    mr.location,
    mr.capacity
FROM 
    reservation AS r
INNER JOIN 
    meeting_room AS mr
ON 
    r.room_id = mr.id
ORDER BY r.date DESC;

查询预订单(自动执行)

queryReservationByDateAndMeetingRoom

roomId

  • date

SELECT 
  * 
FROM 
  reservation 
WHERE 
  room_id = {{roomId}} AND date = {{date}};

查询具体日期具体会议室的预订单

countMeetingGroupByDate

SELECT 
    date,
    COUNT(*) AS count
FROM 
    reservation
GROUP BY 
    date
ORDER BY 
    date;

按日期统计会议室预订次数(自动执行)

countBest10RoomByCount

SELECT 
    DATE_FORMAT(date, '%Y-%m') AS month,
    r.room_id,
    mr.name AS room_name,
    COUNT(*) AS reservation_count
FROM 
    reservation r
INNER JOIN
    meeting_room mr
ON
    r.room_id = mr.id
GROUP BY 
    month, room_id, mr.name
ORDER BY 
    reservation_count DESC
LIMIT 10;

按月份统计最多预订次数的会议室(自动执行)

countBest10RoomByTime

SELECT 
    DATE_FORMAT(date, '%Y-%m') AS month,
    r.room_id,
    mr.name,
    SUM(r.end - r.start) AS total_hours
FROM 
    reservation r
INNER JOIN
    meeting_room mr
ON
    r.room_id = mr.id
GROUP BY 
    month, r.room_id, mr.name
ORDER BY 
    total_hours DESC
LIMIT 10;

按月份统计最多开会时长的会议室(自动执行)

countMeetingGroupByTime

SELECT 
    date,
    SUM(end - start) AS count
FROM 
    reservation
WHERE
   status != "Canceled"
GROUP BY 
    date
ORDER BY 
    date;

按日期统计会议室会议时长(自动执行)

说明

SQL 语句中的 {{ }} 包裹内容可动态执行 JS 代码(如获取输入参数),详情请参考表达式一章。

页面视图及逻辑设计

本节我们将进行页面视图和页面逻辑的设计。

路由及布局设计

创建路由和页面绑定

我们首先根据功能需求进行页面拆分和导航布局的搭建。根据之前拆解的需求,我们可以将应用大致分为以下四个页面视图:

  • 会议室概览页:承载统计会议信息的报表页面,用于显示当前会议室总量、会议室预订次数、会议时长等统计信息,同时也作为门户首页进行呈现。

  • 会议室管理页:承载会议室基本信息展现及创建、编辑、删除等操作的管理页面。

  • 会议室预订页:承载发起会议室预订功能的核心页面,可以筛选特定地点的会议室,并选择该会议室未来未被占用的时间段进行预订。

  • 个人预订页:承载个人预订管理和历史预订信息汇总的页面。

根据分析信息,我们在魔笔设计器中创建上述四个页面,并选择符合语义的页面路由进行设置。在页面设计器左侧面板中,选择「页面&布局」边栏,然后在页面分栏中点击「+」按钮添加页面,在已添加的页面名称和路由处双击可进行名称和路由的设置。可参考的页面名称和路由设置如下:

页面名称

路由

会议室概览页

/dashboard

会议室管理页

/room

会议室预订页

/reservation

个人预订页

/myroom

image

通过布局搭建页面导航

在创建完页面和路由后,我们通过布局能力来设计和搭建所有页面公共的导航视图。在页面设计器左侧面板中,选择「页面&布局」边栏,然后在布局分栏中点击「+」按钮添加布局,在已添加的布局名称处双击可进行名称的设置。

image

点击新创建的布局后,进行布局编辑模式。本次搭建应用需要一个顶部导航栏的通用布局,因此我们可以先「删除」左边栏区域,并根据实际业务需求进行标题和导航组件项的配置:

image

可参考的搭建样式和导航组件配置如下图所示:

image

接下来我们为所有页面引入布局样式,选择「页面&布局」边栏,然后在页面分栏中选择任意页面,在右侧面板中的「页面布局」配置项中选择刚刚创建好的导航布局即可生效。

image

会议室管理页面设计

会议室管理页面的视图和逻辑设计如下:

image

可参考的搭建样式如下:

  • 管理表格视图

image

  • 创建表单视图

image

会议室预订页面设计

会议室预订页面的视图和逻辑设计如下:

image

可参考的搭建样式如下:

  • 会议室列表视图

image

  • 时间预订视图

image

在设计会议室预订逻辑中,如何获取可用的预订时间是一个核心逻辑,我们需要过滤会议当天的不可用时间,这部分的代码逻辑可参考:

// 获取时间选择器中选中的日期
const currentSelectDate = datePicker2.value;

// 获取当前选中的会议室 roomId
const id = currentSelectedId.value;

/*
   获取全部时间分片,时间分片格式参考入下:
   [   
    {key: 0, start:8 , end: 8.5},
    {key: 1, start:8.5 , end: 9},
    ...
    {key: xxx, start:21.5 , end: 22},
   ]
   即通过数组存储了 8:00 - 22:00 的半小时时间分片
*/
let rawTimeSlice = DAY_TIME_SLICE.value?.map(el => ({
  ...el,
  avalibale: true,
}));


if (!currentSelectDate) {
  return [];
}

// 处理当天的逻辑
if (dayjs(currentSelectDate).startOf('day').isSame(dayjs().startOf('day'))) {
// 获取当前时间的小时数和分钟数
const now = new Date();
const currentHour = now.getHours();
const currentMinutes = now.getMinutes();
const currentTime = currentHour + currentMinutes / 60;

// 对于当天已经过去的时间,一定不可被使用
const currentAvalibleTimeToday = rawTimeSlice.map(slot => {
  return {
    ...slot,
    avalibale: slot.end > currentTime
  }
});

  rawTimeSlice =  currentAvalibleTimeToday;
};

// 查询当天该会议室的预订情况
 const roomReveration = queryAllReservation.data.filter(el => {
    return el.roomId === currentSelectedId.value && el.status === RESERVATION_CONFIRMED_STATUS.value && dayjs(`${el.date[0]}-${el.date[1]}-${el.date[2]}`).startOf('day').isSame(dayjs(datePicker2.value).startOf('day'));
  })

// 筛选不可用的会议室时间分片,并设置 avalibale 为 false
 rawTimeSlice = rawTimeSlice.map(el => {
    return {
      ...el,
      avalibale: el.avalibale && !roomReveration.some(outter => outter.start <= el.start && outter.end >= el.end),
    }
  })

 return rawTimeSlice;
说明

为了方便处理时间信息和内容,代码中使用了通过三方依赖管理引入的 dayjs 三方库,如果您也需要使用类似的函数处理工具,请参考三方库依赖一章进行工具依赖的引入。

我的预订页面设计

我的预订页面的视图和逻辑设计如下:

image

可参考的搭建样式如下:

image

会议室概览页面设计

会议室概览页面的视图和逻辑设计如下:

image

可参考的搭建样式如下:

image

开发预览

在正式发布到生产环境之前,您可以通过发布应用的发生获取开发环境的访问地址来快速测试应用。在魔笔设计器中选择右上角的「发布」按钮并填写信息进行应用的发布,发布成功后可以获取到应用开发环境的访问地址:

image

image

image

AI 助手集成

最后我们为会议室预订页面集成一个 AI 助手的能力,来辅助完成会议室预订。

百炼集成资源引入和创建

我们登录魔笔控制台,并选择「资源」标签,进行资源管理页面。在资源管理页面选择「集成」子项进入集成资源管理页面

image

接下来,我们点击「创建集成」按钮,进入集成添加页面。然后在「选择类型」的表单中选中「百炼智能体应用」资源类型后点击下一步。

image

image

然后,我们在添加百炼智能体应用集成资源的表单中填写「集成名称」、「集成描述」以及百炼智能体应用数据资源的「API-KEY」等信息。上述表单主要内容的含义及相关获取方式如下:

  • 集成名称:必填字段,表示集成资源的名称,用于在应用和资源管理中作为集成资源管理和引入的标识。

  • 集成描述:非必填字段,表示该集成资源的描述信息,通常是用户自定义的描述性文档内容。

  • API-KEY:必填字段,表示该集成对应百炼业务空间所对应的 API-KEY,关于 API-KEY 的获取方式,请参考API Key

image

百炼集成操作创建和提示词设计

首先我们设计提示词,在「会议室预订」页面中创建一个字符串类型的计算属性,然后我们设计如下可实现如下功能的提示词:

  • 身份设置:会议室预订助手,再遇到会议室预订问题后学会用指令回答

  • 告知今天的日期

  • 告知可用会议室

  • 告知会议室预订限制:8 点 - 22 点,且不能预订历史时间

  • 自由发挥:在不指定主题时,可自由发挥。可识别模糊时间输入。

  • 规范输出格式:可以被程序简单处理的会议室预订指令

image

可参考的提示词如下:

// 可用的会议室 id 列表
const currentMeetingRoomIds = queryAllMeetingRoom.data.filter(el => el.status === MEETINGROOM_AVALIABLE_STATUS.value).map(el => `${el.name}::${el.id}`);
const promptOrder = `## 要求:\n 1、你是一个会议室预订小助手,当判断意图为会议室预订时,请直接使用会议指令序列回答,并用<order></order>的形式包裹回答内容。\n指令序列格式如下:[会议室id]|[会议室日期(YYYY-MM- DD格式)]|[起始时间(格式为 8 - 22 之间的数字,数字步长为 0.5)]|[结束时间(格式为 8 - 22 之间的数字,数字步长为 0.5)]|[会议主题]]\n 2、在判断用户没有指定日期后,提示用户指定日期 \n 3、可根据用户模糊的会议时间进行回答,如上午、下午这样的用语 \n 4、请随机推荐会议主题 \n  5、不能预订今天之前日期的会议室,也不能预订 8 点之前和 22 点之后点会议室。 \n 6、当回答会议指令序列时,请直接回答不要有别的信息`
const promptInfo = `## 背景知识:\n 下列是可用的会议室列表,格式为[会议室名称]::[会议室id],用逗号分隔:\n ${currentMeetingRoomIds.join(',')} \n 今天的日期是${dayjs().format('YYYY-MM-DD')}\n` 

return promptInfo +  promptOrder + `\n 以下为用户输入:`;

接下来,我们创建百炼应用的集成操作,在「会议室预订」页面中创建一个集成操作后,在底部面板的「集成资源」选单中选择先前创建好的百炼智能体应用集成资源,然后填写应用 ID 及我们刚刚创建好的提示词:

image

说明

截图中的 chat1 为页面中的聊天组件实例,在聊天组件的配置项中绑定集成操作后,可以通过 chat1.currentMessage.content 获取用户输入内容

视图设计

最后我们进行视图设计,引入 AI 智能助手的会议室预订页面的视图和逻辑设计更新如下:

image

可参考的搭建样式如下:

image

image

在设计会议室预订逻辑中,如何解析应用返回的预订会议室指令是一个核心逻辑,我们需要在接受到指令后准确判断会议室可用性,并在可用的情况下完成会议室预订,最后还需要修改聊天内容来给到用户反馈。这部分的代码逻辑可参考:

// chat1 为聊天组件实例,chat1.chats 可以获取到全部聊天内容
const targetIndex = chat1.chats.length - 1;

const content = chat1.chats[targetIndex].content;

const id =chat1.chats[targetIndex].id;


// 解析 <order></order> 包裹的指令
if (content.startsWith('<order>') && content.endsWith('</order>')) {
  const order = content.replace('<order>', '').replace('</order>', '');
  
  let [roomId, date, start, end, subject] = order.split('|');

  const targetReversations = queryAllReservation.data.filter(el => {
    return `${el.date[0]}-${el.date[1]}-${el.date[2]}` === date && Number(el.roomId) === id && el.status !== "Canceled"
  });
  
  start = Number(start);
  end = Number(end);
  
  // 判断是否有人占用该时间
  if (targetReversations.some(el => el.start <= start && el.end >= end)) {
    // 预订失败的情况
    mobi.showMessage(`❌会议室已被占用,请重新尝试`);
    chat1.setMessageContent({
      id,
      content: '会议室已被占用,请重新尝试',
    })
    return;
  } else {
    // 成功预订的情况
    const roomName = queryAllMeetingRoom.data.find(el => el.id === Number(roomId)).name;
    const res = await createReservation.trigger({
      roomId,
      subject,
      date,
      start,
      end,
    })
    
    if (res.success) {
    mobi.showMessage(`会议室预订成功`);
    chat1.setMessageContent({
      id,
      content: `✅成功预订主题为${subject}的会议:\n时间:${date},${DAY_TIME_SLICE_MAP.value[start]}-${DAY_TIME_SLICE_MAP.value[end]}\n会议室:${roomName}`,
    })
    } else {
     // 预订失败的情况
    mobi.showMessage(`会议室预订失败,请重新尝试`);
    chat1.setMessageContent({
      id,
      content: '❌会议室预订失败,请重新尝试',
    })
    }
    
    
  }
  
  
}

总结

通过上述方案,魔笔平台能够简化开发流程,使得企业可以迅速构建和部署会议室预订管理系统。这不仅满足了企业高效会议安排的需求,更提升了资源利用率,优化了内部流程,实现了智能管理。魔笔平台的低代码能力使得开发者可以专注于业务逻辑,而非繁琐的技术实现,从而更快地响应企业需求并交付高质量的应用。