mahjong_group/components/index/MahjongCard.vue
2025-09-11 03:44:13 +08:00

417 lines
9.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>