| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- <template>
- <!--
- 座位布局组件
- 功能:管理学生座位的显示和交互
- 支持两种模式:
- 1. 全览模式:缩放显示所有座位,支持双击进入点名模式
- 2. 点名模式:正常尺寸显示,支持滚动和学生状态切换
- -->
- <view class="seat-layout-container">
- <!-- 全览模式 (overview 和 overview-simple) -->
- <view
- v-if="mode !== 'attendance'"
- class="seat-layout overview-mode"
- :style="overviewContainerStyle"
- >
- <view class="overview-content" :style="layoutStyle" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
- <!-- 学生卡片网格 -->
- <view class="seat-grid" :style="gridStyle">
- <StudentCard
- v-for="student in students"
- :key="student.id"
- :student="student"
- :attendance-mode="false"
- :style="getCardPosition(student)"
- @statusChange="handleStatusChange"
- @click="handleCardClick"
- />
- </view>
- </view>
- </view>
- <!-- 点名模式 -->
- <scroll-view
- v-else
- ref="scrollViewRef"
- class="seat-layout attendance-mode"
- scroll-x="true"
- scroll-y="true"
- enable-flex="true"
- :scroll-top="scrollTop"
- :scroll-left="scrollLeft"
- @touchstart="handleTouchStart"
- @touchend="handleTouchEnd"
- >
- <!-- 学生卡片网格容器 -->
- <view class="scroll-content" :style="scrollContentStyle">
- <view class="seat-grid" :style="gridStyle">
- <StudentCard
- v-for="student in students"
- :key="student.id"
- :student="student"
- :attendance-mode="true"
- :style="getCardPosition(student)"
- @statusChange="handleStatusChange"
- @click="handleCardClick"
- />
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
- <script setup>
- /**
- * 座位布局组件
- *
- * 主要功能:
- * 1. 学生座位的网格布局显示
- * 2. 全览模式和点名模式的切换
- * 3. 座位布局的缩放和滚动控制
- * 4. 双击事件处理和模式切换
- * 5. 滚动位置重置和页面滚动控制
- */
- import { ref, computed, watch, defineProps, defineEmits } from 'vue'
- import StudentCard from './StudentCard.vue'
- const props = defineProps({
- students: {
- type: Array,
- default: () => []
- },
- layout: {
- type: Object,
- default: () => ({ rows: 20, cols: 10 })
- },
- mode: {
- type: String,
- default: 'overview' // 'overview' | 'attendance'
- }
- })
- const emit = defineEmits(['modeChange', 'statusChange', 'studentClick'])
- // 触摸相关状态
- const lastTap = ref(0)
- const touchStartTime = ref(0)
- const touchStartPos = ref({ x: 0, y: 0 })
- // 滚动控制
- const scrollTop = ref(0)
- const scrollLeft = ref(0)
- const scrollViewRef = ref(null)
- // 卡片尺寸常量
- const CARD_WIDTH = 200 // rpx
- const CARD_HEIGHT = 280 // rpx - 根据新的卡片布局调整(20+224+20+文字+20+10)
- const CARD_GAP = 12 // rpx - 适中的间距,避免太挤或太松
- // 计算布局样式
- const layoutStyle = computed(() => {
- const totalWidthRpx = props.layout.cols * (CARD_WIDTH + CARD_GAP) - CARD_GAP // rpx单位
- const totalHeightRpx = props.layout.rows * (CARD_HEIGHT + CARD_GAP) - CARD_GAP // rpx单位
- console.log('SeatLayout 计算样式:', {
- mode: props.mode,
- students: props.students.length,
- layout: props.layout,
- totalWidthRpx,
- totalHeightRpx
- })
- if (props.mode === 'overview' || props.mode === 'overview-simple') {
- // 全览模式:缩放到屏幕宽度的90%,留边距
- const systemInfo = uni.getSystemInfoSync()
- const screenWidthPx = systemInfo.windowWidth // px单位
- const screenWidthRpx = screenWidthPx * 2 // 转换为rpx (1px = 2rpx)
- const targetWidthRpx = screenWidthRpx * 0.9 // 使用90%宽度,留10%边距
- const scale = targetWidthRpx / totalWidthRpx
- return {
- transform: `scale(${scale})`,
- transformOrigin: 'top left',
- width: `${totalWidthRpx}rpx`,
- height: `${totalHeightRpx}rpx`
- }
- } else {
- // 点名模式:正常尺寸,可滚动
- return {
- transform: 'scale(1)',
- transformOrigin: 'top left',
- width: `${totalWidthRpx}rpx`,
- height: `${totalHeightRpx}rpx`
- }
- }
- })
- // 网格样式
- const gridStyle = computed(() => ({
- display: 'grid',
- gridTemplateColumns: `repeat(${props.layout.cols}, ${CARD_WIDTH}rpx)`,
- gridTemplateRows: `repeat(${props.layout.rows}, auto)`, // 改为auto,让行高自适应卡片内容
- gap: `${CARD_GAP}rpx`,
- padding: `${CARD_GAP}rpx`
- }))
- // 全览模式容器样式
- const overviewContainerStyle = computed(() => ({
- width: '100%',
- minHeight: '600rpx', // 最小高度,允许内容撑开
- overflow: 'visible', // 允许内容显示
- position: 'relative'
- }))
- // 滚动内容样式
- const scrollContentStyle = computed(() => {
- const totalWidthRpx = props.layout.cols * (CARD_WIDTH + CARD_GAP) + CARD_GAP
- // 高度改为自适应,不再固定计算
- return {
- width: `${totalWidthRpx}rpx`,
- minWidth: '100%',
- minHeight: '100%'
- // 移除固定高度,让内容自适应
- }
- })
- // 获取卡片位置
- const getCardPosition = (student) => {
- return {
- gridColumn: student.col,
- gridRow: student.row
- }
- }
- // 处理触摸开始
- const handleTouchStart = (e) => {
- touchStartTime.value = Date.now()
- touchStartPos.value = {
- x: e.touches[0].clientX,
- y: e.touches[0].clientY
- }
- }
- // 处理触摸结束
- const handleTouchEnd = () => {
- const touchEndTime = Date.now()
- const touchDuration = touchEndTime - touchStartTime.value
-
- // 检测双击
- if (touchDuration < 300) { // 快速点击
- const now = Date.now()
- if (now - lastTap.value < 300) {
- // 双击检测成功
- handleDoubleClick()
- }
- lastTap.value = now
- }
- }
- // 处理双击事件
- const handleDoubleClick = () => {
- console.log('双击事件,当前模式:', props.mode)
- if (props.mode === 'attendance') {
- // 从点名模式退出
- const newMode = 'overview'
- console.log('从点名模式退出到:', newMode)
- emit('modeChange', newMode)
- } else {
- // 从全览模式(overview 或 overview-simple)进入点名模式
- const newMode = 'attendance'
- console.log('进入点名模式:', newMode)
- emit('modeChange', newMode)
- }
- }
- // 处理学生状态变化
- const handleStatusChange = (statusData) => {
- emit('statusChange', statusData)
- }
- // 处理卡片点击
- const handleCardClick = (student) => {
- emit('studentClick', student)
- }
- // 监听模式变化,重置滚动位置
- watch(() => props.mode, (newMode, oldMode) => {
- console.log('模式变化:', oldMode, '->', newMode)
- if (oldMode === 'attendance' && (newMode === 'overview' || newMode === 'overview-simple')) {
- console.log('从点名模式退出,重置页面滚动位置')
- // 使用 uni.pageScrollTo 强制滚动到页面顶部
- uni.pageScrollTo({
- scrollTop: 0,
- duration: 300,
- success: () => {
- console.log('页面滚动重置成功')
- },
- fail: (err) => {
- console.log('页面滚动重置失败:', err)
- }
- })
- // 同时重置 scroll-view 的滚动位置
- scrollTop.value = 0
- scrollLeft.value = 0
- console.log('滚动位置重置完成')
- }
- })
- </script>
- <style lang="scss" scoped>
- .seat-layout-container {
- width: 100%;
- min-height: 100%;
- position: relative;
- overflow: visible;
-
- .seat-layout {
- transition: transform 0.3s ease;
-
- &.overview-mode {
- // 全览模式样式
- pointer-events: auto;
- .overview-content {
- width: 100%;
- height: 100%;
- }
- .seat-grid {
- pointer-events: none; // 禁用卡片交互
- }
- }
-
- &.attendance-mode {
- // 点名模式样式
- width: 100%;
- height: 100%;
- .scroll-content {
- display: flex;
- flex-direction: column;
- }
- .seat-grid {
- pointer-events: auto; // 启用卡片交互
- flex-shrink: 0;
- }
- }
- }
-
- .seat-grid {
- min-height: 100%;
- }
-
- .mode-tip {
- position: absolute;
- bottom: 40rpx;
- left: 50%;
- transform: translateX(-50%);
- background-color: rgba(0, 0, 0, 0.7);
- color: white;
- padding: 20rpx 40rpx;
- border-radius: 40rpx;
- font-size: 28rpx;
- z-index: 100;
- }
- }
- </style>
|