Browse Source

Merge pull request #20 from pure-admin/release/i18n5.2.0

release: update `5.2.0`
i18n
xiaoming 8 months ago
committed by GitHub
parent
commit
a5752a0493
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .eslintignore
  2. 8
      .lintstagedrc
  3. 20
      README.en-US.md
  4. 16
      README.md
  5. 2
      build/info.ts
  6. 44
      package.json
  7. 2205
      pnpm-lock.yaml
  8. 3
      public/platform-config.json
  9. 17
      src/components/ReDialog/index.vue
  10. 15
      src/components/ReDialog/type.ts
  11. 16
      src/components/ReSegmented/src/index.css
  12. 24
      src/components/ReSegmented/src/index.tsx
  13. 25
      src/layout/components/appMain.vue
  14. 1
      src/layout/components/keepAliveFrame/index.vue
  15. 8
      src/layout/components/navbar.vue
  16. 92
      src/layout/components/setting/index.vue
  17. 70
      src/layout/components/sidebar/centerCollapse.vue
  18. 30
      src/layout/components/sidebar/fullScreen.vue
  19. 7
      src/layout/components/sidebar/horizontal.vue
  20. 4
      src/layout/components/sidebar/leftCollapse.vue
  21. 7
      src/layout/components/sidebar/mixNav.vue
  22. 13
      src/layout/components/sidebar/vertical.vue
  23. 29
      src/layout/components/tag/index.vue
  24. 3
      src/layout/hooks/useLayout.ts
  25. 8
      src/layout/hooks/useNav.ts
  26. 9
      src/layout/hooks/useTag.ts
  27. 1
      src/style/login.css
  28. 1
      src/style/reset.scss
  29. 17
      src/style/sidebar.scss
  30. 3
      src/utils/responsive.ts
  31. 7
      tailwind.config.ts
  32. 11
      types/global.d.ts

2
.eslintignore

@ -7,5 +7,5 @@ eslint.config.js
.prettierrc.js
commitlint.config.js
postcss.config.js
tailwind.config.js
tailwind.config.ts
stylelint.config.js

8
.lintstagedrc

@ -7,10 +7,14 @@
"prettier --cache --write--parser json"
],
"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}": [
"prettier --cache --ignore-unknown --write",
"stylelint --fix"
"stylelint --fix --allow-empty-input"
],
"*.md": ["prettier --cache --ignore-unknown --write"]
}

20
README.en-US.md

