vuepress-theme-uniapp-official 1.6.10 → 1.6.11
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/AIChat/index.vue +16 -20
- package/components/DcloudSearchPage/components/AIAnswer.vue +5 -35
- package/components/DcloudSearchPage/components/AIFeedback.vue +246 -0
- package/components/DcloudSearchPage/constants.js +1 -0
- package/components/DcloudSearchPage/index.vue +3 -1
- package/components/DcloudSearchPage/utils/aiUtils.js +29 -0
- package/global-components/Popover.vue +7 -63
- package/package.json +3 -3
- package/util/index.js +311 -243
|
@@ -13,10 +13,9 @@
|
|
|
13
13
|
<div class="meta">
|
|
14
14
|
<span class="time">{{ m.time }}</span>
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
</div> -->
|
|
16
|
+
<div class="actions" v-if="m.role === 'assistant' && m.uni_ai_feedback_id.length > 0">
|
|
17
|
+
<AIFeedback :like="m.like" :dislike="m.dislike" :uni_ai_feedback_id="m.uni_ai_feedback_id" @action="data => feedbackAction(m.id, data)"/>
|
|
18
|
+
</div>
|
|
20
19
|
</div>
|
|
21
20
|
|
|
22
21
|
</div>
|
|
@@ -59,13 +58,13 @@
|
|
|
59
58
|
import { ref, nextTick, watchEffect, onMounted, computed } from 'vue'
|
|
60
59
|
import searchPageConfig from '@theme-config/searchPage';
|
|
61
60
|
import { renderMarkdown } from "./markdown-loader";
|
|
62
|
-
import { MAX_AI_ANSWER_LENGTH, AI_CHAT_FOR_DOC_SEARCH_URL } from '../constants';
|
|
61
|
+
import { MAX_AI_ANSWER_LENGTH, AI_CHAT_FOR_DOC_SEARCH_URL, AI_UPDATE_FEEDBACK_URL } from '../constants';
|
|
63
62
|
import { ajax } from '../utils/postDcloudServer';
|
|
64
63
|
import SelectPlatform from '../components/SelectPlatform.vue';
|
|
65
|
-
import
|
|
64
|
+
import AIFeedback from '../components/AIFeedback.vue';
|
|
66
65
|
import Skeleton from '../components/Skeleton.vue';
|
|
67
66
|
|
|
68
|
-
const { aiPlatforms = [], aiChatForDocSearch = AI_CHAT_FOR_DOC_SEARCH_URL } = searchPageConfig;
|
|
67
|
+
const { aiPlatforms = [], aiChatForDocSearch = AI_CHAT_FOR_DOC_SEARCH_URL, aiUpdateFeedbackUrl = AI_UPDATE_FEEDBACK_URL } = searchPageConfig;
|
|
69
68
|
|
|
70
69
|
const props = defineProps({
|
|
71
70
|
currentCategory: {
|
|
@@ -232,6 +231,7 @@ async function send() {
|
|
|
232
231
|
sending.value = true
|
|
233
232
|
|
|
234
233
|
let fakeReply = ''
|
|
234
|
+
let uni_ai_feedback_id = ''
|
|
235
235
|
try {
|
|
236
236
|
const res = await ajax(aiChatForDocSearch, 'POST', {
|
|
237
237
|
"question": userText,
|
|
@@ -240,6 +240,7 @@ async function send() {
|
|
|
240
240
|
})
|
|
241
241
|
if (res.errorCode === 0) {
|
|
242
242
|
fakeReply = res.chunk
|
|
243
|
+
uni_ai_feedback_id = res.uni_ai_feedback_id
|
|
243
244
|
} else {
|
|
244
245
|
fakeReply = `抱歉,AI 助手出错了:${res.errorMsg || '未知错误'}`
|
|
245
246
|
}
|
|
@@ -253,6 +254,7 @@ async function send() {
|
|
|
253
254
|
id: Date.now() + 1,
|
|
254
255
|
role: 'assistant',
|
|
255
256
|
raw: fakeReply,
|
|
257
|
+
uni_ai_feedback_id, // 从接口获取反馈 ID
|
|
256
258
|
rendered: await renderHTML(fakeReply),
|
|
257
259
|
time: formatTime(),
|
|
258
260
|
isTyping: false,
|
|
@@ -269,19 +271,13 @@ async function send() {
|
|
|
269
271
|
scrollToBottom()
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
function
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
function dislike(m) {
|
|
281
|
-
console.log('dislike');
|
|
282
|
-
m.dislike = !m.dislike;
|
|
283
|
-
if (m.dislike) {
|
|
284
|
-
m.like = false;
|
|
274
|
+
function feedbackAction(messageId, data) {
|
|
275
|
+
// 更新对应消息的反馈状态
|
|
276
|
+
const msg = messages.value.find(m => m.id === messageId);
|
|
277
|
+
if (msg) {
|
|
278
|
+
msg.like = data.like;
|
|
279
|
+
msg.dislike = data.dislike;
|
|
280
|
+
setSessionMessages(messages.value)
|
|
285
281
|
}
|
|
286
282
|
}
|
|
287
283
|
|
|
@@ -13,23 +13,17 @@
|
|
|
13
13
|
本回答由 AI 生成,可能已过期、失效或不适用于当前情形,仅供参考
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
</div> -->
|
|
16
|
+
<div v-show="hasMessage && props.item.uni_ai_feedback_id.length > 0">
|
|
17
|
+
<AIFeedback :uni_ai_feedback_id="props.item.uni_ai_feedback_id"/>
|
|
18
|
+
</div>
|
|
20
19
|
</div>
|
|
21
20
|
</div>
|
|
22
21
|
</template>
|
|
23
22
|
|
|
24
23
|
<script setup>
|
|
25
|
-
import
|
|
24
|
+
import { computed } from 'vue';
|
|
26
25
|
import Skeleton from './Skeleton.vue';
|
|
27
|
-
import
|
|
28
|
-
import { computed, reactive } from 'vue';
|
|
29
|
-
|
|
30
|
-
const {
|
|
31
|
-
aiChatForDocSearch = 'https://ai-assist-api.dcloud.net.cn/tbox/chatForDocSearch'
|
|
32
|
-
} = searchPageConfig;
|
|
26
|
+
import AIFeedback from './AIFeedback.vue';
|
|
33
27
|
|
|
34
28
|
const props = defineProps({
|
|
35
29
|
item: {
|
|
@@ -41,27 +35,6 @@ const props = defineProps({
|
|
|
41
35
|
const hasMessage = computed(() => {
|
|
42
36
|
return props.item.msg && props.item.msg.length > 0;
|
|
43
37
|
})
|
|
44
|
-
|
|
45
|
-
const status = reactive({
|
|
46
|
-
like: false,
|
|
47
|
-
dislike: false
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
function like() {
|
|
51
|
-
console.log('like');
|
|
52
|
-
status.like = !status.like;
|
|
53
|
-
if (status.like) {
|
|
54
|
-
status.dislike = false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function dislike() {
|
|
59
|
-
console.log('dislike');
|
|
60
|
-
status.dislike = !status.dislike;
|
|
61
|
-
if (status.dislike) {
|
|
62
|
-
status.like = false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
38
|
</script>
|
|
66
39
|
|
|
67
40
|
<style lang="stylus">
|
|
@@ -122,7 +95,4 @@ function dislike() {
|
|
|
122
95
|
justify-content space-between
|
|
123
96
|
font-size 12px
|
|
124
97
|
color #888
|
|
125
|
-
|
|
126
|
-
.ai-answer-footer_right
|
|
127
|
-
display flex
|
|
128
98
|
</style>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { reactive, computed, ref } from 'vue';
|
|
3
|
+
import searchPageConfig from '@theme-config/searchPage';
|
|
4
|
+
import LikeButton from './LikeButton.vue';
|
|
5
|
+
import { sendFeedback } from '../utils/aiUtils';
|
|
6
|
+
import { AI_UPDATE_FEEDBACK_URL } from '../constants';
|
|
7
|
+
import { usePopoverDirective } from '../../../util'
|
|
8
|
+
|
|
9
|
+
const { aiUpdateFeedbackUrl = AI_UPDATE_FEEDBACK_URL, } = searchPageConfig;
|
|
10
|
+
const reasons = [
|
|
11
|
+
{ label: '字数太长' },
|
|
12
|
+
{ label: '常识错误' },
|
|
13
|
+
{ label: '与提问不符' },
|
|
14
|
+
{ label: '产品体验差' }
|
|
15
|
+
];
|
|
16
|
+
const { mouseEnter, directive: vPopover } = usePopoverDirective('#search-container')
|
|
17
|
+
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
uni_ai_feedback_id: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: ''
|
|
22
|
+
},
|
|
23
|
+
like: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false
|
|
26
|
+
},
|
|
27
|
+
dislike: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
const emit = defineEmits(['action'])
|
|
33
|
+
|
|
34
|
+
const status = reactive({
|
|
35
|
+
like: props.like,
|
|
36
|
+
dislike: props.dislike
|
|
37
|
+
})
|
|
38
|
+
const selectedReasons = ref([]);
|
|
39
|
+
const comment = ref('');
|
|
40
|
+
const aiFeedbackVisible = ref(false);
|
|
41
|
+
const feedbackStatus = computed(() => {
|
|
42
|
+
if (!status.dislike && !status.like) {
|
|
43
|
+
return ''
|
|
44
|
+
}
|
|
45
|
+
if (status.like) {
|
|
46
|
+
return true
|
|
47
|
+
} else {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
const lastStatus = reactive({
|
|
52
|
+
like: false,
|
|
53
|
+
dislike: false
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
function setLastStatus() {
|
|
57
|
+
lastStatus.like = status.like;
|
|
58
|
+
lastStatus.dislike = status.dislike;
|
|
59
|
+
}
|
|
60
|
+
function resetStatus() {
|
|
61
|
+
status.like = lastStatus.like;
|
|
62
|
+
status.dislike = lastStatus.dislike;
|
|
63
|
+
}
|
|
64
|
+
function feedback() {
|
|
65
|
+
sendFeedback(aiUpdateFeedbackUrl, {
|
|
66
|
+
uni_ai_feedback_id: props.uni_ai_feedback_id,
|
|
67
|
+
comment: `${selectedReasons.value.join(';')} ${comment.value}`.trim(),
|
|
68
|
+
status: feedbackStatus.value
|
|
69
|
+
})
|
|
70
|
+
.then((response) => {
|
|
71
|
+
emit('action', status)
|
|
72
|
+
if (!response) {
|
|
73
|
+
resetStatus()
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.catch(() => {
|
|
77
|
+
resetStatus()
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function like() {
|
|
82
|
+
if (props.uni_ai_feedback_id) {
|
|
83
|
+
setLastStatus()
|
|
84
|
+
status.like = !status.like;
|
|
85
|
+
if (status.like) {
|
|
86
|
+
status.dislike = false;
|
|
87
|
+
}
|
|
88
|
+
feedback()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function dislike() {
|
|
93
|
+
if (props.uni_ai_feedback_id) {
|
|
94
|
+
setLastStatus()
|
|
95
|
+
status.dislike = !status.dislike;
|
|
96
|
+
aiFeedbackVisible.value = status.dislike
|
|
97
|
+
if (status.dislike) {
|
|
98
|
+
status.like = false;
|
|
99
|
+
}
|
|
100
|
+
feedback()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toggleReason(text) {
|
|
105
|
+
const index = selectedReasons.value.indexOf(text);
|
|
106
|
+
if (index > -1) {
|
|
107
|
+
selectedReasons.value.splice(index, 1);
|
|
108
|
+
} else {
|
|
109
|
+
selectedReasons.value.push(text);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function closeComment() {
|
|
114
|
+
selectedReasons.value = [];
|
|
115
|
+
comment.value = '';
|
|
116
|
+
aiFeedbackVisible.value = false
|
|
117
|
+
}
|
|
118
|
+
function sendComment() {
|
|
119
|
+
feedback()
|
|
120
|
+
if (aiFeedbackVisible.value) {
|
|
121
|
+
selectedReasons.value = [];
|
|
122
|
+
comment.value = '';
|
|
123
|
+
aiFeedbackVisible.value = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<div style="display: flex;">
|
|
130
|
+
<LikeButton :active="status.like" type="like" @click.stop="like" />
|
|
131
|
+
<div class="dislike_container" @mouseenter="mouseEnter">
|
|
132
|
+
<LikeButton :active="status.dislike" type="dislike" @click="dislike" />
|
|
133
|
+
|
|
134
|
+
<div class="ai-feedback" v-show="aiFeedbackVisible" v-popover>
|
|
135
|
+
<div class="ai-feedback-header">进一步反馈</div>
|
|
136
|
+
|
|
137
|
+
<div class="tags">
|
|
138
|
+
<div v-for="item in reasons" :key="item.label" class="tag"
|
|
139
|
+
:class="{ active: selectedReasons.includes(item.label) }" @click="toggleReason(item.label)">
|
|
140
|
+
{{ item.label }}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<textarea v-model="comment" class="feedback-input" placeholder="具体原因和建议"></textarea>
|
|
145
|
+
|
|
146
|
+
<div class="footer">
|
|
147
|
+
<button class="btn cancel" @click="closeComment">关闭</button>
|
|
148
|
+
<button class="btn submit" @click="sendComment">提交</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</template>
|
|
154
|
+
|
|
155
|
+
<style lang="stylus" scoped>
|
|
156
|
+
.ai-feedback
|
|
157
|
+
position absolute
|
|
158
|
+
width 300px
|
|
159
|
+
background #fff
|
|
160
|
+
border-radius 14px
|
|
161
|
+
padding 18px
|
|
162
|
+
box-shadow 0 8px 28px rgba(0,0,0,0.12)
|
|
163
|
+
border 1px solid rgba(0,0,0,0.06)
|
|
164
|
+
animation fadeIn .2s ease
|
|
165
|
+
box-sizing border-box
|
|
166
|
+
|
|
167
|
+
.ai-feedback-header
|
|
168
|
+
font-size 16px
|
|
169
|
+
font-weight 600
|
|
170
|
+
margin-bottom 14px
|
|
171
|
+
color #222
|
|
172
|
+
|
|
173
|
+
.tags
|
|
174
|
+
width 100%
|
|
175
|
+
display flex
|
|
176
|
+
flex-wrap wrap
|
|
177
|
+
justify-content space-between
|
|
178
|
+
|
|
179
|
+
.tag
|
|
180
|
+
width calc(50% - 8px)
|
|
181
|
+
background #f2f3f5
|
|
182
|
+
border-radius 8px
|
|
183
|
+
padding 8px 0
|
|
184
|
+
margin-bottom 8px
|
|
185
|
+
text-align center
|
|
186
|
+
font-size 14px
|
|
187
|
+
color #444
|
|
188
|
+
cursor pointer
|
|
189
|
+
transition all .2s ease
|
|
190
|
+
|
|
191
|
+
.tag.active
|
|
192
|
+
background $accentColor
|
|
193
|
+
color #fff
|
|
194
|
+
|
|
195
|
+
.feedback-input
|
|
196
|
+
width 100%
|
|
197
|
+
box-sizing border-box
|
|
198
|
+
padding 10px 12px
|
|
199
|
+
border 1px solid #e5e5e5
|
|
200
|
+
border-radius 10px
|
|
201
|
+
font-size 14px
|
|
202
|
+
color #333
|
|
203
|
+
min-height 60px
|
|
204
|
+
resize none
|
|
205
|
+
transition border .2s
|
|
206
|
+
outline none
|
|
207
|
+
|
|
208
|
+
&:focus
|
|
209
|
+
border 1px solid $accentColor
|
|
210
|
+
|
|
211
|
+
.footer
|
|
212
|
+
display flex
|
|
213
|
+
justify-content flex-end
|
|
214
|
+
margin-top 8px
|
|
215
|
+
|
|
216
|
+
.btn
|
|
217
|
+
padding 8px 16px
|
|
218
|
+
border-radius 8px
|
|
219
|
+
font-size 14px
|
|
220
|
+
border none
|
|
221
|
+
cursor pointer
|
|
222
|
+
transition background .2s
|
|
223
|
+
|
|
224
|
+
.cancel
|
|
225
|
+
background #f2f3f5
|
|
226
|
+
color #333
|
|
227
|
+
|
|
228
|
+
.cancel:hover
|
|
229
|
+
background #e5e6e7
|
|
230
|
+
|
|
231
|
+
.submit
|
|
232
|
+
margin-left 8px
|
|
233
|
+
background rgba($accentColor, .9)
|
|
234
|
+
color #fff
|
|
235
|
+
|
|
236
|
+
.submit:hover
|
|
237
|
+
background $accentColor
|
|
238
|
+
|
|
239
|
+
@keyframes fadeIn
|
|
240
|
+
from
|
|
241
|
+
opacity 0
|
|
242
|
+
transform translateY(-4px)
|
|
243
|
+
to
|
|
244
|
+
opacity 1
|
|
245
|
+
transform translateY(0)
|
|
246
|
+
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const DEFAULT_ENABLE_AI = true; // 默认启用 AI 助手
|
|
2
2
|
export const MAX_AI_ANSWER_LENGTH = 6; // AI 消息最小长度
|
|
3
3
|
export const AI_CHAT_FOR_DOC_SEARCH_URL = 'https://ai-assist-api-p.dcloud.net.cn/http/tbox/chatForDocSearch'
|
|
4
|
+
export const AI_UPDATE_FEEDBACK_URL = 'https://ai-assist-api-p.dcloud.net.cn/http/tbox/updateFeedBackForDocSearch'
|
|
4
5
|
export const AI_ERROR_MSG = '很抱歉,AI 助手未能找到相关答案。请尝试更换关键词进行搜索。'
|
|
@@ -159,7 +159,7 @@ const {
|
|
|
159
159
|
resultsScreen: { resultsText, noResultsText, askNoResultsText },
|
|
160
160
|
},
|
|
161
161
|
extraFacetFilters = [],
|
|
162
|
-
aiChatForDocSearch = AI_CHAT_FOR_DOC_SEARCH_URL
|
|
162
|
+
aiChatForDocSearch = AI_CHAT_FOR_DOC_SEARCH_URL,
|
|
163
163
|
} = searchPageConfig;
|
|
164
164
|
const crawlerUrl = 'https://zh.uniapp.dcloud.io/'
|
|
165
165
|
|
|
@@ -206,6 +206,7 @@ export default {
|
|
|
206
206
|
|
|
207
207
|
searchAIResult: Promise.resolve(null),
|
|
208
208
|
aiMessage: {
|
|
209
|
+
uni_ai_feedback_id: '',
|
|
209
210
|
isAI: true,
|
|
210
211
|
title: 'AI 助手回答',
|
|
211
212
|
msg: ''
|
|
@@ -448,6 +449,7 @@ export default {
|
|
|
448
449
|
"question": this.searchValue,
|
|
449
450
|
"group_name": this.currentCategory.text
|
|
450
451
|
}).then(res => {
|
|
452
|
+
this.aiMessage.uni_ai_feedback_id = res.uni_ai_feedback_id || ''
|
|
451
453
|
if (res.errorCode === 0) {
|
|
452
454
|
return renderMarkdown(res.chunk)
|
|
453
455
|
} else {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ajax } from './postDcloudServer.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {string} url AI_UPDATE_FEEDBACK_URL
|
|
6
|
+
* @param {{ uni_ai_feedback_id: string, comment: string, status: boolean|string }} payload
|
|
7
|
+
*
|
|
8
|
+
* @returns {Promise<boolean>}
|
|
9
|
+
*/
|
|
10
|
+
export function sendFeedback(url, payload) {
|
|
11
|
+
const data = {
|
|
12
|
+
uni_ai_feedback_id: payload.uni_ai_feedback_id,
|
|
13
|
+
comment: payload.comment,
|
|
14
|
+
status: payload.status, // true: like, false: dislike, '': neutral
|
|
15
|
+
};
|
|
16
|
+
return ajax(url, 'post', data)
|
|
17
|
+
.then(response => {
|
|
18
|
+
if (response.errorCode === 0) {
|
|
19
|
+
return true;
|
|
20
|
+
} else {
|
|
21
|
+
console.error('sendFeedback response error:', response.errorMsg);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.catch(error => {
|
|
26
|
+
console.error('sendFeedback error:', error);
|
|
27
|
+
return false;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, useSlots } from 'vue'
|
|
3
|
-
import {
|
|
3
|
+
import { usePopoverDirective } from '../util'
|
|
4
4
|
|
|
5
5
|
const emit = defineEmits(['show', 'hide'])
|
|
6
6
|
|
|
7
|
+
const { mouseEnter, directive: vPopover } = usePopoverDirective('.theme-default-content')
|
|
8
|
+
|
|
7
9
|
const props = defineProps({
|
|
8
10
|
table: String
|
|
9
11
|
})
|
|
10
12
|
|
|
11
13
|
const infoHover = ref(false)
|
|
12
14
|
const slots = useSlots()
|
|
13
|
-
let mouseOptions = {}
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
mouseOptions = {
|
|
18
|
-
top,
|
|
19
|
-
left,
|
|
20
|
-
bottom,
|
|
21
|
-
right,
|
|
22
|
-
height,
|
|
23
|
-
width
|
|
24
|
-
}
|
|
16
|
+
const wrapperMouseEnter = (e) => {
|
|
17
|
+
mouseEnter(e)
|
|
25
18
|
infoHover.value = true
|
|
26
19
|
emit('show')
|
|
27
20
|
}
|
|
@@ -30,61 +23,12 @@ const mouseLeave = (e) => {
|
|
|
30
23
|
infoHover.value = false
|
|
31
24
|
emit('hide')
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
const vPopover = {
|
|
35
|
-
inserted(el, binding, vnode, prevVnode) {
|
|
36
|
-
const ThemeDefaultContent = document.querySelector('.theme-default-content')
|
|
37
|
-
if (ThemeDefaultContent) {
|
|
38
|
-
ThemeDefaultContent.appendChild(el)
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
componentUpdated(el, binding, vnode, prevVnode) {
|
|
42
|
-
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
|
|
43
|
-
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
|
|
44
|
-
const screenWidth = document.documentElement.clientWidth
|
|
45
|
-
|
|
46
|
-
const { width, height } = el.getBoundingClientRect()
|
|
47
|
-
const { left: targetLeft, top: targetTop, bottom: targetBottom, right: targetRight, height: targetHeight, width: targetWidth } = mouseOptions
|
|
48
|
-
const OFFSET = 10
|
|
49
|
-
const NAVBAR_HEIGHT = getNavbarHeight()
|
|
50
|
-
|
|
51
|
-
// 默认 icon 正中间位置
|
|
52
|
-
const HALF_WIDTH = width / 2
|
|
53
|
-
const HALF_TARGET_WIDTH = targetWidth / 2
|
|
54
|
-
const TOP = height // + OFFSET
|
|
55
|
-
const LEFT = HALF_TARGET_WIDTH - HALF_WIDTH
|
|
56
|
-
|
|
57
|
-
const DEFAULT_TOP = targetTop + scrollTop
|
|
58
|
-
const DEFAULT_LEFT = targetLeft + scrollLeft
|
|
59
|
-
|
|
60
|
-
let currentTop = DEFAULT_TOP - TOP
|
|
61
|
-
let currentLeft = DEFAULT_LEFT + LEFT
|
|
62
|
-
|
|
63
|
-
if (targetTop - NAVBAR_HEIGHT - TOP - OFFSET > 0) {
|
|
64
|
-
// 上方空间够
|
|
65
|
-
currentTop = currentTop - OFFSET
|
|
66
|
-
} else {
|
|
67
|
-
// 上方空间不够
|
|
68
|
-
currentTop = currentTop + (height + targetHeight) + OFFSET
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const RIGHT_OFFSET = (screenWidth - (targetLeft + HALF_TARGET_WIDTH)) - HALF_WIDTH
|
|
72
|
-
// const LEFT_OFFSET = (screenWidth - targetLeft - HALF_TARGET_WIDTH) - HALF_WIDTH
|
|
73
|
-
|
|
74
|
-
if (RIGHT_OFFSET < 0) {
|
|
75
|
-
// 右侧空间不够
|
|
76
|
-
currentLeft = screenWidth - width
|
|
77
|
-
}
|
|
78
|
-
el.style.top = `${currentTop < 0 ? 0 : currentTop}px`
|
|
79
|
-
el.style.left = `${currentLeft < 0 ? 0 : currentLeft}px`
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
26
|
</script>
|
|
83
27
|
|
|
84
28
|
<template>
|
|
85
29
|
<div class="popover" @mouseleave="mouseLeave">
|
|
86
30
|
<span>
|
|
87
|
-
<div class="popover-reference-wrapper" @mouseenter="
|
|
31
|
+
<div class="popover-reference-wrapper" @mouseenter="wrapperMouseEnter">
|
|
88
32
|
<slot name="reference"></slot>
|
|
89
33
|
<svg v-if="!slots.reference" width="20" height="20" t="1715917545486" class="icon" viewBox="0 0 1024 1024"
|
|
90
34
|
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10306" fill="#2c3e50">
|
|
@@ -113,7 +57,7 @@ const vPopover = {
|
|
|
113
57
|
.popover-content-wrapper {
|
|
114
58
|
position: absolute;
|
|
115
59
|
background-color: #fff;
|
|
116
|
-
z-index:
|
|
60
|
+
z-index: 601;
|
|
117
61
|
border-radius: 5px;
|
|
118
62
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
119
63
|
transition: opacity .1s cubic-bezier(0.57, 0.85, 0.85, 0.57);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vuepress-theme-uniapp-official",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.11",
|
|
4
4
|
"description": "uni-app official website theme for vuepress",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"vuepress-plugin-mermaidjs": "1.9.1",
|
|
46
46
|
"vuepress-plugin-named-chunks": "^1.1.4",
|
|
47
47
|
"vuepress-plugin-zooming": "^1.1.8",
|
|
48
|
-
"vuepress-plugin-expandable-row": "^1.0.10",
|
|
49
48
|
"vuepress-plugin-noscript-code": "^1.0.2",
|
|
50
|
-
"vuepress-plugin-check-md2": "^1.0.5"
|
|
49
|
+
"vuepress-plugin-check-md2": "^1.0.5",
|
|
50
|
+
"vuepress-plugin-expandable-row": "^1.0.10"
|
|
51
51
|
},
|
|
52
52
|
"resolutions": {
|
|
53
53
|
"terser-webpack-plugin": "1.4.6",
|
package/util/index.js
CHANGED
|
@@ -1,121 +1,119 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
2
|
|
|
3
|
-
export const isServer = Vue.prototype.$isServer
|
|
4
|
-
export const hashRE =
|
|
5
|
-
export const extRE = /\.(md|html)
|
|
6
|
-
export const endingSlashRE =
|
|
7
|
-
export const outboundRE = /^[a-z]+:/i
|
|
3
|
+
export const isServer = Vue.prototype.$isServer;
|
|
4
|
+
export const hashRE = /#.*$/;
|
|
5
|
+
export const extRE = /\.(md|html)$/;
|
|
6
|
+
export const endingSlashRE = /\/$/;
|
|
7
|
+
export const outboundRE = /^[a-z]+:/i;
|
|
8
8
|
|
|
9
9
|
export function normalize(path) {
|
|
10
|
-
|
|
11
|
-
.replace(hashRE, '')
|
|
12
|
-
.replace(extRE, '')
|
|
10
|
+
return decodeURI(path).replace(hashRE, '').replace(extRE, '');
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
export function getHash(path) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const match = path.match(hashRE);
|
|
15
|
+
if (match) {
|
|
16
|
+
return match[0];
|
|
17
|
+
}
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export function isExternal(path) {
|
|
23
|
-
|
|
21
|
+
return outboundRE.test(path);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
export function isMailto(path) {
|
|
27
|
-
|
|
25
|
+
return /^mailto:/.test(path);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
export function isTel(path) {
|
|
31
|
-
|
|
29
|
+
return /^tel:/.test(path);
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
export function ensureExt(path) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
if (isExternal(path)) {
|
|
34
|
+
return path;
|
|
35
|
+
}
|
|
36
|
+
const hashMatch = path.match(hashRE);
|
|
37
|
+
const hash = hashMatch ? hashMatch[0] : '';
|
|
38
|
+
const normalized = normalize(path);
|
|
39
|
+
|
|
40
|
+
if (endingSlashRE.test(normalized)) {
|
|
41
|
+
return path;
|
|
42
|
+
}
|
|
43
|
+
return normalized + '.html' + hash;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
export function isActive(route, path) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
const routeHash = decodeURIComponent(route.hash);
|
|
48
|
+
const linkHash = getHash(path);
|
|
49
|
+
if (linkHash && routeHash !== linkHash) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const routePath = normalize(route.path);
|
|
53
|
+
const pagePath = normalize(path);
|
|
54
|
+
return routePath === pagePath;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
export function resolvePage(pages, rawPath, base) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
58
|
+
if (isExternal(rawPath)) {
|
|
59
|
+
return {
|
|
60
|
+
type: 'external',
|
|
61
|
+
path: rawPath,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (base) {
|
|
65
|
+
rawPath = resolvePath(rawPath, base);
|
|
66
|
+
}
|
|
67
|
+
const path = normalize(rawPath);
|
|
68
|
+
const hash = rawPath.split('#')[1];
|
|
69
|
+
for (let i = 0; i < pages.length; i++) {
|
|
70
|
+
if (normalize(pages[i].regularPath) === path) {
|
|
71
|
+
return Object.assign({}, pages[i], {
|
|
72
|
+
type: 'page',
|
|
73
|
+
path: ensureExt(pages[i].path) + (hash ? `#${hash}` : ''),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`);
|
|
78
|
+
return {};
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
function resolvePath(relative, base, append) {
|
|
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
|
-
|
|
82
|
+
const firstChar = relative.charAt(0);
|
|
83
|
+
if (firstChar === '/') {
|
|
84
|
+
return relative;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (firstChar === '?' || firstChar === '#') {
|
|
88
|
+
return base + relative;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stack = base.split('/');
|
|
92
|
+
|
|
93
|
+
// remove trailing segment if:
|
|
94
|
+
// - not appending
|
|
95
|
+
// - appending to trailing slash (last segment is empty)
|
|
96
|
+
if (!append || !stack[stack.length - 1]) {
|
|
97
|
+
stack.pop();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// resolve relative path
|
|
101
|
+
const segments = relative.replace(/^\//, '').split('/');
|
|
102
|
+
for (let i = 0; i < segments.length; i++) {
|
|
103
|
+
const segment = segments[i];
|
|
104
|
+
if (segment === '..') {
|
|
105
|
+
stack.pop();
|
|
106
|
+
} else if (segment !== '.') {
|
|
107
|
+
stack.push(segment);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ensure leading slash
|
|
112
|
+
if (stack[0] !== '') {
|
|
113
|
+
stack.unshift('');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return stack.join('/');
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
/**
|
|
@@ -126,29 +124,25 @@ function resolvePath(relative, base, append) {
|
|
|
126
124
|
* @returns { SidebarGroup }
|
|
127
125
|
*/
|
|
128
126
|
export function resolveSidebarItems(page, regularPath, site, localePath) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return config
|
|
149
|
-
? config.map(item => resolveItem(item, pages, base))
|
|
150
|
-
: []
|
|
151
|
-
}
|
|
127
|
+
const { pages, themeConfig } = site;
|
|
128
|
+
|
|
129
|
+
const localeConfig = localePath && themeConfig.locales ? themeConfig.locales[localePath] || themeConfig : themeConfig;
|
|
130
|
+
|
|
131
|
+
const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar;
|
|
132
|
+
if (pageSidebarConfig === 'auto') {
|
|
133
|
+
return resolveHeaders(page);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
|
|
137
|
+
if (!sidebarConfig) {
|
|
138
|
+
return [];
|
|
139
|
+
} else {
|
|
140
|
+
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig);
|
|
141
|
+
if (config === 'auto') {
|
|
142
|
+
return resolveHeaders(page);
|
|
143
|
+
}
|
|
144
|
+
return config ? config.map(item => resolveItem(item, pages, base)) : [];
|
|
145
|
+
}
|
|
152
146
|
}
|
|
153
147
|
|
|
154
148
|
/**
|
|
@@ -156,41 +150,43 @@ export function resolveSidebarItems(page, regularPath, site, localePath) {
|
|
|
156
150
|
* @returns { SidebarGroup }
|
|
157
151
|
*/
|
|
158
152
|
function resolveHeaders(page) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
153
|
+
const headers = groupHeaders(page.headers || []);
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
type: 'group',
|
|
157
|
+
collapsable: false,
|
|
158
|
+
title: page.title,
|
|
159
|
+
path: null,
|
|
160
|
+
children: headers.map(h => ({
|
|
161
|
+
type: 'auto',
|
|
162
|
+
title: h.title,
|
|
163
|
+
basePath: page.path,
|
|
164
|
+
path: page.path + '#' + h.slug,
|
|
165
|
+
children: h.children || [],
|
|
166
|
+
})),
|
|
167
|
+
},
|
|
168
|
+
];
|
|
173
169
|
}
|
|
174
170
|
|
|
175
171
|
export function groupHeaders(headers) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
172
|
+
// group h3s under h2
|
|
173
|
+
headers = headers.map(h => Object.assign({}, h));
|
|
174
|
+
let lastH2;
|
|
175
|
+
headers.forEach(h => {
|
|
176
|
+
if (h.level === 2) {
|
|
177
|
+
lastH2 = h;
|
|
178
|
+
} else if (lastH2) {
|
|
179
|
+
(lastH2.children || (lastH2.children = [])).push(h);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
// TODO h1-h3
|
|
183
|
+
return headers.filter(h => h.level === 2);
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
export function resolveNavLinkItem(linkItem) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
return Object.assign(linkItem, {
|
|
188
|
+
type: linkItem.items && linkItem.items.length ? 'links' : 'link',
|
|
189
|
+
});
|
|
194
190
|
}
|
|
195
191
|
|
|
196
192
|
/**
|
|
@@ -199,100 +195,98 @@ export function resolveNavLinkItem(linkItem) {
|
|
|
199
195
|
* @returns { base: string, config: SidebarConfig }
|
|
200
196
|
*/
|
|
201
197
|
export function resolveMatchingConfig(regularPath, config) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
198
|
+
if (Array.isArray(config)) {
|
|
199
|
+
return {
|
|
200
|
+
base: '/',
|
|
201
|
+
config: config,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
for (const base in config) {
|
|
205
|
+
if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
|
|
206
|
+
return {
|
|
207
|
+
base,
|
|
208
|
+
config: config[base],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return {};
|
|
217
213
|
}
|
|
218
214
|
|
|
219
215
|
function ensureEndingSlash(path) {
|
|
220
|
-
|
|
221
|
-
? path
|
|
222
|
-
: path + '/'
|
|
216
|
+
return /(\.html|\/)$/.test(path) ? path : path + '/';
|
|
223
217
|
}
|
|
224
218
|
|
|
225
219
|
function resolveItem(item, pages, base, groupDepth = 1) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
220
|
+
if (typeof item === 'string') {
|
|
221
|
+
return resolvePage(pages, item, base);
|
|
222
|
+
} else if (Array.isArray(item)) {
|
|
223
|
+
return Object.assign(resolvePage(pages, item[0], base), {
|
|
224
|
+
title: item[1],
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
const children = item.children || [];
|
|
228
|
+
if (children.length === 0 && item.path) {
|
|
229
|
+
return Object.assign(resolvePage(pages, item.path, base), {
|
|
230
|
+
title: item.title,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
type: 'group',
|
|
235
|
+
path: item.path,
|
|
236
|
+
title: item.title,
|
|
237
|
+
sidebarDepth: item.sidebarDepth,
|
|
238
|
+
initialOpenGroupIndex: item.initialOpenGroupIndex,
|
|
239
|
+
children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)),
|
|
240
|
+
collapsable: item.collapsable !== false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
249
243
|
}
|
|
250
244
|
|
|
251
245
|
export function forbidScroll(use = true) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
246
|
+
if (use) {
|
|
247
|
+
const classList = document.body.className.split(' ');
|
|
248
|
+
classList.push('forbid_scroll');
|
|
249
|
+
document.body.className = classList.join(' ');
|
|
250
|
+
} else {
|
|
251
|
+
document.body.className = document.body.className.replace(/\s+forbid_scroll/g, '');
|
|
252
|
+
}
|
|
259
253
|
}
|
|
260
254
|
|
|
261
255
|
export const os = {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
256
|
+
android: false,
|
|
257
|
+
ios: false,
|
|
258
|
+
mobile: false,
|
|
259
|
+
pc: false,
|
|
260
|
+
init: function () {
|
|
261
|
+
var ua = navigator.userAgent;
|
|
262
|
+
if (ua.match(/(Android);?[\s\/]+([\d+.]+)?/)) {
|
|
263
|
+
this.android = true;
|
|
264
|
+
this.mobile = true;
|
|
265
|
+
this.pc = false;
|
|
266
|
+
} else if (ua.match(/(iPhone\sOS)\s([\d_]+)/)) {
|
|
267
|
+
this.ios = true;
|
|
268
|
+
this.mobile = true;
|
|
269
|
+
this.pc = false;
|
|
270
|
+
} else {
|
|
271
|
+
this.android = false;
|
|
272
|
+
this.ios = false;
|
|
273
|
+
this.mobile = false;
|
|
274
|
+
this.pc = true;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
283
277
|
};
|
|
284
278
|
|
|
285
279
|
export function debounce(fn, delay) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
280
|
+
let timeout;
|
|
281
|
+
const newFn = function () {
|
|
282
|
+
clearTimeout(timeout);
|
|
283
|
+
const timerFn = () => fn.apply(this, arguments);
|
|
284
|
+
timeout = setTimeout(timerFn, delay);
|
|
285
|
+
};
|
|
286
|
+
newFn.cancel = function () {
|
|
287
|
+
clearTimeout(timeout);
|
|
288
|
+
};
|
|
289
|
+
return newFn;
|
|
296
290
|
}
|
|
297
291
|
|
|
298
292
|
/*
|
|
@@ -303,31 +297,105 @@ export function debounce(fn, delay) {
|
|
|
303
297
|
* @returns {Element}
|
|
304
298
|
*/
|
|
305
299
|
export function findContainerInVm(ref, vm, def) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
300
|
+
if (!ref) return def;
|
|
301
|
+
let container;
|
|
302
|
+
let parent = vm;
|
|
303
|
+
while ((parent = parent.$parent) && !container) {
|
|
304
|
+
container = parent.$refs[ref];
|
|
305
|
+
}
|
|
306
|
+
// Ensure it's html element (ref could be component)
|
|
307
|
+
if (container && container.$el) {
|
|
308
|
+
container = container.$el;
|
|
309
|
+
}
|
|
310
|
+
return container || def;
|
|
317
311
|
}
|
|
318
312
|
|
|
319
313
|
export function once(fn, ctx = null) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
314
|
+
let res;
|
|
315
|
+
return (...args) => {
|
|
316
|
+
if (fn) {
|
|
317
|
+
res = fn.apply(ctx, args);
|
|
318
|
+
fn = null;
|
|
319
|
+
}
|
|
320
|
+
return res;
|
|
321
|
+
};
|
|
328
322
|
}
|
|
329
323
|
|
|
330
324
|
export const getNavbarHeight = () => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
325
|
+
const { height, top } = document.querySelector('.navbar').getBoundingClientRect();
|
|
326
|
+
return height + top;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export const usePopoverDirective = selector => {
|
|
330
|
+
let mouseOptions = {};
|
|
331
|
+
const mouseEnter = e => {
|
|
332
|
+
const { top, left, bottom, right, height, width } = e.target.getBoundingClientRect();
|
|
333
|
+
mouseOptions = {
|
|
334
|
+
top,
|
|
335
|
+
left,
|
|
336
|
+
bottom,
|
|
337
|
+
right,
|
|
338
|
+
height,
|
|
339
|
+
width,
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
mouseEnter,
|
|
345
|
+
directive: {
|
|
346
|
+
inserted(el, binding, vnode, prevVnode) {
|
|
347
|
+
const insertContent = document.querySelector(selector);
|
|
348
|
+
if (insertContent) {
|
|
349
|
+
insertContent.appendChild(el);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
componentUpdated(el, binding, vnode, prevVnode) {
|
|
353
|
+
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
|
354
|
+
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
|
|
355
|
+
const screenWidth = document.documentElement.clientWidth;
|
|
356
|
+
|
|
357
|
+
const { width, height } = el.getBoundingClientRect();
|
|
358
|
+
const {
|
|
359
|
+
left: targetLeft,
|
|
360
|
+
top: targetTop,
|
|
361
|
+
bottom: targetBottom,
|
|
362
|
+
right: targetRight,
|
|
363
|
+
height: targetHeight,
|
|
364
|
+
width: targetWidth,
|
|
365
|
+
} = mouseOptions;
|
|
366
|
+
const OFFSET = 10;
|
|
367
|
+
const NAVBAR_HEIGHT = getNavbarHeight();
|
|
368
|
+
|
|
369
|
+
// 默认 icon 正中间位置
|
|
370
|
+
const HALF_WIDTH = width / 2;
|
|
371
|
+
const HALF_TARGET_WIDTH = targetWidth / 2;
|
|
372
|
+
const TOP = height; // + OFFSET
|
|
373
|
+
const LEFT = HALF_TARGET_WIDTH - HALF_WIDTH;
|
|
374
|
+
|
|
375
|
+
const DEFAULT_TOP = targetTop + scrollTop;
|
|
376
|
+
const DEFAULT_LEFT = targetLeft + scrollLeft;
|
|
377
|
+
|
|
378
|
+
let currentTop = DEFAULT_TOP - TOP;
|
|
379
|
+
let currentLeft = DEFAULT_LEFT + LEFT;
|
|
380
|
+
|
|
381
|
+
if (targetTop - NAVBAR_HEIGHT - TOP - OFFSET > 0) {
|
|
382
|
+
// 上方空间够
|
|
383
|
+
currentTop = currentTop - OFFSET;
|
|
384
|
+
} else {
|
|
385
|
+
// 上方空间不够
|
|
386
|
+
currentTop = currentTop + (height + targetHeight) + OFFSET;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const RIGHT_OFFSET = screenWidth - (targetLeft + HALF_TARGET_WIDTH) - HALF_WIDTH;
|
|
390
|
+
// const LEFT_OFFSET = (screenWidth - targetLeft - HALF_TARGET_WIDTH) - HALF_WIDTH
|
|
391
|
+
|
|
392
|
+
if (RIGHT_OFFSET < 0) {
|
|
393
|
+
// 右侧空间不够
|
|
394
|
+
currentLeft = screenWidth - width;
|
|
395
|
+
}
|
|
396
|
+
el.style.top = `${currentTop < 0 ? 0 : currentTop}px`;
|
|
397
|
+
el.style.left = `${currentLeft < 0 ? 0 : currentLeft}px`;
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
};
|