Browse Source

release: update `5.2.0`

i18n
xiaoxian521 8 months ago
parent
commit
b4c80166be
  1. 4
      .eslintignore
  2. 8
      .lintstagedrc
  3. 2
      build/info.ts
  4. 44
      package.json
  5. 2205
      pnpm-lock.yaml
  6. 3
      public/platform-config.json
  7. 17
      src/components/ReDialog/index.vue
  8. 15
      src/components/ReDialog/type.ts
  9. 16
      src/components/ReSegmented/src/index.css
  10. 24
      src/components/ReSegmented/src/index.tsx
  11. 25
      src/layout/components/appMain.vue
  12. 1
      src/layout/components/keepAliveFrame/index.vue
  13. 8
      src/layout/components/navbar.vue
  14. 92
      src/layout/components/setting/index.vue
  15. 70
      src/layout/components/sidebar/centerCollapse.vue
  16. 30
      src/layout/components/sidebar/fullScreen.vue
  17. 7
      src/layout/components/sidebar/horizontal.vue
  18. 4
      src/layout/components/sidebar/leftCollapse.vue
  19. 7
      src/layout/components/sidebar/mixNav.vue
  20. 13
      src/layout/components/sidebar/vertical.vue
  21. 29
      src/layout/components/tag/index.vue
  22. 3
      src/layout/hooks/useLayout.ts
  23. 8
      src/layout/hooks/useNav.ts
  24. 9
      src/layout/hooks/useTag.ts
  25. 1
      src/style/login.css
  26. 1
      src/style/reset.scss
  27. 17
      src/style/sidebar.scss
  28. 3
      src/utils/responsive.ts
  29. 7
      tailwind.config.ts
  30. 11
      types/global.d.ts

4
.eslintignore

@ -3,9 +3,9 @@ dist
*.d.ts *.d.ts
/src/assets /src/assets
package.json package.json
eslint.config.js
eslint.config.js
.prettierrc.js .prettierrc.js
commitlint.config.js commitlint.config.js
postcss.config.js postcss.config.js
tailwind.config.js
tailwind.config.ts
stylelint.config.js stylelint.config.js

8
.lintstagedrc

@ -7,10 +7,14 @@
"prettier --cache --write--parser json" "prettier --cache --write--parser json"
], ],
"package.json": ["prettier --cache --write"], "package.json": ["prettier --cache --write"],
"*.vue": ["prettier --write", "eslint --cache --fix", "stylelint --fix"],
"*.vue": [
"prettier --write",
"eslint --cache --fix",
"stylelint --fix --allow-empty-input"
],
"*.{css,scss,html}": [ "*.{css,scss,html}": [
"prettier --cache --ignore-unknown --write", "prettier --cache --ignore-unknown --write",
"stylelint --fix"
"stylelint --fix --allow-empty-input"
], ],
"*.md": ["prettier --cache --ignore-unknown --write"] "*.md": ["prettier --cache --ignore-unknown --write"]
} }

2
build/info.ts

