# 定制主题

1.8.5 版本之后,我们对样式进行了重构,更加灵活和精致,主题定制的方式也发生了变化。可以通过低代码开发IDE,直接配置主题,无需手动修改样式变量。以下是主题定制的详细步骤。

# 调整依赖包

在 ui/package.json 中增加依赖包:

"@fly-vue/style": "1.8.5-2"  // 根据实际情况调整版本号,参考版本更新日志

同时,修改 ui/src/styles/default/index.less 文件,调整引入主题路径:

@import '~@fly-vue/style/style/index.less';

# 调整配置文件

修改 ui/src/setting.js 文件,增加配置:

const Setting = {
  ...
  menuSideCollapseWidth: 60, // 侧边栏折叠宽度
  ...
}

# 调整布局文件

修改 ui/src/layouts/basic-layout/index.vue 文件:

index.vue
<template>
  <Layout class="i-layout">
    <Sider
      v-show="!isMobile && !hideSider && !disabledSiderMenu"
      class="i-layout-sider"
      :class="siderClasses"
      :width="menuSideWidth"
    >
      <i-menu-side :hide-logo="isHeaderStick && headerFix && showHeader" >
        <template slot="pre">
          <h4 v-if="!menuCollapse && currentHeader" class="i-layout-sider-title text-elip">
            {{ currentHeader.title }}
          </h4>
        </template>
        <template slot="suffix">
          <i-header-collapse
              v-if="!isMobile && showSiderCollapse && !hideSider && !disabledSiderMenu"
              class="i-layout-sider-trigger"
              :class="menuCollapse ? 'i-layout-sider-trigger-collapse' : ''"
              @on-toggle-drawer="handleToggleDrawer"
          />
        </template>
      </i-menu-side>
    </Sider>
    <Layout class="i-layout-inside" :class="insideClasses">
      <transition name="fade-quick">
        <Header
          v-show="showHeader"
          v-resize="handleHeaderWidthChange"
          class="i-layout-header"
          :class="headerClasses"
          :style="headerStyle"
        >
          <i-header-logo v-if="isMobile && showMobileLogo" />
          <i-header-logo v-if="!isMobile && isHeaderStick && headerFix" :logo="logoInfo" />
          <i-menu-head v-if="headerMenu && !isMobile" ref="menuHead" />
          <i-header-search v-if="showSearch && !headerMenu && !isMobile && !showBreadcrumb" />
          <div class="i-layout-header-right">

            <i-header-mode v-if="!isMobile && showModeSwitch" />
            <i-header-ide v-if="!isMobile && showIDEOnline" />
            <i-header-search v-if="(showSearch && isMobile) || (showSearch && (headerMenu || showBreadcrumb))" />
            <i-header-reload v-if="!isMobile && showReload" class="mr-4" @on-reload="handleReload" />
            <i-menu-head v-if="headerMenu && isMobile" />
            <i-header-log v-if="isDesktop && showLog" />
            <i-header-fullscreen v-if="isDesktop && showFullscreen" />
            <i-header-notice v-if="showNotice" />
            <!-- 主题配置 -->
            <iHeaderTheme v-if="!isMobile && showThemeChange" ></iHeaderTheme>
            <i-header-user />
            <i-header-i18n v-if="showI18n" />
            <i-header-setting v-if="enableSetting && !isMobile" />
          </div>
        </Header>
      </transition>
      <Content class="i-layout-content" :class="contentClasses">
        <transition name="fade-quick">
          <i-tabs v-if="tabs" v-show="showHeader" @on-reload="handleReload" />
        </transition>
        <div class="i-layout-content-main">
          <i-header-breadcrumb class="mb-8" v-if="showBreadcrumb && !isMobile" ref="breadcrumb" />
          <!-- <bg-keep-alive :include="keepAlive"> -->
          <router-view v-if="loadRouter" />
          <!-- </bg-keep-alive> -->
        </div>
      </Content>
      <i-copyright v-if="showCopyRight" />
    </Layout>
    <div v-if="isMobile && !hideSider">
      <Drawer v-model="showDrawer" placement="left" :closable="false" :class-name="drawerClasses">
        <i-menu-side />
      </Drawer>
    </div>
  </Layout>
