diff --git a/src/assets/iconfont/iconfont.css b/src/assets/iconfont/iconfont.css index 09ee98714..efdec40b4 100644 --- a/src/assets/iconfont/iconfont.css +++ b/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 2208059 */ - src: url("iconfont.woff2?t=1636197082361") format("woff2"), - url("iconfont.woff?t=1636197082361") format("woff"), - url("iconfont.ttf?t=1636197082361") format("truetype"); + src: url("iconfont.woff2?t=1638023560828") format("woff2"), + url("iconfont.woff?t=1638023560828") format("woff"), + url("iconfont.ttf?t=1638023560828") format("truetype"); } .iconfont { @@ -13,6 +13,10 @@ -moz-osx-font-smoothing: grayscale; } +.team-icontabs::before { + content: "\e63e"; +} + .team-iconlogo::before { content: "\e620"; } diff --git a/src/assets/iconfont/iconfont.js b/src/assets/iconfont/iconfont.js index e180203e7..994bf8f35 100644 --- a/src/assets/iconfont/iconfont.js +++ b/src/assets/iconfont/iconfont.js @@ -2,17 +2,17 @@ var l, t, e, + h, i, - a, - n = - '', - h = (h = document.getElementsByTagName("script"))[ - h.length - 1 + a = + '', + n = (n = document.getElementsByTagName("script"))[ + n.length - 1 ].getAttribute("data-injectcss"), o = function (c, l) { l.parentNode.insertBefore(c, l); }; - if (h && !c.__iconfont__svg__cssinject__) { + if (n && !c.__iconfont__svg__cssinject__) { c.__iconfont__svg__cssinject__ = !0; try { document.write( @@ -22,21 +22,21 @@ console && console.log(c); } } - function d() { - a || ((a = !0), e()); - } function v() { + i || ((i = !0), e()); + } + function d() { try { - i.documentElement.doScroll("left"); + h.documentElement.doScroll("left"); } catch (c) { - return void setTimeout(v, 50); + return void setTimeout(d, 50); } - d(); + v(); } (l = function () { var c, l; - ((l = document.createElement("div")).innerHTML = n), - (n = null), + ((l = document.createElement("div")).innerHTML = a), + (a = null), (c = l.getElementsByTagName("svg")[0]) && (c.setAttribute("aria-hidden", "true"), (c.style.position = "absolute"), @@ -55,10 +55,10 @@ document.addEventListener("DOMContentLoaded", t, !1)) : document.attachEvent && ((e = l), - (i = c.document), - (a = !1), - v(), - (i.onreadystatechange = function () { - "complete" == i.readyState && ((i.onreadystatechange = null), d()); + (h = c.document), + (i = !1), + d(), + (h.onreadystatechange = function () { + "complete" == h.readyState && ((h.onreadystatechange = null), v()); })); })(window); diff --git a/src/assets/iconfont/iconfont.json b/src/assets/iconfont/iconfont.json index 58756c7f9..8c8b9b4c8 100644 --- a/src/assets/iconfont/iconfont.json +++ b/src/assets/iconfont/iconfont.json @@ -5,6 +5,13 @@ "css_prefix_text": "team-icon", "description": "pure-admin", "glyphs": [ + { + "icon_id": "20594647", + "name": "标签页", + "font_class": "tabs", + "unicode": "e63e", + "unicode_decimal": 58942 + }, { "icon_id": "22129506", "name": "水能", diff --git a/src/assets/iconfont/iconfont.ttf b/src/assets/iconfont/iconfont.ttf index 9950e0858..24eb3de3e 100644 Binary files a/src/assets/iconfont/iconfont.ttf and b/src/assets/iconfont/iconfont.ttf differ diff --git a/src/assets/iconfont/iconfont.woff b/src/assets/iconfont/iconfont.woff index 7e29e735e..2944d5134 100644 Binary files a/src/assets/iconfont/iconfont.woff and b/src/assets/iconfont/iconfont.woff differ diff --git a/src/assets/iconfont/iconfont.woff2 b/src/assets/iconfont/iconfont.woff2 index 29ba680e3..5b037303d 100644 Binary files a/src/assets/iconfont/iconfont.woff2 and b/src/assets/iconfont/iconfont.woff2 differ diff --git a/src/components/ReIcon/index.ts b/src/components/ReIcon/index.ts index 41bf5192d..0d2b77091 100644 --- a/src/components/ReIcon/index.ts +++ b/src/components/ReIcon/index.ts @@ -3,6 +3,66 @@ import icon from "./src/Icon.vue"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { iconComponents } from "/@/plugins/element-plus"; +/** + * find icon component + * @param icon icon图标 + * @returns component + */ +export function findIconReg(icon: string) { + // fontawesome + const faReg = /^FA-/; + // iconfont + const iFReg = /^IF-/; + // typeof icon === "function" 属于SVG + if (faReg.test(icon)) { + const text = icon.split(faReg)[1]; + return findIcon( + text.slice(0, text.indexOf(" ")), + "FA", + text.slice(text.indexOf(" ") + 1, text.length) + ); + } else if (iFReg.test(icon)) { + return findIcon(icon.split(iFReg)[1], "IF"); + } else if (typeof icon === "function") { + return findIcon(icon, "SVG"); + } else { + return findIcon(icon, "EL"); + } +} + +// 支持fontawesome、iconfont、element-plus/icons、自定义svg +export function findIcon(icon: String, type = "EL", property?: string) { + if (type === "FA") { + return defineComponent({ + name: "FaIcon", + setup() { + return { icon, property }; + }, + components: { FontAwesomeIcon }, + template: `` + }); + } else if (type === "IF") { + return defineComponent({ + name: "IfIcon", + data() { + return { icon: `iconfont ${icon}` }; + }, + template: `` + }); + } else if (type === "EL") { + const components = iconComponents.filter( + component => component.name === icon + ); + if (components.length > 0) { + return components[0]; + } else { + return null; + } + } else if (type === "SVG") { + return icon; + } +} + export const Icon = Object.assign(icon, { install(app: App) { app.component(icon.name, icon); @@ -12,37 +72,3 @@ export const Icon = Object.assign(icon, { export default { Icon }; -/** - * find icon component - * @param icon icon图标 - * @returns component - */ -export function findIconReg(icon: string) { - const faReg = /^fa-/; - if (faReg.test(icon)) { - return findIcon(icon.split(faReg)[1]); - } else { - return findIcon(icon, false); - } -} -export function findIcon(icon: String, isFa: Boolean = true) { - if (isFa) { - return defineComponent({ - name: "FaIcon", - data() { - return { icon: icon }; - }, - components: { FontAwesomeIcon }, - template: `` - }); - } else { - const components = iconComponents.filter( - component => component.name === icon - ); - if (components.length > 0) { - return components[0]; - } else { - return null; - } - } -} diff --git a/src/plugins/element-plus/index.ts b/src/plugins/element-plus/index.ts index 380943f44..7218d3eb2 100644 --- a/src/plugins/element-plus/index.ts +++ b/src/plugins/element-plus/index.ts @@ -41,6 +41,8 @@ import { ElTabPane, ElAvatar, ElEmpty, + ElCollapse, + ElCollapseItem, // 指令 ElLoading, ElInfiniteScroll @@ -106,7 +108,9 @@ const components = [ ElTabs, ElTabPane, ElAvatar, - ElEmpty + ElEmpty, + ElCollapse, + ElCollapseItem ]; // icon export const iconComponents = [ diff --git a/src/plugins/fontawesome/index.ts b/src/plugins/fontawesome/index.ts index 562534e4f..ae3ebaa8e 100644 --- a/src/plugins/fontawesome/index.ts +++ b/src/plugins/fontawesome/index.ts @@ -6,12 +6,16 @@ import { App } from "vue"; import "font-awesome/css/font-awesome.css"; import { library } from "@fortawesome/fontawesome-svg-core"; -import { faUserSecret } from "@fortawesome/free-solid-svg-icons"; +import { + faUserSecret, + faCoffee, + faSpinner +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; // github.com/Remix-Design/RemixIcon/blob/master/README_CN.md#%E5%AE%89%E8%A3%85%E5%BC%95%E5%85%A5 import "remixicon/fonts/remixicon.css"; export function useFontawesome(app: App) { - library.add(faUserSecret); + library.add(faUserSecret, faCoffee, faSpinner); app.component("font-awesome-icon", FontAwesomeIcon); } diff --git a/src/plugins/i18n/config.ts b/src/plugins/i18n/config.ts index e8edef325..83817a509 100644 --- a/src/plugins/i18n/config.ts +++ b/src/plugins/i18n/config.ts @@ -42,6 +42,7 @@ export const menusConfig = { permission: "权限管理", permissionPage: "页面权限", permissionButton: "按钮权限", + hstabs: "标签页操作", externalLink: "外链" } }, @@ -78,6 +79,7 @@ export const menusConfig = { permission: "Permission Manage", permissionPage: "Page Permission", permissionButton: "Button Permission", + hstabs: "Tabs Operate", externalLink: "External Link" } } diff --git a/src/router/index.ts b/src/router/index.ts index 2244531dd..f70f386c5 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -15,6 +15,7 @@ import { usePermissionStoreHook } from "/@/store/modules/permission"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; // 静态路由 +import tabsRouter from "./modules/tabs"; import homeRouter from "./modules/home"; import Layout from "/@/layout/index.vue"; import errorRouter from "./modules/error"; @@ -32,6 +33,7 @@ import { transformI18n } from "../utils/i18n"; const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue"); const constantRoutes: Array = [ + tabsRouter, homeRouter, flowChartRouter, editorRouter, diff --git a/src/router/modules/tabs.ts b/src/router/modules/tabs.ts new file mode 100644 index 000000000..9d9378699 --- /dev/null +++ b/src/router/modules/tabs.ts @@ -0,0 +1,41 @@ +import Layout from "/@/layout/index.vue"; + +const tabsRouter = { + path: "/tabs", + name: "reTabs", + component: Layout, + redirect: "/tags/index", + meta: { + icon: "IF-team-icontabs", + title: "message.hstabs", + i18n: true, + showLink: true, + rank: 8 + }, + children: [ + { + path: "/tabs/index", + name: "reTabs", + component: () => import("/@/views/tabs/index.vue"), + meta: { + title: "message.hstabs", + showLink: true, + i18n: true + } + }, + { + path: "/tabs/detail/:id", + name: "tabDetail", + component: () => import("/@/views/tabs/tabDetail.vue"), + meta: { + title: "", + showLink: false, + i18n: false, + dynamicLevel: 3, + realPath: "/tabs/detail" + } + } + ] +}; + +export default tabsRouter; diff --git a/src/store/modules/multiTags.ts b/src/store/modules/multiTags.ts index c6ee7d5e6..a78d5eabf 100644 --- a/src/store/modules/multiTags.ts +++ b/src/store/modules/multiTags.ts @@ -4,6 +4,13 @@ import { getConfig } from "/@/config"; import { positionType } from "./types"; import { storageLocal } from "/@/utils/storage"; +interface Itag { + path: string; + parentPath: string; + name: string; + meta: any; +} + export const useMultiTagsStore = defineStore({ id: "pure-multiTags", state: () => ({ @@ -34,14 +41,44 @@ export const useMultiTagsStore = defineStore({ this.getMultiTagsCache && storageLocal.setItem("responsive-tags", multiTags); }, - handleTags(mode: string, value?: T, position?: positionType): any { + handleTags( + mode: string, + value?: T | Itag, + position?: positionType + ): any { switch (mode) { case "equal": this.multiTags = value; break; case "push": - this.multiTags.push(value); - this.tagsCache(this.multiTags); + { + const tagVal = value as Itag; + // 判断tag是否已存在: + const tagHasExits = this.multiTags.some(tag => { + return tag.path === tagVal?.path; + }); + + if (tagHasExits) return; + const meta = tagVal?.meta; + const dynamicLevel = meta?.dynamicLevel ?? -1; + if (dynamicLevel > 0) { + // dynamicLevel动态路由可打开的数量 + const realPath = meta?.realPath ?? ""; + // 获取到已经打开的动态路由数, 判断是否大于dynamicLevel + if ( + this.multiTags.filter(e => e.meta?.realPath ?? "" === realPath) + .length >= dynamicLevel + ) { + // 关闭第一个 + const index = this.multiTags.findIndex( + item => item.meta?.realPath === realPath + ); + index !== -1 && this.multiTags.splice(index, 1); + } + } + this.multiTags.push(value); + this.tagsCache(this.multiTags); + } break; case "splice": this.multiTags.splice(position?.startIndex, position?.length); diff --git a/src/style/sidebar.scss b/src/style/sidebar.scss index 49517012d..e37bbe883 100644 --- a/src/style/sidebar.scss +++ b/src/style/sidebar.scss @@ -444,12 +444,6 @@ } } - & > .el-menu { - i { - margin-right: 16px; - } - } - .is-active > .el-sub-menu__title, .is-active.submenu-title-noDropdown { color: $subMenuActiveText !important; diff --git a/src/views/tabs/index.vue b/src/views/tabs/index.vue new file mode 100644 index 000000000..af57aac0f --- /dev/null +++ b/src/views/tabs/index.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/views/tabs/tabDetail.vue b/src/views/tabs/tabDetail.vue new file mode 100644 index 000000000..79abc9c0f --- /dev/null +++ b/src/views/tabs/tabDetail.vue @@ -0,0 +1,11 @@ + + + + +