@ -7,7 +7,7 @@ import boxen, { type Options as BoxenOptions } from "boxen";
dayjs.extend(duration); dayjs.extend(duration);
const welcomeMessage = gradientString("cyan", "magenta").multiline( const welcomeMessage = gradientString("cyan", "magenta").multiline(
`Hello! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://yiming_chang.gitee.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app`
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://yiming_chang.gitee.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app`
); );
const boxenOptions: BoxenOptions = { const boxenOptions: BoxenOptions = {

44
package.json

@ -1,6 +1,6 @@
{ {
"name": "pure-admin-thin", "name": "pure-admin-thin",
"version": "5.1.0",
"version": "5.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -48,16 +48,16 @@
"url": "https://github.com/xiaoxian521" "url": "https://github.com/xiaoxian521"
}, },
"dependencies": { "dependencies": {
"@pureadmin/descriptions": "^1.2.0",
"@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.1.2", "@pureadmin/table": "^3.1.2",
"@pureadmin/utils": "^2.4.5",
"@pureadmin/utils": "^2.4.7",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@vueuse/motion": "^2.1.0", "@vueuse/motion": "^2.1.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.7",
"axios": "^1.6.8",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.6.0",
"element-plus": "^2.6.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
@ -65,19 +65,19 @@
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinyin-pro": "^3.19.6", "pinyin-pro": "^3.19.6",
"qs": "^6.11.2",
"qs": "^6.12.0",
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-i18n": "^9.10.1",
"vue-i18n": "^9.10.2",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vue-tippy": "^6.4.1", "vue-tippy": "^6.4.1",
"vue-types": "^5.1.1" "vue-types": "^5.1.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@commitlint/types": "^18.6.1",
"@commitlint/cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0",
"@commitlint/types": "^19.0.3",
"@eslint/js": "^8.57.0", "@eslint/js": "^8.57.0",
"@faker-js/faker": "^8.4.1", "@faker-js/faker": "^8.4.1",
"@iconify-icons/ep": "^1.2.12", "@iconify-icons/ep": "^1.2.12",
@ -87,34 +87,34 @@
"@pureadmin/theme": "^3.2.0", "@pureadmin/theme": "^3.2.0",
"@types/gradient-string": "^1.1.5", "@types/gradient-string": "^1.1.5",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/node": "^20.11.24",
"@types/node": "^20.11.30",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.12",
"@types/qs": "^6.9.14",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.18",
"autoprefixer": "^10.4.19",
"boxen": "^7.1.1", "boxen": "^7.1.1",
"cloc": "^2.11.0", "cloc": "^2.11.0",
"cssnano": "^6.0.5",
"cssnano": "^6.1.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0", "eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.22.0",
"eslint-plugin-vue": "^9.23.0",
"gradient-string": "^2.0.2", "gradient-string": "^2.0.2",
"husky": "^9.0.11", "husky": "^9.0.11",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"postcss": "^8.4.38",
"postcss-html": "^1.6.0", "postcss-html": "^1.6.0",
"postcss-import": "^16.0.1",
"postcss-import": "^16.1.0",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.71.1",
"sass": "^1.72.0",
"stylelint": "^16.2.1", "stylelint": "^16.2.1",
"stylelint-config-recess-order": "^5.0.0", "stylelint-config-recess-order": "^5.0.0",
"stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-recommended-vue": "^1.5.0",
@ -122,8 +122,8 @@
"stylelint-prettier": "^5.0.0", "stylelint-prettier": "^5.0.0",
"svgo": "^3.2.0", "svgo": "^3.2.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"vite": "^5.1.5",
"typescript": "^5.4.3",
"vite": "^5.2.2",
"vite-plugin-cdn-import": "^0.3.5", "vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-fake-server": "^2.1.1", "vite-plugin-fake-server": "^2.1.1",

2205
pnpm-lock.yaml
File diff suppressed because it is too large
View File

3
public/platform-config.json

@ -1,5 +1,5 @@
{ {
"Version": "5.1.0",
"Version": "5.2.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,
@ -14,6 +14,7 @@
"Weak": false, "Weak": false,
"HideTabs": false, "HideTabs": false,
"HideFooter": false, "HideFooter": false,
"Stretch": false,
"SidebarStatus": true, "SidebarStatus": true,
"EpThemeColor": "#409EFF", "EpThemeColor": "#409EFF",
"ShowLogo": true, "ShowLogo": true,

17
src/components/ReDialog/index.vue

@ -64,9 +64,10 @@ const fullscreenClass = computed(() => {
function eventsCallBack( function eventsCallBack(
event: EventType, event: EventType,
options: DialogOptions, options: DialogOptions,
index: number
index: number,
isClickFullScreen = false
) { ) {
fullscreen.value = options?.fullscreen ?? false;
if (!isClickFullScreen) fullscreen.value = options?.fullscreen ?? false;
if (options?.[event] && isFunction(options?.[event])) { if (options?.[event] && isFunction(options?.[event])) {
return options?.[event]({ options, index }); return options?.[event]({ options, index });
} }
@ -108,7 +109,17 @@ function handleClose(
<i <i
v-if="!options?.fullscreen" v-if="!options?.fullscreen"
:class="fullscreenClass" :class="fullscreenClass"
@click="fullscreen = !fullscreen"
@click="
() => {
fullscreen = !fullscreen;
eventsCallBack(
'fullscreenCallBack',
{ ...options, fullscreen },
index,
true
);
}
"
> >
<IconifyIconOffline <IconifyIconOffline
class="pure-dialog-svg" class="pure-dialog-svg"

15
src/components/ReDialog/type.ts

@ -1,7 +1,12 @@
import type { CSSProperties, VNode, Component } from "vue"; import type { CSSProperties, VNode, Component } from "vue";
type DoneFn = (cancel?: boolean) => void; type DoneFn = (cancel?: boolean) => void;
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
type EventType =
| "open"
| "close"
| "openAutoFocus"
| "closeAutoFocus"
| "fullscreenCallBack";
type ArgsType = { type ArgsType = {
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */ /** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
command: "cancel" | "sure" | "close"; command: "cancel" | "sure" | "close";
@ -175,6 +180,14 @@ interface DialogOptions extends DialogProps {
index: number; index: number;
args: any; args: any;
}) => void; }) => void;
/** 点击全屏按钮时的回调 */
fullscreenCallBack?: ({
options,
index
}: {
options: DialogOptions;
index: number;
}) => void;
/** 输入焦点聚焦在 `Dialog` 内容时的回调 */ /** 输入焦点聚焦在 `Dialog` 内容时的回调 */
openAutoFocus?: ({ openAutoFocus?: ({
options, options,

16
src/components/ReSegmented/src/index.css

@ -8,6 +8,21 @@
border-radius: 2px; border-radius: 2px;
} }
.pure-segmented-block {
display: flex;
}
.pure-segmented-block .pure-segmented-item {
flex: 1;
min-width: 0;
}
.pure-segmented-block .pure-segmented-item > .pure-segmented-item-label > span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.pure-segmented-group { .pure-segmented-group {
position: relative; position: relative;
display: flex; display: flex;
@ -67,6 +82,7 @@
.pure-segmented-item-label { .pure-segmented-item-label {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
} }
.pure-segmented-item-icon svg { .pure-segmented-item-icon svg {

24
src/components/ReSegmented/src/index.tsx

@ -10,7 +10,12 @@ import {
} from "vue"; } from "vue";
import type { OptionsType } from "./type"; import type { OptionsType } from "./type";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { isFunction, isNumber, useDark } from "@pureadmin/utils";
import {
isFunction,
isNumber,
useDark,
useResizeObserver
} from "@pureadmin/utils";
const props = { const props = {
options: { options: {
@ -22,6 +27,11 @@ const props = {
type: undefined, type: undefined,
require: false, require: false,
default: "0" default: "0"
},
/** 将宽度调整为父元素宽度 */
block: {
type: Boolean,
default: false
} }
}; };
@ -77,6 +87,14 @@ export default defineComponent({
}); });
} }
if (props.block) {
useResizeObserver(".pure-segmented", () => {
nextTick(() => {
handleInit(curIndex.value);
});
});
}
watch( watch(
() => curIndex.value, () => curIndex.value,
index => { index => {
@ -148,7 +166,9 @@ export default defineComponent({
}; };
return () => ( return () => (
<div class="pure-segmented">
<div
class={["pure-segmented", props.block ? "pure-segmented-block" : ""]}
>
<div class="pure-segmented-group"> <div class="pure-segmented-group">
<div <div
class="pure-segmented-item-selected" class="pure-segmented-item-selected"

25
src/layout/components/appMain.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Footer from "./footer/index.vue"; import Footer from "./footer/index.vue";
import { useGlobal } from "@pureadmin/utils";
import { useGlobal, isNumber } from "@pureadmin/utils";
import KeepAliveFrame from "./keepAliveFrame/index.vue"; import KeepAliveFrame from "./keepAliveFrame/index.vue";
import backTop from "@/assets/svg/back_top.svg?component"; import backTop from "@/assets/svg/back_top.svg?component";
import { h, computed, Transition, defineComponent } from "vue"; import { h, computed, Transition, defineComponent } from "vue";
@ -30,16 +30,28 @@ const hideFooter = computed(() => {
return $storage?.configure.hideFooter; return $storage?.configure.hideFooter;
}); });
const stretch = computed(() => {
return $storage?.configure.stretch;
});
const layout = computed(() => { const layout = computed(() => {
return $storage?.layout.layout === "vertical"; return $storage?.layout.layout === "vertical";
}); });
const getMainWidth = computed(() => {
return isNumber(stretch.value)
? stretch.value + "px"
: stretch.value
? "1440px"
: "100%";
});
const getSectionStyle = computed(() => { const getSectionStyle = computed(() => {
return [ return [
hideTabs.value && layout ? "padding-top: 48px;" : "", hideTabs.value && layout ? "padding-top: 48px;" : "",
!hideTabs.value && layout ? "padding-top: 85px;" : "",
!hideTabs.value && layout ? "padding-top: 81px;" : "",
hideTabs.value && !layout.value ? "padding-top: 48px;" : "", hideTabs.value && !layout.value ? "padding-top: 48px;" : "",
!hideTabs.value && !layout.value ? "padding-top: 85px;" : "",
!hideTabs.value && !layout.value ? "padding-top: 81px;" : "",
props.fixedHeader props.fixedHeader
? "" ? ""
: `padding-top: 0;${ : `padding-top: 0;${
@ -96,12 +108,15 @@ const transitionMain = defineComponent({
v-if="props.fixedHeader" v-if="props.fixedHeader"
:wrap-style="{ :wrap-style="{
display: 'flex', display: 'flex',
'flex-wrap': 'wrap'
'flex-wrap': 'wrap',
'max-width': getMainWidth,
margin: '0 auto',
transition: 'all 300ms cubic-bezier(0.4, 0, 0.2, 1)'
}" }"
:view-style="{ :view-style="{
display: 'flex', display: 'flex',
flex: 'auto', flex: 'auto',
overflow: 'auto',
overflow: 'hidden',
'flex-direction': 'column' 'flex-direction': 'column'
}" }"
> >

1
src/layout/components/keepAliveFrame/index.vue

@ -63,7 +63,6 @@ watch(
} }
); );
</script> </script>
<template> <template>
<template v-for="[fullPath, Comp] in compList" :key="fullPath"> <template v-for="[fullPath, Comp] in compList" :key="fullPath">
<div v-show="fullPath === props.currRoute.fullPath" class="w-full h-full"> <div v-show="fullPath === props.currRoute.fullPath" class="w-full h-full">

8
src/layout/components/navbar.vue

@ -3,6 +3,7 @@ import Search from "./search/index.vue";
import Notice from "./notice/index.vue"; import Notice from "./notice/index.vue";
import mixNav from "./sidebar/mixNav.vue"; import mixNav from "./sidebar/mixNav.vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import FullScreen from "./sidebar/fullScreen.vue";
import Breadcrumb from "./sidebar/breadCrumb.vue"; import Breadcrumb from "./sidebar/breadCrumb.vue";
import topCollapse from "./sidebar/topCollapse.vue"; import topCollapse from "./sidebar/topCollapse.vue";
import { useTranslationLang } from "../hooks/useTranslationLang"; import { useTranslationLang } from "../hooks/useTranslationLang";
@ -10,7 +11,6 @@ import globalization from "@/assets/svg/globalization.svg?component";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line"; import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line"; import Setting from "@iconify-icons/ri/settings-3-line";
import Check from "@iconify-icons/ep/check"; import Check from "@iconify-icons/ep/check";
const { const {
layout, layout,
device, device,
@ -47,8 +47,6 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<div v-if="layout === 'vertical'" class="vertical-header-right"> <div v-if="layout === 'vertical'" class="vertical-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<Search id="header-search" /> <Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click"> <el-dropdown id="header-translation" trigger="click">
<globalization <globalization
@ -81,6 +79,10 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<!-- 全屏 -->
<FullScreen id="full-screen" />
<!-- 消息通知 -->
<Notice id="header-notice" />
<!-- 退出登录 --> <!-- 退出登录 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none"> <span class="el-dropdown-link navbar-bg-hover select-none">

92
src/layout/components/setting/index.vue

@ -13,13 +13,15 @@ import panel from "../panel/index.vue";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
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, useGlobal, debounce } 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 Segmented, { type OptionsType } from "@/components/ReSegmented";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import { useDark, useGlobal, debounce, isNumber } from "@pureadmin/utils";
import Check from "@iconify-icons/ep/check"; import Check from "@iconify-icons/ep/check";
import LeftArrow from "@iconify-icons/ri/arrow-left-s-line";
import RightArrow from "@iconify-icons/ri/arrow-right-s-line";
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 systemIcon from "@/assets/svg/system.svg?component"; import systemIcon from "@/assets/svg/system.svg?component";
@ -64,7 +66,8 @@ const settings = reactive({
showLogo: $storage.configure.showLogo, showLogo: $storage.configure.showLogo,
showModel: $storage.configure.showModel, showModel: $storage.configure.showModel,
hideFooter: $storage.configure.hideFooter, hideFooter: $storage.configure.hideFooter,
multiTagsCache: $storage.configure.multiTagsCache
multiTagsCache: $storage.configure.multiTagsCache,
stretch: $storage.configure.stretch
}); });
const getThemeColorStyle = computed(() => { const getThemeColorStyle = computed(() => {
@ -141,6 +144,30 @@ function setFalse(Doms): any {
}); });
} }
/** 页宽 */
const stretchTypeOptions: Array<OptionsType> = [
{
label: "固定",
tip: "紧凑页面,轻松找到所需信息",
value: "fixed"
},
{
label: "自定义",
tip: "最小1280、最大1600",
value: "custom"
}
];
const setStretch = value => {
settings.stretch = value;
storageConfigureChange("stretch", value);
};
const stretchTypeChange = ({ option }) => {
const { value } = option;
value === "custom" ? setStretch(1440) : setStretch(false);
};
/** 主题色 激活选择项 */ /** 主题色 激活选择项 */
const getThemeColor = computed(() => { const getThemeColor = computed(() => {
return current => { return current => {
@ -160,6 +187,10 @@ const getThemeColor = computed(() => {
}; };
}); });
const pClass = computed(() => {
return ["mb-[12px]", "font-medium", "text-sm", "dark:text-white"];
});
const themeOptions = computed<Array<OptionsType>>(() => { const themeOptions = computed<Array<OptionsType>>(() => {
return [ return [
{ {
@ -277,8 +308,8 @@ onUnmounted(() => removeMatchMedia);
<template> <template>
<panel> <panel>
<div class="p-6">
<p class="mb-3 font-medium text-sm dark:text-white">整体风格</p>
<div class="p-5">
<p :class="pClass">整体风格</p>
<Segmented <Segmented
class="select-none" class="select-none"
:modelValue="overallStyle === 'system' ? 2 : dataTheme ? 1 : 0" :modelValue="overallStyle === 'system' ? 2 : dataTheme ? 1 : 0"
@ -295,7 +326,7 @@ onUnmounted(() => removeMatchMedia);
" "
/> />
<p class="mt-5 mb-3 font-medium text-sm dark:text-white">主题色</p>
<p :class="['mt-5', pClass]">主题色</p>
<ul class="theme-color"> <ul class="theme-color">
<li <li
v-for="(item, index) in themeColors" v-for="(item, index) in themeColors"
@ -314,7 +345,7 @@ onUnmounted(() => removeMatchMedia);
</li> </li>
</ul> </ul>
<p class="mt-5 mb-3 font-medium text-sm dark:text-white">导航模式</p>
<p :class="['mt-5', pClass]">导航模式</p>
<ul class="pure-theme"> <ul class="pure-theme">
<li <li
ref="verticalRef" ref="verticalRef"
@ -356,7 +387,50 @@ onUnmounted(() => removeMatchMedia);
</li> </li>
</ul> </ul>
<p class="mt-5 mb-3 font-medium text-base dark:text-white">页签风格</p>
<span v-if="device !== 'mobile'">
<p :class="['mt-5', pClass]">页宽</p>
<Segmented
class="mb-2 select-none"
:modelValue="isNumber(settings.stretch) ? 1 : 0"
:options="stretchTypeOptions"
@change="stretchTypeChange"
/>
<el-input-number
v-if="isNumber(settings.stretch)"
v-model="settings.stretch as number"
:min="1280"
:max="1600"
controls-position="right"
@change="value => setStretch(value)"
/>
<button
v-else
v-ripple="{ class: 'text-gray-300' }"
class="bg-transparent flex-c w-full h-20 rounded-md border border-gray-100"
@click="setStretch(!settings.stretch)"
>
<div
class="flex-bc transition-all duration-300"
:class="[settings.stretch ? 'w-[24%]' : 'w-[50%]']"
style="color: var(--el-color-primary)"
>
<IconifyIconOffline
:icon="settings.stretch ? RightArrow : LeftArrow"
height="20"
/>
<div
class="flex-grow border-b border-dashed"
style="border-color: var(--el-color-primary)"
/>
<IconifyIconOffline
:icon="settings.stretch ? LeftArrow : RightArrow"
height="20"
/>
</div>
</button>
</span>
<p :class="['mt-4', pClass]">页签风格</p>
<Segmented <Segmented
class="select-none" class="select-none"
:modelValue="markValue === 'smart' ? 0 : 1" :modelValue="markValue === 'smart' ? 0 : 1"
@ -364,7 +438,7 @@ onUnmounted(() => removeMatchMedia);
@change="onChange" @change="onChange"
/> />
<p class="mt-5 mb-1 font-medium text-sm dark:text-white">界面显示</p>
<p class="mt-5 font-medium text-sm dark:text-white">界面显示</p>
<ul class="setting"> <ul class="setting">
<li> <li>
<span class="dark:text-white">灰色模式</span> <span class="dark:text-white">灰色模式</span>
@ -543,7 +617,7 @@ onUnmounted(() => removeMatchMedia);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 4px 0;
padding: 3px 0;
font-size: 14px; font-size: 14px;
} }
} }

70
src/layout/components/sidebar/centerCollapse.vue

@ -0,0 +1,70 @@
<script setup lang="ts">
import { computed } from "vue";
import { useGlobal } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import ArrowLeft from "@iconify-icons/ri/arrow-left-double-fill";
interface Props {
isActive: boolean;
}
const props = withDefaults(defineProps<Props>(), {
isActive: false
});
const { tooltipEffect } = useNav();
const iconClass = computed(() => {
return ["w-[16px]", "h-[16px]"];
});
const { $storage } = useGlobal<GlobalPropertiesApi>();
const themeColor = computed(() => $storage.layout?.themeColor);
const emit = defineEmits<{
(e: "toggleClick"): void;
}>();
const toggleClick = () => {
emit("toggleClick");
};
</script>
<template>
<div
v-tippy="{
content: props.isActive ? '点击折叠' : '点击展开',
theme: tooltipEffect,
hideOnClick: 'toggle',
placement: 'right'
}"
class="center-collapse"
@click="toggleClick"
>
<IconifyIconOffline
:icon="ArrowLeft"
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
/>
</div>
</template>
<style lang="scss" scoped>
.center-collapse {
position: absolute;
top: 50%;
right: 2px;
z-index: 1002;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 34px;
cursor: pointer;
background: var(--el-bg-color);
border: 1px solid var(--pure-border-color);
border-radius: 4px;
transform: translate(12px, -50%);
}
</style>

30
src/layout/components/sidebar/fullScreen.vue

@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useNav } from "@/layout/hooks/useNav";
const screenIcon = ref();
const { toggle, isFullscreen, Fullscreen, ExitFullscreen } = useNav();
isFullscreen.value = !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
watch(
isFullscreen,
full => {
screenIcon.value = full ? ExitFullscreen : Fullscreen;
},
{
immediate: true
}
);
</script>
<template>
<span class="fullscreen-icon navbar-bg-hover" @click="toggle">
<IconifyIconOffline :icon="screenIcon" />
</span>
</template>

7
src/layout/components/sidebar/horizontal.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Search from "../search/index.vue"; import Search from "../search/index.vue";
import Notice from "../notice/index.vue"; import Notice from "../notice/index.vue";
import FullScreen from "./fullScreen.vue";
import SidebarItem from "./sidebarItem.vue"; import SidebarItem from "./sidebarItem.vue";
import { isAllEmpty } from "@pureadmin/utils"; import { isAllEmpty } from "@pureadmin/utils";
import { ref, nextTick, computed } from "vue"; import { ref, nextTick, computed } from "vue";
@ -65,8 +66,6 @@ nextTick(() => {
<div class="horizontal-header-right"> <div class="horizontal-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<Search id="header-search" /> <Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click"> <el-dropdown id="header-translation" trigger="click">
<globalization <globalization
@ -97,6 +96,10 @@ nextTick(() => {
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<!-- 全屏 -->
<FullScreen id="full-screen" />
<!-- 消息通知 -->
<Notice id="header-notice" />
<!-- 退出登录 --> <!-- 退出登录 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover"> <span class="el-dropdown-link navbar-bg-hover">

4
src/layout/components/sidebar/leftCollapse.vue

@ -41,7 +41,7 @@ const toggleClick = () => {
</script> </script>
<template> <template>
<div class="collapse-container">
<div class="left-collapse">
<IconifyIconOffline <IconifyIconOffline
v-tippy="{ v-tippy="{
content: props.isActive ? '点击折叠' : '点击展开', content: props.isActive ? '点击折叠' : '点击展开',
@ -58,7 +58,7 @@ const toggleClick = () => {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.collapse-container {
.left-collapse {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 100%; width: 100%;

7
src/layout/components/sidebar/mixNav.vue

@ -2,6 +2,7 @@
import extraIcon from "./extraIcon.vue"; import extraIcon from "./extraIcon.vue";
import Search from "../search/index.vue"; import Search from "../search/index.vue";
import Notice from "../notice/index.vue"; import Notice from "../notice/index.vue";
import FullScreen from "./fullScreen.vue";
import { isAllEmpty } from "@pureadmin/utils"; import { isAllEmpty } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n"; import { transformI18n } from "@/plugins/i18n";
@ -98,8 +99,6 @@ watch(
<div class="horizontal-header-right"> <div class="horizontal-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<Search id="header-search" /> <Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 --> <!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click"> <el-dropdown id="header-translation" trigger="click">
<globalization <globalization
@ -130,6 +129,10 @@ watch(
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<!-- 全屏 -->
<FullScreen id="full-screen" />
<!-- 消息通知 -->
<Notice id="header-notice" />
<!-- 退出登录 --> <!-- 退出登录 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none"> <span class="el-dropdown-link navbar-bg-hover select-none">

13
src/layout/components/sidebar/vertical.vue

@ -3,8 +3,9 @@ import Logo from "./logo.vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import SidebarItem from "./sidebarItem.vue"; import SidebarItem from "./sidebarItem.vue";
import leftCollapse from "./leftCollapse.vue";
import LeftCollapse from "./leftCollapse.vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import CenterCollapse from "./centerCollapse.vue";
import { responsiveStorageNameSpace } from "@/config"; import { responsiveStorageNameSpace } from "@/config";
import { storageLocal, isAllEmpty } from "@pureadmin/utils"; import { storageLocal, isAllEmpty } from "@pureadmin/utils";
import { findRouteByPath, getParentPaths } from "@/router/utils"; import { findRouteByPath, getParentPaths } from "@/router/utils";
@ -12,6 +13,7 @@ import { usePermissionStoreHook } from "@/store/modules/permission";
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue"; import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
const route = useRoute(); const route = useRoute();
const isShow = ref(false);
const showLogo = ref( const showLogo = ref(
storageLocal().getItem<StorageConfigs>( storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure` `${responsiveStorageNameSpace()}configure`
@ -88,6 +90,8 @@ onBeforeUnmount(() => {
<div <div
v-loading="loading" v-loading="loading"
:class="['sidebar-container', showLogo ? 'has-logo' : 'no-logo']" :class="['sidebar-container', showLogo ? 'has-logo' : 'no-logo']"
@mouseenter.prevent="isShow = true"
@mouseleave.prevent="isShow = false"
> >
<Logo v-if="showLogo" :collapse="isCollapse" /> <Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar <el-scrollbar
@ -114,7 +118,12 @@ onBeforeUnmount(() => {
/> />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
<leftCollapse
<CenterCollapse
v-if="device !== 'mobile' && (isShow || isCollapse)"
:is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar"
/>
<LeftCollapse
v-if="device !== 'mobile'" v-if="device !== 'mobile'"
:is-active="pureApp.sidebar.opened" :is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar" @toggleClick="toggleSideBar"

29
src/layout/components/tag/index.vue

@ -4,7 +4,7 @@ import { emitter } from "@/utils/mitt";
import { RouteConfigs } from "../../types"; import { RouteConfigs } from "../../types";
import { useTags } from "../../hooks/useTag"; import { useTags } from "../../hooks/useTag";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
import { useFullscreen, onClickOutside } from "@vueuse/core";
import { onClickOutside } from "@vueuse/core";
import { handleAliveRoute, getTopMenu } from "@/router/utils"; import { handleAliveRoute, getTopMenu } from "@/router/utils";
import { useSettingStoreHook } from "@/store/modules/settings"; import { useSettingStoreHook } from "@/store/modules/settings";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
@ -59,7 +59,6 @@ const contextmenuRef = ref();
const isShowArrow = ref(false); const isShowArrow = ref(false);
const topPath = getTopMenu()?.path; const topPath = getTopMenu()?.path;
const { VITE_HIDE_HOME } = import.meta.env; const { VITE_HIDE_HOME } = import.meta.env;
const { isFullscreen, toggle } = useFullscreen();
const dynamicTagView = async () => { const dynamicTagView = async () => {
await nextTick(); await nextTick();
@ -329,28 +328,15 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
handleAliveRoute(route as ToRouteType); handleAliveRoute(route as ToRouteType);
break; break;
case 6: case 6:
//
toggle();
setTimeout(() => {
if (isFullscreen.value) {
tagsViews[6].icon = ExitFullscreen;
tagsViews[6].text = $t("buttons.hswholeExitFullScreen");
} else {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.hswholeFullScreen");
}
}, 100);
break;
case 7:
// //
onContentFullScreen(); onContentFullScreen();
setTimeout(() => { setTimeout(() => {
if (pureSetting.hiddenSideBar) { if (pureSetting.hiddenSideBar) {
tagsViews[7].icon = ExitFullscreen;
tagsViews[7].text = $t("buttons.hscontentExitFullScreen");
tagsViews[6].icon = ExitFullscreen;
tagsViews[6].text = $t("buttons.hscontentExitFullScreen");
} else { } else {
tagsViews[7].icon = Fullscreen;
tagsViews[7].text = $t("buttons.hscontentFullScreen");
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.hscontentFullScreen");
} }
}, 100); }, 100);
break; break;
@ -511,11 +497,6 @@ watch(route, () => {
dynamicTagView(); dynamicTagView();
}); });
watch(isFullscreen, () => {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.hswholeFullScreen");
});
onMounted(() => { onMounted(() => {
if (!instance) return; if (!instance) return;

3
src/layout/hooks/useLayout.ts

@ -41,7 +41,8 @@ export function useLayout() {
hideFooter: $config.HideFooter ?? true, hideFooter: $config.HideFooter ?? true,
showLogo: $config?.ShowLogo ?? true, showLogo: $config?.ShowLogo ?? true,
showModel: $config?.ShowModel ?? "smart", showModel: $config?.ShowModel ?? "smart",
multiTagsCache: $config?.MultiTagsCache ?? false
multiTagsCache: $config?.MultiTagsCache ?? false,
stretch: $config?.Stretch ?? false
}; };
} }
}; };

8
src/layout/hooks/useNav.ts

@ -4,6 +4,7 @@ import { useRouter } from "vue-router";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import userAvatar from "@/assets/user.jpg"; import userAvatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils"; import { getTopMenu } from "@/router/utils";
import { useFullscreen } from "@vueuse/core";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import type { routeMetaType } from "../types"; import type { routeMetaType } from "../types";
import { transformI18n } from "@/plugins/i18n"; import { transformI18n } from "@/plugins/i18n";
@ -13,12 +14,15 @@ import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
const errorInfo = "当前路由配置不正确,请检查配置"; const errorInfo = "当前路由配置不正确,请检查配置";
export function useNav() { export function useNav() {
const pureApp = useAppStoreHook(); const pureApp = useAppStoreHook();
const routers = useRouter().options.routes; const routers = useRouter().options.routes;
const { isFullscreen, toggle } = useFullscreen();
const { wholeMenus } = storeToRefs(usePermissionStoreHook()); const { wholeMenus } = storeToRefs(usePermissionStoreHook());
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */ /** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
const tooltipEffect = getConfig()?.TooltipEffect ?? "light"; const tooltipEffect = getConfig()?.TooltipEffect ?? "light";
@ -136,6 +140,10 @@ export function useNav() {
logout, logout,
routers, routers,
$storage, $storage,
isFullscreen,
Fullscreen,
ExitFullscreen,
toggle,
backTopMenu, backTopMenu,
onPanel, onPanel,
getDivStyle, getDivStyle,

9
src/layout/hooks/useTag.ts

@ -106,15 +106,8 @@ export function useTags() {
}, },
{ {
icon: Fullscreen, icon: Fullscreen,
text: $t("buttons.hswholeFullScreen"),
divided: true,
disabled: false,
show: true
},
{
icon: Fullscreen,
text: $t("buttons.hscontentFullScreen"), text: $t("buttons.hscontentFullScreen"),
divided: false,
divided: true,
disabled: false, disabled: false,
show: true show: true
} }

1
src/style/login.css

@ -31,6 +31,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
text-align: center; text-align: center;
overflow: hidden;
} }
.login-form { .login-form {

1
src/style/reset.scss

@ -194,7 +194,6 @@ button,
cursor: default; cursor: default;
} }
img,
svg, svg,
video, video,
canvas, canvas,

17
src/style/sidebar.scss

@ -35,7 +35,8 @@
} }
} }
.set-icon {
.set-icon,
.fullscreen-icon {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -91,7 +92,7 @@
z-index: 1001; z-index: 1001;
width: $sideBarWidth !important; width: $sideBarWidth !important;
height: 100%; height: 100%;
overflow: hidden;
overflow: visible;
font-size: 0; font-size: 0;
background: $menuBg; background: $menuBg;
border-right: 1px solid var(--pure-border-color); border-right: 1px solid var(--pure-border-color);
@ -460,10 +461,12 @@
/* 搜索 */ /* 搜索 */
.search-container, .search-container,
/* 告警 */
.dropdown-badge,
/* 国际化 */ /* 国际化 */
.globalization, .globalization,
/* 全屏 */
.fullscreen-icon,
/* 消息通知 */
.dropdown-badge,
/* 用户名 */ /* 用户名 */
.el-dropdown-link, .el-dropdown-link,
/* 设置 */ /* 设置 */
@ -642,10 +645,12 @@ body[layout="vertical"] {
/* 搜索 */ /* 搜索 */
.search-container, .search-container,
/* 告警 */
.dropdown-badge,
/* 国际化 */ /* 国际化 */
.globalization, .globalization,
/* 全屏 */
.fullscreen-icon,
/* 消息通知 */
.dropdown-badge,
/* 用户名 */ /* 用户名 */
.el-dropdown-link, .el-dropdown-link,
/* 设置 */ /* 设置 */

3
src/utils/responsive.ts

@ -30,7 +30,8 @@ export const injectResponsiveStorage = (app: App, config: PlatformConfigs) => {
hideFooter: config.HideFooter ?? true, hideFooter: config.HideFooter ?? true,
showLogo: config.ShowLogo ?? true, showLogo: config.ShowLogo ?? true,
showModel: config.ShowModel ?? "smart", showModel: config.ShowModel ?? "smart",
multiTagsCache: config.MultiTagsCache ?? false
multiTagsCache: config.MultiTagsCache ?? false,
stretch: config.Stretch ?? false
} }
}, },
config.MultiTagsCache config.MultiTagsCache

7
tailwind.config.js → tailwind.config.ts

@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
import type { Config } from "tailwindcss";
export default {
darkMode: "class", darkMode: "class",
corePlugins: { corePlugins: {
preflight: false preflight: false
@ -15,4 +16,4 @@ module.exports = {
} }
} }
} }
};
} satisfies Config;

11
types/global.d.ts

@ -39,6 +39,15 @@ declare global {
} }
/** /**
* Document
*/
interface Document {
webkitFullscreenElement?: Element;
mozFullScreenElement?: Element;
msFullscreenElement?: Element;
}
/**
* *
*/ */
type ViteCompression = type ViteCompression =
@ -88,6 +97,7 @@ declare global {
Weak?: boolean; Weak?: boolean;
HideTabs?: boolean; HideTabs?: boolean;
HideFooter?: boolean; HideFooter?: boolean;
Stretch?: boolean | number;
SidebarStatus?: boolean; SidebarStatus?: boolean;
EpThemeColor?: string; EpThemeColor?: string;
ShowLogo?: boolean; ShowLogo?: boolean;
@ -152,6 +162,7 @@ declare global {
showLogo?: boolean; showLogo?: boolean;
showModel?: string; showModel?: string;
multiTagsCache?: boolean; multiTagsCache?: boolean;
stretch?: boolean | number;
}; };
tags?: Array<any>; tags?: Array<any>;
} }

Loading…
Cancel
Save