| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- <template>
- <div v-if="visible" id="gate-control-panel" class="sm-panel gate-control-panel">
- <div class="sm-panel-header">
- <span class="panel-title">闸门启闭控制</span>
- <el-icon
- class="close-icon"
- @click="$emit('close')"
- title="关闭"
- >
- <CircleClose />
- </el-icon>
- </div>
- <div class="gate-control-content">
- <!-- 全部控制 -->
- <div class="all-control-section">
- <div class="all-control-label">全部闸门控制</div>
- <div class="all-control-buttons">
- <el-button
- type="primary"
- size="small"
- :disabled="isAnyAnimating"
- @click="openAllGates"
- class="all-control-btn"
- >
- <el-icon><ArrowUp /></el-icon>
- 全部开启
- </el-button>
- <el-button
- type="danger"
- size="small"
- :disabled="isAnyAnimating"
- @click="closeAllGates"
- class="all-control-btn"
- >
- <el-icon><ArrowDown /></el-icon>
- 全部关闭
- </el-button>
- </div>
- </div>
- <!-- 闸门列表(可收缩) -->
- <div class="gates-section">
- <div class="gates-label" @click="toggleGateList">
- <span class="gates-label-text">闸门列表 (共 {{ gates.length }} 个)</span>
- <el-icon
- class="collapse-icon"
- :class="{ collapsed: gateListCollapsed }"
- >
- <ArrowDown />
- </el-icon>
- </div>
- <div v-if="gates.length === 0" style="color: #999; padding: 20px; text-align: center;">
- 暂无闸门数据
- </div>
- <div v-show="!gateListCollapsed" class="gates-list">
- <div
- v-for="gate in gates"
- :key="gate.name"
- class="gate-item"
- >
- <div class="gate-header">
- <span class="gate-name">{{ gate.name }}</span>
- <span class="gate-status" :class="getGateStatusClass(gate.position)">
- {{ getGateStatusText(gate.position) }}
- </span>
- </div>
- <div class="gate-position">
- <span class="gate-position-value">{{ gate.position }}%</span>
- </div>
- <el-slider
- v-model="gate.sliderValue"
- :min="0"
- :max="100"
- :step="1"
- :disabled="gate.isAnimating"
- @change="(value) => handleGateSliderChange(gate, value)"
- class="gate-slider"
- />
- <div class="gate-controls">
- <el-button
- size="small"
- :disabled="gate.isAnimating || gate.position >= 100"
- @click="openGate(gate)"
- icon="ArrowUp"
- />
- <el-button
- size="small"
- :disabled="gate.isAnimating || gate.position <= 0"
- @click="closeGate(gate)"
- icon="ArrowDown"
- />
- <el-button
- size="small"
- :disabled="gate.isAnimating"
- @click="setGatePosition(gate, 50)"
- >50%</el-button>
- </div>
- <div v-if="gate.isAnimating" class="gate-progress">
- <el-progress
- :percentage="gate.animationProgress"
- :stroke-width="4"
- :color="gate.position < gate.targetPosition ? '#67c23a' : '#f56c6c'"
- />
- </div>
- </div>
- </div>
- </div>
- <!-- 快捷操作 -->
- <div class="quick-section">
- <div class="quick-label">全部快捷操作</div>
- <div class="quick-buttons">
- <el-button
- size="small"
- :disabled="isAnyAnimating"
- @click="setAllPositions(25)"
- >25%</el-button>
- <el-button
- size="small"
- :disabled="isAnyAnimating"
- @click="setAllPositions(50)"
- >50%</el-button>
- <el-button
- size="small"
- :disabled="isAnyAnimating"
- @click="setAllPositions(75)"
- >75%</el-button>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import { ref, computed, onMounted, onUnmounted } from 'vue'
- import { CircleClose, ArrowUp, ArrowDown } from '@element-plus/icons-vue'
- export default {
- name: 'GateControl',
- components: {
- CircleClose,
- ArrowUp,
- ArrowDown
- },
- props: {
- visible: {
- type: Boolean,
- default: false
- },
- initialPosition: {
- type: Number,
- default: 0,
- validator: (value) => value >= 0 && value <= 100
- }
- },
- emits: ['close', 'positionChange'],
- setup(props, { emit }) {
- // 将组件实例注册到全局变量
- onMounted(() => {
- window.gateControlInstance = {
- updateGateList: updateGateList
- };
- console.log('🚪 GateControl 组件已挂载,全局实例已注册');
- });
-
- onUnmounted(() => {
- // 清理全局实例
- if (window.gateControlInstance) {
- window.gateControlInstance = null;
- }
- });
- // 闸门列表
- const gates = ref([])
- const gateListCollapsed = ref(false)
- // 切换闸门列表展开/收缩
- const toggleGateList = () => {
- gateListCollapsed.value = !gateListCollapsed.value
- }
- // 检查是否有任何闸门正在动画
- const isAnyAnimating = computed(() => {
- return gates.value.some(gate => gate.isAnimating)
- })
- // 更新闸门列表
- const updateGateList = (gateNames) => {
- console.log('🔄 GateControl - 更新闸门列表:', gateNames)
- console.log('🔄 GateControl - 闸门数量:', gateNames?.length || 0)
- console.log('🔄 GateControl - 当前 gates.value:', gates.value)
-
- if (!gateNames || gateNames.length === 0) {
- console.warn('⚠️ GateControl - 闸门名称列表为空')
- return
- }
-
- gates.value = gateNames.map(name => ({
- name: name,
- position: props.initialPosition,
- sliderValue: props.initialPosition,
- isAnimating: false,
- animationProgress: 0,
- targetPosition: props.initialPosition,
- animationFrame: null
- }))
-
- console.log('✅ GateControl - 闸门列表已更新,共', gates.value.length, '个闸门')
- }
- // 获取闸门状态样式类
- const getGateStatusClass = (position) => {
- if (position === 0) return 'closed'
- if (position === 100) return 'opened'
- return 'opening'
- }
- // 获取闸门状态文本
- const getGateStatusText = (position) => {
- if (position === 0) return '关闭'
- if (position === 100) return '全开'
- return '调节中'
- }
- // 缓动函数
- const easeInOutCubic = (t) => {
- return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
- }
- // 动画单个闸门
- const animateGate = (gate, toPosition) => {
- if (gate.isAnimating) return
- if (gate.position === toPosition) return
- gate.isAnimating = true
- const startPosition = gate.position
- gate.targetPosition = toPosition
- const startTime = performance.now()
- const duration = 3000 // 3秒动画
- const animate = (currentTime) => {
- const elapsed = currentTime - startTime
- const progress = Math.min(elapsed / duration, 1)
-
- const easeProgress = easeInOutCubic(progress)
-
- gate.position = Math.round(
- startPosition + (toPosition - startPosition) * easeProgress
- )
-
- gate.sliderValue = gate.position
- gate.animationProgress = Math.round(progress * 100)
- // 实时通知父组件闸门位置变化(动画过程中持续更新)
- emit('positionChange', {
- gateName: gate.name,
- position: gate.position
- })
- if (progress < 1) {
- gate.animationFrame = requestAnimationFrame(animate)
- } else {
- gate.isAnimating = false
- }
- }
- gate.animationFrame = requestAnimationFrame(animate)
- }
- // 开启单个闸门
- const openGate = (gate) => {
- animateGate(gate, 100)
- }
- // 关闭单个闸门
- const closeGate = (gate) => {
- animateGate(gate, 0)
- }
- // 设置单个闸门位置
- const setGatePosition = (gate, position) => {
- animateGate(gate, position)
- }
- // 处理滑块变化
- const handleGateSliderChange = (gate, value) => {
- animateGate(gate, value)
- }
- // 开启所有闸门
- const openAllGates = () => {
- gates.value.forEach(gate => {
- if (!gate.isAnimating) {
- animateGate(gate, 100)
- }
- })
- }
- // 关闭所有闸门
- const closeAllGates = () => {
- gates.value.forEach(gate => {
- if (!gate.isAnimating) {
- animateGate(gate, 0)
- }
- })
- }
- // 设置所有闸门位置
- const setAllPositions = (position) => {
- gates.value.forEach(gate => {
- if (!gate.isAnimating) {
- animateGate(gate, position)
- }
- })
- }
- // 组件卸载时清理动画
- onUnmounted(() => {
- gates.value.forEach(gate => {
- if (gate.animationFrame) {
- cancelAnimationFrame(gate.animationFrame)
- }
- })
- })
- return {
- gates,
- gateListCollapsed,
- isAnyAnimating,
- updateGateList,
- getGateStatusClass,
- getGateStatusText,
- toggleGateList,
- openGate,
- closeGate,
- setGatePosition,
- handleGateSliderChange,
- openAllGates,
- closeAllGates,
- setAllPositions
- }
- }
- }
- </script>
- <style scoped>
- .gate-control-panel {
- width: 320px;
- max-height: 80vh; /* 使用视口高度的80%作为最大高度 */
- top: 100px;
- right: 20px;
- padding: 15px;
- overflow-y: auto;
- }
- .sm-panel-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 1px solid #eee;
- }
- .panel-title {
- font-size: 16px;
- font-weight: bold;
- color: #303133;
- }
- .close-icon {
- cursor: pointer;
- color: #999;
- font-size: 18px;
- transition: color 0.2s;
- }
- .close-icon:hover {
- color: #666;
- }
- .gate-control-content {
- display: flex;
- flex-direction: column;
- gap: 15px;
- }
- /* 全部控制 */
- .all-control-section {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px;
- background-color: #f8f9fa;
- border-radius: 4px;
- }
- .all-control-label {
- font-size: 14px;
- color: #606266;
- }
- .all-control-buttons {
- display: flex;
- gap: 8px;
- }
- .all-control-btn {
- display: flex;
- align-items: center;
- gap: 4px;
- }
- /* 闸门列表 */
- .gates-section {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .gates-label {
- font-size: 14px;
- color: #606266;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- user-select: none;
- padding: 4px 0;
- }
- .gates-label:hover {
- color: #409eff;
- }
- .collapse-icon {
- font-size: 14px;
- transition: transform 0.25s ease;
- }
- .collapse-icon.collapsed {
- transform: rotate(-90deg);
- }
- .gates-list {
- display: flex;
- flex-direction: column;
- gap: 12px;
- max-height: calc(80vh - 200px); /* 根据面板高度减去其他部分的高度 */
- overflow-y: auto;
- }
- .gate-item {
- padding: 12px;
- background-color: #fafafa;
- border-radius: 6px;
- border: 1px solid #e4e7ed;
- }
- .gate-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- }
- .gate-name {
- font-size: 14px;
- font-weight: 500;
- color: #303133;
- }
- .gate-status {
- font-size: 12px;
- padding: 2px 8px;
- border-radius: 10px;
- }
- .gate-status.closed {
- background-color: #fef0f0;
- color: #f56c6c;
- }
- .gate-status.opened {
- background-color: #f0f9eb;
- color: #67c23a;
- }
- .gate-status.opening {
- background-color: #fdf6ec;
- color: #e6a23c;
- }
- .gate-position {
- text-align: right;
- margin-bottom: 8px;
- }
- .gate-position-value {
- font-size: 18px;
- font-weight: bold;
- color: #409eff;
- }
- .gate-slider {
- margin-bottom: 8px;
- }
- .gate-controls {
- display: flex;
- gap: 6px;
- }
- .gate-controls el-button {
- flex: 1;
- }
- .gate-progress {
- margin-top: 8px;
- }
- /* 快捷操作 */
- .quick-section {
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .quick-label {
- font-size: 14px;
- color: #606266;
- }
- .quick-buttons {
- display: flex;
- gap: 10px;
- }
- .quick-buttons el-button {
- flex: 1;
- }
- </style>
|