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