513 lines
12 KiB
Vue
513 lines
12 KiB
Vue
<template>
|
||
<view class="custom-header">
|
||
<view class="header-left">
|
||
<text class="menu-icon" @click="toggleSideMenu">☰</text>
|
||
</view>
|
||
<view class="header-center">
|
||
<!-- 仅在有空间数据时渲染头像与标题;无数据时不展示任何占位 -->
|
||
<image v-if="hasSpaces && currentSpacePicture" @click="toggleDropdown" class="avatar" :src="currentSpacePicture" mode="aspectFill"></image>
|
||
<view v-if="hasSpaces && currentSpaceName" class="title-group" @click="toggleDropdown">
|
||
<text class="title">{{currentSpaceName}}</text>
|
||
<view class="subtitle-row">
|
||
<view class="subtitle selectable">
|
||
<text style="color: #F15A04;" v-if="isArmedStatus">{{ getCurrentStatusText() }}</text>
|
||
<text style="color: #39986D;" v-if="isDisarmedStatus">{{ getCurrentStatusText() }}</text>
|
||
<text style="color: #AA57AC;" v-if="isNightStatus">{{ getCurrentStatusText() }}</text>
|
||
<text style="color: red;" v-if="isEmergencyStatus">{{ getCurrentStatusText() }}</text>
|
||
<text class="chevron" :class="{ open: showDropdown }">▼</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 下拉遮罩与面板 -->
|
||
<view v-if="showDropdown" class="dropdown-overlay" @click="closeDropdown"></view>
|
||
<view v-if="showDropdown" class="dropdown-panel" :class="{ 'show': showDropdown }" @click.stop>
|
||
<view class="dropdown-header">
|
||
<text class="close-btn" @click="closeDropdown">✕</text>
|
||
<text class="dropdown-title">{{ getYourSpacesText() }}</text>
|
||
</view>
|
||
<scroll-view scroll-y class="dropdown-list">
|
||
<view class="" style="display: flex;justify-content: space-between;flex-wrap: wrap;">
|
||
<view
|
||
v-for="(space, idx) in spaces"
|
||
:key="idx"
|
||
class="dropdown-item"
|
||
@click.stop="selectSpace(idx)">
|
||
<image class="daimg" :class="{ active: idx === selectedIndex }" :src="getSpaceIcon(space, idx)" mode="aspectFill"></image>
|
||
<image class="xiao" :src="getSpaceName(space)" mode=""></image>
|
||
<view class="nametxt">
|
||
{{ space.name }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'CustomHeader',
|
||
props: {
|
||
title: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
subtitle: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 空间数组,支持字符串或包含 name/title/label 字段的对象
|
||
spaces: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
// 当前选中的空间索引
|
||
selectedIndex: {
|
||
type: Number,
|
||
default: 0
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
showDropdown: false,
|
||
// showDropdown: true
|
||
}
|
||
},
|
||
computed: {
|
||
// 计算当前语言,用于触发更新
|
||
currentLanguage() {
|
||
return this.$i18n && this.$i18n.getCurrentLanguage ? this.$i18n.getCurrentLanguage() : 'zh';
|
||
},
|
||
// 响应式的标题显示
|
||
displayTitle() {
|
||
// 添加语言依赖,确保语言变化时重新计算
|
||
this.currentLanguage;
|
||
return this.title;
|
||
},
|
||
// 响应式的副标题显示
|
||
displaySubtitle() {
|
||
// 添加语言依赖,确保语言变化时重新计算
|
||
this.currentLanguage;
|
||
return this.subtitle;
|
||
},
|
||
// 规范化当前空间的状态:armed/disarmed/night/emergency
|
||
currentNormalizedStatus() {
|
||
this.currentLanguage; // 依赖语言变化,便于触发重算显示
|
||
// 优先使用空间的 status
|
||
if (this.hasSpaces) {
|
||
const spaces = this.spaces || [];
|
||
const index = Math.min(Math.max(0, this.selectedIndex || 0), spaces.length - 1);
|
||
const item = spaces[index];
|
||
let rawStatus = 'disarmed';
|
||
if (item && item.status !== undefined && item.status !== null) {
|
||
rawStatus = String(item.status).toLowerCase();
|
||
}
|
||
switch (rawStatus) {
|
||
case '1':
|
||
case 'armed':
|
||
return 'armed';
|
||
case '2':
|
||
case 'disarmed':
|
||
return 'disarmed';
|
||
case '3':
|
||
case 'night':
|
||
case 'armed_night':
|
||
return 'night';
|
||
case '4':
|
||
case 'emergency':
|
||
case 'alarm':
|
||
return 'emergency';
|
||
default:
|
||
return 'disarmed';
|
||
}
|
||
}
|
||
// 无空间数据时,兼容旧逻辑:从标题文案回推状态
|
||
const title = this.displayTitle;
|
||
if (title === '布防' || title === 'Armed' || title === '警備中' || title === 'Вооружен') return 'armed';
|
||
if (title === '撤防' || title === 'Disarmed' || title === '解除済み' || title === 'Разоружен') return 'disarmed';
|
||
if (title === '夜间' || title === 'Night' || title === '夜間' || title === 'Ночь') return 'night';
|
||
if (title === '紧急' || title === 'Emergency' || title === '緊急' || title === 'Экстренный') return 'emergency';
|
||
return 'disarmed';
|
||
},
|
||
// 状态判断的computed属性
|
||
isArmedStatus() {
|
||
this.currentLanguage; // 添加语言依赖
|
||
return this.currentNormalizedStatus === 'armed';
|
||
},
|
||
isDisarmedStatus() {
|
||
this.currentLanguage; // 添加语言依赖
|
||
return this.currentNormalizedStatus === 'disarmed';
|
||
},
|
||
isNightStatus() {
|
||
this.currentLanguage; // 添加语言依赖
|
||
return this.currentNormalizedStatus === 'night';
|
||
},
|
||
isEmergencyStatus() {
|
||
this.currentLanguage; // 添加语言依赖
|
||
return this.currentNormalizedStatus === 'emergency';
|
||
},
|
||
// 是否有空间可选
|
||
hasSpaces() {
|
||
return Array.isArray(this.spaces) && this.spaces.length > 0;
|
||
},
|
||
// 仅用于 下拉面板 的显示名称数组
|
||
spaceNames() {
|
||
if (!this.hasSpaces) return [];
|
||
return this.spaces.map(item => typeof item === 'string' ? item : (item.name || item.title || item.label || ''))
|
||
},
|
||
currentSpaceName() {
|
||
if (!this.hasSpaces) return this.displaySubtitle;
|
||
const index = Math.min(Math.max(0, this.selectedIndex || 0), this.spaceNames.length - 1);
|
||
return this.spaceNames[index] || this.displaySubtitle;
|
||
},
|
||
currentSpacePicture() {
|
||
if (!this.hasSpaces) return '';
|
||
const spaces = this.spaces || [];
|
||
const index = Math.min(Math.max(0, this.selectedIndex || 0), spaces.length - 1);
|
||
const item = spaces[index];
|
||
return item && (item.picture || item.avatar || item.icon || '');
|
||
}
|
||
},
|
||
watch: {
|
||
// 监听语言变化
|
||
currentLanguage() {
|
||
console.log('CustomHeader语言变化:', this.currentLanguage);
|
||
this.$forceUpdate();
|
||
}
|
||
},
|
||
mounted() {
|
||
// 监听语言变化事件
|
||
uni.$on('languageChanged', this.handleLanguageChange);
|
||
},
|
||
beforeDestroy() {
|
||
// 移除事件监听
|
||
uni.$off('languageChanged', this.handleLanguageChange);
|
||
},
|
||
methods: {
|
||
handleLanguageChange(lang) {
|
||
console.log('CustomHeader语言切换事件:', lang);
|
||
this.$forceUpdate();
|
||
},
|
||
toggleSideMenu() {
|
||
console.log('CustomHeader: 点击了菜单按钮');
|
||
this.$emit('toggle-side-menu');
|
||
},
|
||
toggleDropdown() {
|
||
this.showDropdown = !this.showDropdown;
|
||
},
|
||
closeDropdown() {
|
||
this.showDropdown = false;
|
||
},
|
||
selectSpace(index) {
|
||
const rawItem = this.spaces[index];
|
||
const name = typeof rawItem === 'string' ? rawItem : (rawItem && (rawItem.name || rawItem.title || rawItem.label));
|
||
this.$emit('space-change', index, rawItem, name);
|
||
this.closeDropdown();
|
||
},
|
||
// 判断状态赋值图标
|
||
getSpaceName(space) {
|
||
if (space.status == 1) {
|
||
return 'https://api.ccttiot.com/smartmeter/img/static/uV9wKuL8c1I4RsSf1NeM'
|
||
} else if (space.status == 2) {
|
||
return 'https://api.ccttiot.com/smartmeter/img/static/umIdhp0jue9C13B6gIBu'
|
||
} else if (space.status == 3) {
|
||
return 'https://api.ccttiot.com/smartmeter/img/static/uGQQVRWolprdT8oMTl66'
|
||
} else if (space.status == 4) {
|
||
return 'https://api.ccttiot.com/smartmeter/img/static/uvdoPqJibC01UQZ2S0bN'
|
||
}
|
||
},
|
||
// 标题多语言化
|
||
getYourSpacesText() {
|
||
// 添加容错处理
|
||
const t = this.$i18n && this.$i18n.t ? this.$i18n.t.bind(this.$i18n) : (key) => key;
|
||
return t('yourSpaces') || '您的空间';
|
||
},
|
||
getCurrentStatusText() {
|
||
// 添加容错处理
|
||
const t = this.$i18n && this.$i18n.t ? this.$i18n.t.bind(this.$i18n) : (key) => key;
|
||
// 根据当前状态返回对应的多语言文本
|
||
if (this.isArmedStatus) {
|
||
return t('statusArmed') || '布防';
|
||
} else if (this.isDisarmedStatus) {
|
||
return t('statusDisarmed') || '撤防';
|
||
} else if (this.isNightStatus) {
|
||
return t('statusNight') || '夜间';
|
||
} else if (this.isEmergencyStatus) {
|
||
return t('statusEmergency') || '紧急';
|
||
}
|
||
// 默认返回原始标题
|
||
return this.displayTitle;
|
||
},
|
||
getSpaceIcon(space, index) {
|
||
// 如果有自定义图标,使用自定义图标
|
||
if (space && (space.picture || space.avatar || space.icon)) {
|
||
return space.picture || space.avatar || space.icon
|
||
}
|
||
// 返回空字符串,让CSS处理默认样式
|
||
return ''
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.xiao{
|
||
width: 76rpx;
|
||
height: 76rpx;
|
||
position: absolute;
|
||
left: 40rpx;
|
||
bottom: 80rpx;
|
||
}
|
||
.daimg{
|
||
width: 260rpx;
|
||
height: 260rpx;
|
||
border-radius: 50%;
|
||
}
|
||
.custom-header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx 24rpx;
|
||
background: #fff;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
padding-top: 100rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.header-left {
|
||
margin-right: 220rpx;
|
||
}
|
||
|
||
.menu-icon {
|
||
font-size: 36rpx;
|
||
color: #333;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.header-center {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.avatar {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background: #e0e0e0;
|
||
border-radius: 50%;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.title-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.title {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 24rpx;
|
||
color: #4caf50;
|
||
line-height: 1.2;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.subtitle-row {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.selectable {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
color: #4caf50;
|
||
}
|
||
|
||
.chevron {
|
||
margin-left: 8rpx;
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
.chevron.open {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
/* 下拉面板与遮罩 */
|
||
.dropdown-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.3);
|
||
z-index: 999;
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
.dropdown-panel {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: #fff;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
box-shadow: 0 -8rpx 32rpx rgba(0,0,0,0.15);
|
||
z-index: 1000;
|
||
height: 84vh;
|
||
transform: translateY(100%);
|
||
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||
}
|
||
|
||
.dropdown-panel.show {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.dropdown-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 32rpx 32rpx 24rpx;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.close-btn {
|
||
font-size: 32rpx;
|
||
color: #666;
|
||
font-weight: bold;
|
||
line-height: 1;
|
||
}
|
||
|
||
.dropdown-title {
|
||
font-size: 32rpx;
|
||
color: #000;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.dropdown-list {
|
||
height: 80vh;
|
||
padding: 0;
|
||
padding-bottom: 100rpx;
|
||
overflow: scroll;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
width: 100%;
|
||
}
|
||
|
||
.dropdown-item {
|
||
padding: 32rpx;
|
||
margin: 16rpx 24rpx 16rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
transition: all 0.2s ease;
|
||
width: 260rpx;
|
||
height: 320rpx;
|
||
position: relative;
|
||
text-align: center;
|
||
}
|
||
.nametxt{
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-size: 32rpx;
|
||
color: #3D3D3D;
|
||
}
|
||
.dropdown-item:last-child {
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.daimg.active {
|
||
border: 6rpx solid #000;
|
||
}
|
||
|
||
.item-left {
|
||
margin-right: 24rpx;
|
||
}
|
||
|
||
.space-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
background: #e8e8e8;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.default-icon {
|
||
background: #f0f0f0;
|
||
}
|
||
|
||
.default-icon-text {
|
||
font-size: 32rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.item-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.item-text {
|
||
font-size: 32rpx;
|
||
color: #000;
|
||
font-weight: 500;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.item-status {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 24rpx;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.status-disarmed {
|
||
color: #4caf50; /* 绿色 - 撤防 */
|
||
}
|
||
|
||
.status-armed {
|
||
color: #ff9800; /* 橙色 - 布防 */
|
||
}
|
||
|
||
.status-night {
|
||
color: #2196f3; /* 蓝色 - 夜间 */
|
||
}
|
||
|
||
.status-emergency {
|
||
color: #f44336; /* 红色 - 紧急 */
|
||
}
|
||
|
||
.status-icon {
|
||
font-size: 20rpx;
|
||
color: #4caf50;
|
||
}
|
||
|
||
.check {
|
||
font-size: 32rpx;
|
||
color: #000;
|
||
font-weight: bold;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style> |