417 lines
9.0 KiB
Vue
417 lines
9.0 KiB
Vue
<template>
|
||
<view class="grid" @click="handleCardClick">
|
||
<view class="grid-item">
|
||
<!-- 麻将桌区域 -->
|
||
<view class="mahjong-table-section">
|
||
<!-- 状态标签 -->
|
||
<view class="status-tag">
|
||
{{ statusName }}
|
||
</view>
|
||
|
||
<!-- 九宫格麻将桌 -->
|
||
<view class="nine-grid-container">
|
||
<view></view>
|
||
<view class="item">
|
||
<view v-if="getJoinPlayerAtPosition(1)" class="item-avatar">
|
||
<image :src="getPlayerAtPosition(0)" class="avatar-img" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<view></view>
|
||
|
||
<view class="item">
|
||
<view v-if="getJoinPlayerAtPosition(2)" class="item-avatar">
|
||
<image :src="getPlayerAtPosition(1)" class="avatar-img" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<view></view>
|
||
|
||
<view class="item">
|
||
<view v-if="getJoinPlayerAtPosition(3)" class="item-avatar">
|
||
<image :src="getPlayerAtPosition(2)" class="avatar-img" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<view></view>
|
||
|
||
<view class="item">
|
||
<view v-if="getJoinPlayerAtPosition(4)" class="item-avatar">
|
||
<image :src="getPlayerAtPosition(3)" class="avatar-img" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<view></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 信息区域 -->
|
||
<view class="info-section">
|
||
<view class="info-content">
|
||
<view class="info-title">{{ item.description }}</view>
|
||
<view class="info-text">{{ item.dateStr }}</view>
|
||
<view class="info-text">{{ item.time }}</view>
|
||
<view class="info-text">{{ item.room }}</view>
|
||
<view class="info-text">{{ item.requirements }}</view>
|
||
<view class="info-text">{{ item.extra_info }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { defineProps, defineEmits, computed } from 'vue'
|
||
|
||
// ==================== Props 定义 ====================
|
||
const props = defineProps({
|
||
item: {
|
||
type: Object,
|
||
required: true,
|
||
default: () => ({
|
||
id: '',
|
||
status: '',
|
||
description: '',
|
||
dateStr:'',
|
||
time: '',
|
||
room: '',
|
||
requirements: '',
|
||
extra_info: '',
|
||
personCount: 4, // 总人数
|
||
joinPerson: [{
|
||
id: 1,
|
||
name: '张三',
|
||
avatar: '',
|
||
phone: '',
|
||
}], // 已加入人员
|
||
})
|
||
}
|
||
})
|
||
|
||
// ==================== Emits 定义 ====================
|
||
const emit = defineEmits(['click', 'join'])
|
||
|
||
// ==================== 计算属性 ====================
|
||
/**
|
||
* 获取玩家位置布局
|
||
* 根据总人数确定不同的布局方式
|
||
*/
|
||
const playerPositions = computed(() => {
|
||
const { personCount, joinPerson } = props.item
|
||
const joinedCount = joinPerson.length
|
||
|
||
// 根据总人数确定布局
|
||
if (personCount === 2) {
|
||
// 2人局:第二行左边和右边
|
||
return {
|
||
positions: [null, joinPerson[0] || null, joinPerson[1] || null, null],
|
||
joinPositions: [null, !joinPerson[0], !joinPerson[1], null]
|
||
}
|
||
} else if (personCount === 3) {
|
||
// 3人局:第一行中间、第二行左边和右边
|
||
return {
|
||
positions: [joinPerson[2] || null, joinPerson[0] || null, joinPerson[1] || null, null],
|
||
joinPositions: [!joinPerson[2], !joinPerson[0], !joinPerson[1], null]
|
||
}
|
||
} else {
|
||
// 4人局:标准布局(上、左、右、下)
|
||
return {
|
||
positions: [
|
||
joinPerson[2] || null, // 第一行中间
|
||
joinPerson[0] || null, // 第二行左边
|
||
joinPerson[1] || null, // 第二行右边
|
||
joinPerson[3] || null // 第三行中间
|
||
],
|
||
joinPositions: [
|
||
!joinPerson[2], // 第一行中间
|
||
!joinPerson[0], // 第二行左边
|
||
!joinPerson[1], // 第二行右边
|
||
!joinPerson[3] // 第三行中间
|
||
]
|
||
}
|
||
}
|
||
})
|
||
|
||
/**
|
||
* 状态名称计算
|
||
* 0=待开始,1=进行中,2=已结束,3=取消
|
||
*/
|
||
const statusName = computed(() => {
|
||
const { status, personCount, joinPerson } = props.item
|
||
const count = joinPerson.length
|
||
|
||
if (status === 0) {
|
||
return personCount === count ? "待开始" : "组局中..."
|
||
} else if (status === 1) {
|
||
return "进行中"
|
||
} else if (status === 2) {
|
||
return "已结束"
|
||
} else if (status === 3) {
|
||
return "取消"
|
||
}
|
||
return "其它"
|
||
})
|
||
|
||
// ==================== 方法函数 ====================
|
||
/**
|
||
* 获取指定位置的玩家头像
|
||
* @param {number} position - 位置索引
|
||
* @returns {string} 头像URL
|
||
*/
|
||
const getPlayerAtPosition = (position) => {
|
||
if (playerPositions.value.positions[position] == null) {
|
||
return "@@:app/static/add.png"
|
||
}
|
||
return playerPositions.value.positions[position].avatar
|
||
}
|
||
|
||
/**
|
||
* 判断指定位置是否显示玩家
|
||
* @param {number} index - 位置索引
|
||
* @returns {boolean} 是否显示
|
||
*/
|
||
const getJoinPlayerAtPosition = (index) => {
|
||
const personCount = props.item.personCount
|
||
|
||
if (personCount === 2) {
|
||
// 2人局:第二行左边和右边
|
||
return [2, 3].includes(index)
|
||
} else if (personCount === 3) {
|
||
// 3人局:第一行中间、第二行左边和右边
|
||
return [1, 2, 3].includes(index)
|
||
} else {
|
||
return true
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断指定位置是否可以加入
|
||
* @param {number} position - 位置索引
|
||
* @returns {boolean} 是否可以加入
|
||
*/
|
||
const canJoinAtPosition = (position) => {
|
||
const { personCount, joinPerson } = props.item
|
||
const joinedCount = joinPerson.length
|
||
|
||
// 如果已满员,不能加入
|
||
if (joinedCount >= personCount) {
|
||
return false
|
||
}
|
||
|
||
// 直接使用计算属性中的 joinPositions
|
||
return playerPositions.value.joinPositions[position]
|
||
}
|
||
|
||
/**
|
||
* 获取单元格样式类
|
||
* @param {number} position - 位置索引
|
||
* @returns {string} 样式类名
|
||
*/
|
||
const getCellClass = (position) => {
|
||
if (getPlayerAtPosition(position)) {
|
||
return 'player-cell'
|
||
} else if (canJoinAtPosition(position)) {
|
||
return 'join-cell'
|
||
}
|
||
return ''
|
||
}
|
||
|
||
// ==================== 事件处理 ====================
|
||
/**
|
||
* 处理卡片点击事件
|
||
*/
|
||
const handleCardClick = () => {
|
||
emit('click', props.item)
|
||
}
|
||
|
||
/**
|
||
* 处理加入按钮点击事件
|
||
*/
|
||
const handleJoin = () => {
|
||
emit('join', props.item)
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// ==================== 主容器样式 ====================
|
||
.grid {
|
||
width: 100%;
|
||
display: flex;
|
||
align-content: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.grid-item {
|
||
width: 320rpx;
|
||
height: 460rpx;
|
||
background: #FFFFFF;
|
||
box-shadow: -4rpx 12rpx 7rpx 0rpx rgba(0, 0, 0, 0.25);
|
||
border-radius: 27rpx;
|
||
border: 4rpx solid #006C1A;
|
||
}
|
||
|
||
// ==================== 麻将桌区域样式 ====================
|
||
.mahjong-table-section {
|
||
height: 251rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.status-tag {
|
||
position: absolute;
|
||
left: 16rpx;
|
||
top: 0;
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-weight: 500;
|
||
font-size: 26rpx;
|
||
color: #000000;
|
||
text-align: left;
|
||
height: 30rpx;
|
||
z-index: 10;
|
||
}
|
||
|
||
/* 九宫格容器 */
|
||
.nine-grid-container {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
grid-template-rows: 1fr 1fr 1fr;
|
||
width: 100%;
|
||
height: 100%;
|
||
box-sizing: border-box;
|
||
background: url('@@:app/static/index_logo.png') no-repeat center center;
|
||
background-size: 50%;
|
||
}
|
||
|
||
.item {
|
||
display: flex;
|
||
align-content: center;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.item-avatar {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background-color: #60AA72;
|
||
border-radius: 50rpx;
|
||
}
|
||
|
||
.avatar-img {
|
||
border-radius: 50%;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
}
|
||
|
||
// ==================== 信息区域样式 ====================
|
||
.info-section {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.info-content {
|
||
width: 90%;
|
||
}
|
||
|
||
.info-title {
|
||
color: #000000;
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
text-align: left;
|
||
}
|
||
|
||
.info-text {
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 20rpx;
|
||
color: #575757;
|
||
text-align: left;
|
||
display: block;
|
||
}
|
||
|
||
// ==================== 网格单元格样式 ====================
|
||
/* 网格单元格 */
|
||
.grid-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
}
|
||
|
||
/* 玩家头像单元格 */
|
||
.player-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 中心单元格(麻将桌) */
|
||
.center-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 加入按钮单元格 */
|
||
.join-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
// ==================== 其他组件样式 ====================
|
||
.item-content {
|
||
height: 540rpx;
|
||
color: white;
|
||
border-radius: 10rpx;
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.mahjong-table {
|
||
width: 100%;
|
||
background-color: #D4D4D4;
|
||
border-radius: 10rpx 10rpx 0 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 麻将桌背景图 */
|
||
.table-bg {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
}
|
||
|
||
/* 玩家头像 */
|
||
.player-avatar {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background-color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 加入按钮 */
|
||
.join-button {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background-color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.join-button:hover {
|
||
transform: scale(1.1);
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.join-text {
|
||
color: black;
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
}
|
||
</style> |