first commit

This commit is contained in:
2026-09-11 01:45:04 +08:00
commit 0a8d60d87f
145 changed files with 40978 additions and 0 deletions
@@ -0,0 +1,6 @@
{
"usingComponents": {
"navigation-bar": "/components/navigation-bar/navigation-bar",
"empty-state": "/components/empty-state/empty-state"
}
}
@@ -0,0 +1,78 @@
import { request } from '../../utils/request'
import { PagedResult, NotificationItem } from '../../types/models'
import { formatRelativeTime } from '../../utils/format'
const FILTERS = [
{ name: '全部', value: 0 },
{ name: '点赞', value: 1 },
{ name: '订单', value: 2 },
{ name: '系统', value: 3 },
]
Page({
data: {
filters: FILTERS,
activeFilter: 0,
list: [] as NotificationItem[],
loading: true,
page: 1,
pageSize: 20,
hasMore: true,
},
onShow() {
this.refresh()
this.fetchUnread()
},
fetchUnread() {
request<{ unread_count: number }>({ url: '/notification/unread', showError: false })
.then((res) => {
wx.setStorageSync('zt_closet_unread', res.unread_count)
const tabBar = (this as any).getTabBar && (this as any).getTabBar()
if (tabBar && tabBar.refreshUnread) tabBar.refreshUnread()
})
.catch(() => {})
},
refresh() {
this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 })
this.fetchList(1)
},
onReachBottom() {
if (!this.data.hasMore || this.data.loading) return
this.fetchList(this.data.page + 1)
},
fetchList(page: number) {
const params: Record<string, any> = { page, page_size: this.data.pageSize }
const filter = this.data.activeFilter
if (filter > 0) params.msg_type = filter
request<PagedResult<NotificationItem>>({ url: '/notification', data: params })
.then((res) => {
const list = res.list.map((it) => ({ ...it, _time: formatRelativeTime(it.created_at) }))
const merged = page === 1 ? list : this.data.list.concat(list)
this.setData({
list: merged,
hasMore: merged.length < res.total,
loading: false,
page,
})
})
.catch(() => this.setData({ loading: false }))
},
onFilterTap(e: WechatMiniprogram.TouchEvent) {
const index = Number(e.currentTarget.dataset.index)
if (index === this.data.activeFilter) return
this.setData({ activeFilter: index, list: [], loading: true })
this.refresh()
},
onItemTap(e: WechatMiniprogram.TouchEvent) {
const uuid = e.currentTarget.dataset.uuid as string
wx.navigateTo({ url: `/pages/notification-detail/notification-detail?uuid=${uuid}` })
},
})
@@ -0,0 +1,53 @@
<view class="page page-flex">
<navigation-bar title="通知" />
<scroll-view
scroll-y
class="scroll-body"
bindscrolltolower="onReachBottom"
>
<!-- 筛选 -->
<scroll-view scroll-x class="filter-scroll" show-scrollbar="{{false}}">
<view class="filter-row">
<view
wx:for="{{filters}}"
wx:key="name"
class="filter-item {{activeFilter === index ? 'active' : ''}}"
data-index="{{index}}"
bindtap="onFilterTap"
>{{item.name}}</view>
</view>
</scroll-view>
<!-- 空态 -->
<empty-state
wx:if="{{!loading && !list.length}}"
icon="icon-bell"
title="还没有消息"
desc="穿搭被点赞、订单动态都会出现在这里"
/>
<!-- 列表 -->
<view class="list">
<view
class="n-card card {{item.is_read === 1 ? 'unread' : ''}}"
wx:for="{{list}}"
wx:key="notification_uuid"
data-uuid="{{item.notification_uuid}}"
bindtap="onItemTap"
>
<view class="n-icon {{item.msg_type === 1 ? 'like' : item.msg_type === 2 ? 'order' : 'sys'}}">
<view class="icon {{item.msg_type === 1 ? 'icon-heart' : item.msg_type === 2 ? 'icon-doc' : 'icon-bell'}}"></view>
</view>
<view class="n-body">
<view class="n-title-row">
<view wx:if="{{item.is_read === 1}}" class="n-dot"></view>
<text class="n-title {{item.is_read === 2 ? 'read' : ''}}">{{item.title}}</text>
</view>
<view class="n-content">{{item.content}}</view>
<view class="n-time">{{item._time}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
@@ -0,0 +1,126 @@
.page-flex {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.scroll-body {
flex: 1;
height: 0;
}
.filter-scroll {
background: #fff;
white-space: nowrap;
}
.filter-row {
display: inline-flex;
padding: 20rpx 24rpx;
}
.filter-item {
flex-shrink: 0;
padding: 0 32rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30rpx;
font-size: 26rpx;
color: #3C3C43;
background: #F2F2F7;
margin-right: 16rpx;
}
.filter-item.active {
background: #007AFF;
color: #fff;
}
.list {
padding: 24rpx;
}
.n-card {
display: flex;
align-items: flex-start;
padding: 28rpx;
margin-bottom: 20rpx;
}
.n-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 24rpx;
}
.n-icon.like {
background: #FFE8EC;
color: #FF3B30;
}
.n-icon.order {
background: #E8F2FF;
color: #007AFF;
}
.n-icon.sys {
background: #F2F2F7;
color: #8E8E93;
}
.n-icon .icon {
width: 44rpx;
height: 44rpx;
}
.n-body {
flex: 1;
min-width: 0;
}
.n-title-row {
display: flex;
align-items: center;
}
.n-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #007AFF;
margin-right: 12rpx;
flex-shrink: 0;
}
.n-title {
font-size: 30rpx;
font-weight: 600;
color: #000;
}
.n-title.read {
color: #8E8E93;
font-weight: 500;
}
.n-content {
font-size: 26rpx;
color: #3C3C43;
margin-top: 8rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.n-time {
font-size: 22rpx;
color: #C7C7CC;
margin-top: 8rpx;
}