@ -8,18 +8,24 @@
The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb`
## Supporting Video
## Supporting video
- [Click Watch Tutorial](https://www.bilibili.com/video/BV1kg411v7QT)
- [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
[Click me to view UI design](https://www.bilibili.com/video/BV17g411T7rq)
[Click me to view the rapid development tutorial](https://www.bilibili.com/video/BV1kg411v7QT)
[Click me to view all pages and function demonstrations of vue-pure-admin](https://www.bilibili.com/video/BV1Rx4y1U7Mv)
## Docs
## Nanny-level documents
- [documentation site](https://yiming_chang.gitee.io/pure-admin-doc)
[Click me to view vue-pure-admin documentation](https://yiming_chang.gitee.io/pure-admin-doc)
[Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app)
## Quality service, software outsourcing, sponsorship support
[Click me to view details](https://yiming_chang.gitee.io/pure-admin-doc/pages/service/)
## Preview
- [Click me to view the preview station](https://pure-admin-thin.netlify.app/#/login)
[Click me to view the preview station](https://pure-admin-thin.netlify.app/#/login)
## Maintainer
@ -27,7 +33,7 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
## ⚠️ Attention
- The Lite version does not accept any issues and prs. If you have any questions, please go to the full version [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) to mention, thank you!
The Lite version does not accept any issues and prs. If you have any questions, please go to the full version [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) to mention, thank you!
## License

16
README.md

@ -14,16 +14,22 @@
## 配套视频
- [点我查看教程](https://www.bilibili.com/video/BV1kg411v7QT)
- [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
[点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
[点我查看快速开发教程](https://www.bilibili.com/video/BV1kg411v7QT)
[点我查看 vue-pure-admin 的所有页面、功能演示](https://www.bilibili.com/video/BV1Rx4y1U7Mv)
## 配套保姆级文档
- [查看文档](https://yiming_chang.gitee.io/pure-admin-doc)
[点我查看 vue-pure-admin 文档](https://yiming_chang.gitee.io/pure-admin-doc)
[点我查看 @pureadmin/utils 文档](https://pure-admin-utils.netlify.app)
## 优质服务、软件外包、赞助支持
[点我查看详情](https://yiming_chang.gitee.io/pure-admin-doc/pages/service/)
## 预览
- [查看预览](https://pure-admin-thin.netlify.app/#/login)
[查看预览](https://pure-admin-thin.netlify.app/#/login)
## 维护者
@ -31,7 +37,7 @@
## ⚠️ 注意
- 精简版不接受任何 `issues``pr`,如果有问题请到完整版 [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) 去提,谢谢!
精简版不接受任何 `issues``pr`,如果有问题请到完整版 [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) 去提,谢谢!
## 许可证

2
build/info.ts

@ -7,7 +7,7 @@ import boxen, { type Options as BoxenOptions } from "boxen";
dayjs.extend(duration);
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 = {

44
package.json

@ -1,6 +1,6 @@
{
"name": "pure-admin-thin",
"version": "5.1.0",
"version": "5.2.0",
"private": true,
"type": "module",
"scripts": {
@ -48,16 +48,16 @@
"url": "https://github.com/xiaoxian521"
},
"dependencies": {
"@pureadmin/descriptions": "^1.2.0",
"@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.1.2",
"@pureadmin/utils": "^2.4.5",
"@pureadmin/utils": "^2.4.7",
"@vueuse/core": "^10.9.0",
"@vueuse/motion": "^2.1.0",
"animate.css": "^4.1.1",
"axios": "^1.6.7",
"axios": "^1.6.8",
"dayjs": "^1.11.10",
"echarts": "^5.5.0",
"element-plus": "^2.6.0",
"element-plus": "^2.6.1",
"js-cookie": "^3.0.5",
"localforage": "^1.10.0",
"mitt": "^3.0.1",
@ -65,19 +65,19 @@
"path": "^0.12.7",
"pinia": "^2.1.7",
"pinyin-pro": "^3.19.6",
"qs": "^6.11.2",
"qs": "^6.12.0",
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.2",
"vue": "^3.4.21",
"vue-i18n": "^9.10.1",
"vue-i18n": "^9.10.2",
"vue-router": "^4.3.0",
"vue-tippy": "^6.4.1",
"vue-types": "^5.1.1"
},
"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",
"@faker-js/faker": "^8.4.1",
"@iconify-icons/ep": "^1.2.12",
@ -87,34 +87,34 @@
"@pureadmin/theme": "^3.2.0",
"@types/gradient-string": "^1.1.5",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.11.24",
"@types/node": "^20.11.30",
"@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.12",
"@types/qs": "^6.9.14",
"@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-jsx": "^3.1.0",
"autoprefixer": "^10.4.18",
"autoprefixer": "^10.4.19",
"boxen": "^7.1.1",
"cloc": "^2.11.0",
"cssnano": "^6.0.5",
"cssnano": "^6.1.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.22.0",
"eslint-plugin-vue": "^9.23.0",
"gradient-string": "^2.0.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"postcss": "^8.4.38",
"postcss-html": "^1.6.0",
"postcss-import": "^16.0.1",
"postcss-import": "^16.1.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.71.1",
"sass": "^1.72.0",
"stylelint": "^16.2.1",
"stylelint-config-recess-order": "^5.0.0",
"stylelint-config-recommended-vue": "^1.5.0",
@ -122,8 +122,8 @@
"stylelint-prettier": "^5.0.0",
"svgo": "^3.2.0",
"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-compression": "^0.5.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",
"FixedHeader": true,
"HiddenSideBar": false,
@ -14,6 +14,7 @@
"Weak": false,
"HideTabs": false,
"HideFooter": false,
"Stretch": false,
"SidebarStatus": true,
"EpThemeColor": "#409EFF",
"ShowLogo": true,

17
src/components/ReDialog/index.vue

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

15
src/components/ReDialog/type.ts

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

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

@ -8,6 +8,21 @@
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 {
position: relative;
display: flex;
@ -67,6 +82,7 @@
.pure-segmented-item-label {
display: flex;
align-items: center;
justify-content: center;
}
.pure-segmented-item-icon svg {

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

@ -10,7 +10,12 @@ import {
} from "vue";
import type { OptionsType } from "./type";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { isFunction, isNumber, useDark } from "@pureadmin/utils";
import {
isFunction,
isNumber,
useDark,
useResizeObserver
} from "@pureadmin/utils";
const props = {
options: {
@ -22,6 +27,11 @@ const props = {
type: undefined,
require: false,
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(
() => curIndex.value,
index => {
@ -148,7 +166,9 @@ export default defineComponent({
};
return () => (
<div class="pure-segmented">
<div
class={["pure-segmented", props.block ? "pure-segmented-block" : ""]}
>
<div class="pure-segmented-group">
<div
class="pure-segmented-item-selected"

25
src/layout/components/appMain.vue

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

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

@ -63,7 +63,6 @@ watch(
}
);
</script>
<template>
<template v-for="[fullPath, Comp] in compList" :key="fullPath">
<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 mixNav from "./sidebar/mixNav.vue";
import { useNav } from "@/layout/hooks/useNav";
import FullScreen from "./sidebar/fullScreen.vue";
import Breadcrumb from "./sidebar/breadCrumb.vue";
import topCollapse from "./sidebar/topCollapse.vue";
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 Setting from "@iconify-icons/ri/settings-3-line";
import Check from "@iconify-icons/ep/check";
const {
layout,
device,
@ -47,8 +47,6 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<div v-if="layout === 'vertical'" class="vertical-header-right">
<!-- 菜单搜索 -->
<Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<globalization
@ -81,6 +79,10 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 -->
<FullScreen id="full-screen" />
<!-- 消息通知 -->
<Notice id="header-notice" />
<!-- 退出登录 -->
<el-dropdown trigger="click">
<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 { useNav } from "@/layout/hooks/useNav";
import { useAppStoreHook } from "@/store/modules/app";
import { useDark, useGlobal, debounce } 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, useGlobal, debounce, isNumber } from "@pureadmin/utils";
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 darkIcon from "@/assets/svg/dark.svg?component";
import systemIcon from "@/assets/svg/system.svg?component";
@ -64,7 +66,8 @@ const settings = reactive({
showLogo: $storage.configure.showLogo,
showModel: $storage.configure.showModel,
hideFooter: $storage.configure.hideFooter,
multiTagsCache: $storage.configure.multiTagsCache
multiTagsCache: $storage.configure.multiTagsCache,
stretch: $storage.configure.stretch
});
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(() => {
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>>(() => {
return [
{
@ -277,8 +308,8 @@ onUnmounted(() => removeMatchMedia);
<template>
<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
class="select-none"
: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">
<li
v-for="(item, index) in themeColors"
@ -314,7 +345,7 @@ onUnmounted(() => removeMatchMedia);
</li>
</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">
<li
ref="verticalRef"
@ -356,7 +387,50 @@ onUnmounted(() => removeMatchMedia);
</li>
</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
class="select-none"
:modelValue="markValue === 'smart' ? 0 : 1"
@ -364,7 +438,7 @@ onUnmounted(() => removeMatchMedia);
@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">
<li>
<span class="dark:text-white">灰色模式</span>
@ -543,7 +617,7 @@ onUnmounted(() => removeMatchMedia);
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 0;
padding: 3px 0;
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">
import Search from "../search/index.vue";
import Notice from "../notice/index.vue";
import FullScreen from "./fullScreen.vue";
import SidebarItem from "./sidebarItem.vue";
import { isAllEmpty } from "@pureadmin/utils";
import { ref, nextTick, computed } from "vue";
@ -65,8 +66,6 @@ nextTick(() => {
<div class="horizontal-header-right">
<!-- 菜单搜索 -->
<Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<globalization
@ -97,6 +96,10 @@ nextTick(() => {
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 -->
<FullScreen id="full-screen" />
<!-- 消息通知 -->
<Notice id="header-notice" />
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover">

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

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

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

@ -2,6 +2,7 @@
import extraIcon from "./extraIcon.vue";
import Search from "../search/index.vue";
import Notice from "../notice/index.vue";
import FullScreen from "./fullScreen.vue";
import { isAllEmpty } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
@ -98,8 +99,6 @@ watch(
<div class="horizontal-header-right">
<!-- 菜单搜索 -->
<Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<globalization
@ -130,6 +129,10 @@ watch(
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 -->
<FullScreen id="full-screen" />
<!-- 消息通知 -->
<Notice id="header-notice" />
<!-- 退出登录 -->
<el-dropdown trigger="click">
<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 { emitter } from "@/utils/mitt";
import SidebarItem from "./sidebarItem.vue";
import leftCollapse from "./leftCollapse.vue";
import LeftCollapse from "./leftCollapse.vue";
import { useNav } from "@/layout/hooks/useNav";
import CenterCollapse from "./centerCollapse.vue";
import { responsiveStorageNameSpace } from "@/config";
import { storageLocal, isAllEmpty } from "@pureadmin/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";
const route = useRoute();
const isShow = ref(false);
const showLogo = ref(
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
@ -88,6 +90,8 @@ onBeforeUnmount(() => {
<div
v-loading="loading"
:class="['sidebar-container', showLogo ? 'has-logo' : 'no-logo']"
@mouseenter.prevent="isShow = true"
@mouseleave.prevent="isShow = false"
>
<Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar
@ -114,7 +118,12 @@ onBeforeUnmount(() => {
/>
</el-menu>
</el-scrollbar>
<leftCollapse
<CenterCollapse
v-if="device !== 'mobile' && (isShow || isCollapse)"
:is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar"
/>
<LeftCollapse
v-if="device !== 'mobile'"
:is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar"

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

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

3
src/layout/hooks/useLayout.ts

@ -41,7 +41,8 @@ export function useLayout() {
hideFooter: $config.HideFooter ?? true,
showLogo: $config?.ShowLogo ?? true,
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 userAvatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils";
import { useFullscreen } from "@vueuse/core";
import { useGlobal } from "@pureadmin/utils";
import type { routeMetaType } from "../types";
import { transformI18n } from "@/plugins/i18n";
@ -13,12 +14,15 @@ import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
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 = "当前路由配置不正确,请检查配置";
export function useNav() {
const pureApp = useAppStoreHook();
const routers = useRouter().options.routes;
const { isFullscreen, toggle } = useFullscreen();
const { wholeMenus } = storeToRefs(usePermissionStoreHook());
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
const tooltipEffect = getConfig()?.TooltipEffect ?? "light";
@ -136,6 +140,10 @@ export function useNav() {
logout,
routers,
$storage,
isFullscreen,
Fullscreen,
ExitFullscreen,
toggle,
backTopMenu,
onPanel,
getDivStyle,

9
src/layout/hooks/useTag.ts

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

1
src/style/login.css

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

1
src/style/reset.scss

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

17
src/style/sidebar.scss

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

3
src/utils/responsive.ts

@ -30,7 +30,8 @@ export const injectResponsiveStorage = (app: App, config: PlatformConfigs) => {
hideFooter: config.HideFooter ?? true,
showLogo: config.ShowLogo ?? true,
showModel: config.ShowModel ?? "smart",
multiTagsCache: config.MultiTagsCache ?? false
multiTagsCache: config.MultiTagsCache ?? false,
stretch: config.Stretch ?? false
}
},
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",
corePlugins: {
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 =
@ -88,6 +97,7 @@ declare global {
Weak?: boolean;
HideTabs?: boolean;
HideFooter?: boolean;
Stretch?: boolean | number;
SidebarStatus?: boolean;
EpThemeColor?: string;
ShowLogo?: boolean;
@ -152,6 +162,7 @@ declare global {
showLogo?: boolean;
showModel?: string;
multiTagsCache?: boolean;
stretch?: boolean | number;
};
tags?: Array<any>;
}

Loading…
Cancel
Save