智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

486 lines
12 KiB

<template>
<div class="seat-selector">
<div class="seats-container">
<div v-if="rows.length === 0" class="no-seats-message">
没有可用座位,请确认场地容量设置
</div>
<div v-else v-for="row in rows" :key="row" class="seat-row">
<div
v-for="col in cols"
:key="`${row}-${col}`"
class="seat-container"
>
<div
class="seat"
:class="{
'seat-available': isSeatAvailable(row, col),
'seat-occupied': !isSeatAvailable(row, col) && isSeatValid(row, col),
'seat-selected': isSelected(row, col),
'seat-hidden': !isSeatValid(row, col)
}"
@click="toggleSeat(row, col)"
>
{{ col }}
</div>
<div v-if="isSelected(row, col)" class="seat-name">
{{ getSeatObject(row, col)?.name || '' }}
</div>
</div>
</div>
</div>
<div class="seat-legend">
<div class="legend-item">
<div class="legend-box seat-available"></div>
<span>可选</span>
</div>
<div class="legend-item">
<div class="legend-box seat-occupied"></div>
<span>已占</span>
</div>
<div class="legend-item">
<div class="legend-box seat-selected"></div>
<span>已选</span>
</div>
</div>
<div class="seat-info">
<span>已选: {{ selectedSeats.length }}/{{ props.maxSelections === Infinity ? '不限' : props.maxSelections }}</span>
<span>剩余空位: {{ availableSeats.length }}</span>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { getClassroompeople } from '@/app/api/classroom';
import { getAllVenueList } from '@/app/api/venue';
const props = defineProps({
venueId: {
type: [Number, String],
default: null
},
capacity: {
type: Number,
default: 0
},
occupiedSeats: {
type: Array,
default: () => []
},
selectedSeatIds: {
type: Array,
default: () => []
},
maxSelections: {
type: Number,
default: Infinity
}
});
const emit = defineEmits(['update:selectedSeats', 'change', 'update:selectedSeatIds']);
// 位置布局配置
const rows = ref([]);
const cols = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // 固定每行10个座位
const selectedSeats = ref([]);
const occupiedSeatsData = ref([]);
const selectedSeatObjects = ref([]);
const totalSeats = ref(0);
// 调整位置行列数以匹配容量 - 不再使用该函数,因为我们在onMounted和watch中直接设置了
const adjustLayout = (capacityValue) => {
console.log('已废弃的adjustLayout调用,使用值:', capacityValue);
};
// 加载场地信息和已占用位置数据
const loadVenueInfoAndOccupiedSeats = async () => {
if (!props.venueId) return;
try {
// 如果有传入的已占用位置,直接使用
if (props.occupiedSeats && props.occupiedSeats.length) {
occupiedSeatsData.value = props.occupiedSeats;
} else {
// 否则尝试从API获取
const response = await getClassroompeople(props.venueId);
if (response.data && Array.isArray(response.data)) {
// 假设API返回的是已占用位置的数组
occupiedSeatsData.value = response.data;
}
}
} catch (error) {
console.error('获取已占用位置失败:', error);
// 模拟一些随机占用的位置
occupiedSeatsData.value = generateRandomOccupiedSeats();
}
};
// 生成一些随机占用的位置(用于演示)
const generateRandomOccupiedSeats = () => {
const occupied = [];
const totalSeats = rows.value.length * cols.value.length;
const occupiedCount = Math.floor(totalSeats * 0.3); // 30%的座位被占用
for (let i = 0; i < occupiedCount; i++) {
const row = rows.value[Math.floor(Math.random() * rows.value.length)];
const col = cols.value[Math.floor(Math.random() * cols.value.length)];
const seat = `${row}-${col}`;
if (!occupied.includes(seat)) {
occupied.push(seat);
}
}
return occupied;
};
// 计算所有有效座位
const allValidSeats = computed(() => {
const seats = [];
if (rows.value.length === 0) return seats;
rows.value.forEach(row => {
cols.value.forEach(col => {
if (isSeatValid(row, col)) {
const seatId = `${row}-${col}`;
seats.push(seatId);
}
});
});
return seats;
});
// 计算可用座位(未被占用的有效座位)
const availableSeats = computed(() => {
const seats = [];
allValidSeats.value.forEach(seatId => {
if (!occupiedSeatsData.value.includes(seatId)) {
seats.push(seatId);
}
});
console.log('有效座位总数:', allValidSeats.value.length);
console.log('已占用座位数:', occupiedSeatsData.value.length);
console.log('可用座位数:', seats.length);
console.log('已选座位数:', selectedSeats.value.length);
return seats;
});
// 检查座位是否有效(在总座位数范围内)
const isSeatValid = (row, col) => {
const seatIndex = (row.charCodeAt(0) - 65) * 10 + (col - 1);
return seatIndex < totalSeats.value;
};
// 检查座位是否可用(未被占用且有效)
const isSeatAvailable = (row, col) => {
// 先检查座位是否有效
if (!isSeatValid(row, col)) {
return false;
}
const seatId = `${row}-${col}`;
return !occupiedSeatsData.value.includes(seatId);
};
// 检查座位是否被选中
const isSelected = (row, col) => {
const seatId = `${row}-${col}`;
return selectedSeats.value.includes(seatId);
};
// 获取座位对应的选中对象
const getSeatObject = (row, col) => {
const seatId = `${row}-${col}`;
return selectedSeatObjects.value.find(obj => obj.id === seatId);
};
// 切换座位选择状态
const toggleSeat = (row, col) => {
console.log('点击座位:', row, col);
// 检查座位是否有效
if (!isSeatValid(row, col)) {
console.log('座位无效,不能选择');
return;
}
const seatId = `${row}-${col}`;
// 检查座位是否可用
if (!isSeatAvailable(row, col)) {
console.log('座位已占用,不能选择');
return;
}
const index = selectedSeats.value.indexOf(seatId);
if (index === -1) {
// 检查是否已达到最大选择数量
if (selectedSeats.value.length >= props.maxSelections) {
console.log('已达到最大选择数量:', props.maxSelections);
return;
}
console.log('添加座位:', seatId);
selectedSeats.value.push(seatId);
// 检查是否有对应的外部选中对象,没有则创建一个新的
if (!selectedSeatObjects.value.some(obj => obj.id === seatId)) {
selectedSeatObjects.value.push({ id: seatId, name: '' });
}
} else {
console.log('移除座位:', seatId);
selectedSeats.value.splice(index, 1);
// 移除对应的外部选中对象
const objIndex = selectedSeatObjects.value.findIndex(obj => obj.id === seatId);
if (objIndex !== -1) {
selectedSeatObjects.value.splice(objIndex, 1);
}
}
console.log('选中座位:', selectedSeats.value);
emit('update:selectedSeats', selectedSeats.value);
emit('update:selectedSeatIds', selectedSeatObjects.value);
emit('change', selectedSeats.value);
};
// 添加场地列表数据
const venueList = ref([]);
// 加载场地列表
const loadVenueList = async () => {
try {
const response = await getAllVenueList({});
if (response.data && Array.isArray(response.data)) {
venueList.value = response.data;
}
} catch (error) {
console.error('获取场地列表失败:', error);
}
};
// 获取当前场地信息
const getCurrentVenueInfo = () => {
const venue = venueList.value.find(venue => venue.id === props.venueId) || null;
console.log('获取场地信息:', venue, '场地ID:', props.venueId, '场地列表:', venueList.value);
return venue;
};
// 监听属性变化
watch(() => props.venueId, async () => {
console.log('监听venueId变化:', props.venueId);
selectedSeats.value = [];
selectedSeatObjects.value = [];
// 重新设置座位布局
if (props.capacity) {
console.log('使用props容量:', props.capacity);
totalSeats.value = props.capacity;
const rowCount = Math.ceil(props.capacity / 10);
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26);
} else {
// 设置默认值
totalSeats.value = 30;
const rowCount = 3;
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
);
}
console.log('更新布局 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
// 加载已占用座位
loadVenueInfoAndOccupiedSeats();
});
// 监听容量变化
watch(() => props.capacity, (newCapacity) => {
console.log('监听capacity变化:', newCapacity);
// 更新座位布局
if (newCapacity || newCapacity === 0) {
totalSeats.value = newCapacity;
const rowCount = Math.ceil(newCapacity / 10);
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26);
console.log('根据新容量更新布局 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
}
});
watch(() => props.occupiedSeats, () => {
occupiedSeatsData.value = props.occupiedSeats;
});
// 监听外部传入的selectedSeatIds变化
watch(() => props.selectedSeatIds, (newValue) => {
if (newValue && Array.isArray(newValue)) {
selectedSeatObjects.value = [...newValue];
selectedSeats.value = newValue.map(item => item.id);
}
}, { immediate: true });
// 组件挂载时初始化
onMounted(async () => {
console.log('组件挂载 - venueId:', props.venueId, 'capacity:', props.capacity);
// 确保有默认值
if (!props.capacity && props.capacity !== 0) {
totalSeats.value = 30;
// 生成默认行数
const rowCount = Math.ceil(totalSeats.value / 10);
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
);
console.log('使用默认设置 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
} else {
console.log('使用props容量:', props.capacity);
totalSeats.value = props.capacity;
// 计算需要的行数
const rowCount = Math.ceil(props.capacity / 10);
// 生成行标识
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26);
console.log('根据容量设置 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
}
// 加载已占用座位
loadVenueInfoAndOccupiedSeats();
});
</script>
<style scoped>
.seat-selector {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.screen {
width: 80%;
height: 30px;
background: linear-gradient(to bottom, #ccc, #f5f5f5);
margin-bottom: 30px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
}
.screen-text {
font-size: 14px;
color: #666;
}
.seats-container {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
.seat-row {
display: flex;
align-items: flex-start;
gap: 10px;
}
.row-label {
width: 20px;
text-align: center;
font-weight: bold;
margin-top: 10px;
}
.seat-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
margin-bottom: 5px;
}
.seat {
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
}
.seat-name {
font-size: 10px;
max-width: 40px;
text-align: center;
word-break: break-all;
color: #333;
height: 24px;
overflow: hidden;
}
.seat-available {
background-color: #67c23a;
color: white;
}
.seat-occupied {
background-color: #909399;
color: white;
cursor: not-allowed;
}
.seat-selected {
background-color: #409eff;
color: white;
}
.seat-hidden {
visibility: hidden;
pointer-events: none;
}
.seat-legend {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-box {
width: 15px;
height: 15px;
border-radius: 3px;
}
.seat-info {
display: flex;
gap: 20px;
font-size: 14px;
}
.no-seats-message {
font-size: 14px;
color: #666;
padding: 20px;
text-align: center;
}
</style>