search.vue 5.3 KB

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