|
|
@@ -88,8 +88,8 @@
|
|
|
<uni-list-item
|
|
|
v-for="schedule in daySchedules"
|
|
|
:key="schedule.scheduleId"
|
|
|
- :title="schedule.taskName"
|
|
|
- :note="schedule.staffName + ' | ' + (schedule.vehicleNo || '未分配车辆')"
|
|
|
+ :title="schedule.teamName || '未命名班组'"
|
|
|
+ :note="(schedule.staffIds || '未分配人员') + ' | ' + (schedule.vehicleIds || '未分配车辆')"
|
|
|
:right-text="getStatusText(schedule.status)"
|
|
|
show-arrow
|
|
|
>
|
|
|
@@ -101,6 +101,80 @@
|
|
|
</uni-list-item>
|
|
|
</uni-list>
|
|
|
</view>
|
|
|
+ <!-- 新增排班弹窗 -->
|
|
|
+ <view v-if="showModal" class="fixed inset-0 z-50 flex items-center justify-center">
|
|
|
+ <view class="absolute inset-0 bg-black/50" @click="closeModal"></view>
|
|
|
+ <view class="relative bg-white rounded-2xl w-80 p-4 max-h-[80vh] overflow-y-auto">
|
|
|
+ <view class="flex justify-between items-center mb-4">
|
|
|
+ <text class="text-base font-semibold text-gray-800">新增排班</text>
|
|
|
+ <text class="uni-icons uniui-close text-gray-400 p-2" @click="closeModal"></text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="mb-3">
|
|
|
+ <text class="text-sm text-gray-600 block mb-1">选择班组</text>
|
|
|
+ <picker mode="selector" :range="teamOptions" range-key="label" :value="teamIndex" @change="(e: any) => { const idx = e.detail.value; form.teamId = teamOptions[idx].value; onTeamChange(form.teamId) }">
|
|
|
+ <view class="border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-800">
|
|
|
+ {{ teamNameDisplay || '请选择班组' }}
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="mb-3">
|
|
|
+ <text class="text-sm text-gray-600 block mb-1">工作日期</text>
|
|
|
+ <picker mode="date" :value="form.workDate" @change="(e: any) => form.workDate = e.detail.value">
|
|
|
+ <view class="border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-800">
|
|
|
+ {{ form.workDate || '请选择日期' }}
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="mb-3">
|
|
|
+ <text class="text-sm text-gray-600 block mb-1">排班类型</text>
|
|
|
+ <view class="flex rounded-lg border border-gray-200 overflow-hidden">
|
|
|
+ <view
|
|
|
+ class="flex-1 py-2 text-center text-sm"
|
|
|
+ :class="form.shiftType === 'month_plan' ? 'bg-primary text-white' : 'bg-white text-gray-600'"
|
|
|
+ @click="form.shiftType = 'month_plan'"
|
|
|
+ >月计划</view>
|
|
|
+ <view
|
|
|
+ class="flex-1 py-2 text-center text-sm"
|
|
|
+ :class="form.shiftType === 'emergency' ? 'bg-red-500 text-white' : 'bg-white text-gray-600'"
|
|
|
+ @click="form.shiftType = 'emergency'"
|
|
|
+ >应急</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="mb-3">
|
|
|
+ <text class="text-sm text-gray-600 block mb-1">选择车辆</text>
|
|
|
+ <view class="flex flex-wrap gap-2">
|
|
|
+ <view
|
|
|
+ v-for="v in vehicleOptions"
|
|
|
+ :key="v.value"
|
|
|
+ class="px-2 py-1 rounded-lg text-xs border"
|
|
|
+ :class="form.vehicleIds.includes(v.value) ? 'bg-primary text-white border-primary' : 'bg-white text-gray-600 border-gray-200'"
|
|
|
+ @click="toggleVehicle(v.value)"
|
|
|
+ >
|
|
|
+ {{ v.label }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="mb-3">
|
|
|
+ <text class="text-sm text-gray-600 block mb-1">备注(可选)</text>
|
|
|
+ <textarea
|
|
|
+ v-model="form.remark"
|
|
|
+ class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm"
|
|
|
+ placeholder="请输入备注信息..."
|
|
|
+ :auto-height="true"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="flex gap-3 mt-4">
|
|
|
+ <button class="flex-1 py-2 bg-white text-gray-600 rounded-lg border border-gray-200 text-sm" @click="closeModal">取消</button>
|
|
|
+ <button class="flex-1 py-2 bg-primary text-white rounded-lg text-sm" @click="submitSchedule">确认</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
@@ -111,7 +185,9 @@ 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'
|
|
|
+import { getSchedulesByDate, createSchedule } from '@/api/schedule'
|
|
|
+import { getTeamSimpleList, getTeamMembers } from '@/api/team'
|
|
|
+import { getVehicleSimpleList } from '@/api/vehicle'
|
|
|
|
|
|
withDefaults(
|
|
|
defineProps<{
|
|
|
@@ -132,6 +208,24 @@ const currentYear = ref(new Date().getFullYear())
|
|
|
const currentMonth = ref(new Date().getMonth() + 1)
|
|
|
const selectedDate = ref('')
|
|
|
|
|
|
+const showModal = ref(false)
|
|
|
+const teamOptions = ref<Array<{ value: number; label: string }>>([])
|
|
|
+const vehicleOptions = ref<Array<{ value: number; label: string }>>([])
|
|
|
+
|
|
|
+const form = ref({
|
|
|
+ teamId: undefined as number | undefined,
|
|
|
+ teamName: '',
|
|
|
+ workDate: '',
|
|
|
+ shiftType: 'month_plan' as 'month_plan' | 'emergency',
|
|
|
+ vehicleIds: [] as number[],
|
|
|
+ staffIds: '',
|
|
|
+ status: 'idle',
|
|
|
+ remark: '',
|
|
|
+})
|
|
|
+
|
|
|
+const teamIndex = computed(() => teamOptions.value.findIndex(t => t.value === form.value.teamId))
|
|
|
+const teamNameDisplay = computed(() => teamOptions.value.find(t => t.value === form.value.teamId)?.label)
|
|
|
+
|
|
|
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
|
|
|
|
|
|
const calendarWeeks = computed(() => {
|
|
|
@@ -158,7 +252,7 @@ const calendarWeeks = computed(() => {
|
|
|
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)
|
|
|
+ const hasSchedule = schedules.value.some(s => s.workDate === dateStr)
|
|
|
|
|
|
currentWeek.push({
|
|
|
day,
|
|
|
@@ -191,7 +285,7 @@ const calendarWeeks = computed(() => {
|
|
|
|
|
|
const daySchedules = computed(() => {
|
|
|
if (!selectedDate.value) return []
|
|
|
- return schedules.value.filter(s => s.scheduleDate === selectedDate.value)
|
|
|
+ return schedules.value.filter(s => s.workDate === selectedDate.value)
|
|
|
})
|
|
|
|
|
|
function changeMonth(delta: number) {
|
|
|
@@ -250,15 +344,97 @@ function getStatusIconClass(status: string) {
|
|
|
return map[status] || 'uniui-calendar text-gray-400'
|
|
|
}
|
|
|
|
|
|
-function addSchedule() {
|
|
|
- uni.showToast({ title: '排班功能开发中', icon: 'none' })
|
|
|
+function toggleVehicle(vehicleId: number) {
|
|
|
+ const idx = form.value.vehicleIds.indexOf(vehicleId)
|
|
|
+ if (idx >= 0) {
|
|
|
+ form.value.vehicleIds.splice(idx, 1)
|
|
|
+ } else {
|
|
|
+ form.value.vehicleIds.push(vehicleId)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function addSchedule() {
|
|
|
+ form.value.workDate = selectedDate.value
|
|
|
+ showModal.value = true
|
|
|
+ if (teamOptions.value.length === 0) {
|
|
|
+ try {
|
|
|
+ teamOptions.value = await getTeamSimpleList()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载班组失败', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (vehicleOptions.value.length === 0) {
|
|
|
+ try {
|
|
|
+ vehicleOptions.value = await getVehicleSimpleList()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载车辆失败', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function closeModal() {
|
|
|
+ showModal.value = false
|
|
|
+}
|
|
|
+
|
|
|
+function resetForm() {
|
|
|
+ form.value = {
|
|
|
+ teamId: undefined,
|
|
|
+ teamName: '',
|
|
|
+ workDate: selectedDate.value,
|
|
|
+ shiftType: 'month_plan',
|
|
|
+ vehicleIds: [],
|
|
|
+ staffIds: '',
|
|
|
+ status: 'idle',
|
|
|
+ remark: '',
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function onTeamChange(teamId: number) {
|
|
|
+ const team = teamOptions.value.find(t => t.value === teamId)
|
|
|
+ form.value.teamName = team?.label || ''
|
|
|
+ try {
|
|
|
+ const members = await getTeamMembers(teamId)
|
|
|
+ form.value.staffIds = members.map(m => m.staffId).join(',')
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载班组成员失败', e)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function submitSchedule() {
|
|
|
+ if (!form.value.teamId) {
|
|
|
+ uni.showToast({ title: '请选择班组', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!form.value.workDate) {
|
|
|
+ uni.showToast({ title: '请选择工作日期', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await createSchedule({
|
|
|
+ teamId: form.value.teamId,
|
|
|
+ teamName: form.value.teamName,
|
|
|
+ workDate: form.value.workDate,
|
|
|
+ shiftType: form.value.shiftType,
|
|
|
+ vehicleIds: form.value.vehicleIds.join(','),
|
|
|
+ staffIds: form.value.staffIds,
|
|
|
+ status: form.value.status,
|
|
|
+ remark: form.value.remark,
|
|
|
+ })
|
|
|
+ uni.showToast({ title: '新增排班成功', icon: 'success' })
|
|
|
+ closeModal()
|
|
|
+ resetForm()
|
|
|
+ await fetchSchedules()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('新增排班失败', e)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async function fetchSchedules() {
|
|
|
+ if (!selectedDate.value) return
|
|
|
loading.value = true
|
|
|
try {
|
|
|
- const res = await getScheduleList(currentYear.value, currentMonth.value)
|
|
|
- schedules.value = res.rows || []
|
|
|
+ const res = await getSchedulesByDate(selectedDate.value)
|
|
|
+ schedules.value = res || []
|
|
|
} catch (error) {
|
|
|
console.error('获取排班列表失败:', error)
|
|
|
} finally {
|
|
|
@@ -266,7 +442,7 @@ async function fetchSchedules() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-watch(() => [currentYear.value, currentMonth.value], fetchSchedules)
|
|
|
+watch(selectedDate, fetchSchedules)
|
|
|
|
|
|
onMounted(() => {
|
|
|
const today = new Date().toISOString().split('T')[0]
|