search.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <template>
  2. <el-autocomplete
  3. v-model="query"
  4. size="small"
  5. :popper-class="`algolia-search${ isEmpty ? ' is-empty' : '' }`"
  6. :fetch-suggestions="querySearch"
  7. :placeholder="placeholder"
  8. :trigger-on-focus="false"
  9. @select="handleSelect"
  10. highlight-first-item>
  11. <template slot-scope="props">
  12. <p class="algolia-search-title" v-if="props.item.title">
  13. <span v-html="props.item.highlightedCompo"></span>
  14. <span class="algolia-search-separator"></span>
  15. <span v-html="props.item.title"></span>
  16. </p>
  17. <p
  18. class="algolia-search-content"
  19. v-if="props.item.content"
  20. v-html="props.item.content"></p>
  21. <a
  22. class="algolia-search-link"
  23. v-if="props.item.img"
  24. target="_blank"
  25. href="https://www.algolia.com/docsearch">
  26. <img
  27. class="algolia-search-logo"
  28. src="../assets/images/search-by-algolia.svg"
  29. alt="algolia-logo">
  30. </a>
  31. <p
  32. class="algolia-search-empty"
  33. v-if="props.item.isEmpty">{{ emptyText }}</p>
  34. </template>
  35. </el-autocomplete>
  36. </template>
  37. <style lang="scss">
  38. .algolia-search {
  39. width: 450px !important;
  40. &.is-empty {
  41. .el-autocomplete-suggestion__list {
  42. padding-bottom: 0;
  43. }
  44. }
  45. .el-autocomplete-suggestion__list {
  46. position: static !important;
  47. padding-bottom: 28px;
  48. }
  49. li {
  50. border-bottom: solid 1px #ebebeb;
  51. &:last-child {
  52. border-bottom: none;
  53. }
  54. }
  55. .algolia-highlight {
  56. color: #409EFF;
  57. font-weight: bold;
  58. }
  59. .algolia-search-title {
  60. font-size: 14px;
  61. margin: 6px 0;
  62. line-height: 1.8;
  63. }
  64. .algolia-search-separator {
  65. padding: 0 6px;
  66. }
  67. .algolia-search-content {
  68. font-size: 12px;
  69. margin: 6px 0;
  70. line-height: 2.4;
  71. overflow: hidden;
  72. text-overflow: ellipsis;
  73. white-space: nowrap;
  74. }
  75. .algolia-search-link {
  76. position: absolute;
  77. bottom: 0;
  78. left: 0;
  79. width: 100%;
  80. padding-right: 20px;
  81. background-color: #e4e7ed;
  82. border-bottom-left-radius: 4px;
  83. border-bottom-right-radius: 4px;
  84. box-sizing: border-box;
  85. text-align: right;
  86. &:hover {
  87. background-color: #e4e7ed;
  88. }
  89. img {
  90. display: inline-block;
  91. height: 17px;
  92. margin-top: 10px;
  93. }
  94. }
  95. .algolia-search-empty {
  96. margin: 5px 0;
  97. text-align: center;
  98. color: #999;
  99. }
  100. }
  101. </style>
  102. <script>
  103. import algoliasearch from 'algoliasearch';
  104. export default {
  105. data() {
  106. return {
  107. index: null,
  108. query: '',
  109. isEmpty: false,
  110. langs: {
  111. 'zh-CN': {
  112. search: '搜索文档',
  113. empty: '无匹配结果',
  114. index: 'zh'
  115. },
  116. 'en-US': {
  117. search: 'Search',
  118. empty: 'No results',
  119. index: 'en'
  120. },
  121. 'es': {
  122. search: 'Buscar',
  123. empty: 'No hay datos que coincidan',
  124. index: 'es'
  125. },
  126. 'fr-FR': {
  127. search: 'Rechercher',
  128. empty: 'Aucun résultat',
  129. index: 'fr'
  130. }
  131. }
  132. };
  133. },
  134. computed: {
  135. lang() {
  136. return this.$route.meta.lang;
  137. },
  138. placeholder() {
  139. return this.lang ? this.langs[this.lang].search : '';
  140. },
  141. emptyText() {
  142. return this.lang ? this.langs[this.lang].empty : '';
  143. }
  144. },
  145. watch: {
  146. lang() {
  147. this.initIndex();
  148. }
  149. },
  150. methods: {
  151. initIndex() {
  152. const client = algoliasearch('4C63BTGP6S', '0729c3c7f4dc8db7395ad0b19c0748d2');
  153. this.index = client.initIndex(`element-${ this.lang ? this.langs[this.lang].index : 'zh' }`);
  154. },
  155. querySearch(query, cb) {
  156. if (!query) return;
  157. this.index.search({ query, hitsPerPage: 6 }, (err, res) => {
  158. if (err) {
  159. console.error(err);
  160. return;
  161. }
  162. if (res.hits.length > 0) {
  163. this.isEmpty = false;
  164. cb(res.hits.map(hit => {
  165. let content = hit._highlightResult.content.value.replace(/\s+/g, ' ');
  166. const highlightStart = content.indexOf('<span class="algolia-highlight">');
  167. if (highlightStart > -1) {
  168. const startEllipsis = highlightStart - 15 > 0;
  169. content = (startEllipsis ? '...' : '') +
  170. content.slice(Math.max(0, highlightStart - 15), content.length);
  171. } else if (content.indexOf('|') > -1) {
  172. content = '';
  173. }
  174. return {
  175. anchor: hit.anchor,
  176. component: hit.component,
  177. highlightedCompo: hit._highlightResult.component.value,
  178. title: hit._highlightResult.title.value,
  179. content
  180. };
  181. }).concat({ img: true }));
  182. } else {
  183. this.isEmpty = true;
  184. cb([{ isEmpty: true }]);
  185. }
  186. });
  187. },
  188. handleSelect(val) {
  189. if (val.img || val.isEmpty) return;
  190. const component = val.component || '';
  191. const anchor = val.anchor;
  192. this.$router.push(`/${ this.lang }/component/${ component }${ anchor ? `#${ anchor }` : '' }`);
  193. }
  194. },
  195. mounted() {
  196. this.initIndex();
  197. }
  198. };
  199. </script>