jihua-search.vue
5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<!--
基础版训练计划搜索页(仅关键词搜索)
页面位于:训记横向导航栏的‘计划’模块,右侧搜索图标点击后进入的页面就是,核心功能是提供一个简单的搜索界面,允许用户输入关键词搜索训练计划,还未完成
-->
<template>
<view class="search-page">
<!-- 顶部搜索栏 -->
<view class="search-header">
<view class="search-input-wrapper">
<uni-icons type="search" size="18" color="#999" class="search-icon"></uni-icons>
<input ref="searchInput" v-model="searchKeyword" class="search-input" type="text" placeholder="搜索训练计划"
placeholder-style="color:#999" confirm-type="search" @confirm="handleSearch" focus />
</view>
<view class="cancel-btn" @click="handleCancel">
<text class="cancel-text">取消</text>
</view>
</view>
<!-- 搜索结果区域 -->
<view class="search-results">
<!-- 未搜索时提示 -->
<view v-if="!hasSearched" class="empty-hint">
<text>请输入关键词搜索训练计划</text>
</view>
<!-- 已搜索但无结果 -->
<view v-else-if="searchResults.length === 0" class="empty-hint">
<text>未找到相关训练计划</text>
</view>
<!-- 搜索结果列表 -->
<view v-else class="results-list">
<view v-for="plan in searchResults" :key="plan.id" class="result-item" @click="goToDetail(plan)">
<image v-if="plan.cover" :src="plan.cover" class="result-cover" mode="aspectFill" />
<view class="result-info">
<text class="result-title">{{ plan.title }}</text>
<text class="result-meta" v-if="plan.meta">{{ plan.meta }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import QueryPlanApi from '@/sheep/api/plan/queryplan'
// 响应式数据
const searchKeyword = ref('');
const hasSearched = ref(false);
const searchResults = ref([]);
const allPlans = ref([]);
const searchInput = ref(null);
// 难度/频率/场景/人群 数字转文字映射(和列表页保持一致)
const difficultyText = { 1: '初阶', 2: '中阶', 3: '高阶' };
const frequencyText = { 0: '不限', 1: '1练/周', 2: '2练/周', 3: '3练/周', 4: '4练/周', 5: '5练/周', 6: '6练/周', 7: '7练/周' };
// 页面加载时获取全局数据
onMounted(() => {
// 自动聚焦输入框(uni-app 需要延迟)
setTimeout(() => {
if (searchInput.value && typeof searchInput.value.focus === 'function') {
searchInput.value.focus();
}
}, 300);
});
// 返回
const goBack = () => {
uni.navigateBack();
};
// 执行接口搜索
const handleSearch = async () => {
const keyword = searchKeyword.value.trim();
hasSearched.value = true;
if (!keyword) {
searchResults.value = [];
return;
}
// 调用接口:根据名称模糊查询计划
try {
const res = await QueryPlanApi.searchPlansByName(keyword);
// 接口返回成功,处理数据
if (res.code === 0) {
// 映射接口字段到页面字段
searchResults.value = res.data.map(item => ({
id: item.id,
title: item.name, // 接口字段name → 页面字段title
cover: item.urlCover, // 接口字段urlCover → 页面字段cover
meta: `${difficultyText[item.difficultyLevel] || '初阶'} · ${frequencyText[item.frequencyPerWeek] || '1练/周'}`
}));
} else {
// 接口返回错误,提示用户
uni.showToast({ title: res.msg || '搜索失败', icon: 'none' });
searchResults.value = [];
}
} catch (err) {
// 网络/接口异常处理
uni.showToast({ title: '网络异常,请重试', icon: 'none' });
searchResults.value = [];
console.error('搜索接口调用失败:', err);
}
};
// 取消并返回
const handleCancel = () => {
uni.navigateBack();
};
// 点击跳转详情(根据你项目结构调整)
const goToDetail = (plan) => {
if (!plan?.id) {
uni.showToast({ title: '计划信息异常', icon: 'none' });
return;
}
uni.navigateTo({
url: `/pages4/pages/xunji/xunji-xunlian-jihua?planid=${plan.id}`,
});
};
</script>
<style lang="scss" scoped>
.search-page {
// 整个页面往下偏移,避免被顶部导航栏遮挡
padding-top: 160rpx;
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 顶部搜索栏 */
.search-header {
display: flex;
align-items: center;
padding: 20rpx 20rpx 20rpx 30rpx;
background-color: #ffffff;
// box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
z-index: 999;
// 固定顶部搜索栏
position: fixed;
top: 160rpx;
left: 0;
right: 0;
}
.search-input-wrapper {
flex: 1;
background-color: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
height: 64rpx;
display: flex;
align-items: center;
gap: 10rpx;
}
.search-icon {
flex-shrink: 0;
}
.search-input {
flex: 1;
height: 100%;
font-size: 28rpx;
color: #333;
background: transparent;
border: none;
}
.cancel-btn {
margin-left: 24rpx;
padding: 10rpx 0;
height: 64rpx;
display: flex;
align-items: center;
}
.cancel-text {
font-size: 28rpx;
color: #333;
/* 主题绿色,与你的 UI 一致 */
}
/* 搜索结果区域 */
.search-results {
margin-top: 100rpx; // 改成 100rpx,避免被搜索栏遮挡
flex: 1;
padding: 30rpx 20rpx;
overflow-y: auto;
}
.empty-hint {
text-align: center;
color: #999;
font-size: 28rpx;
margin-top: 200rpx;
}
.results-list {
display: flex;
flex-direction: column;
}
.result-item {
display: flex;
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
padding: 20rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
}
.result-cover {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.result-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.result-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.result-meta {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
</style>