This commit is contained in:
wanchun 2023-06-19 17:09:31 +08:00
commit 9d7bc0471e
7 changed files with 2146 additions and 2085 deletions

View File

@ -1,54 +1,64 @@
# @fesjs/plugin-layout # @fesjs/plugin-layout
## 介绍 ## 介绍
为了进一步降低研发成本,我们尝试将布局通过 fes 插件的方式内置,只需通过简单的配置即可拥有布局,包括导航以及侧边栏。从而做到用户无需关心布局。
- 侧边栏菜单数据根据路由中的配置自动生成。
- 布局,提供 `side``top``mixin` 三种布局。
- 主题,提供 `light``dark` 两种主题。
- 默认实现对路由的 404、403 处理。
- 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。
- 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。
- 支持自定义头部区域。
- 菜单支持配置icon
- 菜单标题支持国际化
- 可配置页面是否需要 layout。 为了进一步降低研发成本,我们尝试将布局通过 fes 插件的方式内置,只需通过简单的配置即可拥有布局,包括导航以及侧边栏。从而做到用户无需关心布局。
- 侧边栏菜单数据根据路由中的配置自动生成。
- 布局,提供 `side``top``mixin` 三种布局。
- 主题,提供 `light``dark` 两种主题。
- 默认实现对路由的 404、403 处理。
- 搭配 [@fesjs/plugin-access](./access.html) 插件使用,可以完成对路由的权限控制。
- 搭配 [@fesjs/plugin-locale](./locale.html) 插件使用,提供切换语言的能力。
- 支持自定义头部区域。
- 菜单支持配置 icon
- 菜单标题支持国际化
- 可配置页面是否需要 layout。
## 启用方式 ## 启用方式
`package.json` 中引入依赖: `package.json` 中引入依赖:
```json ```json
{ {
"dependencies": { "dependencies": {
"@fesjs/fes": "^2.0.0", "@fesjs/fes": "^2.0.0",
"@fesjs/plugin-layout": "^4.0.0" "@fesjs/plugin-layout": "^4.0.0"
}, }
} }
``` ```
## 布局类型 ## 布局类型
配置参数是 `navigation`, 布局有三种类型 `side``mixin``top` 默认是 `side` 配置参数是 `navigation`, 布局有三种类型 `side``mixin``top` 默认是 `side`
```js ```js
export default { export default {
layout: { layout: {
navigation: 'side' navigation: 'side'
} }
} };
``` ```
### side ### side
<!-- ![side](/side.png) --> <!-- ![side](/side.png) -->
<img :src="$withBase('side.png')" alt="side"> <img :src="$withBase('side.png')" alt="side">
### top ### top
<!-- ![top](/top.png) --> <!-- ![top](/top.png) -->
<img :src="$withBase('top.png')" alt="top"> <img :src="$withBase('top.png')" alt="top">
### mixin ### mixin
<!-- ![mixin](/mixin.png) --> <!-- ![mixin](/mixin.png) -->
<img :src="$withBase('mixin.png')" alt="mixin"> <img :src="$withBase('mixin.png')" alt="mixin">
### 页面禁用布局 ### 页面禁用布局
布局是默认开启的,但是可能某些页面不需要展示布局样式,比如登录页面。我们只需要在页面的`.vue`中添加如下配置: 布局是默认开启的,但是可能某些页面不需要展示布局样式,比如登录页面。我们只需要在页面的`.vue`中添加如下配置:
```vue ```vue
<config lang="json"> <config lang="json">
{ {
@ -56,7 +66,9 @@ export default {
} }
</config> </config>
``` ```
如果只是不想展示`sidebar`,则: 如果只是不想展示`sidebar`,则:
``` ```
<config lang="json"> <config lang="json">
{ {
@ -66,17 +78,18 @@ export default {
} }
</config> </config>
``` ```
`layout`的可选配置有: `layout`的可选配置有:
- **sidebar** 左侧区域从v4.0.0开始,之前名称叫`side` - **sidebar** 左侧区域,从 v4.0.0 开始,之前名称叫`side`
- **header** 头部区域,从 v4.0.0 开始,之前名称叫`top`
- **header** 头部区域从v4.0.0开始,之前名称叫`top`
- **logo**logo和标题区域。
- **logo**logo 和标题区域。
## keep-alive ## keep-alive
从 4.0.7 开始支持配置路由页面缓存: 从 4.0.7 开始支持配置路由页面缓存:
``` ```
<config lang="json"> <config lang="json">
{ {
@ -86,7 +99,9 @@ export default {
``` ```
## 编译时配置 ## 编译时配置
`.fes.js` 中配置: `.fes.js` 中配置:
```js ```js
export default { export default {
layout: { layout: {
@ -124,74 +139,73 @@ export default {
``` ```
### footer ### footer
- **类型**`String`
- **默认值**`null` - **类型**`String`
- **默认值**`null`
- **详情**:页面底部的文字。 - **详情**:页面底部的文字。
### theme ### theme
- **类型**`String`
- **默认值**`dark` - **类型**`String`
- **默认值**`dark`
- **详情**:主题,可选有 `dark``light` - **详情**:主题,可选有 `dark``light`
### navigation ### navigation
- **类型**`String`
- **默认值**`side` - **类型**`String`
- **默认值**`side`
- **详情**:页面布局类型,可选有 `side``top``mixin` - **详情**:页面布局类型,可选有 `side``top``mixin`
### fixedHeader ### fixedHeader
- **类型**`Boolean`
- **默认值**`false` - **类型**`Boolean`
- **默认值**`false`
- **详情**:是否固定头部,不跟随页面滚动。 - **详情**:是否固定头部,不跟随页面滚动。
### fixedSideBar ### fixedSideBar
- **类型**`Boolean`
- **默认值**`true` - **类型**`Boolean`
- **默认值**`true`
- **详情**是否固定sidebar不跟随页面滚动。 - **详情**:是否固定 sidebar不跟随页面滚动。
### title ### title
- **类型**`String`
- **默认值**`name` in package.json - **类型**`String`
- **默认值**`name` in package.json
- **详情**:产品名,会显示在 Logo 旁边。 - **详情**:产品名,会显示在 Logo 旁边。
### logo ### logo
- **类型**`String`
- **默认值**:默认提供 fes.js 的 Logo - **类型**`String`
- **默认值**:默认提供 fes.js 的 Logo
- **详情**Logo的链接
- **详情**Logo 的链接
### multiTabs ### multiTabs
- **类型**`boolean`
- **默认值**`false` - **类型**`boolean`
- **默认值**`false`
- **详情**:是否开启多页。 - **详情**:是否开启多页。
### menus ### menus
- **类型**`Array`
- **默认值**`[]` - **类型**`Array`
- **默认值**`[]`
- **详情**:菜单配置,子项具体配置如下: - **详情**:菜单配置,子项具体配置如下:
- **name**:菜单的名称。通过匹配 `name` 和路由元信息 [meta](../../../guide/route.md#扩展路由元信息) 中的 `name`,把菜单和路由关联起来,然后使用路由元信息补充菜单配置,比如 `title``path` 等。 - **name**:菜单的名称。通过匹配 `name` 和路由元信息 [meta](../../../guide/route.md#扩展路由元信息) 中的 `name`,把菜单和路由关联起来, 然后使用路由元信息补充菜单配置,比如 `title``path`  等。
- **path**:菜单的路径,可配置第三方地址。 - **path**:菜单的路径,可配置第三方地址。
- **match**:额外匹配的路径,当前路由命中匹配规则时,此菜单高亮。 (v4.0.0+ - **match**:额外匹配的路径,当前路由命中匹配规则时,此菜单高亮。 (v4.0.0+
``` ```
{ {
@ -200,122 +214,139 @@ export default {
} }
``` ```
- **title**:菜单的标题,如果同时使用[国际化插件](./locale.md),而且`title`的值以`$`开头,则使用`$`后面的内容去匹配语言设置。 - **title**:菜单的标题,如果同时使用[国际化插件](./locale.md),而且`title`的值以`$`开头,则使用`$`后面的内容去匹配语言设置。
- **icon**: 菜单的图标,只有一级标题展示图标。
- 图标使用[fes-design icon](https://fes-design-4gvn317r3b6bfe17-1254145788.ap-shanghai.app.tcloudbase.com/zh/components/icon.html),在这里使用组件名称。
- **icon**: 菜单的图标,只有一级标题展示图标。
- 图标使用[fes-design icon](https://fes-design-4gvn317r3b6bfe17-1254145788.ap-shanghai.app.tcloudbase.com/zh/components/icon.html),在这里使用组件名称。
```js ```js
{ {
icon: "AppstoreOutlined" icon: 'AppstoreOutlined';
} }
``` ```
- 图标使用本地或者远程svg图片。 - 图标使用本地或者远程svg图片。
```js ```js
{ {
icon: "/wine-outline.svg" icon: '/wine-outline.svg';
} }
``` ```
- **children**:子菜单配置。 - **children**:子菜单配置。
### menusConfig ### menusConfig
- **类型**`Object`
- **默认值**`{}` - **类型**`Object`
- **默认值**`{}`
- **详情**:菜单的配置: - **详情**:菜单的配置:
- **defaultExpandAll**:是否默认展开全部菜单。 - **defaultExpandAll**:是否默认展开全部菜单。
- **expandedKeys**:配置默认展开的菜单,需要传子项是菜单路径的数组。 - **expandedKeys**:配置默认展开的菜单,需要传子项是菜单路径的数组。
- **accordion**:是否只保持一个子菜单的展开。 - **accordion**:是否只保持一个子菜单的展开。
## 运行时配置 ## 运行时配置
`app.js` 中配置: `app.js` 中配置:
```js ```js
import UserCenter from '@/components/UserCenter'; import UserCenter from '@/components/UserCenter';
export const layout = { export const layout = {
customHeader: <UserCenter /> customHeader: <UserCenter />
}; };
``` ```
### menus ### menus
- **类型**`(defaultMenus: [] )=> Ref | []`
- **详情**:运行时修改菜单,入参是默认菜单配置(.fes.js中的menu配置需要返回一个`Ref`或者数组。 - **类型**`(defaultMenus: [] )=> Ref | []`
- **详情**:运行时修改菜单,入参是默认菜单配置(.fes.js 中的 menu 配置),需要返回一个`Ref`或者数组。
```js ```js
import { ClusterOutlined } from '@fesjs/fes-design/icon' import { ClusterOutlined } from '@fesjs/fes-design/icon';
export const layout = layoutConfig => ({ export const layout = (layoutConfig) => ({
...layoutConfig, ...layoutConfig,
customHeader: <UserCenter />, customHeader: <UserCenter />,
menus: (defaultMenuData) => { menus: (defaultMenuData) => {
const menusRef = ref(defaultMenuData); const menusRef = ref(defaultMenuData);
watch(() => layoutConfig.initialState.userName, () => { watch(
menusRef.value = [{ () => layoutConfig.initialState.userName,
name: 'store', () => {
icon: <ClusterOutlined /> menusRef.value = [
}]; {
}); name: 'store',
icon: <ClusterOutlined />
}
];
}
);
return menusRef; return menusRef;
} }
}); });
``` ```
`layoutConfig.initialState``beforeRender.action`执行后创建的应用初始状态数据。 `layoutConfig.initialState``beforeRender.action`执行后创建的应用初始状态数据。
如果菜单需要根据某些状态动态改变,则返回`Ref`,否则只需要返回数组。 如果菜单需要根据某些状态动态改变,则返回`Ref`,否则只需要返回数组。
:::tip :::tip
在运行时配置菜单中的icon需要传组件本身而不是组件的名称。 在运行时配置菜单中的 icon需要传组件本身而不是组件的名称。
::: :::
### header ### header
- **类型**`String`
- **默认值**`true` - **类型**`String`
- **默认值**`true`
- **详情**:是否显示 header 区域。 - **详情**:是否显示 header 区域。
### sidebar ### sidebar
- **类型**`String`
- **默认值**`true` - **类型**`String`
- **默认值**`true`
- **详情**:是否显示 sidebar 区域。 - **详情**:是否显示 sidebar 区域。
### logo ### logo
- **类型**`String`
- **默认值**`true` - **类型**`String`
- **默认值**`true`
- **详情**:是否显示 logo 区域。 - **详情**:是否显示 logo 区域。
### onClickLogo
- **类型**`Function`
- **默认值**`null`
- **详情**logo 点击事件
### customHeader ### customHeader
- **类型**Vue Component
- **默认值**`null` - **类型**Vue Component
- **默认值**`null`
- **详情**top的区域部分位置提供组件自定义功能。 - **详情**top 的区域部分位置提供组件自定义功能。
### unAccessHandler ### unAccessHandler
- **类型**`Function`
- **默认值**`null` - **类型**`Function`
- **默认值**`null`
- **详情** - **详情**
当进入某个路由时,如果路由对应的页面不属于可见资源列表,则会暂停进入,调用 `unAccessHandler` 函数。 当进入某个路由时,如果路由对应的页面不属于可见资源列表,则会暂停进入,调用 `unAccessHandler` 函数。
- **参数**
- routercreateRouter 创建的路由实例 - **参数**
- to 准备进入的路由 - routercreateRouter 创建的路由实例
- from离开的路由 - to 准备进入的路由
- next [next函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next) - from离开的路由
- next [next 函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next)
比如: 比如:
```js ```js
export const access = { export const access = {
unAccessHandler({ to, next }) { unAccessHandler({ to, next }) {
@ -330,24 +361,25 @@ export const access = {
next('/403'); next('/403');
} }
}; };
``` ```
### noFoundHandler ### noFoundHandler
- **类型**:函数
- **默认值**null - **类型**:函数
- **默认值**null
- **详情** - **详情**
当进入某个路由时,如果路由对应的页面不存在,则会调用 `noFoundHandler` 函数。 当进入某个路由时,如果路由对应的页面不存在,则会调用 `noFoundHandler` 函数。
- **参数**
- routercreateRouter 创建的路由实例 - **参数**
- to 准备进入的路由 - routercreateRouter 创建的路由实例
- from离开的路由 - to 准备进入的路由
- next [next函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next) - from离开的路由
- next [next 函数](https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%8F%AF%E9%80%89%E7%9A%84%E7%AC%AC%E4%B8%89%E4%B8%AA%E5%8F%82%E6%95%B0-next)
比如: 比如:
```js ```js
export const access = { export const access = {
noFoundHandler({ next }) { noFoundHandler({ next }) {
@ -358,16 +390,15 @@ export const access = {
next('/404'); next('/404');
} }
}; };
``` ```
### logoUrl ### logoUrl
- **类型**`String`
- **默认值**:默认提供 fes.js 的 Logo - **类型**`String`
- **默认值**:默认提供 fes.js 的 Logo
- **详情**Logo的链接。
- **详情**Logo 的链接。
### 其他运行时配置 (> 4.1.0) ### 其他运行时配置 (> 4.1.0)
编译时配置的内容同样支持在运行时配置,但是`logo`除外,用`logoUrl`替代。 编译时配置的内容同样支持在运行时配置,但是`logo`除外,用`logoUrl`替代。

View File

@ -1,6 +1,6 @@
{ {
"name": "@fesjs/plugin-layout", "name": "@fesjs/plugin-layout",
"version": "4.2.4", "version": "4.2.5",
"description": "@fesjs/plugin-layout", "description": "@fesjs/plugin-layout",
"main": "lib/index.js", "main": "lib/index.js",
"files": [ "files": [

View File

@ -10,10 +10,18 @@
collapsible collapsible
:inverted="theme === 'dark'" :inverted="theme === 'dark'"
> >
<div v-if="routeLayout.logo" class="layout-logo"> <a
v-if="routeLayout.logo"
:class="[
'layout-logo',
routeLayout.onClickLogo && 'is-active'
]"
@click="routeLayout.onClickLogo"
>
<img :src="logo" class="logo-img" /> <img :src="logo" class="logo-img" />
<div class="logo-name">{{title}}</div> <span class="logo-name">{{title}}</span>
</div> </a>
<Menu <Menu
class="layout-menu" class="layout-menu"
:menus="menus" :menus="menus"
@ -25,10 +33,7 @@
:accordion="menuConfig?.accordion" :accordion="menuConfig?.accordion"
/> />
</f-aside> </f-aside>
<f-layout <f-layout :fixed="fixedSideBar" :style="sideStyleRef">
:fixed="fixedSideBar"
:style="sideStyleRef"
>
<f-header <f-header
v-if="routeLayout.header" v-if="routeLayout.header"
ref="headerRef" ref="headerRef"
@ -64,10 +69,17 @@
:inverted="theme === 'dark'" :inverted="theme === 'dark'"
:fixed="currentFixedHeaderRef" :fixed="currentFixedHeaderRef"
> >
<div v-if="routeLayout.logo" class="layout-logo"> <a
v-if="routeLayout.logo"
:class="[
'layout-logo',
routeLayout.onClickLogo && 'is-active'
]"
@click="routeLayout.onClickLogo"
>
<img :src="logo" class="logo-img" /> <img :src="logo" class="logo-img" />
<div class="logo-name">{{title}}</div> <span class="logo-name">{{title}}</span>
</div> </a>
<Menu <Menu
class="layout-menu" class="layout-menu"
:menus="menus" :menus="menus"
@ -105,10 +117,17 @@
:fixed="currentFixedHeaderRef" :fixed="currentFixedHeaderRef"
:inverted="theme === 'dark'" :inverted="theme === 'dark'"
> >
<div v-if="routeLayout.logo" class="layout-logo"> <a
v-if="routeLayout.logo"
:class="[
'layout-logo',
routeLayout.onClickLogo && 'is-active'
]"
@click="routeLayout.onClickLogo"
>
<img :src="logo" class="logo-img" /> <img :src="logo" class="logo-img" />
<div class="logo-name">{{title}}</div> <span class="logo-name">{{title}}</span>
</div> </a>
<div class="layout-header-custom"> <div class="layout-header-custom">
<slot name="customHeader"></slot> <slot name="customHeader"></slot>
</div> </div>
@ -160,8 +179,8 @@ import {
FLayout, FAside, FMain, FFooter, FHeader FLayout, FAside, FMain, FFooter, FHeader
} from '@fesjs/fes-design'; } from '@fesjs/fes-design';
import Menu from './Menu'; import Menu from './Menu';
import MultiTabProvider from './MultiTabProvider';
import defaultLogo from '../assets/logo.png'; import defaultLogo from '../assets/logo.png';
import MultiTabProvider from './MultiTabProvider';
import getRuntimeConfig from '../helpers/getRuntimeConfig'; import getRuntimeConfig from '../helpers/getRuntimeConfig';
export default { export default {
@ -264,7 +283,9 @@ export default {
const currentFixedHeaderRef = computed( const currentFixedHeaderRef = computed(
() => props.fixedHeader || props.navigation === 'mixin' () => props.fixedHeader || props.navigation === 'mixin'
); );
const headerStyleRef = computed(() => (currentFixedHeaderRef.value ? { top: `${headerHeightRef.value}px` } : null)); const headerStyleRef = computed(() => (currentFixedHeaderRef.value
? { top: `${headerHeightRef.value}px` }
: null));
const sideStyleRef = computed(() => (props.fixedSideBar const sideStyleRef = computed(() => (props.fixedSideBar
? { ? {
left: collapsedRef.value ? '48px' : `${props.sideWidth}px` left: collapsedRef.value ? '48px' : `${props.sideWidth}px`
@ -286,6 +307,11 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.main-layout { .main-layout {
height: 100vh; height: 100vh;
.layout-logo {
&.is-active {
cursor: pointer;
}
}
.layout-main { .layout-main {
z-index: 0; z-index: 0;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@fesjs/plugin-watermark", "name": "@fesjs/plugin-watermark",
"version": "2.1.1", "version": "2.1.2",
"description": "@fesjs/plugin-watermark", "description": "@fesjs/plugin-watermark",
"main": "lib/index.js", "main": "lib/index.js",
"files": [ "files": [

View File

@ -30,40 +30,39 @@ function timeFormat(date, format = 'YYYY-MM-DD') {
return format.replace(/Y+|M+|D+|H+|h+|m+|s+|S+|Q/g, str => String(map[str])); return format.replace(/Y+|M+|D+|H+|h+|m+|s+|S+|Q/g, str => String(map[str]));
} }
const defaultOption = { let wmContainerObx = null; // MutationObserver
content: '请勿外传', let wmObx = null; // MutationObserver
container: document.body, let wmTimer = null; // timestamp
width: 300, let watermarkDiv = null;
height: 300,
textAlign: 'center',
textBaseline: 'middle',
fontSize: '14px',
fontFamily: 'Microsoft Yahei',
fillStyle: 'rgba(184, 184, 184, 0.3)',
rotate: 25,
zIndex: 99999,
timestamp: 'YYYY-MM-DD HH:mm'
};
let _wmMo = null; // MutationObserver // 销毁水印
let _wmTimer = null; // timestamp export function destroyWatermark() {
// 监听器关闭
wmObx?.disconnect();
wmObx = null;
wmContainerObx?.disconnect();
wmContainerObx = null;
// 清除timer
if (wmTimer) {
window.clearTimeout(wmTimer);
wmTimer = null;
}
// 删除水印元素
watermarkDiv?.parentNode?.removeChild(watermarkDiv);
watermarkDiv = null;
}
function _createWatermark(param) { function innerCreateWatermark(param) {
const { const {
content, content, container, width, height, textAlign, textBaseline, fontSize, fontFamily, fillStyle, rotate, zIndex, timestamp, watch
container,
width,
height,
textAlign,
textBaseline,
fontSize,
fontFamily,
fillStyle,
rotate,
zIndex,
timestamp
} = param; } = param;
if (!container) {
return console.warn('createWatermark配置的container不能为空');
}
destroyWatermark();
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.setAttribute('width', `${width}px`); canvas.setAttribute('width', `${width}px`);
canvas.setAttribute('height', `${height}px`); canvas.setAttribute('height', `${height}px`);
@ -75,19 +74,14 @@ function _createWatermark(param) {
ctx.fillStyle = fillStyle; ctx.fillStyle = fillStyle;
ctx.translate(width / 2, height / 2); ctx.translate(width / 2, height / 2);
ctx.rotate(-(Math.PI / 180) * rotate); ctx.rotate(-(Math.PI / 180) * rotate);
ctx.fillText( ctx.fillText(`${content}`, 0, 0);
`${content}`, if (timestamp) {
0, ctx.fillText(`${timeFormat(new Date(), timestamp)}`, 0, parseInt(fontSize) + 5);
0 }
);
timestamp && ctx.fillText(
`${timeFormat(new Date(), timestamp)}`,
0,
parseInt(fontSize) + 5
);
let __wm = document.querySelector('.__wm'); const CLASS_NAME = `wm_${Date.now()}`;
const watermarkDiv = __wm || document.createElement('div');
watermarkDiv = document.createElement('div');
const styleStr = ` const styleStr = `
position: ${container === document.body ? 'fixed' : 'absolute'}; position: ${container === document.body ? 'fixed' : 'absolute'};
user-select: none; user-select: none;
@ -99,31 +93,36 @@ function _createWatermark(param) {
pointer-events: none !important; pointer-events: none !important;
background-repeat: repeat; background-repeat: repeat;
background-image: url('${canvas.toDataURL()}')`; background-image: url('${canvas.toDataURL()}')`;
watermarkDiv.setAttribute('style', styleStr); watermarkDiv.setAttribute('style', styleStr);
watermarkDiv.classList.add('__wm'); watermarkDiv.classList.add(CLASS_NAME);
if (!__wm) { if (container.firstChild) {
container.insertBefore(watermarkDiv, container.firstChild); container.insertBefore(watermarkDiv, container.firstChild);
} else {
container.appendChild(watermarkDiv);
} }
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; const MutationObserver = window.MutationObserver;
if (MutationObserver) { if (watch && MutationObserver) {
_wmMo = new MutationObserver(() => { wmContainerObx = new MutationObserver(() => {
__wm = document.querySelector('.__wm'); if (!container.querySelector(`.${CLASS_NAME}`)) {
if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) { innerCreateWatermark(param);
// 避免一直触发
_wmMo.disconnect();
_wmMo = null;
_createWatermark(param);
} }
}); });
_wmMo.observe(container, { wmContainerObx.observe(container, {
attributes: true,
subtree: true,
childList: true childList: true
}); });
wmObx = new MutationObserver(() => {
if (watermarkDiv.getAttribute('style') !== styleStr) {
innerCreateWatermark(param);
}
});
wmObx.observe(watermarkDiv, {
attributes: true
});
} }
if (timestamp) { if (timestamp) {
@ -136,26 +135,12 @@ function _createWatermark(param) {
timeout = 1000 * 60 * 60; timeout = 1000 * 60 * 60;
} }
_wmTimer = window.setTimeout(() => { wmTimer = window.setTimeout(() => {
// 触发 MutationObserver innerCreateWatermark(param);
watermarkDiv.style.bottom = '0';
}, timeout); }, timeout);
} }
} }
// 销毁水印
export function destroyWatermark() {
// 监听器关闭
_wmMo && _wmMo.disconnect();
_wmMo = null;
_wmTimer && window.clearTimeout(_wmTimer);
_wmTimer = null;
// 删除水印元素
const __wm = document.querySelector('.__wm');
__wm && __wm.parentNode.removeChild(__wm);
}
// canvas 实现 watermark // canvas 实现 watermark
export function createWatermark(option) { export function createWatermark(option) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -163,10 +148,23 @@ export function createWatermark(option) {
return; return;
} }
// 为避免多次调用 createWatermark 触发重复监听,这里先执行销毁水印操作 const defaultOption = {
destroyWatermark(); content: '请勿外传',
container: document.body,
width: 300,
height: 300,
textAlign: 'center',
textBaseline: 'middle',
fontSize: '14px',
fontFamily: 'Microsoft Yahei',
fillStyle: 'rgba(184, 184, 184, 0.3)',
rotate: 25,
zIndex: 99999,
timestamp: 'YYYY-MM-DD HH:mm',
watch: true
};
_createWatermark({ innerCreateWatermark({
...defaultOption, ...defaultOption,
...option ...option
}); });

View File

@ -1,6 +1,8 @@
import { access as accessApi, pinia, createWatermark } from '@fesjs/fes'; import {
access as accessApi, pinia, getRouter, createWatermark
} from '@fesjs/fes';
import PageLoading from '@/components/PageLoading'; import PageLoading from '@/components/PageLoading';
import UserCenter from '@/components/UserCenter'; import UserCenter from '@/components/UserCenter';
import { useStore } from '@/store/main'; import { useStore } from '@/store/main';
@ -29,6 +31,10 @@ export const beforeRender = {
export const layout = layoutConfig => ({ export const layout = layoutConfig => ({
...layoutConfig, ...layoutConfig,
customHeader: <UserCenter />, customHeader: <UserCenter />,
onClickLogo() {
const router = getRouter();
router.push('/');
},
menus: (defaultMenuData) => { menus: (defaultMenuData) => {
const menusRef = ref(defaultMenuData); const menusRef = ref(defaultMenuData);
// watch(() => initialValue.initialState.userName, () => { // watch(() => initialValue.initialState.userName, () => {

3708
yarn.lock

File diff suppressed because it is too large Load Diff