</template>
<script>
  import { mapState, mapGetters, mapMutations } from 'vuex'
  import { config } from '@fly-vue/core'
  import iHeaderNotice from './header-notice'
  import iHeaderSearch from './header-search'
  import iHeaderUser from './header-user'
  import iHeaderMode from './header-dev/mode.vue'
  import iHeaderIde from './header-dev/ide.vue'
  import { requestAnimation } from '@fly-vue/iview-admin'

  const Setting = config()

  function joinPath(path, base) {
    return path.startsWith('http') ? path : base + path
  }

  export default {
    name: 'BasicLayout',
    components: {
      iHeaderNotice,
      iHeaderSearch,
      iHeaderUser,
      iHeaderMode,
      iHeaderIde
    },
    data() {
      let logoInfo = {}
      const layout = config().layout || {}
      const resourceBaseUrl = Setting.resourceBaseUrl || ''
      const normal = layout.logo || ''
      const small = layout.logoSmall || ''
      const dark = layout.logoDark || ''
      if (normal) {
        logoInfo.normal = joinPath(normal, resourceBaseUrl)
      }
      if (small) {
        logoInfo.small = joinPath(small, resourceBaseUrl)
      }
      if (dark) {
        logoInfo.dark = joinPath(dark, resourceBaseUrl)
      }
      if (!Object.keys(logoInfo).length) {
        logoInfo = undefined
      }
      return {
        showDrawer: false,
        ticking: false,
        headerVisible: true,
        oldScrollTop: 0,
        isDelayHideSider: false, // hack,当从隐藏侧边栏的 header 切换到正常 header 时,防止 Logo 抖动
        loadRouter: true,
        logoInfo
      }
    },
    computed: {
      ...mapState('admin/layout', [
        'siderTheme',
        'headerTheme',
        'headerStick',
        "showThemeChange",
        'tabs',
        'tabsFix',
        'siderFix',
        'headerFix',
        'headerHide',
        'headerMenu',
        'isMobile',
        'isTablet',
        'isDesktop',
        'menuCollapse',
        'showMobileLogo',
        'showSearch',
        'showNotice',
        'showFullscreen',
        'showSiderCollapse',
        'showBreadcrumb',
        'showLog',
        'showI18n',
        'showReload',
        'enableSetting',
        'showCopyRight',
        'showModeSwitch',
        'showIDEOnline',
        'disabledSiderMenu'
      ]),
      ...mapState('admin/page', ['keepAlive']),
      ...mapGetters('admin/menu', ['hideSider']),
      // 如果开启 headerMenu,且当前 header 的 hideSider 为 true,则将顶部按 headerStick 处理
      // 这时,即使没有开启 headerStick,仍然按开启处理
      isHeaderStick() {
        let state = this.headerStick
        if (this.hideSider) state = true
        return state
      },
      showHeader() {
        //_pst=h 强制隐藏头部
        if (this.headerHide) {
          return false
        }
        //滚动隐藏头部
        if (this.headerFix && !this.headerVisible) {
          return false
        }
        return true
      },
      headerClasses() {
        return [
          `i-layout-header-color-${this.headerTheme}`,
          {
            'i-layout-header-fix': this.headerFix,
            'i-layout-header-fix-collapse': this.headerFix && this.menuCollapse,
            'i-layout-header-mobile': this.isMobile,
            'i-layout-header-stick': this.isHeaderStick && !this.isMobile,
            'i-layout-header-with-menu': this.headerMenu,
            'i-layout-header-with-hide-sider': this.hideSider || this.isDelayHideSider
          }
        ]
      },
      headerStyle() {
        const menuWidth = this.isHeaderStick
          ? 0
          : this.menuCollapse
          ? Setting.menuSideCollapseWidth
          : Setting.menuSideWidth
        return this.isMobile || !this.headerFix
          ? {}
          : {
              width: `calc(100% - ${menuWidth}px)`
            }
      },
      siderClasses() {
        return {
          'i-layout-sider-fix': this.siderFix,
          'i-layout-sider-dark': this.siderTheme === 'dark'
        }
      },
      contentClasses() {
        return {
          'i-layout-content-fix-with-header': this.headerFix,
          'i-layout-content-with-tabs': this.tabs,
          'i-layout-content-with-tabs-fix': this.tabs && this.tabsFix
        }
      },
      insideClasses() {
        return {
          'i-layout-inside-fix-with-sider': this.siderFix && !this.disabledSiderMenu,
          'i-layout-inside-fix-with-sider-collapse': this.siderFix && this.menuCollapse,
          'i-layout-inside-with-hide-sider': this.hideSider,
          'i-layout-inside-mobile': this.isMobile
        }
      },
      drawerClasses() {
        let className = 'i-layout-drawer'
        if (this.siderTheme === 'dark') className += ' i-layout-drawer-dark'
        return className
      },
      menuSideWidth() {
        return this.menuCollapse ? Setting.menuSideCollapseWidth : Setting.menuSideWidth
      }
    },
    watch: {
      hideSider() {
        this.isDelayHideSider = true
        setTimeout(() => {
          this.isDelayHideSider = false
        }, 0)
      },
      $route(to, from) {
        if (to.name === from.name) {
          // 相同路由,不同参数,跳转时,重载页面
          if (Setting.sameRouteForceUpdate) {
            this.handleReload()
          }
        }
      }
    },
    methods: {
      ...mapMutations('admin/layout', ['updateMenuCollapse']),
      ...mapMutations('admin/page', ['keepAlivePush', 'keepAliveRemove']),
      handleToggleDrawer(state) {
        if (typeof state === 'boolean') {
          this.showDrawer = state
        } else {
          this.showDrawer = !this.showDrawer
        }
      },
      handleScroll() {
        if (!this.headerHide) return

        const scrollTop = document.body.scrollTop + document.documentElement.scrollTop

        if (!this.ticking) {
          this.ticking = true
          requestAnimation(() => {
            if (this.oldScrollTop > scrollTop) {
              this.headerVisible = true
            } else if (scrollTop > 300 && this.headerVisible) {
              this.headerVisible = false
            } else if (scrollTop < 300 && !this.headerVisible) {
              this.headerVisible = true
            }
            this.oldScrollTop = scrollTop
            this.ticking = false
          })
        }
      },
      handleHeaderWidthChange() {
        const $breadcrumb = this.$refs.breadcrumb
        if ($breadcrumb) {
          $breadcrumb.handleGetWidth()
          $breadcrumb.handleCheckWidth()
        }
        const $menuHead = this.$refs.menuHead
        if ($menuHead) {
          // todo $menuHead.handleGetMenuHeight();
        }
      },
      handleReload() {
        // 针对缓存的页面也生效
        const isCurrentPageCache = this.keepAlive.indexOf(this.$route.name) > -1
        const pageName = this.$route.name
        if (isCurrentPageCache) {
          this.keepAliveRemove(pageName)
        }
        this.loadRouter = false
        this.$nextTick(() => {
          this.loadRouter = true
          if (isCurrentPageCache) {
            this.keepAlivePush(pageName)
          }
        })
      }
    },
    mounted() {
      document.addEventListener('scroll', this.handleScroll, { passive: true })
    },
    beforeDestroy() {
      document.removeEventListener('scroll', this.handleScroll)
    },
    created() {
      if (this.isTablet && this.showSiderCollapse) this.updateMenuCollapse(true)
    }
  }
</script>

# IDE配置主题

打开低代码开发IDE,进入主题配置页面,可以直接调整主题变量,实时预览效果。例如切换成 党政红

主题配置

设置为当前主题后,刷新页面即可看到效果。

主题配置

顶部