add searchable

This commit is contained in:
Timi
2025-12-08 18:47:44 +08:00
parent 71fc077726
commit 00bcbf9e63
6 changed files with 243 additions and 68 deletions

View File

@ -5,6 +5,8 @@
"t-icon": "tdesign-miniprogram/icon/icon",
"t-button": "tdesign-miniprogram/button/button",
"t-loading": "tdesign-miniprogram/loading/loading",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group"
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-search": "tdesign-miniprogram/search/search",
"t-empty": "tdesign-miniprogram/empty/empty"
}
}

View File

@ -2,29 +2,52 @@
.journal-list {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.item {
&.selected {
background: var(--td-bg-color-secondarycontainer);
}
.thumb {
width: 96rpx;
height: 96rpx;
object-fit: cover;
background: var(--td-bg-color-component);
flex-shrink: 0;
margin-right: 16rpx;
border-radius: 8rpx;
}
.search-bar {
padding: 8rpx 16rpx;
flex-shrink: 0;
background: var(--td-bg-color-container);
box-shadow: 0 4rpx 8rpx var(--theme-shadow-light);
}
.loading,
.finished,
.empty {
color: var(--td-text-color-placeholder);
padding: 24rpx 0;
text-align: center;
.scroll-content {
flex: 1;
height: 0;
padding: 0;
.item {
&.selected {
background: var(--td-bg-color-secondarycontainer);
}
.thumb {
width: 96rpx;
height: 96rpx;
flex-shrink: 0;
object-fit: cover;
margin-right: 16rpx;
border-radius: 8rpx;
background: var(--td-bg-color-component);
}
.date {
color: gray;
}
.location {
font-size: 24rpx;
}
}
.loading,
.finished {
color: var(--td-text-color-placeholder);
padding: 32rpx 0;
font-size: 24rpx;
text-align: center;
}
}
}

View File

@ -2,6 +2,7 @@
import config from "../../config/index";
import { JournalPage, JournalPageType } from "../../types/Journal";
import Time from "../../utils/Time";
import Toolkit from "../../utils/Toolkit";
export type JournalListItem = {
id: number;
@ -16,9 +17,15 @@ interface JournalListData {
isFetching: boolean;
isFinished: boolean;
page: JournalPage;
searchValue: string;
}
Component({
// 组件实例类型扩展
interface ComponentInstance {
debouncedSearch?: ((keyword: string) => void) & { cancel(): void };
}
Component<JournalListData, {}, {}, ComponentInstance>({
options: {
styleIsolation: 'apply-shared'
},
@ -34,6 +41,14 @@ Component({
selectedId: {
type: Number,
value: undefined
},
searchable: {
type: Boolean,
value: false // 是否显示搜索框
},
mode: {
type: String,
value: 'select' // 'select' 选择模式 或 'navigate' 跳转模式
}
},
data: <JournalListData>{
@ -44,27 +59,63 @@ Component({
index: 0,
size: 16,
type: JournalPageType.PREVIEW
},
searchValue: ""
},
lifetimes: {
ready() {
// 创建防抖搜索函数
this.debouncedSearch = Toolkit.debounce(
(keyword: string) => {
this.resetAndSearch(keyword);
},
false, // 不立即执行,等待输入停止
400 // 400ms 延迟
);
// 组件加载时就获取数据
this.fetch();
},
detached() {
// 组件销毁时取消防抖
if (this.debouncedSearch) {
this.debouncedSearch.cancel();
}
}
},
observers: {
'visible': function(visible: boolean) {
// visible 从 false 变为 true 时,如果还没有数据则加载
if (visible && this.data.list.length === 0) {
this.fetch();
}
},
'type': function() {
this.resetPage();
}
},
methods: {
resetPage() {
const likeExample = this.data.searchValue ? {
idea: this.data.searchValue,
location: this.data.searchValue
} : undefined;
const equalsExample = this.properties.type ? {
type: this.properties.type
} : undefined;
this.setData({
page: {
index: 0,
size: 16,
type: JournalPageType.PREVIEW
type: JournalPageType.PREVIEW,
equalsExample,
likeExample
},
list: [],
isFinished: false
});
}
},
methods: {
},
fetch() {
if (this.data.isFetching || this.data.isFinished) {
return;
@ -88,7 +139,7 @@ Component({
const firstThumb = journal.items.find((item: any) => item.attachType === "THUMB");
return {
id: journal.id,
date: Time.toPassedDateTime(journal.createdAt),
date: Time.toPassedDate(journal.createdAt),
idea: journal.idea,
location: journal.location,
thumbUrl: firstThumb ? `${config.url}/attachment/read/${firstThumb.mongoId}` : undefined
@ -108,9 +159,70 @@ Component({
}
});
},
onSearchChange(e: WechatMiniprogram.CustomEvent) {
const value = e.detail.value.trim();
this.setData({ searchValue: value });
// 如果是清空操作不使用防抖clear 事件会处理)
if (value === "" && this.debouncedSearch) {
this.debouncedSearch.cancel();
return;
}
// 使用防抖自动搜索
if (this.debouncedSearch) {
this.debouncedSearch(value);
}
},
onSearchSubmit(e: WechatMiniprogram.CustomEvent) {
const value = e.detail.value.trim();
// 立即搜索,取消防抖
if (this.debouncedSearch) {
this.debouncedSearch.cancel();
}
this.resetAndSearch(value);
},
onSearchClear() {
// 取消防抖,立即搜索
if (this.debouncedSearch) {
this.debouncedSearch.cancel();
}
this.resetAndSearch("");
},
resetAndSearch(keyword: string) {
const likeExample = keyword ? {
idea: keyword,
location: keyword
} : undefined;
const equalsExample = this.properties.type ? {
type: this.properties.type
} : undefined;
this.setData({
searchValue: keyword,
list: [],
page: {
index: 0,
size: 16,
type: JournalPageType.PREVIEW,
equalsExample,
likeExample
},
isFetching: false,
isFinished: false
}, () => {
// 无论是否有搜索词,都重新获取列表
this.fetch();
});
},
onSelectItem(e: WechatMiniprogram.BaseEvent) {
const { id } = e.currentTarget.dataset;
this.triggerEvent('select', { id });
if (this.properties.mode === 'navigate') {
// 跳转模式:触发 navigate 事件
this.triggerEvent('navigate', { id });
} else {
// 选择模式:触发 select 事件
this.triggerEvent('select', { id });
}
},
onScrollToLower() {
this.fetch();

View File

@ -1,40 +1,77 @@
<!--components/journal-list/index.wxml-->
<scroll-view
class="journal-list"
scroll-y
bindscrolltolower="onScrollToLower"
>
<t-cell-group>
<t-cell
class="item {{selectedId === item.id ? 'selected' : ''}}"
wx:for="{{list}}"
wx:key="id"
title="{{item.date}}"
description="{{item.idea}}"
bind:tap="onSelectItem"
data-id="{{item.id}}"
>
<image
slot="left-icon"
wx:if="{{item.thumbUrl}}"
class="thumb"
src="{{item.thumbUrl}}"
mode="aspectFill"
/>
<t-icon wx:if="{{!item.thumbUrl}}" slot="left-icon" name="image-off" color="gray" size="96rpx" />
<t-icon
wx:if="{{selectedId === item.id}}"
slot="note"
class="check"
name="check"
size="48rpx"
color="var(--theme-wx)"
/>
</t-cell>
</t-cell-group>
<view wx:if="{{isFetching}}" class="loading">
<t-loading theme="dots" size="40rpx" />
<view class="journal-list">
<view wx:if="{{searchable}}" class="search-bar">
<t-search
value="{{searchValue}}"
placeholder="搜索日记内容或地点"
bind:change="onSearchChange"
bind:submit="onSearchSubmit"
bind:clear="onSearchClear"
/>
</view>
<view wx:if="{{isFinished && list.length > 0}}" class="finished">没有更多了</view>
<view wx:if="{{!isFetching && list.length === 0}}" class="empty">暂无记录</view>
</scroll-view>
<scroll-view
class="scroll-content"
scroll-y
bindscrolltolower="onScrollToLower"
>
<block wx:if="{{list.length > 0}}">
<t-cell-group>
<t-cell
class="item {{selectedId === item.id ? 'selected' : ''}}"
wx:for="{{list}}"
wx:key="id"
hover
bind:tap="onSelectItem"
data-id="{{item.id}}"
>
<image
slot="left-icon"
wx:if="{{item.thumbUrl}}"
class="thumb"
src="{{item.thumbUrl}}"
mode="aspectFill"
/>
<t-icon wx:else slot="left-icon" name="image" color="gray" size="96rpx" />
<view slot="title">
<text class="date">{{item.date}}</text>
<text wx:if="{{item.idea}}"> · {{item.idea}}</text>
</view>
<text wx:if="{{item.location}}" class="location" slot="description">{{item.location}}</text>
<t-icon
wx:if="{{mode === 'select' && selectedId === item.id}}"
slot="right-icon"
class="check"
name="check"
size="48rpx"
color="var(--theme-wx)"
/>
<t-icon
wx:elif="{{mode === 'navigate'}}"
slot="right-icon"
name="chevron-right"
/>
</t-cell>
</t-cell-group>
<view wx:if="{{isFetching}}" class="loading">
<t-loading theme="circular" size="40rpx" text="加载中..." />
</view>
<view wx:elif="{{isFinished}}" class="finished">
{{searchable && searchValue ? '已显示全部结果' : '没有更多了'}}
</view>
</block>
<block wx:elif="{{isFetching}}">
<view class="loading">
<t-loading theme="circular" size="40rpx" text="加载中..." />
</view>
</block>
<block wx:elif="{{searchable && searchValue}}">
<t-empty icon="search" description="未找到相关日记" />
</block>
<block wx:else>
<t-empty icon="image" description="暂无记录" />
</block>
</scroll-view>
</view>

View File

@ -118,7 +118,7 @@
border-top-right-radius: 16rpx;
&.select-journal {
max-height: 80vh;
height: 80vh;
.content {
flex: 1;

View File

@ -184,6 +184,7 @@
<view class="content">
<journal-list
visible="{{archiveStep === 'select-journal'}}"
searchable="{{true}}"
type="{{type}}"
selectedId="{{selectedJournalId}}"
bind:select="onJournalListSelect"