# 图片水印

# 水印模块

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)