congming_huose-apk/common/components/CustomHeader.vue

513 lines
12 KiB
Vue
Raw Permalink 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="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>