Explorar el Código

fix:修复房间管理与预期不一致的问题;修改为房间为主体,房间排期状态为附属

yangyi hace 4 meses
padre
commit
b8a90a66da
Se han modificado 3 ficheros con 291 adiciones y 47 borrados
  1. 1 1
      src/router/index.ts
  2. 1 0
      src/type/index.ts
  3. 289 46
      src/view/admin/AdminRoomCalendarView.vue

+ 1 - 1
src/router/index.ts

@@ -165,7 +165,7 @@ export const adminRoutes: RouteRecordRaw[] = [
                 name: 'adminRooms',
                 component: AdminRoomCalendarView,
                 meta: {
-                    title: '房态管理',
+                    title: '宠物房间管理',
                     icon: Calendar
                 }
             },

+ 1 - 0
src/type/index.ts

@@ -41,6 +41,7 @@ export interface Package {
 export interface BoardingRoom {
     id: string
     name: string
+    roomType?: string
     capacity: number
     pricePerNight: number
     description?: string

+ 289 - 46
src/view/admin/AdminRoomCalendarView.vue

@@ -1,53 +1,135 @@
 <template>
   <div class="admin-room-calendar">
-    <h1>宠物房间管理</h1>
-
-    <div class="toolbar">
-      <el-select v-model="selectedRoomId" placeholder="选择房型" @change="fetchStatus">
-        <el-option v-for="room in rooms" :key="room.id" :label="room.name" :value="room.id" />
-      </el-select>
-      <el-date-picker
-        v-model="currentMonth"
-        type="month"
-        placeholder="选择月份"
-        value-format="YYYY-MM"
-        @change="fetchStatus"
-      />
+    <div class="header">
+      <h1>宠物房间管理</h1>
+      <el-button type="primary" @click="openCreate">+ 新增房型</el-button>
     </div>
 
-    <RoomCalendar
-      v-if="selectedRoomId"
-      :room-id="selectedRoomId"
-      :month="currentMonth"
-      :status-list="roomStatusList"
-      @date-click="handleDateClick"
-      @month-change="onMonthChange"
-    />
+    <el-table :data="rooms" border stripe v-loading="loading">
+      <el-table-column prop="name" label="房间名称" />
+      <el-table-column prop="roomType" label="房型类型" width="120">
+        <template #default="{ row }">
+          {{ roomTypeLabel(row.roomType) }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="capacity" label="容量" width="80" align="center" />
+      <el-table-column prop="pricePerNight" label="价格(元/天)" width="130" align="right" />
+      <el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip />
+      <el-table-column label="操作" width="300" fixed="right">
+        <template #default="{ row }">
+          <el-button size="small" type="primary" @click="openEdit(row)">编辑</el-button>
+          <el-button size="small" type="danger" @click="handleDelete(row.id)">删除</el-button>
+          <el-button size="small" @click="openStatus(row)">入住状态</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-dialog v-model="createVisible" title="新增房型" width="480px">
+      <el-form :model="roomForm" label-width="100px">
+        <el-form-item label="房间名称" required>
+          <el-input v-model="roomForm.roomName" placeholder="请输入房间名称" />
+        </el-form-item>
+        <el-form-item label="房型类型">
+          <el-select v-model="roomForm.roomType" placeholder="请选择房型类型" clearable>
+            <el-option label="标准间" value="STANDARD" />
+            <el-option label="豪华间" value="DELUXE" />
+            <el-option label="VIP" value="VIP" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="容量">
+          <el-input-number v-model="roomForm.capacity" :min="1" :max="10" />
+        </el-form-item>
+        <el-form-item label="价格(元/天)" required>
+          <el-input-number v-model="roomForm.pricePerDay" :min="0" :precision="2" :step="10" />
+        </el-form-item>
+        <el-form-item label="描述">
+          <el-input v-model="roomForm.description" type="textarea" :rows="3" placeholder="请输入房间描述" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="createVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="handleCreate">保存</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="editVisible" title="编辑房型" width="480px">
+      <el-form :model="editForm" label-width="100px">
+        <el-form-item label="房间名称" required>
+          <el-input v-model="editForm.roomName" placeholder="请输入房间名称" />
+        </el-form-item>
+        <el-form-item label="房型类型">
+          <el-select v-model="editForm.roomType" placeholder="请选择房型类型" clearable>
+            <el-option label="标准间" value="STANDARD" />
+            <el-option label="豪华间" value="DELUXE" />
+            <el-option label="VIP" value="VIP" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="容量">
+          <el-input-number v-model="editForm.capacity" :min="1" :max="10" />
+        </el-form-item>
+        <el-form-item label="价格(元/天)" required>
+          <el-input-number v-model="editForm.pricePerDay" :min="0" :precision="2" :step="10" />
+        </el-form-item>
+        <el-form-item label="描述">
+          <el-input v-model="editForm.description" type="textarea" :rows="3" placeholder="请输入房间描述" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="editVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="handleUpdate">保存</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="statusDialogVisible" :title="`入住状态 - ${statusRoomName}`" width="540px">
+      <div class="status-dialog-body">
+        <div class="status-toolbar">
+          <el-date-picker
+            v-model="statusMonth"
+            type="month"
+            placeholder="选择月份"
+            value-format="YYYY-MM"
+            @change="fetchStatusForDialog"
+          />
+        </div>
+        <RoomCalendar
+          v-if="statusRoomId"
+          :room-id="statusRoomId"
+          :month="statusMonth"
+          :status-list="statusList"
+          @date-click="handleStatusDateClick"
+          @month-change="onStatusMonthChange"
+        />
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
-import { ElMessage } from 'element-plus'
+import { reactive, ref, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import type { BoardingRoom, RoomStatus } from '@/type'
 import RoomCalendar from '@/components/RoomCalendar.vue'
-import { getBoardingRoomController } from '@/api/boarding-room-controller'
 import { getAdminBoardingRoomController } from '@/api/admin-boarding-room-controller'
-import type { RoomStatusDto } from '@/api/models'
+import type { BoardingRoomCreateDto, RoomStatusDto } from '@/api/models'
 
 const rooms = ref<BoardingRoom[]>([])
-const selectedRoomId = ref('')
-const currentMonth = ref(new Date().toISOString().slice(0, 7))
-const roomStatusList = ref<RoomStatus[]>([])
+const loading = ref(false)
+
+const roomTypeLabel = (type?: string) => {
+  const map: Record<string, string> = { STANDARD: '标准间', DELUXE: '豪华间', VIP: 'VIP' }
+  return map[type ?? ''] || type || '-'
+}
 
 const fetchRooms = async () => {
+  loading.value = true
   try {
-    const api = getBoardingRoomController()
-    const res = await api.listRooms()
+    const api = getAdminBoardingRoomController()
+    const res = await api.listAll()
     if (res.code === 200 && res.data) {
       rooms.value = res.data.map(r => ({
         id: String(r.id ?? ''),
         name: r.roomName ?? '',
+        roomType: r.roomType ?? '',
         capacity: r.capacity ?? 0,
         pricePerNight: r.pricePerDay ?? 0,
         description: r.description
@@ -55,16 +137,168 @@ const fetchRooms = async () => {
     }
   } catch {
     // ignore
+  } finally {
+    loading.value = false
+  }
+}
+
+// --- Create ---
+const createVisible = ref(false)
+const submitting = ref(false)
+const roomForm = reactive({
+  roomName: '',
+  roomType: '',
+  capacity: 1,
+  pricePerDay: 0,
+  description: ''
+})
+
+const openCreate = () => {
+  roomForm.roomName = ''
+  roomForm.roomType = ''
+  roomForm.capacity = 1
+  roomForm.pricePerDay = 0
+  roomForm.description = ''
+  createVisible.value = true
+}
+
+const handleCreate = async () => {
+  if (!roomForm.roomName.trim()) {
+    ElMessage.warning('请输入房间名称')
+    return
+  }
+  if (roomForm.pricePerDay <= 0) {
+    ElMessage.warning('请输入有效的价格')
+    return
+  }
+  submitting.value = true
+  try {
+    const api = getAdminBoardingRoomController()
+    const dto: BoardingRoomCreateDto = {
+      roomName: roomForm.roomName.trim(),
+      roomType: roomForm.roomType || undefined,
+      capacity: roomForm.capacity,
+      pricePerDay: roomForm.pricePerDay,
+      description: roomForm.description.trim() || undefined
+    }
+    const res = await api.create3(dto)
+    if (res.code === 200) {
+      ElMessage.success('新增房型成功')
+      createVisible.value = false
+      await fetchRooms()
+    } else {
+      ElMessage.error(res.message || '新增失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '新增失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+// --- Edit ---
+const editVisible = ref(false)
+const editForm = reactive({
+  roomName: '',
+  roomType: '',
+  capacity: 1,
+  pricePerDay: 0,
+  description: ''
+})
+const editingId = ref('')
+
+const openEdit = (row: BoardingRoom) => {
+  editingId.value = row.id
+  editForm.roomName = row.name
+  editForm.roomType = row.roomType ?? ''
+  editForm.capacity = row.capacity
+  editForm.pricePerDay = row.pricePerNight
+  editForm.description = row.description ?? ''
+  editVisible.value = true
+}
+
+const handleUpdate = async () => {
+  if (!editForm.roomName.trim()) {
+    ElMessage.warning('请输入房间名称')
+    return
+  }
+  if (editForm.pricePerDay <= 0) {
+    ElMessage.warning('请输入有效的价格')
+    return
+  }
+  submitting.value = true
+  try {
+    const api = getAdminBoardingRoomController()
+    const dto: BoardingRoomCreateDto = {
+      roomName: editForm.roomName.trim(),
+      roomType: editForm.roomType || undefined,
+      capacity: editForm.capacity,
+      pricePerDay: editForm.pricePerDay,
+      description: editForm.description.trim() || undefined
+    }
+    const res = await api.update2(Number(editingId.value), dto)
+    if (res.code === 200) {
+      ElMessage.success('编辑房型成功')
+      editVisible.value = false
+      await fetchRooms()
+    } else {
+      ElMessage.error(res.message || '编辑失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '编辑失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+// --- Delete ---
+const handleDelete = async (id: string) => {
+  try {
+    await ElMessageBox.confirm('确认删除该房型?删除后相关数据将不可恢复。', '确认删除', {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+  } catch {
+    return
+  }
+  try {
+    const api = getAdminBoardingRoomController()
+    const res = await api.delete2(Number(id))
+    if (res.code === 200) {
+      ElMessage.success('删除成功')
+      await fetchRooms()
+    } else {
+      ElMessage.error(res.message || '删除失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '删除失败')
   }
 }
 
-const fetchStatus = async () => {
-  if (!selectedRoomId.value || !currentMonth.value) return
+// --- Status Dialog ---
+const statusDialogVisible = ref(false)
+const statusRoomId = ref('')
+const statusRoomName = ref('')
+const statusMonth = ref(new Date().toISOString().slice(0, 7))
+const statusList = ref<RoomStatus[]>([])
+
+const openStatus = (row: BoardingRoom) => {
+  statusRoomId.value = row.id
+  statusRoomName.value = row.name
+  statusMonth.value = new Date().toISOString().slice(0, 7)
+  statusList.value = []
+  statusDialogVisible.value = true
+  fetchStatusForDialog()
+}
+
+const fetchStatusForDialog = async () => {
+  if (!statusRoomId.value || !statusMonth.value) return
   try {
-    const api = getBoardingRoomController()
-    const res = await api.getStatus(Number(selectedRoomId.value), { month: currentMonth.value })
+    const api = getAdminBoardingRoomController()
+    const res = await api.getStatus1(Number(statusRoomId.value), { month: statusMonth.value })
     if (res.code === 200 && res.data) {
-      roomStatusList.value = res.data.map(s => ({
+      statusList.value = res.data.map(s => ({
         date: s.date ?? '',
         status: (s.status === 'AVAILABLE' ? 'AVAILABLE' : 'OCCUPIED') as 'AVAILABLE' | 'OCCUPIED'
       }))
@@ -74,15 +308,15 @@ const fetchStatus = async () => {
   }
 }
 
-const handleDateClick = async (date: string, status: 'AVAILABLE' | 'OCCUPIED') => {
+const handleStatusDateClick = async (date: string, status: 'AVAILABLE' | 'OCCUPIED') => {
   const newStatus = status === 'AVAILABLE' ? 'OCCUPIED' : 'AVAILABLE'
   try {
     const api = getAdminBoardingRoomController()
     const dto: RoomStatusDto = { date, status: newStatus }
-    const res = await api.updateStatus1(Number(selectedRoomId.value), dto)
+    const res = await api.updateStatus1(Number(statusRoomId.value), dto)
     if (res.code === 200) {
       ElMessage.success(`已将 ${date} 状态切换为 ${newStatus === 'AVAILABLE' ? '空闲' : '占用'}`)
-      await fetchStatus()
+      await fetchStatusForDialog()
     } else {
       ElMessage.error(res.message || '操作失败')
     }
@@ -91,9 +325,9 @@ const handleDateClick = async (date: string, status: 'AVAILABLE' | 'OCCUPIED') =
   }
 }
 
-const onMonthChange = (month: string) => {
-  currentMonth.value = month
-  fetchStatus()
+const onStatusMonthChange = (month: string) => {
+  statusMonth.value = month
+  fetchStatusForDialog()
 }
 
 onMounted(fetchRooms)
@@ -104,13 +338,22 @@ onMounted(fetchRooms)
   padding: 20px;
 }
 
-h1 {
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
   margin-bottom: 20px;
 }
 
-.toolbar {
-  display: flex;
-  gap: 16px;
-  margin-bottom: 24px;
+h1 {
+  margin: 0;
+}
+
+.status-dialog-body {
+  min-height: 300px;
+}
+
+.status-toolbar {
+  margin-bottom: 16px;
 }
 </style>