|
@@ -8,47 +8,61 @@
|
|
|
<image src="/pages-sell/static/search/icon-scan.png" class="search-icon-left" mode="aspectFit"></image>
|
|
<image src="/pages-sell/static/search/icon-scan.png" class="search-icon-left" mode="aspectFit"></image>
|
|
|
<input class="search-input" v-model="keyword" placeholder="书名 / 作者 / ISBN" confirm-type="search"
|
|
<input class="search-input" v-model="keyword" placeholder="书名 / 作者 / ISBN" confirm-type="search"
|
|
|
@confirm="onSearch" :focus="true" />
|
|
@confirm="onSearch" :focus="true" />
|
|
|
|
|
+ <u-icon name="close-circle-fill" color="#c0c4cc" size="32" v-if="keyword" @click="keyword = ''"
|
|
|
|
|
+ class="clear-icon"></u-icon>
|
|
|
<view class="search-btn" @click="onSearch">
|
|
<view class="search-btn" @click="onSearch">
|
|
|
<text>搜索</text>
|
|
<text>搜索</text>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- History -->
|
|
|
|
|
- <view class="section history-section" v-if="historyList.length">
|
|
|
|
|
- <view class="section-header">
|
|
|
|
|
- <text class="title">搜索历史</text>
|
|
|
|
|
- <image src="/pages-sell/static/search/icon-delete.png" class="icon-delete" mode="aspectFit"
|
|
|
|
|
- @click="clearHistory"></image>
|
|
|
|
|
- </view>
|
|
|
|
|
- <view class="tags-list">
|
|
|
|
|
- <view class="tag-item history-tag" v-for="(item, index) in historyList" :key="index"
|
|
|
|
|
- @click="doSearch(item)">
|
|
|
|
|
- {{ item }}
|
|
|
|
|
|
|
+ <!-- Search Prompt -->
|
|
|
|
|
+ <view class="prompt-list" v-if="keyword.trim()">
|
|
|
|
|
+ <view class="prompt-item" v-for="(item, index) in promptList" :key="index" @click="doSearch(item)">
|
|
|
|
|
+ <rich-text :nodes="highlightKeyword(item, keyword)" class="prompt-text"></rich-text>
|
|
|
|
|
+ <view class="arrow-wrap" @click.stop="fillKeyword(item)">
|
|
|
|
|
+ <u-icon name="arrow-up" color="#ccc" size="28" style="transform: rotate(45deg);"></u-icon>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- Hot Search -->
|
|
|
|
|
- <view class="section hot-section">
|
|
|
|
|
- <view class="section-header">
|
|
|
|
|
- <view class="left">
|
|
|
|
|
- <image src="/pages-sell/static/search/icon-fire.png" class="icon-fire" mode="aspectFit"></image>
|
|
|
|
|
- <text class="title">热门搜索</text>
|
|
|
|
|
|
|
+ <view v-else>
|
|
|
|
|
+ <!-- History -->
|
|
|
|
|
+ <view class="section history-section" v-if="historyList.length">
|
|
|
|
|
+ <view class="section-header">
|
|
|
|
|
+ <text class="title">搜索历史</text>
|
|
|
|
|
+ <image src="/pages-sell/static/search/icon-delete.png" class="icon-delete" mode="aspectFit"
|
|
|
|
|
+ @click="clearHistory"></image>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="tags-list">
|
|
|
|
|
+ <view class="tag-item history-tag" v-for="(item, index) in historyList" :key="index"
|
|
|
|
|
+ @click="fillKeyword(item)">
|
|
|
|
|
+ {{ item }}
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
- <image
|
|
|
|
|
- :src="hotVisible ? '/pages-sell/static/search/icon-view.png' : '/pages-sell/static/search/icon-view.png'"
|
|
|
|
|
- class="icon-view" mode="aspectFit" @click="toggleHot" :style="{ opacity: hotVisible ? 1 : 0.5 }">
|
|
|
|
|
- </image>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
- <view class="tags-list" v-if="hotVisible">
|
|
|
|
|
- <view class="tag-item" v-for="(item, index) in hotList" :key="index" @click="doSearch(item.name)"
|
|
|
|
|
- :class="item.className">
|
|
|
|
|
- <image v-if="item.tag === 'NEW'" src="/pages-sell/static/search/icon-new.png" class="tag-icon-new"
|
|
|
|
|
- mode="heightFix"></image>
|
|
|
|
|
- <image v-if="item.tag === 'HOT'" src="/pages-sell/static/search/icon-fire.png" class="tag-icon-fire"
|
|
|
|
|
- mode="heightFix"></image>
|
|
|
|
|
- <text>{{ item.name }}</text>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Hot Search -->
|
|
|
|
|
+ <view class="section hot-section">
|
|
|
|
|
+ <view class="section-header">
|
|
|
|
|
+ <view class="left">
|
|
|
|
|
+ <image src="/pages-sell/static/search/icon-fire.png" class="icon-fire" mode="aspectFit"></image>
|
|
|
|
|
+ <text class="title">热门搜索</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <image
|
|
|
|
|
+ :src="hotVisible ? '/pages-sell/static/search/icon-view.png' : '/pages-sell/static/search/icon-view.png'"
|
|
|
|
|
+ class="icon-view" mode="aspectFit" @click="toggleHot" :style="{ opacity: hotVisible ? 1 : 0.5 }">
|
|
|
|
|
+ </image>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="tags-list" v-if="hotVisible">
|
|
|
|
|
+ <view class="tag-item" v-for="(item, index) in hotList" :key="index" @click="fillKeyword(item.name)"
|
|
|
|
|
+ :class="item.className">
|
|
|
|
|
+ <image v-if="item.tag === 'NEW'" src="/pages-sell/static/search/icon-new.png" class="tag-icon-new"
|
|
|
|
|
+ mode="heightFix"></image>
|
|
|
|
|
+ <image v-if="item.tag === 'HOT'" src="/pages-sell/static/search/icon-fire.png" class="tag-icon-fire"
|
|
|
|
|
+ mode="heightFix"></image>
|
|
|
|
|
+ <text>{{ item.name }}</text>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
@@ -68,6 +82,20 @@
|
|
|
historyList: [],
|
|
historyList: [],
|
|
|
hotVisible: true,
|
|
hotVisible: true,
|
|
|
hotList: [],
|
|
hotList: [],
|
|
|
|
|
+ promptList: [],
|
|
|
|
|
+ inputTimer: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ keyword(val) {
|
|
|
|
|
+ if (!val.trim()) {
|
|
|
|
|
+ this.promptList = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.inputTimer) clearTimeout(this.inputTimer);
|
|
|
|
|
+ this.inputTimer = setTimeout(() => {
|
|
|
|
|
+ this.getPromptData(val.trim());
|
|
|
|
|
+ }, 300);
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
onShow() {
|
|
onShow() {
|
|
@@ -75,6 +103,28 @@
|
|
|
this.getHotData();
|
|
this.getHotData();
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ getPromptData(keyword) {
|
|
|
|
|
+ this.$u.api.getSearchPromptAjax({ keyword }).then(res => {
|
|
|
|
|
+ if (res.code == 200) {
|
|
|
|
|
+ this.promptList = res.data || [];
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ highlightKeyword(text, keyword) {
|
|
|
|
|
+ if (!text) return '';
|
|
|
|
|
+ // 如果后端返回了 <em> 标签,直接替换样式
|
|
|
|
|
+ if (text.includes('<em>')) {
|
|
|
|
|
+ return text
|
|
|
|
|
+ .replace(/<em>/g, '<span style="color: #37C148; font-style: normal;">')
|
|
|
|
|
+ .replace(/<\/em>/g, '</span>');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!keyword) return text;
|
|
|
|
|
+ // escape keyword for regex
|
|
|
|
|
+ const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
+ const reg = new RegExp(`(${escapedKeyword})`, 'gi');
|
|
|
|
|
+ return text.replace(reg, '<span style="color: #37C148; font-style: normal;">$1</span>');
|
|
|
|
|
+ },
|
|
|
getHistoryData() {
|
|
getHistoryData() {
|
|
|
this.$u.api.getSearchHistoryAjax().then(res => {
|
|
this.$u.api.getSearchHistoryAjax().then(res => {
|
|
|
this.historyList = res.data || [];
|
|
this.historyList = res.data || [];
|
|
@@ -93,17 +143,25 @@
|
|
|
this.doSearch(this.keyword);
|
|
this.doSearch(this.keyword);
|
|
|
},
|
|
},
|
|
|
doSearch(key) {
|
|
doSearch(key) {
|
|
|
- console.log('Search:', key);
|
|
|
|
|
- this.keyword = key;
|
|
|
|
|
|
|
+ if (!key) return;
|
|
|
|
|
+ // 去除可能包含的 HTML 标签 (如后端返回的 <em>)
|
|
|
|
|
+ const cleanKey = key.replace(/<[^>]+>/g, '');
|
|
|
|
|
+ console.log('Search:', cleanKey);
|
|
|
|
|
+ this.keyword = cleanKey;
|
|
|
// Navigate to result page or show results
|
|
// Navigate to result page or show results
|
|
|
uni.showToast({
|
|
uni.showToast({
|
|
|
- title: '搜索: ' + key,
|
|
|
|
|
|
|
+ title: '搜索: ' + cleanKey,
|
|
|
icon: 'none'
|
|
icon: 'none'
|
|
|
});
|
|
});
|
|
|
uni.navigateTo({
|
|
uni.navigateTo({
|
|
|
- url: '/pages-sell/pages/search-result?keyword=' + key
|
|
|
|
|
|
|
+ url: '/pages-sell/pages/search-result?keyword=' + cleanKey
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
|
|
+ fillKeyword(key) {
|
|
|
|
|
+ if (!key) return;
|
|
|
|
|
+ const cleanKey = key.replace(/<[^>]+>/g, '');
|
|
|
|
|
+ this.keyword = cleanKey;
|
|
|
|
|
+ },
|
|
|
clearHistory() {
|
|
clearHistory() {
|
|
|
uni.showModal({
|
|
uni.showModal({
|
|
|
title: '提示',
|
|
title: '提示',
|
|
@@ -162,6 +220,10 @@
|
|
|
color: #333;
|
|
color: #333;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .clear-icon {
|
|
|
|
|
+ margin: 0 10rpx;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.search-btn {
|
|
.search-btn {
|
|
|
width: 120rpx;
|
|
width: 120rpx;
|
|
|
height: 60rpx;
|
|
height: 60rpx;
|
|
@@ -249,4 +311,26 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .prompt-list {
|
|
|
|
|
+ padding: 0 30rpx;
|
|
|
|
|
+
|
|
|
|
|
+ .prompt-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 30rpx 0;
|
|
|
|
|
+ border-bottom: 1rpx solid #F5F5F5;
|
|
|
|
|
+
|
|
|
|
|
+ .prompt-text {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .arrow-wrap {
|
|
|
|
|
+ padding: 10rpx;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
</style>
|
|
</style>
|