mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	perf: 更干净整洁的项目配置右侧弹出面板 (#841)
This commit is contained in:
		
							parent
							
								
									e7d55ff67e
								
							
						
					
					
						commit
						7acdf03f87
					
				@ -1,7 +1,5 @@
 | 
			
		||||
@import "cropperjs/dist/cropper.css";
 | 
			
		||||
@import "tippy.js/dist/tippy.css";
 | 
			
		||||
@import "tippy.js/themes/light.css";
 | 
			
		||||
@import "tippy.js/animations/perspective.css";
 | 
			
		||||
@import "cropperjs/dist/cropper.css";
 | 
			
		||||
 | 
			
		||||
.re-circled {
 | 
			
		||||
  .cropper-view-box,
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import "./circled.css";
 | 
			
		||||
import Cropper from "cropperjs";
 | 
			
		||||
import { useTippy } from "vue-tippy";
 | 
			
		||||
import { ElUpload } from "element-plus";
 | 
			
		||||
import type { CSSProperties } from "vue";
 | 
			
		||||
import { useResizeObserver } from "@vueuse/core";
 | 
			
		||||
import { longpress } from "@/directives/longpress";
 | 
			
		||||
import { useTippy, directive as tippy } from "vue-tippy";
 | 
			
		||||
import { delay, debounce, isArray, downloadByBase64 } from "@pureadmin/utils";
 | 
			
		||||
import {
 | 
			
		||||
  ref,
 | 
			
		||||
@ -233,7 +233,6 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
    const menuContent = defineComponent({
 | 
			
		||||
      directives: {
 | 
			
		||||
        tippy,
 | 
			
		||||
        longpress
 | 
			
		||||
      },
 | 
			
		||||
      setup() {
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ export function useRenderIcon(icon: any, attrs?: iconType): Component {
 | 
			
		||||
    });
 | 
			
		||||
  } else if (typeof icon === "function" || typeof icon?.render === "function") {
 | 
			
		||||
    // svg
 | 
			
		||||
    return icon;
 | 
			
		||||
    return h(icon, { ...attrs });
 | 
			
		||||
  } else if (typeof icon === "object") {
 | 
			
		||||
    return defineComponent({
 | 
			
		||||
      name: "OfflineIcon",
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,8 @@ export interface iconType {
 | 
			
		||||
  align?: string;
 | 
			
		||||
  onLoad?: Function;
 | 
			
		||||
  includes?: Function;
 | 
			
		||||
 | 
			
		||||
  // svg 需要什么SVG属性自行添加
 | 
			
		||||
  fill?: string;
 | 
			
		||||
  // all icon
 | 
			
		||||
  style?: object;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@
 | 
			
		||||
  color: rgba(0, 0, 0, 0.65);
 | 
			
		||||
  background-color: rgb(0 0 0 / 4%);
 | 
			
		||||
  border-radius: 2px;
 | 
			
		||||
  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pure-segmented-group {
 | 
			
		||||
@ -43,7 +42,7 @@
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
 | 
			
		||||
  transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pure-segmented-item > div {
 | 
			
		||||
 | 
			
		||||
@ -121,7 +121,11 @@ export default defineComponent({
 | 
			
		||||
                  class="pure-segmented-item-icon"
 | 
			
		||||
                  style={{ marginRight: option.label ? "6px" : 0 }}
 | 
			
		||||
                >
 | 
			
		||||
                  {h(useRenderIcon(option.icon))}
 | 
			
		||||
                  {h(
 | 
			
		||||
                    useRenderIcon(option.icon, {
 | 
			
		||||
                      ...option?.iconAttrs
 | 
			
		||||
                    })
 | 
			
		||||
                  )}
 | 
			
		||||
                </span>
 | 
			
		||||
              ) : null}
 | 
			
		||||
              {option.label ? (
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import type { VNode, Component } from "vue";
 | 
			
		||||
import type { iconType } from "@/components/ReIcon/src/types.ts";
 | 
			
		||||
 | 
			
		||||
export interface OptionsType {
 | 
			
		||||
  /** 文字 */
 | 
			
		||||
@ -8,6 +9,8 @@ export interface OptionsType {
 | 
			
		||||
   * @see {@link 用法参考 https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
 | 
			
		||||
   */
 | 
			
		||||
  icon?: string | Component;
 | 
			
		||||
  /** 图标属性、样式配置 */
 | 
			
		||||
  iconAttrs?: iconType;
 | 
			
		||||
  /** 值 */
 | 
			
		||||
  value?: string | number;
 | 
			
		||||
  /** 是否禁用 */
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
import { emitter } from "@/utils/mitt";
 | 
			
		||||
import { onClickOutside } from "@vueuse/core";
 | 
			
		||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
 | 
			
		||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
 | 
			
		||||
import Close from "@iconify-icons/ep/close";
 | 
			
		||||
 | 
			
		||||
const target = ref(null);
 | 
			
		||||
@ -9,7 +10,6 @@ const show = ref<Boolean>(false);
 | 
			
		||||
 | 
			
		||||
const iconClass = computed(() => {
 | 
			
		||||
  return [
 | 
			
		||||
    "mr-[20px]",
 | 
			
		||||
    "outline-none",
 | 
			
		||||
    "width-[20px]",
 | 
			
		||||
    "height-[20px]",
 | 
			
		||||
@ -22,6 +22,8 @@ const iconClass = computed(() => {
 | 
			
		||||
  ];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { onReset } = useDataThemeChange();
 | 
			
		||||
 | 
			
		||||
onClickOutside(target, (event: any) => {
 | 
			
		||||
  if (event.clientX > target.value.offsetLeft) return;
 | 
			
		||||
  show.value = false;
 | 
			
		||||
@ -43,10 +45,18 @@ onBeforeUnmount(() => {
 | 
			
		||||
  <div :class="{ show: show }" class="right-panel-container">
 | 
			
		||||
    <div class="right-panel-background" />
 | 
			
		||||
    <div ref="target" class="right-panel bg-bg_color">
 | 
			
		||||
      <div class="right-panel-items">
 | 
			
		||||
        <div class="project-configuration">
 | 
			
		||||
      <div
 | 
			
		||||
        class="project-configuration border-b-[1px] border-solid border-[var(--pure-border-color)]"
 | 
			
		||||
      >
 | 
			
		||||
        <h4 class="dark:text-white">项目配置</h4>
 | 
			
		||||
          <span title="关闭配置" :class="iconClass">
 | 
			
		||||
        <span
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '关闭配置',
 | 
			
		||||
            placement: 'bottom-start',
 | 
			
		||||
            zIndex: 41000
 | 
			
		||||
          }"
 | 
			
		||||
          :class="iconClass"
 | 
			
		||||
        >
 | 
			
		||||
          <IconifyIconOffline
 | 
			
		||||
            class="dark:text-white"
 | 
			
		||||
            width="20px"
 | 
			
		||||
@ -56,10 +66,26 @@ onBeforeUnmount(() => {
 | 
			
		||||
          />
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
        <div
 | 
			
		||||
          class="border-b-[1px] border-solid border-[#dcdfe6] dark:border-[#303030]"
 | 
			
		||||
        />
 | 
			
		||||
      <el-scrollbar>
 | 
			
		||||
        <slot />
 | 
			
		||||
      </el-scrollbar>
 | 
			
		||||
 | 
			
		||||
      <div
 | 
			
		||||
        class="flex justify-end p-3 border-t-[1px] border-solid border-[var(--pure-border-color)]"
 | 
			
		||||
      >
 | 
			
		||||
        <el-button
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '清空缓存并返回登录页',
 | 
			
		||||
            placement: 'left-start',
 | 
			
		||||
            zIndex: 41000
 | 
			
		||||
          }"
 | 
			
		||||
          type="danger"
 | 
			
		||||
          text
 | 
			
		||||
          bg
 | 
			
		||||
          @click="onReset"
 | 
			
		||||
        >
 | 
			
		||||
          清空缓存
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@ -74,6 +100,10 @@ onBeforeUnmount(() => {
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
:deep(.el-scrollbar) {
 | 
			
		||||
  height: calc(100vh - 110px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right-panel-background {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
@ -90,7 +120,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
  right: 0;
 | 
			
		||||
  z-index: 40000;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-width: 315px;
 | 
			
		||||
  max-width: 300px;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
 | 
			
		||||
  transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
 | 
			
		||||
@ -112,47 +142,10 @@ onBeforeUnmount(() => {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-button {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 45%;
 | 
			
		||||
  left: -48px;
 | 
			
		||||
  z-index: 0;
 | 
			
		||||
  width: 48px;
 | 
			
		||||
  height: 48px;
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  line-height: 48px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  pointer-events: auto;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  background: rgb(24 144 255);
 | 
			
		||||
  border-radius: 6px 0 0 6px !important;
 | 
			
		||||
 | 
			
		||||
  i {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
    line-height: 48px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right-panel-items {
 | 
			
		||||
  height: calc(100vh - 60px);
 | 
			
		||||
  margin-top: 60px;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.project-configuration {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 15px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:deep(.el-divider--horizontal) {
 | 
			
		||||
  width: 90%;
 | 
			
		||||
  margin: 20px auto 0;
 | 
			
		||||
  padding: 14px 20px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -8,28 +8,22 @@ import {
 | 
			
		||||
  nextTick,
 | 
			
		||||
  onBeforeMount
 | 
			
		||||
} from "vue";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import panel from "../panel/index.vue";
 | 
			
		||||
import { emitter } from "@/utils/mitt";
 | 
			
		||||
import { resetRouter } from "@/router";
 | 
			
		||||
import { removeToken } from "@/utils/auth";
 | 
			
		||||
import { routerArrays } from "@/layout/types";
 | 
			
		||||
import { useNav } from "@/layout/hooks/useNav";
 | 
			
		||||
import { useAppStoreHook } from "@/store/modules/app";
 | 
			
		||||
import { useDark, debounce, useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import Segmented, { type OptionsType } from "@/components/ReSegmented";
 | 
			
		||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
 | 
			
		||||
import { useDark, debounce, useGlobal, storageLocal } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
import Check from "@iconify-icons/ep/check";
 | 
			
		||||
import dayIcon from "@/assets/svg/day.svg?component";
 | 
			
		||||
import darkIcon from "@/assets/svg/dark.svg?component";
 | 
			
		||||
import Check from "@iconify-icons/ep/check";
 | 
			
		||||
import Logout from "@iconify-icons/ri/logout-circle-r-line";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const { device } = useNav();
 | 
			
		||||
const { isDark } = useDark();
 | 
			
		||||
const { device, tooltipEffect } = useNav();
 | 
			
		||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
 | 
			
		||||
 | 
			
		||||
const mixRef = ref();
 | 
			
		||||
@ -40,8 +34,8 @@ const {
 | 
			
		||||
  dataTheme,
 | 
			
		||||
  layoutTheme,
 | 
			
		||||
  themeColors,
 | 
			
		||||
  toggleClass,
 | 
			
		||||
  dataThemeChange,
 | 
			
		||||
  setEpThemeColor,
 | 
			
		||||
  setLayoutThemeColor
 | 
			
		||||
} = useDataThemeChange();
 | 
			
		||||
 | 
			
		||||
@ -89,13 +83,6 @@ function storageConfigureChange<T>(key: string, val: T): void {
 | 
			
		||||
  $storage.configure = storageConfigure;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
 | 
			
		||||
  const targetEl = target || document.body;
 | 
			
		||||
  let { className } = targetEl;
 | 
			
		||||
  className = className.replace(clsName, "").trim();
 | 
			
		||||
  targetEl.className = flag ? `${className} ${clsName} ` : className;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 灰色模式设置 */
 | 
			
		||||
const greyChange = (value): void => {
 | 
			
		||||
  toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
 | 
			
		||||
@ -132,24 +119,11 @@ const multiTagsCacheChange = () => {
 | 
			
		||||
  useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 清空缓存并返回登录页 */
 | 
			
		||||
function onReset() {
 | 
			
		||||
  removeToken();
 | 
			
		||||
  storageLocal().clear();
 | 
			
		||||
  const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
 | 
			
		||||
  useAppStoreHook().setLayout(Layout);
 | 
			
		||||
  setEpThemeColor(EpThemeColor);
 | 
			
		||||
  useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
 | 
			
		||||
  toggleClass(Grey, "html-grey", document.querySelector("html"));
 | 
			
		||||
  toggleClass(Weak, "html-weakness", document.querySelector("html"));
 | 
			
		||||
  router.push("/login");
 | 
			
		||||
  useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
 | 
			
		||||
  resetRouter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onChange(label) {
 | 
			
		||||
  storageConfigureChange("showModel", label);
 | 
			
		||||
  emitter.emit("tagViewsShowModel", label);
 | 
			
		||||
function onChange({ option }) {
 | 
			
		||||
  const { value } = option;
 | 
			
		||||
  markValue.value = value;
 | 
			
		||||
  storageConfigureChange("showModel", value);
 | 
			
		||||
  emitter.emit("tagViewsShowModel", value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 侧边栏Logo */
 | 
			
		||||
@ -185,6 +159,32 @@ const getThemeColor = computed(() => {
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const themeOptions = computed<Array<OptionsType>>(() => {
 | 
			
		||||
  return [
 | 
			
		||||
    {
 | 
			
		||||
      label: "亮色",
 | 
			
		||||
      icon: dayIcon,
 | 
			
		||||
      iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: "暗色",
 | 
			
		||||
      icon: darkIcon,
 | 
			
		||||
      iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
 | 
			
		||||
    }
 | 
			
		||||
  ];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const markOptions: Array<OptionsType> = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "灵动",
 | 
			
		||||
    value: "smart"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "卡片",
 | 
			
		||||
    value: "card"
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
/** 设置导航模式 */
 | 
			
		||||
function setLayoutModel(layout: string) {
 | 
			
		||||
  layoutTheme.value.layout = layout;
 | 
			
		||||
@ -234,73 +234,20 @@ onBeforeMount(() => {
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <panel>
 | 
			
		||||
    <el-divider>主题</el-divider>
 | 
			
		||||
    <el-switch
 | 
			
		||||
      v-model="dataTheme"
 | 
			
		||||
      inline-prompt
 | 
			
		||||
      class="pure-datatheme"
 | 
			
		||||
      :active-icon="dayIcon"
 | 
			
		||||
      :inactive-icon="darkIcon"
 | 
			
		||||
      @change="dataThemeChange"
 | 
			
		||||
    <div class="p-6">
 | 
			
		||||
      <p class="mb-3 font-medium text-sm dark:text-white">整体风格</p>
 | 
			
		||||
      <Segmented
 | 
			
		||||
        :modelValue="dataTheme ? 1 : 0"
 | 
			
		||||
        :options="themeOptions"
 | 
			
		||||
        @change="
 | 
			
		||||
          {
 | 
			
		||||
            dataTheme = !dataTheme;
 | 
			
		||||
            dataThemeChange();
 | 
			
		||||
          }
 | 
			
		||||
        "
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
    <el-divider>导航栏模式</el-divider>
 | 
			
		||||
    <ul class="pure-theme">
 | 
			
		||||
      <el-tooltip
 | 
			
		||||
        :effect="tooltipEffect"
 | 
			
		||||
        class="item"
 | 
			
		||||
        content="左侧模式"
 | 
			
		||||
        placement="bottom"
 | 
			
		||||
        popper-class="pure-tooltip"
 | 
			
		||||
      >
 | 
			
		||||
        <li
 | 
			
		||||
          ref="verticalRef"
 | 
			
		||||
          :class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
 | 
			
		||||
          @click="setLayoutModel('vertical')"
 | 
			
		||||
        >
 | 
			
		||||
          <div />
 | 
			
		||||
          <div />
 | 
			
		||||
        </li>
 | 
			
		||||
      </el-tooltip>
 | 
			
		||||
 | 
			
		||||
      <el-tooltip
 | 
			
		||||
        v-if="device !== 'mobile'"
 | 
			
		||||
        :effect="tooltipEffect"
 | 
			
		||||
        class="item"
 | 
			
		||||
        content="顶部模式"
 | 
			
		||||
        placement="bottom"
 | 
			
		||||
        popper-class="pure-tooltip"
 | 
			
		||||
      >
 | 
			
		||||
        <li
 | 
			
		||||
          ref="horizontalRef"
 | 
			
		||||
          :class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
 | 
			
		||||
          @click="setLayoutModel('horizontal')"
 | 
			
		||||
        >
 | 
			
		||||
          <div />
 | 
			
		||||
          <div />
 | 
			
		||||
        </li>
 | 
			
		||||
      </el-tooltip>
 | 
			
		||||
 | 
			
		||||
      <el-tooltip
 | 
			
		||||
        v-if="device !== 'mobile'"
 | 
			
		||||
        :effect="tooltipEffect"
 | 
			
		||||
        class="item"
 | 
			
		||||
        content="混合模式"
 | 
			
		||||
        placement="bottom"
 | 
			
		||||
        popper-class="pure-tooltip"
 | 
			
		||||
      >
 | 
			
		||||
        <li
 | 
			
		||||
          ref="mixRef"
 | 
			
		||||
          :class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
 | 
			
		||||
          @click="setLayoutModel('mix')"
 | 
			
		||||
        >
 | 
			
		||||
          <div />
 | 
			
		||||
          <div />
 | 
			
		||||
        </li>
 | 
			
		||||
      </el-tooltip>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <el-divider>主题色</el-divider>
 | 
			
		||||
      <p class="mt-5 mb-3 font-medium text-sm dark:text-white">主题色</p>
 | 
			
		||||
      <ul class="theme-color">
 | 
			
		||||
        <li
 | 
			
		||||
          v-for="(item, index) in themeColors"
 | 
			
		||||
@ -319,14 +266,62 @@ onBeforeMount(() => {
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
 | 
			
		||||
    <el-divider>界面显示</el-divider>
 | 
			
		||||
      <p class="mt-5 mb-3 font-medium text-sm dark:text-white">导航模式</p>
 | 
			
		||||
      <ul class="pure-theme">
 | 
			
		||||
        <li
 | 
			
		||||
          ref="verticalRef"
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '左侧菜单',
 | 
			
		||||
            zIndex: 41000
 | 
			
		||||
          }"
 | 
			
		||||
          :class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
 | 
			
		||||
          @click="setLayoutModel('vertical')"
 | 
			
		||||
        >
 | 
			
		||||
          <div />
 | 
			
		||||
          <div />
 | 
			
		||||
        </li>
 | 
			
		||||
        <li
 | 
			
		||||
          v-if="device !== 'mobile'"
 | 
			
		||||
          ref="horizontalRef"
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '顶部菜单',
 | 
			
		||||
            zIndex: 41000
 | 
			
		||||
          }"
 | 
			
		||||
          :class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
 | 
			
		||||
          @click="setLayoutModel('horizontal')"
 | 
			
		||||
        >
 | 
			
		||||
          <div />
 | 
			
		||||
          <div />
 | 
			
		||||
        </li>
 | 
			
		||||
        <li
 | 
			
		||||
          v-if="device !== 'mobile'"
 | 
			
		||||
          ref="mixRef"
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '混合菜单',
 | 
			
		||||
            zIndex: 41000
 | 
			
		||||
          }"
 | 
			
		||||
          :class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
 | 
			
		||||
          @click="setLayoutModel('mix')"
 | 
			
		||||
        >
 | 
			
		||||
          <div />
 | 
			
		||||
          <div />
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
 | 
			
		||||
      <p class="mt-5 mb-3 font-medium text-base dark:text-white">页签风格</p>
 | 
			
		||||
      <Segmented
 | 
			
		||||
        :modelValue="markValue === 'smart' ? 0 : 1"
 | 
			
		||||
        :options="markOptions"
 | 
			
		||||
        @change="onChange"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <p class="mt-5 mb-1 font-medium text-sm dark:text-white">界面显示</p>
 | 
			
		||||
      <ul class="setting">
 | 
			
		||||
        <li>
 | 
			
		||||
          <span class="dark:text-white">灰色模式</span>
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="settings.greyVal"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
          inactive-color="#a6a6a6"
 | 
			
		||||
            active-text="开"
 | 
			
		||||
            inactive-text="关"
 | 
			
		||||
            @change="greyChange"
 | 
			
		||||
@ -337,7 +332,6 @@ onBeforeMount(() => {
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="settings.weakVal"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
          inactive-color="#a6a6a6"
 | 
			
		||||
            active-text="开"
 | 
			
		||||
            inactive-text="关"
 | 
			
		||||
            @change="weekChange"
 | 
			
		||||
@ -348,7 +342,6 @@ onBeforeMount(() => {
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="settings.tabsVal"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
          inactive-color="#a6a6a6"
 | 
			
		||||
            active-text="开"
 | 
			
		||||
            inactive-text="关"
 | 
			
		||||
            @change="tagsChange"
 | 
			
		||||
@ -359,60 +352,35 @@ onBeforeMount(() => {
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="settings.hideFooter"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
          inactive-color="#a6a6a6"
 | 
			
		||||
            active-text="开"
 | 
			
		||||
            inactive-text="关"
 | 
			
		||||
            @change="hideFooterChange"
 | 
			
		||||
          />
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
        <span class="dark:text-white">侧边栏Logo</span>
 | 
			
		||||
          <span class="dark:text-white">Logo</span>
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="logoVal"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
            :active-value="true"
 | 
			
		||||
            :inactive-value="false"
 | 
			
		||||
          inactive-color="#a6a6a6"
 | 
			
		||||
            active-text="开"
 | 
			
		||||
            inactive-text="关"
 | 
			
		||||
            @change="logoChange"
 | 
			
		||||
          />
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
        <span class="dark:text-white">标签页持久化</span>
 | 
			
		||||
          <span class="dark:text-white">页签持久化</span>
 | 
			
		||||
          <el-switch
 | 
			
		||||
            v-model="settings.multiTagsCache"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
          inactive-color="#a6a6a6"
 | 
			
		||||
            active-text="开"
 | 
			
		||||
            inactive-text="关"
 | 
			
		||||
            @change="multiTagsCacheChange"
 | 
			
		||||
          />
 | 
			
		||||
        </li>
 | 
			
		||||
 | 
			
		||||
      <li>
 | 
			
		||||
        <span class="dark:text-white">标签风格</span>
 | 
			
		||||
        <el-radio-group v-model="markValue" size="small" @change="onChange">
 | 
			
		||||
          <el-radio label="card">卡片</el-radio>
 | 
			
		||||
          <el-radio label="smart">灵动</el-radio>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
 | 
			
		||||
    <el-divider />
 | 
			
		||||
    <el-button
 | 
			
		||||
      type="danger"
 | 
			
		||||
      style="width: 90%; margin: 24px 15px"
 | 
			
		||||
      @click="onReset"
 | 
			
		||||
    >
 | 
			
		||||
      <IconifyIconOffline
 | 
			
		||||
        :icon="Logout"
 | 
			
		||||
        width="15"
 | 
			
		||||
        height="15"
 | 
			
		||||
        style="margin-right: 4px"
 | 
			
		||||
      />
 | 
			
		||||
      清空缓存并返回登录页
 | 
			
		||||
    </el-button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </panel>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -422,41 +390,41 @@ onBeforeMount(() => {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-select {
 | 
			
		||||
  border: 2px solid var(--el-color-primary);
 | 
			
		||||
:deep(.el-switch__core) {
 | 
			
		||||
  --el-switch-off-color: var(--pure-switch-off-color);
 | 
			
		||||
 | 
			
		||||
  min-width: 36px;
 | 
			
		||||
  height: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.setting {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
:deep(.el-switch__core .el-switch__action) {
 | 
			
		||||
  height: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.theme-color {
 | 
			
		||||
  height: 20px;
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    margin: 25px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
    float: left;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
 | 
			
		||||
.pure-datatheme {
 | 
			
		||||
  display: block;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  padding-top: 25px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
    &:nth-child(2) {
 | 
			
		||||
      border: 1px solid #ddd;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pure-theme {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  margin-top: 25px;
 | 
			
		||||
  gap: 12px;
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 18%;
 | 
			
		||||
    height: 45px;
 | 
			
		||||
    width: 46px;
 | 
			
		||||
    height: 36px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    background: #f0f2f5;
 | 
			
		||||
@ -517,27 +485,17 @@ onBeforeMount(() => {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.theme-color {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 40px;
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
.is-select {
 | 
			
		||||
  border: 2px solid var(--el-color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.setting {
 | 
			
		||||
  li {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
 | 
			
		||||
    &:nth-child(2) {
 | 
			
		||||
      border: 1px solid #ddd;
 | 
			
		||||
    }
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    padding: 4px 0;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,14 @@
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { useLayout } from "./useLayout";
 | 
			
		||||
import { useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import { removeToken } from "@/utils/auth";
 | 
			
		||||
import { routerArrays } from "@/layout/types";
 | 
			
		||||
import { router, resetRouter } from "@/router";
 | 
			
		||||
import type { themeColorsType } from "../types";
 | 
			
		||||
import { useAppStoreHook } from "@/store/modules/app";
 | 
			
		||||
import { useGlobal, storageLocal } from "@pureadmin/utils";
 | 
			
		||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import {
 | 
			
		||||
  darken,
 | 
			
		||||
  lighten,
 | 
			
		||||
@ -37,6 +42,13 @@ export function useDataThemeChange() {
 | 
			
		||||
  const dataTheme = ref<boolean>($storage?.layout?.darkMode);
 | 
			
		||||
  const body = document.documentElement as HTMLElement;
 | 
			
		||||
 | 
			
		||||
  function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
 | 
			
		||||
    const targetEl = target || document.body;
 | 
			
		||||
    let { className } = targetEl;
 | 
			
		||||
    className = className.replace(clsName, "").trim();
 | 
			
		||||
    targetEl.className = flag ? `${className} ${clsName} ` : className;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 设置导航主题色 */
 | 
			
		||||
  function setLayoutThemeColor(theme = getConfig().Theme ?? "default") {
 | 
			
		||||
    layoutTheme.value.theme = theme;
 | 
			
		||||
@ -78,9 +90,8 @@ export function useDataThemeChange() {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** 日间、夜间主题切换 */
 | 
			
		||||
  /** 亮色、暗色整体风格切换 */
 | 
			
		||||
  function dataThemeChange() {
 | 
			
		||||
    /* 如果当前是light夜间主题,默认切换到default主题 */
 | 
			
		||||
    if (useEpThemeStoreHook().epTheme === "light" && dataTheme.value) {
 | 
			
		||||
      setLayoutThemeColor("default");
 | 
			
		||||
    } else {
 | 
			
		||||
@ -94,11 +105,28 @@ export function useDataThemeChange() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 清空缓存并返回登录页 */
 | 
			
		||||
  function onReset() {
 | 
			
		||||
    removeToken();
 | 
			
		||||
    storageLocal().clear();
 | 
			
		||||
    const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
 | 
			
		||||
    useAppStoreHook().setLayout(Layout);
 | 
			
		||||
    setEpThemeColor(EpThemeColor);
 | 
			
		||||
    useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
 | 
			
		||||
    toggleClass(Grey, "html-grey", document.querySelector("html"));
 | 
			
		||||
    toggleClass(Weak, "html-weakness", document.querySelector("html"));
 | 
			
		||||
    router.push("/login");
 | 
			
		||||
    useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
 | 
			
		||||
    resetRouter();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    body,
 | 
			
		||||
    dataTheme,
 | 
			
		||||
    layoutTheme,
 | 
			
		||||
    themeColors,
 | 
			
		||||
    onReset,
 | 
			
		||||
    toggleClass,
 | 
			
		||||
    dataThemeChange,
 | 
			
		||||
    setEpThemeColor,
 | 
			
		||||
    setLayoutThemeColor
 | 
			
		||||
 | 
			
		||||
@ -45,6 +45,14 @@ app.component("FontIcon", FontIcon);
 | 
			
		||||
import { Auth } from "@/components/ReAuth";
 | 
			
		||||
app.component("Auth", Auth);
 | 
			
		||||
 | 
			
		||||
// 全局注册`vue-tippy`
 | 
			
		||||
import "tippy.js/dist/tippy.css";
 | 
			
		||||
import "tippy.js/animations/perspective.css";
 | 
			
		||||
import VueTippy from "vue-tippy";
 | 
			
		||||
app.use(VueTippy, {
 | 
			
		||||
  defaultProps: { animation: "perspective" }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
getPlatformConfig(app).then(async config => {
 | 
			
		||||
  setupStore(app);
 | 
			
		||||
  app.use(router);
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,18 @@
 | 
			
		||||
 | 
			
		||||
/* 暗黑模式适配 */
 | 
			
		||||
html.dark {
 | 
			
		||||
  /* 自定义深色背景颜色 */
 | 
			
		||||
  // --el-bg-color: #020409;
 | 
			
		||||
  $border-style: #303030;
 | 
			
		||||
  $color-white: #fff;
 | 
			
		||||
 | 
			
		||||
  /* 自定义深色背景颜色 */
 | 
			
		||||
  // --el-bg-color: #020409;
 | 
			
		||||
 | 
			
		||||
  /* 常用border-color 需要时可取用 */
 | 
			
		||||
  --pure-border-color: rgb(253 253 253 / 12%);
 | 
			
		||||
 | 
			
		||||
  /* switch关闭状态下的color 需要时可取用 */
 | 
			
		||||
  --pure-switch-off-color: #ffffff3f;
 | 
			
		||||
 | 
			
		||||
  .navbar,
 | 
			
		||||
  .tags-view,
 | 
			
		||||
  .contextmenu,
 | 
			
		||||
 | 
			
		||||
@ -50,12 +50,6 @@
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 自定义 tooltip 的类名 */
 | 
			
		||||
.pure-tooltip {
 | 
			
		||||
  // 右侧操作面板right-panel类名的z-index为40000,tooltip需要大于它才能显示
 | 
			
		||||
  z-index: 41000 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* nprogress 适配 element-plus 的主题色 */
 | 
			
		||||
#nprogress {
 | 
			
		||||
  & .bar {
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,12 @@
 | 
			
		||||
:root {
 | 
			
		||||
  /* 左侧菜单展开、收起动画时长 */
 | 
			
		||||
  --pure-transition-duration: 0.3s;
 | 
			
		||||
 | 
			
		||||
  /* 常用border-color 需要时可取用 */
 | 
			
		||||
  --pure-border-color: rgb(5 5 5 / 6%);
 | 
			
		||||
 | 
			
		||||
  /* switch关闭状态下的color 需要时可取用 */
 | 
			
		||||
  --pure-switch-off-color: #a6a6a6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 灰色模式 */
 | 
			
		||||
 | 
			
		||||
@ -4,15 +4,10 @@ import { getTime } from "@pureadmin/utils";
 | 
			
		||||
import { Play, Pause, Forward, Rewind } from "./svg";
 | 
			
		||||
import { ref, onMounted, onBeforeUnmount } from "vue";
 | 
			
		||||
 | 
			
		||||
import "tippy.js/dist/tippy.css";
 | 
			
		||||
import "tippy.js/animations/scale.css";
 | 
			
		||||
import { directive as tippy } from "vue-tippy";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "Wavesurfer"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const vTippy = tippy;
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const wavesurfer = ref(null);
 | 
			
		||||
const wavesurferRef = ref();
 | 
			
		||||
@ -114,8 +109,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
        <Rewind
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '快退(可长按)',
 | 
			
		||||
            placement: 'bottom',
 | 
			
		||||
            animation: 'scale'
 | 
			
		||||
            placement: 'bottom'
 | 
			
		||||
          }"
 | 
			
		||||
          v-longpress:0:100="() => wavesurfer?.skip(-1)"
 | 
			
		||||
          class="cursor-pointer"
 | 
			
		||||
@ -123,8 +117,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
        <div
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: isPlay ? '暂停' : '播放',
 | 
			
		||||
            placement: 'bottom',
 | 
			
		||||
            animation: 'scale'
 | 
			
		||||
            placement: 'bottom'
 | 
			
		||||
          }"
 | 
			
		||||
          class="cursor-pointer"
 | 
			
		||||
          @click="wavesurfer?.playPause()"
 | 
			
		||||
@ -135,8 +128,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
        <Forward
 | 
			
		||||
          v-tippy="{
 | 
			
		||||
            content: '快进(可长按)',
 | 
			
		||||
            placement: 'bottom',
 | 
			
		||||
            animation: 'scale'
 | 
			
		||||
            placement: 'bottom'
 | 
			
		||||
          }"
 | 
			
		||||
          v-longpress:0:100="() => wavesurfer?.skip(1)"
 | 
			
		||||
          class="cursor-pointer"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user