# 图片水印
# 水印模块
import { createGlobalStyle } from 'styled-components'
// 卡片水印
// 防止表格遮挡水印
const Water = createGlobalStyle`
.ant-card {
// prettier-ignore
background-position: 0 0, 160PX 160PX !important;
background-repeat: repeat, repeat !important;
background-image: url('${window.location.origin}/script/account/print.svg'), url('${window.location.origin}/script/account/print.svg') !important;
}
.ant-table-placeholder {
background: transparent !important;
}
`
export default Water
# 获取水印
app/router.js
router.get('/script/account/print.svg', controller.script.account.print)
app/controller/script/account.js
'use strict'
const { Controller } = require('egg')
const path = require('path')
const TextToSVG = require('text-to-svg')
const textToSVG = TextToSVG.loadSync(path.resolve(__dirname, '../../fonts/SourceHanSansCN-Light.otf'))
const attributes = { fill: '#ededed', stroke: '#ededed', transform: 'rotate(-20, 90 75)' }
const options = { anchor: 'center middle', x: 160, y: 160, width: 320, height: 320, fontSize: 18, letterSpacing: 0.05, attributes }
const getSvgContent = text => {
const svgPath = textToSVG.getPath(text, options)
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${options.width}" height="${options.height}">${svgPath}</svg>`
}
const loginRules = {
token: {
type: 'string',
required: true,
},
}
class EggController extends Controller {
async print() {
const accountInfo = await this.ctx.adminUser.getAccountInfo()
const { isLogin, name, mobile } = accountInfo
const content = isLogin ? `${name}${String(mobile).slice(7)}` : '小帮规划'
this.ctx.type = 'image/svg+xml'
this.ctx.body = getSvgContent(content)
}
async infojs() {
const token = await this.ctx.cookieUtils.getToken()
const accountInfo = await this.ctx.adminUser.getAccountInfo()
const info = Object.assign({ token }, accountInfo)
this.ctx.type = 'application/javascript'
this.ctx.body = `
window.APP_ACCOUNT = ${JSON.stringify(info)};
`
}
async login() {
try {
this.ctx.validator.validateBody(loginRules)
} catch (error) {
return this.ctx.reject(10004, error.message)
}
const { token } = this.ctx.request.body
this.ctx.cookieUtils.setToken(token)
return this.ctx.resolve()
}
async logout() {
const { deprecateTokenKey } = this.app.config
this.ctx.cookieUtils.removeToken()
this.ctx.cookies.set(deprecateTokenKey, null, this.ctx.cookieUtils.cookieOptions)
return this.ctx.redirect('/')
}
}
module.exports = EggController
# 依赖
package.json
"@types/styled-components": "^5.0.1",
"babel-plugin-styled-components": "^1.10.7",
"styled-components": "^5.0.1",
# 配置
.umirc.js
extraBabelPlugins: [
[
'babel-plugin-styled-components',
{
ssr: false,
fileName: false,
displayName: false,
},
],
],
# 使用
import { useState, useEffect, Fragment } from 'react'
import { ConfigProvider } from 'antd'
import ProLayout from '@ant-design/pro-layout'
import Dashboard from '@xb/layouts/Dashboard'
import Water from '@xb/components/Water'
import PageLoading from '@xb/components/PageLoading'
import HeaderContent from '@xb/components/HeaderContent'
import router from 'umi/router'
import { registerApplication, start } from 'single-spa'
import { connect } from 'dva'
import iconLogo from '@xb/assets/logo.gif'
// 布局默认样式配置
const layoutSettings = {
navTheme: 'dark',
primaryColor: '#1890ff',
layout: 'sidemenu',
contentWidth: 'Fluid',
fixedHeader: true,
autoHideHeader: false,
fixSiderbar: true,
menu: {
locale: true,
},
title: '小帮规划',
pwa: false,
colorWeak: false,
}
const { mobileLayoutPrefix, wxworkAuthBlackList, menuList, subappList } = window.APP_STATE
// 获取扁平菜单标识
const getFlattenKeys = menuList => {
let flattenKeys = []
menuList.forEach(item => {
flattenKeys.push({
path: item.path,
keys: item.keys,
})
if (item.children) {
flattenKeys = [...flattenKeys, ...getFlattenKeys(item.children)]
}
})
return flattenKeys
}
// 判断当前菜单是否展开
const isMenuExpanded = (currentKeys = [], menuKeys = []) => {
const currentKey = currentKeys.join(',')
const menuKey = menuKeys.join(',')
return currentKey.indexOf(menuKey) !== -1
}
// 获取父级菜单展开键
const getFatherKeys = (keys = []) => {
return keys.filter((item, index) => index + 1 !== keys.length)
}
// 获取默认展开菜单
const flattenKeys = getFlattenKeys(menuList)
const defaultMenu = flattenKeys.find(item => item.path === window.location.pathname)
const defaultOpenKeys = defaultMenu ? defaultMenu.keys : []
const BasicLayout = ({ collapsed, location, dispatch }) => {
const [openKeys, setOpenKeys] = useState(defaultOpenKeys)
const { pathname } = location
// 获取应用名称
const getAppByPath = pathname => {
const row = subappList.find(({ manifest }) => {
return manifest.paths.some(prefix => pathname.startsWith(prefix))
})
return Object.assign({}, row)
}
// 应用注册
useEffect(() => {
subappList.forEach(({ name, manifest }) => {
const loadingFunction = () => window.System.import(manifest.mainScript)
const activityFunction = location => {
const isMatched = manifest.paths.some(prefix => location.pathname.startsWith(prefix))
return isMatched
}
registerApplication(name, loadingFunction, activityFunction)
start()
})
}, [])
// 侧边栏开关
const onCollapse = collapsed => {
dispatch({
type: 'global/changeLayoutCollapsed',
payload: Boolean(collapsed),
})
}
// 获取当前子应用
const { name: sourceAppName, manifest: sourceManifest } = getAppByPath(pathname)
const hasMatched = Boolean(sourceAppName && sourceManifest)
// 菜单数据
const menuDataRender = () => menuList
// 父级菜单
const subMenuItemRender = (menuItemProps, defaultDom) => {
const onMenuItemClick = () => {
const isExpanded = isMenuExpanded(openKeys, menuItemProps.keys)
const keys = isExpanded ? getFatherKeys(menuItemProps.keys) : menuItemProps.keys
setOpenKeys(keys)
}
return (
<div style={{ overflow: 'hidden' }} onClick={onMenuItemClick}>
{defaultDom}
</div>
)
}
// 基础菜单
// 参考文档 http://docs.xiaobangtouzi.com/pages/viewpage.action?pageId=6529045
const menuItemRender = (menuItemProps, defaultDom) => {
const onItemClick = () => {
setOpenKeys(menuItemProps.keys)
const { name: targetAppName } = getAppByPath(menuItemProps.path)
const httpReload = sourceAppName !== targetAppName
const isReplacePath = pathname === menuItemProps.path
switch (menuItemProps.type) {
case 0:
return window.open(window.APP_HERO.Linker.getDeprecatedAdminUrl(menuItemProps.path))
case 1:
return window.open(window.APP_HERO.Linker.getInsuranceAdminUrl(menuItemProps.path))
case 2:
if (httpReload) {
return isReplacePath ? window.location.replace(menuItemProps.path) : window.open(menuItemProps.path, '_self')
}
return isReplacePath ? router.replace({ pathname: menuItemProps.path }) : router.push({ pathname: menuItemProps.path })
case 3:
return window.open(menuItemProps.originalUrl)
default:
return null
}
}
return (
<div style={{ overflow: 'hidden' }} onClick={onItemClick}>
{defaultDom}
</div>
)
}
const children = (
<Fragment>
<Water />
<div id='sub-app'>
{/* 首页友情提示 */}
{pathname === '/' && <Dashboard />}
{/* 子应用匹配成功显示默认加载动画 */}
{hasMatched && <PageLoading />}
</div>
{/* 异步加载样式 */}
{hasMatched && <link rel='stylesheet' href={sourceManifest.mainStyle} />}
</Fragment>
)
// 没有侧边栏
const withoutLayout = wxworkAuthBlackList.includes(pathname) || pathname.startsWith(mobileLayoutPrefix)
if (withoutLayout) {
return <ConfigProvider autoInsertSpaceInButton={false}>{children}</ConfigProvider>
}
return (
<ConfigProvider autoInsertSpaceInButton={false}>
<ProLayout
menuProps={{ openKeys }}
logo={iconLogo}
settings={layoutSettings}
collapsed={collapsed}
onCollapse={onCollapse}
subMenuItemRender={subMenuItemRender}
menuItemRender={menuItemRender}
menuDataRender={menuDataRender}
footerRender={false}
rightContentRender={props => <HeaderContent {...props} />}
{...layoutSettings}
>
{children}
</ProLayout>
</ConfigProvider>
)
}
const connector = ({ global: { collapsed } }) => ({ collapsed })
export default connect(connector)(BasicLayout)