Compare commits

..

No commits in common. "054f05af7074411842c9ab5e5f287e311353706c" and "d1681ee08cba449902c12cc5171180e55bc97b32" have entirely different histories.

10 changed files with 20 additions and 776 deletions

View File

@ -1,5 +1,5 @@
{
"name" : "SEC_Warehouse",
"name" : "smarthome",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",

View File

@ -57,21 +57,15 @@
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
"text": "首页"
},
{
"pagePath": "pages/event/index",
"text": "事件",
"iconPath": "static/tabbar/event.png",
"selectedIconPath": "static/tabbar/event-active.png"
"text": "事件"
},
{
"pagePath": "pages/mine/index",
"text": "我的",
"iconPath": "static/tabbar/mine.png",
"selectedIconPath": "static/tabbar/mine-active.png"
"text": "我的"
}
]
},

View File

@ -4,794 +4,41 @@
<up-navbar title="事件" :fixed="false" :safe-area-inset-top="true" :border="false" left-icon="" />
<view class="filters">
<view class="filter-cell" @click="openDrawer('scene')">
<view class="filter-trigger" :class="{ 'filter-trigger--active': filters.sceneId !== 0 }">
<text class="filter-text">{{ sceneLabel }}</text>
<up-icon name="arrow-down" size="13" :color="filters.sceneId !== 0 ? '#12a37a' : '#909399'" />
</view>
</view>
<view class="filter-cell" @click="openDrawer('status')">
<view class="filter-trigger" :class="{ 'filter-trigger--active': filters.status !== 0 }">
<text class="filter-text">{{ statusLabel }}</text>
<up-icon name="arrow-down" size="13" :color="filters.status !== 0 ? '#12a37a' : '#909399'" />
</view>
</view>
<view class="filter-cell" @click="openDrawer('time')">
<view class="filter-trigger" :class="{ 'filter-trigger--active': !!filters.startDate }">
<text class="filter-text">{{ filters.timeText || '时间' }}</text>
<up-icon name="arrow-down" size="13" :color="filters.startDate ? '#12a37a' : '#909399'" />
</view>
</view>
<view><up-button size="mini" type="primary" plain text="全部场所" /></view>
<view><up-button size="mini" type="primary" plain text="全部状态" /></view>
<view><up-button size="mini" type="primary" plain text="时间" /></view>
</view>
<!-- 数据层筛选栏以下区域三个下拉抽屉都锚定在这层顶部筛选栏下缘展开 -->
<view class="content-area">
<view class="content-body">
<view class="empty-area">
<up-empty mode="message" text="暂无事件数据" />
</view>
<view class="empty-hint">
<text>调整筛选条件后的事件列表将在此展示</text>
</view>
</view>
<!-- 遮罩盖住筛选栏以下全部内容 -->
<view v-if="drawer" class="content-mask" @click="closeDrawer" @touchmove.stop.prevent="noop" />
<!-- 场所抽屉从筛选栏下缘展开 -->
<view v-if="drawer === 'scene'" class="drop-panel drop-panel--options">
<view class="option-drawer-header">
<text class="option-drawer-title">选择场所</text>
<view class="option-drawer-close" @click="closeDrawer">
<up-icon name="close" size="20" color="#909399" />
</view>
</view>
<view class="option-drawer-list">
<view
v-for="opt in sceneOptions"
:key="opt.id"
class="option-drawer-item"
:class="{ 'option-drawer-item--active': filters.sceneId === opt.id }"
@click="selectScene(opt.id)"
>
<text class="option-drawer-item__text">{{ opt.name }}</text>
<up-icon v-if="filters.sceneId === opt.id" name="checkmark" size="16" color="#2CCB98" />
</view>
</view>
</view>
<!-- 状态抽屉从筛选栏下缘展开 -->
<view v-if="drawer === 'status'" class="drop-panel drop-panel--options">
<view class="option-drawer-header">
<text class="option-drawer-title">选择状态</text>
<view class="option-drawer-close" @click="closeDrawer">
<up-icon name="close" size="20" color="#909399" />
</view>
</view>
<view class="option-drawer-list">
<view
v-for="opt in statusOptions"
:key="opt.id"
class="option-drawer-item"
:class="{ 'option-drawer-item--active': filters.status === opt.id }"
@click="selectStatus(opt.id)"
>
<text class="option-drawer-item__text">{{ opt.name }}</text>
<up-icon v-if="filters.status === opt.id" name="checkmark" size="16" color="#2CCB98" />
</view>
</view>
</view>
<!-- 时间抽屉从筛选栏下缘展开滚动式日历懒渲染窗口外月份占位 -->
<view v-if="drawer === 'time'" class="drop-panel drop-panel--calendar">
<view class="time-drawer-header">
<text class="time-drawer-title">选择时间范围</text>
<view class="time-drawer-close" @click="closeDrawer">
<up-icon name="close" size="20" color="#909399" />
</view>
</view>
<!-- 快捷范围点选直接填满起止 -->
<view class="quick-row">
<view
v-for="q in quickRanges"
:key="q.label"
class="quick-chip"
:class="{ 'quick-chip--active': activeQuick === q.label }"
@click="applyQuick(q)"
>
<text>{{ q.label }}</text>
</view>
</view>
<!-- 星期行钉在滚动区上方 -->
<view class="cal-weekdays">
<text v-for="w in weekdayLabels" :key="w" class="cal-weekday">{{ w }}</text>
</view>
<!-- 滚动日历scroll-view 纵向滚月懒渲染窗口外月份只渲染占位高度 -->
<view class="cal-scroll-wrap">
<scroll-view
class="cal-scroll"
scroll-y
:scroll-into-view="calAnchorId"
scroll-anchoring
@scroll="onCalScroll"
>
<view
v-for="m in renderedMonths"
:key="m.key"
:id="m.key"
class="cal-month"
:style="m.placeholder ? { height: m.height + 'rpx' } : {}"
>
<template v-if="!m.placeholder">
<view class="cal-month-title">
<text>{{ m.title }}</text>
</view>
<view class="cal-grid">
<view
v-for="(cell, i) in m.cells"
:key="i"
class="cal-cell"
:class="cell.cls"
@click="pickDate(cell)"
>
<view v-if="cell.day" class="cal-day" :class="cell.dayCls">
<text class="cal-day-num">{{ cell.day }}</text>
<text v-if="cell.tag" class="cal-day-tag">{{ cell.tag }}</text>
</view>
</view>
</view>
</template>
</view>
</scroll-view>
</view>
<view class="time-drawer-footer">
<up-button
shape="circle"
:text="rangeReady ? '确定' : '请选择起止日期'"
color="#2CCB98"
:disabled="!rangeReady"
@click="onConfirmClick"
/>
</view>
</view>
<view class="empty-area">
<up-empty mode="message" text="暂无事件数据" />
</view>
</view>
</template>
<script>
import { getSceneList } from '@/api/scene'
//
const CALENDAR_YEARS_BACK = 10
const WEEKDAY_LABELS = ['一', '二', '三', '四', '五', '六', '日']
const pad2 = (n) => String(n).padStart(2, '0')
// Date -> 'YYYY-MM-DD'
const fmtDate = (d) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
// 'YYYY-MM-DD' -> Date
const parseDate = (s) => {
const [y, m, d] = s.split('-').map(Number)
return new Date(y, m - 1, d)
}
// N
const quickRange = (days) => {
const end = new Date()
const start = new Date()
start.setDate(start.getDate() - (days - 1))
return { label: `${days}`, start: fmtDate(start), end: fmtDate(end) }
}
export default {
data() {
return {
drawer: '', // '' | 'scene' | 'status' | 'time'
windowWidth: 375, // pxonLoad pxrpx
calScroll: 0, // rpx
calAnchorId: '', // scroll-into-view
pendingStart: '', //
pendingEnd: '', //
activeQuick: '',
weekdayLabels: WEEKDAY_LABELS,
quickRanges: [quickRange(7), quickRange(30), quickRange(90)],
filters: {
sceneId: 0, // 0
status: 0,
startDate: '',
endDate: '',
timeText: ''
},
sceneOptions: [{ id: 0, name: '全部' }],
statusOptions: [
{ id: 0, name: '全部' },
{ id: 1, name: '未处理' },
{ id: 2, name: '已处理' },
{ id: 3, name: '已忽略' }
]
}
},
computed: {
sceneLabel() {
const hit = this.sceneOptions.find((o) => o.id === this.filters.sceneId)
return hit ? hit.name : '全部场所'
},
statusLabel() {
const hit = this.statusOptions.find((o) => o.id === this.filters.status)
return hit ? hit.name : '全部状态'
},
//
minLimit() {
const d = new Date()
d.setFullYear(d.getFullYear() - CALENDAR_YEARS_BACK)
d.setDate(1)
return fmtDate(d)
},
maxLimit() {
return fmtDate(new Date())
},
//
monthMetas() {
const metas = []
const start = parseDate(this.minLimit)
const now = new Date()
const endY = now.getFullYear()
const endM = now.getMonth()
let y = start.getFullYear()
let m = start.getMonth()
let top = 0
while (y < endY || (y === endY && m <= endM)) {
const weeks = this.monthWeeks(y, m)
// + × aspect ×1.15 750rpx/7
const height = 96 + weeks * Math.round((750 / 7) * 1.15) + 16
metas.push({
key: `m-${y}-${m}`,
year: y,
month: m,
title: `${y}${pad2(m + 1)}`,
top,
height
})
top += height
if (m === 11) {
y += 1
m = 0
} else {
m += 1
}
}
return metas
},
// ±4
renderedMonths() {
// top calScroll
const viewRpx = 1400 // rpx
const buffer = 3000 // rpx
const lo = this.calScroll - buffer
const hi = this.calScroll + viewRpx + buffer
return this.monthMetas.map((meta) => {
const inWindow = meta.top + meta.height > lo && meta.top < hi
if (!inWindow) {
return { ...meta, placeholder: true, cells: [] }
}
return {
...meta,
placeholder: false,
cells: this.buildMonthCells(meta.year, meta.month)
}
})
},
//
rangeReady() {
return !!this.pendingStart && !!this.pendingEnd
}
},
onLoad() {
// pxrpx
const info = uni.getWindowInfo ? uni.getWindowInfo() : uni.getSystemInfoSync()
this.windowWidth = info.windowWidth || 375
this.loadScenes()
},
methods: {
// //
buildCell(dateStr, day) {
const disabled = dateStr < this.minLimit || dateStr > this.maxLimit
let dayCls = ''
let tag = ''
if (!disabled) {
if (this.pendingStart && dateStr === this.pendingStart) {
dayCls = 'cal-day--start'
tag = this.pendingStart === this.pendingEnd ? '起/止' : '开始'
} else if (this.pendingEnd && dateStr === this.pendingEnd) {
dayCls = 'cal-day--end'
tag = '结束'
} else if (
this.pendingStart &&
this.pendingEnd &&
dateStr > this.pendingStart &&
dateStr < this.pendingEnd
) {
dayCls = 'cal-day--in'
}
}
return { day, date: dateStr, disabled, cls: { 'cal-cell--disabled': disabled }, dayCls, tag }
},
//
buildMonthCells(y, m) {
const firstDay = new Date(y, m, 1)
const lead = (firstDay.getDay() + 6) % 7
const daysInMonth = new Date(y, m + 1, 0).getDate()
const cells = []
for (let i = 0; i < lead; i++) cells.push({ day: 0 })
for (let d = 1; d <= daysInMonth; d++) {
cells.push(this.buildCell(`${y}-${pad2(m + 1)}-${pad2(d)}`, d))
}
return cells
},
// 1
monthWeeks(y, m) {
const firstDay = new Date(y, m, 1)
const lead = (firstDay.getDay() + 6) % 7
const daysInMonth = new Date(y, m + 1, 0).getDate()
return Math.ceil((lead + daysInMonth) / 7)
},
// /scene/list
// ""toast
async loadScenes() {
try {
const data = await getSceneList()
const list = Array.isArray(data) ? data : []
this.sceneOptions = [{ id: 0, name: '全部' }, ...list]
} catch (error) {
this.sceneOptions = [{ id: 0, name: '全部' }]
}
},
noop() {},
openDrawer(name) {
if (name === 'time') {
//
this.pendingStart = this.filters.startDate
this.pendingEnd = this.filters.endDate
this.activeQuick = ''
const focus = this.filters.startDate || fmtDate(new Date())
const d = parseDate(focus)
const meta = this.monthMetas.find(
(mm) => mm.year === d.getFullYear() && mm.month === d.getMonth()
)
this.calScroll = meta ? meta.top : 0
// scroll-into-view
this.calAnchorId = ''
this.$nextTick(() => {
this.calAnchorId = meta ? meta.key : ''
})
}
this.drawer = name
},
closeDrawer() {
this.drawer = ''
},
// /
selectScene(id) {
this.filters.sceneId = id
this.closeDrawer()
this.handle()
},
selectStatus(id) {
this.filters.status = id
this.closeDrawer()
this.handle()
},
// 便
handle() {
// eslint-disable-next-line no-console
console.log('[event filters]', { ...this.filters })
},
// px rpx750 /
onCalScroll(e) {
const px = e.detail.scrollTop || 0
const rpx = px * (750 / this.windowWidth)
// computed
if (Math.abs(rpx - this.calScroll) > 200) {
this.calScroll = rpx
}
},
//
pickDate(cell) {
if (!cell.day || cell.disabled) return
const date = cell.date
if (!this.pendingStart || (this.pendingStart && this.pendingEnd)) {
this.pendingStart = date
this.pendingEnd = ''
this.activeQuick = ''
} else if (date < this.pendingStart) {
this.pendingStart = date
this.activeQuick = ''
} else {
this.pendingEnd = date
this.activeQuick = ''
}
},
//
applyQuick(q) {
this.pendingStart = q.start
this.pendingEnd = q.end
this.activeQuick = q.label
const d = parseDate(q.start)
const meta = this.monthMetas.find(
(mm) => mm.year === d.getFullYear() && mm.month === d.getMonth()
)
this.calAnchorId = ''
this.$nextTick(() => {
this.calAnchorId = meta ? meta.key : ''
})
},
//
onConfirmClick() {
if (!this.rangeReady) {
return
}
this.filters.startDate = this.pendingStart
this.filters.endDate = this.pendingEnd
this.filters.timeText = `${this.pendingStart}${this.pendingEnd}`
this.closeDrawer()
this.handle()
}
}
}
//
export default {}
</script>
<style lang="scss" scoped>
@import '@/styles/common.css';
/* 页面容器:纵向 flex让数据层撑满筛选栏以下的全部剩余视口 */
.page {
display: flex;
flex-direction: column;
}
.filters {
flex-shrink: 0;
display: flex;
align-items: stretch;
> view + view {
margin-left: 18rpx;
}
min-height: 96rpx;
padding: 16rpx 24rpx;
background: #ffffff;
border-top: 1rpx solid #ededed;
}
.filter-cell {
flex: 1;
min-width: 0;
+ .filter-cell {
margin-left: 18rpx;
}
}
.filter-trigger {
height: 100%;
min-height: 64rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: #f5f6f8;
border: 1rpx solid transparent;
border-radius: 12rpx;
color: #606266;
font-size: 26rpx;
transition: background-color 0.2s, border-color 0.2s;
&--active {
background: #e9fff3;
border-color: #2ccb98;
color: #12a37a;
}
}
.filter-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.empty-area {
min-height: 720rpx;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 120rpx;
}
.empty-hint {
margin-top: -40rpx;
text-align: center;
font-size: 24rpx;
color: #b5b9c0;
}
/* 数据层:撑满筛选栏以下全部视口,是三个下拉抽屉的定位锚点 */
.content-area {
position: relative;
flex: 1;
min-height: 0;
}
/* 遮罩:盖住筛选栏以下内容,点击关闭 */
.content-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 10;
}
/* 下拉面板:从数据层顶部(筛选栏下缘)展开 */
.drop-panel {
position: absolute;
top: 0;
left: 0;
width: 100%;
background: #ffffff;
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.08);
z-index: 11;
display: flex;
flex-direction: column;
border-bottom-left-radius: 24rpx;
border-bottom-right-radius: 24rpx;
overflow: hidden;
}
/* 场所/状态选项面板:内容自适应高度 */
.drop-panel--options {
max-height: 60vh;
}
/* 时间面板:日历占剩余空间,但不超过数据层高度(时间抽屉时遮罩仍完整盖住剩余空间) */
.drop-panel--calendar {
height: 66vh;
max-height: 100%;
}
.option-drawer-header {
padding: 28rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #f0f1f3;
}
.option-drawer-title {
font-size: 32rpx;
font-weight: 600;
color: #2a2d33;
}
.option-drawer-close {
width: 56rpx;
height: 56rpx;
min-height: 880rpx;
display: flex;
align-items: center;
justify-content: center;
}
.option-drawer-list {
padding: 12rpx 24rpx 24rpx;
}
.option-drawer-item {
min-height: 96rpx;
padding: 0 28rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 14rpx;
&:active {
background: #f7f8fa;
}
&--active {
background: #e9fff3;
}
}
.option-drawer-item__text {
font-size: 30rpx;
color: #2a2d33;
}
.option-drawer-item--active .option-drawer-item__text {
color: #2CCB98;
font-weight: 600;
}
/* 时间面板头/底部(面板已锚定在筛选栏下方,不再 fixed */
.time-drawer-header {
padding: 20rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #f0f1f3;
}
.time-drawer-title {
font-size: 30rpx;
font-weight: 600;
color: #2a2d33;
}
.time-drawer-close {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 快捷范围行 */
.quick-row {
display: flex;
padding: 20rpx 24rpx 4rpx;
}
.quick-chip {
flex: 1;
margin-right: 16rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f6f8;
border: 1rpx solid transparent;
border-radius: 999rpx;
font-size: 24rpx;
color: #606266;
&:last-child {
margin-right: 0;
}
&--active {
background: #e9fff3;
border-color: #2ccb98;
color: #12a37a;
font-weight: 600;
}
}
/* 星期行:钉在滚动区上方 */
.cal-weekdays {
display: flex;
padding: 0 16rpx;
border-bottom: 1rpx solid #f0f1f3;
}
.cal-weekday {
flex: 1;
text-align: center;
font-size: 24rpx;
color: #909399;
line-height: 48rpx;
}
/* 滚动日历容器:占满抽屉剩余空间 */
.cal-scroll-wrap {
flex: 1;
min-height: 0;
}
.cal-scroll {
height: 100%;
}
/* 月份块 */
.cal-month {
padding: 8rpx 16rpx 0;
}
.cal-month-title {
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
color: #2a2d33;
}
/* 日期网格:固定 42 格(前置空格 + 当月天),节点数恒定 */
.cal-grid {
display: flex;
flex-wrap: wrap;
}
.cal-cell {
width: calc(100% / 7);
box-sizing: border-box;
padding: 4rpx;
aspect-ratio: 1 / 1.15;
display: flex;
align-items: stretch;
&--disabled {
opacity: 0.3;
}
}
.cal-day {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 12rpx;
&:active {
background: #f2f3f5;
}
&--start,
&--end {
background: #2ccb98;
}
&--start.cal-day--end {
background: #2ccb98;
}
&--in {
background: #e9fff3;
border-radius: 0;
&:active {
background: #ddf6ec;
}
}
}
.cal-day-num {
font-size: 28rpx;
font-weight: 500;
color: #2a2d33;
line-height: 40rpx;
}
.cal-day--start .cal-day-num,
.cal-day--end .cal-day-num {
color: #ffffff;
font-weight: 600;
}
.cal-day--in .cal-day-num {
color: #12a37a;
}
/* 开始/结束小字标注 */
.cal-day-tag {
font-size: 18rpx;
line-height: 24rpx;
color: rgba(255, 255, 255, 0.9);
}
/* 起止回显:只占内容高度,不与日历滚动区平分空间 */
.range-preview {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #6b6f76;
min-height: 56rpx;
}
.time-drawer-footer {
flex-shrink: 0;
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f1f3;
background: #ffffff;
}
</style>

View File

@ -14,8 +14,11 @@
<up-cell-group :border="false" custom-style="margin: 24rpx; border-radius: 24rpx; overflow: hidden; background: #ffffff;">
<up-cell title="设备配置" is-link @click="goConfig" />
<up-cell title="分享的设备" is-link />
<up-cell title="场所管理" is-link />
<up-cell title="接警人管理" is-link />
<up-cell title="手机号" :value="phoneText" is-link />
<up-cell title="语音广播" is-link />
<up-cell title="当前版本" value="2.8.44" />
<up-cell title="退出登录" @click="logout" />
</up-cell-group>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB