congming_huose-apk/common/components/NotificationTab.vue

850 lines
22 KiB
Vue
Raw Normal View History

2025-11-08 11:30:06 +08:00
<template>
<view class="page">
<view class="tabs-wrap">
<view :class="swiperCurrent == index ? 'active tab' : 'tab'" v-for="(item,index) in tabsDisplay"
:key="item.key" @click="onTabClick(index)">
{{item.name}}
</view>
<view class="settings-btn" @click="showNotificationSettings">
</view>
</view>
<!-- 原来的筛选按钮和时间选择弹窗已移除时间筛选功能已集成到tab中 -->
<swiper class="swiper" :current="swiperCurrent" @change="onSwiperChange" :duration="200" :circular="false">
<swiper-item v-for="(tab) in tabsDisplay" :key="tab.key">
<scroll-view class="list" scroll-y @scrolltolower="handqixing" refresher-enabled
@refresherrefresh="onRefresh" :refresher-triggered="isRefreshing" refresher-default-style="black">
<view class="cell" v-for="(n,idx) in listByKey(tab.key)" :key="tab.key + idx">
<view class="content">
<view class="row1">
<image class="small-icon" v-if="n.type == 'delete'"
src="https://api.ccttiot.com/smartmeter/img/static/uzIKbrzVRDbX1TdbALJx"
mode="aspectFill"></image>
<image class="small-icon" v-if="n.type == 'unbind'"
src="https://api.ccttiot.com/smartmeter/img/static/uJ4PSY8O4kde1AdCdf91"
mode="aspectFill"></image>
<image class="small-icon" v-if="n.type == 'bind'"
src="https://api.ccttiot.com/smartmeter/img/static/un9RVkNVxeENkCrtYVug"
mode="aspectFill"></image>
<image class="small-icon" v-if="n.type == 'add'"
src="https://api.ccttiot.com/smartmeter/img/static/uzSrhuScUj743gsVwEqd"
mode="aspectFill"></image>
<image class="small-icon" v-if="n.type == 'edit'"
src="https://api.ccttiot.com/smartmeter/img/static/u42kBohuBIohCoNWYazi"
mode="aspectFill"></image>
<text class="time" :style="{color: n.timeColor}">{{ n.time }}</text>
</view>
<view class="row2">{{ n.titleKey }}</view>
</view>
<image class="thumb thumb-placeholder" :src="n.picture" mode="aspectFill"></image>
</view>
<view class="no-more">{{ $i18n.t('noMore') }}</view>
</scroll-view>
</swiper-item>
</swiper>
<!-- 通知设置弹窗 -->
<view class="settings-modal" v-if="showSettings" @click="hideNotificationSettings">
<view class="settings-content" @click.stop>
<view class="settings-header">
<text class="settings-title">{{ $i18n.t('notificationSettingsTitle') }}</text>
<text class="close-btn" @click="hideNotificationSettings">×</text>
</view>
<scroll-view class="settings-list" scroll-y>
<view class="setting-item" v-for="(category, index) in notificationSettings" :key="index">
<view class="setting-main" @click="toggleCategory(index)">
<view class="setting-info">
<text class="setting-title">{{ $i18n.t(category.titleKey) }}</text>
<view class="setting-desc-row">
<text class="setting-desc">{{ getEnabledSubItemsDesc(category) }}</text>
<text class="arrow" v-if="category.hasSubSettings"></text>
</view>
</view>
<view class="setting-controls">
<switch
:checked="category.enabled"
@change="onMainSwitchChange(index, $event)"
@click.stop
color="#000000"
/>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 子设置弹窗 -->
<view class="sub-settings-modal" v-if="showSubSettings" @click="hideSubSettings">
<view class="sub-settings-content" @click.stop>
<view class="sub-settings-header">
<text class="sub-settings-title">{{ getSubSettingsTitle() }}</text>
<text class="close-btn" @click="hideSubSettings">×</text>
</view>
<scroll-view class="sub-settings-list" scroll-y>
<view class="sub-setting-item" v-for="(subItem, index) in currentCategory.subSettings" :key="index">
<view class="sub-setting-main">
<view class="sub-setting-info">
<text class="sub-setting-title">{{ $i18n.t(subItem.titleKey) }}</text>
</view>
<switch
:checked="subItem.enabled"
@change="onSubSwitchChange(index, $event)"
color="#000000"
/>
</view>
</view>
</scroll-view>
<view class="save-btn" @click="saveSubSettings">
<text class="save-btn-text">{{ $i18n.t('saveSettings') }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'NotificationTab',
data() {
return {
timeindex:0,
createTimeRange:'',
swiperCurrent: 0,
timelist: [
{ key: 'allTime', name: 'allTime' },
{ key: 'oneHour', name: 'oneHour' },
{ key: 'twentyFourHours', name: 'twentyFourHours' },
{ key: 'oneMonth', name: 'oneMonth' }
],
icons: {
security: 'https://api.ccttiot.com/FleUeHa_R3Rr2RsjlZ30f1xqvjR7',
picture: 'https://api.ccttiot.com/smartmeter/img/static/usZCKDHBGZ2XQ5FUy0Al',
malfunctions: 'https://api.ccttiot.com/smartmeter/img/static/usZCKDHBGZ2XQ5FUy0Al',
control: 'https://api.ccttiot.com/smartmeter/img/static/ui8Gz7gVZYFjS8XSDS45',
system: 'https://api.ccttiot.com/smartmeter/img/static/uiRElxpcH7NbtJWIRtf1',
env: 'https://api.ccttiot.com/smartmeter/img/static/uiRElxpcH7NbtJWIRtf1'
},
notifications: [],
isRefreshing: false,
pageNum: 1,
pageSize: 10,
kjid: '',
total: 0,
hasMore: true,
requestInProgress: false,
// 通知设置相关
showSettings: false,
showSubSettings: false,
currentCategoryIndex: -1,
notificationSettings: [
{
titleKey: 'alertTitle',
descKey: 'alertDesc',
enabled: true,
hasSubSettings: true,
subSettings: [
{ titleKey: 'intrusion', enabled: true },
{ titleKey: 'fire', enabled: false },
{ titleKey: 'waterLeak', enabled: false },
{ titleKey: 'serverDisconnected', enabled: false }
]
},
{
titleKey: 'videoWarningTitle',
descKey: 'videoWarningDesc',
enabled: false,
hasSubSettings: false,
subSettings: []
},
{
titleKey: 'faultTitle',
descKey: 'faultDesc',
enabled: true,
hasSubSettings: true,
subSettings: [
{ titleKey: 'connectionLost', enabled: true },
{ titleKey: 'lowBattery', enabled: true },
{ titleKey: 'coverOpen', enabled: false }
]
},
{
titleKey: 'securityStatusChangeTitle',
descKey: 'securityStatusChangeDesc',
enabled: true,
hasSubSettings: true,
subSettings: [
{ titleKey: 'arm', enabled: true },
{ titleKey: 'disarm', enabled: true },
{ titleKey: 'nightMode', enabled: false }
]
},
{
titleKey: 'systemNotificationTitle',
descKey: 'systemNotificationDesc',
enabled: true,
hasSubSettings: true,
subSettings: [
{ titleKey: 'hubUpdate', enabled: true },
{ titleKey: 'maintenance', enabled: true }
]
},
{
titleKey: 'environmentalNotificationTitle',
descKey: 'environmentalNotificationDesc',
enabled: false,
hasSubSettings: true,
subSettings: [
{ titleKey: 'temperature', enabled: false },
{ titleKey: 'humidity', enabled: false },
{ titleKey: 'carbonDioxide', enabled: false }
]
}
]
};
},
computed: {
// 计算当前语言,用于触发更新
currentLanguage() {
return this.$i18n.getCurrentLanguage();
},
// 生成展示用的标签文案(使用时间列表)
tabsDisplay() {
return this.timelist.map((timeItem, index) => ({
key: `time_${index}`,
name: this.$i18n.t(timeItem.name)
}));
},
filteredList() {
// 现在所有tab都显示相同的数据因为时间筛选在API层面处理
return this.notifications;
},
// 当前选中的分类
currentCategory() {
return this.currentCategoryIndex >= 0 ? this.notificationSettings[this.currentCategoryIndex] : null;
}
},
watch: {
// 监听语言变化
currentLanguage() {
console.log('通知页面语言变化:', this.currentLanguage);
// 语言切换时重新加载设置
this.loadNotificationSettings();
}
},
mounted() {
// 监听语言变化事件
uni.$on('languageChanged', this.handleLanguageChange);
// 初始化空间ID并首次拉取列表
this.kjid = uni.getStorageSync('kjid')
// 初始化时设置默认时间筛选(全部)
this.timeindex = 0
this.btntimeindex(0)
this.getlist()
// 监听空间切换事件
uni.$on('spaceChanged', this.handleSpaceChanged)
// 加载通知设置
this.loadNotificationSettings()
},
beforeDestroy() {
// 移除事件监听
uni.$off('languageChanged', this.handleLanguageChange);
uni.$off('spaceChanged', this.handleSpaceChanged)
},
methods: {
// 点击选择时间
btntimeindex(index){
this.timeindex = index
// 计算并打印时间范围
const now = new Date()
if(index === 0){
// 全部 -> 清空
this.createTimeRange = ''
console.log('时间筛选:全部(清空)')
return
}
let start = new Date(now)
switch(index){
case 1: // 1小时
start = new Date(now.getTime() - 1 * 60 * 60 * 1000)
break
case 2: // 24小时
start = new Date(now.getTime() - 24 * 60 * 60 * 1000)
break
case 3: // 1个月
start.setMonth(start.getMonth() - 1)
break
default:
break
}
const startStr = this.formatDate(start)
const endStr = this.formatDate(now)
this.createTimeRange = startStr + ',' + endStr
console.log('时间范围:', { startTime: startStr, endTime: endStr })
},
// 格式化时间为 YYYY-MM-DD HH:mm:ss
formatDate(date){
const pad = (n) => n < 10 ? ('0' + n) : n
const y = date.getFullYear()
const m = pad(date.getMonth() + 1)
const d = pad(date.getDate())
const hh = pad(date.getHours())
const mm = pad(date.getMinutes())
const ss = pad(date.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
},
// 请求通知列表
getlist() {
if (this.requestInProgress) return
this.requestInProgress = true
// 添加调试日志
console.log('请求通知列表:', {
pageNum: this.pageNum,
pageSize: this.pageSize,
spaceId: this.kjid,
createTimeRange: this.createTimeRange
})
this.$http.get(`/bst/notice/list?pageNum=${this.pageNum}&pageSize=${this.pageSize}&spaceId=${this.kjid}&classify=&orderByColumn=createTime&isAsc=desc&createTimeRange=${this.createTimeRange}`).then((res) => {
if (res.code == 200) {
this.total = Number(res.total || 0)
const rows = Array.isArray(res.rows) ? res.rows : []
console.log('原始数据行:', rows)
const mapped = rows.map(r => ({
type: r.classify || r.type || 'system', // 优先使用classify字段兼容type字段
time: r.createTime,
timeColor: this.getTimeColor(r.classify || r.type),
titleKey: r.content || '',
picture: r.picture || ''
}))
console.log('映射后的数据:', mapped)
if (this.pageNum == 1) {
this.notifications = mapped
} else {
this.notifications = this.notifications.concat(mapped)
}
this.hasMore = this.notifications.length < this.total
console.log('当前通知数据:', this.notifications)
} else {
console.error('API请求失败:', res)
}
}).catch(error => {
console.error('请求通知列表失败:', error)
}).finally(() => {
this.requestInProgress = false
if (this.isRefreshing) {
this.isRefreshing = false
}
})
},
// 上拉加载更多
handqixing() {
if (!this.hasMore) return
this.pageNum += 1
this.getlist()
},
// 下拉刷新
onRefresh() {
if (this.isRefreshing || this.requestInProgress) return
this.isRefreshing = true
this.pageNum = 1
this.getlist()
},
onTabChange(index) {
this.current = index
},
// 滑动切换时间标签
onSwiperChange(e) {
console.log(':', e.detail.current)
this.swiperCurrent = e.detail.current
// 设置时间筛选
this.timeindex = e.detail.current
this.btntimeindex(e.detail.current)
// 分栏切换后重新拉取
this.pageNum = 1
// 清空当前数据,避免显示错误的数据
this.notifications = []
this.getlist()
},
// 监听语言变化事件
handleLanguageChange(lang) {
console.log('通知页面语言切换事件:', lang)
},
handleSpaceChanged(payload) {
try {
this.kjid = (payload && payload.kjid) || uni.getStorageSync('kjid')
this.pageNum = 1
this.getlist()
} catch (e) {
console.warn('处理空间切换失败:', e)
}
},
// 点击切换时间标签
onTabClick(index) {
console.log(':', index, '标签信息:', this.tabsDisplay[index])
this.swiperCurrent = index
// 设置时间筛选
this.timeindex = index
this.btntimeindex(index)
// 点击切换分栏时主动刷新当前分类数据
this.pageNum = 1
// 清空当前数据,避免显示错误的数据
this.notifications = []
this.getlist()
},
getTimeColor(classify) {
switch (classify) {
case 'security':
return '#1EC28B'
case 'picture':
return '#007AFF'
case 'malfunctions':
return '#FF3B30'
case 'control':
return '#FF8A00'
case 'system':
return '#8B8E94'
case 'env':
return '#34C759'
default:
return '#8B8E94'
}
},
listByKey(key) {
console.log('listByKey调用:', key, '当前数据:', this.notifications)
// 现在所有tab都显示相同的数据因为时间筛选在API层面处理
console.log('返回所有数据:', this.notifications)
return this.notifications
},
// 通知设置相关方法
showNotificationSettings() {
this.showSettings = true
},
hideNotificationSettings() {
this.showSettings = false
},
hideSubSettings() {
this.showSubSettings = false
this.currentCategoryIndex = -1
},
// 切换主分类开关
onMainSwitchChange(index, event) {
const enabled = event.detail.value
this.notificationSettings[index].enabled = enabled
// 如果关闭主开关,则关闭所有子开关
if (!enabled) {
this.notificationSettings[index].subSettings.forEach(subItem => {
subItem.enabled = false
})
}
// 如果开启主开关,则开启所有子开关
else {
this.notificationSettings[index].subSettings.forEach(subItem => {
subItem.enabled = true
})
}
// 保存设置
this.saveNotificationSettings()
},
// 切换子分类开关
onSubSwitchChange(index, event) {
const enabled = event.detail.value;
this.notificationSettings[this.currentCategoryIndex].subSettings[index].enabled = enabled
// 检查是否所有子开关都关闭了,如果是则关闭主开关
const allSubDisabled = this.notificationSettings[this.currentCategoryIndex].subSettings.every(subItem => !subItem.enabled)
if (allSubDisabled) {
this.notificationSettings[this.currentCategoryIndex].enabled = false
}
// 如果至少有一个子开关开启,则开启主开关
else {
this.notificationSettings[this.currentCategoryIndex].enabled = true
}
},
// 点击分类项
toggleCategory(index) {
if (this.notificationSettings[index].hasSubSettings) {
this.currentCategoryIndex = index
this.showSubSettings = true
}
},
// 保存子设置
saveSubSettings() {
this.saveNotificationSettings();
this.hideSubSettings();
uni.showToast({
title: this.$i18n.t('settingsSaved'),
icon: 'success'
});
},
// 保存通知设置到本地存储
saveNotificationSettings() {
try {
uni.setStorageSync('notificationSettings', this.notificationSettings);
console.log('通知设置已保存:', this.notificationSettings);
} catch (error) {
console.error('保存通知设置失败:', error);
}
},
// 从本地存储加载通知设置
loadNotificationSettings() {
try {
const savedSettings = uni.getStorageSync('notificationSettings');
if (savedSettings && Array.isArray(savedSettings)) {
// 检查是否是新的数据结构包含titleKey
if (savedSettings[0] && savedSettings[0].titleKey) {
this.notificationSettings = savedSettings;
} else {
// 旧数据结构,需要转换
this.convertOldSettingsToNew(savedSettings);
}
console.log('通知设置已加载:', this.notificationSettings);
}
} catch (error) {
console.error('加载通知设置失败:', error);
}
},
// 转换旧数据结构到新结构
convertOldSettingsToNew(oldSettings) {
// 如果旧数据存在,尝试转换
// 这里可以根据实际情况进行转换,或者直接使用默认设置
console.log('检测到旧数据结构,使用默认设置');
// 清除旧的本地存储数据
uni.removeStorageSync('notificationSettings');
// 保存新的默认设置
this.saveNotificationSettings();
},
// 获取已开启的子项描述
getEnabledSubItemsDesc(category) {
if (!category.hasSubSettings || !category.subSettings || category.subSettings.length === 0) {
return this.$i18n.t(category.descKey);
}
const enabledItems = category.subSettings.filter(item => item.enabled);
if (enabledItems.length === 0) {
return this.$i18n.t('noEnabledItems');
}
if (enabledItems.length === category.subSettings.length) {
return this.$i18n.t(category.descKey);
}
return enabledItems.map(item => this.$i18n.t(item.titleKey)).join('、');
},
// 获取子设置标题
getSubSettingsTitle() {
if (!this.currentCategory) return '';
return this.$i18n.t(this.currentCategory.titleKey) + this.$i18n.t('saveSettings').replace('保存', '').replace('Save', '').replace('設定', '').replace('Настройки', '');
}
}
}
</script>
<style lang="scss" scoped>
page {
padding-top: 20rpx;
}
.tabs-wrap {
padding: 0 50rpx;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 0;
// justify-content: flex-start;
box-shadow: 1rpx -8rpx 14rpx 4rpx #ccc;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
view {
text-align: center;
flex-shrink: 0;
}
}
.tab {
color: #8B8E94;
font-size: 28rpx;
padding: 24rpx 12rpx;
position: relative;
margin: 0 5rpx;
max-width: 920rpx;
white-space: nowrap;
}
.tab.active {
color: #222;
font-weight: 600;
}
.tab.active:after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 8rpx;
height: 6rpx;
background: #0F0F0F;
border-radius: 6rpx;
}
.swiper {
height: 74vh;
}
.list {
box-sizing: border-box;
height: 74vh;
overflow: scroll;
padding-bottom: 30rpx;
}
.cell {
width: 700rpx;
margin: 16rpx auto;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 20rpx;
border-bottom: 0;
background: #fff;
border-radius: 16rpx;
position: relative;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.cell-badge {
position: absolute;
right: 140rpx;
top: 20rpx;
background: #F5F6F7;
border-radius: 30rpx;
padding: 6rpx 12rpx 6rpx 36rpx;
display: flex;
align-items: center;
color: #5F6368;
font-size: 22rpx;
}
.badge-icon {
position: absolute;
left: 8rpx;
width: 24rpx;
height: 24rpx;
}
.thumb {
width: 112rpx;
height: 112rpx;
border-radius: 12rpx;
margin-left: auto;
background: #E6E6E6;
}
.content {
flex: 1;
min-width: 0;
padding-right: 24rpx;
}
.row1 {
display: flex;
align-items: center;
}
.small-icon {
width: 28rpx;
height: 28rpx;
margin-right: 10rpx;
}
.time {
color: #8B8E94;
font-size: 24rpx;
}
.row2 {
margin-top: 14rpx;
color: #222;
font-size: 28rpx;
}
.no-more {
width: 100%;
text-align: center;
margin-top: 24rpx;
color: #ccc;
}
// 设置按钮样式
.settings-btn {
font-size: 36rpx;
color: #333;
font-weight: bold;
padding: 24rpx 12rpx;
cursor: pointer;
}
// 通知设置弹窗样式
.settings-modal {
position: fixed;
top: 140px;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.settings-content {
background: #fff;
border-radius:0 0 20rpx 20rpx;
width: 90%;
width: 750rpx;
max-height: 80vh;
overflow: hidden;
}
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 30rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.settings-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.close-btn {
font-size: 40rpx;
color: #999;
line-height: 1;
}
.settings-list {
max-height: 60vh;
}
.setting-item {
border-bottom: 1rpx solid #f0f0f0;
}
.setting-main {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
}
.setting-info {
flex: 1;
margin-right: 20rpx;
}
.setting-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
display: block;
margin-bottom: 8rpx;
}
.setting-desc-row {
display: flex;
align-items: center;
margin-top: 8rpx;
}
.setting-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
// flex: 1;
}
.setting-controls {
display: flex;
align-items: center;
}
.arrow {
font-size: 20rpx;
color: #999;
margin-left: 10rpx;
}
// 子设置弹窗样式
.sub-settings-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
}
.sub-settings-content {
background: #fff;
border-radius: 20rpx;
width: 90%;
max-width: 600rpx;
max-height: 80vh;
overflow: hidden;
}
.sub-settings-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 30rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.sub-settings-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.sub-settings-list {
max-height: 50vh;
}
.sub-setting-item {
border-bottom: 1rpx solid #f0f0f0;
}
.sub-setting-main {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
}
.sub-setting-info {
flex: 1;
}
.sub-setting-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.save-btn {
background: #333;
margin: 30rpx;
border-radius: 12rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
.save-btn-text {
color: #fff;
font-size: 28rpx;
font-weight: 500;
}
</style>