挂牌上锁程序开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

226 lines
4.7 KiB

3 days ago
<template>
<div class="mobile-container">
<div>位置权限{{ locationPermission ? '是' : '否' }}</div>
<div>蓝牙权限{{ bluetoothPermission ? '是' : '否' }}</div>
<div>相机权限{{ cameraPermission ? '是' : '否' }}</div>
<div class="mobile-header">
<el-input v-model="searchText" placeholder="搜索隔离点或任务名称" prefix-icon="Search" clearable class="search-input" />
<el-button type="primary" @click="$emit('refresh')" class="refresh-btn">刷新</el-button>
</div>
<div class="plan-list">
<PlanCard v-for="isolationPlan in filteredPlans" :key="isolationPlan.id" :isolation-plan="isolationPlan"
:expanded-plan="expandedPlans.includes(isolationPlan.id)" :expanded-points="expandedPoints"
:isolation-points="isolationPoints" :locks="locks" :users="users" :current-user-id="currentUserId"
:is-bluetooth-supported="isBluetoothSupported" :plan-id="isolationPlan[0]?.isolationPlanId"
@toggle-plan="togglePlan" @toggle-point="togglePoint" @bind-lock="$emit('bindLock', $event)"
@lock-operation="$emit('lockOperation', $event)" @verify-lock="$emit('verifyLock', $event)"
@unlock-verify="$emit('unlockVerify', $event)" @unlock-operation="$emit('unlockOperation', $event)" />
</div>
</div>
</template>
<script setup>
import { ref, computed, toRefs } from 'vue'
import { Refresh, Search } from '@element-plus/icons-vue'
import PlanCard from './PlanCard.vue'
import { useUserStore } from '@/store/modules/user'
// Props
const props = defineProps({
isolationPlanList: {
type: Object,
required: true
},
isolationPoints: {
type: Array,
default: () => []
},
locks: {
type: Array,
default: () => []
},
users: {
type: Array,
default: () => []
},
currentUserId: {
type: Number,
default: 0
},
isBluetoothSupported: {
type: Boolean,
default: false
}
})
// Emits
const emit = defineEmits([
'refresh',
'bindLock',
'lockOperation',
'unlockOperation',
'verifyLock',
'unlockVerify',
'unlockOperation'
])
// Reactive props
const { isolationPlanList, isolationPoints, locks, users, currentUserId } = toRefs(props)
// 本地状态
const searchText = ref('')
const expandedPlans = ref([])
const expandedPoints = ref([])
// 计算属性
const filteredPlans = computed(() => {
let plans = Object.values(isolationPlanList.value)
plans = plans.filter((plan) => {
return plan.some((item) =>
[item.operatorId, item.verifierId, item.operatorHelperId, item.verifierHelperId].some(
(val) => val === currentUserId.value
)
)
})
if (!searchText.value) return plans
return plans.filter((plan) =>
plan.some((item) =>
[item.isolationPointId, item.isolationPlanName].some((val) =>
val?.toString().toLowerCase().includes(searchText.value.toLowerCase())
)
)
)
})
const togglePlan = (planId) => {
const index = expandedPlans.value.indexOf(planId)
if (index > -1) {
expandedPlans.value.splice(index, 1)
} else {
expandedPlans.value.push(planId)
}
}
const togglePoint = ({ pointId, planId }) => {
const compositeKey = `${planId}_${pointId}`
const index = expandedPoints.value.indexOf(compositeKey)
if (index > -1) {
expandedPoints.value.splice(index, 1)
} else {
expandedPoints.value.push(compositeKey)
}
}
// 暴露方法和数据给父组件
defineExpose({
searchText,
expandedPlans,
expandedPoints,
filteredPlans,
})
</script>
<style scoped>
.mobile-container {
padding: 16px;
background-color: #f5f5f5;
min-height: 100vh;
}
.mb-4 {
margin-bottom: 16px;
}
.mobile-header {
display: flex;
gap: 12px;
margin-bottom: 16px;
align-items: center;
}
.search-input {
flex: 1;
}
.refresh-btn {
flex-shrink: 0;
}
.status-summary {
display: flex;
gap: 12px;
margin-bottom: 16px;
padding: 16px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.summary-item {
flex: 1;
text-align: center;
}
.summary-value {
font-size: 24px;
font-weight: bold;
color: #409eff;
margin-bottom: 4px;
}
.summary-value.locked {
color: #67c23a;
}
.summary-value.unlocked {
color: #f56c6c;
}
.summary-label {
font-size: 14px;
color: #666;
}
.plan-list {
display: flex;
flex-direction: column;
gap: 12px;
}
@media (max-width: 480px) {
.mobile-container {
padding: 12px;
}
.mobile-header {
flex-direction: column;
gap: 8px;
}
.refresh-btn {
width: 100%;
}
.status-summary {
padding: 12px;
}
.summary-value {
font-size: 20px;
}
}
.mobile-container .el-tag {
border-radius: 12px;
}
.mobile-container .el-button {
border-radius: 6px;
font-size: 14px;
}
.mobile-container .el-input {
border-radius: 6px;
}
</style>