release: update 5.5.0

This commit is contained in:
xiaoxian521 2024-05-10 12:09:23 +08:00
parent 923fdd9ff1
commit 0d8f6b314b
59 changed files with 875 additions and 932 deletions

View File

@ -15,12 +15,12 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
## Nanny-level documents ## Nanny-level documents
[Click me to view vue-pure-admin documentation](https://yiming_chang.gitee.io/pure-admin-doc) [Click me to view vue-pure-admin documentation](https://pure-admin.github.io/pure-admin-doc)
[Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app) [Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app)
## Quality service, software outsourcing, sponsorship support ## Quality service, software outsourcing, sponsorship support
[Click me to view details](https://yiming_chang.gitee.io/pure-admin-doc/pages/service/) [Click me to view details](https://pure-admin.github.io/pure-admin-doc/pages/service/)
## Preview ## Preview

View File

@ -19,12 +19,12 @@
## 配套保姆级文档 ## 配套保姆级文档
[点我查看 vue-pure-admin 文档](https://yiming_chang.gitee.io/pure-admin-doc) [点我查看 vue-pure-admin 文档](https://pure-admin.github.io/pure-admin-doc)
[点我查看 @pureadmin/utils 文档](https://pure-admin-utils.netlify.app) [点我查看 @pureadmin/utils 文档](https://pure-admin-utils.netlify.app)
## 优质服务、软件外包、赞助支持 ## 优质服务、软件外包、赞助支持
[点我查看详情](https://yiming_chang.gitee.io/pure-admin-doc/pages/service/) [点我查看详情](https://pure-admin.github.io/pure-admin-doc/pages/service/)
## 预览 ## 预览

View File

@ -3,7 +3,6 @@ import { Plugin as importToCDN } from "vite-plugin-cdn-import";
/** /**
* @description `cdn`使cdn模式 .env.production VITE_CDN true * @description `cdn`使cdn模式 .env.production VITE_CDN true
* cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com * cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
* mockjs不能用cdn模式引入mockjs使
* 使jscss文件cdn * 使jscss文件cdn
*/ */
export const cdn = importToCDN({ export const cdn = importToCDN({

View File

@ -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(
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://yiming_chang.gitee.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app` `您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.github.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app`
); );
const boxenOptions: BoxenOptions = { const boxenOptions: BoxenOptions = {

View File

@ -48,7 +48,7 @@ const __APP_INFO__ = {
}; };
/** 处理环境变量 */ /** 处理环境变量 */
const warpperEnv = (envConf: Recordable): ViteEnv => { const wrapperEnv = (envConf: Recordable): ViteEnv => {
// 默认值 // 默认值
const ret: ViteEnv = { const ret: ViteEnv = {
VITE_PORT: 8848, VITE_PORT: 8848,
@ -107,4 +107,4 @@ const getPackageSize = options => {
}); });
}; };
export { root, pathResolve, alias, __APP_INFO__, warpperEnv, getPackageSize }; export { root, pathResolve, alias, __APP_INFO__, wrapperEnv, getPackageSize };

View File

@ -1,6 +1,6 @@
{ {
"name": "pure-admin-thin", "name": "pure-admin-thin",
"version": "5.4.0", "version": "5.5.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -38,7 +38,7 @@
"url": "git+https://github.com/pure-admin/pure-admin-thin.git" "url": "git+https://github.com/pure-admin/pure-admin-thin.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/pure-admin/pure-admin-thin/issues" "url": "https://github.com/pure-admin/vue-pure-admin/issues"
}, },
"license": "MIT", "license": "MIT",
"author": { "author": {
@ -54,29 +54,29 @@
"@vueuse/motion": "^2.1.0", "@vueuse/motion": "^2.1.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"dayjs": "^1.11.10", "dayjs": "^1.11.11",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.7.1", "element-plus": "2.7.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",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinyin-pro": "^3.20.2", "pinyin-pro": "^3.20.4",
"qs": "^6.12.1", "qs": "^6.12.1",
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"vue": "^3.4.25", "vue": "^3.4.27",
"vue-router": "^4.3.2", "vue-router": "^4.3.2",
"vue-tippy": "^6.4.1", "vue-tippy": "^6.4.1",
"vue-types": "^5.1.1" "vue-types": "^5.1.2"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.3.0", "@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2", "@commitlint/config-conventional": "^19.2.2",
"@commitlint/types": "^19.0.3", "@commitlint/types": "^19.0.3",
"@eslint/js": "^9.1.1", "@eslint/js": "^9.2.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",
"@iconify-icons/ri": "^1.2.10", "@iconify-icons/ri": "^1.2.10",
@ -84,18 +84,18 @@
"@pureadmin/theme": "^3.2.0", "@pureadmin/theme": "^3.2.0",
"@types/gradient-string": "^1.1.6", "@types/gradient-string": "^1.1.6",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/node": "^20.12.7", "@types/node": "^20.12.11",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.15", "@types/qs": "^6.9.15",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.7.1", "@typescript-eslint/parser": "^7.8.0",
"@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.19", "autoprefixer": "^10.4.19",
"boxen": "^7.1.1", "boxen": "^7.1.1",
"cssnano": "^7.0.0", "cssnano": "^7.0.1",
"eslint": "^9.1.1", "eslint": "^9.2.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",
@ -104,22 +104,22 @@
"husky": "^9.0.11", "husky": "^9.0.11",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"postcss-html": "^1.6.0", "postcss-html": "^1.7.0",
"postcss-import": "^16.1.0", "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.75.0", "sass": "^1.77.0",
"stylelint": "^16.4.0", "stylelint": "^16.5.0",
"stylelint-config-recess-order": "^5.0.1", "stylelint-config-recess-order": "^5.0.1",
"stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard-scss": "^13.1.0", "stylelint-config-standard-scss": "^13.1.0",
"stylelint-prettier": "^5.0.0", "stylelint-prettier": "^5.0.0",
"svgo": "^3.2.0", "svgo": "^3.3.0",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.10", "vite": "^5.2.11",
"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",

1073
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"Version": "5.4.0", "Version": "5.5.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,

View File

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 161 B

After

Width:  |  Height:  |  Size: 161 B

View File

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

View File

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 840 B

View File

@ -29,9 +29,11 @@ const addDialog = (options: DialogOptions) => {
const closeDialog = (options: DialogOptions, index: number, args?: any) => { const closeDialog = (options: DialogOptions, index: number, args?: any) => {
dialogStore.value[index].visible = false; dialogStore.value[index].visible = false;
options.closeCallBack && options.closeCallBack({ options, index, args }); options.closeCallBack && options.closeCallBack({ options, index, args });
const closeDelay = options?.closeDelay ?? 200;
useTimeoutFn(() => { useTimeoutFn(() => {
dialogStore.value.splice(index, 1); dialogStore.value.splice(index, 1);
}, 200); }, closeDelay);
}; };
/** /**

View File

@ -4,7 +4,7 @@ import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
/** /**
* `iconfont` `svg` `iconify` * `iconfont` `svg` `iconify`
* @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/} * @see {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/}
* @param icon * @param icon
* @param attrs iconType * @param attrs iconType
* @returns Component * @returns Component

View File

@ -17,11 +17,11 @@ import {
getKeyList getKeyList
} from "@pureadmin/utils"; } from "@pureadmin/utils";
import DragIcon from "./svg/drag.svg?component"; import DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "./svg/expand.svg?component"; import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "./svg/refresh.svg?component"; import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
import SettingIcon from "./svg/settings.svg?component"; import SettingIcon from "@/assets/table-bar/settings.svg?component";
import CollapseIcon from "./svg/collapse.svg?component"; import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
const props = { const props = {
/** 头部最左边的标题 */ /** 头部最左边的标题 */

View File

@ -127,7 +127,9 @@ export default defineComponent({
} }
); );
watch(() => props.size, handleResizeInit); watch(() => props.size, handleResizeInit, {
immediate: true
});
const rendLabel = () => { const rendLabel = () => {
return props.options.map((option, index) => { return props.options.map((option, index) => {

View File

@ -6,7 +6,7 @@ export interface OptionsType {
label?: string | (() => VNode | Component); label?: string | (() => VNode | Component);
/** /**
* @description `useRenderIcon` * @description `useRenderIcon`
* @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://pure-admin.github.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;
/** 图标属性、样式配置 */ /** 图标属性、样式配置 */

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import Footer from "./footer/index.vue"; import LayFrame from "../lay-frame/index.vue";
import LayFooter from "../lay-footer/index.vue";
import { useGlobal, isNumber } from "@pureadmin/utils"; import { useGlobal, isNumber } from "@pureadmin/utils";
import KeepAliveFrame from "./keepAliveFrame/index.vue"; import BackTopIcon 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";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
@ -97,15 +97,15 @@ const transitionMain = defineComponent({
<template> <template>
<section <section
:class="[props.fixedHeader ? 'app-main' : 'app-main-nofixed-header']" :class="[fixedHeader ? 'app-main' : 'app-main-nofixed-header']"
:style="getSectionStyle" :style="getSectionStyle"
> >
<router-view> <router-view>
<template #default="{ Component, route }"> <template #default="{ Component, route }">
<KeepAliveFrame :currComp="Component" :currRoute="route"> <LayFrame :currComp="Component" :currRoute="route">
<template #default="{ Comp, fullPath, frameInfo }"> <template #default="{ Comp, fullPath, frameInfo }">
<el-scrollbar <el-scrollbar
v-if="props.fixedHeader" v-if="fixedHeader"
:wrap-style="{ :wrap-style="{
display: 'flex', display: 'flex',
'flex-wrap': 'wrap', 'flex-wrap': 'wrap',
@ -124,7 +124,7 @@ const transitionMain = defineComponent({
title="回到顶部" title="回到顶部"
target=".app-main .el-scrollbar__wrap" target=".app-main .el-scrollbar__wrap"
> >
<backTop /> <BackTopIcon />
</el-backtop> </el-backtop>
<div class="grow"> <div class="grow">
<transitionMain :route="route"> <transitionMain :route="route">
@ -148,7 +148,7 @@ const transitionMain = defineComponent({
/> />
</transitionMain> </transitionMain>
</div> </div>
<Footer v-if="!hideFooter" /> <LayFooter v-if="!hideFooter" />
</el-scrollbar> </el-scrollbar>
<div v-else class="grow"> <div v-else class="grow">
<transitionMain :route="route"> <transitionMain :route="route">
@ -173,12 +173,12 @@ const transitionMain = defineComponent({
</transitionMain> </transitionMain>
</div> </div>
</template> </template>
</KeepAliveFrame> </LayFrame>
</template> </template>
</router-view> </router-view>
<!-- 页脚 --> <!-- 页脚 -->
<Footer v-if="!hideFooter && !props.fixedHeader" /> <LayFooter v-if="!hideFooter && !fixedHeader" />
</section> </section>
</template> </template>

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import { useMultiFrame } from "@/layout/hooks/useMultiFrame";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { type Component, shallowRef, watch, computed } from "vue"; import { type Component, shallowRef, watch, computed } from "vue";
import { type RouteRecordRaw, RouteLocationNormalizedLoaded } from "vue-router"; import { type RouteRecordRaw, RouteLocationNormalizedLoaded } from "vue-router";
import { useMultiFrame } from "@/layout/components/keepAliveFrame/useMultiFrame";
const props = defineProps<{ const props = defineProps<{
currRoute: RouteLocationNormalizedLoaded; currRoute: RouteLocationNormalizedLoaded;
@ -20,7 +20,7 @@ const keep = computed(() => {
!!props.currRoute.meta?.frameSrc !!props.currRoute.meta?.frameSrc
); );
}); });
// frameView // LayFrame
const normalComp = computed(() => !keep.value && props.currComp); const normalComp = computed(() => !keep.value && props.currComp);
watch(useMultiTagsStoreHook().multiTags, (tags: any) => { watch(useMultiTagsStoreHook().multiTags, (tags: any) => {
@ -65,7 +65,7 @@ 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 === currRoute.fullPath" class="w-full h-full">
<slot <slot
:fullPath="fullPath" :fullPath="fullPath"
:Comp="Comp" :Comp="Comp"
@ -74,6 +74,6 @@ watch(
</div> </div>
</template> </template>
<div v-show="!keep" class="w-full h-full"> <div v-show="!keep" class="w-full h-full">
<slot :Comp="normalComp" :fullPath="props.currRoute.fullPath" frameInfo /> <slot :Comp="normalComp" :fullPath="currRoute.fullPath" frameInfo />
</div> </div>
</template> </template>

View File

@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
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 { useNav } from "@/layout/hooks/useNav";
import FullScreen from "./sidebar/fullScreen.vue"; import LaySearch from "../lay-search/index.vue";
import Breadcrumb from "./sidebar/breadCrumb.vue"; import LayNotice from "../lay-notice/index.vue";
import topCollapse from "./sidebar/topCollapse.vue"; import LayNavMix from "../lay-sidebar/NavMix.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.vue";
import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse.vue";
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";
@ -24,27 +25,27 @@ const {
<template> <template>
<div class="navbar bg-[#fff] shadow-sm shadow-[rgba(0,21,41,0.08)]"> <div class="navbar bg-[#fff] shadow-sm shadow-[rgba(0,21,41,0.08)]">
<topCollapse <LaySidebarTopCollapse
v-if="device === 'mobile'" v-if="device === 'mobile'"
class="hamburger-container" class="hamburger-container"
:is-active="pureApp.sidebar.opened" :is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar" @toggleClick="toggleSideBar"
/> />
<Breadcrumb <LaySidebarBreadCrumb
v-if="layout !== 'mix' && device !== 'mobile'" v-if="layout !== 'mix' && device !== 'mobile'"
class="breadcrumb-container" class="breadcrumb-container"
/> />
<mixNav v-if="layout === 'mix'" /> <LayNavMix v-if="layout === 'mix'" />
<div v-if="layout === 'vertical'" class="vertical-header-right"> <div v-if="layout === 'vertical'" class="vertical-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<Search id="header-search" /> <LaySearch id="header-search" />
<!-- 全屏 --> <!-- 全屏 -->
<FullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
<Notice id="header-notice" /> <LayNotice 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">

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ListItem } from "./data"; import { ListItem } from "../data";
import { ref, PropType, nextTick } from "vue"; import { ref, PropType, nextTick } from "vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import { deviceDetection } from "@pureadmin/utils"; import { deviceDetection } from "@pureadmin/utils";
const props = defineProps({ defineProps({
noticeItem: { noticeItem: {
type: Object as PropType<ListItem>, type: Object as PropType<ListItem>,
default: () => {} default: () => {}
@ -52,9 +52,9 @@ function hoverDescription(event, description) {
class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]" class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"
> >
<el-avatar <el-avatar
v-if="props.noticeItem.avatar" v-if="noticeItem.avatar"
:size="30" :size="30"
:src="props.noticeItem.avatar" :src="noticeItem.avatar"
class="notice-container-avatar" class="notice-container-avatar"
/> />
<div class="notice-container-text"> <div class="notice-container-text">
@ -63,7 +63,7 @@ function hoverDescription(event, description) {
popper-class="notice-title-popper" popper-class="notice-title-popper"
:effect="tooltipEffect" :effect="tooltipEffect"
:disabled="!titleTooltip" :disabled="!titleTooltip"
:content="props.noticeItem.title" :content="noticeItem.title"
placement="top-start" placement="top-start"
:enterable="!isMobile" :enterable="!isMobile"
> >
@ -72,16 +72,16 @@ function hoverDescription(event, description) {
class="notice-title-content" class="notice-title-content"
@mouseover="hoverTitle" @mouseover="hoverTitle"
> >
{{ props.noticeItem.title }} {{ noticeItem.title }}
</div> </div>
</el-tooltip> </el-tooltip>
<el-tag <el-tag
v-if="props.noticeItem?.extra" v-if="noticeItem?.extra"
:type="props.noticeItem?.status" :type="noticeItem?.status"
size="small" size="small"
class="notice-title-extra" class="notice-title-extra"
> >
{{ props.noticeItem?.extra }} {{ noticeItem?.extra }}
</el-tag> </el-tag>
</div> </div>
@ -89,19 +89,19 @@ function hoverDescription(event, description) {
popper-class="notice-title-popper" popper-class="notice-title-popper"
:effect="tooltipEffect" :effect="tooltipEffect"
:disabled="!descriptionTooltip" :disabled="!descriptionTooltip"
:content="props.noticeItem.description" :content="noticeItem.description"
placement="top-start" placement="top-start"
> >
<div <div
ref="descriptionRef" ref="descriptionRef"
class="notice-text-description" class="notice-text-description"
@mouseover="hoverDescription($event, props.noticeItem.description)" @mouseover="hoverDescription($event, noticeItem.description)"
> >
{{ props.noticeItem.description }} {{ noticeItem.description }}
</div> </div>
</el-tooltip> </el-tooltip>
<div class="notice-text-datetime text-[#00000073] dark:text-white"> <div class="notice-text-datetime text-[#00000073] dark:text-white">
{{ props.noticeItem.datetime }} {{ noticeItem.datetime }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { PropType } from "vue";
import { ListItem } from "../data";
import NoticeItem from "./NoticeItem.vue";
defineProps({
list: {
type: Array as PropType<Array<ListItem>>,
default: () => []
},
emptyText: {
type: String,
default: ""
}
});
</script>
<template>
<div v-if="list.length">
<NoticeItem v-for="(item, index) in list" :key="index" :noticeItem="item" />
</div>
<el-empty v-else :description="emptyText" />
</template>

View File

@ -0,0 +1,97 @@
export interface ListItem {
avatar: string;
title: string;
datetime: string;
type: string;
description: string;
status?: "primary" | "success" | "warning" | "info" | "danger";
extra?: string;
}
export interface TabItem {
key: string;
name: string;
list: ListItem[];
emptyText: string;
}
export const noticesData: TabItem[] = [
{
key: "1",
name: "通知",
list: [],
emptyText: "暂无通知"
},
{
key: "2",
name: "消息",
list: [
{
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile1.svg",
title: "小铭 评论了你",
description: "诚在于心,信在于行,诚信在于心行合一。",
datetime: "今天",
type: "2"
},
{
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile2.svg",
title: "李白 回复了你",
description: "长风破浪会有时,直挂云帆济沧海。",
datetime: "昨天",
type: "2"
},
{
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile5.svg",
title: "标题",
description:
"请将鼠标移动到此处以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
datetime: "时间",
type: "2"
}
],
emptyText: "暂无消息"
},
{
key: "3",
name: "待办",
list: [
{
avatar: "",
title: "第三方紧急代码变更",
description:
"小林提交于 2024-05-10需在 2024-05-11 前完成代码变更任务",
datetime: "",
extra: "马上到期",
status: "danger",
type: "3"
},
{
avatar: "",
title: "版本发布",
description: "指派小铭于 2024-06-18 前完成更新并发布",
datetime: "",
extra: "已耗时 8 天",
status: "warning",
type: "3"
},
{
avatar: "",
title: "新功能开发",
description: "开发多租户管理",
datetime: "",
extra: "进行中",
type: "3"
},
{
avatar: "",
title: "任务名称",
description: "任务需要在 2030-10-30 10:00 前启动",
datetime: "",
extra: "未开始",
status: "info",
type: "3"
}
],
emptyText: "暂无待办"
}
];

View File

@ -1,22 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref, computed } from "vue";
import { noticesData } from "./data"; import { noticesData } from "./data";
import NoticeList from "./noticeList.vue"; import NoticeList from "./components/NoticeList.vue";
import Bell from "@iconify-icons/ep/bell"; import BellIcon from "@iconify-icons/ep/bell";
const noticesNum = ref(0); const noticesNum = ref(0);
const notices = ref(noticesData); const notices = ref(noticesData);
const activeKey = ref(noticesData[0].key); const activeKey = ref(noticesData[0]?.key);
notices.value.map(v => (noticesNum.value += v.list.length)); notices.value.map(v => (noticesNum.value += v.list.length));
const getLabel = computed(
() => item =>
item.name + (item.list.length > 0 ? `(${item.list.length})` : "")
);
</script> </script>
<template> <template>
<el-dropdown trigger="click" placement="bottom-end"> <el-dropdown trigger="click" placement="bottom-end">
<span class="dropdown-badge navbar-bg-hover select-none"> <span
<el-badge :value="noticesNum" :max="99"> :class="[
'dropdown-badge',
'navbar-bg-hover',
'select-none',
Number(noticesNum) !== 0 && 'mr-[10px]'
]"
>
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
<span class="header-notice-icon"> <span class="header-notice-icon">
<IconifyIconOffline :icon="Bell" /> <IconifyIconOffline :icon="BellIcon" />
</span> </span>
</el-badge> </el-badge>
</span> </span>
@ -35,13 +47,10 @@ notices.value.map(v => (noticesNum.value += v.list.length));
/> />
<span v-else> <span v-else>
<template v-for="item in notices" :key="item.key"> <template v-for="item in notices" :key="item.key">
<el-tab-pane <el-tab-pane :label="getLabel(item)" :name="`${item.key}`">
:label="`${item.name}(${item.list.length})`"
:name="`${item.key}`"
>
<el-scrollbar max-height="330px"> <el-scrollbar max-height="330px">
<div class="noticeList-container"> <div class="noticeList-container">
<NoticeList :list="item.list" /> <NoticeList :list="item.list" :emptyText="item.emptyText" />
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </el-tab-pane>
@ -60,7 +69,6 @@ notices.value.map(v => (noticesNum.value += v.list.length));
justify-content: center; justify-content: center;
width: 40px; width: 40px;
height: 48px; height: 48px;
margin-right: 10px;
cursor: pointer; cursor: pointer;
.header-notice-icon { .header-notice-icon {

View File

@ -3,7 +3,7 @@ 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 { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import Close from "@iconify-icons/ep/close"; import CloseIcon from "@iconify-icons/ep/close";
const target = ref(null); const target = ref(null);
const show = ref<Boolean>(false); const show = ref<Boolean>(false);
@ -64,7 +64,7 @@ onBeforeUnmount(() => {
class="dark:text-white" class="dark:text-white"
width="18px" width="18px"
height="18px" height="18px"
:icon="Close" :icon="CloseIcon"
@click="show = !show" @click="show = !show"
/> />
</span> </span>

View File

@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component"; import MdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component"; import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line"; import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line"; import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
const props = withDefaults(defineProps<{ total: number }>(), { withDefaults(defineProps<{ total: number }>(), {
total: 0 total: 0
}); });
@ -15,7 +15,7 @@ const { device } = useNav();
<template> <template>
<div class="search-footer text-[#333] dark:text-white"> <div class="search-footer text-[#333] dark:text-white">
<span class="search-footer-item"> <span class="search-footer-item">
<enterOutlined class="icon" /> <EnterOutlined class="icon" />
确认 确认
</span> </span>
<span class="search-footer-item"> <span class="search-footer-item">
@ -24,14 +24,11 @@ const { device } = useNav();
切换 切换
</span> </span>
<span class="search-footer-item"> <span class="search-footer-item">
<mdiKeyboardEsc class="icon" /> <MdiKeyboardEsc class="icon" />
关闭 关闭
</span> </span>
<p <p v-if="device !== 'mobile' && total > 0" class="search-footer-total">
v-if="device !== 'mobile' && props.total > 0" {{ `${total}` }}
class="search-footer-total"
>
{{ props.total }}
</p> </p>
</div> </div>
</template> </template>

View File

@ -160,7 +160,7 @@ defineExpose({ handleScroll });
</template> </template>
<template v-if="collectList.length"> <template v-if="collectList.length">
<div :style="titleStyle"> <div :style="titleStyle">
收藏{{ collectList.length > 1 ? "(可拖拽排序)" : "" }} {{ `收藏${collectList.length > 1 ? "(可拖拽排序)" : ""}` }}
</div> </div>
<div class="collect-container"> <div class="collect-container">
<div <div

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { optionsItem } from "../types"; import type { optionsItem } from "../types";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Star from "@iconify-icons/ep/star"; import StarIcon from "@iconify-icons/ep/star";
import Close from "@iconify-icons/ep/close"; import CloseIcon from "@iconify-icons/ep/close";
interface Props { interface Props {
item: optionsItem; item: optionsItem;
@ -32,12 +32,12 @@ function handleDelete(item) {
</span> </span>
<IconifyIconOffline <IconifyIconOffline
v-show="item.type === 'history'" v-show="item.type === 'history'"
:icon="Star" :icon="StarIcon"
class="w-[18px] h-[18px] mr-2 hover:text-[#d7d5d4]" class="w-[18px] h-[18px] mr-2 hover:text-[#d7d5d4]"
@click.stop="handleCollect(item)" @click.stop="handleCollect(item)"
/> />
<IconifyIconOffline <IconifyIconOffline
:icon="Close" :icon="CloseIcon"
class="w-[18px] h-[18px] hover:text-[#d7d5d4] cursor-pointer" class="w-[18px] h-[18px] hover:text-[#d7d5d4] cursor-pointer"
@click.stop="handleDelete(item)" @click.stop="handleDelete(item)"
/> />

View File

@ -11,7 +11,7 @@ import { ref, computed, shallowRef, watch } from "vue";
import { useDebounceFn, onKeyStroke } from "@vueuse/core"; import { useDebounceFn, onKeyStroke } from "@vueuse/core";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import { cloneDeep, isAllEmpty, storageLocal } from "@pureadmin/utils"; import { cloneDeep, isAllEmpty, storageLocal } from "@pureadmin/utils";
import Search from "@iconify-icons/ri/search-line"; import SearchIcon from "@iconify-icons/ri/search-line";
interface Props { interface Props {
/** 弹窗显隐 */ /** 弹窗显隐 */
@ -294,7 +294,7 @@ onKeyStroke("ArrowDown", handleDown);
> >
<template #prefix> <template #prefix>
<IconifyIconOffline <IconifyIconOffline
:icon="Search" :icon="SearchIcon"
class="text-primary w-[24px] h-[24px]" class="text-primary w-[24px] h-[24px]"
/> />
</template> </template>

View File

@ -4,7 +4,7 @@ import { useResizeObserver } from "@pureadmin/utils";
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { ref, computed, getCurrentInstance, onMounted } from "vue"; import { ref, computed, getCurrentInstance, onMounted } from "vue";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component"; import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
interface Emits { interface Emits {
(e: "update:value", val: string): void; (e: "update:value", val: string): void;
@ -83,7 +83,7 @@ defineExpose({ handleScroll });
<span class="result-item-title"> <span class="result-item-title">
{{ item.meta?.title }} {{ item.meta?.title }}
</span> </span>
<enterOutlined /> <EnterOutlined />
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { SearchModal } from "./components";
import { useBoolean } from "../../hooks/useBoolean"; import { useBoolean } from "../../hooks/useBoolean";
import SearchModal from "./components/SearchModal.vue";
const { bool: show, toggle } = useBoolean(); const { bool: show, toggle } = useBoolean();
function handleSearch() { function handleSearch() {

View File

@ -9,8 +9,8 @@ import {
onUnmounted, onUnmounted,
onBeforeMount onBeforeMount
} from "vue"; } from "vue";
import panel from "../panel/index.vue";
import { emitter } from "@/utils/mitt"; import { emitter } from "@/utils/mitt";
import LayPanel from "../lay-panel/index.vue";
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 { toggleTheme } from "@pureadmin/theme/dist/browser-utils"; import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
@ -22,9 +22,9 @@ 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 LeftArrow from "@iconify-icons/ri/arrow-left-s-line";
import RightArrow from "@iconify-icons/ri/arrow-right-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";
const { device } = useNav(); const { device } = useNav();
const { isDark } = useDark(); const { isDark } = useDark();
@ -197,21 +197,21 @@ const themeOptions = computed<Array<OptionsType>>(() => {
return [ return [
{ {
label: "浅色", label: "浅色",
icon: dayIcon, icon: DayIcon,
theme: "light", theme: "light",
tip: "清新启航,点亮舒适的工作界面", tip: "清新启航,点亮舒适的工作界面",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" } iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}, },
{ {
label: "深色", label: "深色",
icon: darkIcon, icon: DarkIcon,
theme: "dark", theme: "dark",
tip: "月光序曲,沉醉于夜的静谧雅致", tip: "月光序曲,沉醉于夜的静谧雅致",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" } iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
}, },
{ {
label: "自动", label: "自动",
icon: systemIcon, icon: SystemIcon,
theme: "system", theme: "system",
tip: "同步时光,界面随晨昏自然呼应", tip: "同步时光,界面随晨昏自然呼应",
iconAttrs: { fill: isDark.value ? "#fff" : "#000" } iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
@ -311,7 +311,7 @@ onUnmounted(() => removeMatchMedia);
</script> </script>
<template> <template>
<panel> <LayPanel>
<div class="p-5"> <div class="p-5">
<p :class="pClass">整体风格</p> <p :class="pClass">整体风格</p>
<Segmented <Segmented
@ -511,7 +511,7 @@ onUnmounted(() => removeMatchMedia);
</li> </li>
</ul> </ul>
</div> </div>
</panel> </LayPanel>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,12 +1,13 @@
<script setup lang="ts"> <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 { isAllEmpty } from "@pureadmin/utils";
import { ref, nextTick, computed } from "vue"; import { ref, nextTick, computed } from "vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
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";
@ -16,11 +17,11 @@ const {
route, route,
title, title,
logout, logout,
backTopMenu,
onPanel, onPanel,
getLogo, getLogo,
username, username,
userAvatar, userAvatar,
backTopMenu,
avatarsStyle avatarsStyle
} = useNav(); } = useNav();
@ -44,13 +45,12 @@ nextTick(() => {
</div> </div>
<el-menu <el-menu
ref="menuRef" ref="menuRef"
router
mode="horizontal" mode="horizontal"
popper-class="pure-scrollbar" popper-class="pure-scrollbar"
class="horizontal-header-menu" class="horizontal-header-menu"
:default-active="defaultActive" :default-active="defaultActive"
> >
<sidebar-item <LaySidebarItem
v-for="route in usePermissionStoreHook().wholeMenus" v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path" :key="route.path"
:item="route" :item="route"
@ -59,11 +59,11 @@ nextTick(() => {
</el-menu> </el-menu>
<div class="horizontal-header-right"> <div class="horizontal-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<Search id="header-search" /> <LaySearch id="header-search" />
<!-- 全屏 --> <!-- 全屏 -->
<FullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
<Notice id="header-notice" /> <LayNotice 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">

View File

@ -1,14 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
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 { isAllEmpty } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/index.vue";
import { ref, toRaw, watch, onMounted, nextTick } from "vue"; import { ref, toRaw, watch, onMounted, nextTick } from "vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { getParentPaths, findRouteByPath } from "@/router/utils"; import { getParentPaths, findRouteByPath } from "@/router/utils";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarExtraIcon from "../lay-sidebar/components/SidebarExtraIcon.vue";
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
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";
@ -84,18 +85,18 @@ watch(
<span class="select-none"> <span class="select-none">
{{ route.meta.title }} {{ route.meta.title }}
</span> </span>
<extraIcon :extraIcon="route.meta.extraIcon" /> <LaySidebarExtraIcon :extraIcon="route.meta.extraIcon" />
</div> </div>
</template> </template>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
<div class="horizontal-header-right"> <div class="horizontal-header-right">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<Search id="header-search" /> <LaySearch id="header-search" />
<!-- 全屏 --> <!-- 全屏 -->
<FullScreen id="full-screen" /> <LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 --> <!-- 消息通知 -->
<Notice id="header-notice" /> <LayNotice 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">

View File

@ -1,16 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
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 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";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue"; import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
import LaySidebarLogo from "../lay-sidebar/components/SidebarLogo.vue";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
import LaySidebarLeftCollapse from "../lay-sidebar/components/SidebarLeftCollapse.vue";
import LaySidebarCenterCollapse from "../lay-sidebar/components/SidebarCenterCollapse.vue";
const route = useRoute(); const route = useRoute();
const isShow = ref(false); const isShow = ref(false);
@ -93,13 +93,12 @@ onBeforeUnmount(() => {
@mouseenter.prevent="isShow = true" @mouseenter.prevent="isShow = true"
@mouseleave.prevent="isShow = false" @mouseleave.prevent="isShow = false"
> >
<Logo v-if="showLogo" :collapse="isCollapse" /> <LaySidebarLogo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar <el-scrollbar
wrap-class="scrollbar-wrapper" wrap-class="scrollbar-wrapper"
:class="[device === 'mobile' ? 'mobile' : 'pc']" :class="[device === 'mobile' ? 'mobile' : 'pc']"
> >
<el-menu <el-menu
router
unique-opened unique-opened
mode="vertical" mode="vertical"
popper-class="pure-scrollbar" popper-class="pure-scrollbar"
@ -109,7 +108,7 @@ onBeforeUnmount(() => {
:popper-effect="tooltipEffect" :popper-effect="tooltipEffect"
:default-active="defaultActive" :default-active="defaultActive"
> >
<sidebar-item <LaySidebarItem
v-for="routes in menuData" v-for="routes in menuData"
:key="routes.path" :key="routes.path"
:item="routes" :item="routes"
@ -118,12 +117,12 @@ onBeforeUnmount(() => {
/> />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
<CenterCollapse <LaySidebarCenterCollapse
v-if="device !== 'mobile' && (isShow || isCollapse)" v-if="device !== 'mobile' && (isShow || isCollapse)"
:is-active="pureApp.sidebar.opened" :is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar" @toggleClick="toggleSideBar"
/> />
<LeftCollapse <LaySidebarLeftCollapse
v-if="device !== 'mobile'" v-if="device !== 'mobile'"
:is-active="pureApp.sidebar.opened" :is-active="pureApp.sidebar.opened"
@toggleClick="toggleSideBar" @toggleClick="toggleSideBar"

View File

@ -9,7 +9,7 @@ interface Props {
isActive: boolean; isActive: boolean;
} }
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
isActive: false isActive: false
}); });
@ -34,7 +34,7 @@ const toggleClick = () => {
<template> <template>
<div <div
v-tippy="{ v-tippy="{
content: props.isActive ? '点击折叠' : '点击展开', content: isActive ? '点击折叠' : '点击展开',
theme: tooltipEffect, theme: tooltipEffect,
hideOnClick: 'toggle', hideOnClick: 'toggle',
placement: 'right' placement: 'right'
@ -45,7 +45,7 @@ const toggleClick = () => {
<IconifyIconOffline <IconifyIconOffline
:icon="ArrowLeft" :icon="ArrowLeft"
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']" :class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }" :style="{ transform: isActive ? 'none' : 'rotateY(180deg)' }"
/> />
</div> </div>
</template> </template>

View File

@ -2,7 +2,7 @@
import { toRaw } from "vue"; import { toRaw } from "vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
const props = defineProps({ defineProps({
extraIcon: { extraIcon: {
type: String, type: String,
default: "" default: ""
@ -11,9 +11,9 @@ const props = defineProps({
</script> </script>
<template> <template>
<div v-if="props.extraIcon" class="flex justify-center items-center"> <div v-if="extraIcon" class="flex justify-center items-center">
<component <component
:is="useRenderIcon(toRaw(props.extraIcon))" :is="useRenderIcon(toRaw(extraIcon))"
class="w-[30px] h-[30px]" class="w-[30px] h-[30px]"
/> />
</div> </div>

View File

@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import path from "path"; import path from "path";
import { getConfig } from "@/config"; import { getConfig } from "@/config";
import LinkItem from "./linkItem.vue"; import { menuType } from "@/layout/types";
import { menuType } from "../../types";
import extraIcon from "./extraIcon.vue";
import { ReText } from "@/components/ReText"; import { ReText } from "@/components/ReText";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import SidebarLinkItem from "./SidebarLinkItem.vue";
import SidebarExtraIcon from "./SidebarExtraIcon.vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks"; import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { import {
type PropType, type PropType,
@ -105,9 +105,9 @@ function resolvePath(routePath) {
</script> </script>
<template> <template>
<link-item <SidebarLinkItem
v-if=" v-if="
hasOneShowingChild(props.item.children, props.item) && hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) (!onlyOneChild.children || onlyOneChild.noShowingChildren)
" "
:to="item" :to="item"
@ -119,7 +119,7 @@ function resolvePath(routePath) {
v-bind="attrs" v-bind="attrs"
> >
<div <div
v-if="toRaw(props.item.meta.icon)" v-if="toRaw(item.meta.icon)"
class="sub-menu-icon" class="sub-menu-icon"
:style="getSubMenuIconStyle" :style="getSubMenuIconStyle"
> >
@ -127,21 +127,21 @@ function resolvePath(routePath) {
:is=" :is="
useRenderIcon( useRenderIcon(
toRaw(onlyOneChild.meta.icon) || toRaw(onlyOneChild.meta.icon) ||
(props.item.meta && toRaw(props.item.meta.icon)) (item.meta && toRaw(item.meta.icon))
) )
" "
/> />
</div> </div>
<el-text <el-text
v-if=" v-if="
(!props.item?.meta.icon && (!item?.meta.icon &&
isCollapse && isCollapse &&
layout === 'vertical' && layout === 'vertical' &&
props.item?.pathList?.length === 1) || item?.pathList?.length === 1) ||
(!onlyOneChild.meta.icon && (!onlyOneChild.meta.icon &&
isCollapse && isCollapse &&
layout === 'mix' && layout === 'mix' &&
props.item?.pathList?.length === 2) item?.pathList?.length === 2)
" "
truncated truncated
class="!w-full !px-4 !text-inherit" class="!w-full !px-4 !text-inherit"
@ -160,37 +160,35 @@ function resolvePath(routePath) {
> >
{{ onlyOneChild.meta.title }} {{ onlyOneChild.meta.title }}
</ReText> </ReText>
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" /> <SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div> </div>
</template> </template>
</el-menu-item> </el-menu-item>
</link-item> </SidebarLinkItem>
<el-sub-menu <el-sub-menu
v-else v-else
ref="subMenu" ref="subMenu"
teleported teleported
:index="resolvePath(props.item.path)" :index="resolvePath(item.path)"
v-bind="expandCloseIcon" v-bind="expandCloseIcon"
> >
<template #title> <template #title>
<div <div
v-if="toRaw(props.item.meta.icon)" v-if="toRaw(item.meta.icon)"
:style="getSubMenuIconStyle" :style="getSubMenuIconStyle"
class="sub-menu-icon" class="sub-menu-icon"
> >
<component <component :is="useRenderIcon(item.meta && toRaw(item.meta.icon))" />
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
/>
</div> </div>
<ReText <ReText
v-if=" v-if="
layout === 'mix' && toRaw(props.item.meta.icon) layout === 'mix' && toRaw(item.meta.icon)
? !isCollapse || props.item?.pathList?.length !== 2 ? !isCollapse || item?.pathList?.length !== 2
: !( : !(
layout === 'vertical' && layout === 'vertical' &&
isCollapse && isCollapse &&
toRaw(props.item.meta.icon) && toRaw(item.meta.icon) &&
props.item.parentId === null item.parentId === null
) )
" "
:tippyProps="{ :tippyProps="{
@ -203,17 +201,17 @@ function resolvePath(routePath) {
'!px-4': '!px-4':
layout !== 'horizontal' && layout !== 'horizontal' &&
isCollapse && isCollapse &&
!toRaw(props.item.meta.icon) && !toRaw(item.meta.icon) &&
props.item.parentId === null item.parentId === null
}" }"
> >
{{ props.item.meta.title }} {{ item.meta.title }}
</ReText> </ReText>
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" /> <SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" />
</template> </template>
<sidebar-item <sidebar-item
v-for="child in props.item.children" v-for="child in item.children"
:key="child.path" :key="child.path"
:is-nest="true" :is-nest="true"
:item="child" :item="child"

View File

@ -9,7 +9,7 @@ interface Props {
isActive: boolean; isActive: boolean;
} }
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
isActive: false isActive: false
}); });
@ -44,14 +44,14 @@ const toggleClick = () => {
<div class="left-collapse"> <div class="left-collapse">
<IconifyIconOffline <IconifyIconOffline
v-tippy="{ v-tippy="{
content: props.isActive ? '点击折叠' : '点击展开', content: isActive ? '点击折叠' : '点击展开',
theme: tooltipEffect, theme: tooltipEffect,
hideOnClick: 'toggle', hideOnClick: 'toggle',
placement: 'right' placement: 'right'
}" }"
:icon="MenuFold" :icon="MenuFold"
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']" :class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }" :style="{ transform: isActive ? 'none' : 'rotateY(180deg)' }"
@click="toggleClick" @click="toggleClick"
/> />
</div> </div>

View File

@ -3,10 +3,6 @@ import { computed } from "vue";
import { isUrl } from "@pureadmin/utils"; import { isUrl } from "@pureadmin/utils";
import { menuType } from "@/layout/types"; import { menuType } from "@/layout/types";
defineOptions({
name: "LinkItem"
});
const props = defineProps<{ const props = defineProps<{
to: menuType; to: menuType;
}>(); }>();

View File

@ -2,7 +2,7 @@
import { getTopMenu } from "@/router/utils"; import { getTopMenu } from "@/router/utils";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
const props = defineProps({ defineProps({
collapse: Boolean collapse: Boolean
}); });
@ -10,11 +10,11 @@ const { title, getLogo } = useNav();
</script> </script>
<template> <template>
<div class="sidebar-logo-container" :class="{ collapses: props.collapse }"> <div class="sidebar-logo-container" :class="{ collapses: collapse }">
<transition name="sidebarLogoFade"> <transition name="sidebarLogoFade">
<router-link <router-link
v-if="props.collapse" v-if="collapse"
key="props.collapse" key="collapse"
:title="title" :title="title"
class="sidebar-logo-link" class="sidebar-logo-link"
:to="getTopMenu()?.path ?? '/'" :to="getTopMenu()?.path ?? '/'"

View File

@ -6,7 +6,7 @@ interface Props {
isActive: boolean; isActive: boolean;
} }
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
isActive: false isActive: false
}); });
@ -22,11 +22,11 @@ const toggleClick = () => {
<template> <template>
<div <div
class="px-3 mr-1 navbar-bg-hover" class="px-3 mr-1 navbar-bg-hover"
:title="props.isActive ? '点击折叠' : '点击展开'" :title="isActive ? '点击折叠' : '点击展开'"
@click="toggleClick" @click="toggleClick"
> >
<IconifyIconOffline <IconifyIconOffline
:icon="props.isActive ? MenuFold : MenuUnfold" :icon="isActive ? MenuFold : MenuUnfold"
class="inline-block align-middle hover:text-primary dark:hover:!text-white" class="inline-block align-middle hover:text-primary dark:hover:!text-white"
/> />
</div> </div>

View File

@ -1,146 +0,0 @@
export interface ListItem {
avatar: string;
title: string;
datetime: string;
type: string;
description: string;
status?: "primary" | "success" | "warning" | "info" | "danger";
extra?: string;
}
export interface TabItem {
key: string;
name: string;
list: ListItem[];
}
export const noticesData: TabItem[] = [
{
key: "1",
name: "通知",
list: [
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "你收到了 12 份新周报",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
title: "你推荐的 前端高手 已通过第三轮面试",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
title: "这种模板可以区分多种通知类型",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title:
"展示标题内容超过一行后的处理方式如果内容超过1行将自动截断并支持tooltip显示完整标题。",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title: "左侧图标用于区分不同的类型",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title: "左侧图标用于区分不同的类型",
datetime: "一年前",
description: "",
type: "1"
}
]
},
{
key: "2",
name: "消息",
list: [
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "李白 评论了你",
description: "长风破浪会有时,直挂云帆济沧海",
datetime: "一年前",
type: "2"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "李白 回复了你",
description: "行路难,行路难,多歧路,今安在。",
datetime: "一年前",
type: "2"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "标题",
description:
"请将鼠标移动到此处以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
datetime: "一年前",
type: "2"
}
]
},
{
key: "3",
name: "待办",
list: [
{
avatar: "",
title: "任务名称",
description: "任务需要在 2022-11-16 20:00 前启动",
datetime: "",
extra: "未开始",
status: "info",
type: "3"
},
{
avatar: "",
title: "第三方紧急代码变更",
description:
"一拳提交于 2022-11-16需在 2022-11-18 前完成代码变更任务",
datetime: "",
extra: "马上到期",
status: "danger",
type: "3"
},
{
avatar: "",
title: "信息安全考试",
description: "指派小仙于 2022-12-12 前完成更新并发布",
datetime: "",
extra: "已耗时 8 天",
status: "warning",
type: "3"
},
{
avatar: "",
title: "vue-pure-admin 版本发布",
description: "vue-pure-admin 版本发布",
datetime: "",
extra: "进行中",
type: "3"
}
]
}
];

View File

@ -1,23 +0,0 @@
<script setup lang="ts">
import { PropType } from "vue";
import { ListItem } from "./data";
import NoticeItem from "./noticeItem.vue";
const props = defineProps({
list: {
type: Array as PropType<Array<ListItem>>,
default: () => []
}
});
</script>
<template>
<div v-if="props.list.length">
<NoticeItem
v-for="(item, index) in props.list"
:key="index"
:noticeItem="item"
/>
</div>
<el-empty v-else description="暂无消息" />
</template>

View File

@ -1,3 +0,0 @@
import SearchModal from "./SearchModal.vue";
export { SearchModal };

View File

@ -3,7 +3,7 @@ import { useRoute } from "vue-router";
import { ref, unref, watch, onMounted, nextTick } from "vue"; import { ref, unref, watch, onMounted, nextTick } from "vue";
defineOptions({ defineOptions({
name: "FrameView" name: "LayFrame"
}); });
const props = defineProps<{ const props = defineProps<{

View File

@ -23,13 +23,13 @@ import {
useResizeObserver useResizeObserver
} from "@pureadmin/utils"; } from "@pureadmin/utils";
import navbar from "./components/navbar.vue"; import LayTag from "./components/lay-tag/index.vue";
import tag from "./components/tag/index.vue"; import LayNavbar from "./components/lay-navbar/index.vue";
import appMain from "./components/appMain.vue"; import LayContent from "./components/lay-content/index.vue";
import setting from "./components/setting/index.vue"; import LaySetting from "./components/lay-setting/index.vue";
import Vertical from "./components/sidebar/vertical.vue"; import NavVertical from "./components/lay-sidebar/NavVertical.vue";
import Horizontal from "./components/sidebar/horizontal.vue"; import NavHorizontal from "./components/lay-sidebar/NavHorizontal.vue";
import backTop from "@/assets/svg/back_top.svg?component"; import BackTopIcon from "@/assets/svg/back_top.svg?component";
const appWrapperRef = ref(); const appWrapperRef = ref();
const { isDark } = useDark(); const { isDark } = useDark();
@ -124,7 +124,8 @@ onBeforeMount(() => {
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle); useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
}); });
const layoutHeader = defineComponent({ const LayHeader = defineComponent({
name: "LayHeader",
render() { render() {
return h( return h(
"div", "div",
@ -142,12 +143,12 @@ const layoutHeader = defineComponent({
default: () => [ default: () => [
!pureSetting.hiddenSideBar && !pureSetting.hiddenSideBar &&
(layout.value.includes("vertical") || layout.value.includes("mix")) (layout.value.includes("vertical") || layout.value.includes("mix"))
? h(navbar) ? h(LayNavbar)
: null, : null,
!pureSetting.hiddenSideBar && layout.value.includes("horizontal") !pureSetting.hiddenSideBar && layout.value.includes("horizontal")
? h(Horizontal) ? h(NavHorizontal)
: null, : null,
h(tag) h(LayTag)
] ]
} }
); );
@ -166,7 +167,7 @@ const layoutHeader = defineComponent({
class="app-mask" class="app-mask"
@click="useAppStoreHook().toggleSideBar()" @click="useAppStoreHook().toggleSideBar()"
/> />
<Vertical <NavVertical
v-show=" v-show="
!pureSetting.hiddenSideBar && !pureSetting.hiddenSideBar &&
(layout.includes('vertical') || layout.includes('mix')) (layout.includes('vertical') || layout.includes('mix'))
@ -179,24 +180,24 @@ const layoutHeader = defineComponent({
]" ]"
> >
<div v-if="set.fixedHeader"> <div v-if="set.fixedHeader">
<layout-header /> <LayHeader />
<!-- 主体内容 --> <!-- 主体内容 -->
<app-main :fixed-header="set.fixedHeader" /> <LayContent :fixed-header="set.fixedHeader" />
</div> </div>
<el-scrollbar v-else> <el-scrollbar v-else>
<el-backtop <el-backtop
title="回到顶部" title="回到顶部"
target=".main-container .el-scrollbar__wrap" target=".main-container .el-scrollbar__wrap"
> >
<backTop /> <BackTopIcon />
</el-backtop> </el-backtop>
<layout-header /> <LayHeader />
<!-- 主体内容 --> <!-- 主体内容 -->
<app-main :fixed-header="set.fixedHeader" /> <LayContent :fixed-header="set.fixedHeader" />
</el-scrollbar> </el-scrollbar>
</div> </div>
<!-- 系统设置 --> <!-- 系统设置 -->
<setting /> <LaySetting />
</div> </div>
</template> </template>

View File

@ -22,7 +22,7 @@ import { userKey, type DataInfo } from "@/utils/auth";
import { type menuType, routerArrays } from "@/layout/types"; import { type menuType, routerArrays } from "@/layout/types";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
const IFrame = () => import("@/layout/frameView.vue"); const IFrame = () => import("@/layout/frame.vue");
// https://cn.vitejs.dev/guide/features.html#glob-import // https://cn.vitejs.dev/guide/features.html#glob-import
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}"); const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");

View File

@ -28,8 +28,8 @@ const ruleFormRef = ref<FormInstance>();
const { initStorage } = useLayout(); const { initStorage } = useLayout();
initStorage(); initStorage();
const { dataTheme, dataThemeChange } = useDataThemeChange(); const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
dataThemeChange(); dataThemeChange(overallStyle.value);
const { title } = useNav(); const { title } = useNav();
const ruleForm = reactive({ const ruleForm = reactive({

6
types/global.d.ts vendored
View File

@ -61,7 +61,7 @@ declare global {
/** /**
* *
* @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/config/#%E5%85%B7%E4%BD%93%E9%85%8D%E7%BD%AE} * @see {@link https://pure-admin.github.io/pure-admin-doc/pages/config/#%E5%85%B7%E4%BD%93%E9%85%8D%E7%BD%AE}
*/ */
interface ViteEnv { interface ViteEnv {
VITE_PORT: number; VITE_PORT: number;
@ -79,7 +79,7 @@ declare global {
/** /**
* `public/platform-config.json` * `public/platform-config.json`
* @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/config/#platform-config-json} * @see {@link https://pure-admin.github.io/pure-admin-doc/pages/config/#platform-config-json}
*/ */
interface PlatformConfigs { interface PlatformConfigs {
Version?: string; Version?: string;
@ -112,7 +112,7 @@ declare global {
/** /**
* `PlatformConfigs` * `PlatformConfigs`
* @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/config/#platform-config-json} * @see {@link https://pure-admin.github.io/pure-admin-doc/pages/config/#platform-config-json}
*/ */
interface StorageConfigs { interface StorageConfigs {
version?: string; version?: string;

View File

@ -4,14 +4,14 @@ import { type UserConfigExport, type ConfigEnv, loadEnv } from "vite";
import { import {
root, root,
alias, alias,
warpperEnv, wrapperEnv,
pathResolve, pathResolve,
__APP_INFO__ __APP_INFO__
} from "./build/utils"; } from "./build/utils";
export default ({ mode }: ConfigEnv): UserConfigExport => { export default ({ mode }: ConfigEnv): UserConfigExport => {
const { VITE_CDN, VITE_PORT, VITE_COMPRESSION, VITE_PUBLIC_PATH } = const { VITE_CDN, VITE_PORT, VITE_COMPRESSION, VITE_PUBLIC_PATH } =
warpperEnv(loadEnv(mode, root)); wrapperEnv(loadEnv(mode, root));
return { return {
base: VITE_PUBLIC_PATH, base: VITE_PUBLIC_PATH,
root, root,