欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

微信小程序日程預約功能實現(xiàn)

 更新時間:2025年02月13日 10:32:59   作者:伊丶二  
本文介紹了如何使用微信小程序開發(fā)一個日程預約系統(tǒng),該系統(tǒng)包括頂部日期選擇器、中部的canvas繪制區(qū)和底部的數(shù)據(jù)回顯區(qū),本文通過實例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧

涉及儀器的預約使用,仿照小米日歷日程預約開發(fā)開發(fā)對應頁。

效果展示

需求分析

  • 頂部七日選擇器
    • 橫向顯示從當前日期開始后的七天,并區(qū)分月-日
    • 七天共計預約時間段綜合為3
  • 中部canvas繪制區(qū)
    • 左側(cè)時間刻度
    • 右側(cè)繪制區(qū),總計24格,每大格為1h,一大格后期拆分四小格,為15min
    • 右側(cè)繪制區(qū)功能
      • 激活:單擊
      • 長按:拖動激活區(qū)域移動選區(qū),存在激活區(qū)域之間的互斥
      • 拉伸:雙擊后改變預約起止時間
  • 底部數(shù)據(jù)回顯區(qū)
    • 顯示預約時間段
    • 支持刪除

代碼實現(xiàn)

一、構建基礎頁面結構

1. 頂部日期選擇器

獲取當前日期,即六天后的所有日期,并解析出具體月-日,存入數(shù)組dateList

 // 初始化日期列表
  initDateList() {
    const dateList = [];
    const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
    for (let i = 0; i < 7; i++) {
      const date = new Date();
      // 獲取未來幾天的日期
      date.setDate(date.getDate() + i);
      dateList.push({
        date: date.getTime(),
        month: date.getMonth() + 1,
        day: date.getDate(),
        weekDay: weekDays[date.getDay()]
      });
    }
    this.setData({ dateList });
  },
<view 
      wx:for="{{ dateList }}" 
      wx:key="date"
      class="date-item {{ currentDateIndex === index ? 'active' : '' }}"
      bindtap="onDateSelect"
      data-index="{{ index }}"
    >
      <text class="date-text">{{ item.month }}-{{ item.day }}</text>
      <text class="week-text">{{ item.weekDay }}</text>
      <text class="today-text" wx:if="{{ index === 0 }}">今天</text>
    </view>

2. 中部canvas繪制

左側(cè)25條數(shù)據(jù),從0:00-24:00,只作為標志數(shù)據(jù);【主體】右側(cè)24格,通過canvas進行繪制。

初始化canvas,獲取寬高,并通過ctx.scale(dpr,dpr)縮放canvas適應設備像素比;

繪制網(wǎng)格

   for (let i = 0; i <= 24; i++) {
     ctx.beginPath();
     const y = i * hourHeight;
     ctx.moveTo(0, y);
     ctx.lineTo(width, y);
     ctx.stroke();
   }

3. 底部數(shù)據(jù)回顯

二、中間canvas功能細分

1. 激活狀態(tài)的判斷

首先給canvas添加點擊事件bindtouchstart="onCanvasClick"

獲取點擊坐標,并解析首次觸摸點的位置touch[0],clientX clientY 是觸摸點在屏幕上的坐標

