SEC_Warehouse/pages/event/index.vue

558 lines
17 KiB
Vue
Raw 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.

<!-- 事件页使用 uview-plus 筛选与空状态组件 -->
<template>
<view class="page page-base">
<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>
<view class="empty-area">
<up-empty mode="message" text="暂无事件数据" />
</view>
<view class="empty-hint">
<text>调整筛选条件后的事件列表将在此展示</text>
</view>
<!-- 场所抽屉顶部下拉选项列表 -->
<up-popup :show="drawer === 'scene'" mode="top" round="24" :overlay-opacity="0.35" :safe-area-inset-bottom="false" :custom-style="{ width: '100vw' }" @close="closeDrawer">
<view class="option-drawer">
<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>
</up-popup>
<!-- 状态抽屉:顶部下拉选项列表 -->
<up-popup :show="drawer === 'status'" mode="top" round="24" :overlay-opacity="0.35" :safe-area-inset-bottom="false" :custom-style="{ width: '100vw' }" @close="closeDrawer">
<view class="option-drawer">
<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>
</up-popup>
<!-- 时间抽屉:顶部下拉,内嵌日历范围选择 -->
<!-- 注意up-popup 内容区自带 @touchmove.stop.prevent会吞掉日历内 scroll-view 的滚动手势,
所以日历不放在 up-popup 插槽里,而是用条件渲染 + 自绘遮罩实现抽屉 -->
<view v-if="drawer === 'time'" class="time-mask" @click="closeDrawer" @touchmove.stop.prevent="noop" />
<view v-if="drawer === 'time'" class="time-drawer">
<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="time-drawer-body">
<up-calendar
:key="calendarKey"
mode="range"
:show="drawer === 'time'"
:page-inline="true"
:show-confirm="false"
:show-title="false"
:show-subtitle="true"
:row-height="48"
:month-num="calendarMonthNum"
:min-date="calendarMinDate"
:max-date="calendarMaxDate"
:allow-same-day="true"
:max-range="31"
range-prompt="单次最多选择31天"
:show-range-prompt="true"
start-text="开始"
end-text="结束"
color="#2CCB98"
:default-date="calendarDefaultDate"
@confirm="onCalendarConfirm"
@close="closeDrawer"
/>
</view>
<!-- 确定按钮:自绘钉底。组件内 footer 在 pageInline 多层嵌套下定位不可控。
选满起止日期时 showConfirm=false 的组件会自动 emit confirm中间态
这里只记录值不关闭抽屉,由这枚按钮确认后才写入筛选 -->
<view class="time-drawer-footer">
<up-button
shape="circle"
:text="calendarReady ? '确定' : '请选择起止日期'"
color="#2CCB98"
:disabled="!calendarReady"
@click="onConfirmClick"
/>
</view>
</view>
</view>
</template>
<script>
import { getSceneList } from '@/api/scene'
// 日历可回溯的年数按需调整monthNum 会随 minDate 动态计算,无需同步改)
const CALENDAR_YEARS_BACK = 1
export default {
data() {
return {
drawer: '', // '' | 'scene' | 'status' | 'time'
calendarKey: 0, // 强制重建日历,规避内部 selected 状态残留
pendingRange: [], // 日历临时选中的起止日期,点确定按钮才写入 filters
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 : '全部状态'
},
// 日历可选范围:过去一年 ~ 今天(不传 minDate 时组件默认从当月开始,无法回看历史)。
calendarMinDate() {
const d = new Date()
d.setFullYear(d.getFullYear() - CALENDAR_YEARS_BACK)
return d.getTime()
},
calendarMaxDate() {
return Date.now()
},
// up-calendar 的 monthNum 同时参与 maxDate 兜底计算:未传 maxDate 时月份终点是
// minDate + monthNum 个月;传了 maxDate 后它又反过来限制可渲染的月份数。
// 所以这里动态算出 min~max 实际跨过的月数,保证完整展示过去一年。
calendarMonthNum() {
return (
(new Date(this.calendarMaxDate).getFullYear() -
new Date(this.calendarMinDate).getFullYear()) *
12 +
new Date(this.calendarMaxDate).getMonth() -
new Date(this.calendarMinDate).getMonth() +
1
)
},
// 日历回显:已选范围作为 defaultDaterange 模式要求数组)。
calendarDefaultDate() {
const dates = [this.filters.startDate, this.filters.endDate].filter(Boolean)
return dates.length === 2 ? dates : []
},
// 日历当前是否已选满起止日期pendingRange 在日历选中时更新)。
calendarReady() {
return this.pendingRange.length >= 2
}
},
onLoad() {
this.loadScenes()
},
methods: {
// 动态加载场所列表:复用场景接口,后端 /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') {
// 重建日历实例up-calendar 内部选中态不会随 defaultDate 变化重置
this.calendarKey += 1
// 回显已提交的日期范围,避免重开抽屉时按钮误判为“未选择”
const dates = [this.filters.startDate, this.filters.endDate].filter(Boolean)
this.pendingRange = dates.length === 2 ? dates : []
}
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 })
},
// 日历选中变化showConfirm=false 时组件在选满起止时自动触发 confirm
// 只记录临时选择,不关闭抽屉——最终提交由底部自绘确定按钮完成。
onCalendarConfirm(dateArr) {
if (!Array.isArray(dateArr) || dateArr.length < 2) {
return
}
this.pendingRange = dateArr
},
// 自绘确定按钮:提交临时选择并写入筛选值。
// range 模式选满后 confirm 会带完整日期数组(起止之间每一天都在内),
// 所以取数组首尾作为起止日期,而非前两个元素。
onConfirmClick() {
if (!this.calendarReady) {
return
}
const start = this.pendingRange[0]
const end = this.pendingRange[this.pendingRange.length - 1]
this.filters.startDate = start
this.filters.endDate = end
this.filters.timeText = `${start}${end}`
this.closeDrawer()
this.handle()
}
}
}
</script>
<style lang="scss" scoped>
@import '@/styles/common.css';
.filters {
display: flex;
align-items: stretch;
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;
}
/* 场所/状态选项抽屉 */
.option-drawer {
background: #ffffff;
padding-bottom: 24rpx;
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.08);
}
.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;
display: flex;
align-items: center;
justify-content: center;
}
.option-drawer-list {
padding: 12rpx 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;
}
/* 时间抽屉:自绘遮罩 + 顶部面板(不经过 up-popup避免其 touchmove 拦截吞掉日历滚动手势) */
.time-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 10075;
}
.time-drawer {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 66vh; /* 固定高度:日历窗口+按钮填满,按钮钉底 */
z-index: 10076;
background: #ffffff;
border-bottom-left-radius: 32rpx;
border-bottom-right-radius: 32rpx;
display: flex;
flex-direction: column;
overflow: hidden;
}
.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;
}
.time-drawer-body {
/* 占满抽屉剩余空间;日历内部 scroll-view 通过 calc 抬高到填满 body
组件内的确定按钮再用绝对定位钉回 body 底部,消除按钮下方空白 */
flex: 1;
min-height: 0;
position: relative;
}
/* 日历滚动窗口高度。
pageInline 下 up-calendar 内部仍套着 u-popup.u-popup → .u-transition → .u-popup__content
这几层默认高度都是 auto导致 scroll-view 的百分比高度无法解析
(退化成内容高度=全部月份堆叠、滚动窗口失效)。这里把中间层逐级撑到 100%
再让 scroll-view 用 flex 填满 header 以下的剩余空间,实现自适应、无空隙。 */
.time-drawer-body :deep(.u-popup),
.time-drawer-body :deep(.u-transition),
.time-drawer-body :deep(.u-popup__content) {
height: 100%;
}
.time-drawer-body :deep(.u-calendar) {
box-sizing: border-box;
height: 100%;
display: flex;
flex-direction: column;
padding: 0 16rpx;
}
.time-drawer-body :deep(.u-calendar-header) {
flex-shrink: 0;
}
.time-drawer-body :deep(.u-calendar .u-scroll-view),
.time-drawer-body :deep(.u-calendar scroll-view) {
flex: 1;
min-height: 0;
height: auto !important;
}
/* 确定按钮:不依赖组件内 footer在多层嵌套里定位/裁切不可控),
在 body 底部自绘一枚按钮,选满起止日期后可点 */
.time-drawer-footer {
flex-shrink: 0;
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f1f3;
background: #ffffff;
}
/* 副标题(当前滚动到的月份):弱化为居中小胶囊 */
.time-drawer-body :deep(.u-calendar-header__subtitle) {
align-self: center;
margin: 8rpx 0 4rpx;
padding: 4rpx 24rpx;
background: #f2f3f5;
border-radius: 999rpx;
height: auto;
line-height: 40rpx;
font-size: 24rpx;
font-weight: 400;
color: #6b6f76;
}
/* 星期行 */
.time-drawer-body :deep(.u-calendar-header__weekdays) {
padding: 8rpx 0;
border-bottom: 1rpx solid #f0f1f3;
}
.time-drawer-body :deep(.u-calendar-header__weekdays__weekday) {
text-align: center;
font-size: 24rpx;
color: #909399;
line-height: 44rpx;
}
.time-drawer-body :deep(.u-calendar-month-wrapper) {
/* 不能加横向 padding首日偏移用 JS 实测容器宽计算、格子宽用 CSS 百分比,
两者对 padding 的包含不同会造成偏差,偏移大的月份首行末尾格子会被挤换行 */
margin-top: 0;
}
/* 每月标题行 */
.time-drawer-body :deep(.u-calendar-month__title) {
font-size: 28rpx;
font-weight: 600;
color: #2a2d33;
padding: 16rpx 12rpx;
line-height: 64rpx;
height: 64rpx;
}
/* 月份背景水印:默认 155px 过大过亮,缩小压淡 */
.time-drawer-body :deep(.u-calendar-month__days__month-mark-wrapper__text) {
font-size: 110rpx;
color: rgba(231, 232, 234, 0.4);
}
.time-drawer-body :deep(.u-calendar-month__days__day) {
padding: 8rpx;
}
/* 日期字号恢复正常可读尺寸(此前随抽屉缩小被误压) */
.time-drawer-body :deep(.u-calendar-month__days__day__select__info) {
font-size: 28rpx;
font-weight: 500;
color: #2a2d33;
}
/* 开始/结束提示小字 */
.time-drawer-body :deep(.u-calendar-month__days__day__select__buttom-info) {
font-size: 20rpx;
color: #6b6f76;
}
</style>