提交 65816470 编写于 作者: D DCloud_LXH

refactor: use theme

上级 6e9646e2
......@@ -5,7 +5,7 @@ const createSidebar = require('./markdown/createSidebar')
const { simplifySlugText, tabs } = require('./utils')
const config = {
// TODO use theme
theme: 'vuepress-theme-uni-app-test',
title: 'uni-app官网',
head: [
['link', {
......
export const LOCALE_ZH_HANS = 'zh-CN'
export const LOCALE_EN = 'en-US'
\ No newline at end of file
const routerMap = {
'/collocation/frame/lifecycle#页面生命周期': '/tutorial/page.html#lifecycle',
'/collocation/frame/lifecycle#应用生命周期': '/collocation/App.html#applifecycle',
'/collocation/frame/lifetime': '/collocation/App.html#applifecycle',
'/collocation/frame/lifecycle': '/collocation/App.html#applifecycle',
'/collocation/frame/communication': '/tutorial/page.html#页面通讯',
'/collocation/frame/lifecycle#page': '/tutorial/page.html#lifecycle',
'/collocation/frame/lifecycle#component': '/tutorial/page.html#componentlifecycle',
'/collocation/frame/timer': '/api/timer.html',
'/collocation/auto/hbuilderx-extension/index': '/worktile/auto/hbuilderx-extension/',
'/collocation/auto/hbuilderx-extension/': '/worktile/auto/hbuilderx-extension/',
'/collocation/auto/quick-start': '/worktile/auto/quick-start.html',
'/collocation/auto/uniapp-cli-project': '/worktile/auto/uniapp-cli-project.html',
'/collocation/i18n': '/tutorial/i18n.html',
'/worktile/i18n': '/tutorial/i18n.html',
'/worktile/i18n.html': '/tutorial/i18n.html',
'/collocation/env': '/tutorial/env.html',
'/collocation/ssr': '/tutorial/ssr.html',
'/ssr': '/tutorial/ssr.html',
'/ssr/': '/tutorial/ssr.html',
'/collocation/frame/window': '/tutorial/page.html#getapp',
'/collocation/frame/window#getcurrentpages': '/tutorial/page.html#getcurrentpages',
'/collocation/auto/api': '/worktile/auto/api.html',
'/collocation/frame/log': '/api/log.html',
'/component/mp-weixin-plugin': '/tutorial/mp-weixin-plugin.html',
'/component/uniui': '/component/uniui/uni-ui.html',
'/frame': '/tutorial/',
'/frame#renderjs': '/tutorial/renderjs.html',
'/frame#css变量': '/tutorial/syntax-css.html#css-变量',
'/frame#css引入静态资源': '/tutorial/page-static-assets.html',
'/frame#js文件引入': '/tutorial/page-script.html',
'/frame#字体图标': '/tutorial/syntax-css.html#字体图标',
'/frame#wxs': '/tutorial/miniprogram-subject.html#wxs',
'/frame#flex布局': '/tutorial/syntax-css.html#flex-布局',
'/frame#npm支持': '/tutorial/page-script.html#npm支持',
'/frame#尺寸单位': '/tutorial/syntax-css.html#尺寸单位',
'/frame#目录结构': '/tutorial/project.html#目录结构',
'/frame#路由跳转': '/tutorial/page.html#路由跳转',
'/frame#小程序组件支持': '/tutorial/miniprogram-subject.html',
'/frame#小程序自定义组件支持': '/tutorial/miniprogram-subject.html#小程序自定义组件支持',
'/frame#判断平台': '/worktile/running-env.html#判断平台',
'/frame#typescript-支持': '/tutorial/typescript-subject.html#typescript-支持',
'/frame#全局样式与局部样式': '/tutorial/syntax-css.html#全局样式与局部样式',
'/frame#注意事项-1': '/tutorial/renderjs.html#注意事项',
'/api/ui/language': '/api/ui/locale.html',
'/api/ad/rewarded-video-ad': '/api/a-d/rewarded-video.html',
'/api/a-d/rewarded-video-ad': '/api/a-d/rewarded-video.html',
'/api/ad/interstitial-ad': '/api/a-d/interstitial.html',
'/api/ui/navigate': '/api/router.html',
'/api/plugins/getLaunchOptionsSync': '/api/getLaunchOptionsSync.html',
'/api/plugins/getEnterOptionsSync': '/api/getEnterOptionsSync.html',
'/api/plugins/getLaunchOptionsSync.html': '/api/getLaunchOptionsSync.html',
'/api/plugins/getEnterOptionsSync.html': '/api/getEnterOptionsSync.html',
'/api/lifetime': '/collocation/App.html#applifecycle',
'/api/extend/native-plugin': '/plugin/native-plugin.html',
'/vue-components': '/tutorial/vue-components.html',
'/vue-components.html': '/tutorial/vue-components.html',
'/vue-basics': '/tutorial/vue-basics.html',
'/vue-api': '/tutorial/vue-api.html',
'/vue-vuex': '/tutorial/vue-vuex.html',
'/vue3-api': '/tutorial/vue3-api.html',
'/vue3-basics': '/tutorial/vue3-basics.html',
'/vue3-vuex': '/tutorial/vue3-vuex.html',
'/vue3-components': '/tutorial/vue3-components.html',
'/migration-to-vue3': '/tutorial/migration-to-vue3.html',
'/nvue-outline': '/tutorial/nvue-outline.html',
'/nvue-api': '/tutorial/nvue-api.html',
'/nvue-css': '/tutorial/nvue-css.html',
'/uniCloud/database': '/uniCloud/clientdb.html',
'/uniCloud/uni-clientDB': '/uniCloud/clientdb.html',
'/uniCloud/uni-data-picker': '/component/uniui/uni-data-picker.html',
'/uniCloud/uni-push/introduction.html': '/unipush-v2.html',
'/plugin/publish.html#pages-init':'https://uniapp.dcloud.io/plugin/uni_modules.html#pages-init',
'/plugin/hybrid': '/hybrid.html',
'/adapt': '/tutorial/adapt.html',
'/share': '/api/plugins/share.html',
'/performance': '/tutorial/performance.html',
'/use-weex': '/tutorial/nvue-outline.html',
'/uni_modules': '/plugin/uni_modules.html',
'/snippet': '/tutorial/snippet.html',
'/store': '/tutorial/store.html',
'/platform': '/tutorial/platform.html',
'/nvue-event': '/tutorial/nvue-event.html',
'/use-html5plus': '/tutorial/use-html5plus.html',
}
export default ({ fullPath, path, hash }) => {
fullPath = decodeURIComponent(fullPath)
const matchFullPath = routerMap[fullPath.replace('?id=', '#')];
if (matchFullPath) {
return {
path: matchFullPath,
replace: true
}
}
const matchPath = routerMap[path]
if (matchPath) {
return {
path: matchPath,
hash,
replace: true
}
}
if (path.indexOf('/app-') === 0 || path.indexOf('/android-') === 0 || path.indexOf('/ios-') === 0) {
return {
path: `/tutorial${path}`,
hash,
replace: true
}
}
const routerMap = {
'/collocation/frame/lifecycle#页面生命周期': '/tutorial/page.html#lifecycle',
'/collocation/frame/lifecycle#应用生命周期': '/collocation/App.html#applifecycle',
'/collocation/frame/lifetime': '/collocation/App.html#applifecycle',
'/collocation/frame/lifecycle': '/collocation/App.html#applifecycle',
'/collocation/frame/communication': '/tutorial/page.html#页面通讯',
'/collocation/frame/lifecycle#page': '/tutorial/page.html#lifecycle',
'/collocation/frame/lifecycle#component': '/tutorial/page.html#componentlifecycle',
'/collocation/frame/timer': '/api/timer.html',
'/collocation/auto/hbuilderx-extension/index': '/worktile/auto/hbuilderx-extension/',
'/collocation/auto/hbuilderx-extension/': '/worktile/auto/hbuilderx-extension/',
'/collocation/auto/quick-start': '/worktile/auto/quick-start.html',
'/collocation/auto/uniapp-cli-project': '/worktile/auto/uniapp-cli-project.html',
'/collocation/i18n': '/tutorial/i18n.html',
'/worktile/i18n': '/tutorial/i18n.html',
'/worktile/i18n.html': '/tutorial/i18n.html',
'/collocation/env': '/tutorial/env.html',
'/collocation/ssr': '/tutorial/ssr.html',
'/ssr': '/tutorial/ssr.html',
'/ssr/': '/tutorial/ssr.html',
'/collocation/frame/window': '/tutorial/page.html#getapp',
'/collocation/frame/window#getcurrentpages': '/tutorial/page.html#getcurrentpages',
'/collocation/auto/api': '/worktile/auto/api.html',
'/collocation/frame/log': '/api/log.html',
'/component/mp-weixin-plugin': '/tutorial/mp-weixin-plugin.html',
'/component/uniui': '/component/uniui/uni-ui.html',
'/frame': '/tutorial/',
'/frame#renderjs': '/tutorial/renderjs.html',
'/frame#css变量': '/tutorial/syntax-css.html#css-变量',
'/frame#css引入静态资源': '/tutorial/page-static-assets.html',
'/frame#js文件引入': '/tutorial/page-script.html',
'/frame#字体图标': '/tutorial/syntax-css.html#字体图标',
'/frame#wxs': '/tutorial/miniprogram-subject.html#wxs',
'/frame#flex布局': '/tutorial/syntax-css.html#flex-布局',
'/frame#npm支持': '/tutorial/page-script.html#npm支持',
'/frame#尺寸单位': '/tutorial/syntax-css.html#尺寸单位',
'/frame#目录结构': '/tutorial/project.html#目录结构',
'/frame#路由跳转': '/tutorial/page.html#路由跳转',
'/frame#小程序组件支持': '/tutorial/miniprogram-subject.html',
'/frame#小程序自定义组件支持': '/tutorial/miniprogram-subject.html#小程序自定义组件支持',
'/frame#判断平台': '/worktile/running-env.html#判断平台',
'/frame#typescript-支持': '/tutorial/typescript-subject.html#typescript-支持',
'/frame#全局样式与局部样式': '/tutorial/syntax-css.html#全局样式与局部样式',
'/frame#注意事项-1': '/tutorial/renderjs.html#注意事项',
'/api/ui/language': '/api/ui/locale.html',
'/api/ad/rewarded-video-ad': '/api/a-d/rewarded-video.html',
'/api/a-d/rewarded-video-ad': '/api/a-d/rewarded-video.html',
'/api/ad/interstitial-ad': '/api/a-d/interstitial.html',
'/api/ui/navigate': '/api/router.html',
'/api/plugins/getLaunchOptionsSync': '/api/getLaunchOptionsSync.html',
'/api/plugins/getEnterOptionsSync': '/api/getEnterOptionsSync.html',
'/api/plugins/getLaunchOptionsSync.html': '/api/getLaunchOptionsSync.html',
'/api/plugins/getEnterOptionsSync.html': '/api/getEnterOptionsSync.html',
'/api/lifetime': '/collocation/App.html#applifecycle',
'/api/extend/native-plugin': '/plugin/native-plugin.html',
'/vue-components': '/tutorial/vue-components.html',
'/vue-components.html': '/tutorial/vue-components.html',
'/vue-basics': '/tutorial/vue-basics.html',
'/vue-api': '/tutorial/vue-api.html',
'/vue-vuex': '/tutorial/vue-vuex.html',
'/vue3-api': '/tutorial/vue3-api.html',
'/vue3-basics': '/tutorial/vue3-basics.html',
'/vue3-vuex': '/tutorial/vue3-vuex.html',
'/vue3-components': '/tutorial/vue3-components.html',
'/migration-to-vue3': '/tutorial/migration-to-vue3.html',
'/nvue-outline': '/tutorial/nvue-outline.html',
'/nvue-api': '/tutorial/nvue-api.html',
'/nvue-css': '/tutorial/nvue-css.html',
'/uniCloud/database': '/uniCloud/clientdb.html',
'/uniCloud/uni-clientDB': '/uniCloud/clientdb.html',
'/uniCloud/uni-data-picker': '/component/uniui/uni-data-picker.html',
'/uniCloud/uni-push/introduction.html': '/unipush-v2.html',
'/plugin/publish.html#pages-init':'https://uniapp.dcloud.io/plugin/uni_modules.html#pages-init',
'/plugin/hybrid': '/hybrid.html',
'/adapt': '/tutorial/adapt.html',
'/share': '/api/plugins/share.html',
'/performance': '/tutorial/performance.html',
'/use-weex': '/tutorial/nvue-outline.html',
'/uni_modules': '/plugin/uni_modules.html',
'/snippet': '/tutorial/snippet.html',
'/store': '/tutorial/store.html',
'/platform': '/tutorial/platform.html',
'/nvue-event': '/tutorial/nvue-event.html',
'/use-html5plus': '/tutorial/use-html5plus.html',
}
export default ({ fullPath, path, hash }) => {
fullPath = decodeURIComponent(fullPath)
const matchFullPath = routerMap[fullPath.replace('?id=', '#')];
if (matchFullPath) {
return {
path: matchFullPath,
replace: true
}
}
const matchPath = routerMap[path]
if (matchPath) {
return {
path: matchPath,
hash,
replace: true
}
}
if (path.indexOf('/app-') === 0 || path.indexOf('/android-') === 0 || path.indexOf('/ios-') === 0) {
return {
path: `/tutorial${path}`,
hash,
replace: true
}
}
}
\ No newline at end of file
export default {
category: [
{
text: 'uni-app',
type: 'algolia',
},
{
text: 'uniCloud',
type: 'algolia',
},
{
text: '问答社区',
tag: 'ask',
type: 'server',
},
{
text: '插件市场',
tag: 'ext',
type: 'server',
},
{
text: '原生开发文档',
type: 'link',
link: 'https://nativesupport.dcloud.net.cn/?s=',
},
{
text: 'HBuilderX 文档',
type: 'link',
link: 'https://hx.dcloud.net.cn/?s=',
},
],
translations: {
searchBox: {
buttonText: '取消',
placeholder: '搜索内容',
searchBy: '搜索提供者'
},
resultsScreen: {
resultsText: '${resultHits}个相关结果',
noResultsText: '`没有找到${categoryText}相关内容`',
askNoResultsText: '以下为${categoryText}相关内容'
}
}
}
\ No newline at end of file
export default {
onThisPage: '文档结构'
}
\ No newline at end of file
<template>
<div id="docsearch">
<button type="button" class="DocSearch DocSearch-Button" aria-label="搜索文档" @click="openSearch">
<span class="DocSearch-Button-Container">
<svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20">
<path
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
stroke="currentColor"
fill="none"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</svg>
<span class="DocSearch-Button-Placeholder">搜索文档</span>
</span>
<span class="DocSearch-Button-Keys">
<span class="DocSearch-Button-Key">
<svg width="15" height="15" class="DocSearch-Control-Key-Icon">
<path
d="M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953"
stroke-width="1.2"
stroke="currentColor"
fill="none"
stroke-linecap="square"
></path>
</svg>
</span>
<span class="DocSearch-Button-Key">K</span>
</span>
</button>
</div>
</template>
<script>
import '@docsearch/css';
export default {
name: 'AlgoliaSearchBox',
watch: {},
mounted() {},
methods: {
openSearch(){
this.$parent.$refs.dcloudSearchPage.onSearchOpen()
}
},
};
</script>
<style lang="stylus">
.DocSearch
--docsearch-primary-color $accentColor
--docsearch-highlight-color var(--docsearch-primary-color)
--docsearch-searchbox-shadow inset 0 0 0 2px var(--docsearch-primary-color)
#docsearch
display flex
flex-direction column
justify-content center
#docsearch span
@media (min-width: 1200px)
&
display flex
@media (max-width: 1200px)
:root
--docsearch-spacing 10px
--docsearch-footer-height 40px
.DocSearch-Button-Keys,.DocSearch-Button-Key,.DocSearch-Button-KeySeparator,.DocSearch-Button-Placeholder
display none !important
.DocSearch-Search-Icon
vertical-align middle
.DocSearch-Dropdown
height 100%
.DocSearch-Container
height 100vh
height -webkit-fill-available
position absolute
.DocSearch-Footer
border-radius 0
bottom 0
position absolute
.DocSearch-Hit-content-wrapper
display flex
position relative
width 80%
.DocSearch-Modal
border-radius 0
box-shadow none
height 100vh
height -webkit-fill-available
margin 0
max-width 100%
width 100%
.DocSearch-Cancel
-webkit-appearance none
-moz-appearance none
appearance none
background none
border 0
color var(--docsearch-highlight-color)
cursor pointer
display inline-block
flex none
font inherit
font-size 1em
font-weight 500
margin-left var(--docsearch-spacing)
outline none
overflow hidden
padding 0
-webkit-user-select none
-moz-user-select none
-ms-user-select none
user-select none
white-space nowrap
.DocSearch-Commands,.DocSearch-Hit-Tree
display none
</style>
<template>
<li :class="li_class">
<a :href="item.url">
<div class="DocSearch-Hit-Container">
<div
class="DocSearch-Hit-content-wrapper"
v-if="item.hierarchy[item.type] && item.type === 'lvl1'"
>
<span class="DocSearch-Hit-title" v-html="hierarchyLvl1Html" />
<span v-if="item.content" class="DocSearch-Hit-path" v-html="contentHtml" />
</div>
<div v-else-if="isContent" class="DocSearch-Hit-content-wrapper">
<span class="DocSearch-Hit-title" v-html="contentHtml" />
<span class="DocSearch-Hit-path" v-html="hierarchyLvl1Html || hierarchyLvl2Html" />
</div>
<div v-else class="DocSearch-Hit-content-wrapper">
<span
class="DocSearch-Hit-title"
v-html="snippetResultContent(`hierarchy.${item.type}`)"
/>
<span class="DocSearch-Hit-path" v-html="hierarchyLvl1Html" />
</div>
</div>
</a>
</li>
</template>
<script>
function getPropertyByPath(object, path) {
const parts = path.split('.');
return parts.reduce((prev, current) => {
if (prev?.[current]) return prev[current];
return null;
}, object);
}
export default {
data() {
return {};
},
props: {
item: {
type: Object,
default: () => ({}),
},
index: {
type: Number,
default: 0,
},
},
computed: {
li_class() {
return ['DocSearch-Hit', this.item.__docsearch_parent && 'DocSearch-Hit--Child']
.filter(Boolean)
.join(' ');
},
isContent() {
return this.item.type === 'content';
},
contentHtml() {
return this.snippetResultContent('content');
},
hierarchyLvl1Html() {
return this.snippetResultContent('hierarchy.lvl1');
},
hierarchyLvl2Html() {
const lvl2 = this.snippetResultContent('hierarchy.lvl2');
return this.isContent ? this.contentHtml === lvl2 ? '' : lvl2 : '';
},
},
methods: {
snippetResultContent(attribute) {
return (
getPropertyByPath(this.item, `_snippetResult.${attribute}.value`) ||
getPropertyByPath(this.item, attribute)
);
},
},
};
</script>
<style lang="stylus">
.DocSearch-Hit
border-radius 0px
display flex
padding-bottom 0px
position relative
&:not(:first-child)
border-top 1px solid #f5f6f7
a
background var(--docsearch-hit-background)
border-radius 0px
// box-shadow var(--docsearch-hit-shadow)
box-shadow none
display block
padding-left var(--docsearch-spacing)
width 100%
.DocSearch-Hit-Container
align-items center
color #444950
display flex
flex-direction row
height 56px
padding 0 12px 0 0
.DocSearch-Hit-content-wrapper
overflow hidden
display flex
flex 1 1 auto
flex-direction column
font-weight 500
justify-content center
line-height 1.2em
margin 0 8px
overflow-x hidden
position relative
text-overflow ellipsis
white-space nowrap
width 80%
.DocSearch-Hit-title
font-size 0.9em
.DocSearch-Hit-path
// color $accentColor
font-size 0.75em
</style>
<template>
<section class="DocSearch-Hits">
<div class="DocSearch-Hit-source">
<span v-if="tag" class="DocSearch-Hit-source_tag">
{{ tag }}
</span>
{{ title }}
</div>
<ul id="docsearch-list">
<template v-for="(item, index) in results">
<Result
:key="[title, item.objectID].join(':')"
:item="item"
:index="index"
@click.native="event => onSelect({ item, event })"
/>
</template>
</ul>
</section>
</template>
<script>
import Result from './Result.vue';
export default {
components: { Result },
data() {
return {};
},
props: {
title: {
type: String,
default: '文档',
},
results: {
type: Array,
default: [],
},
onSelect: {
type: Function,
default: () => {},
},
},
computed: {
tag() {
return this.results[0].tag !== 'uniCloud' ? this.results[0].tag : '';
},
},
};
</script>
<style lang="stylus">
#docsearch-list{
border-radius: 5px;
padding: 0;
border: 1px solid #dfe2e5;
overflow: hidden;
}
.DocSearch-Hits mark {
background: none;
color: $accentColor;
}
.DocSearch-Hit-source {
background-color $search-container-color;
color: $accentColor;
font-size 1em;
padding 15px 4px 15px
}
.DocSearch-Hit-source_tag {
background-color: #f0f0f0;
font-size: 12px;
padding: 2px 4px;
color: #999;
border-radius: 3px;
margin-right: 5px;
font-weight: normal;
}
</style>
<template>
<!-- 分页结构 -->
<div class="page-bar">
<ul>
<li v-if="cur > 1"><a class="clearfix" v-on:click="cur--, pageClick()">上一页</a></li>
<li v-if="cur == 1"><a class="banclick clearfix">上一页</a></li>
<template v-for="index in indexs">
<li :key="index" :class="{ active: cur == index }">
<a class="clearfix" v-on:click="btnClick(index)">{{ index }}</a>
</li>
</template>
<li v-if="cur != all"><a class="clearfix" v-on:click="cur++, pageClick()">下一页</a></li>
<li v-if="cur == all"><a class="banclick clearfix">下一页</a></li>
<li>
<span>
<i>{{ all }}</i>
</span>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
all: 10, //总页数
cur: 1, //当前页码
// totalPage: 0, //当前条数
};
},
props: {
curPage: {
type: Number,
default: 1,
},
totalPage: {
type: Number,
default: 0,
},
},
created() {
this.all = this.totalPage;
this.cur = this.curPage;
},
watch: {
curPage: {
immediate: true,
handler(val) {
this.cur = val;
},
},
totalPage: {
immediate: true,
handler(val) {
this.all = val;
},
},
},
methods: {
//请求数据
dataListFn: function (index) {
this.$emit('research', index);
},
//分页
btnClick: function (data) {
if (data != this.cur) {
this.cur = data;
this.dataListFn(this.cur);
}
},
pageClick: function () {
this.dataListFn(this.cur);
},
},
computed: {
//分页
indexs: function () {
var left = 1;
var right = this.all;
var ar = [];
if (this.all >= 5) {
if (this.cur > 3 && this.cur < this.all - 2) {
left = this.cur - 2;
right = this.cur + 2;
} else {
if (this.cur <= 3) {
left = 1;
right = 5;
} else {
right = this.all;
left = this.all - 4;
}
}
}
while (left <= right) {
ar.push(left);
left++;
}
return ar;
},
},
};
</script>
<style lang="stylus" scoped>
.page-bar
display flex
justify-content center
margin 10px
ul, li
margin 0px
padding 0px
user-select none
li
display inline-block
list-style none
overflow hidden
.page-bar li:first-child > a
margin-left 0px
.page-bar a
border 1px solid #ddd
text-decoration none
position relative
float left
padding 2px 10px
margin-left -1px
line-height 1.42857143
color #5d6062
cursor pointer
margin-right 20px
background-color #fff
.page-bar a:hover
background-color #eee
.page-bar a.banclick
cursor not-allowed
.page-bar .active a
color #fff
cursor default
background-color $accentColor
border-color $accentColor
.page-bar i
font-style normal
color $accentColor
margin 0px 4px
font-size 12px
</style>
$svg-color = #b1b2b3
$svg-hover-color = #9b9b9b
@import './styles/ext.styl'
@import './styles/ask.styl'
#search-container
position relative
overflow auto
position fixed
width 100%
height 100%
left 0
top 0
z-index 200
background-color $search-container-color
.sub-navbar, .result-wrap
width 80%
max-width 960px
margin 0 auto
.search-wrap
width 100%
display inline-block
vertical-align middle
position relative
.input-wrap
margin-top 24px
position relative
display flex
align-items center
box-sizing border-box
.search-input-btn
display flex
flex-direction column
justify-content center
padding 0
font-size 0
background-color #fff
button
width 40px
font-family inherit
font-size 100%
margin 0
outline 0
background-color transparent
padding 0
border-width 0
vertical-align middle
cursor pointer
svg
fill $svg-color
&:hover
fill $svg-hover-color
.search-input
width 100%
height 56px
font-size 16px
border none
box-sizing border-box
outline none
padding 1px 10px
border-radius 4px
.search-input-btn
height 56px
.search-back__btn
display block !important
font-size 17px
color #576b95
line-height 24px
padding 10px 16px
white-space nowrap
/* position absolute
right -65px */
.search-category
.navbar
border none
.main-navbar
box-shadow none
.main-navbar-links
width 100%
padding 0
@media (max-width 630px)
&
white-space normal !important
.main-navbar-item
@media (max-width 1070px)
&
padding 0 2%
@media (max-width 900px)
&
padding 0 1%
.result-number
display flex
justify-content center
align-items center
padding-top 20px
font-size 20px
.uni-loading
width 27px
height 27px
.DocSearch-Logo
font-size 16px
vertical-align middle
display inline-block
.search-result
box-sizing border-box
.result-wrap
ul
list-style none
margin 0
padding 0
.algolia-logo
display flex
justify-content center
padding 10px 0 20px
.search-more
display block
text-align center
padding 10px 0
color inherit
cursor pointer
&:hover
color $accentColor
@media (max-width $MQMobile)
#search-container
.search-navbar-header>.main-navbar
display none !important
.search-navbar>.navbar, .search-category>.navbar
line-height 3rem
height auto
position static !important
.sub-navbar, .result-wrap
width 100% !important
.input-wrap
padding-left 5px
margin-top 5px !important
.search-input, .search-input-btn
height 32px !important
.search-result
padding 0 10px
.search-result-aside
display none
\ No newline at end of file
<template>
<div v-if="openSearch" id="search-container" ref="pageContainer">
<div class="search-navbar">
<div class="search-navbar-header navbar">
<div class="main-navbar">
<NavbarLogo />
<div class="main-navbar-links can-hide">
<div class="main-navbar-item active"></div>
</div>
</div>
<div class="sub-navbar">
<div class="search-wrap">
<div class="input-wrap">
<input
ref="searchInput"
class="search-input"
:placeholder="placeholder"
type="text"
@keydown.enter="
() => {
resetSearchPage();
search();
}
"
v-model="searchValue"
/>
<span class="search-input-btn">
<button
@click="
() => {
resetSearchPage();
search();
}
"
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.33 10.007l4.273 4.273a.502.502 0 0 1 .005.709l-.585.584a.499.499 0 0 1-.709-.004L10.046 11.3a6.278 6.278 0 1 1 1.284-1.294zm.012-3.729a5.063 5.063 0 1 0-10.127 0 5.063 5.063 0 0 0 10.127 0z"
></path>
</svg>
</button>
</span>
<a href="javascript:;" class="search-back__btn" @click="onSearchClose">取消</a>
</div>
<div class="search-category">
<div class="navbar">
<div class="main-navbar">
<div class="main-navbar-links">
<template v-for="(item, index) in category">
<div :class="mainNavLinkClass(index)" :key="item.text">
<MainNavbarLink
v-if="item.link"
:key="item.text"
:item="{
...item,
link: item.link + searchValue,
}"
/>
<a v-else href="javascript:;" @click="switchCategory(index)">
{{ item.text }}
</a>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="result-number">
<span v-if="showLoading" class="uni-loading"></span>
<span v-else>{{ resultText }}</span>
</div>
<div class="search-result">
<div class="result-wrap">
<template v-if="isAlgolia">
<template v-for="item in resultList">
<Results
:key="item.sourceId"
:title="item.title"
:results="item.items"
:onSelect="item.onSelect"
/>
</template>
</template>
<template v-else>
<div
class="markdown-section search-result-list"
v-if="serverHtml"
v-html="serverHtml"
></div>
</template>
</div>
<div v-if="isAlgolia" class="algolia-logo">
<div class="DocSearch-Logo">
<a
href="https://www.algolia.com/ref/docsearch/?utm_source=uniapp.dcloud.io&amp;utm_medium=referral&amp;utm_content=powered_by&amp;utm_campaign=docsearch"
target="_blank"
rel="noopener noreferrer"
>
<span class="DocSearch-Label">搜索提供者</span>
<svg width="77" height="19" aria-label="Algolia" role="img">
<path
d="M2.5067 0h14.0245c1.384.001 2.5058 1.1205 2.5068 2.5017V16.5c-.0014 1.3808-1.1232 2.4995-2.5068 2.5H2.5067C1.1232 18.9995.0014 17.8808 0 16.5V2.4958A2.495 2.495 0 01.735.7294 2.505 2.505 0 012.5068 0zM37.95 15.0695c-3.7068.0168-3.7068-2.986-3.7068-3.4634L34.2372.3576 36.498 0v11.1794c0 .2715 0 1.9889 1.452 1.994v1.8961zm-9.1666-1.8388c.694 0 1.2086-.0397 1.5678-.1088v-2.2934a5.3639 5.3639 0 00-1.3303-.1679 4.8283 4.8283 0 00-.758.0582 2.2845 2.2845 0 00-.688.2024c-.2029.0979-.371.2362-.4919.4142-.1268.1788-.185.2826-.185.5533 0 .5297.185.8359.5205 1.0375.3355.2016.7928.3053 1.365.3053v-.0008zm-.1969-8.1817c.7463 0 1.3768.092 1.8856.2767.5088.1838.9195.4428 1.2204.7717.3068.334.5147.7777.6423 1.251.1327.4723.196.991.196 1.5603v5.798c-.5235.1036-1.05.192-1.5787.2649-.7048.1037-1.4976.156-2.3774.156-.5832 0-1.1215-.0582-1.6016-.167a3.385 3.385 0 01-1.2432-.5364 2.6034 2.6034 0 01-.8037-.9565c-.191-.3922-.29-.9447-.29-1.5208 0-.5533.11-.905.3246-1.2863a2.7351 2.7351 0 01.8849-.9329c.376-.242.8029-.415 1.2948-.5187a7.4517 7.4517 0 011.5381-.156 7.1162 7.1162 0 011.6667.2024V8.886c0-.259-.0296-.5061-.093-.7372a1.5847 1.5847 0 00-.3245-.6158 1.5079 1.5079 0 00-.6119-.4158 2.6788 2.6788 0 00-.966-.173c-.5206 0-.9948.0634-1.4283.1384a6.5481 6.5481 0 00-1.065.259l-.2712-1.849c.2831-.0986.7048-.1964 1.2491-.2943a9.2979 9.2979 0 011.752-.1501v.0008zm44.6597 8.1193c.6947 0 1.2086-.0405 1.567-.1097v-2.2942a5.3743 5.3743 0 00-1.3303-.1679c-.2485 0-.503.0177-.7573.0582a2.2853 2.2853 0 00-.688.2024 1.2333 1.2333 0 00-.4918.4142c-.1268.1788-.1843.2826-.1843.5533 0 .5297.1843.8359.5198 1.0375.3414.2066.7927.3053 1.365.3053v.0009zm-.191-8.1767c.7463 0 1.3768.0912 1.8856.2759.5087.1847.9195.4436 1.2204.7717.3.329.5147.7786.6414 1.251a5.7248 5.7248 0 01.197 1.562v5.7972c-.3466.0742-.874.1602-1.5788.2648-.7049.1038-1.4976.1552-2.3774.1552-.5832 0-1.1215-.0573-1.6016-.167a3.385 3.385 0 01-1.2432-.5356 2.6034 2.6034 0 01-.8038-.9565c-.191-.3922-.2898-.9447-.2898-1.5216 0-.5533.1098-.905.3245-1.2854a2.7373 2.7373 0 01.8849-.9338c.376-.2412.8029-.4141 1.2947-.5178a7.4545 7.4545 0 012.325-.1097c.2781.0287.5672.081.879.156v-.3686a2.7781 2.7781 0 00-.092-.738 1.5788 1.5788 0 00-.3246-.6166 1.5079 1.5079 0 00-.612-.415 2.6797 2.6797 0 00-.966-.1729c-.5205 0-.9947.0633-1.4282.1384a6.5608 6.5608 0 00-1.065.259l-.2712-1.8498c.283-.0979.7048-.1957 1.2491-.2935a9.8597 9.8597 0 011.752-.1494zm-6.79-1.072c-.7576.001-1.373-.6103-1.3759-1.3664 0-.755.6128-1.3664 1.376-1.3664.764 0 1.3775.6115 1.3775 1.3664s-.6195 1.3664-1.3776 1.3664zm1.1393 11.1507h-2.2726V5.3409l2.2734-.3568v10.0845l-.0008.0017zm-3.984 0c-3.707.0168-3.707-2.986-3.707-3.4642L59.7069.3576 61.9685 0v11.1794c0 .2715 0 1.9889 1.452 1.994V15.0703zm-7.3512-4.979c0-.975-.2138-1.7873-.6305-2.3516-.4167-.571-.9998-.852-1.747-.852-.7454 0-1.3302.281-1.7452.852-.4166.5702-.6195 1.3765-.6195 2.3516 0 .9851.208 1.6473.6254 2.2183.4158.576.9998.8587 1.7461.8587.7454 0 1.3303-.2885 1.747-.8595.4158-.5761.6237-1.2315.6237-2.2184v.0009zm2.3132-.006c0 .7609-.1099 1.3361-.3356 1.9654a4.654 4.654 0 01-.9533 1.6076A4.214 4.214 0 0155.613 14.69c-.579.2412-1.4697.3795-1.9143.3795-.4462-.005-1.3303-.1324-1.9033-.3795a4.307 4.307 0 01-1.474-1.0316c-.4115-.4445-.7293-.9801-.9609-1.6076a5.3423 5.3423 0 01-.3465-1.9653c0-.7608.104-1.493.3356-2.1155a4.683 4.683 0 01.9719-1.5958 4.3383 4.3383 0 011.479-1.0257c.5739-.242 1.2043-.3567 1.8864-.3567.6829 0 1.3125.1197 1.8906.3567a4.1245 4.1245 0 011.4816 1.0257 4.7587 4.7587 0 01.9592 1.5958c.2426.6225.3643 1.3547.3643 2.1155zm-17.0198 0c0 .9448.208 1.9932.6238 2.431.4166.4386.955.6579 1.6142.6579.3584 0 .6998-.0523 1.0176-.1502.3186-.0978.5721-.2134.775-.3517V7.0784a8.8706 8.8706 0 00-1.4926-.1906c-.8206-.0236-1.4452.312-1.8847.8468-.4335.5365-.6533 1.476-.6533 2.3516v-.0008zm6.2863 4.4485c0 1.5385-.3938 2.662-1.1866 3.3773-.791.7136-2.0005 1.0712-3.6308 1.0712-.5958 0-1.834-.1156-2.8228-.334l.3643-1.7865c.8282.173 1.9202.2193 2.4932.2193.9077 0 1.555-.1847 1.943-.5533.388-.3686.578-.916.578-1.643v-.3687a6.8289 6.8289 0 01-.8848.3349c-.3634.1096-.786.167-1.261.167-.6246 0-1.1917-.0979-1.7055-.2944a3.5554 3.5554 0 01-1.3244-.8645c-.3642-.3796-.6541-.8579-.8561-1.4289-.2028-.571-.3068-1.59-.3068-2.339 0-.7034.1099-1.5856.3245-2.1735.2198-.5871.5316-1.0949.9542-1.515.4167-.42.9255-.743 1.5213-.98a5.5923 5.5923 0 012.052-.3855c.7353 0 1.4114.092 2.0707.2024.6592.1088 1.2204.2236 1.6776.35v8.945-.0008zM11.5026 4.2418v-.6511c-.0005-.4553-.3704-.8241-.8266-.8241H8.749c-.4561 0-.826.3688-.8265.824v.669c0 .0742.0693.1264.1445.1096a6.0346 6.0346 0 011.6768-.2362 6.125 6.125 0 011.6202.2185.1116.1116 0 00.1386-.1097zm-5.2806.852l-.3296-.3282a.8266.8266 0 00-1.168 0l-.393.3922a.8199.8199 0 000 1.164l.3237.323c.0524.0515.1268.0397.1733-.0117.191-.259.3989-.507.6305-.7372.2374-.2362.48-.4437.7462-.6335.0575-.0354.0634-.1155.017-.1687zm3.5159 2.069v2.818c0 .081.0879.1392.1622.0987l2.5102-1.2964c.0574-.0287.0752-.0987.0464-.1552a3.1237 3.1237 0 00-2.603-1.574c-.0575 0-.115.0456-.115.1097l-.0008-.0009zm.0008 6.789c-2.0933.0005-3.7915-1.6912-3.7947-3.7804C5.9468 8.0821 7.6452 6.39 9.7387 6.391c2.0932-.0005 3.7911 1.6914 3.794 3.7804a3.7783 3.7783 0 01-1.1124 2.675 3.7936 3.7936 0 01-2.6824 1.1054h.0008zM9.738 4.8002c-1.9218 0-3.6975 1.0232-4.6584 2.6841a5.359 5.359 0 000 5.3683c.9609 1.661 2.7366 2.6841 4.6584 2.6841a5.3891 5.3891 0 003.8073-1.5725 5.3675 5.3675 0 001.578-3.7987 5.3574 5.3574 0 00-1.5771-3.797A5.379 5.379 0 009.7387 4.801l-.0008-.0008z"
fill="currentColor"
fill-rule="evenodd"
></path>
</svg>
</a>
</div>
</div>
<div class="search-pagination">
<pagination
v-show="showPagination"
@research="research"
:totalPage="totalPage"
:curPage="curPage"
:pageSize="pageSize"
/>
<a v-if="showMoreAsk" class="search-more" @click="moreAskResult">
<span v-if="showServerLoading" class="uni-loading"></span>
<span v-else>{{ hasNoMoreServerResult ? '没有更多了' : '更多...' }}</span>
</a>
</div>
</div>
</div>
</template>
<script>
import NavbarLogo from '../NavbarLogo.vue';
import Results from './components/Results.vue';
import pagination from './components/pagination.vue';
import MainNavbarLink from '../MainNavbarLink.vue';
import { search as searchClient } from './utils/searchClient';
import { postExt, postAsk } from './utils/postDcloudServer';
import { forbidScroll, debounce } from '../../util';
import { removeHighlightTags, isEditingContent } from './utils/searchUtils';
const resolveRoutePathFromUrl = (url, base = '/') =>
url
// remove url origin
.replace(/^(https?:)?\/\/[^/]*/, '')
// remove site base
.replace(new RegExp(`^${base}`), '/');
export default {
name: 'DcloudSearchPage',
props: ['options'],
components: { NavbarLogo, Results, pagination, MainNavbarLink },
data() {
return {
openSearch: false,
placeholder: '搜索内容',
snippetLength: 30,
searchValue: '',
category: Object.freeze([
{
text: 'uni-app',
type: 'algolia',
},
{
text: 'uniCloud',
type: 'algolia',
},
{
text: '问答社区',
tag: 'ask',
type: 'server',
},
{
text: '插件市场',
tag: 'ext',
type: 'server',
},
{
text: '原生开发文档',
type: 'link',
link: 'https://nativesupport.dcloud.net.cn/?s=',
},
{
text: 'HBuilderX 文档',
type: 'link',
link: 'https://hx.dcloud.net.cn/?s=',
},
]),
categoryIndex: 0,
resultList: [],
noResult: false,
serverHtml: '',
showLoading: false,
showServerLoading: false,
hasNoMoreServerResult: false,
searchPage: 0, // 跳转页数
curHits: 0, // 当前搜索结果总条数
totalPage: 0, // 搜索结果总共页数
curPage: 1, // 当前页
pageSize: 0, // 每页条数
};
},
computed: {
currentCategory() {
return this.category[this.categoryIndex];
},
isAlgolia() {
return this.currentCategory.type === 'algolia';
},
isAsk() {
return this.currentCategory.tag === 'ask';
},
showPagination() {
return !!(this.resultList.length && this.totalPage > 1 && this.isAlgolia);
},
showMoreAsk() {
return this.isAsk && this.serverHtml;
},
resultText() {
return this.noResult
? `没有找到${this.currentCategory.text}相关内容`
: !this.isAsk
? `${this.curHits}个相关结果`
: `以下为${this.currentCategory.text}相关内容`;
},
},
mounted() {
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('resize', this.initSnippetLength);
},
watch: {
resultList() {
this.$refs.pageContainer.scrollTop = 0;
},
openSearch(val) {
this.$nextTick(() => {
if (val) {
forbidScroll();
document.body.appendChild(this.$el);
this.$nextTick(() => {
this.$refs.searchInput.focus();
this.initResultWrapHeight();
});
} else {
this.cancel();
forbidScroll(false);
document.body.removeChild(this.$el);
}
});
},
searchValue: debounce(function () {
this.resetSearchPage();
this.search();
}, 300),
},
methods: {
initResultWrapHeight() {
const pageHeight = this.$el.clientHeight;
const searchNavbarHeight = document.querySelector('.search-navbar').clientHeight;
const resultNumberHeight = (
document.querySelector('.result-number') || { clientHeight: 47 }
).clientHeight;
const algoliaLogoHeight = (document.querySelector('.algolia-logo') || { clientHeight: 49 })
.clientHeight;
const searchPagination = 36;
document.querySelector('.result-wrap').style.minHeight =
pageHeight -
searchNavbarHeight -
resultNumberHeight -
algoliaLogoHeight -
searchPagination -
20 +
'px';
},
resetSearchPage() {
this.searchPage = 0;
},
research(curPage) {
this.searchPage = curPage - 1;
this.search();
},
search() {
if (!this.searchValue || !this.searchValue.trim().length) return;
const { type } = this.currentCategory;
switch (type) {
case 'algolia':
this.showLoading = true;
this.searchByAlgolia()
.then(({ hitsPerPage, nbHits, nbPages, page, hits }) => {
this.resultList = hits.map(item => {
const items = item.getItems();
return {
...item,
title: removeHighlightTags(items[0]),
items,
};
});
this.noResult = !this.resultList.length;
this.curHits = nbHits;
this.pageSize = hitsPerPage;
this.totalPage = nbPages;
this.curPage = page + 1;
})
.finally(() => (this.showLoading = false));
break;
case 'server':
this.showLoading = true;
this.searchByServer().finally(() => (this.showLoading = false));
break;
}
},
searchByAlgolia() {
const { searchParameters = {} } = this.options;
let categoryArr = [];
if (this.currentCategory.text === 'uni-app') {
categoryArr = [[`category:uni-app`, `category:uniCloud`]];
} else {
categoryArr = [`category:${this.currentCategory.text}`];
}
return searchClient(
Object.assign({}, this.options, {
query: this.searchValue || '',
page: this.searchPage,
snippetLength: this.snippetLength,
searchParameters: {
...searchParameters,
facetFilters: [`lang:${this.$lang}`].concat(
searchParameters.facetFilters || [],
categoryArr
),
},
transformItems: items =>
items.map(item => {
// the `item.url` is full url with protocol and hostname
// so we have to transform it to vue-router path
return {
...item,
url: resolveRoutePathFromUrl(item.url, this.$site.base),
};
}),
onClose: this.onSearchClose,
})
);
},
searchByServer(append = false) {
const { tag } = this.currentCategory;
const query = this.searchValue || '';
switch (tag) {
case 'ext':
return postExt(query).then(({ html, hits }) => {
this.serverHtml = html;
this.curHits = hits;
this.noResult = !hits;
});
case 'ask':
append && (this.showServerLoading = true);
this.searchPage === 0 && (this.searchPage = 1);
return postAsk(query, this.searchPage)
.then(res => {
if (res) {
const { html = '', hits } = res;
if (append) {
this.serverHtml += html;
} else {
this.hasNoMoreServerResult = false;
this.serverHtml = html;
}
this.noResult = !this.serverHtml.length;
} else {
if (append) {
this.hasNoMoreServerResult = true;
} else {
this.serverHtml = '';
this.noResult = true;
}
}
})
.finally(() => (this.showServerLoading = false));
}
},
moreAskResult() {
if (this.hasNoMoreServerResult) return;
this.searchPage === 0 ? (this.searchPage = 2) : this.searchPage++;
this.searchByServer(true);
},
mainNavLinkClass(index) {
return ['main-navbar-item', this.categoryIndex === index ? 'active' : ''];
},
initSnippetLength() {
if (window.matchMedia('(max-width: 980px)').matches) {
this.snippetLength = 20;
}
if (window.matchMedia('(max-width: 600px)').matches) {
this.snippetLength = 15;
}
if (this.$refs.pageContainer) {
this.$refs.pageContainer.style.maxHeight = document.documentElement.clientHeight;
this.$refs.pageContainer.style.maxWidth = document.documentElement.clientWidth;
}
},
switchCategory(index) {
this.categoryIndex = index;
this.research(1);
},
cancel() {
this.resultList.length = 0;
this.searchValue = '';
this.curHits = 0;
this.totalPage = 0;
this.serverHtml = '';
this.noResult = false;
},
onSearchOpen() {
this.openSearch = true;
},
onSearchClose() {
this.openSearch = false;
},
onKeyDown(event) {
if (
(event.keyCode === 27 && this.openSearch) ||
// The `Cmd+K` shortcut both opens and closes the modal.
(event.key === 'k' && (event.metaKey || event.ctrlKey)) ||
// The `/` shortcut opens but doesn't close the modal because it's
// a character.
(!isEditingContent(event) && event.key === '/' && !this.openSearch)
) {
event.preventDefault();
if (this.openSearch) {
this.onSearchClose();
} else {
this.onSearchOpen();
}
}
},
},
};
</script>
<style lang="stylus">
@import './index'
</style>
#search-container
.aw-item, .plugin-li
box-sizing border-box
$padding-n = 25px
padding $padding-n $padding-n ($padding-n - 10px) $padding-n
@media (max-width $MQMobile)
&
padding: $padding-n - 10px $padding-n - 10px 5px $padding-n - 10px
&:not(:last-child)
border-bottom 1px solid #eee
&:first-child
margin-top 0 !important
.aw-item
a
text-decoration none
color inherit
font-weight 400
p
margin 0 0 10px
.tip-recommend
background-color #22ac38
color #ffffff
padding 2px 5px
border-radius 3px
margin-right 10px
font-size 12px
.aw-title a
text-decoration underline
font-size 16px
.aw-hide-text
font-size 12px
color #828288
.aw-search-result-tags
background-color #ffffff
color #ef5656
margin-right 10px
.aw-search-body
font-size 14px
color #666
word-break: break-all;
em
font-weight bold
font-style normal
color $accentColor
.plugin-li
color rgba(0, 0, 0, 0.6)
width 100%
text-align -webkit-match-parent
font-size 14px
.pull-right
float right !important
.col-lg-9
width 70%
.col-lg-2
width 16.66666667%
.col-lg-6
width 50%
float left
.col-md-6, .col-lg-6, .col-xs-12, .col-sm-12
position relative
min-height 1px
padding-left 15px
padding-right 15px
a
color inherit
:before, :after
box-sizing border-box
.row
margin-right -15px
margin-left -15px
&:before, &:after
content ' '
display table
&:after
clear both
.card
margin-bottom 15px
border-radius 10px
background-color #fff
&:last-child
margin-bottom 0
.preview-img
display inline-block
max-height 192px
padding-right 0
float left
overflow-y hidden
img
width 100%
height auto
vertical-align middle
border 0
.plugin-info
padding-left 30px
padding-right 0
float left
.row
margin-bottom 8px
&:last-child
margin-bottom 0
.extend-list
margin 0
position relative
line-height 24px
color #999
transition all 0.3s
.extend-list-header .extend-title a
color #595a78
font-size 16px
.extend-list-desc
color #424249
font-size 14px
.badge
padding 2px 6px
margin-left 5px
display inline-block
min-width 10px
padding 3px 7px
font-size 12px
font-weight 700
color #fff
line-height 1
vertical-align baseline
white-space nowrap
text-align center
background-color #999
border-radius 10px
.bg-green, .bg-green:hover
color #639069
a.&:focus, a.&:hover
color #fff
text-decoration none
cursor pointer
h2
margin-top 0
padding-bottom 0
font-size 100%
line-height 20px
color #333
border none
.ext-slg
padding 0
margin 0 auto
width 14%
float right
text-align center
.rating
margin-top 10px
.star-rating
unicode-bidi bidi-override
color #96969b
font-size 0
height 25px
position relative
display inline-block
padding 0
margin-left 0
float left
span
padding 1px
font-size 16px
.star-rating-top
color #fde16d
padding 0
position absolute
z-index 1
display inline-block
top 4px
left 4px
overflow hidden
white-space nowrap
float left
span
width 20px
height 16px
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAASCAYAAAC5DOVpAAABDUlEQVQ4ja3SsS/DQRjG8c81YrLYxCz8pLHUYhCtzf9gtIilo1VM/gSmisR/YCCRSMTIyhEzo7nR9CytVPn112qf5Ib3fZ/73nO5o0ApaqQopahR5C0C1Tqg7qoN8pcKeAcF9XCwTopqX7s6KF3IAU3hBut/jO+wGTKtX7AUzWALS1jEApYxm5cAH3jCK17wjKuQonusDtg4rB5CipqYngCsWUJ9AiCol0LmGLtojwHaC5mT79dM0TbOFP+9XrWxEzKnejeGzLnRr7zfBf2AdfQ2Iuy9t+iHrYwI++EfF1b+D+wxpz8w2VxffYlKyJRRwUXffD7nEFJ0mKLPFN2maCPHs5ai6xS1UnTUO/sCG35T7tAgzwcAAAAASUVORK5CYII=') no-repeat
background-size 16px
.star-rating-bottom
padding 4px
display inline-block
z-index 0
float left
span
width 20px
height 16px
background url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAASCAYAAAC5DOVpAAABBElEQVQ4ja3Ur04DQRDH8U8vSAyOoFkIAUxRqFaC4BUIdQRTiW1ST4KCKmp4AwQIFBZSB2GDBskLQIPgStojd9d/Y3ZnfjPfzGYmW4kxKrErHKGLRlYMIfzdkxJQPQVJz3pRchmsVeKPDaujlonVFHSXB1tAO0drp/o/q8QYF7GHdaxhFRtYyusAn3jBGyJecVeJMT5ip6BwXHtKsD0HEGwlaM4J1kxwiWP0ZwCdhBA6g2l2cDgFsI9GCOGC0dW4NvmTT0MI3YGT3bP3CWEfw04WNulkR/JnhW1OA3vOiRd2tpzxb1FNO6jiJqOvFMHO8YUHvz/EPnqp1sMBdnGPb5wNF/8A+HAvGx6fm+sAAAAASUVORK5CYII=') no-repeat
background-size 16px
.glyphicon
position relative
top 1px
display inline-block
font-family 'Glyphicons Halflings'
font-style normal
font-weight 400
line-height 1
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
.extend-list-tag
.badge.bg-orange
color #ff7c00
background rgba(255, 194, 0, 0.28) !important
.badge.bg-green
color #639069
background-color #eff9f0 !important
.extend-list-more
font-size 13px
color #828288
line-height 40px
.categorys
width 45% !important
a
color #22ac38
.col-lg-4
float left
.extend-list-user .avatar
width 30px
height 30px
border-radius 100%
margin-right 10px
margin-left 10px
\ No newline at end of file
#search-container
.markdown-section
margin-top 20px
background-color #fff
.matching-post
$padding-n = 25px
padding $padding-n $padding-n ($padding-n - 15px)$padding-n
@media (max-width $MQMobile)
&
padding: $padding-n - 10px $padding-n - 10px 0px $padding-n - 10px
&:not(:last-child)
border-bottom 1px solid #eee
&:first-child
margin-top 0 !important
a
text-decoration none
color inherit
font-weight 400
&:hover
color $accentColor
.post-wrapper
display flex
p.aw-text
font-weight normal
margin 0
display inline-flex
p
font-size 14px
overflow hidden
text-overflow ellipsis
display -webkit-box
-webkit-line-clamp 2
-webkit-box-orient vertical
.post-tag
background-color #f0f0f0
font-size 12px
padding 2px 4px
color #999
border-radius 3px
margin-right 5px
.matching-post h2
margin-top 0
padding-top 0
h2
color #2c3e50
font-size 16px
margin 0
padding 0
display inline-block
font-weight 600
border none
.search-keyword
font-style normal
font-weight 700
color $accentColor
\ No newline at end of file
export default {
ext: {
"ret": 0,
"desc": "ok",
"data": [
{
"id": 7637,
"name": "map高德地图组件升级、离线设置个性化地图、样式文件 (ios、android)",
"version": "1.0.1",
"description": "nvue map 高德地图组件升级、离线个性化地图、离线设置样式文件、可使用nvue map 所有功能、免VIP使用个性化地图 QQ群:656731785",
"tags": "nvue map 高德地图组件升级、离线个性化地图、离线设置样式文件、可使用nvuemap所有功能、免VIP使用个性化地图",
"platforms": [
"Android",
"iOS"
],
"category_level1": "App原生插件",
"category_level2": "App原生插件",
"total_download": 2,
"average_rating": 0,
"support_count": 0,
"tagsArray": [
"nvue",
"map",
"高德地图组件升级、离线个性化地图、离线设置样式文件、可使用nvuemap所有功能、免VIP使用个性化地图"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=7637"
},
{
"id": 7457,
"name": "【猫优壁纸】创作者收益系统、支持抖音、快手、微信、QQ四版本(尊享版)",
"version": "3.6.8.1",
"description": "版本升级来袭带PC后台管理,内置壁纸下载、头像下载、视频下载等超多丰富内容,更有完善的积分系统和订单系统,并且包括QQ小程序、微信小程序、抖音小程序三版本",
"tags": "maoeus-wall-pro 好看的前端UI设计 PC后台管理系统 云开发 完善的积分系统",
"platforms": [
"aliyun",
"tcb"
],
"category_level1": "uniCloud",
"category_level2": "云端一体项目模板",
"total_download": 103,
"average_rating": 0,
"support_count": 0,
"tagsArray": [
"maoeus-wall-pro",
"好看的前端UI设计",
"PC后台管理系统",
"云开发",
"完善的积分系统"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=7457"
},
{
"id": 6998,
"name": "原生sqlite数据库",
"version": "1.0.1",
"description": "原生稳定sqlite数据库增删查改,分页查询,版本升级,事务控制,批量执行",
"tags": "sqlite数据库 增删查改 分页查询版本升级事务控制批量执行",
"platforms": [
"Android",
"iOS"
],
"category_level1": "App原生插件",
"category_level2": "App原生插件",
"total_download": 13,
"average_rating": 5,
"support_count": 0,
"tagsArray": [
"sqlite数据库",
"增删查改",
"分页查询版本升级事务控制批量执行"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=6998"
},
{
"id": 6139,
"name": "【猫优壁纸】创作者收益系统、支持抖音、微信、QQ三版本(运营版)",
"version": "3.6.8",
"description": "版本升级来袭带PC后台管理,内置壁纸下载、头像下载、视频下载等超多丰富内容,更有完善的积分系统和订单系统,并且包括QQ小程序、微信小程序、抖音小程序三版本",
"tags": "maoeus-wallpaper 好看的前端UI设计 PC后台管理系统 云开发 完善的积分系统",
"platforms": [
"aliyun",
"tcb"
],
"category_level1": "uniCloud",
"category_level2": "云端一体项目模板",
"total_download": 482,
"average_rating": 4.5,
"support_count": 0,
"tagsArray": [
"maoeus-wallpaper",
"好看的前端UI设计",
"PC后台管理系统",
"云开发",
"完善的积分系统"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=6139"
},
{
"id": 7453,
"name": "【猫优壁纸】单系统、积分系统、壁纸、头像、视频、表情包下载(基础版)",
"version": "3.6.6.3",
"description": "版本升级来袭带PC后台管理,内置壁纸下载、头像下载、视频下载等超多丰富内容,更有完善的积分系统和订单系统,并且包括QQ小程序、微信小程序、抖音小程序三版本",
"tags": "maoeus-wall-base 好看的前端UI设计 PC后台管理系统 云开发 完善的积分系统",
"platforms": [
"aliyun",
"tcb"
],
"category_level1": "uniCloud",
"category_level2": "云端一体项目模板",
"total_download": 35,
"average_rating": 0,
"support_count": 0,
"tagsArray": [
"maoeus-wall-base",
"好看的前端UI设计",
"PC后台管理系统",
"云开发",
"完善的积分系统"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=7453"
},
{
"id": 3243,
"name": "app更新升级-wgt包热更新-整包更新-遮盖原生导航及tabbar ",
"version": "1.2.0",
"description": "请求接口返回ios及Android线上版本信息,再与本机app版本对比,如需更新则弹出更新提示框-支持外部链接、整包、热更新,",
"tags": "更新 热更新 app 不支持NVUE APP升级",
"platforms": [],
"category_level1": "前端组件",
"category_level2": "通用组件",
"total_download": 2997,
"average_rating": 4.7,
"support_count": 3,
"tagsArray": [
"更新",
"热更新",
"app",
"不支持NVUE",
"APP升级"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=3243"
},
{
"id": 5579,
"name": "腾讯Bugly插件",
"version": "1.0.4",
"description": "腾讯Bugly插件,APP检查应用版本升级、运营统计、主动上报异常,bug奔溃闪退日志、设置用户ID、用户标签",
"tags": "腾讯Bugly bugly 版本升级 运营统计 bug奔溃闪退日志收集",
"platforms": [
"Android",
"iOS"
],
"category_level1": "App原生插件",
"category_level2": "App原生插件",
"total_download": 49,
"average_rating": 2.3,
"support_count": 0,
"tagsArray": [
"腾讯Bugly",
"bugly",
"版本升级",
"运营统计",
"bug奔溃闪退日志收集"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=5579"
},
{
"id": 7238,
"name": "腾讯bugly服务插件",
"version": "1.0.1",
"description": "一键接入接入bugly服务",
"tags": "bugly 升级",
"platforms": [
"Android"
],
"category_level1": "App原生插件",
"category_level2": "App原生插件",
"total_download": 4,
"average_rating": 0,
"support_count": 0,
"tagsArray": [
"bugly",
"升级"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=7238"
},
{
"id": 1371,
"name": "app版本升级原生弹框和进度提示(包含wgt升级)",
"version": "1.48",
"description": "app版本升级(包含热更新)弹框提示和进度显示,apk自动安装",
"tags": "版本升级 进度提示 app自动安装 弹框提示 wgt升级",
"platforms": [
"Android",
"iOS"
],
"category_level1": "App原生插件",
"category_level2": "App原生插件",
"total_download": 4967,
"average_rating": 4.9,
"support_count": 1,
"tagsArray": [
"版本升级",
"进度提示",
"app自动安装",
"弹框提示",
"wgt升级"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=1371"
},
{
"id": 4542,
"name": "升级中心 uni-upgrade-center - App",
"version": "0.3.2",
"description": "uni升级中心 - 客户端检查更新",
"tags": "uniCloud update 升级 wgt",
"platforms": [
"aliyun",
"tcb"
],
"category_level1": "uniCloud",
"category_level2": "云端一体页面模板",
"total_download": 11374,
"average_rating": 4.8,
"support_count": 3,
"tagsArray": [
"uniCloud",
"update",
"升级",
"wgt"
],
"android_abis": [],
"url": "https://ext.dcloud.net.cn/plugin?id=4542"
}
]
},
ask: {
"code": 0,
"data": [
{
"url": "//ask.dcloud.net.cn/article/182",
"title": "App资源在线升级更新",
"content": " ... &amp;amp;&amp;amp;(wgtVer!=newVer)){\n\t\t\t\t\tdownWgt();\t// 下载&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;包\n\t\t\t\t}else{\n\t\t\t\t\tplus.nativeUI.alert(&amp;quot;无 ... ",
"type": "articles",
"comment_count": "212",
"view_count": "260871"
},
{
"url": "//ask.dcloud.net.cn/article/199",
"title": "App资源在线差量升级更新",
"content": " ... wgtu | 根节点 | | | |\t\n| appid | wgtu | 属性 | &lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;应用的appid | 是 |\n| basis | wgtu ... ",
"type": "articles",
"comment_count": "83",
"view_count": "76496"
},
{
"url": "//ask.dcloud.net.cn/article/17",
"title": "HBuilder升级失败或长时间无响应?",
"content": "如果装过其他eclipse插件,其&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;服务器可能连不上而导致报错。\n此时在工具-插件安装-手动安装eclipse插件-可用软件站点,取消其他插件的&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;地址。\n重新点击帮助-检查新版本即可。",
"type": "articles",
"comment_count": 0,
"view_count": "2500"
},
{
"url": "//ask.dcloud.net.cn/article/266",
"title": "增量升级",
"content": " ... ask.dcloud.net.cn/question/3388)\n\n**增量&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;的问题**\n增量、差量&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt; ... 。但是在使用H5 的增量&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;的时候遇到一个问题。 ... 是:\n当APP启动是出现&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;页面,用户点击增量 ... ",
"type": "articles",
"comment_count": "5",
"view_count": "3090"
},
{
"url": "//ask.dcloud.net.cn/article/273",
"title": "请Mac版用户近期不要升级MacOS 10.11beta3及10.11公测版",
"content": " ... 近期发布的公测版上,HBuilder也无法启动。\n**请大家近期不要&lt;span style='font-weight:bold;color:red'&gt;升级&lt;/span&gt;Mac 10.11beta3及10.11公测版,并等待苹果修复bug**\n详情 ... ",
"type": "articles",
"comment_count": "9",
"view_count": "3054"
}
],
"searchKeyword": "5Y2H57qn"
},
askHtml: `<div
class="aw-item active" >
<p class="aw-title">
<span class="tip-recommend">官方</span>
<a href="//ask.dcloud.net.cn/article/676" target="_blank"> 手机端监听<em>短信</em>验证码并自动提交表单 </a>
</p>
<p class="aw-search-body">
鉴定<em>短信</em>验证码仅能在App端实现,其他端无法使用。但在App端,更为合理的方式其实是使用一键登录。即无需<em>短信</em>验证码,一键登录获取真实的手机号。一键登陆的体验比<em>短信</em>验证码好很多,不需等待验证码、输入验证码,并且一键登陆的费用比<em>短信</em>便宜不少。所以*强烈推荐改用一键登陆*,uni一键登陆的文档另见:... </p>
<p class="aw-hide-text aw-text-color-666">
分类:
<a class="aw-search-result-tags"> 文章 </a>
77 个回复 •
32933 次查看 </p>
</div>
<div
class="aw-item active" >
<p class="aw-title">
<span class="tip-recommend">官方</span>
<a href="//ask.dcloud.net.cn/article/37534" target="_blank"> 发送<em>短信</em>教程(验证码、<em>短信</em>通知) </a>
</p>
<p class="aw-search-body">
添加模板完成后,如模板审核通过,则根据[https://uniapp.dcloud.io/uniCloud/send-<em>sms</em>](https://uniapp.dcloud.io/uniCloud/send-<em>sms</em>)进行<em>短信</em>接入。[attach]61670[/attach] 充值 <em>短信</em>验证码为预付费业务,使用<em>短信</em>验证码服务之前,需要先进行充值。点击页面上的“充值”按钮,可以选择预设金额进行充值,也可以选择自定义... </p>
<p class="aw-hide-text aw-text-color-666">
分类:
<a class="aw-search-result-tags"> 文章 </a>
60 个回复 •
16940 次查看 </p>
</div>
<div
class="aw-item active" >
<p class="aw-title">
<span class="tip-recommend">官方</span>
<a href="//ask.dcloud.net.cn/article/37746" target="_blank"> 公告:<em>短信</em>模板报备通知 </a>
</p>
<p class="aw-search-body">
由于近期有个别开发者利用<em>短信</em>服务发送违法信息,导致【uniID】、【uni验证】等<em>短信</em>服务模板被运营商停用。为了保证其他开发者可以正常使用,即日起,开发者使用<em>短信</em>服务需要进行<em>短信</em>模板...参考文档[https://uniapp.dcloud.net.cn/uniCloud/send-<em>sms</em>](https://uniapp.dcloud.net.cn/uniCloud/send-<em>sms</em>)更新发送<em>短信</em>相关代码。 </p>
<p class="aw-hide-text aw-text-color-666">
分类:
<a class="aw-search-result-tags"> 文章 </a>
43 个回复 •
3276 次查看 </p>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 plugin-li" data="4542">
<div class="row extend-list card has-preview-img">
<div class="col-sm-3 col-md-3 col-lg-3 preview-img should-pop" style="width: 20%;" data-toggle="popover" data-src="//img-cdn-aliyun.dcloud.net.cn/stream/plugin_screens/3c30aa60-8d63-11eb-9913-cd96a30ab764_0.jpg?image_process=quality,q_70&amp;v=1641979438" data-original-title="" title="">
<a href="https://ext.dcloud.net.cn/plugin?id=4542" target="_blank">
<img src="//img-cdn-aliyun.dcloud.net.cn/stream/plugin_screens/3c30aa60-8d63-11eb-9913-cd96a30ab764_0.jpg?image_process=quality,q_70&amp;v=1641979438">
</a>
</div>
<div class="col-xs-12 col-sm-9 col-md-9 col-lg-9 plugin-info">
<div class="row extend-list-header">
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-6">
<h2 class="extend-title elip">
<a href="https://ext.dcloud.net.cn/plugin?id=4542" target="_blank"> <em>升级中心</em> uni-upgrade-center-App </a>
</h2>
</div>
<div class="collection col-xs-6 col-sm-6 col-md-2 col-lg-2 pull-right ext-slg">
<a href="https://ext.dcloud.net.cn/plugin?id=4542" target="_blank" style="color: #ef5656;">
<img src="//img-cdn-aliyun.dcloud.net.cn/dev/img/ext/collection-small.png">&nbsp;收藏&nbsp;<span class="collection-list-total-4542">385</span>
</a>
</div>
<div class="support col-xs-6 col-sm-6 col-md-2 col-lg-2 pull-right ext-slg">
<a href="https://ext.dcloud.net.cn/plugin?id=4542" target="_blank" style="color: #ef5656;">
<img src="//img-cdn-aliyun.dcloud.net.cn/dev/img/ext/heart.png">&nbsp;赞赏&nbsp;3 </a>
</div>
<div class="download col-xs-6 col-sm-6 col-md-2 col-lg-2 pull-right ext-slg">
<a href="https://ext.dcloud.net.cn/plugin?id=4542" target="_blank" style="color: #4e92df;">
<img src="//img-cdn-aliyun.dcloud.net.cn/dev/img/ext/download.png">&nbsp;下载&nbsp;11539 </a>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<a href="https://ext.dcloud.net.cn/plugin?id=4542" target="_blank">
<span class="extend-list-desc">
uni<em>升级中心</em>-客户端检查更新 </span>
</a>
</div>
</div>
<div class="row rating" title="评分: 4.8 (129人评分)">
<div class="col-xs-12">
<div class="star-rating">
<div class="star-rating-top" style="width:95%">
<span class="glyphicon"></span>
<span class="glyphicon"></span>
<span class="glyphicon"></span>
<span class="glyphicon"></span>
<span class="glyphicon"></span>
</div>
<div class="star-rating-bottom">
<span class="glyphicon"></span>
<span class="glyphicon"></span>
<span class="glyphicon"></span>
<span class="glyphicon"></span>
<span class="glyphicon"></span>
</div>
</div>
<span class="star-rating-text">(129)</span>
</div>
</div>
<div class="row extend-list-tag">
<div class="col-xs-12">
<a class="badge bg-orange" href="javascript:;">uni_modules</a>
<a class="badge bg-green" href="javascript:;">uniCloud</a>
<a class="badge bg-green" href="javascript:;">update</a>
<a class="badge bg-green" href="javascript:;">升级</a>
<a class="badge bg-green" href="javascript:;">wgt</a>
</div>
</div>
<div class="row extend-list-more">
<div class="categorys col-xs-12 col-sm-6 col-md-4 col-lg-4" style="width:60%">
分类:<a href="javascript:;">uniCloud</a> /
<a href="javascript:;">云端一体页面模板</a>
</div>
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4" style="width:30%">
更新日期:2022-01-12</div>
<div class="extend-list-user col-md-4 col-lg-4" style="width: 10%">
<a href="https://ext.dcloud.net.cn/publisher?id=586192">
<img class="avatar pull-right" src="https://img-cdn-tc.dcloud.net.cn/uploads/avatar/001/67/43/81_avatar_max.jpg" alt="DCloud前端团队">
<span class="pull-right"></span>
</a>
</div>
</div>
</div>
<div class="plugin-action" style="display: none;">
<div class="plugin-like-btn " onclick="collection(this, 4542)">收藏</div>
</div>
</div>
</div>
`
}
\ No newline at end of file
const isProduction = process.env.NODE_ENV === "production"
const isMock = false
import mock from './mock'
function ajax(url = '', method = 'get',) {
return new Promise((resolve, reject) => {
if (!url) reject('url 不可为空')
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
resolve(this.response)
}
}
xhr.send()
})
}
export async function postExt(query) {
const base = isProduction ? '//ext.dcloud.net.cn' : '/ext'
let extRet
if (!isMock) {
const extRes = await ajax(base + '/search/json?query=' + encodeURIComponent(query))
extRet = JSON.parse(extRes);
} else {
extRet = mock.ext;
}
let extHtml = '';
let data = extRet.data;
if (extRet.ret === 0) {
for (let i = 0, len = data.length; i < len; i++) {
extHtml += _renderExt(data[i], query);
}
}
return {
html: extHtml,
hits: data.length
}
}
export async function postAsk(query, page = 1) {
const base = isProduction ? '//ask.dcloud.net.cn' : '/ask'
let askHtml = '';
if (!isMock) {
askHtml = await ajax(base + `/search/ajax/search_result/search_type-all__q-${query}__page-${page}`)
if (!askHtml) {
return;
}
} else {
askHtml = mock.askHtml
}
return {
html: askHtml,
hits: 0
}
}
function _renderExt(ext, keyword) {
return `<div class="matching-post">
<a href="${ext.url}" target="_blank">
<div class="post-wrapper">
<h2>
<p class="aw-text">
<span class="post-tag">插件</span>
</p>
${_handleHTMLString(ext.name, keyword)}
</h2>
</div>
<p>${ext.total_download}次下载</p>
<p>${_handleHTMLString(ext.description, keyword)}</p>
</a>
</div>`
}
function _renderPost(post, value) {
let html = '';
let commentText = '';
let tagName = '规范';
// 1,问题;2,文章;默认是规范。
switch (post.type) {
case 'questions':
tagName = '问题';
break;
case 'articles':
tagName = '文章';
break;
}
if (!!value) {
post.title = _handleHTMLString(post.title, value);
post.content = _handleHTMLString(post.content, value);
}
html += `<div class="matching-post">
<a href="${post.url}" target="_blank">
<div class="post-wrapper">
<h2>
<p class="aw-text">
<span class="post-tag">${tagName}</span>
</p>
${post.title}
</h2>
</div>`
if (!!value) {
commentText = post.type === 'questions' ? '回复' : '评论';
html += `<p>
${post.comment_count}${commentText}
<span class="aw-text-space">-</span>
${post.view_count}次浏览
</p>`;
}
html += `\n<p>${post.content}</p>\n</a>\n</div>`;
return html;
}
function _handleHTMLString(dataString, keyword) {
let keywordReg = new RegExp(
keyword.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'),
'gi'
);
let tagStartReg = new RegExp(
'&lt;span style=\'font-weight:bold;color:red\'&gt;',
'g'
);
let tagEndReg = new RegExp(
'&lt;/span&gt;',
'g'
);
return dataString
.replace(tagStartReg, '')
.replace(tagEndReg, '')
.replace(keywordReg, ("<em class=\"search-keyword\">" + keyword + "</em>"));
};
\ No newline at end of file
import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser';
import { removeHighlightTags, groupBy } from './searchUtils'
let searchClient
function createSearchClient(appId, apiKey) {
if (searchClient) return searchClient
searchClient = algoliasearch(appId, apiKey);
searchClient.addAlgoliaAgent('dcloudsearch', '1.0.0');
return searchClient
}
export function search({ query, indexName, appId, apiKey, searchParameters = {}, snippetLength = 0, transformItems = () => { }, onClose = () => { }, ...args }) {
return createSearchClient(appId, apiKey)
.search([
{
query,
indexName,
params: {
attributesToRetrieve: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
'type',
'url',
'url_without_anchor',
'category',
'tag'
],
attributesToSnippet: [
`hierarchy.lvl1:${snippetLength}`,
`hierarchy.lvl2:${snippetLength}`,
`hierarchy.lvl3:${snippetLength}`,
`hierarchy.lvl4:${snippetLength}`,
`hierarchy.lvl5:${snippetLength}`,
`hierarchy.lvl6:${snippetLength}`,
`content:${snippetLength}`,
],
snippetEllipsisText: '',
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
hitsPerPage: 20,
...searchParameters,
...args,
},
},
])
.catch((error) => {
throw error;
})
.then(({ results }) => {
const { hits, hitsPerPage, nbHits, nbPages, page } = results[0];
const sources = groupBy(hits, (hit) => removeHighlightTags(hit));
return {
hitsPerPage, nbHits, nbPages, page,
hits: Object.values(sources).map(
(items, index) => {
return {
sourceId: `hits${index}`,
onSelect({ item, event }) {
// saveRecentSearch(item);
if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
onClose()
}
},
getItemUrl({ item }) {
return item.url;
},
getItems() {
return Object.values(
groupBy(items, (item) => item.hierarchy.lvl1)
)
.map(transformItems)
.map((groupedHits) =>
groupedHits.map((item) => {
return {
...item,
__docsearch_parent:
item.type !== 'lvl1' &&
groupedHits.find(
(siblingItem) =>
siblingItem.type === 'lvl1' &&
siblingItem.hierarchy.lvl1 ===
item.hierarchy.lvl1
),
};
})
).flat();
},
};
}
)
}
});
}
\ No newline at end of file
export function groupBy(values, predicate) {
return values.reduce((acc, item) => {
const key = predicate(item);
if (!acc.hasOwnProperty(key)) {
acc[key] = [];
}
// We limit each section to show 5 hits maximum.
// This acts as a frontend alternative to `distinct`.
if (acc[key].length < 5) {
acc[key].push(item);
}
return acc;
}, {});
}
const regexHighlightTags = /(<mark>|<\/mark>)/g;
const regexHasHighlightTags = RegExp(regexHighlightTags.source);
export function removeHighlightTags(hit) {
const internalDocSearchHit = hit
if (!internalDocSearchHit.__docsearch_parent && !hit._highlightResult) {
return hit.hierarchy.lvl0;
}
const { value } =
(internalDocSearchHit.__docsearch_parent
? internalDocSearchHit.__docsearch_parent?._highlightResult?.hierarchy
?.lvl0
: hit._highlightResult?.hierarchy?.lvl0) || {};
return value && regexHasHighlightTags.test(value)
? value.replace(regexHighlightTags, '')
: value;
}
export function isEditingContent(event) {
const element = event.target;
const tagName = element.tagName;
return (
element.isContentEditable ||
tagName === 'INPUT' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
);
}
\ No newline at end of file
<template>
<div ref="container" id="footNavBox">
<div class="footNav">
<div id="footNavClassify">
<footNavItem :list="footNavList" />
</div>
<div id="aboutusBox">
<footNavItem :list="aboutusList" />
</div>
</div>
<div class="hbLogo"></div>
<div class="companyBox">
<span class="companyInfo">DCloud 即数字天堂(北京)网络技术有限公司是</span>
<div style="display: inline; margin-left: 5px" class="companyInfo">
<a href="//www.w3.org/" target="_blank" class="w3c">W3C</a>
成员及
<a href="//www.html5plus.org/" target="_blank" class="html5">HTML5中国产业联盟</a>
发起单位
</div>
</div>
<div class="beianBox">
<a
id="domain"
class="beian"
href="https://beian.miit.gov.cn/#/Integrated/index"
target="_blank"
>
{{ domain }}
</a>
<div class="domainImgBox">
<img
class="domainImg"
src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-f184e7c3-1912-41b2-b81f-435d1b37c7b4/769929a3-65eb-4d11-815d-84f88197a152.png"
/>
<a
class="beian"
href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802035340"
target="_blank"
>
京公网安备:11010802035340号
</a>
</div>
<span class="anbaoInfo">国家信息安全等级保护三级,证书编号:11010813802-20001</span>
</div>
</div>
</template>
<script>
import footerConfig from '../config/footer';
const { footNavList, aboutusList } = footerConfig;
export default {
components: {
footNavItem: {
functional: true,
props: {
list: {
type: Array,
default: () => [],
},
},
render: (h, { props }) => {
return props.list.map((footNavListItem, _index) =>
h('div', { staticClass: 'footNavItem', key: footNavListItem.title || _index }, [
h('div', { staticClass: 'navItemTitle' }, footNavListItem.title),
h('div', { staticClass: 'navLine' }),
h(
'div',
{ staticClass: 'navItemDetailBox' },
footNavListItem.content.map((item, index) =>
h(
'a',
{
staticClass: 'navItemDetail',
key: item.url || index,
attrs: {
target: '_blank',
href: item.url,
},
},
item.subTitle
)
)
),
])
);
},
},
},
data: () => ({
footNavList: Object.freeze(footNavList),
aboutusList: Object.freeze(aboutusList),
domain: '',
}),
mounted() {
if (document.domain === 'uniapp.dcloud.net.cn') {
this.domain = '京ICP备12046007号-4';
} else {
this.domain = '蒙ICP备14002744号-1';
}
this.fixBottom();
},
methods: {
fixBottom() {
this.$nextTick(() => {
this.$refs.container.style.bottom = `0px`;
const bottom =
document.documentElement.clientHeight -
this.$refs.container.getBoundingClientRect().bottom;
if (bottom > 0) {
const preBottom = parseFloat(this.$refs.container.style.bottom);
this.$refs.container.style.position = 'relative';
this.$refs.container.style.bottom = `-${bottom}px`;
} else {
this.$refs.container.removeAttribute('style');
}
});
},
},
watch: {
$route() {
this.fixBottom();
},
},
};
</script>
<style lang="stylus" scoped>
@import '../styles/footer.styl'
</style>
<template>
<div
v-if="show"
class="main-navbar-link"
>
<DropdownLink
v-if="item.type === 'links'"
:item="item"
/>
<NavLink
v-else
:item="item"
/>
</div>
</template>
<script>
import DropdownLink from '@theme/components/DropdownLink.vue'
import NavLink from '@theme/components/NavLink.vue'
export default {
name: 'MainNavbarLink',
components: {
DropdownLink,
NavLink
},
props: {
item: {
type: Object,
default: () => ({})
}
},
computed: {
show() {
return this.item.type === 'link'
? this.item.link
: this.item.type === 'links'
? !!this.item.items.length
: false
}
}
}
</script>
<style lang="stylus" scope>
.main-navbar-link a
color inherit
@media (max-width: $MQMobile)
ul
list-style none
a
padding-top 0 !important
</style>
\ No newline at end of file
<template>
<RouterLink
v-if="isInternal"
class="nav-link"
:to="link"
:exact="exact"
@focusout.native="focusoutAction"
>
{{ item.text }}
</RouterLink>
<a
v-else
:href="link"
class="nav-link external"
:target="target"
:rel="rel"
@focusout="focusoutAction"
>
{{ item.text }}
<OutboundLink v-if="item.needOutbound !== false && isBlankTarget" />
</a>
</template>
<script>
import { isExternal, isMailto, isTel, ensureExt } from '../util'
export default {
name: 'NavLink',
inject: [ 'customNavBar' ],
props: {
item: {
required: true
}
},
computed: {
link () {
return ensureExt(this.item.link)
},
exact () {
if (this.$site.locales) {
return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link)
}
return this.link === '/'
},
isNonHttpURI () {
return isMailto(this.link) || isTel(this.link)
},
isBlankTarget () {
return this.target === '_blank'
},
isInternal () {
return !isExternal(this.link) && !this.isBlankTarget
},
target () {
if (this.isNonHttpURI) {
return null
}
if (this.item.target) {
return this.item.target
}
return isExternal(this.link) ? '_blank' : ''
},
rel () {
if (this.isNonHttpURI) {
return null
}
if (this.item.rel === false) {
return null
}
if (this.item.rel) {
return this.item.rel
}
return this.isBlankTarget ? 'noopener noreferrer' : null
}
},
methods: {
focusoutAction () {
this.$emit('focusout')
}
}
}
</script>
<template>
<nav
v-if="(userLinks.length || repoLink) && showSubNavBar"
class="nav-links"
>
<!-- user links -->
<div
v-for="item in userLinks"
:key="item.link"
class="nav-item"
>
<DropdownLink
v-if="item.type === 'links'"
:item="item"
/>
<NavLink
v-else
:item="item"
/>
</div>
<!-- repo link -->
<!-- <a
v-if="repoLink"
:href="repoLink"
class="repo-link"
target="_blank"
rel="noopener noreferrer"
>
{{ repoLabel }}
<OutboundLink />
</a> -->
</nav>
</template>
<script>
import DropdownLink from '@theme/components/DropdownLink.vue'
import { resolveNavLinkItem } from '../util'
import NavLink from '@theme/components/NavLink.vue'
import navInject from '../mixin/navInject';
export default {
name: 'NavLinks',
mixins: [ navInject ],
components: {
NavLink,
DropdownLink
},
computed: {
userNav () {
return this.customNavBar[this.navConfig.userNavIndex].items || []
// return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || []
},
nav () {
const { locales } = this.$site
if (locales && Object.keys(locales).length > 1) {
const currentLink = this.$page.path
const routes = this.$router.options.routes
const themeLocales = this.$site.themeConfig.locales || {}
const languageDropdown = {
text: this.$themeLocaleConfig.selectText || 'Languages',
ariaLabel: this.$themeLocaleConfig.ariaLabel || 'Select language',
items: Object.keys(locales).map(path => {
const locale = locales[path]
const text = themeLocales[path] && themeLocales[path].label || locale.lang
let link
// Stay on the current page
if (locale.lang === this.$lang) {
link = currentLink
} else {
// Try to stay on the same page
link = currentLink.replace(this.$localeConfig.path, path)
// fallback to homepage
if (!routes.some(route => route.path === link)) {
link = path
}
}
return { text, link }
})
}
return [...this.userNav, languageDropdown]
}
return this.userNav
},
userLinks () {
return (this.nav || []).map(link => {
return Object.assign(resolveNavLinkItem(link), {
items: (link.items || []).map(resolveNavLinkItem)
})
})
},
repoLink () {
const { repo } = this.$site.themeConfig
if (repo) {
return /^https?:/.test(repo)
? repo
: `https://github.com/${repo}`
}
return null
},
repoLabel () {
if (!this.repoLink) return
if (this.$site.themeConfig.repoLabel) {
return this.$site.themeConfig.repoLabel
}
const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0]
const platforms = ['GitHub', 'GitLab', 'Bitbucket']
for (let i = 0; i < platforms.length; i++) {
const platform = platforms[i]
if (new RegExp(platform, 'i').test(repoHost)) {
return platform
}
}
return 'Source'
}
}
}
</script>
<style lang="stylus">
.nav-links
display inline-block
a
line-height 1.4rem
color inherit
&:hover, &.router-link-active
color $accentColor
.nav-item
position relative
display inline-block
margin-left 1.5rem
line-height 2rem
&:first-child
margin-left 0
.repo-link
margin-left 1.5rem
@media (max-width: $MQMobile)
.nav-links
.nav-item, .repo-link
margin-left 0
@media (min-width: $MQMobile)
.nav-item > .nav-link:not(.external)
display block
height 40px
line-height 40px
min-width 24px
padding 0 2vw
background-color #fff
border-radius 4px
transition background .3s
font-size 14px
/* .nav-links a
&:hover, &.router-link-active
color $textColor*/
.nav-item > a:not(.external)
&:hover
background-color #f0f0f0
&.router-link-active
color #fff
background-color $accentColor
</style>
<template>
<header class="navbar" :class="{ 'navbar-fixed': fixedNavbar }">
<div class="main-navbar">
<!-- <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" /> -->
<NavbarLogo />
<div class="main-navbar-links can-hide">
<template v-for="(item, index) in customNavBar">
<div :class="mainNavLinkClass(index)" :key="item.text">
<MainNavbarLink v-if="item.type" :item='item' />
<a v-else href="javascript:;" @click="changeUserNav(index)">{{item.text}}</a>
</div>
</template>
</div>
<div class="mobile-main-navbar">
<div class="mobile-links_mobile">
<a href="javascript:;" class="mobile-links__btn" @click="toggleMobilePanel">{{mainNavBarText}}</a>
</div>
<div class="mobile-links__panel" :class="{open: showMobilePanel}">
<template v-for="(item, index) in customNavBar">
<div :class="mainNavLinkClass(index)" :key="item.text">
<MainNavbarLink v-if="item.type" :item='item' @click.native="toggleMobilePanel"/>
<a v-else href="javascript:;" @click="changeUserNav(index),toggleMobilePanel()">{{item.text}}</a>
</div>
</template>
</div>
</div>
<div
class="links"
:style="SearchBoxStyle"
>
<!-- <a class="switch-version" href="javascript:void(0)">回到旧版</a> -->
<DcloudSearchPage v-if="isAlgoliaSearch" ref="dcloudSearchPage" :options="algolia"/>
<AlgoliaSearchBox v-if="isAlgoliaSearch" />
<SearchBox v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false" />
</div>
</div>
<div class="sub-navbar">
<DropdownLink
class="custom-main-navbar can-hide"
v-if="showSubNavBar && fixedNavbar"
:item="{
text: customNavBarKeys[navConfig.userNavIndex],
items: customNavBar
}"
/>
<NavLinks class="can-hide" />
<div class="mobile-sub-navbar">
<div class="subnavbar__item" @click="$emit('toggle-sidebar')">
<a href="javascript:;">{{subNavBarText}}</a>
</div>
</div>
</div>
</header>
</template>
<script>
import AlgoliaSearchBox from './AlgoliaSearchBox'
import SearchBox from './SearchBox'
import SidebarButton from '@theme/components/SidebarButton.vue'
import NavLinks from '@theme/components/NavLinks.vue'
import MainNavbarLink from './MainNavbarLink.vue';
import NavbarLogo from './NavbarLogo.vue';
import DcloudSearchPage from './DcloudSearchPage';
import navInject from '../mixin/navInject';
import DropdownLink from '@theme/components/DropdownLink.vue'
import { forbidScroll, os } from '../util';
export default {
name: 'Navbar',
mixins: [ navInject ],
components: {
SidebarButton,
NavLinks,
MainNavbarLink,
SearchBox,
AlgoliaSearchBox,
NavbarLogo,
DcloudSearchPage,
DropdownLink
},
data () {
return {
linksWrapMaxWidth: null,
showMobilePanel: false,
fixedNavbar: false,
SearchBoxTop: 0
}
},
computed: {
algolia () {
return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
},
isAlgoliaSearch () {
return this.algolia && this.algolia.apiKey && this.algolia.indexName
},
SearchBoxStyle () {
const initStyle = {
top: `${this.SearchBoxTop}px`,
zIndex: 100
};
return this.linksWrapMaxWidth
? Object.assign({}, initStyle, {
'max-width': this.linksWrapMaxWidth + 'px',
})
: initStyle;
}
},
mounted () {
const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl
const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight'))
const handleLinksWrapWidth = () => {
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
this.linksWrapMaxWidth = null
} else {
this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING
- (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0)
}
this.$nextTick(this.initNavBar)
}
handleLinksWrapWidth()
window.addEventListener('resize', handleLinksWrapWidth, false)
this.initNavBar()
},
methods: {
initNavBar () {
os.init()
this.sideBar = document.querySelector('.sidebar')
this.navbar = document.querySelector('.navbar')
this.mainNavBar = document.querySelector('.main-navbar')
this.subNavBar = document.querySelector('.sub-navbar')
this.pageContainer = document.querySelector('.page')
this.vuepressToc = document.querySelector('.vuepress-toc')
this.navbarHeight = this.navbar.clientHeight
this.subNavBarHeight = this.subNavBar.clientHeight
this.mainNavBarHeight = this.mainNavBar.clientHeight
this.scrollBehavior()
},
scrollBehavior () {
this.removeWindowScroll()
this.onWindowScroll()
if(this.showSubNavBar) {
this.addWindowScroll()
} else {
this.fixedNavbar = true
this.SearchBoxTop = 0
}
},
addWindowScroll () {
if (os.pc) {
window.addEventListener('scroll', this.onWindowScroll, false)
}
},
removeWindowScroll () {
window.removeEventListener('scroll', this.onWindowScroll)
this.fixedNavbar = false
this.sideBar && this.sideBar.removeAttribute('style')
this.vuepressToc && this.vuepressToc.removeAttribute('style')
this.navbar && this.navbar.removeAttribute('style')
if (this.pageContainer) {
this.pageContainer.style.marginTop = this.showSubNavBar || !os.pc ? 'auto' : `${this.navbarHeight}px`
}
},
onWindowScroll () {
const scrollTop = !this.showSubNavBar ? 0 : document.documentElement.scrollTop || document.body.scrollTop;
if (!this.fixedNavbar) {
let sideTop = this.navbarHeight - scrollTop
sideTop <= this.subNavBarHeight && (sideTop = this.subNavBarHeight)
this.sideBar && (this.sideBar.style.top = `${sideTop + 1}px`)
this.vuepressToc && (this.vuepressToc.style.top = `${sideTop + 1}px`)
}
if (scrollTop >= this.mainNavBarHeight) {
if (!this.fixedNavbar) {
this.fixedNavbar = true
this.navbar.style.top = `-${this.mainNavBarHeight}px`
this.SearchBoxTop = this.mainNavBarHeight + (this.subNavBarHeight - this.mainNavBarHeight) / 2
this.$nextTick(() => {
this.pageContainer && (this.pageContainer.style.marginTop = `${this.navbarHeight}px`)
})
}
} else if (scrollTop < this.mainNavBarHeight) {
if (this.fixedNavbar) {
this.fixedNavbar = false
this.pageContainer && (this.pageContainer.style.marginTop = 'auto')
this.SearchBoxTop = 0
}
}
},
fixedSideBarHeight () {
if(!os.pc) return
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
this.navbarHeight = this.navbar.clientHeight
this.subNavBarHeight = this.subNavBar.clientHeight
const setHeight = !!scrollTop || this.fixedNavbar ? this.subNavBarHeight : this.navbarHeight
this.sideBar.style.top = `${setHeight + 1}px`
this.vuepressToc.style.top = `${setHeight + 1}px`
},
mainNavLinkClass (index) {
return ['main-navbar-item', this.navConfig.userNavIndex === index ? 'active' : '']
},
toggleMobilePanel () {
this.showMobilePanel = !this.showMobilePanel
forbidScroll(this.showMobilePanel)
},
switchVersion () {
document.cookie = encodeURIComponent('__old_version') + "=__old_version; path=/"
// document.cookie = encodeURIComponent('__new_version') + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"
location.replace(location.origin + '?v=' + Date.now())
}
},
watch: {
fixedNavbar () {
this.fixedSideBarHeight()
this.scrollBehavior()
},
'navConfig.userNavIndex' () {
this.$nextTick(()=>{
this.fixedSideBarHeight()
this.scrollBehavior()
})
}
}
}
function css (el, property) {
// NOTE: Known bug, will return 'auto' if style value is 'auto'
const win = el.ownerDocument.defaultView
// null means not to return pseudo styles
return win.getComputedStyle(el, null)[property]
}
</script>
<style lang="stylus">
$navbar-vertical-padding = 0.7rem
$navbar-horizontal-padding = 1.5rem
@import '../styles/navbar'
.navbar
// padding $navbar-vertical-padding $navbar-horizontal-padding
line-height $navbar-main-navbar-height // $navbarHeight - 1.4rem
a, span, img
display inline-block
.title-logo
.logo
height $navbar-logo-height // $navbarHeight - 1.4rem
min-width $navbar-main-navbar-height // $navbarHeight - 1.4rem
margin-right 0.8rem
vertical-align top
.site-name
font-size 1.3rem
font-weight 600
color $textColor
position relative
.links
height 100%
padding-left 1.5rem
box-sizing border-box
background-color white
white-space nowrap
font-size 0.9rem
position absolute
right $navbar-horizontal-padding
top 0 //$navbar-vertical-padding
transition: top 0.1s ease 0s
display flex
.search-box
flex: 0 0 auto
vertical-align top
@media (max-width: $MQMobile)
.navbar
// padding-left 4rem
.can-hide
display none !important
.links
padding-left 0rem // 1.5rem
top 0 !important
.site-name
width calc(100vw - 9.4rem)
overflow hidden
white-space nowrap
text-overflow ellipsis
</style>
<template>
<a href="https://www.dcloud.io" class="home-link">
<img
v-if="$site.themeConfig.logo"
class="logo"
:src="$withBase($site.themeConfig.logo)"
:alt="$siteTitle"
/>
<img
v-if="$site.themeConfig.titleLogo"
class="title-logo can-hide"
:src="$withBase($site.themeConfig.titleLogo)"
:alt="$siteTitle"
/>
<span
v-else-if="$siteTitle"
ref="siteName"
class="site-name"
:class="{ 'can-hide': $site.themeConfig.logo }"
>
{{ $siteTitle }}
</span>
</a>
</template>
<template>
<div class="search-box">
<input
ref="input"
aria-label="Search"
:value="query"
:class="{ focused: focused }"
:placeholder="placeholder"
class="custom-search"
autocomplete="off"
spellcheck="false"
@input="query = $event.target.value"
@focus="focused = true"
@blur="focused = false"
@keyup.enter="go(focusIndex)"
@keyup.up="onUp"
@keyup.down="onDown"
/>
<ul
v-if="showSuggestions"
class="suggestions"
:class="{ 'align-right': alignRight }"
style="z-index: 1"
@mouseleave="unfocus"
>
<template v-if="this.suggestions && this.suggestions.length">
<li
v-for="(s, i) in suggestions"
:key="i"
class="suggestion"
:class="{ focused: i === focusIndex }"
@mousedown="go(i)"
@mouseenter="focus(i)"
>
<a :href="s.path" @click.prevent>
<div class="suggestion-item">
<span class="page-title">{{ s.title || 'Document' }}</span>
<span v-if="s.header" class="header">{{ s.header.title }}</span>
</div>
</a>
</li>
</template>
<li v-else class="suggestion">
<div class="suggestion-item">
<span class="page-title">暂无结果</span>
</div>
</li>
</ul>
</div>
</template>
<script>
import matchQuery from './match-query';
/* global SEARCH_MAX_SUGGESTIONS, SEARCH_PATHS, SEARCH_HOTKEYS */
export default {
name: 'SearchBox',
data() {
return {
query: '',
focused: false,
focusIndex: 0,
placeholder: undefined,
SEARCH_PATHS: null,
SEARCH_HOTKEYS: ['s', '/'],
SEARCH_MAX_SUGGESTIONS: 5,
};
},
computed: {
showSuggestions() {
return this.focused && this.query && this.query.length; // && this.suggestions && this.suggestions.length;
},
suggestions() {
const query = this.query.trim().toLowerCase();
if (!query) {
return;
}
const { pages } = this.$site;
const max = this.$site.themeConfig.searchMaxSuggestions || this.SEARCH_MAX_SUGGESTIONS;
const localePath = this.$localePath;
const res = [];
for (let i = 0; i < pages.length; i++) {
if (res.length >= max) break;
const p = pages[i];
// filter out results that do not match current locale
if (this.getPageLocalePath(p) !== localePath) {
continue;
}
// filter out results that do not match searchable paths
if (!this.isSearchable(p)) {
continue;
}
if (matchQuery(query, p)) {
res.push(p);
} else if (p.headers) {
for (let j = 0; j < p.headers.length; j++) {
if (res.length >= max) break;
const h = p.headers[j];
if (h.title && matchQuery(query, p, h.title)) {
res.push(
Object.assign({}, p, {
path: p.path + '#' + h.slug,
header: h,
})
);
}
}
}
}
return res;
},
// make suggestions align right when there are not enough items
alignRight() {
const navCount = (this.$site.themeConfig.nav || []).length;
const repo = this.$site.repo ? 1 : 0;
return navCount + repo <= 2;
},
},
mounted() {
this.placeholder = this.$site.themeConfig.searchPlaceholder || '';
document.addEventListener('keydown', this.onHotkey);
},
beforeDestroy() {
document.removeEventListener('keydown', this.onHotkey);
},
methods: {
getPageLocalePath(page) {
for (const localePath in this.$site.locales || {}) {
if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
return localePath;
}
}
return '/';
},
isSearchable(page) {
let searchPaths = this.SEARCH_PATHS;
// all paths searchables
if (searchPaths === null) {
return true;
}
searchPaths = Array.isArray(searchPaths) ? searchPaths : new Array(searchPaths);
return (
searchPaths.filter(path => {
return page.path.match(path);
}).length > 0
);
},
onHotkey(event) {
if (event.srcElement === document.body && this.SEARCH_HOTKEYS.includes(event.key)) {
this.$refs.input.focus();
event.preventDefault();
}
},
onUp() {
if (this.showSuggestions) {
if (this.focusIndex > 0) {
this.focusIndex--;
} else {
this.focusIndex = this.suggestions.length - 1;
}
}
},
onDown() {
if (this.showSuggestions) {
if (this.focusIndex < this.suggestions.length - 1) {
this.focusIndex++;
} else {
this.focusIndex = 0;
}
}
},
go(i) {
if (!this.showSuggestions) {
return;
}
this.$router.push(this.suggestions[i].path);
this.query = '';
this.focusIndex = 0;
},
focus(i) {
this.focusIndex = i;
},
unfocus() {
this.focusIndex = -1;
},
},
};
</script>
<style lang="stylus">
.search-box
display inline-block
position relative
margin-right 1rem
input
cursor text
width 10rem
height: 2rem
color lighten($textColor, 25%)
display inline-block
border 1px solid darken($borderColor, 10%)
border-radius 2rem
font-size 0.9rem
line-height 2rem
padding 0 0.5rem 0 2rem
outline none
transition all .2s ease
background #fff url(search.svg) 0.6rem 0.5rem no-repeat
background-size 1rem
&:focus
cursor auto
border-color $accentColor
.suggestions
background #fff
width 20rem
position absolute
top 2 rem
border 1px solid darken($borderColor, 10%)
border-radius 6px
padding 0.4rem
list-style-type none
&.align-right
right 0
.suggestion
line-height 1.4
padding 0.4rem 0.6rem
border-radius 4px
cursor pointer
a
white-space normal
color lighten($textColor, 35%)
.page-title
font-weight 600
.header
font-size 0.9em
margin-left 0.25em
&.focused
background-color #f3f4f5
a
color $accentColor
@media (max-width: $MQNarrow)
.search-box
input
cursor pointer
width 0
border-color transparent
position relative
&:focus
cursor text
left 0
width 10rem
// Match IE11
@media all and (-ms-high-contrast: none)
.search-box input
height 2rem
@media (max-width: $MQNarrow) and (min-width: $MQMobile)
.search-box
.suggestions
left 0
@media (max-width: $MQMobile)
.search-box
margin-right 0
input
left 1rem
.suggestions
right 0
@media (max-width: $MQMobileNarrow)
.search-box
.suggestions
width calc(100vw - 4rem)
input:focus
width 8rem
// by Lxh
.search-box
.suggestions
width 30rem
.suggestion
$li-border-color = #f0f0f0
border 1px solid $li-border-color
border-radius 0
padding 0
&:not(:last-child)
border-bottom none
&:first-child
border-top-left-radius 6px
border-top-right-radius 6px
&:last-child
border-bottom 1px solid $li-border-color
border-bottom-left-radius 6px
border-bottom-right-radius 6px
a
width 100%
.suggestion-item
width 100%
display flex
.page-title
flex 1
width 9rem
background-color #f7f7f7
padding 10px
display flex
flex-direction column
justify-content center
text-align center
word-break break-all
.header
flex 2
padding-left 20px
display flex
flex-direction column
justify-content center
text-align left
</style>
import get from 'lodash/get'
export default (query, page, additionalStr = null) => {
let domain = get(page, 'title', '')
if (get(page, 'frontmatter.tags')) {
domain += ` ${page.frontmatter.tags.join(' ')}`
}
if (additionalStr) {
domain += ` ${additionalStr}`
}
return matchTest(query, domain)
}
const matchTest = (query, domain) => {
const escapeRegExp = str => str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
// eslint-disable-next-line no-control-regex
const nonASCIIRegExp = new RegExp('[^\x00-\x7F]')
const words = query
.split(/\s+/g)
.map(str => str.trim())
.filter(str => !!str)
if (!nonASCIIRegExp.test(query)) {
// if the query only has ASCII chars, treat as English
const hasTrailingSpace = query.endsWith(' ')
const searchRegex = new RegExp(
words
.map((word, index) => {
if (words.length === index + 1 && !hasTrailingSpace) {
// The last word - ok with the word being "startswith"-like
return `(?=.*\\b${escapeRegExp(word)})`
} else {
// Not the last word - expect the whole word exactly
return `(?=.*\\b${escapeRegExp(word)}\\b)`
}
})
.join('') + '.+',
'gi'
)
return searchRegex.test(domain)
} else {
// if the query has non-ASCII chars, treat as other languages
return words.some(word => domain.toLowerCase().indexOf(word) > -1)
}
}
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="12" height="13"><g stroke-width="2" stroke="#aaa" fill="none"><path d="M11.29 11.71l-4-4"/><circle cx="5" cy="5" r="4"/></g></svg>
<script>
import { isActive, hashRE, groupHeaders } from '../util'
export default {
functional: true,
props: ['item', 'sidebarDepth'],
render (h,
{
parent: {
$page,
$site,
$route,
$themeConfig,
$themeLocaleConfig
},
props: {
item,
sidebarDepth
}
}) {
// use custom active class matching logic
// due to edge case of paths ending with / + hash
const selfActive = isActive($route, item.path)
const maxDepth = [
$page.frontmatter.sidebarDepth,
sidebarDepth,
$themeLocaleConfig.sidebarDepth,
$themeConfig.sidebarDepth,
1
].find(depth => depth !== undefined)
// for sidebar: auto pages, a hash link should be active if one of its child
// matches
const active = item.type === 'auto'
? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
: selfActive
const link = item.type === 'external'
? renderExternal(h, item.path, item.title || item.path)
: renderLink(h, item.path, item.title || item.path, active, undefined, item.headers)
const displayAllHeaders = $themeLocaleConfig.displayAllHeaders
|| $themeConfig.displayAllHeaders
if (item.type === 'auto') {
return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
} else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
const children = groupHeaders(item.headers)
return [link, renderChildren(h, children, item.path, $route, maxDepth)]
} else {
return link
}
}
}
let beforeActiveLinkText = ''
function renderLink (h, to, text, active, level, headers) {
let VNode
let firstRender
if(headers && active){
firstRender = beforeActiveLinkText !== text
beforeActiveLinkText = text
}
const component = {
props: {
to,
activeClass: '',
exactActiveClass: ''
},
class: {
active,
'sidebar-link': true,
'data-no-emphasize': headers && headers.some(item => item.level < 3)
},
nativeOn: {
click: (e) => {
if (firstRender) { firstRender = false; return; }
if (e.target.className.indexOf('data-no-emphasize') === -1) return;
const child = VNode.componentOptions.children[0].elm.parentElement.nextElementSibling
if(!child) return
const originDisplay = child.style.display
child.style.display = originDisplay === 'none' ? 'block' : 'none'
}
}
}
if (level > 2) {
component.style = {
'padding-left': level + 'rem'
}
}
VNode = h('RouterLink', component, text)
return VNode
}
function renderChildren (h, children, path, route, maxDepth, depth = 1) {
if (!children || depth > maxDepth) return null
return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
const active = isActive(route, path + '#' + c.slug)
return h('li', { class: 'sidebar-sub-header' }, [
renderLink(h, path + '#' + c.slug, c.title, active, c.level - 1),
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
])
}))
}
function renderExternal (h, to, text) {
return h('a', {
attrs: {
href: to,
target: '_blank',
rel: 'noopener noreferrer'
},
class: {
'sidebar-link': true
}
}, [text, h('OutboundLink')])
}
</script>
<style lang="stylus">
.sidebar .sidebar-sub-headers
padding-left 1rem
font-size 0.95em
a.sidebar-link
font-size 1em
font-weight 400
display inline-block
color $textColor
border-left 0.25rem solid transparent
padding 0.35rem 1rem 0.35rem 1.25rem
line-height 1.4
width: 100%
box-sizing: border-box
&:hover
color $accentColor
&.active
font-weight 600
color $accentColor
border-left-color $accentColor
.sidebar-group &
padding-left 2rem
.sidebar-sub-headers &
padding-top 0.25rem
padding-bottom 0.25rem
border-left none
&.active
font-weight 500
</style>
\ No newline at end of file
<template>
<ul
v-if="items.length"
class="sidebar-links"
>
<li
v-for="(item, i) in items"
:key="i"
>
<SidebarGroup
v-if="item.type === 'group'"
:item="item"
:open="i === openGroupIndex"
:collapsable="item.collapsable || item.collapsible"
:depth="depth"
@toggle="toggleGroup(i)"
/>
<SidebarLink
v-else
:sidebar-depth="sidebarDepth"
:item="item"
/>
</li>
</ul>
</template>
<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import { isActive, debounce } from '../util'
export default {
name: 'SidebarLinks',
components: { SidebarGroup, SidebarLink },
props: [
'items',
'depth', // depth of current sidebar links
'sidebarDepth', // depth of headers to be extracted
'initialOpenGroupIndex'
],
data () {
return {
openGroupIndex: this.initialOpenGroupIndex || 0
}
},
watch: {
'$route': debounce(function () {
this.refreshIndex()
})
},
created () {
this.refreshIndex()
},
methods: {
refreshIndex () {
const index = resolveOpenGroupIndex(
this.$route,
this.items
)
if (index > -1) {
this.openGroupIndex = index
}
},
toggleGroup (index) {
this.openGroupIndex = index === this.openGroupIndex ? -1 : index
},
isActive (page) {
return isActive(this.$route, page.regularPath)
}
}
}
function resolveOpenGroupIndex (route, items) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (descendantIsActive(route, item)) {
return i
}
}
return -1
}
function descendantIsActive (route, item) {
if (item.type === 'group') {
const childIsActive = item.path && isActive(route, item.path)
const grandChildIsActive = item.children.some(child => {
if (child.type === 'group') {
return descendantIsActive(route, child)
} else {
return child.type === 'page' && isActive(route, child.path)
}
})
return childIsActive || grandChildIsActive
}
return false
}
</script>
<template>
<div class="contact-box">
<template v-if="currentConfig.contactItems && currentConfig.contactItems.length">
<template v-for="item in currentConfig.contactItems">
<a :key="item.name" :href="item.url" target="_blank" class="contact-item">
<img :src="item.imageUrl" width="20" height="20" />
<div class="contact-smg">
<div>{{ item.name }}</div>
</div>
</a>
</template>
</template>
<div class="contact-item" v-if="currentConfig.codeHosting && currentConfig.codeHosting.length">
<img
src="https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/74cda950-4f2d-11eb-a16f-5b3e54966275.png"
width="20"
height="20"
/>
<div class="contact-smg">
<div>
代码仓库:
<template v-for="(item, index) in currentConfig.codeHosting">
<a :key="item.url" :href="item.url" target="_blank">{{ item.name }}</a>
{{ currentConfig.codeHosting.length - index > 1 ? '' : '' }}
</template>
</div>
</div>
</div>
<div class="contact-item" v-if="currentQQGroup.length">
<img
src="https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/759713d0-4f2d-11eb-a16f-5b3e54966275.png"
width="20"
height="20"
/>
<div class="contact-smg">
<div>官方QQ交流群</div>
<template v-for="item in currentQQGroup">
<div v-if="!item.state" :key="item.number">
{{ item.prefix }}{{ item.number }} &nbsp;
<a v-if="QQOfUnicloud"
target="_blank"
style="text-decoration: underline"
href="https://qm.qq.com/cgi-bin/qm/qr?k=ez84UYIZgPKpTvrXD8GINUr62u8CF96x&jump_from=webapi"
>
点此加入
</a>
<a v-else
target="_blank"
style="text-decoration: underline"
href="https://qm.qq.com/cgi-bin/qm/qr?k=6t6SpBI2961dTlf9BNf20AkaUgxuNaOO&jump_from=webapi"
>
点此加入
</a>
</div>
<div v-else :key="item.number">
{{ item.prefix }}{{ item.number }}{{ item.attendance || 2000 }}人已满)
</div>
</template>
</div>
</div>
<div class="contact-item">
<img
src="https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/77df7d30-4f2d-11eb-bd01-97bc1429a9ff.png"
width="20"
height="20"
/>
<div class="contact-smg">
<div>关注微信公众号</div>
<img
src="https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/78a8e7b0-4f2d-11eb-8ff1-d5dcf8779628.jpg"
width="90"
height="90"
/>
</div>
</div>
</div>
</template>
<script>
import navInject from '../mixin/navInject';
import siderbarConfig from '../config/siderbar';
export default {
mixins: [navInject],
data: () => ({
siderbarConfig,
}),
computed: {
currentConfig() {
return this.siderbarConfig[this.customNavBarKeys[this.navConfig.userNavIndex]] || {};
},
currentQQGroup() {
return [...(this.currentConfig.qq_group || [])].reverse();
},
QQOfUnicloud(){
return this.mainNavBarText == 'uniCloud'? true : false
}
},
};
</script>
<style>
.contact-box {
border-top: 1px solid #eee;
margin-top: 20px;
margin-bottom: 20px;
padding: 0 10px;
}
.contact-box a {
color: #42b983;
}
.contact-item {
padding-top: 30px;
padding-left: 0;
display: flex;
flex-direction: row;
}
a.contact-item {
display: flex;
padding: 0;
margin-top: 20px;
padding-left: 0;
text-decoration: none;
}
.contact-item > img {
margin: 3px 10px 0 10px;
}
.contact-smg {
display: flex;
flex-direction: column;
}
.contact-smg div {
font-size: 15px;
color: #000000;
line-height: 24px;
}
</style>
<template>
<component
:is="tag || 'div'"
class="sticker"
:class="needFloat ? ['stick-float'] : undefined"
:style="needFloat ? { bottom: `${stickBottom}px` } : undefined"
>
<slot></slot>
</component>
</template>
<script>
import { findContainerInVm } from '../util';
export default {
props: ['stick', 'tag'],
data() {
return {
needFloat: false,
stickBottom: 0,
};
},
watch: {
stick() {
this.unStick();
this.stickHandle();
},
},
mounted() {
this.stickHandle();
},
beforeDestroy() {
this.unStick();
},
methods: {
stickHandle() {
if (!this.stick) return;
const stickElement = findContainerInVm(this.stick, this);
if (!stickElement) return;
this._stickerScroll = () => {
const rect = this.$el.getBoundingClientRect();
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop;
this.needFloat =
document.body.offsetHeight - scrollTop - rect.height < stickElement.offsetHeight;
this.stickBottom = stickElement.offsetHeight;
};
this._stickerScroll();
window.addEventListener('scroll', this._stickerScroll);
},
unStick() {
this.needFloat = false;
this.stickBottom = 0;
window.removeEventListener('scroll', this._stickerScroll);
},
},
};
</script>
<style lang="stylus">
.sticker
position fixed
&.stick-float
top auto
position absolute
</style>
<template>
<div class="table-of-contents">
<div
v-for="item in $page.headers"
ref="chairTocItem"
:key="item.slug"
class="vuepress-toc-item-top"
:class="[`vuepress-toc-h${item.level}`]"
>
<a :href="`#${item.slug}`" :title="item.title">{{ item.title }}</a>
</div>
</div>
</template>
<style lang="stylus">
.table-of-contents
margin 0 auto
padding 2rem 2.5rem 0 2.5rem
@media (max-width: $MQNarrow)
padding 2rem 2rem 0 2rem
@media (max-width: $MQMobileNarrow)
padding 1.5rem 1.5rem 0 1.5rem
border-left: 2px solid #e6e6e6
.vuepress-toc-item-top
position relative
padding 0.1rem 0.6rem 0.1rem 1.5rem
line-height 1.5rem
border-left 2px solid rgba(0, 0, 0, 0.08)
overflow hidden
a
display block
// color $textColor
width 100%
box-sizing border-box
font-size 16px
font-weight 400
text-decoration none
transition color 0.3s
overflow hidden
text-overflow ellipsis
white-space nowrap
for i in range(2, 6)
.vuepress-toc-h{i} a
padding-left 1rem * (i - 1)
</style>
<template>
<Sticker
ref="sticker"
:class="['vuepress-toc', visible ? '' : 'table-of-contents-sticker']"
v-bind="$attrs"
>
<h5>文档结构</h5>
<div
v-for="(item, index) in $page.headers"
ref="chairTocItem"
:key="item.slug"
class="vuepress-toc-item"
:class="[`vuepress-toc-h${item.level}`, { active: activeIndex === index }]"
>
<a :href="`#${item.slug}`" :title="item.title">{{ item.title }}</a>
</div>
</Sticker>
</template>
<script>
import Sticker from './Sticker.vue';
import toc from '../mixin/toc';
let initTop;
// get offset top
function getAbsoluteTop(dom) {
return dom && dom.getBoundingClientRect
? dom.getBoundingClientRect().top +
document.body.scrollTop +
document.documentElement.scrollTop
: 0;
}
export default {
mixins: [toc],
components: {
Sticker,
},
data() {
return {
activeIndex: 0
};
},
watch: {
activeIndex() {
const items = this.$refs.chairTocItem || [];
const dom = items[this.activeIndex];
if (!dom) return;
const rect = dom.getBoundingClientRect();
const wrapperRect = this.$el.getBoundingClientRect();
const top = rect.top - wrapperRect.top;
if (top < 20) {
this.$el.scrollTop = this.$el.scrollTop + top - 20;
} else if (top + rect.height > wrapperRect.height) {
this.$el.scrollTop += rect.top - (wrapperRect.height - rect.height);
}
},
$route() {},
},
mounted() {
// sync visible to parent component
const syncVisible = () => {
this.$emit('visible-change', this.visible);
};
syncVisible();
this.$watch('visible', syncVisible);
// binding event
setTimeout(() => this._onScroll(), 1000);
this._onScroll = () => this.onScroll();
this._onHashChange = () => {
const hash = decodeURIComponent(location.hash.substring(1));
const index = (this.$page.headers || []).findIndex(h => h.slug === hash);
if (index >= 0) this.activeIndex = index;
const dom = hash && document.getElementById(hash);
if (dom) window.scrollTo(0, getAbsoluteTop(dom) - 20);
};
window.addEventListener('scroll', this._onScroll);
// window.addEventListener('hashchange', this._onHashChange);
const sideBar = document.querySelector('.sidebar');
this.$refs.sticker.$el.style.top = sideBar && sideBar.style && sideBar.style.top;
},
beforeDestroy() {
window.removeEventListener('scroll', this._onScroll);
window.removeEventListener('hashchange', this._onHashChange);
},
methods: {
onScroll() {
if (initTop === undefined) {
initTop = getAbsoluteTop(this.$el);
}
// update position
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop;
const headings = this.$page.headers || [];
// change active toc with scrolling
let i = 0;
const addLink = index => {
this.activeIndex = index;
};
for (; i < headings.length; i++) {
const dom = document.getElementById(headings[i].slug);
const top = getAbsoluteTop(dom);
if (top - 50 < scrollTop) {
addLink(i);
} else {
if (!i) addLink(i);
break;
}
}
},
triggerEvt() {
this._onScroll();
this._onHashChange();
},
},
};
</script>
<style lang="stylus">
.table-of-contents-sticker
display none !important
.vuepress-toc
position fixed
display none
max-height 89vh
width $vuepress-toc-width
overflow-y auto
// margin-top $navbarHeight
top $navbarHeight
right 0
box-sizing border-box
background-color #fff
/* background: #fff; */
z-index 0
.vuepress-toc-item
position relative
padding 0.1rem 0.6rem 0.1rem 1.5rem
line-height 1.5rem
border-left 2px solid rgba(0, 0, 0, 0.08)
overflow hidden
a
display block
color $textColor
width 100%
box-sizing border-box
font-size 14px
font-weight 400
text-decoration none
transition color 0.3s
overflow hidden
text-overflow ellipsis
white-space nowrap
&.active
border-left-color $accentColor
a
color $accentColor
&:hover
a
color $accentColor
.vuepress-toc-h1 a
.vuepress-toc-h2 a
font-weight bold
for i in range(2, 6)
.vuepress-toc-h{i} a
padding-left 1rem * (i - 1)
</style>
import getRedirectRouter from './config/redirectRouter';
import VueRouter from 'vue-router'
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, resolve, reject) {
if (resolve || reject) return originalPush.call(this, location, resolve, reject)
return originalPush.call(this, location).catch(err => err)
}
function handleRedirectForCleanUrls(router, to) {
if (isRouteExists(router, to.path)) {
return to.path
} else {
if (!/(\/|\.html)$/.test(to.path)) {
const endingSlashUrl = to.path + '/'
const endingHtmlUrl = to.path + '.html'
if (isRouteExists(router, endingHtmlUrl)) {
return endingHtmlUrl
} else if (isRouteExists(router, endingSlashUrl)) {
return endingSlashUrl
} else {
return to.path.replace(/\.md$/, '')
}
} else if (/\/$/.test(to.path)) {
const endingHtmlUrl = to.path.replace(/\/$/, '') + '.html'
if (isRouteExists(router, endingHtmlUrl)) {
return endingHtmlUrl
} else {
return to.path
}
} else {
return to.path
}
}
}
function isRouteExists(router, path) {
const pathLower = path.toLowerCase()
return router.options.routes.some(route => route.path.toLowerCase() === pathLower)
}
function handlePath(router, to) {
// 重定向路由表
const redirectRouter = getRedirectRouter(to)
if (redirectRouter) return redirectRouter
const id = to.query.id
const hash = decodeURIComponent(id || to.hash).toLowerCase()
const redirectPath = handleRedirectForCleanUrls(router, to)
if (id) {
return {
path: redirectPath,
replace: true,
hash
}
}
if (redirectPath !== to.path) {
return {
path: redirectPath,
replace: true,
hash
}
}
if (/\bREADME\b/.test(to.path)) {
return {
path: to.path.replace(/\bREADME\b/, ''),
replace: true,
hash
}
}
}
export default ({
Vue,
options,
router,
siteData
}) => {
let mounted = false
const ScrollBehavior = 'instant'
router.beforeHooks.unshift((to, from, next) => {
next(handlePath(router, to))
})
router.options.scrollBehavior = function scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return window.scrollTo({
top: savedPosition.y,
behavior: ScrollBehavior,
});
}
else if (to.hash) {
if (Vue.$vuepress.$get('disableScrollBehavior')) {
return false;
}
const selector = decodeURIComponent(to.hash.toLowerCase())
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!mounted) mounted = true
const targetElement = document.querySelector(selector);
if (targetElement) {
return window.scrollTo({
top: getElementPosition(targetElement).y,
behavior: ScrollBehavior,
});
}
return resolve(false);
}, mounted ? 0 : 700)
})
}
else {
return window.scrollTo({
top: 0,
behavior: ScrollBehavior,
});
}
}
}
function getElementPosition(el) {
const docEl = document.documentElement;
const docRect = docEl.getBoundingClientRect();
const elRect = el.getBoundingClientRect();
return {
x: elRect.left - docRect.left,
y: elRect.top - docRect.top,
};
}
\ No newline at end of file
<script>
export default {
props:{
src:{
type:String,
default:''
}
},
data(){
return {
activeIndex: 0
}
},
mounted(){
this.onWindowResize()
window.addEventListener('resize', this.onWindowResize)
},
beforeDestroy(){
window.removeEventListener('resize', this.onWindowResize)
},
methods:{
onWindowResize(){
const contentWidth = getComputedStyle(document.querySelector('.theme-default-content')).width
if (window.matchMedia('(max-width: 410px)').matches) {
this.$refs.codeIframe.style.maxWidth = contentWidth
}
},
onClick(index){
this.activeIndex = index
},
createdDom(h,node){
let headerDom = []
node.forEach((v,index)=>{
headerDom.push(h('p',{class:`pages-tabs-header-text ${this.activeIndex === index?'pages-tabs--active':''}`,on:{click:(e)=>{
this.onClick(index)
}}},v.title),)
})
return this.renderDom(h,h('div',{class:'page-tabs'},[
h('div',{class:'pages-tabs-header'},headerDom),
h('div',{class:'page-snippet-code',key:this.activeIndex},[node[this.activeIndex].node]),
]))
},
renderDom(h,node){
return h('div',{class:'page-runtime'},
[
h('div',{class:'page-snippet'},[node]),
h('div',{class:'code-content',style:{display:this.src?'block':'none'}},[
h('iframe',{class:'code-iframe',attrs:{
src:this.src,
frameborder:'0'
},ref:'codeIframe'})
]),
]
)
}
},
render(h){
const columns = this.$slots.default || []
let boxObj = []
let realDom = []
columns.forEach((v,i)=>{
if(v.tag && v.children){
realDom.push(v)
}
})
realDom.forEach((vnode,index)=>{
let code = vnode.children[0]
if(vnode.tag === 'div' && code.tag === 'pre'){
let i = index - 1
if(i >= 0){
let textDom = realDom[i]
if(textDom.tag === 'blockquote'){
let text = textDom.children[0]
let p = text.children[0]
boxObj.push({
title:p.text,
node:vnode
})
}
}
}
})
if(boxObj.length > 0){
return h('div',null,[this.createdDom(h,boxObj)])
}else{
if(this.src){
return this.renderDom(h,this.$slots.default)
}else{
return h('div',null,this.$slots.default)
}
}
}
}
</script>
<style lang="stylus" scoped>
.page-runtime {
display: flex;
height: 667px;
border: 1px #eee solid;
margin-top: 16px;
}
.page-snippet {
width: 100%;
overflow: hidden;
}
.page-snippet-code {
height: calc(100% - 50px);
}
.page-tabs {
height: 100%;
box-sizing: border-box;
}
.pages-tabs-header {
display: flex;
height: 50px;
background-color: #222;
}
.pages-tabs-header-text {
display: flex;
flex-direction: column;
justify-content: center;
margin: 5px;
margin-bottom: 0px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 0 45px;
text-align: center;
font-size: 18px;
color: #eee;
background:transparent;
cursor: pointer;
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
}
.pages-tabs--active {
background:#282c34;
color: #fff;
font-weight: bold;
}
.page-snippet div[class*="language-"]{
width: 100%;
height: 100%;
border-radius: 0;
overflow: hidden;
}
.page-snippet pre[class*="language-"]{
margin: 0;
padding: 20px 20px;
width: 100%;
height: 100%;
overflow: auto;
box-sizing: border-box;
}
.code-iframe {
flex-shrink: 0;
width: 375px;
height: 667px;
}
@media (max-width: $MQMobileNarrow)
{$contentClass}
div[class*="language-"]
margin 0 !important
</style>
<!-- fixed by mehaotian -->
<template>
<div class="icons-box">
<div class="icons-item" v-for="(item, index) in iconsList" :key="index" @click="onClipboard(item)">
<span class="icons-tip " :class="{ show: item.tipShow }">复制成功</span>
<span class="uni-icon" :class="['uniui-'+item.text]"></span>
<span class="icons-text">{{ item.text }}</span>
</div>
</div>
</template>
<script>
import uniicons from './icons.js';
import Clipboard from 'clipboard';
export default {
name: 'iconsLayouts',
functional: false,
props: {},
data() {
return {
iconsList: []
};
},
created() {
let icons = uniicons.glyphs
icons.forEach(v=>{
this.iconsList.push({
icon: v.unicode,
text: v.font_class,
tipShow: false
});
})
},
methods: {
onClipboard(data) {
console.log(data);
let index = this.iconsList.findIndex((item)=> item.tipShow)
if(index !== -1){
this.iconsList[index].tipShow = false
}
const _this = this
let clipboard = new Clipboard('.icons-item', {
text: function() {
return data.text;
}
});
clipboard.on('success', e => {
data.tipShow = true
clearTimeout(_this.timer)
_this.timer = setTimeout(()=>{
data.tipShow = false
},1000)
// 释放内存
clipboard.destroy();
});
clipboard.on('error', e => {
clipboard.destroy();
});
}
}
};
</script>
<style lang="stylus" >
@import './uniicons.css'
@font-face
font-family uniicons
font-weight normal
font-style normal
src url('./uniicons.ttf') format('truetype')
.icons-box
display flex
flex-wrap wrap
width 100%
border-left 1px #eaecef solid
border-top 1px #eaecef solid
color #5e6d82
.icons-item
position relative
display flex
flex-direction column
align-items center
padding 25px 10px
width 16.66%
border-right 1px #eaecef solid
border-bottom 1px #eaecef solid
box-sizing border-box
-moz-user-select none /* 火狐 */
-webkit-user-select none /* webkit浏览器 */
-ms-user-select none /* IE10 */
-khtml-user-select none /* 早期浏览器 */
user-select none
.icons-tip
padding 2px 10px
position absolute
top 0
left 0
border-radius 5px
color #1AAD19
opacity 0
transition all 0.3s
font-size 14px
&.show
opacity 1
&:hover
background-color #F5F5F5
cursor pointer
.icons-text
text-align center
.uni-icon
font-family uniicons
font-size 35px
margin-bottom 10px
@media (max-width: $MQNarrow)
width 33.3%
padding 15px 0
</style>
module.exports = {
extend: '@vuepress/theme-default',
plugins: [
['container', {
type: 'preview',
validate: (params) =>{
// return params.trim().match(/^preview\s+(.*)$/);
return params.trim().match(/^preview/);
},
render: (tokens, idx,otps,event)=> {
var m = tokens[idx].info.trim().match(/^preview\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return `<CodeSimulator class="code" src="${m && m.length > 0?m[1]:''}">`;
} else {
// closing tag
return `</CodeSimulator>`;
}
}
}],
'@vuepress/back-to-top'
]
}
<template>
<div class="theme-container">
<div class="theme-default-content">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<RouterLink to="/">Take me home.</RouterLink>
</div>
</div>
</template>
<script>
const msgs = [
`There's nothing here.`,
`How did we get here?`,
`That's a Four-Oh-Four.`,
`Looks like we've got some broken links.`,
];
export default {
methods: {
getMsg() {
return msgs[Math.floor(Math.random() * msgs.length)];
},
},
mounted() {
const xhr = new XMLHttpRequest();
xhr.open('post', 'https://b1ecec17-5c3f-4de9-8b5b-ab739a9a47d5.bspapp.com/unidocs-zh-stat');
xhr.setRequestHeader('content-type', 'application/json;charset=utf-8');
xhr.send(
JSON.stringify({
params: {
fullPath: this.$route.fullPath,
hash: this.$route.hash,
path: this.$route.path,
params: this.$route.params,
query: this.$route.query,
},
})
);
},
};
</script>
此差异已折叠。
export default {
inject: ['navConfig', 'customNavBar', 'changeUserNav', 'customNavBarKeys'],
computed: {
showSubNavBar() {
return !!this.customNavBar[this.navConfig.userNavIndex].items
},
mainNavBarText() {
return this.customNavBar[this.navConfig.userNavIndex].text
},
subNavBarText() {
const curNavBar = this.customNavBar[this.navConfig.userNavIndex]
const curLink = (this.$page.path.match(/\/(\w+)+\/*/) || [])[1]
const item = curNavBar.items ? curNavBar.items.filter(
item => item.type === 'link' && item.link.indexOf(curLink) !== -1
)[0] : curNavBar
return item ? item.text : curNavBar.items[0].text
}
}
}
\ No newline at end of file
import { navbar } from '../config/navbar';
export default {
data() {
return { navConfig: { userNavIndex: 0 } }
},
provide() {
return {
navConfig: this.navConfig,
customNavBar: this.customNavBar,
changeUserNav: this.changeUserNav,
customNavBarKeys: this.customNavBarKeys
}
},
created() {
this.customNavBar.forEach((item, index) => {
item.text === this.$page.path.split('/')[1] && (this.navConfig.userNavIndex = index)
})
},
computed: {
customNavBar() {
const list = []
navbar.forEach(item => {
if (item.items && item.items.length) {
list.push(item)
}
item.type === 'link' && list.push(item)
})
return list
},
customNavBarKeys() {
return this.customNavBar.map(item => item.text)
}
},
methods: {
changeUserNav(index) {
this.navConfig.userNavIndex = index
const curNavBar = this.customNavBar[index]
const firstItemLink = curNavBar.items ? curNavBar.items[0].link : curNavBar.link
if (this.$page.path !== firstItemLink) this.$router.push(firstItemLink)
}
},
watch: {
$route(after) {
let navbarIndex = this.customNavBarKeys.indexOf((after.fullPath.match(/\/(\w+)+\/*/) || [])[1])
navbarIndex === -1 && (navbarIndex = 0)
this.navConfig.userNavIndex !== navbarIndex && navbarIndex !== -1 && (this.navConfig.userNavIndex = navbarIndex)
}
}
}
\ No newline at end of file
export default {
computed: {
visible() {
return (
this.$frontmatter &&
this.$frontmatter.toc !== false &&
!!(this.$page && this.$page.headers && this.$page.headers.length) &&
this.$page.headers.some(header => header.level > 2)
);
},
}
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
// 布局
$accentColor = #42b983
$contentWidth = 1200px
$navbarHeight = 9rem
$navbar-sub-navbar-height = 5rem
$navbar-background-color = #f7f7f7
$search-container-color = #f5f6f7
$vuepress-toc-width = 14rem
$vuepress-display-min-width = 1500px
\ No newline at end of file
此差异已折叠。
......@@ -30,6 +30,7 @@
"vuepress": "^1.8.2"
},
"dependencies": {
"@docsearch/js": "^3.0.0-alpha.50"
"@docsearch/js": "^3.1.0",
"vuepress-theme-uni-app-test": "^1.0.5"
}
}
\ No newline at end of file
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册