add searchable
This commit is contained in:
@ -5,6 +5,8 @@
|
|||||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||||
"t-button": "tdesign-miniprogram/button/button",
|
"t-button": "tdesign-miniprogram/button/button",
|
||||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,29 +2,52 @@
|
|||||||
.journal-list {
|
.journal-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.item {
|
.search-bar {
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
&.selected {
|
flex-shrink: 0;
|
||||||
background: var(--td-bg-color-secondarycontainer);
|
background: var(--td-bg-color-container);
|
||||||
}
|
box-shadow: 0 4rpx 8rpx var(--theme-shadow-light);
|
||||||
|
|
||||||
.thumb {
|
|
||||||
width: 96rpx;
|
|
||||||
height: 96rpx;
|
|
||||||
object-fit: cover;
|
|
||||||
background: var(--td-bg-color-component);
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 16rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading,
|
.scroll-content {
|
||||||
.finished,
|
flex: 1;
|
||||||
.empty {
|
height: 0;
|
||||||
color: var(--td-text-color-placeholder);
|
padding: 0;
|
||||||
padding: 24rpx 0;
|
|
||||||
text-align: center;
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import config from "../../config/index";
|
import config from "../../config/index";
|
||||||
import { JournalPage, JournalPageType } from "../../types/Journal";
|
import { JournalPage, JournalPageType } from "../../types/Journal";
|
||||||
import Time from "../../utils/Time";
|
import Time from "../../utils/Time";
|
||||||
|
import Toolkit from "../../utils/Toolkit";
|
||||||
|
|
||||||
export type JournalListItem = {
|
export type JournalListItem = {
|
||||||
id: number;
|
id: number;
|
||||||
@ -16,9 +17,15 @@ interface JournalListData {
|
|||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
isFinished: boolean;
|
isFinished: boolean;
|
||||||
page: JournalPage;
|
page: JournalPage;
|
||||||
|
searchValue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component({
|
// 组件实例类型扩展
|
||||||
|
interface ComponentInstance {
|
||||||
|
debouncedSearch?: ((keyword: string) => void) & { cancel(): void };
|
||||||
|
}
|
||||||
|
|
||||||
|
Component<JournalListData, {}, {}, ComponentInstance>({
|
||||||
options: {
|
options: {
|
||||||
styleIsolation: 'apply-shared'
|
styleIsolation: 'apply-shared'
|
||||||
},
|
},
|
||||||
@ -34,6 +41,14 @@ Component({
|
|||||||
selectedId: {
|
selectedId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
value: undefined
|
value: undefined
|
||||||
|
},
|
||||||
|
searchable: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false // 是否显示搜索框
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
value: 'select' // 'select' 选择模式 或 'navigate' 跳转模式
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: <JournalListData>{
|
data: <JournalListData>{
|
||||||
@ -44,27 +59,63 @@ Component({
|
|||||||
index: 0,
|
index: 0,
|
||||||
size: 16,
|
size: 16,
|
||||||
type: JournalPageType.PREVIEW
|
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: {
|
observers: {
|
||||||
'visible': function(visible: boolean) {
|
'visible': function(visible: boolean) {
|
||||||
|
// visible 从 false 变为 true 时,如果还没有数据则加载
|
||||||
if (visible && this.data.list.length === 0) {
|
if (visible && this.data.list.length === 0) {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'type': function() {
|
'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({
|
this.setData({
|
||||||
page: {
|
page: {
|
||||||
index: 0,
|
index: 0,
|
||||||
size: 16,
|
size: 16,
|
||||||
type: JournalPageType.PREVIEW
|
type: JournalPageType.PREVIEW,
|
||||||
|
equalsExample,
|
||||||
|
likeExample
|
||||||
},
|
},
|
||||||
list: [],
|
list: [],
|
||||||
isFinished: false
|
isFinished: false
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch() {
|
fetch() {
|
||||||
if (this.data.isFetching || this.data.isFinished) {
|
if (this.data.isFetching || this.data.isFinished) {
|
||||||
return;
|
return;
|
||||||
@ -88,7 +139,7 @@ Component({
|
|||||||
const firstThumb = journal.items.find((item: any) => item.attachType === "THUMB");
|
const firstThumb = journal.items.find((item: any) => item.attachType === "THUMB");
|
||||||
return {
|
return {
|
||||||
id: journal.id,
|
id: journal.id,
|
||||||
date: Time.toPassedDateTime(journal.createdAt),
|
date: Time.toPassedDate(journal.createdAt),
|
||||||
idea: journal.idea,
|
idea: journal.idea,
|
||||||
location: journal.location,
|
location: journal.location,
|
||||||
thumbUrl: firstThumb ? `${config.url}/attachment/read/${firstThumb.mongoId}` : undefined
|
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) {
|
onSelectItem(e: WechatMiniprogram.BaseEvent) {
|
||||||
const { id } = e.currentTarget.dataset;
|
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() {
|
onScrollToLower() {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
|||||||
@ -1,40 +1,77 @@
|
|||||||
<!--components/journal-list/index.wxml-->
|
<!--components/journal-list/index.wxml-->
|
||||||
<scroll-view
|
<view class="journal-list">
|
||||||
class="journal-list"
|
<view wx:if="{{searchable}}" class="search-bar">
|
||||||
scroll-y
|
<t-search
|
||||||
bindscrolltolower="onScrollToLower"
|
value="{{searchValue}}"
|
||||||
>
|
placeholder="搜索日记内容或地点"
|
||||||
<t-cell-group>
|
bind:change="onSearchChange"
|
||||||
<t-cell
|
bind:submit="onSearchSubmit"
|
||||||
class="item {{selectedId === item.id ? 'selected' : ''}}"
|
bind:clear="onSearchClear"
|
||||||
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>
|
</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>
|
||||||
|
|||||||
@ -118,7 +118,7 @@
|
|||||||
border-top-right-radius: 16rpx;
|
border-top-right-radius: 16rpx;
|
||||||
|
|
||||||
&.select-journal {
|
&.select-journal {
|
||||||
max-height: 80vh;
|
height: 80vh;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@ -184,6 +184,7 @@
|
|||||||
<view class="content">
|
<view class="content">
|
||||||
<journal-list
|
<journal-list
|
||||||
visible="{{archiveStep === 'select-journal'}}"
|
visible="{{archiveStep === 'select-journal'}}"
|
||||||
|
searchable="{{true}}"
|
||||||
type="{{type}}"
|
type="{{type}}"
|
||||||
selectedId="{{selectedJournalId}}"
|
selectedId="{{selectedJournalId}}"
|
||||||
bind:select="onJournalListSelect"
|
bind:select="onJournalListSelect"
|
||||||
|
|||||||
Reference in New Issue
Block a user