const query = wx.createSelectorQuery();
query.select('#timeGridCanvas')
  .boundingClientRect(rect => {
    const x = e.touches[0].clientX - rect.left;
    const y = e.touches[0].clientY - rect.top;

計算時間格

const hourIndex = Math.floor(y / this.data.hourHeight);

hourHeight: rect.height / 24,來自于initCanvas初始化時,提前計算好的每個時間格的高度

獲取選中的時間段

const existingBlockIndex = this.data.selectedBlocks.findIndex(block => 
          hourIndex >= block.startHour && hourIndex < block.endHour
        );

使用 findIndex 查找點擊位置是否在已選時間段內(nèi)

取消選中邏輯

if (existingBlockIndex !== -1) {
  // 從當前日期的選中塊中移除
  const newSelectedBlocks = [...this.data.selectedBlocks];
  newSelectedBlocks.splice(existingBlockIndex, 1);
  // 從所有選中塊中移除
  const currentDate = `${this.data.dateList[this.data.currentDateIndex].month}-${this.data.dateList[this.data.currentDateIndex].day}`;
  const allBlockIndex = this.data.allSelectedBlocks.findIndex(block => 
    block.date === currentDate && 
    block.startHour === this.data.selectedBlocks[existingBlockIndex].startHour
  );
  const newAllBlocks = [...this.data.allSelectedBlocks];
  if (allBlockIndex !== -1) {
    newAllBlocks.splice(allBlockIndex, 1);
  }
  this.setData({
    selectedBlocks: newSelectedBlocks,
    allSelectedBlocks: newAllBlocks
  });
}

同時需要考慮兩個數(shù)組:當前日期選中時間段selectedBlocks,七日內(nèi)選中時間段總數(shù)allSelectedBlocks

新增時間段邏輯

else {
  // 檢查限制
  if (this.data.allSelectedBlocks.length >= 3) {
    wx.showToast({
      title: '最多只能選擇3個時間段',
      icon: 'none'
    });
    return;
  }
  // 添加新時間段
  const startHour = Math.floor(y / this.data.hourHeight);
  const endHour = startHour + 1;
  const newBlock = {
    date: `${this.data.dateList[this.data.currentDateIndex].month}-${this.data.dateList[this.data.currentDateIndex].day}`,
    startHour: startHour,
    endHour: endHour,
    startTime: this.formatTime(startHour * 60),
    endTime: this.formatTime(endHour * 60)
  };
  this.setData({
    selectedBlocks: [...this.data.selectedBlocks, newBlock],
    allSelectedBlocks: [...this.data.allSelectedBlocks, newBlock]
  });
}

先檢查是否達到最大選擇限制,創(chuàng)建新的時間段對象

date: 當前選中的日期
startHour: 開始小時
endHour: 結束小時
startTime: 格式化后的開始時間
endTime: 格式化后的結束時間

2. 時間塊拉伸邏輯

檢測拉伸手柄
為了避免和后期的長按拖動邏輯的沖突,在選中時間塊上額外添加上下手柄以作區(qū)分:

checkResizeHandle(x, y) {
  const handleSize = 16; // 手柄的點擊范圍大小
  for (let i = 0; i < this.data.selectedBlocks.length; i++) {
    const block = this.data.selectedBlocks[i];
    const startY = block.startHour * this.data.hourHeight;
    const endY = block.endHour * this.data.hourHeight;
    // 檢查是否點擊到上方手柄
    if (y >= startY - handleSize && y <= startY + handleSize) {
      return { blockIndex: i, isStart: true, position: startY };
    }
    // 檢查是否點擊到下方手柄
    if (y >= endY - handleSize && y <= endY + handleSize) {
      return { blockIndex: i, isStart: false, position: endY };
    }
  }
  return null;
}

處理拖拽拉伸邏輯
在判斷確定點擊到拉伸手柄的情況下,處理邏輯

const resizeHandle = this.checkResizeHandle(x, y);
        if (resizeHandle) {
          // 開始拉伸操作
          this.setData({
            isResizing: true,
            resizingBlockIndex: resizeHandle.blockIndex,
            startY: y,
            initialY: resizeHandle.position,
            isResizingStart: resizeHandle.isStart
          });
          return;
        }
isResizing:標記正在拉伸
startY:開始拖動的位置
initialY:手柄的初始位置
isResizingStart:是否在調(diào)整開始時間

處理拖動過程
需要根據(jù)拖動的距離來計算新的時間,將拖動的距離轉(zhuǎn)換成時間的變化。簡單來說,假設一小時占60px的高度,那么15min=15px,如果用戶往下拖動30px,換算成時間就是30min。

// 計算拖動了多遠
const deltaY = currentY - startY;  // 比如拖動了30像素
// 計算15分鐘對應的高度
const quarterHeight = hourHeight / 4;  // 假設hourHeight是60,那么這里是15
// 計算移動了多少個15分鐘
const quarterMoved = Math.floor(Math.abs(deltaY) / quarterHeight) * (deltaY > 0 ? 1 : -1);
// 計算新的時間
const newTime = originalTime + (quarterMoved * 0.25);  // 0.25代表15分鐘

更新時間顯示
計算出新的時間后,需要在確保有效范圍內(nèi)的同時,對齊15min的刻度并轉(zhuǎn)化顯示格式

// 確保時間合理,比如不能小于0點,不能超過24點
if (newTime >= 0 && newTime <= 24) {
  // 對齊到15分鐘
  const alignedTime = Math.floor(newTime * 4) / 4;
  // 轉(zhuǎn)換成"HH:MM"格式
  const hours = Math.floor(alignedTime);
  const minutes = Math.round((alignedTime - hours) * 60);
  const timeString = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
}

結束拉伸邏輯
當松手時,清楚拖動狀態(tài),將標識符置false
this.setData({
isResizing: false, // 結束拖動狀態(tài)
resizingBlockIndex: null, // 清除正在拖動的時間塊
startY: 0 // 重置起始位置
});

3. 時間塊拖動邏輯

長按時間塊
首先找到點擊的時間塊并存儲信息,在原視圖上”刪除“該時間塊,并標記拖動狀態(tài)

onCanvasLongPress(e) {
  // 1. 先找到用戶點擊的是哪個時間塊
  const hourIndex = Math.floor(y / this.data.hourHeight);
  const pressedBlockIndex = this.data.selectedBlocks.findIndex(block => 
    hourIndex >= block.startHour && hourIndex < block.endHour
  );
  // 2. 如果真的點到了時間塊
  if (pressedBlockIndex !== -1) {
    // 3. 保存這個時間塊的信息,因為待會要用
    const pressedBlock = {...this.data.selectedBlocks[pressedBlockIndex]};
    // 4. 從原來的位置刪除這個時間塊
    const newBlocks = [...this.data.selectedBlocks];
    newBlocks.splice(pressedBlockIndex, 1);
    // 5. 設置拖動狀態(tài)
    this.setData({
      isDragging: true,                // 標記正在拖動
      dragBlock: pressedBlock,         // 保存被拖動的時間塊
      dragStartY: y,                   // 記錄開始拖動的位置
      selectedBlocks: newBlocks,       // 更新剩下的時間塊
      dragBlockDuration: pressedBlock.endHour - pressedBlock.startHour  // 記錄時間塊長度
    });
  }
}

時間塊投影
為了區(qū)分正常激活時間塊,將長按的以投影虛化方式顯示,提示拖動結束的位置。
首先計算觸摸移動的距離,并根據(jù)上文,推測相應時間變化。在合理的范圍內(nèi),檢測是否和其他時間塊互斥,最終更新時間塊的顯示。

onCanvasMove(e) {
  if (this.data.isDragging) {
    const y = e.touches[0].clientY - rect.top;
    const deltaY = y - this.data.dragStartY;
    const quarterHeight = this.data.hourHeight / 4;
    const quarterMoved = Math.floor(deltaY / quarterHeight);
    const targetHour = this.data.dragBlock.startHour + (quarterMoved * 0.25);
    const boundedHour = Math.max(0, Math.min(24 - this.data.dragBlockDuration, targetHour));
    const isOccupied = this.checkTimeConflict(boundedHour, boundedHour + this.data.dragBlockDuration);
    this.setData({
      dragShadowHour: boundedHour,     // 投影的位置
      dragShadowWarning: isOccupied    // 是否顯示沖突警告
    });
  }
}

互斥檢測
排除掉當前拖動時間塊,檢測與其余是否重疊。
具體來說,假設當前時間塊9:00-10:00,新位置9:30-10:30,這種情況 startHour(9:30) < block.endHour(10:00)endHour(10:30) > block.startHour(9:00)所以檢測為重疊

checkTimeConflict(startHour, endHour) {
  return this.data.selectedBlocks.some(block => {
    if (block === this.data.dragBlock) return false;
    return (startHour < block.endHour && endHour > block.startHour);
  });
}

結束拖動
當位置不互斥,區(qū)域有效的情況下,放置新的時間塊,并添加到列表中,最后清理所有拖動相關的狀態(tài)

onCanvasEnd(e) {
  if (this.data.isDragging) {
    if (this.data.dragShadowHour !== null && 
        this.data.dragBlock && 
        !this.data.dragShadowWarning) {
      const newHour = Math.floor(this.data.dragShadowHour * 4) / 4;
      const duration = this.data.dragBlockDuration;
      const newBlock = {
        startHour: newHour,
        endHour: newHour + duration,
        startTime: this.formatTime(Math.round(newHour * 60)),
        endTime: this.formatTime(Math.round((newHour + duration) * 60))
      };
      const newSelectedBlocks = [...this.data.selectedBlocks, newBlock];
      this.setData({ selectedBlocks: newSelectedBlocks });
    } else if (this.data.dragShadowWarning) {
      const newSelectedBlocks = [...this.data.selectedBlocks, this.data.dragBlock];
      this.setData({ selectedBlocks: newSelectedBlocks });
      wx.showToast({
        title: '該時間段已被占用',
        icon: 'none'
      });
    }
    this.setData({
      isDragging: false,
      dragBlock: null,
      dragStartY: 0,
      dragCurrentY: 0,
      dragShadowHour: null,
      dragBlockDuration: null,
      dragShadowWarning: false
    });
  }
}

三、底部數(shù)據(jù)回顯

就是基本的數(shù)據(jù)更新回顯,setData

新增時間段回顯

const newBlock = {
  date: `${this.data.dateList[this.data.currentDateIndex].month}-${this.data.dateList[this.data.currentDateIndex].day}`,
  startHour: startHour,
  endHour: endHour,
  startTime: this.formatTime(startHour * 60),
  endTime: this.formatTime(endHour * 60)
};
this.setData({
  allSelectedBlocks: [...this.data.allSelectedBlocks, newBlock]  
});

刪除時間段映射

removeTimeBlock(e) {
  const index = e.currentTarget.dataset.index;
  const removedBlock = this.data.allSelectedBlocks[index];
  // 從總列表中刪除
  const newAllBlocks = [...this.data.allSelectedBlocks];
  newAllBlocks.splice(index, 1);
  const currentDate = `${this.data.dateList[this.data.currentDateIndex].month}-${this.data.dateList[this.data.currentDateIndex].day}`;
  if (removedBlock.date === currentDate) {
    const newSelectedBlocks = this.data.selectedBlocks.filter(block => 
      block.startHour !== removedBlock.startHour || 
      block.endHour !== removedBlock.endHour
    );
    this.setData({ selectedBlocks: newSelectedBlocks });
  }
  this.setData({ allSelectedBlocks: newAllBlocks });
}

總結

相比于初版的div控制時間塊的操作,canvas的渲染性能更好,交互也也更加靈活(dom操作的時候還需要考慮到阻止事件冒泡等情況),特別是頻繁更新時,并且具有完全自定義的繪制能力和更精確的觸摸事件處理。

到此這篇關于微信小程序日程預約的文章就介紹到這了,更多相關微信小程序日程預約內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論