vuepress-theme-uniapp-official 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/DcloudSearchPage/ai-result.styl +61 -0
- package/components/DcloudSearchPage/components/AIChat/index.vue +27 -22
- package/components/DcloudSearchPage/components/AIChat/markdown-loader.js +26 -14
- package/components/DcloudSearchPage/components/{AIChat/skeleton.styl → Skeleton.vue} +16 -4
- package/components/DcloudSearchPage/index.vue +82 -10
- package/components/DcloudSearchPage/utils/postDcloudServer.js +99 -106
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.ai-answer-card
|
|
2
|
+
padding 14px 16px
|
|
3
|
+
margin-top 12px
|
|
4
|
+
background #fff
|
|
5
|
+
border-radius 12px
|
|
6
|
+
border 1px solid rgba(0,0,0,0.06)
|
|
7
|
+
box-shadow 0 2px 8px rgba(0,0,0,0.04)
|
|
8
|
+
transition box-shadow .25s, transform .2s
|
|
9
|
+
cursor pointer
|
|
10
|
+
|
|
11
|
+
&:hover
|
|
12
|
+
box-shadow 0 4px 14px rgba(0,0,0,0.08)
|
|
13
|
+
transform translateY(-1px)
|
|
14
|
+
/* 重置部分元素样式,防止样式冲突 */
|
|
15
|
+
pre
|
|
16
|
+
margin: 0
|
|
17
|
+
padding: 5px
|
|
18
|
+
border-radius 10px
|
|
19
|
+
background #f6f8fa
|
|
20
|
+
& + pre
|
|
21
|
+
margin-top 8px
|
|
22
|
+
pre, code
|
|
23
|
+
white-space: pre-wrap; /* 允许换行 */
|
|
24
|
+
word-wrap: break-word; /* 允许长行断开 */
|
|
25
|
+
word-break: break-word;
|
|
26
|
+
h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
|
|
27
|
+
line-height: normal
|
|
28
|
+
margin: 0
|
|
29
|
+
padding: 0
|
|
30
|
+
ul, ol
|
|
31
|
+
list-style: none
|
|
32
|
+
|
|
33
|
+
.ai-answer-header
|
|
34
|
+
display flex
|
|
35
|
+
align-items center
|
|
36
|
+
margin-bottom 8px
|
|
37
|
+
|
|
38
|
+
.ai-answer-icon
|
|
39
|
+
font-size 18px
|
|
40
|
+
margin-right 6px
|
|
41
|
+
|
|
42
|
+
.ai-answer-title
|
|
43
|
+
font-weight 600
|
|
44
|
+
font-size 15px
|
|
45
|
+
color #333
|
|
46
|
+
|
|
47
|
+
.ai-answer-msg
|
|
48
|
+
font-size 14px
|
|
49
|
+
line-height normal
|
|
50
|
+
color #555
|
|
51
|
+
// white-space pre-wrap
|
|
52
|
+
word-break break-word
|
|
53
|
+
animation fadeIn .3s ease
|
|
54
|
+
|
|
55
|
+
@keyframes fadeIn
|
|
56
|
+
from
|
|
57
|
+
opacity 0
|
|
58
|
+
transform translateY(4px)
|
|
59
|
+
to
|
|
60
|
+
opacity 1
|
|
61
|
+
transform translateY(0)
|
|
@@ -28,14 +28,7 @@
|
|
|
28
28
|
</div>
|
|
29
29
|
</transition-group>
|
|
30
30
|
|
|
31
|
-
<
|
|
32
|
-
<div class="content">
|
|
33
|
-
<div class="line"></div>
|
|
34
|
-
<div class="line"></div>
|
|
35
|
-
<div class="line short"></div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
31
|
+
<Skeleton style="width: 60%" v-if="sending" />
|
|
39
32
|
</main>
|
|
40
33
|
|
|
41
34
|
<footer class="chat-input-bar">
|
|
@@ -52,14 +45,14 @@
|
|
|
52
45
|
</template>
|
|
53
46
|
|
|
54
47
|
<script setup>
|
|
55
|
-
import { ref, nextTick, watchEffect, onMounted
|
|
48
|
+
import { ref, nextTick, watchEffect, onMounted } from 'vue'
|
|
56
49
|
import { renderMarkdown } from "./markdown-loader";
|
|
57
|
-
import 'highlight.js/styles/github.min.css'
|
|
58
50
|
import SelectPlatform from './SelectPlatform.vue';
|
|
59
51
|
import { ajax } from '../../utils/postDcloudServer';
|
|
60
52
|
import searchPageConfig from '@theme-config/searchPage';
|
|
53
|
+
import Skeleton from '../Skeleton';
|
|
61
54
|
|
|
62
|
-
const { aiPlatforms = [], aiChatForDocSearch } = searchPageConfig;
|
|
55
|
+
const { aiPlatforms = [], aiChatForDocSearch = 'https://ai-assist-api.dcloud.net.cn/tbox/chatForDocSearch' } = searchPageConfig;
|
|
63
56
|
|
|
64
57
|
const props = defineProps({
|
|
65
58
|
currentCategory: {
|
|
@@ -98,7 +91,8 @@ watchEffect(() => {
|
|
|
98
91
|
if (props.visible) {
|
|
99
92
|
nextTick(() => {
|
|
100
93
|
scrollToBottom()
|
|
101
|
-
autoGrow(
|
|
94
|
+
autoGrow()
|
|
95
|
+
input.value.focus()
|
|
102
96
|
})
|
|
103
97
|
}
|
|
104
98
|
})
|
|
@@ -111,11 +105,11 @@ function formatTime() {
|
|
|
111
105
|
.padStart(2, '0')}`
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
function autoGrow(
|
|
108
|
+
function autoGrow() {
|
|
115
109
|
const el = input.value
|
|
116
110
|
el.style.height = 'auto'
|
|
117
111
|
scrollToBottom()
|
|
118
|
-
if (
|
|
112
|
+
if (inputText.value.length === 0) {
|
|
119
113
|
return
|
|
120
114
|
}
|
|
121
115
|
el.style.height = el.scrollHeight + 'px'
|
|
@@ -164,12 +158,21 @@ function platformChange(newPlatform) {
|
|
|
164
158
|
sendPlatform.value = newPlatform
|
|
165
159
|
}
|
|
166
160
|
|
|
161
|
+
// 限制只取最近 6 条消息
|
|
162
|
+
function getChatHistory() {
|
|
163
|
+
return messages.value.slice(-6).map(m => ({
|
|
164
|
+
role: m.role,
|
|
165
|
+
contentType: 'text',
|
|
166
|
+
content: m.raw
|
|
167
|
+
}))
|
|
168
|
+
}
|
|
169
|
+
|
|
167
170
|
async function send() {
|
|
168
171
|
if (!inputText.value.trim() || sending.value) return
|
|
169
172
|
|
|
170
173
|
const userText = inputText.value.trim()
|
|
171
174
|
inputText.value = ''
|
|
172
|
-
autoGrow(
|
|
175
|
+
autoGrow()
|
|
173
176
|
|
|
174
177
|
// 用户消息
|
|
175
178
|
messages.value.push({
|
|
@@ -186,17 +189,18 @@ async function send() {
|
|
|
186
189
|
|
|
187
190
|
let fakeReply = ''
|
|
188
191
|
try {
|
|
189
|
-
const res = await ajax(aiChatForDocSearch
|
|
192
|
+
const res = await ajax(aiChatForDocSearch, 'POST', {
|
|
190
193
|
"question": userText,
|
|
191
|
-
"group_name": sendPlatform.value
|
|
194
|
+
"group_name": sendPlatform.value,
|
|
195
|
+
"history": getChatHistory()
|
|
192
196
|
})
|
|
193
197
|
if (res.errorCode === 0) {
|
|
194
198
|
fakeReply = res.chunk
|
|
195
199
|
} else {
|
|
196
|
-
fakeReply = `抱歉,AI 助手出错了:${res.
|
|
200
|
+
fakeReply = `抱歉,AI 助手出错了:${res.errorMsg || '未知错误'}`
|
|
197
201
|
}
|
|
198
202
|
} catch (error) {
|
|
199
|
-
|
|
203
|
+
fakeReply = `抱歉,AI 助手出错了:${error.message || '未知错误'}`
|
|
200
204
|
}
|
|
201
205
|
sending.value = false
|
|
202
206
|
|
|
@@ -210,6 +214,7 @@ async function send() {
|
|
|
210
214
|
isTyping: false,
|
|
211
215
|
like: 0
|
|
212
216
|
}
|
|
217
|
+
|
|
213
218
|
messages.value.push(aiMsg)
|
|
214
219
|
|
|
215
220
|
// 动态打字
|
|
@@ -223,8 +228,6 @@ window.addEventListener('resize', scrollToBottom)
|
|
|
223
228
|
</script>
|
|
224
229
|
|
|
225
230
|
<style lang="stylus">
|
|
226
|
-
@import './skeleton.styl'
|
|
227
|
-
|
|
228
231
|
.chat-wrapper
|
|
229
232
|
display flex
|
|
230
233
|
flex-direction column
|
|
@@ -288,6 +291,8 @@ window.addEventListener('resize', scrollToBottom)
|
|
|
288
291
|
word-break break-word
|
|
289
292
|
box-shadow 0 1px 3px rgba(0,0,0,0.08)
|
|
290
293
|
pre
|
|
294
|
+
margin: 0
|
|
295
|
+
padding: 5px
|
|
291
296
|
border-radius 10px
|
|
292
297
|
& + pre
|
|
293
298
|
margin-top 8px
|
|
@@ -295,7 +300,7 @@ window.addEventListener('resize', scrollToBottom)
|
|
|
295
300
|
white-space: pre-wrap; /* 允许换行 */
|
|
296
301
|
word-wrap: break-word; /* 允许长行断开 */
|
|
297
302
|
word-break: break-word;
|
|
298
|
-
h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
|
|
303
|
+
h1, h2, h3, h4, h5, h6, p, ul, ol, dl, figure, blockquote
|
|
299
304
|
margin: 0
|
|
300
305
|
padding: 0
|
|
301
306
|
ul, ol
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
// markdown-loader.js
|
|
2
2
|
let markedInstance = null;
|
|
3
|
-
let hljsInstance = null;
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
// `1.` 转义为 `1、`,防止 marked 解析失败
|
|
5
|
+
function escapeMD(str) {
|
|
6
|
+
return str
|
|
7
|
+
.replace(/(\s*\b)(\d+)\./g, '$1$2、')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getLangCodeFromExtension(extension) {
|
|
11
|
+
const extensionMap = {
|
|
12
|
+
vue: 'markup',
|
|
13
|
+
html: 'markup',
|
|
14
|
+
md: 'markdown',
|
|
15
|
+
rb: 'ruby',
|
|
16
|
+
ts: 'typescript',
|
|
17
|
+
py: 'python',
|
|
18
|
+
sh: 'bash',
|
|
19
|
+
yml: 'yaml',
|
|
20
|
+
styl: 'stylus',
|
|
21
|
+
kt: 'kotlin',
|
|
22
|
+
rs: 'rust',
|
|
23
|
+
uts: 'typescript',
|
|
24
|
+
json5: 'json',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return extensionMap[extension] || extension;
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
export async function renderMarkdown(md) {
|
|
@@ -21,7 +35,7 @@ export async function renderMarkdown(md) {
|
|
|
21
35
|
headerIds: false,
|
|
22
36
|
mangle: false,
|
|
23
37
|
highlight(code, lang) {
|
|
24
|
-
lang =
|
|
38
|
+
lang = getLangCodeFromExtension(lang);
|
|
25
39
|
if (lang && hljs.getLanguage(lang)) {
|
|
26
40
|
return hljs.highlight(code, { language: lang }).value;
|
|
27
41
|
}
|
|
@@ -30,8 +44,6 @@ export async function renderMarkdown(md) {
|
|
|
30
44
|
});
|
|
31
45
|
|
|
32
46
|
markedInstance = marked;
|
|
33
|
-
hljsInstance = hljs;
|
|
34
47
|
}
|
|
35
|
-
|
|
36
|
-
return markedInstance.parse(md || '');
|
|
48
|
+
return markedInstance.parse(escapeMD(md || ''));
|
|
37
49
|
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="chat-skeleton chat-skeleton-left">
|
|
3
|
+
<div class="chat-skeleton_content">
|
|
4
|
+
<div class="chat-skeleton_content_line"></div>
|
|
5
|
+
<div class="chat-skeleton_content_line"></div>
|
|
6
|
+
<div class="chat-skeleton_content_line short"></div>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<style lang="stylus" scope>
|
|
1
12
|
/* Skeleton base style */
|
|
2
13
|
.skeleton
|
|
3
14
|
background: #eee
|
|
@@ -18,13 +29,12 @@
|
|
|
18
29
|
gap: 12px
|
|
19
30
|
padding: 12px 0
|
|
20
31
|
|
|
21
|
-
.chat-skeleton .
|
|
32
|
+
.chat-skeleton .chat-skeleton_content_line
|
|
22
33
|
height: 14px
|
|
23
|
-
width: 60%
|
|
24
34
|
border-radius: 6px
|
|
25
35
|
@extend .skeleton
|
|
26
36
|
|
|
27
|
-
.chat-skeleton .
|
|
37
|
+
.chat-skeleton .chat-skeleton_content_line.short
|
|
28
38
|
width: 40%
|
|
29
39
|
|
|
30
40
|
/* 左侧消息骨架(AI) */
|
|
@@ -32,8 +42,10 @@
|
|
|
32
42
|
display: flex
|
|
33
43
|
flex-direction: row
|
|
34
44
|
gap: 10px
|
|
35
|
-
.
|
|
45
|
+
.chat-skeleton_content
|
|
36
46
|
flex: 1
|
|
37
47
|
display: flex
|
|
38
48
|
flex-direction: column
|
|
39
49
|
gap: 10px
|
|
50
|
+
|
|
51
|
+
</style>
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
<div class="input-wrap">
|
|
30
30
|
<input ref="searchInput" class="search-input" :placeholder="placeholder" type="text" @keydown.enter="
|
|
31
31
|
() => {
|
|
32
|
-
|
|
32
|
+
resetSearch();
|
|
33
33
|
search();
|
|
34
34
|
}
|
|
35
35
|
" v-model="searchValue" />
|
|
36
36
|
<span class="search-input-btn">
|
|
37
37
|
<button @click="
|
|
38
38
|
() => {
|
|
39
|
-
|
|
39
|
+
resetSearch();
|
|
40
40
|
search();
|
|
41
41
|
}
|
|
42
42
|
">
|
|
@@ -83,7 +83,21 @@
|
|
|
83
83
|
<div class="result-wrap">
|
|
84
84
|
<template v-if="isAlgolia">
|
|
85
85
|
<template v-for="item in resultList">
|
|
86
|
-
<
|
|
86
|
+
<template v-if="item">
|
|
87
|
+
<Results v-if="!item.isAI" :key="item.sourceId" :title="item.title" :results="item.items"
|
|
88
|
+
:onSelect="item.onSelect" />
|
|
89
|
+
<template v-else>
|
|
90
|
+
<div class="ai-answer-card">
|
|
91
|
+
<div class="ai-answer-header">
|
|
92
|
+
<span class="ai-answer-icon">🤖</span>
|
|
93
|
+
<span class="ai-answer-title">{{ item.title }}</span>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div v-if="item.msg.length" class="ai-answer-msg" v-html="item.msg" />
|
|
97
|
+
<Skeleton v-else />
|
|
98
|
+
</div>
|
|
99
|
+
</template>
|
|
100
|
+
</template>
|
|
87
101
|
</template>
|
|
88
102
|
</template>
|
|
89
103
|
|
|
@@ -128,17 +142,21 @@
|
|
|
128
142
|
</template>
|
|
129
143
|
|
|
130
144
|
<script>
|
|
145
|
+
import searchPageConfig from '@theme-config/searchPage';
|
|
131
146
|
import NavbarLogo from '../NavbarLogo.vue';
|
|
132
147
|
import Results from './components/Results.vue';
|
|
133
148
|
import pagination from './components/pagination.vue';
|
|
134
149
|
import AIChat from './components/AIChat/index.vue';
|
|
135
150
|
import MainNavbarLink from '../MainNavbarLink.vue';
|
|
151
|
+
import Skeleton from './components/Skeleton.vue';
|
|
136
152
|
import { search as searchClient } from './utils/searchClient';
|
|
137
153
|
import { postExt, postAsk } from './utils/postDcloudServer';
|
|
138
154
|
import { forbidScroll, debounce } from '../../util';
|
|
139
155
|
import { removeHighlightTags, isEditingContent } from './utils/searchUtils';
|
|
140
|
-
import searchPageConfig from '@theme-config/searchPage';
|
|
141
156
|
import Base64 from './utils/Base64';
|
|
157
|
+
import { ajax } from './utils/postDcloudServer';
|
|
158
|
+
import { renderMarkdown } from "./components/AIChat/markdown-loader";
|
|
159
|
+
import 'highlight.js/styles/github.min.css'
|
|
142
160
|
|
|
143
161
|
const {
|
|
144
162
|
enableAI = true,
|
|
@@ -147,9 +165,11 @@ const {
|
|
|
147
165
|
searchBox: { placeholder, buttonText, searchBy },
|
|
148
166
|
resultsScreen: { resultsText, noResultsText, askNoResultsText },
|
|
149
167
|
},
|
|
150
|
-
extraFacetFilters = []
|
|
168
|
+
extraFacetFilters = [],
|
|
169
|
+
aiChatForDocSearch = 'https://ai-assist-api.dcloud.net.cn/tbox/chatForDocSearch'
|
|
151
170
|
} = searchPageConfig;
|
|
152
171
|
const crawlerUrl = 'https://zh.uniapp.dcloud.io/'
|
|
172
|
+
const AIErrorMsg = '很抱歉,AI 助手未能找到相关答案。请尝试更换关键词进行搜索。'
|
|
153
173
|
|
|
154
174
|
const resolveRoutePathFromUrl = (url, base = '/') => {
|
|
155
175
|
if (url.indexOf(crawlerUrl) === 0) {
|
|
@@ -165,7 +185,7 @@ export default {
|
|
|
165
185
|
|
|
166
186
|
props: ['options'],
|
|
167
187
|
|
|
168
|
-
components: { NavbarLogo, Results, pagination, MainNavbarLink, AIChat },
|
|
188
|
+
components: { NavbarLogo, Results, pagination, MainNavbarLink, AIChat, Skeleton },
|
|
169
189
|
|
|
170
190
|
data() {
|
|
171
191
|
return {
|
|
@@ -191,6 +211,13 @@ export default {
|
|
|
191
211
|
totalPage: 0, // 搜索结果总共页数
|
|
192
212
|
curPage: 1, // 当前页
|
|
193
213
|
pageSize: 0, // 每页条数
|
|
214
|
+
|
|
215
|
+
searchAIResult: Promise.resolve(null),
|
|
216
|
+
aiMessage: {
|
|
217
|
+
isAI: true,
|
|
218
|
+
title: 'AI 助手回答',
|
|
219
|
+
msg: ''
|
|
220
|
+
}
|
|
194
221
|
};
|
|
195
222
|
},
|
|
196
223
|
|
|
@@ -260,10 +287,10 @@ export default {
|
|
|
260
287
|
});
|
|
261
288
|
},
|
|
262
289
|
|
|
263
|
-
searchValue: debounce(function () {
|
|
264
|
-
this.
|
|
290
|
+
/* searchValue: debounce(function () {
|
|
291
|
+
this.resetSearch();
|
|
265
292
|
this.search();
|
|
266
|
-
}, 300),
|
|
293
|
+
}, 300), */
|
|
267
294
|
|
|
268
295
|
$route: {
|
|
269
296
|
immediate: true,
|
|
@@ -320,8 +347,20 @@ export default {
|
|
|
320
347
|
}
|
|
321
348
|
},
|
|
322
349
|
|
|
323
|
-
|
|
350
|
+
resetSearch() {
|
|
324
351
|
this.searchPage = 0;
|
|
352
|
+
this.resetAI();
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
resetAI() {
|
|
356
|
+
if (this.searchAIResult && typeof this.searchAIResult.abort === 'function') {
|
|
357
|
+
this.searchAIResult.abort()
|
|
358
|
+
}
|
|
359
|
+
this.aiMessage.msg = ''
|
|
360
|
+
this.searchAIResult = null
|
|
361
|
+
if (this.enableAI && this.searchValue.trim().length) {
|
|
362
|
+
this.searchByAI()
|
|
363
|
+
}
|
|
325
364
|
},
|
|
326
365
|
|
|
327
366
|
research(curPage) {
|
|
@@ -345,11 +384,16 @@ export default {
|
|
|
345
384
|
items,
|
|
346
385
|
};
|
|
347
386
|
});
|
|
387
|
+
|
|
348
388
|
this.noResult = !this.resultList.length;
|
|
349
389
|
this.curHits = nbHits;
|
|
350
390
|
this.pageSize = hitsPerPage;
|
|
351
391
|
this.totalPage = nbPages;
|
|
352
392
|
this.curPage = page + 1;
|
|
393
|
+
|
|
394
|
+
if (this.curPage === 1 && this.enableAI) {
|
|
395
|
+
this.resultList.splice(1, 0, this.aiMessage);
|
|
396
|
+
}
|
|
353
397
|
})
|
|
354
398
|
.finally(() => {
|
|
355
399
|
this.showLoading = false
|
|
@@ -396,6 +440,33 @@ export default {
|
|
|
396
440
|
);
|
|
397
441
|
},
|
|
398
442
|
|
|
443
|
+
searchByAI() {
|
|
444
|
+
try {
|
|
445
|
+
this.searchAIResult = ajax(aiChatForDocSearch, 'POST', {
|
|
446
|
+
"question": this.searchValue,
|
|
447
|
+
"group_name": this.currentCategory.text
|
|
448
|
+
}).then(res => {
|
|
449
|
+
if (res.errorCode === 0) {
|
|
450
|
+
return renderMarkdown(res.chunk)
|
|
451
|
+
} else {
|
|
452
|
+
this.aiMessage.msg = res.errorMsg || AIErrorMsg;
|
|
453
|
+
return ''
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
.catch((err) => {
|
|
457
|
+
console.log('err :>> ', err);
|
|
458
|
+
return ''
|
|
459
|
+
})
|
|
460
|
+
.then(msg => {
|
|
461
|
+
this.aiMessage.msg = msg || AIErrorMsg;
|
|
462
|
+
})
|
|
463
|
+
} catch (err) {
|
|
464
|
+
console.log('err :>> ', err);
|
|
465
|
+
this.aiMessage.msg = err.message || AIErrorMsg;
|
|
466
|
+
return ''
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
|
|
399
470
|
searchByServer(append = false) {
|
|
400
471
|
const { tag } = this.currentCategory;
|
|
401
472
|
const query = this.searchValue || '';
|
|
@@ -509,4 +580,5 @@ export default {
|
|
|
509
580
|
|
|
510
581
|
<style lang="stylus">
|
|
511
582
|
@import './index'
|
|
583
|
+
@import './ai-result.styl'
|
|
512
584
|
</style>
|
|
@@ -1,74 +1,76 @@
|
|
|
1
|
-
const isProduction = process.env.NODE_ENV ===
|
|
2
|
-
const isMock = false
|
|
3
|
-
import mock from './mock'
|
|
1
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
2
|
+
const isMock = false;
|
|
3
|
+
import mock from './mock';
|
|
4
4
|
|
|
5
5
|
export function ajax(url = '', method = 'get', data = {}) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
if (!url) return Promise.reject('url 不可为空');
|
|
7
|
+
const xhr = new XMLHttpRequest();
|
|
8
|
+
const p = new Promise((resolve, reject) => {
|
|
9
|
+
xhr.open(method, url);
|
|
10
|
+
xhr.onreadystatechange = function () {
|
|
11
|
+
if (this.readyState == 4 && this.status == 200) {
|
|
12
|
+
try {
|
|
13
|
+
resolve(JSON.parse(this.response));
|
|
14
|
+
} catch (error) {
|
|
15
|
+
resolve(this.response);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
if (method.toLowerCase() === 'post') {
|
|
20
|
+
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
|
21
|
+
xhr.send(JSON.stringify(data));
|
|
22
|
+
} else {
|
|
23
|
+
xhr.send();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
p.abort = () => xhr.abort();
|
|
27
|
+
return p;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export async function postExt(query) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
const base = isProduction ? '//ext.dcloud.net.cn' : '/ext';
|
|
32
|
+
let extRet;
|
|
33
|
+
if (!isMock) {
|
|
34
|
+
const extRes = await ajax(base + '/search/json?query=' + encodeURIComponent(query));
|
|
35
|
+
extRet = JSON.parse(extRes);
|
|
36
|
+
} else {
|
|
37
|
+
extRet = mock.ext;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let extHtml = '';
|
|
41
|
+
let data = extRet.data;
|
|
42
|
+
if (extRet.ret === 0) {
|
|
43
|
+
for (let i = 0, len = data.length; i < len; i++) {
|
|
44
|
+
extHtml += _renderExt(data[i], query);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
html: extHtml,
|
|
49
|
+
hits: data.length,
|
|
50
|
+
};
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export async function postAsk(query, page = 1) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
const base = isProduction ? '//ask.dcloud.net.cn' : '/ask';
|
|
55
|
+
let askHtml = '';
|
|
56
|
+
|
|
57
|
+
if (!isMock) {
|
|
58
|
+
askHtml = await ajax(base + `/search/ajax/search_result/search_type-all__q-${query}__page-${page}`);
|
|
59
|
+
if (!askHtml) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
askHtml = mock.askHtml;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
html: askHtml,
|
|
68
|
+
hits: 0,
|
|
69
|
+
};
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function _renderExt(ext, keyword) {
|
|
71
|
-
|
|
73
|
+
return `<div class="matching-post">
|
|
72
74
|
<a href="${ext.url}" target="_blank">
|
|
73
75
|
<div class="post-wrapper">
|
|
74
76
|
<h2>
|
|
@@ -81,30 +83,30 @@ function _renderExt(ext, keyword) {
|
|
|
81
83
|
<p>${ext.total_download}次下载</p>
|
|
82
84
|
<p>${_handleHTMLString(ext.description, keyword)}</p>
|
|
83
85
|
</a>
|
|
84
|
-
</div
|
|
86
|
+
</div>`;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
function _renderPost(post, value) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
90
|
+
let html = '';
|
|
91
|
+
let commentText = '';
|
|
92
|
+
let tagName = '规范';
|
|
93
|
+
|
|
94
|
+
// 1,问题;2,文章;默认是规范。
|
|
95
|
+
switch (post.type) {
|
|
96
|
+
case 'questions':
|
|
97
|
+
tagName = '问题';
|
|
98
|
+
break;
|
|
99
|
+
case 'articles':
|
|
100
|
+
tagName = '文章';
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!!value) {
|
|
105
|
+
post.title = _handleHTMLString(post.title, value);
|
|
106
|
+
post.content = _handleHTMLString(post.content, value);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
html += `<div class="matching-post">
|
|
108
110
|
<a href="${post.url}" target="_blank">
|
|
109
111
|
<div class="post-wrapper">
|
|
110
112
|
<h2>
|
|
@@ -113,38 +115,29 @@ function _renderPost(post, value) {
|
|
|
113
115
|
</p>
|
|
114
116
|
${post.title}
|
|
115
117
|
</h2>
|
|
116
|
-
</div
|
|
118
|
+
</div>`;
|
|
117
119
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
if (!!value) {
|
|
121
|
+
commentText = post.type === 'questions' ? '回复' : '评论';
|
|
122
|
+
html += `<p>
|
|
121
123
|
${post.comment_count}个${commentText}
|
|
122
124
|
<span class="aw-text-space">-</span>
|
|
123
125
|
${post.view_count}次浏览
|
|
124
126
|
</p>`;
|
|
125
|
-
|
|
127
|
+
}
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
html += `\n<p>${post.content}</p>\n</a>\n</div>`;
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
return html;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
function _handleHTMLString(dataString, keyword) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
'</span>',
|
|
143
|
-
'g'
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
return dataString
|
|
147
|
-
.replace(tagStartReg, '')
|
|
148
|
-
.replace(tagEndReg, '')
|
|
149
|
-
.replace(keywordReg, ("<em class=\"search-keyword\">" + keyword + "</em>"));
|
|
150
|
-
};
|
|
135
|
+
let keywordReg = new RegExp(keyword.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'), 'gi');
|
|
136
|
+
let tagStartReg = new RegExp("<span style='font-weight:bold;color:red'>", 'g');
|
|
137
|
+
let tagEndReg = new RegExp('</span>', 'g');
|
|
138
|
+
|
|
139
|
+
return dataString
|
|
140
|
+
.replace(tagStartReg, '')
|
|
141
|
+
.replace(tagEndReg, '')
|
|
142
|
+
.replace(keywordReg, '<em class="search-keyword">' + keyword + '</em>');
|
|
143
|
+
}
|