Skip to content

Commit

Permalink
Merge pull request #1016 from XiaoMi/feature/watermark
Browse files Browse the repository at this point in the history
Feature/watermark
  • Loading branch information
solarjoker authored Mar 30, 2020
2 parents 44c65c9 + 272da22 commit 28dc4a6
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# 更新日志

## 2.12.0

- 新增 `<Watermark />` 水印组件 [#121](https://github.com/XiaoMi/hiui/issues/121)
- 修复 `<Tree />` 多选模式下在禁用状态复选框样式异常的问题 [#1014](https://github.com/XiaoMi/hiui/issues/1014)
- 修复 `<Select />` DataSource 配置 headers 参数无效的问题 [#1011](https://github.com/XiaoMi/hiui/issues/1011)

## 2.11.1

- 修复打包印度节假日数据问题
Expand Down
1 change: 1 addition & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ export { default as Message } from './message'
export { default as Tag } from './tag'
export { default as Breadcrumb } from './breadcrumb'
export { default as Carousel } from './carousel'
export { default as Watermark } from './watermark'
export { ThemeContext, LocaleContext } from './context'
1 change: 1 addition & 0 deletions components/select/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ class Select extends Component {
jsonpCallback = 'callback',
...options
} = _dataSource

keyword =
!keyword && this.autoloadFlag && autoload
? _dataSource.keyword
Expand Down
5 changes: 3 additions & 2 deletions components/table/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import loading from '../loading'
import '../pagination/style'
import '../icon/style'
import Provider from '../context'
import {setKey, scrollTop, getStyle, getPosition, offset} from './tool'
import { setKey, scrollTop, getStyle, getPosition, offset } from './tool'
import request from 'axios'
import qs from 'qs'

Expand Down Expand Up @@ -482,7 +482,7 @@ class Table extends Component {
<div className={prifix({table: true, [`theme__${theme}`]: true, [size]: size, bordered, striped})} ref={this.dom}>
{header && <div className={prifix({'table-pre-header': true})}>{header()}</div>}
<div className={prifix({'table-container': true})}>
<div >{content}</div>
<div>{content}</div>
{ name &&
<div className={prifix('table-setting')} ref={this.setting}>
<div onClick={(e) => {
Expand Down Expand Up @@ -896,6 +896,7 @@ class Table extends Component {
componentDidMount () {
let {fixTop, scroll, name, origin} = this.props
let dom = this.dom.current

if (fixTop) {
// 吸顶逻辑
document.addEventListener('scroll', () => {
Expand Down
1 change: 0 additions & 1 deletion components/table/tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export function insertAfter (newElement, targetElement) {
parent.insertBefore(newElement, targetElement.nextSibling)
}
}

export function setKey (data, name) {
name = name || 'id'
return data.map(item => {
Expand Down
58 changes: 58 additions & 0 deletions components/watermark/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import PropTypes from 'prop-types'
import WaterMarker from './watermark'
import _ from 'lodash'
class Watermark extends React.Component {
constructor (props) {
super(props)

this.rootRef = React.createRef()
}
componentDidMount () {
const container = this.rootRef.current
const options = _.cloneDeep(this.props)
delete options.children

WaterMarker(container, options)
}
render () {
return (
<div ref={this.rootRef} style={{ overflow: 'hidden' }}>
{this.props.children}
</div>
)
}
}

Watermark.propTypes = {
id: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
textAlign: PropTypes.string,
font: PropTypes.string,
color: PropTypes.string,
content: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
rotate: PropTypes.number,
zIndex: PropTypes.number,
logo: PropTypes.string,
grayLogo: PropTypes.bool,
isAutoWrap: PropTypes.bool,
textOverflowEffect: PropTypes.oneOfType(['zoom', 'cut'])
}

Watermark.defaultProps = {
id: null,
density: 'default',
textAlign: 'left',
font: '16px microsoft yahei',
color: 'rgba(128, 128, 128, 0.2)',
content: '请勿外传',
rotate: -30,
zIndex: 1000,
logo: null,
grayLogo: true,
isAutoWrap: false,
textOverflowEffect: 'zoom'
}
Watermark.generate = WaterMarker
export default Watermark
200 changes: 200 additions & 0 deletions components/watermark/watermark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
const defaultOptions = {
id: null,
textAlign: 'left',
font: '16px microsoft yahei',
color: 'rgba(128, 128, 128, 0.2)',
content: '请勿外传',
rotate: -30,
zIndex: 1000,
logo: null,
grayLogo: true, // 是否对图标进行灰度处理
isAutoWrap: false, // 文字是否自动换行
textOverflowEffect: 'zoom' // 当isAutoWrap 为 false 时,文本长度超出画布长度时的处理方式: zoom - 缩小文字 cut - 截断文字
}

const parseTextData = (ctx, texts, width, isWrap) => {
let content = []
let lines = []
if (texts instanceof Array) {
content = texts
} else if (typeof texts === 'string') {
content.push(texts)
} else {
console.warn('Only support String Or Array')
}
if (isWrap) {
content.forEach((text) => {
let curLine = ''
for (let char of text) {
let nextLine = curLine + char
if (char === '\n' || ctx.measureText(nextLine).width > width) {
lines.push(curLine)
curLine = char === '\n' ? '' : char
} else {
curLine = nextLine
}
}
lines.push(curLine)
})
return lines
} else {
return content
}
}
const drawText = (ctx, options) => {
let {
width,
_w = width,
height,
textOverflowEffect,
content: text,
isAutoWrap,
logo
} = options
let oldBaseLine = ctx.textBaseline
let x = 0
let y = 0
ctx.textBaseline = 'hanging'
/**
* LOGO 固定宽高: 32 * 32
* 内容区域为 画布宽度 - 48 (预留左右各24的 padding)
* 如含 LOGO ,文字的起始 X 坐标为: 24(padding-left) + 32(logo size) + 4(logo 与 text 间距)
*/
let lineHeight = parseInt(ctx.font) // ctx.font必须以'XXpx'开头
if (logo) {
x += 32
_w -= 32
}
const lines = parseTextData(ctx, text, width, isAutoWrap)

// 计算 Y 的起始位置
let lineY = y + (ctx.canvas.height) / 2 - (lineHeight * lines.length) / 2
const initLineY = lineY
for (let line of lines) {
let lineX
if (ctx.textAlign === 'center') {
lineX = x + width / 2 + 4
} else if (ctx.textAlign === 'right') {
lineX = x + width + 4
} else {
lineX = x + 4
}
if (textOverflowEffect === 'zoom') {
const size = parseInt(Math.sqrt((_w * _w + height * height) / 2))
ctx.fillText(line, lineX, lineY, size)
} else {
ctx.fillText(line, lineX, lineY)
}
lineY += lineHeight
}
ctx.textBaseline = oldBaseLine
return initLineY - lineHeight / 2
}
const drawLogo = (ctx, logo, cb) => {
const img = new window.Image()
img.src = logo
img.onload = () => {
ctx.globalAlpha = 0.2
ctx.drawImage(img, 0, ctx.canvas.height / 2 - 16, 32, 32)
cb()
}
}

const toImage = (canvas, key, container, options) => {
var base64Url = canvas.toDataURL()
const _top = (options._top || 0) + 'px'
const __wm = document.querySelector(`.${key}`)
const watermarkDiv = __wm || document.createElement('div')
const styleStr = `
position:absolute;
top:${_top};
left:0;
bottom:0;
width:100%;
height:100%;
z-index:${options.zIndex};
pointer-events:none;
background-repeat:repeat;
visibility:visible !important;
display: block !important;
opacity: 1 !important;
background-image:url('${base64Url}');
${options.grayLogo ? '-webkit-filter: grayscale(100%);-moz-filter: grayscale(100%);-ms-filter: grayscale(100%);-o-filter: grayscale(100%);filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);_filter:none;' : ''}
`
watermarkDiv.setAttribute('style', styleStr)
if (window.getComputedStyle(container).getPropertyValue('position') === 'static') {
container.style.position = 'relative'
}
watermarkDiv.classList.add(key)
container.insertBefore(watermarkDiv, container.firstChild)
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver
if (MutationObserver) {
let mo = new MutationObserver(function () {
const __wm = document.querySelector(`.${key}`)
const cs = __wm ? window.getComputedStyle(__wm) : {}
if ((__wm && (__wm.getAttribute('style') !== styleStr || cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === 0)) || !__wm) {
mo.disconnect()
mo = null
WaterMarker(container, JSON.parse(JSON.stringify(options)))
}
})
mo.observe(container, {
attributes: true,
subtree: true,
childList: true
})
}
}
const WaterMarker = (container, args) => {
const _container = container || document.body
const {density} = args
let _markSize = {
width: 210,
height: 180
}
if (['low', 'high'].includes(density)) {
_markSize = {
width: density === 'low' ? 240 : 180,
height: density === 'low' ? 210 : 150
}
}
const options = Object.assign({}, defaultOptions, _markSize, args)
const {
id,
width,
height,
textAlign,
textBaseline,
font,
color,
logo,
rotate
} = options
let key = 'hi-' + Math.floor(Math.random() * (9999 - 1000)) + 1000 + '__wm'
if (id && id.trim().length > 0 && !document.querySelector(id + '__wm')) {
key = id + '__wm'
}
const canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
canvas.setAttribute('width', width + 'px')
canvas.setAttribute('height', height + 'px')

ctx.textAlign = textAlign
ctx.textBaseline = textBaseline
ctx.font = font
ctx.fillStyle = color
ctx.translate(width / 2, height / 2)
ctx.rotate(Math.PI / 180 * rotate)
ctx.translate(-width / 2, -height / 2)

drawText(ctx, options)
if (logo) {
drawLogo(ctx, logo, () => {
toImage(canvas, key, _container, options)
})
} else {
toImage(canvas, key, _container, options)
}
}

export default WaterMarker
34 changes: 34 additions & 0 deletions docs/demo/watermark/section-base.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import DocViewer from '../../../libs/doc-viewer'
import Watermark from '../../../components/watermark'
import logo from '../../../site/static/img/logo.png'
const prefix = 'watermark-base'
const desc = ''
const code = `import React from 'react'
import Watermark from '@hi-ui/hiui/es/Watermark'\n
class Demo extends React.Component {
constructor(props) {
super(props)
this.options = {logo: logo, content: ['HIUI', '做中台,就用 HIUI'],density:'high'}
}
render () {
return (
<Watermark
{...this.options}
>
<div id="watermark-box"
style={{width: '100%', height: 400, border: '1px solid rgb(230, 231, 232)'}} ref={this.boxRef1}
/>
</Watermark>
)
}
}`

const DemoBase = () => (
<DocViewer
desc={desc}
code={code}
scope={{ Watermark, logo }}
prefix={prefix}
/>)
export default DemoBase
6 changes: 6 additions & 0 deletions docs/zh-CN/components/changelog.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# 更新日志

## 2.12.0

- 新增 `<Watermark />` 水印组件 [#121](https://github.com/XiaoMi/hiui/issues/121)
- 修复 `<Tree />` 多选模式下在禁用状态复选框样式异常的问题 [#1014](https://github.com/XiaoMi/hiui/issues/1014)
- 修复 `<Select />` DataSource 配置 headers 参数无效的问题 [#1011](https://github.com/XiaoMi/hiui/issues/1011)

## 2.11.1

- 修复打包印度节假日数据问题
Expand Down
21 changes: 21 additions & 0 deletions docs/zh-CN/components/watermark.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Watermark 水印

用于向元素添加水印

## 何时使用

页面内容涉及到保密或其它情况时

## 基础用法

import DemoBase from '../../demo/watermark/section-base.jsx'

<DemoBase />

## Props

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------- | ------------------------------------------ | ----------------------- | ---------------------------- | ---------- |
| density | 水印间距,调节疏密程度 | string | 'low' \| 'default' \| 'high' | 'default' |
| content | 水印文案,传入数组代表多行,不建议超过三行 | string \| Array[string] | - | '请勿外传' |
| logo | 图片资源地址(暂只支持本地资源) | string | - | - |
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hi-ui/hiui",
"version": "2.11.1",
"version": "2.12.0",
"description": "HIUI for React",
"scripts": {
"test": "node_modules/.bin/standard && node_modules/.bin/stylelint --config .stylelintrc 'components/**/*.scss'",
Expand Down
Loading

0 comments on commit 28dc4a6

Please sign in to comment.