first commit

This commit is contained in:
2026-08-26 14:25:17 +08:00
commit 06debfd1fc
10595 changed files with 1836924 additions and 0 deletions
@@ -0,0 +1,172 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak style="padding-bottom: 70px;">
<van-nav-bar title="代表阅览" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-sticky offset-top="46px">
<van-search
v-model="pageForm.searchKeyword"
:show-action="false"
:reverse-color="false"
input-align="left"
placeholder="请输入姓名/工号搜索"
@search="doSearch"
></van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.sessionId" :options="sessionOptions"
@change="sessionChange"></van-dropdown-item>
<van-dropdown-item
v-if="pageForm.sessionId"
v-model="pageForm.delegationId"
:options="delegationOptions"
@change="doSearch"
></van-dropdown-item>
<!-- <van-dropdown-item ref="moreDropDownItemOption" title="更多查询" multiple :options="moreOptions"-->
<!-- @change="moreOptionChange">-->
<!-- <van-button type="primary" class="margin-top20 width100" @click="$refs.moreDropDownItemOption.toggle()">-->
<!-- 查询-->
<!-- </van-button>-->
<!-- </van-dropdown-item>-->
</van-dropdown-menu>
</van-sticky>
<div class="export-buttons" style="position: fixed; bottom: 0; left: 0; right: 0; padding: 10px; display: flex; gap: 10px; background: white; border-top: 1px solid #eee; z-index: 1000; box-shadow: 0 -2px 8px rgba(0,0,0,0.1);">
<van-button type="primary" size="medium" @click="exportXlsx" style="flex: 1;">
<i class="fa fa-file-excel-o"></i> 导出Excel
</van-button>
<van-button type="info" size="medium" @click="exportDocx" style="flex: 1;">
<i class="fa fa-file-word-o"></i> 导出Word
</van-button>
</div>
<table-list api="/platform/teacherCongress/delegate/read/pageData" :page_form.sync="pageForm" json
@ready="onReady"
ref="tableListRef"
style="padding-bottom: 65px">
<template v-slot="{index,row}">
<table-column label="代表姓名">{{row.userName}}</table-column>
<table-column label="代表工号">{{row.loginName}}</table-column>
<table-column label="代表团">{{delegationOptions.find(v=>v.id===row.delegationId)?.name}}</table-column>
<table-column label="代表身份">
{{moreOptions.find(v=>v.title==='代表身份')?.options.find(v=>v.id===row.roleId)?.name}}
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
</template>
</table-list>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: null,
sessionId: null,
delegationId: null,
roleId: null
},
sessionOptions: [],
delegationOptions: [],
roleOptions: [],
moreOptions: []
}
},
methods: {
historyBack,
moreOptionChange(options) {
this.moreOptions = options
this.moreOptions.forEach((item) => {
const checkedItems = item.options.filter((obj) => obj.checked)
if (item.title === "代表身份") {
this.$set(
this.pageForm,
"roleIds",
checkedItems.map((v) => v.id)
)
}
})
this.doSearch()
},
sessionChange() {
this.pageForm.delegationId = null
if (this.pageForm.sessionId) {
this.listDelegation()
} else {
this.delegationOptions = [
{
text: "全部代表团",
value: null
}
]
}
this.doSearch()
},
listSession() {
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
if (res.code === 0) {
this.sessionOptions = res.data.map((v) => ({text: v.fullName, value: v.id}))
this.pageForm.sessionId = res.data[0].id
this.sessionChange()
}
})
},
listDelegation() {
this.$axios.post("/platform/proposal/common/listDelegation", {sessionId: this.pageForm.sessionId}).then((res) => {
if (res.code === 0) {
this.delegationOptions = [
{
text: "全部代表团",
value: null
}
].concat(res.data.map((v) => ({...v, text: v.name, value: v.id})))
}
})
},
listRole() {
this.$axios.post("/platform/teacherCongress/common/listDelegateRole").then((res) => {
if (res.code === 0) {
this.moreOptions.push({
title: "代表身份",
options: res.data.map((v) => ({...v, text: v.name}))
})
}
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
onReady() {
this.listRole()
this.listSession()
},
exportXlsx() {
this.$downLoad("/platform/teacherCongress/delegate/read/exportXlsx", this.pageForm)
},
exportDocx() {
this.$downLoad("/platform/teacherCongress/delegate/read/exportDocx", this.pageForm)
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,908 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
*{
box-sizing: border-box;
}
.mobile-seat-container {
background: #f7f8fa;
min-height: 100vh;
padding-bottom: 160px;
}
.mobile-header {
background: white;
padding: 16px;
border-bottom: 1px solid #ebedf0;
/*position: sticky;*/
/*top: 0;*/
z-index: 10;
}
.mobile-header .meeting-title {
font-size: 18px;
font-weight: 600;
color: #323233;
margin-bottom: 4px;
}
.mobile-header .meeting-time {
font-size: 14px;
color: #969799;
}
.mobile-stage {
text-align: center;
padding: 8px;
background: transparent;
margin: 16px;
border-radius: 16px;
position: relative;
overflow: hidden;
}
.stage-svg {
width: 100%;
height: 80px;
margin-bottom: 8px;
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.15));
}
.stage-text {
font-size: 18px;
font-weight: 700;
color: #1565c0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
background: linear-gradient(45deg, #1976d2, #42a5f5);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-top: -4px;
}
.mobile-legend {
display: flex;
justify-content: space-around;
padding: 12px 16px;
background: white;
margin: 0 16px 16px;
border-radius: 8px;
font-size: 12px;
}
.mobile-legend-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.mobile-legend-seat {
width: 20px;
height: 20px;
}
.mobile-seat-area {
padding: 0;
}
.zoom-controls {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
padding: 8px 16px;
margin-bottom: 8px;
/*background: rgba(128, 128, 128, 0.1);*/
border-radius: 8px;
}
.zoom-controls .van-button {
background: rgba(128, 128, 128, 0.2) !important;
border: 1px solid rgba(128, 128, 128, 0.3) !important;
color: #666 !important;
font-size: 11px;
padding: 2px 6px !important;
height: 24px !important;
min-width: auto !important;
}
.zoom-controls .van-button:disabled {
background: rgba(128, 128, 128, 0.1) !important;
color: #ccc !important;
}
.zoom-level {
display: flex;
align-items: center;
gap: 3px;
font-size: 10px;
font-weight: 500;
color: #666;
padding: 3px 6px;
background: rgba(128, 128, 128, 0.15);
border-radius: 6px;
min-width: 45px;
justify-content: center;
}
.seat-grid-container {
width: 100%;
min-height: 300px;
position: relative;
padding: 16px;
}
.mobile-seat-grid {
display: grid;
/*grid-template-columns: repeat(10, 1fr);*/
gap: 4px;
background: white;
padding: 12px;
border-radius: 12px;
width: 100%;
user-select: none;
-webkit-user-select: none;
overflow: auto;
max-height: 60vh;
-webkit-overflow-scrolling: touch;
/* 隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 和 Edge */
}
/* 隐藏 Webkit 浏览器的滚动条 */
.mobile-seat-grid::-webkit-scrollbar {
display: none;
}
.mobile-seat {
width: 32px;
height: 32px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
touch-action: manipulation;
transform-origin: center;
}
.mobile-seat-icon {
width: 24px;
height: 24px;
margin-bottom: 1px;
}
.mobile-seat-number {
font-size: 8px;
font-weight: 600;
text-align: center;
line-height: 1;
}
/* 座位状态样式 */
.mobile-seat.available .seat-back {
fill: #f8f9fa;
stroke: #dee2e6;
stroke-width: 1;
}
.mobile-seat.available .seat-cushion {
fill: #ffffff;
stroke: #dee2e6;
stroke-width: 1;
}
.mobile-seat.available .seat-armrest {
fill: #e9ecef;
stroke: #dee2e6;
stroke-width: 1;
}
.mobile-seat.available .mobile-seat-number {
color: #666;
}
.mobile-seat.available:active .seat-back {
fill: #e3f2fd;
stroke: #1867b0;
}
.mobile-seat.available:active .seat-cushion {
fill: #f0f8ff;
stroke: #1867b0;
}
.mobile-seat.available:active .seat-armrest {
fill: #e3f2fd;
stroke: #1867b0;
}
.mobile-seat.occupied .seat-back {
fill: #adb5bd;
stroke: #6c757d;
stroke-width: 1;
}
.mobile-seat.occupied .seat-cushion {
fill: #d0d0d0;
stroke: #6c757d;
stroke-width: 1;
}
.mobile-seat.occupied .seat-armrest {
fill: #adb5bd;
stroke: #6c757d;
stroke-width: 1;
}
.mobile-seat.occupied .mobile-seat-number {
color: #666;
}
.mobile-seat.occupied {
cursor: not-allowed;
}
.mobile-seat.selected .seat-back {
fill: #0d47a1;
stroke: #1867b0;
stroke-width: 2;
}
.mobile-seat.selected .seat-cushion {
fill: #1867b0;
stroke: #0d47a1;
stroke-width: 2;
}
.mobile-seat.selected .seat-armrest {
fill: #0d47a1;
stroke: #1867b0;
stroke-width: 2;
}
.mobile-seat.selected .mobile-seat-number {
font-weight: 700;
}
.mobile-seat.my-seat .seat-back {
fill: #c62828;
stroke: #ff3742;
stroke-width: 2;
}
.mobile-seat.my-seat .seat-cushion {
fill: #ff4757;
stroke: #c62828;
stroke-width: 2;
}
.mobile-seat.my-seat .seat-armrest {
fill: #c62828;
stroke: #ff3742;
stroke-width: 2;
}
.mobile-seat.my-seat .mobile-seat-number {
font-weight: 700;
}
.mobile-seat.disabled .seat-back {
fill: #f5f5f5;
stroke: #cccccc;
stroke-width: 1;
}
.mobile-seat.disabled .seat-cushion {
fill: #f0f0f0;
stroke: #cccccc;
stroke-width: 1;
}
.mobile-seat.disabled .seat-armrest {
fill: #f5f5f5;
stroke: #cccccc;
stroke-width: 1;
}
.mobile-seat.disabled .mobile-seat-number {
color: #999;
}
.mobile-seat.disabled {
cursor: not-allowed;
opacity: 0.6;
}
.mobile-bottom-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
border-top: 1px solid #ebedf0;
padding: 16px;
z-index: 200;
}
.mobile-selected-info {
background: #f0f8ff;
border: 1px solid #1989fa;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
font-size: 14px;
}
.mobile-selected-info .seat-info {
color: #1989fa;
font-weight: 600;
}
.mobile-my-seat-info {
background: #fff2f0;
border: 1px solid #ff4757;
border-radius: 8px;
padding: 12px;
margin-top: 16px;
font-size: 14px;
}
.mobile-my-seat-info .seat-info {
color: #ff4757;
font-weight: 600;
}
.mobile-loading {
text-align: center;
padding: 60px 20px;
color: #969799;
}
/* 会议选择弹窗样式 */
.meeting-modal-content {
max-height: 70vh;
overflow-y: auto;
}
.meeting-modal-content .van-nav-bar__text {
display: none;
}
.meeting-item {
border: 1px solid #ebedf0;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
background: white;
transition: all 0.2s ease;
}
.meeting-item:active {
background: #f0f8ff;
border-color: #1989fa;
}
.meeting-item.selected {
background: #f0f8ff;
border-color: #1989fa;
}
.meeting-name {
font-size: 16px;
font-weight: 600;
color: #323233;
margin-bottom: 8px;
}
.meeting-info {
font-size: 14px;
color: #646566;
margin-bottom: 4px;
}
.meeting-time {
font-size: 12px;
color: #969799;
}
@media (max-width: 375px) {
.mobile-seat-grid {
grid-template-columns: repeat(8, 28px);
gap: 3px;
padding: 10px;
}
.mobile-seat {
width: 28px;
height: 28px;
}
.mobile-seat-icon {
width: 20px;
height: 20px;
}
.mobile-seat-number {
font-size: 8px;
}
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="会议选座" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<!-- 会议选择弹窗 -->
<van-popup v-model="showMeetingModal" position="bottom" :close-on-click-overlay="false">
<div class="meeting-modal-content">
<van-nav-bar title="选择会议"></van-nav-bar>
<div style="padding: 16px;">
<div v-for="meeting in meetings" :key="meeting.id"
:class="['meeting-item', { selected: selectedMeetingId === meeting.id }]"
@click="selectMeeting(meeting)">
<div class="meeting-name">{{ meeting.name }}</div>
<div class="meeting-info">地点:{{ meeting.address }}</div>
<div class="meeting-time">时间:{{ meeting.startTime }} 至 {{ meeting.endTime }}</div>
</div>
<div style="display: flex; gap: 22px">
<van-button block @click="showMeetingModal = false" style="margin-top: 16px;">
取消选择
</van-button>
<van-button block type="info" :disabled="!selectedMeetingId"
@click="confirmMeetingSelection" style="margin-top: 16px;">
确认选择
</van-button>
</div>
</div>
</div>
</van-popup>
<div class="mobile-seat-container">
<!-- 头部信息 -->
<div class="mobile-header" v-if="meeting">
<div class="meeting-title">{{ meeting.name }}</div>
<div class="meeting-time">{{ meeting.startTime }} 至 {{ meeting.endTime }}</div>
</div>
<!-- 主席台 -->
<div class="mobile-stage">
<svg class="stage-svg" viewBox="0 0 320 100" xmlns="http://www.w3.org/2000/svg">
<!-- 背景装饰 -->
<rect x="0" y="0" width="320" height="100" fill="url(#bgGradient)" rx="12" ry="12"/>
<!-- 主舞台基座 -->
<ellipse cx="160" cy="75" rx="140" ry="15" fill="url(#baseGradient)" opacity="0.8"/>
<!-- 主舞台平台 -->
<path d="M 30 45 Q 30 35 40 35 L 280 35 Q 290 35 290 45 L 285 65 Q 285 70 280 70 L 40 70 Q 35 70 35 65 Z"
fill="url(#stageGradient)" stroke="url(#stageStroke)" stroke-width="2"/>
<!-- 台阶层次 -->
<path d="M 45 50 Q 45 45 50 45 L 270 45 Q 275 45 275 50 L 272 62 Q 272 65 270 65 L 50 65 Q 47 65 47 62 Z"
fill="url(#stepGradient)" stroke="#1976d2" stroke-width="1"/>
<!-- 装饰花纹 -->
<path d="M 60 52 Q 65 50 70 52 Q 75 54 80 52" stroke="#ffffff" stroke-width="1.5" fill="none" opacity="0.7"/>
<path d="M 240 52 Q 245 50 250 52 Q 255 54 260 52" stroke="#ffffff" stroke-width="1.5" fill="none" opacity="0.7"/>
<!-- 中央装饰 -->
<circle cx="160" cy="52" r="8" fill="url(#centerGradient)" stroke="#ffffff" stroke-width="1" opacity="0.9"/>
<circle cx="160" cy="52" r="4" fill="#ffd700" opacity="0.8"/>
<!-- 聚光灯光束 -->
<path d="M 80 20 L 85 35 L 75 35 Z" fill="url(#lightGradient)" opacity="0.6"/>
<path d="M 160 15 L 167 35 L 153 35 Z" fill="url(#lightGradient)" opacity="0.8"/>
<path d="M 240 20 L 245 35 L 235 35 Z" fill="url(#lightGradient)" opacity="0.6"/>
<!-- 聚光灯 -->
<circle cx="80" cy="18" r="4" fill="#ffd700" stroke="#ffb300" stroke-width="1"/>
<circle cx="160" cy="13" r="5" fill="#ffd700" stroke="#ffb300" stroke-width="1"/>
<circle cx="240" cy="18" r="4" fill="#ffd700" stroke="#ffb300" stroke-width="1"/>
<!-- 星光效果 -->
<g opacity="0.8">
<path d="M 100 25 L 102 30 L 107 30 L 103 33 L 105 38 L 100 35 L 95 38 L 97 33 L 93 30 L 98 30 Z" fill="#ffd700"/>
<path d="M 220 28 L 222 32 L 226 32 L 223 35 L 225 39 L 220 36 L 215 39 L 217 35 L 214 32 L 218 32 Z" fill="#ffd700"/>
</g>
<!-- 渐变定义 -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#e3f2fd;stop-opacity:1" />
<stop offset="100%" style="stop-color:#bbdefb;stop-opacity:1" />
</linearGradient>
<linearGradient id="stageGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#1e88e5;stop-opacity:1" />
<stop offset="30%" style="stop-color:#1976d2;stop-opacity:1" />
<stop offset="70%" style="stop-color:#1565c0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0d47a1;stop-opacity:1" />
</linearGradient>
<linearGradient id="stageStroke" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#42a5f5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1565c0;stop-opacity:1" />
</linearGradient>
<linearGradient id="stepGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#42a5f5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1976d2;stop-opacity:1" />
</linearGradient>
<linearGradient id="baseGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#90caf9;stop-opacity:0.6" />
<stop offset="100%" style="stop-color:#1976d2;stop-opacity:0.8" />
</linearGradient>
<radialGradient id="centerGradient" cx="50%" cy="50%" r="50%">
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1976d2;stop-opacity:1" />
</radialGradient>
<linearGradient id="lightGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#fff9c4;stop-opacity:0.9" />
<stop offset="100%" style="stop-color:#fff59d;stop-opacity:0.3" />
</linearGradient>
</defs>
</svg>
<div class="stage-text">主席台</div>
</div>
<!-- 图例 -->
<div class="mobile-legend">
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#f8f9fa" stroke="#dee2e6"
stroke-width="1"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#ffffff" stroke="#dee2e6"
stroke-width="1"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#e9ecef" stroke="#dee2e6"
stroke-width="1"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#e9ecef" stroke="#dee2e6"
stroke-width="1"/>
</svg>
<span>可选</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#adb5bd" stroke="#6c757d"
stroke-width="1"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#d0d0d0" stroke="#6c757d"
stroke-width="1"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#adb5bd" stroke="#6c757d"
stroke-width="1"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#adb5bd" stroke="#6c757d"
stroke-width="1"/>
</svg>
<span>占用</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#0d47a1" stroke="#1867b0"
stroke-width="2"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#1867b0" stroke="#0d47a1"
stroke-width="2"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#0d47a1" stroke="#1867b0"
stroke-width="2"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#0d47a1" stroke="#1867b0"
stroke-width="2"/>
</svg>
<span>选中</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#c62828" stroke="#ff3742"
stroke-width="2"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#ff4757" stroke="#c62828"
stroke-width="2"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#c62828" stroke="#ff3742"
stroke-width="2"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#c62828" stroke="#ff3742"
stroke-width="2"/>
</svg>
<span>我的</span>
</div>
<div class="mobile-legend-item">
<svg class="mobile-legend-seat" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" fill="#f5f5f5" stroke="#cccccc"
stroke-width="1"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" fill="#f0f0f0" stroke="#cccccc"
stroke-width="1"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" fill="#f5f5f5" stroke="#cccccc"
stroke-width="1"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" fill="#f5f5f5" stroke="#cccccc"
stroke-width="1"/>
</svg>
<span>禁用</span>
</div>
</div>
<!-- 座位区域 -->
<div class="mobile-seat-area">
<van-loading v-if="loading" type="spinner" color="#1989fa" class="mobile-loading">
正在加载座位信息...
</van-loading>
<div v-else class="seat-grid-container">
<div class="mobile-seat-grid" id="seatGrid" :style="gridStyle">
<div v-for="seat in seats" :key="seat.id"
:class="getSeatClass(seat)"
class="mobile-seat"
@click="selectSeat(seat)">
<svg class="mobile-seat-icon" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="5" width="34" height="20" rx="3" ry="3" class="seat-back"/>
<rect x="5" y="22" width="40" height="18" rx="4" ry="4" class="seat-cushion"/>
<rect x="2" y="20" width="6" height="15" rx="2" ry="2" class="seat-armrest"/>
<rect x="42" y="20" width="6" height="15" rx="2" ry="2" class="seat-armrest"/>
</svg>
<span class="mobile-seat-number">{{ seat.rowNumber }}-{{ seat.colNumber }}</span>
</div>
</div>
</div>
</div>
<!-- 缩放控制按钮 -->
<div class="zoom-controls">
<van-button type="default" size="mini" @click="zoomOut" :disabled="currentScale <= 0.5">
<van-icon name="minus" size="12" />
缩小
</van-button>
<div class="zoom-level">
<van-icon name="search" size="12" />
{{ Math.round(currentScale * 100) }}%
</div>
<van-button type="default" size="mini" @click="resetZoom">
<van-icon name="replay" size="12" />
重置
</van-button>
<van-button type="default" size="mini" @click="zoomIn" :disabled="currentScale >= 3">
<van-icon name="plus" size="12" />
放大
</van-button>
</div>
<!-- 我的座位信息 -->
<div v-if="myCurrentSeat" class="mobile-my-seat-info" style="margin: 16px;">
<div style="margin-bottom: 4px;">我的座位</div>
<div class="seat-info">{{ myCurrentSeat.rowNumber }}-{{ myCurrentSeat.colNumber }} (第{{ myCurrentSeat.rowNumber }}排第{{
myCurrentSeat.colNumber }}列)
</div>
</div>
</div>
<!-- 底部操作面板 -->
<div class="mobile-bottom-panel">
<div v-if="selectedSeat" class="mobile-selected-info">
<div style="margin-bottom: 4px;">已选择座位</div>
<div class="seat-info">{{ selectedSeat.rowNumber }}-{{ selectedSeat.colNumber }} (第{{ selectedSeat.rowNumber }}排第{{
selectedSeat.colNumber }}列)
</div>
</div>
<div style="display: flex; gap: 22px">
<van-button type="info" block @click="showMeetingModal = true">
切换会议
</van-button>
<van-button type="info" block :disabled="!selectedSeat || submitting"
:loading="submitting" @click="confirmSelection">
{{ submitting ? '提交中...' : '确认选座' }}
</van-button>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: '#app',
data() {
return {
meetings: [], // 会议列表
selectedMeetingId: null, // 选中的会议ID
showMeetingModal: true, // 显示会议选择弹窗
meeting: null,
seats: [],
selectedSeat: null,
myCurrentSeat: null,
loading: false,
submitting: false,
currentScale: 1, // 当前缩放比例
seatConfig: {
rows: 8,
cols: 12
},
}
},
mounted() {
this.loadMeetings();
this.$nextTick(() => {
this.initPinchZoom();
});
},
computed: {
gridStyle() {
return {
'grid-template-columns': 'repeat(' + this.seatConfig.cols + ', 1fr)'
}
}
},
methods: {
// 加载会议列表
loadMeetings() {
$.post('/platform/guildHall/meeting/selectList').then(res => {
if (res.code === 0) {
this.meetings = res.data
}
})
},
// 选择会议
selectMeeting(meeting) {
this.selectedMeetingId = meeting.id;
},
// 确认会议选择
confirmMeetingSelection() {
if (!this.selectedMeetingId) {
this.$toast('请选择一个会议');
return;
}
const selectedMeeting = this.meetings.find(m => m.id === this.selectedMeetingId);
if (selectedMeeting) {
this.meeting = selectedMeeting
this.loadSeats();
}
},
// 查询选座情况
async loadSeats() {
if (!this.meeting.id) {
return;
}
this.loading = true;
const {
code,
data,
msg
} = await $.post('/platform/guildHall/selectSeat/loadSeats', {meetingId: this.meeting.id})
if (code === 0) {
this.seats = data;
this.myCurrentSeat = this.seats.find(seat => seat.isMine);
// 根据现有数据计算行列数
const maxRow = Math.max(...this.seats.map(s => parseInt(s.rowNumber)))
const maxCol = Math.max(...this.seats.map(s => parseInt(s.colNumber)))
this.seatConfig.rows = maxRow
this.seatConfig.cols = maxCol
}
this.loading = false
this.showMeetingModal = false;
},
selectSeat(seat) {
if (!seat.enable) {
this.$toast('该座位已被禁用');
return;
}
if (seat.isOccupied && !seat.isMine) {
this.$toast('该座位已被占用');
return;
}
if (seat.isMine) {
this.$toast('这是您当前的座位');
return;
}
this.selectedSeat = seat;
// 添加触觉反馈
if (navigator.vibrate) {
navigator.vibrate(50);
}
},
async confirmSelection() {
if (!this.selectedSeat) {
this.$toast('请先选择座位');
return;
}
this.submitting = true;
const {code, data, msg} = await $.post('/platform/guildHall/selectSeat/doSelect', {
meetingId: this.meeting.id,
rowNumber: this.selectedSeat.rowNumber,
colNumber: this.selectedSeat.colNumber
})
this.submitting = false
if (code === 0) {
this.$toast.success("选座成功")
this.selectedSeat = null;
this.loadSeats();
} else {
this.$toast.fail(msg || '选座失败');
}
},
getSeatClass(seat) {
if (!seat.enable) {
return 'disabled';
}
if (seat.isMine) return 'my-seat';
if (seat === this.selectedSeat) return 'selected';
if (seat.isOccupied) return 'occupied';
return 'available';
},
getSeatTitle(seat) {
if (seat.isMine) return '我的座位';
if (seat === this.selectedSeat) return '当前选择';
if (seat.status === 1) return '已被占用';
return '点击选择此座位';
},
formatTime(time) {
if (!time) return '';
return new Date(time).toLocaleString('zh-CN');
},
// 放大
zoomIn() {
if (this.currentScale < 3) {
this.currentScale = Math.min(3, this.currentScale + 0.2);
this.applyZoom();
}
},
// 缩小
zoomOut() {
if (this.currentScale > 0.5) {
this.currentScale = Math.max(0.5, this.currentScale - 0.2);
this.applyZoom();
}
},
// 重置缩放
resetZoom() {
this.currentScale = 1;
this.applyZoom();
},
// 应用缩放
applyZoom() {
const seats = document.querySelectorAll('.mobile-seat');
const seatGrid = document.querySelector('.mobile-seat-grid');
seats.forEach(seat => {
seat.style.transform = 'scale(' + this.currentScale + ')';
seat.style.transformOrigin = 'center center';
seat.style.transition = 'transform 0.2s ease';
});
// 动态调整网格间距
if (seatGrid) {
const baseGap = 4;
const adjustedGap = baseGap * this.currentScale * 5;
seatGrid.style.gap = adjustedGap + 'px';
}
},
// 初始化双指缩放功能(已替换为按钮控制)
initPinchZoom() {
// 双指缩放功能已被按钮控制替代,保留此方法以避免错误
console.log('双指缩放功能已替换为按钮控制');
}
}
})
</script>
<!--#
}
#-->