forked from liweijie/education
1 line
16 KiB
JSON
1 line
16 KiB
JSON
{"remainingRequest":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\index.js??vue-loader-options!C:\\Users\\Admin\\Desktop\\education\\frontend\\src\\components\\HeaderSearch\\index.vue?vue&type=style&index=0&id=032bd1f0&lang=scss&scoped=true","dependencies":[{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\src\\components\\HeaderSearch\\index.vue","mtime":1787567871502},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\css-loader\\dist\\cjs.js","mtime":1787309792478},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\loaders\\stylePostLoader.js","mtime":1787309794547},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\postcss-loader\\src\\index.js","mtime":1787309793084},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\sass-loader\\dist\\cjs.js","mtime":1787309791955},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\cache-loader\\dist\\cjs.js","mtime":1787309791955},{"path":"C:\\Users\\Admin\\Desktop\\education\\frontend\\node_modules\\vue-loader\\lib\\index.js","mtime":1787309793790}],"contextDependencies":[],"result":[{"type":"Buffer","data":"base64:CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoNCjo6di1kZWVwIHsNCiAgLmVsLWRpYWxvZ19faGVhZGVyIHsNCiAgICBwYWRkaW5nOiA2cHggIWltcG9ydGFudDsNCiAgfQ0KDQogIC5oaWdobGlnaHQgew0KICAgIGNvbG9yOiByZWQ7DQogICAgZm9udC13ZWlnaHQ6IDYwMDsNCiAgfQ0KDQogIC5pcy1hY3RpdmUgLmhpZ2hsaWdodCB7DQogICAgY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45KTsNCiAgICBmb250LXdlaWdodDogNjAwOw0KICB9DQp9DQoNCi5oZWFkZXItc2VhcmNoIHsNCiAgLnNlYXJjaC1pY29uIHsNCiAgICBjdXJzb3I6IHBvaW50ZXI7DQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7DQogIH0NCn0NCg0KLnJlc3VsdC1jb3VudCB7DQogIHBhZGRpbmc6IDZweCAxNnB4IDA7DQogIGZvbnQtc2l6ZTogMTJweDsNCiAgY29sb3I6ICNhYWE7DQoNCiAgc3Ryb25nIHsNCiAgICBjb2xvcjogcmVkOw0KICAgIGZvbnQtd2VpZ2h0OiA2MDA7DQogIH0NCn0NCg0KLnJlc3VsdC13cmFwIHsNCiAgaGVpZ2h0OiAyODBweDsNCiAgbWFyZ2luOiA0cHggMDsNCg0KICAuc2VhcmNoLWl0ZW0gew0KICAgIGRpc3BsYXk6IGZsZXg7DQogICAgaGVpZ2h0OiA0OHB4Ow0KICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAgcGFkZGluZy1yaWdodDogMTBweDsNCiAgICBib3JkZXItcmFkaXVzOiA0cHg7DQogICAgdHJhbnNpdGlvbjogYmFja2dyb3VuZCAwLjE1czsNCg0KICAgIC5sZWZ0IHsNCiAgICAgIHdpZHRoOiA2MHB4Ow0KICAgICAgdGV4dC1hbGlnbjogY2VudGVyOw0KICAgICAgZmxleC1zaHJpbms6IDA7DQoNCiAgICAgIC5tZW51LWljb24gew0KICAgICAgICB3aWR0aDogMThweDsNCiAgICAgICAgaGVpZ2h0OiAxOHB4Ow0KICAgICAgfQ0KICAgIH0NCg0KICAgIC5zZWFyY2gtaW5mbyB7DQogICAgICBwYWRkaW5nLWxlZnQ6IDVweDsNCiAgICAgIG1hcmdpbi10b3A6IDEwcHg7DQogICAgICB3aWR0aDogMTAwJTsNCiAgICAgIGRpc3BsYXk6IGZsZXg7DQogICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOw0KICAgICAganVzdGlmeS1jb250ZW50OiBmbGV4LXN0YXJ0Ow0KICAgICAgZmxleDogMTsNCiAgICAgIG92ZXJmbG93OiBoaWRkZW47DQoNCiAgICAgIC5tZW51LXRpdGxlLA0KICAgICAgLm1lbnUtcGF0aCB7DQogICAgICAgIGhlaWdodDogMjBweDsNCiAgICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDsNCiAgICAgICAgb3ZlcmZsb3c6IGhpZGRlbjsNCiAgICAgICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7DQogICAgICB9DQoNCiAgICAgIC5tZW51LXBhdGggew0KICAgICAgICBjb2xvcjogI2NjYzsNCiAgICAgICAgZm9udC1zaXplOiAxMHB4Ow0KICAgICAgfQ0KICAgIH0NCiAgfQ0KDQogIC5zZWFyY2gtaXRlbTpob3ZlciB7DQogICAgY3Vyc29yOiBwb2ludGVyOw0KICB9DQoNCiAgLmVtcHR5LXN0YXRlIHsNCiAgICBkaXNwbGF5OiBmbGV4Ow0KICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47DQogICAgYWxpZ24taXRlbXM6IGNlbnRlcjsNCiAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsNCiAgICBoZWlnaHQ6IDEwMCU7DQoNCiAgICAuZW1wdHktaWNvbiB7DQogICAgICBmb250LXNpemU6IDQycHg7DQogICAgICBjb2xvcjogI2UwZTBlMDsNCiAgICAgIG1hcmdpbi1ib3R0b206IDE0cHg7DQogICAgfQ0KDQogICAgLmVtcHR5LXRleHQgew0KICAgICAgZm9udC1zaXplOiAxNHB4Ow0KICAgICAgY29sb3I6ICM5OTk7DQogICAgICBtYXJnaW46IDAgMCA2cHg7DQoNCiAgICAgIHN0cm9uZyB7DQogICAgICAgIGNvbG9yOiAjNjY2Ow0KICAgICAgfQ0KICAgIH0NCg0KICAgIC5lbXB0eS10aXAgew0KICAgICAgZm9udC1zaXplOiAxMnB4Ow0KICAgICAgY29sb3I6ICNiYmI7DQogICAgICBtYXJnaW46IDA7DQogICAgfQ0KICB9DQp9DQoNCi5zZWFyY2gtZm9vdGVyIHsNCiAgZGlzcGxheTogZmxleDsNCiAgYWxpZ24taXRlbXM6IGNlbnRlcjsNCiAgZ2FwOiAyOHB4Ow0KICBwYWRkaW5nOiAxMHB4IDIwcHg7DQogIGJvcmRlci10b3A6IDFweCBzb2xpZCAjZjBmMGYwOw0KICBjb2xvcjogIzk5OTsNCiAgZm9udC1zaXplOiAxMnB4Ow0KDQogIC5zaG9ydGN1dC1pdGVtIHsNCiAgICBkaXNwbGF5OiBmbGV4Ow0KICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAgZ2FwOiA1cHg7DQogIH0NCg0KICBrYmQgew0KICAgIGRpc3BsYXk6IGlubGluZS1mbGV4Ow0KICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7DQogICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7DQogICAgbWluLXdpZHRoOiAyMHB4Ow0KICAgIGhlaWdodDogMjBweDsNCiAgICBwYWRkaW5nOiAwIDVweDsNCiAgICBib3JkZXI6IDFweCBzb2xpZCAjZGRkOw0KICAgIGJvcmRlci1yYWRpdXM6IDRweDsNCiAgICBiYWNrZ3JvdW5kOiAjZjdmN2Y3Ow0KICAgIGNvbG9yOiAjNTU1Ow0KICAgIGZvbnQtc2l6ZTogMTFweDsNCiAgICBmb250LWZhbWlseTogaW5oZXJpdDsNCiAgICBsaW5lLWhlaWdodDogMTsNCiAgICBib3gtc2hhZG93OiAwIDFweCAwICNjY2M7DQogIH0NCn0NCg=="},{"version":3,"sources":["index.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmPA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.vue","sourceRoot":"src/components/HeaderSearch","sourcesContent":["<template>\r\n <div class=\"header-search\">\r\n <svg-icon class-name=\"search-icon\" icon-class=\"search\" @click.stop=\"click\" />\r\n <el-dialog\r\n :visible.sync=\"show\"\r\n width=\"600px\"\r\n @close=\"close\"\r\n @opened=\"onDialogOpened\"\r\n :show-close=\"false\"\r\n append-to-body\r\n >\r\n <el-input\r\n v-model=\"search\"\r\n ref=\"headerSearchSelectRef\"\r\n size=\"large\"\r\n @input=\"querySearch\"\r\n prefix-icon=\"el-icon-search\"\r\n placeholder=\"菜单搜索,支持标题、URL模糊查询\"\r\n clearable\r\n @keyup.enter.native=\"selectActiveResult\"\r\n @keydown.up.native=\"navigateResult('up')\"\r\n @keydown.down.native=\"navigateResult('down')\"\r\n >\r\n </el-input>\r\n\r\n <div class=\"result-count\" v-if=\"search && options.length > 0\">\r\n 找到 <strong>{{ options.length }}</strong> 个结果\r\n </div>\r\n\r\n <el-scrollbar wrap-class=\"right-scrollbar-wrapper\">\r\n <div class=\"result-wrap\">\r\n <template v-if=\"options.length > 0\">\r\n <div\r\n class=\"search-item\"\r\n v-for=\"(item, index) in options\"\r\n :key=\"item.path\"\r\n :class=\"{ 'is-active': index === activeIndex }\"\r\n :style=\"activeStyle(index)\"\r\n @mouseenter=\"activeIndex = index\"\r\n @mouseleave=\"activeIndex = -1\"\r\n >\r\n <div class=\"left\">\r\n <svg-icon class=\"menu-icon\" :icon-class=\"item.icon\" />\r\n </div>\r\n <div class=\"search-info\" @click=\"change(item)\">\r\n <div class=\"menu-title\" v-html=\"highlightText(item.title.join(' / '))\"></div>\r\n <div class=\"menu-path\" v-html=\"highlightText(item.path)\"></div>\r\n </div>\r\n <svg-icon icon-class=\"enter\" v-show=\"index === activeIndex\" />\r\n </div>\r\n </template>\r\n\r\n <div class=\"empty-state\" v-else-if=\"search && options.length === 0\">\r\n <i class=\"el-icon-search empty-icon\"></i>\r\n <p class=\"empty-text\">未找到 \"<strong>{{ search }}</strong>\" 相关菜单</p>\r\n <p class=\"empty-tip\">试试其他关键词或路径</p>\r\n </div>\r\n\r\n </div>\r\n </el-scrollbar>\r\n\r\n <div class=\"search-footer\">\r\n <span class=\"shortcut-item\">\r\n <kbd>↑</kbd><kbd>↓</kbd> 切换\r\n </span>\r\n <span class=\"shortcut-item\">\r\n <kbd>↵</kbd> 选择\r\n </span>\r\n <span class=\"shortcut-item\">\r\n <kbd>Esc</kbd> 关闭\r\n </span>\r\n </div>\r\n </el-dialog>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport Fuse from 'fuse.js/dist/fuse.min.js'\r\nimport path from 'path'\r\nimport { isHttp } from '@/utils/validate'\r\n\r\nexport default {\r\n name: 'HeaderSearch',\r\n data() {\r\n return {\r\n search: '',\r\n options: [],\r\n searchPool: [],\r\n activeIndex: -1,\r\n show: false,\r\n fuse: undefined\r\n }\r\n },\r\n computed: {\r\n routes() {\r\n return this.$store.getters.defaultRoutes\r\n }\r\n },\r\n watch: {\r\n routes() {\r\n this.searchPool = this.generateRoutes(this.routes)\r\n },\r\n searchPool(list) {\r\n this.initFuse(list)\r\n }\r\n },\r\n mounted() {\r\n this.searchPool = this.generateRoutes(this.routes)\r\n },\r\n methods: {\r\n click() {\r\n this.show = !this.show\r\n if (this.show) {\r\n this.options = this.searchPool\r\n }\r\n },\r\n onDialogOpened() {\r\n this.$nextTick(() => {\r\n this.$refs.headerSearchSelectRef && this.$refs.headerSearchSelectRef.focus()\r\n })\r\n },\r\n close() {\r\n this.$refs.headerSearchSelectRef && this.$refs.headerSearchSelectRef.blur()\r\n this.search = ''\r\n this.options = this.searchPool\r\n this.show = false\r\n this.activeIndex = -1\r\n },\r\n change(val) {\r\n const p = val.path\r\n const query = val.query\r\n if (isHttp(val.path)) {\r\n // http(s):// 路径新窗口打开\r\n const pindex = p.indexOf('http')\r\n window.open(p.substr(pindex, p.length), '_blank')\r\n } else {\r\n if (query) {\r\n this.$router.push({ path: p, query: JSON.parse(query) })\r\n } else {\r\n this.$router.push(p)\r\n }\r\n }\r\n this.search = ''\r\n this.options = this.searchPool\r\n this.$nextTick(() => {\r\n this.show = false\r\n })\r\n },\r\n initFuse(list) {\r\n this.fuse = new Fuse(list, {\r\n shouldSort: true,\r\n threshold: 0.2,\r\n minMatchCharLength: 1,\r\n keys: [{\r\n name: 'title',\r\n weight: 0.7\r\n }, {\r\n name: 'path',\r\n weight: 0.3\r\n }]\r\n })\r\n },\r\n generateRoutes(routes, basePath = '/', prefixTitle = []) {\r\n let res = []\r\n for (const router of routes) {\r\n if (router.hidden) { continue }\r\n const data = {\r\n path: !isHttp(router.path) ? path.resolve(basePath, router.path) : router.path,\r\n title: [...prefixTitle],\r\n icon: ''\r\n }\r\n if (router.meta && router.meta.title) {\r\n data.title = [...data.title, router.meta.title]\r\n data.icon = router.meta.icon\r\n if (router.redirect !== 'noRedirect') {\r\n res.push(data)\r\n }\r\n }\r\n if (router.query) {\r\n data.query = router.query\r\n }\r\n if (router.children) {\r\n const tempRoutes = this.generateRoutes(router.children, data.path, data.title)\r\n if (tempRoutes.length >= 1) {\r\n res = [...res, ...tempRoutes]\r\n }\r\n }\r\n }\r\n return res\r\n },\r\n querySearch(query) {\r\n this.activeIndex = -1\r\n if (query !== '') {\r\n const q = query.toLowerCase()\r\n const pathMatches = this.searchPool.filter(item =>\r\n item.path.toLowerCase().includes(q)\r\n )\r\n const fuseMatches = this.fuse.search(query).map(item => item.item)\r\n const merged = [...pathMatches]\r\n fuseMatches.forEach(item => {\r\n if (!merged.find(m => m.path === item.path)) {\r\n merged.push(item)\r\n }\r\n })\r\n this.options = merged\r\n } else {\r\n this.options = this.searchPool\r\n }\r\n },\r\n activeStyle(index) {\r\n if (index !== this.activeIndex) return {}\r\n return {\r\n 'background-color': '#00875A',\r\n 'color': '#fff'\r\n }\r\n },\r\n navigateResult(direction) {\r\n if (direction === 'up') {\r\n this.activeIndex = this.activeIndex <= 0 ? this.options.length - 1 : this.activeIndex - 1\r\n } else if (direction === 'down') {\r\n this.activeIndex = this.activeIndex >= this.options.length - 1 ? 0 : this.activeIndex + 1\r\n }\r\n },\r\n selectActiveResult() {\r\n if (this.options.length > 0 && this.activeIndex >= 0) {\r\n this.change(this.options[this.activeIndex])\r\n }\r\n },\r\n highlightText(text) {\r\n if (!text) return ''\r\n if (!this.search) return text\r\n const keyword = this.escapeRegExp(this.search)\r\n const reg = new RegExp(`(${keyword})`, 'gi')\r\n return text.replace(reg, '<span class=\"highlight\">$1</span>')\r\n },\r\n escapeRegExp(str) {\r\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n }\r\n }\r\n}\r\n</script>\r\n\r\n<style lang='scss' scoped>\r\n::v-deep {\r\n .el-dialog__header {\r\n padding: 6px !important;\r\n }\r\n\r\n .highlight {\r\n color: red;\r\n font-weight: 600;\r\n }\r\n\r\n .is-active .highlight {\r\n color: rgba(255, 255, 255, 0.9);\r\n font-weight: 600;\r\n }\r\n}\r\n\r\n.header-search {\r\n .search-icon {\r\n cursor: pointer;\r\n font-size: 18px;\r\n vertical-align: middle;\r\n }\r\n}\r\n\r\n.result-count {\r\n padding: 6px 16px 0;\r\n font-size: 12px;\r\n color: #aaa;\r\n\r\n strong {\r\n color: red;\r\n font-weight: 600;\r\n }\r\n}\r\n\r\n.result-wrap {\r\n height: 280px;\r\n margin: 4px 0;\r\n\r\n .search-item {\r\n display: flex;\r\n height: 48px;\r\n align-items: center;\r\n padding-right: 10px;\r\n border-radius: 4px;\r\n transition: background 0.15s;\r\n\r\n .left {\r\n width: 60px;\r\n text-align: center;\r\n flex-shrink: 0;\r\n\r\n .menu-icon {\r\n width: 18px;\r\n height: 18px;\r\n }\r\n }\r\n\r\n .search-info {\r\n padding-left: 5px;\r\n margin-top: 10px;\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: flex-start;\r\n flex: 1;\r\n overflow: hidden;\r\n\r\n .menu-title,\r\n .menu-path {\r\n height: 20px;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n }\r\n\r\n .menu-path {\r\n color: #ccc;\r\n font-size: 10px;\r\n }\r\n }\r\n }\r\n\r\n .search-item:hover {\r\n cursor: pointer;\r\n }\r\n\r\n .empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n height: 100%;\r\n\r\n .empty-icon {\r\n font-size: 42px;\r\n color: #e0e0e0;\r\n margin-bottom: 14px;\r\n }\r\n\r\n .empty-text {\r\n font-size: 14px;\r\n color: #999;\r\n margin: 0 0 6px;\r\n\r\n strong {\r\n color: #666;\r\n }\r\n }\r\n\r\n .empty-tip {\r\n font-size: 12px;\r\n color: #bbb;\r\n margin: 0;\r\n }\r\n }\r\n}\r\n\r\n.search-footer {\r\n display: flex;\r\n align-items: center;\r\n gap: 28px;\r\n padding: 10px 20px;\r\n border-top: 1px solid #f0f0f0;\r\n color: #999;\r\n font-size: 12px;\r\n\r\n .shortcut-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 5px;\r\n }\r\n\r\n kbd {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n min-width: 20px;\r\n height: 20px;\r\n padding: 0 5px;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n background: #f7f7f7;\r\n color: #555;\r\n font-size: 11px;\r\n font-family: inherit;\r\n line-height: 1;\r\n box-shadow: 0 1px 0 #ccc;\r\n }\r\n}\r\n</style>"]}]} |