Forráskód Böngészése

feat: ScheduleView move today and add-schedule buttons to TopBar right slot with CapsuleSafeArea

xuyunhui 1 hete
szülő
commit
57fee7b183
1 módosított fájl, 276 hozzáadás és 0 törlés
  1. 276 0
      src/components/dispatch/ScheduleView.vue

+ 276 - 0
src/components/dispatch/ScheduleView.vue

@@ -0,0 +1,276 @@
+<template>
+  <view class="app-container pb-20">
+    <StatusBar v-if="showStatusBar" />
+    <TopBar v-if="showTopBar" title="排班管理" :show-back="showBack">
+      <template #right>
+        <view class="flex items-center gap-2">
+          <view class="text-sm text-blue-500 flex items-center" @click="goToToday">
+            <text class="uni-icons uniui-calendar-filled mr-1"></text>
+            今天
+          </view>
+          <view class="text-blue-500 text-sm flex items-center" @click="addSchedule">
+            <text class="uni-icons uniui-plusempty mr-1"></text>
+            新增排班
+          </view>
+        </view>
+      </template>
+    </TopBar>
+
+    <!-- 月份切换 -->
+    <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
+      <view class="flex items-center">
+        <view class="p-2" @click="changeMonth(-1)">
+          <text class="uni-icons uniui-arrowleft text-gray-600"></text>
+        </view>
+        <text class="text-base font-semibold text-gray-800 mx-2">{{ currentYear }}年{{ currentMonth }}月</text>
+        <view class="p-2" @click="changeMonth(1)">
+          <text class="uni-icons uniui-arrowright text-gray-600"></text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 日历 -->
+    <view class="bg-white px-4 py-3">
+      <!-- 星期标题 -->
+      <view class="flex mb-2">
+        <view v-for="day in weekDays" :key="day" class="flex-1 text-center">
+          <text class="text-xs text-gray-400">{{ day }}</text>
+        </view>
+      </view>
+
+      <!-- 日期 -->
+      <view v-for="(week, weekIndex) in calendarWeeks" :key="weekIndex" class="flex mb-2">
+        <view
+          v-for="(date, dateIndex) in week"
+          :key="dateIndex"
+          class="flex-1 h-11 flex flex-col items-center justify-center relative mx-1"
+          :class="{
+            'opacity-30': date.isOtherMonth,
+            'bg-blue-500 rounded-xl shadow-md': date.isToday && !isSelected(date),
+            'bg-white border-2 border-blue-500 rounded-xl shadow-sm': isSelected(date) && !date.isToday,
+            'bg-blue-600 rounded-xl shadow-md': isSelected(date) && date.isToday,
+          }"
+          @click="selectDate(date)"
+        >
+          <text
+            class="text-sm font-medium"
+            :class="{
+              'text-white': date.isToday || isSelected(date),
+              'text-gray-800': !date.isToday && !isSelected(date)
+            }"
+          >
+            {{ date.day }}
+          </text>
+          <view
+            v-if="date.hasSchedule"
+            class="w-1 h-1 rounded-full mt-1"
+            :class="date.isToday || isSelected(date) ? 'bg-white' : 'bg-blue-500'"
+          ></view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 排班列表 -->
+    <view class="px-4 mt-4">
+      <view class="flex justify-between items-center mb-3">
+        <text class="text-base font-semibold text-gray-800">{{ selectedDate }} 排班</text>
+      </view>
+
+      <view v-if="loading" class="py-10 text-center">
+        <text class="text-gray-400">加载中...</text>
+      </view>
+
+      <view v-else-if="daySchedules.length === 0">
+        <EmptyState message="该日无排班" />
+      </view>
+
+      <uni-list v-else border>
+        <uni-list-item
+          v-for="schedule in daySchedules"
+          :key="schedule.scheduleId"
+          :title="schedule.taskName"
+          :note="schedule.staffName + ' | ' + (schedule.vehicleNo || '未分配车辆')"
+          :right-text="getStatusText(schedule.status)"
+          show-arrow
+        >
+          <template #header>
+            <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(schedule.status)">
+              <text class="uni-icons text-lg" :class="getStatusIconClass(schedule.status)"></text>
+            </view>
+          </template>
+        </uni-list-item>
+      </uni-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, watch } from 'vue'
+import StatusBar from '../common/StatusBar.vue'
+import TopBar from '../common/TopBar.vue'
+import EmptyState from '../common/EmptyState.vue'
+import UniList from '../uni-list/uni-list.vue'
+import UniListItem from '../uni-list/uni-list-item.vue'
+import { getScheduleList } from '@/api/schedule'
+
+withDefaults(
+  defineProps<{
+    showTopBar?: boolean
+    showBack?: boolean
+    showStatusBar?: boolean
+  }>(),
+  {
+    showTopBar: true,
+    showBack: true,
+    showStatusBar: false,
+  }
+)
+
+const loading = ref(false)
+const schedules = ref<any[]>([])
+const currentYear = ref(new Date().getFullYear())
+const currentMonth = ref(new Date().getMonth() + 1)
+const selectedDate = ref('')
+
+const weekDays = ['日', '一', '二', '三', '四', '五', '六']
+
+const calendarWeeks = computed(() => {
+  const year = currentYear.value
+  const month = currentMonth.value
+  const firstDay = new Date(year, month - 1, 1)
+  const lastDay = new Date(year, month, 0)
+  const firstWeekDay = firstDay.getDay()
+  const daysInMonth = lastDay.getDate()
+
+  const weeks: any[] = []
+  let currentWeek: any[] = []
+
+  const prevMonthDays = new Date(year, month - 1, 0).getDate()
+  for (let i = firstWeekDay - 1; i >= 0; i--) {
+    currentWeek.push({
+      day: prevMonthDays - i,
+      isOtherMonth: true,
+      isToday: false,
+      hasSchedule: false,
+    })
+  }
+
+  for (let day = 1; day <= daysInMonth; day++) {
+    const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
+    const isToday = dateStr === new Date().toISOString().split('T')[0]
+    const hasSchedule = schedules.value.some(s => s.scheduleDate === dateStr)
+
+    currentWeek.push({
+      day,
+      isOtherMonth: false,
+      isToday,
+      hasSchedule,
+      dateStr,
+    })
+
+    if (currentWeek.length === 7) {
+      weeks.push(currentWeek)
+      currentWeek = []
+    }
+  }
+
+  if (currentWeek.length > 0) {
+    for (let i = 1; currentWeek.length < 7; i++) {
+      currentWeek.push({
+        day: i,
+        isOtherMonth: true,
+        isToday: false,
+        hasSchedule: false,
+      })
+    }
+    weeks.push(currentWeek)
+  }
+
+  return weeks
+})
+
+const daySchedules = computed(() => {
+  if (!selectedDate.value) return []
+  return schedules.value.filter(s => s.scheduleDate === selectedDate.value)
+})
+
+function changeMonth(delta: number) {
+  let newMonth = currentMonth.value + delta
+  if (newMonth > 12) {
+    newMonth = 1
+    currentYear.value++
+  } else if (newMonth < 1) {
+    newMonth = 12
+    currentYear.value--
+  }
+  currentMonth.value = newMonth
+}
+
+function goToToday() {
+  const now = new Date()
+  currentYear.value = now.getFullYear()
+  currentMonth.value = now.getMonth() + 1
+  selectedDate.value = now.toISOString().split('T')[0]
+}
+
+function selectDate(date: any) {
+  if (!date.isOtherMonth) {
+    selectedDate.value = date.dateStr
+  }
+}
+
+function isSelected(date: any): boolean {
+  return date.dateStr === selectedDate.value
+}
+
+function getStatusText(status: string) {
+  const map: Record<string, string> = {
+    scheduled: '已排班',
+    completed: '已完成',
+    cancelled: '已取消',
+  }
+  return map[status] || status
+}
+
+function getStatusBgClass(status: string) {
+  const map: Record<string, string> = {
+    scheduled: 'bg-blue-50',
+    completed: 'bg-green-50',
+    cancelled: 'bg-red-50',
+  }
+  return map[status] || 'bg-gray-50'
+}
+
+function getStatusIconClass(status: string) {
+  const map: Record<string, string> = {
+    scheduled: 'uniui-calendar-filled text-blue-500',
+    completed: 'uniui-checkmarkempty text-green-500',
+    cancelled: 'uniui-close text-red-500',
+  }
+  return map[status] || 'uniui-calendar text-gray-400'
+}
+
+function addSchedule() {
+  uni.showToast({ title: '排班功能开发中', icon: 'none' })
+}
+
+async function fetchSchedules() {
+  loading.value = true
+  try {
+    const res = await getScheduleList(currentYear.value, currentMonth.value)
+    schedules.value = res.rows || []
+  } catch (error) {
+    console.error('获取排班列表失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(() => [currentYear.value, currentMonth.value], fetchSchedules)
+
+onMounted(() => {
+  const today = new Date().toISOString().split('T')[0]
+  selectedDate.value = today
+  fetchSchedules()
+})
+</script>