水印模块

  1. import { createGlobalStyle } from 'styled-components'
  2. // 卡片水印
  3. // 防止表格遮挡水印
  4. const Water = createGlobalStyle`
  5. .ant-card {
  6. // prettier-ignore
  7. background-position: 0 0, 160PX 160PX !important;
  8. background-repeat: repeat, repeat !important;
  9. background-image: url('${window.location.origin}/script/account/print.svg'), url('${window.location.origin}/script/account/print.svg') !important;
  10. }
  11. .ant-table-placeholder {
  12. background: transparent !important;
  13. }
  14. `
  15. export default Water

获取水印

app/router.js

  1. router.get('/script/account/print.svg', controller.script.account.print)

app/controller/script/account.js

  1. 'use strict'
  2. const { Controller } = require('egg')
  3. const path = require('path')
  4. const TextToSVG = require('text-to-svg')
  5. const textToSVG = TextToSVG.loadSync(path.resolve(__dirname, '../../fonts/SourceHanSansCN-Light.otf'))
  6. const attributes = { fill: '#ededed', stroke: '#ededed', transform: 'rotate(-20, 90 75)' }
  7. const options = { anchor: 'center middle', x: 160, y: 160, width: 320, height: 320, fontSize: 18, letterSpacing: 0.05, attributes }
  8. const getSvgContent = text => {
  9. const svgPath = textToSVG.getPath(text, options)
  10. 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>`
  11. }
  12. const loginRules = {
  13. token: {
  14. type: 'string',
  15. required: true,
  16. },
  17. }
  18. class EggController extends Controller {
  19. async print() {
  20. const accountInfo = await this.ctx.adminUser.getAccountInfo()
  21. const { isLogin, name, mobile } = accountInfo
  22. const content = isLogin ? `${name}${String(mobile).slice(7)}` : '小帮规划'
  23. this.ctx.type = 'image/svg+xml'
  24. this.ctx.body = getSvgContent(content)
  25. }
  26. async infojs() {
  27. const token = await this.ctx.cookieUtils.getToken()
  28. const accountInfo = await this.ctx.adminUser.getAccountInfo()
  29. const info = Object.assign({ token }, accountInfo)
  30. this.ctx.type = 'application/javascript'
  31. this.ctx.body = `
  32. window.APP_ACCOUNT = ${JSON.stringify(info)};
  33. `
  34. }
  35. async login() {
  36. try {
  37. this.ctx.validator.validateBody(loginRules)
  38. } catch (error) {
  39. return this.ctx.reject(10004, error.message)
  40. }
  41. const { token } = this.ctx.request.body
  42. this.ctx.cookieUtils.setToken(token)
  43. return this.ctx.resolve()
  44. }
  45. async logout() {
  46. const { deprecateTokenKey } = this.app.config
  47. this.ctx.cookieUtils.removeToken()
  48. this.ctx.cookies.set(deprecateTokenKey, null, this.ctx.cookieUtils.cookieOptions)
  49. return this.ctx.redirect('/')
  50. }
  51. }
  52. module.exports = EggController

依赖

package.json

  1. "@types/styled-components": "^5.0.1",
  2. "babel-plugin-styled-components": "^1.10.7",
  3. "styled-components": "^5.0.1",

配置

.umirc.js

  1. extraBabelPlugins: [
  2. [
  3. 'babel-plugin-styled-components',
  4. {
  5. ssr: false,
  6. fileName: false,
  7. displayName: false,
  8. },
  9. ],
  10. ],

使用

  1. import { useState, useEffect, Fragment } from 'react'
  2. import { ConfigProvider } from 'antd'
  3. import ProLayout from '@ant-design/pro-layout'
  4. import Dashboard from '@xb/layouts/Dashboard'
  5. import Water from '@xb/components/Water'
  6. import PageLoading from '@xb/components/PageLoading'
  7. import HeaderContent from '@xb/components/HeaderContent'
  8. import router from 'umi/router'
  9. import { registerApplication, start } from 'single-spa'
  10. import { connect } from 'dva'
  11. import iconLogo from '@xb/assets/logo.gif'
  12. // 布局默认样式配置
  13. const layoutSettings = {
  14. navTheme: 'dark',
  15. primaryColor: '#1890ff',
  16. layout: 'sidemenu',
  17. contentWidth: 'Fluid',
  18. fixedHeader: true,
  19. autoHideHeader: false,
  20. fixSiderbar: true,
  21. menu: {
  22. locale: true,
  23. },
  24. title: '小帮规划',
  25. pwa: false,
  26. colorWeak: false,
  27. }
  28. const { mobileLayoutPrefix, wxworkAuthBlackList, menuList, subappList } = window.APP_STATE
  29. // 获取扁平菜单标识
  30. const getFlattenKeys = menuList => {
  31. let flattenKeys = []
  32. menuList.forEach(item => {
  33. flattenKeys.push({
  34. path: item.path,
  35. keys: item.keys,
  36. })
  37. if (item.children) {
  38. flattenKeys = [...flattenKeys, ...getFlattenKeys(item.children)]
  39. }
  40. })
  41. return flattenKeys
  42. }
  43. // 判断当前菜单是否展开
  44. const isMenuExpanded = (currentKeys = [], menuKeys = []) => {
  45. const currentKey = currentKeys.join(',')
  46. const menuKey = menuKeys.join(',')
  47. return currentKey.indexOf(menuKey) !== -1
  48. }
  49. // 获取父级菜单展开键
  50. const getFatherKeys = (keys = []) => {
  51. return keys.filter((item, index) => index + 1 !== keys.length)
  52. }
  53. // 获取默认展开菜单
  54. const flattenKeys = getFlattenKeys(menuList)
  55. const defaultMenu = flattenKeys.find(item => item.path === window.location.pathname)
  56. const defaultOpenKeys = defaultMenu ? defaultMenu.keys : []
  57. const BasicLayout = ({ collapsed, location, dispatch }) => {
  58. const [openKeys, setOpenKeys] = useState(defaultOpenKeys)
  59. const { pathname } = location
  60. // 获取应用名称
  61. const getAppByPath = pathname => {
  62. const row = subappList.find(({ manifest }) => {
  63. return manifest.paths.some(prefix => pathname.startsWith(prefix))
  64. })
  65. return Object.assign({}, row)
  66. }
  67. // 应用注册
  68. useEffect(() => {
  69. subappList.forEach(({ name, manifest }) => {
  70. const loadingFunction = () => window.System.import(manifest.mainScript)
  71. const activityFunction = location => {
  72. const isMatched = manifest.paths.some(prefix => location.pathname.startsWith(prefix))
  73. return isMatched
  74. }
  75. registerApplication(name, loadingFunction, activityFunction)
  76. start()
  77. })
  78. }, [])
  79. // 侧边栏开关
  80. const onCollapse = collapsed => {
  81. dispatch({
  82. type: 'global/changeLayoutCollapsed',
  83. payload: Boolean(collapsed),
  84. })
  85. }
  86. // 获取当前子应用
  87. const { name: sourceAppName, manifest: sourceManifest } = getAppByPath(pathname)
  88. const hasMatched = Boolean(sourceAppName && sourceManifest)
  89. // 菜单数据
  90. const menuDataRender = () => menuList
  91. // 父级菜单
  92. const subMenuItemRender = (menuItemProps, defaultDom) => {
  93. const onMenuItemClick = () => {
  94. const isExpanded = isMenuExpanded(openKeys, menuItemProps.keys)
  95. const keys = isExpanded ? getFatherKeys(menuItemProps.keys) : menuItemProps.keys
  96. setOpenKeys(keys)
  97. }
  98. return (
  99. <div style={{ overflow: 'hidden' }} onClick={onMenuItemClick}>
  100. {defaultDom}
  101. </div>
  102. )
  103. }
  104. // 基础菜单
  105. // 参考文档 http://docs.xiaobangtouzi.com/pages/viewpage.action?pageId=6529045
  106. const menuItemRender = (menuItemProps, defaultDom) => {
  107. const onItemClick = () => {
  108. setOpenKeys(menuItemProps.keys)
  109. const { name: targetAppName } = getAppByPath(menuItemProps.path)
  110. const httpReload = sourceAppName !== targetAppName
  111. const isReplacePath = pathname === menuItemProps.path
  112. switch (menuItemProps.type) {
  113. case 0:
  114. return window.open(window.APP_HERO.Linker.getDeprecatedAdminUrl(menuItemProps.path))
  115. case 1:
  116. return window.open(window.APP_HERO.Linker.getInsuranceAdminUrl(menuItemProps.path))
  117. case 2:
  118. if (httpReload) {
  119. return isReplacePath ? window.location.replace(menuItemProps.path) : window.open(menuItemProps.path, '_self')
  120. }
  121. return isReplacePath ? router.replace({ pathname: menuItemProps.path }) : router.push({ pathname: menuItemProps.path })
  122. case 3:
  123. return window.open(menuItemProps.originalUrl)
  124. default:
  125. return null
  126. }
  127. }
  128. return (
  129. <div style={{ overflow: 'hidden' }} onClick={onItemClick}>
  130. {defaultDom}
  131. </div>
  132. )
  133. }
  134. const children = (
  135. <Fragment>
  136. <Water />
  137. <div id='sub-app'>
  138. {/* 首页友情提示 */}
  139. {pathname === '/' && <Dashboard />}
  140. {/* 子应用匹配成功显示默认加载动画 */}
  141. {hasMatched && <PageLoading />}
  142. </div>
  143. {/* 异步加载样式 */}
  144. {hasMatched && <link rel='stylesheet' href={sourceManifest.mainStyle} />}
  145. </Fragment>
  146. )
  147. // 没有侧边栏
  148. const withoutLayout = wxworkAuthBlackList.includes(pathname) || pathname.startsWith(mobileLayoutPrefix)
  149. if (withoutLayout) {
  150. return <ConfigProvider autoInsertSpaceInButton={false}>{children}</ConfigProvider>
  151. }
  152. return (
  153. <ConfigProvider autoInsertSpaceInButton={false}>
  154. <ProLayout
  155. menuProps={{ openKeys }}
  156. logo={iconLogo}
  157. settings={layoutSettings}
  158. collapsed={collapsed}
  159. onCollapse={onCollapse}
  160. subMenuItemRender={subMenuItemRender}
  161. menuItemRender={menuItemRender}
  162. menuDataRender={menuDataRender}
  163. footerRender={false}
  164. rightContentRender={props => <HeaderContent {...props} />}
  165. {...layoutSettings}
  166. >
  167. {children}
  168. </ProLayout>
  169. </ConfigProvider>
  170. )
  171. }
  172. const connector = ({ global: { collapsed } }) => ({ collapsed })
  173. export default connect(connector)(BasicLayout)