From 5508ed61dee1d6bd00a8e086a75d2ee4245c634b Mon Sep 17 00:00:00 2001 From: worldSaySorry Date: Fri, 25 Nov 2022 11:17:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0water=20marker?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: collapse组件重构修复,保留原有api(#403) * feat: form组件新增onChange方法(#406) * feat: 新增waterMark组件 * feat: stepper组件新增adjustPosition属性,默认开启 * fix: alipay下的canvas获取 * style: z-index统一 Co-authored-by: kongjing@dian.so --- packages/vantui-doc/src/form/README.md | 17 +- packages/vantui-doc/src/stepper/README.md | 59 +++---- packages/vantui-doc/src/water-mark/README.md | 54 ++++++ packages/vantui-doc/vant.config.js | 4 + packages/vantui/src/collapse-item/animate.ts | 89 ---------- packages/vantui/src/collapse-item/index.less | 8 +- packages/vantui/src/collapse-item/index.tsx | 164 +++++++------------ packages/vantui/src/collapse/index.tsx | 89 ++++++---- packages/vantui/src/form/core/formstore.ts | 26 ++- packages/vantui/src/form/index.tsx | 2 + packages/vantui/src/stepper/index.tsx | 2 + packages/vantui/src/style/var.less | 1 + packages/vantui/src/water-mark/index.less | 16 ++ packages/vantui/src/water-mark/index.tsx | 103 ++++++++++++ packages/vantui/src/water-mark/utils.ts | 89 ++++++++++ packages/vantui/types/form.d.ts | 7 + packages/vantui/types/index.d.ts | 1 + packages/vantui/types/stepper.d.ts | 106 +++++++++++- packages/vantui/types/water-mark.d.ts | 87 ++++++++++ 19 files changed, 658 insertions(+), 266 deletions(-) create mode 100644 packages/vantui-doc/src/water-mark/README.md delete mode 100644 packages/vantui/src/collapse-item/animate.ts create mode 100644 packages/vantui/src/water-mark/index.less create mode 100644 packages/vantui/src/water-mark/index.tsx create mode 100644 packages/vantui/src/water-mark/utils.ts create mode 100644 packages/vantui/types/water-mark.d.ts diff --git a/packages/vantui-doc/src/form/README.md b/packages/vantui-doc/src/form/README.md index 059d82930..123dcf0dc 100644 --- a/packages/vantui-doc/src/form/README.md +++ b/packages/vantui-doc/src/form/README.md @@ -421,14 +421,15 @@ function DatetimePickerBox_(props) { ### FormProps [[详情]](https://github.com/AntmJS/vantui/tree/main/packages/vantui/types/form.d.ts) -| 参数 | 说明 | 类型 | 默认值 | 必填 | -| -------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | ------- | -| form | 传入 form 实例(const formStore1 = useRef()) | _  IFormInstanceAPI
_ | - | `false` | -| initialValues | 初始化表单仓库值 | _  Record<
    string,
    any
  >
_ | - | `false` | -| children | 第一级必须是 FormItem 组件 | _  ReactNode
_ | - | `true` | -| className | 类名 | _  string
_ | - | `false` | -| onFinish | 表单提交触发,配合 button.formType = submit | _  (
    errs: string[] ¦ null,
    values: Record<
      string,
      any
    >
  ) => void
_ | - | `false` | -| onFinishFailed | 表单提交失败触发,会拦截 onFinish | _  (
    errs: string[] ¦ null
  ) => void
_ | - | `false` | +| 参数 | 说明 | 类型 | 默认值 | 必填 | +| -------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | +| form | 传入 form 实例(const formStore1 = useRef()) | _  IFormInstanceAPI
_ | - | `false` | +| initialValues | 初始化表单仓库值 | _  Record<
    string,
    any
  >
_ | - | `false` | +| children | 第一级必须是 FormItem 组件 | _  ReactNode
_ | - | `true` | +| className | 类名 | _  string
_ | - | `false` | +| onFinish | 表单提交触发,配合 button.formType = submit | _  (
    errs: string[] ¦ null,
    values: Record<
      string,
      any
    >
  ) => void
_ | - | `false` | +| onFinishFailed | 表单提交失败触发,会拦截 onFinish | _  (
    errs: string[] ¦ null
  ) => void
_ | - | `false` | +| onChange | 字段值更新时触发的回调事件 | _  (
    changedValues: Record<
      string,
      any
    >,
    allValues: Record<
      string,
      any
    >
  ) => void
_ | - | `false` | ### FormItemProps [[详情]](https://github.com/AntmJS/vantui/tree/main/packages/vantui/types/form.d.ts) diff --git a/packages/vantui-doc/src/stepper/README.md b/packages/vantui-doc/src/stepper/README.md index 2c72b822a..fc3ed31af 100644 --- a/packages/vantui-doc/src/stepper/README.md +++ b/packages/vantui-doc/src/stepper/README.md @@ -126,35 +126,36 @@ function Demo() { ### StepperProps [[详情]](https://github.com/AntmJS/vantui/tree/main/packages/vantui/types/stepper.d.ts) -| 参数 | 说明 | 类型 | 默认值 | 必填 | -| ------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | ------- | -| name | - | _  ReactNode
_ | - | `false` | -| value | - | _  string ¦ number
_ | - | `false` | -| integer | - | _  boolean
_ | - | `false` | -| disabled | - | _  boolean
_ | - | `false` | -| inputWidth | - | _  string ¦ number
_ | - | `false` | -| buttonSize | - | _  string ¦ number
_ | - | `false` | -| asyncChange | - | _  boolean
_ | - | `false` | -| disableInput | - | _  boolean
_ | - | `false` | -| decimalLength | - | _  number
_ | - | `false` | -| min | - | _  string ¦ number
_ | - | `false` | -| max | - | _  string ¦ number
_ | - | `false` | -| step | - | _  string ¦ number
_ | - | `false` | -| showPlus | - | _  boolean
_ | - | `false` | -| showMinus | - | _  boolean
_ | - | `false` | -| disablePlus | - | _  boolean
_ | - | `false` | -| disableMinus | - | _  boolean
_ | - | `false` | -| longPress | - | _  boolean
_ | - | `false` | -| theme | - | _  "round"
_ | - | `false` | -| alwaysEmbed | - | _  boolean
_ | - | `false` | -| onFocus | - | _  CommonEventFunction
_ | - | `false` | -| onChange | - | _  (event: {
    detail:
      ¦ number
      ¦ string
  }) => void
_ | - | `false` | -| onBlur | - | _  CommonEventFunction
_ | - | `false` | -| onOverlimit | - | _  () => void
_ | - | `false` | -| onPlus | - | _  () => void
_ | - | `false` | -| onMinus | - | _  () => void
_ | - | `false` | -| renderMinus | - | _  ReactNode
_ | - | `false` | -| renderPlus | - | _  ReactNode
_ | - | `false` | +| 参数 | 说明 | 类型 | 默认值 | 必填 | +| -------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ------- | +| name | 在表单内提交时的标识符 | _  ReactNode
_ | - | `false` | +| value | 输入值 | _  string ¦ number
_ | - | `false` | +| integer | 是否只允许输入整数 | _  boolean
_ | `false` | `false` | +| disabled | 是否禁用 | _  boolean
_ | `false` | `false` | +| adjustPosition | 输入聚焦后,键盘弹起时,是否自动上推页面 | _  boolean
_ | `true` | `false` | +| inputWidth | 输入框宽度 | _  string ¦ number
_ | `32px` | `false` | +| buttonSize | 按钮大小 | _  string ¦ number
_ | `28px` | `false` | +| asyncChange | 是否异步更新 | _  boolean
_ | `false` | `false` | +| disableInput | 禁止输入 | _  boolean
_ | `false` | `false` | +| decimalLength | 固定显示的小数位数 | _  number
_ | - | `false` | +| min | 最小值 | _  string ¦ number
_ | `1` | `false` | +| max | 最大值 | _  string ¦ number
_ | - | `false` | +| step | 步长 | _  string ¦ number
_ | `1` | `false` | +| showPlus | 是否展示新增按钮 | _  boolean
_ | `true` | `false` | +| showMinus | 是否展示减少按钮 | _  boolean
_ | `true` | `false` | +| disablePlus | 是否禁用新增按钮 | _  boolean
_ | `false` | `false` | +| disableMinus | 是否禁用减少按钮 | _  boolean
_ | `false` | `false` | +| longPress | 是否开启长按 | _  boolean
_ | `false` | `false` | +| theme | 可选值 round | _  "round"
_ | - | `false` | +| alwaysEmbed | 强制 input 处于同层状态,默认 focus 时 input 会切到非同层状态 (仅在 iOS 下生效) | _  boolean
_ | `false` | `false` | +| onFocus | 输入框聚焦时触发 | _  CommonEventFunction
_ | - | `false` | +| onChange | 当绑定值变化时触发的事件 | _  (event: {
    detail:
      ¦ number
      ¦ string
  }) => void
_ | - | `false` | +| onBlur | 输入框失焦时触发 | _  CommonEventFunction
_ | - | `false` | +| onOverlimit | 点击不可用的按钮时触发 | _  () => void
_ | - | `false` | +| onPlus | 点击增加按钮时触发 | _  () => void
_ | - | `false` | +| onMinus | 点击减少按钮时触发 | _  () => void
_ | - | `false` | +| renderMinus | 自定义渲染新增按钮 | _  ReactNode
_ | - | `false` | +| renderPlus | 自定义渲染减少按钮 | _  ReactNode
_ | - | `false` | ### 样式变量 diff --git a/packages/vantui-doc/src/water-mark/README.md b/packages/vantui-doc/src/water-mark/README.md new file mode 100644 index 000000000..6549903fc --- /dev/null +++ b/packages/vantui-doc/src/water-mark/README.md @@ -0,0 +1,54 @@ +# WaterMark 水印 + +### 介绍 + +页面上添加特定的文字或图案, 作为版权标识或者其他特定信息标识 + +### 引入 + +在 Taro 文件中引入组件 + +```js +import { WaterMark } from '@antmjs/vantui' +``` + +## 基本使用 + +```jsx +function Demo() { + const [show, setShow] = react.useState(1) + + return ( + + ) +} +``` + +### WaterMarkProps [[详情]](https://github.com/AntmJS/vantui/tree/main/packages/vantui/types/water-mark.d.ts) + +| 参数 | 说明 | 类型 | 默认值 | 必填 | +| ----------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------- | +| gapX | 水印之间的水平间距 | _  number
_ | `24` | `false` | +| gapY | 水印之间的垂直间距 | _  number
_ | `48` | `false` | +| zIndex | 追加的水印元素的 z-index | _  number
_ | `2000` | `false` | +| width | 水印的宽度 | _  number
_ | `120` | `false` | +| height | 水印的高度 | _  number
_ | `64` | `false` | +| rotate | 水印绘制时,旋转的角度,单位 ° | _  number
_ | `2000` | `false` | +| image | 图片源,建议导出 2 倍或 3 倍图,优先使用图片渲染水印 | _  string
_ | - | `false` | +| imageWidth | 图片宽度 | _  number
_ | `120` | `false` | +| imageHeight | 图片高度 | _  number
_ | `64` | `false` | +| content | 水印文字内容 | _  string
_ | - | `false` | +| fontColor | 字体颜色 | _  string
_ | `rgba(0,0,0,.15)` | `false` | +| fontStyle | 字体 style | _  attr:
    ¦ "none"
    ¦ "normal"
    ¦ "italic"
    ¦ "oblique"
_ | `normal` | `false` | +| fontFamily | 字体 | _  string
_ | `sans-serif` | `false` | +| fontWeight | 字体粗细 | _  attr:
    ¦ "normal"
    ¦ "light"
    ¦ "weight"
    ¦ number
_ | `normal` | `false` | +| fontSize | 字体大小 | _  number ¦ string
_ | `14` | `false` | +| fullPage | 是否沾满屏幕 | _  boolean
_ | true | `false` | + +### 样式变量 + +组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考[ConfigProvider 组件](https://antmjs.github.io/vantui/#/config-provider) + +| 名称 | 默认值 | +| -------------------- | -------- | +| --water-mark-z-index | ` 2000;` | diff --git a/packages/vantui-doc/vant.config.js b/packages/vantui-doc/vant.config.js index 156c7a3c9..d644eee1e 100644 --- a/packages/vantui-doc/vant.config.js +++ b/packages/vantui-doc/vant.config.js @@ -265,6 +265,10 @@ module.exports = { path: 'tag', title: 'Tag 标签', }, + { + path: 'water-mark', + title: 'WaterMark 水印', + }, { path: 'power-scroll-view', title: 'PowerScrollView 滚动列表', diff --git a/packages/vantui/src/collapse-item/animate.ts b/packages/vantui/src/collapse-item/animate.ts deleted file mode 100644 index ec45f11af..000000000 --- a/packages/vantui/src/collapse-item/animate.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { createAnimation, nextTick } from '@tarojs/taro' -// import { canIUseAnimate } from '../common/version' -import { getRect } from '../common/utils' -// function useAnimate(context: any, expanded: any, mounted: any, height: any) { -// const selector = '.van-collapse-item__wrapper' -// if (expanded) { -// context.animate( -// selector, -// [ -// { height: 0, ease: 'ease-in-out', offset: 0 }, -// { height: `${height}px`, ease: 'ease-in-out', offset: 1 }, -// { height: `auto`, ease: 'ease-in-out', offset: 1 }, -// ], -// mounted ? 300 : 0, -// () => { -// context.clearAnimation(selector) -// }, -// ) -// return -// } -// context.animate( -// selector, -// [ -// { height: `${height}px`, ease: 'ease-in-out', offset: 0 }, -// { height: 0, ease: 'ease-in-out', offset: 1 }, -// ], -// 300, -// () => { -// context.clearAnimation(selector) -// }, -// ) -// } -function useAnimation(expanded: any, mounted: any, height: any, setState: any) { - const animation = createAnimation({ - duration: 0, - timingFunction: 'ease-in-out', - }) - if (expanded) { - if (height === 0 || height === null || height === undefined) { - animation.height('auto').top(1).step() - } else { - animation - .height(height) - .top(1) - .step({ - duration: mounted ? 300 : 1, - }) - .height('auto') - .step() - } - const animationclass = animation.export() - setState?.((state: any) => { - return { - ...state, - animation: animationclass, - } - }) - } else { - animation.height(height).top(0).step({ duration: 1 }).height(0).step({ - duration: 300, - }) - const animationclass = animation.export() - nextTick(() => { - setState?.((state: any) => { - return { - ...state, - animation: animationclass, - } - }) - }) - } -} -export function setContentAnimate( - selector: any, - expanded: any, - mounted: any, - setState: any, - ref?: any, -) { - getRect(null, selector) - .then((rect: any) => { - return process.env.TARO_ENV === 'h5' - ? ref.current.clientHeight - : rect?.height - }) - .then((height) => { - useAnimation(expanded, mounted, height, setState) - }) -} diff --git a/packages/vantui/src/collapse-item/index.less b/packages/vantui/src/collapse-item/index.less index 0252c154e..c0f2957c4 100644 --- a/packages/vantui/src/collapse-item/index.less +++ b/packages/vantui/src/collapse-item/index.less @@ -34,10 +34,16 @@ } &__content { - .theme(padding, '@collapse-item-content-padding'); .theme(color, '@collapse-item-content-text-color'); .theme(font-size, '@collapse-item-content-font-size'); .theme(line-height, '@collapse-item-content-line-height'); .theme(background-color, '@collapse-item-content-background-color'); + + transition: all 0.3s ease-in-out; + overflow: hidden; + + &_wrapper { + .theme(padding, '@collapse-item-content-padding'); + } } } diff --git a/packages/vantui/src/collapse-item/index.tsx b/packages/vantui/src/collapse-item/index.tsx index 9625f9c5b..50eba2be0 100644 --- a/packages/vantui/src/collapse-item/index.tsx +++ b/packages/vantui/src/collapse-item/index.tsx @@ -1,31 +1,22 @@ -import { useReady } from '@tarojs/taro' -import { useState, useEffect, useRef, useCallback } from 'react' -import { View, ITouchEvent } from '@tarojs/components' - +import { useState, useEffect, useCallback } from 'react' +import { View } from '@tarojs/components' +import { nextTick } from '@tarojs/taro' import * as utils from '../wxs/utils' import { CollapseItemProps } from '../../types/collapse' +import { getRect } from '../common/utils' import VanCell from '../cell/index' -import { setContentAnimate } from './animate' + +let compIndex = 0 export function CollapseItem( props: CollapseItemProps & { - parent?: any + isOpen: any + handleToggle: any + index: any }, ) { - const ref = useRef({ - mounted: false, - }) - - const [state, setState] = useState({ - index: undefined, - expanded: false, - animation: { actions: [] }, - ready: false, - }) - const { size, - parent, name = null, title = '', value = '', @@ -42,94 +33,59 @@ export function CollapseItem( style, className, children, + index, + isOpen, + handleToggle, ...others } = props - useReady(() => { - if (process.env.TARO_ENV !== 'h5') { - setState((state) => { - return { - ...state, - ready: true, - } - }) - } - }) - useEffect(() => { - // if (process.env.TARO_ENV === 'h5') { - setTimeout(() => { - setState((state) => { - return { - ...state, - ready: true, - } - }) - }, 0) - // } - }, []) + const [domHeight, setDomHeight] = useState(-1) + const [currHeight, setCurrHeight] = useState('auto') + const [init, setInit] = useState(false) + const [curCompIndex] = useState(compIndex++) + const [update, setUpdate] = useState(false) - const refDom = useRef(null) - const refRandomClass = useRef( - `selector-${`${+new Date()}${Math.ceil(Math.random() * 10000)}`.slice(-8)}`, - ) - - const updateExpanded = useCallback(() => { - if (!parent) { - return - } - const { value, accordion } = parent?.data - const index = parent?.index - const currentName = name == null ? index : name - const expanded = accordion - ? value === currentName - : (value || []).some((name: any) => name === currentName) - if (expanded !== state.expanded) { - setContentAnimate( - `.${refRandomClass.current}`, - expanded, - ref.current.mounted, - setState, - refDom, - ) - } - setState((state) => { - return { - ...state, - index, - expanded, + const contentRef = useCallback( + (node: any) => { + if (node !== null) { + if (process.env.TARO_ENV !== 'h5') { + getRect(null, `#${node.props.id}`).then((res: any) => { + if (res) { + setDomHeight(res.height) + nextTick(() => { + setInit(true) + }) + } + }) + } else { + setDomHeight(node.getBoundingClientRect().height) + nextTick(() => { + setInit(true) + }) + } } - }) - }, [parent, name, state.expanded]) + }, + [update], + ) useEffect(() => { - if (state.ready) { - updateExpanded() - ref.current.mounted = true - } - }, [state.ready, updateExpanded]) + setCurrHeight('auto') + setUpdate(!update) + }, [children]) useEffect(() => { - if (state.ready) { - updateExpanded() + if (domHeight !== -1) { + nextTick(() => { + isOpen ? setCurrHeight(`${domHeight}px`) : setCurrHeight('0px') + }) } - }, [state.ready, updateExpanded, parent.data]) - - const onClick = useCallback( - (event: ITouchEvent) => { - if (disabled) { - return - } - const currentName = name == null ? parent?.index : name - parent?.handleSwitch(event, currentName, !state.expanded) - }, - [parent, disabled, name, state.expanded], - ) + }, [isOpen, domHeight]) return ( { + if (disabled) return + handleToggle && handleToggle(isOpen, name) + }} renderTitle={<>{renderTitle}} renderIcon={<>{renderIcon}} renderRightIcon={<>{renderRightIcon}} @@ -158,17 +117,18 @@ export function CollapseItem( {renderValue} - {children} + {children} diff --git a/packages/vantui/src/collapse/index.tsx b/packages/vantui/src/collapse/index.tsx index 7ac271c38..a3ce253b5 100644 --- a/packages/vantui/src/collapse/index.tsx +++ b/packages/vantui/src/collapse/index.tsx @@ -1,4 +1,11 @@ -import { cloneElement, useCallback, useMemo, Children } from 'react' +import { + cloneElement, + useMemo, + Children, + useState, + useCallback, + useEffect, +} from 'react' import { ITouchEvent, View } from '@tarojs/components' import { CollapseProps } from '../../types/collapse' @@ -17,46 +24,66 @@ export function Collapse(props: CollapseProps) { ...others } = props - const handleSwitch = useCallback( - (event: ITouchEvent, name: any, expanded: any) => { - const changeItem = name - if (!accordion && Array.isArray(value)) { - name = expanded - ? (value || []).concat(name) - : (value || []).filter((activeName) => activeName !== name) - } else { - name = expanded ? name : '' + const [defaultOpenIndex, setDefaultOpenIndex] = useState>([]) + + const handleActiveName = useCallback(() => { + let openIndex_: any[] = [] + if (value && !Array.isArray(value)) { + openIndex_.push(value.toString()) + } else if (value && Array.isArray(value)) { + if (accordion && value.length > 1) { + console.warn('手风琴模式不支持传多个打开页签') } - Object.defineProperty(event, 'detail', { - value: changeItem, - writable: true, + const arr = value.map((item) => { + return item.toString() + }) + openIndex_ = [...arr] + } + return openIndex_ + }, [value]) + + useEffect(() => { + const openIndex_ = handleActiveName() + setDefaultOpenIndex(openIndex_) + }, [value]) + + const handleToggle = (isOpen: boolean, name: string) => { + let newOpenIndex = [...defaultOpenIndex] + if (isOpen) { + // 当前状态为true,则变为false,闭合 + const removeIndex = newOpenIndex.findIndex((value) => { + return value === name }) - if (expanded) { - onOpen?.(event) + newOpenIndex.splice(removeIndex, 1) + onClose?.({ + detail: name, + } as ITouchEvent) + } else { + if (accordion) { + newOpenIndex = [name] } else { - onClose?.(event) + newOpenIndex.push(name) } - event.detail = name - onChange?.(event) - }, - [value, accordion, onOpen, onClose, onChange], - ) + onOpen?.({ + detail: name, + } as ITouchEvent) + } + setDefaultOpenIndex(newOpenIndex) + onChange?.({ + detail: newOpenIndex, + } as ITouchEvent) + } const newChildren: any = useMemo(() => { return Children.map(children, (child: any, index: number) => { return cloneElement(child, { - key: index, - parent: { - index, - handleSwitch, - data: { - value, - accordion, - }, - }, + isOpen: defaultOpenIndex.includes(child.props.name), + handleToggle: (isOpen: boolean, name: string) => + handleToggle(isOpen, name), + index, }) }) - }, [children, value, accordion, handleSwitch]) + }, [children, value, accordion, handleActiveName, defaultOpenIndex]) return ( ) { if (typeof object !== 'object') return this.transformSingellevelData(object, this.multiLevelKeys) + for (const key in this.model) { const item = this.model[key] - this.setValueClearStatus(item, key, item.value) + this.setValueClearStatus(item, key, item.value, true) // 批量更新hiddenFormChange + } + + const { onChange } = this.callback + if (onChange) { + onChange(object, this.getFieldsValue()) } } @@ -175,12 +188,17 @@ class FormStore { } } - setValueClearStatus(model: Record, name_: Iname, value: any) { + setValueClearStatus( + model: Record, + name_: Iname, + value: any, + hiddenFormChange?: boolean, + ) { const name = Array.isArray(name_) ? name_.join('.') : name_ model['value'] = value model['status'] = 'pendding' - this.notifyChange(name) + this.notifyChange(name, hiddenFormChange) } // 扁平数据转多层数据结构 static transformMultilevelData(data: Record) { diff --git a/packages/vantui/src/form/index.tsx b/packages/vantui/src/form/index.tsx index 2bdf2c65c..83a414f7b 100644 --- a/packages/vantui/src/form/index.tsx +++ b/packages/vantui/src/form/index.tsx @@ -15,6 +15,7 @@ function Index( className = '', onFinish, onFinishFailed, + onChange, } = props const formInstance = useForm(form, initialValues) // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -24,6 +25,7 @@ function Index( setCallback({ onFinish: onFinish, onFinishFailed: onFinishFailed, + onChange: onChange, }) useImperativeHandle(ref, () => formInstanceAPI as IFormInstanceAPI, [ diff --git a/packages/vantui/src/stepper/index.tsx b/packages/vantui/src/stepper/index.tsx index f3059bd1e..7ab85f5a8 100644 --- a/packages/vantui/src/stepper/index.tsx +++ b/packages/vantui/src/stepper/index.tsx @@ -44,6 +44,7 @@ export function Stepper(props: StepperProps) { onMinus, renderMinus, renderPlus, + adjustPosition = true, ...others } = props const [currentValue, setCurrentValue] = useState() @@ -272,6 +273,7 @@ export function Stepper(props: StepperProps) { onInput={_onInput} onFocus={_onFocus} onBlur={_onBlur} + adjustPosition={adjustPosition} > {showPlus && ( = (props) => { + const { + zIndex = 2000, + gapX = 24, + gapY = 48, + width = 120, + height = 64, + rotate = -22, + image, + imageWidth = 120, + imageHeight = 64, + content, + fontStyle = 'normal', + fontWeight = 'normal', + fontColor = 'rgba(0,0,0,.15)', + fontSize = 14, + fontFamily = 'sans-serif', + fullPage = true, + } = props + + const [base64Url, setBase64Url] = useState('') + const [canvasRect, setCanvasRect] = useState() + const [compIndex] = useState(cIndex++) + + const work = useCallback(async () => { + const canvas: any = await getCanvas(compIndex) + const base64Url_ = await getWaterData({ + gapX, + gapY, + rotate, + fontStyle, + fontWeight, + width, + height, + fontFamily, + fontColor, + image, + content, + fontSize, + imageWidth, + imageHeight, + canvas, + setCanvasRect, + }) + setBase64Url(base64Url_) + }, [ + gapX, + gapY, + rotate, + fontStyle, + fontWeight, + width, + height, + fontFamily, + fontColor, + image, + content, + fontSize, + imageWidth, + imageHeight, + ]) + + useEffect(() => { + nextTick(() => { + work() + }) + }, [work]) + + return ( + + + + ) +} + +export default WaterMark diff --git a/packages/vantui/src/water-mark/utils.ts b/packages/vantui/src/water-mark/utils.ts new file mode 100644 index 000000000..78fe1c7c9 --- /dev/null +++ b/packages/vantui/src/water-mark/utils.ts @@ -0,0 +1,89 @@ +import { createSelectorQuery, getSystemInfo } from '@tarojs/taro' + +export function getCanvas(compIndex) { + return new Promise((resolve) => { + if (process.env.TARO_ENV !== 'h5') { + createSelectorQuery() + .select(`#van-water-mark${compIndex}`) + .node() + .exec((res) => { + resolve(res[0].node) + }) + } else { + resolve(document.createElement('canvas')) + } + }) +} + +export function getWaterData({ + setCanvasRect, + width, + height, + gapX, + gapY, + rotate, + image, + imageWidth, + imageHeight, + fontSize, + content, + fontWeight, + fontFamily, + fontStyle, + fontColor, + canvas, +}): Promise { + return new Promise(async (resolve) => { + const ctx = canvas.getContext('2d') + const ratio = (await getSystemInfo()).pixelRatio + const canvasWidth = `${(gapX + width) * ratio}px` + const canvasHeight = `${(gapY + height) * ratio}px` + + const markWidth = width * ratio + const markHeight = height * ratio + let base64Url: any + + setCanvasRect({ + width: canvasWidth, + height: canvasHeight, + }) + if (image) { + ctx.translate(markWidth / 2, markHeight / 2) + ctx.rotate((Math.PI / 180) * Number(rotate)) + + const img = + process.env.TARO_ENV === 'h5' ? new Image() : canvas.createImage() + img.crossOrigin = 'anonymous' + img.referrerPolicy = 'no-referrer' + img.src = image + img.onload = () => { + ctx.drawImage( + img, + (-imageWidth * ratio) / 2, + (-imageHeight * ratio) / 2, + imageWidth * ratio, + imageHeight * ratio, + ) + ctx.restore() + base64Url = canvas.toDataURL() + resolve(base64Url) + } + } else if (content) { + ctx.textBaseline = 'middle' + ctx.textAlign = 'center' + // 文字绕中间旋转 + ctx.translate(markWidth / 2, markHeight / 2) + ctx.rotate((Math.PI / 180) * Number(rotate)) + + const markSize = Number(fontSize) * ratio + ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}` + ctx.fillStyle = fontColor + + ctx.fillText(content, 0, 0) + ctx.restore() + base64Url = canvas.toDataURL() + + resolve(base64Url) + } + }) +} diff --git a/packages/vantui/types/form.d.ts b/packages/vantui/types/form.d.ts index 71466bcf6..7a37440a7 100644 --- a/packages/vantui/types/form.d.ts +++ b/packages/vantui/types/form.d.ts @@ -30,6 +30,13 @@ export interface FormProps extends ViewProps { * @description 表单提交失败触发,会拦截onFinish */ onFinishFailed?: (errs: string[] | null) => void + /** + * @description 字段值更新时触发的回调事件 + */ + onChange?: ( + changedValues: Record, + allValues: Record, + ) => void } /** * @title FormItemProps diff --git a/packages/vantui/types/index.d.ts b/packages/vantui/types/index.d.ts index 8037fb553..a9b681a5b 100644 --- a/packages/vantui/types/index.d.ts +++ b/packages/vantui/types/index.d.ts @@ -67,3 +67,4 @@ export { Pagination, PaginationProps } from './pagination' export { Swiper, SwiperItem } from './swiper' export { Cascader } from './cascader' export { Sku } from './sku' +export { WaterMark } from './water-mark' diff --git a/packages/vantui/types/stepper.d.ts b/packages/vantui/types/stepper.d.ts index 3920789b0..1a400a50a 100644 --- a/packages/vantui/types/stepper.d.ts +++ b/packages/vantui/types/stepper.d.ts @@ -2,33 +2,135 @@ import { FunctionComponent, ReactNode } from 'react' import { ViewProps, CommonEventFunction } from '@tarojs/components' import { InputProps } from '@tarojs/components/types/Input' +type InputForceEvent = InputProps.inputForceEventDetail + export interface StepperProps extends ViewProps { + /** + * @description 在表单内提交时的标识符 + */ name?: ReactNode + /** + * @description 输入值 + */ value?: string | number + /** + * @description 是否只允许输入整数 + * @default `false` + */ integer?: boolean + /** + * @description 是否禁用 + * @default `false` + */ disabled?: boolean + /** + * @description 输入聚焦后,键盘弹起时,是否自动上推页面 + * @default `true` + */ + adjustPosition?: boolean + /** + * @description 输入框宽度 + * @default `32px` + */ inputWidth?: string | number + /** + * @description 按钮大小 + * @default `28px` + */ buttonSize?: string | number + /** + * @description 是否异步更新 + * @default `false` + */ asyncChange?: boolean + /** + * @description 禁止输入 + * @default `false` + */ disableInput?: boolean + /** + * @description 固定显示的小数位数 + */ decimalLength?: number + /** + * @description 最小值 + * @default `1` + */ min?: string | number + /** + * @description 最大值 + */ max?: string | number + /** + * @description 步长 + * @default `1` + */ step?: string | number + /** + * @description 是否展示新增按钮 + * @default `true` + */ showPlus?: boolean + /** + * @description 是否展示减少按钮 + * @default `true` + */ showMinus?: boolean + /** + * @description 是否禁用新增按钮 + * @default `false` + */ disablePlus?: boolean + /** + * @description 是否禁用减少按钮 + * @default `false` + */ disableMinus?: boolean + /** + * @description 是否开启长按 + * @default `false` + */ longPress?: boolean + /** + * @description 可选值round + */ theme?: 'round' + /** + * @description 强制 input 处于同层状态,默认 focus 时 input 会切到非同层状态 (仅在 iOS 下生效) + * @default `false` + */ alwaysEmbed?: boolean - onFocus?: CommonEventFunction + /** + * @description 输入框聚焦时触发 + */ + onFocus?: CommonEventFunction + /** + * @description 当绑定值变化时触发的事件 + */ onChange?: (event: { detail: number | string }) => void - onBlur?: CommonEventFunction + /** + * @description 输入框失焦时触发 + */ + onBlur?: CommonEventFunction + /** + * @description 点击不可用的按钮时触发 + */ onOverlimit?: () => void + /** + * @description 点击增加按钮时触发 + */ onPlus?: () => void + /** + * @description 点击减少按钮时触发 + */ onMinus?: () => void + /** + * @description 自定义渲染新增按钮 + */ renderMinus?: ReactNode + /** + * @description 自定义渲染减少按钮 + */ renderPlus?: ReactNode } // Partial diff --git a/packages/vantui/types/water-mark.d.ts b/packages/vantui/types/water-mark.d.ts new file mode 100644 index 000000000..ec4580154 --- /dev/null +++ b/packages/vantui/types/water-mark.d.ts @@ -0,0 +1,87 @@ +import { FunctionComponent } from 'react' +import { ViewProps } from '@tarojs/components' + +export interface WaterMarkProps extends ViewProps { + /** + * @description 水印之间的水平间距 + * @default `24` + */ + gapX?: number + /** + * @description 水印之间的垂直间距 + * @default `48` + */ + gapY?: number + /** + * @description 追加的水印元素的 z-index + * @default `2000` + */ + zIndex?: number + /** + * @description 水印的宽度 + * @default `120` + */ + width?: number + /** + * @description 水印的高度 + * @default `64` + */ + height?: number + /** + * @description 水印绘制时,旋转的角度,单位 ° + * @default `2000` + */ + rotate?: number + /** + * @description 图片源,建议导出 2 倍或 3 倍图,优先使用图片渲染水印 + */ + image?: string + /** + * @description 图片宽度 + * @default `120` + */ + imageWidth?: number + /** + * @description 图片高度 + * @default `64` + */ + imageHeight?: number + /** + * @description 水印文字内容 + */ + content?: string + /** + * @description 字体颜色 + * @default `rgba(0,0,0,.15)` + */ + fontColor?: string + /** + * @description 字体style + * @default `normal` + */ + fontStyle?: 'none' | 'normal' | 'italic' | 'oblique' + /** + * @description 字体 + * @default `sans-serif` + */ + fontFamily?: string + /** + * @description 字体粗细 + * @default `normal` + */ + fontWeight?: 'normal' | 'light' | 'weight' | number + /** + * @description 字体大小 + * @default `14` + */ + fontSize?: number | string + /** + * @description 是否沾满屏幕 + * @default true + */ + fullPage?: boolean +} + +declare const WaterMark: FunctionComponent + +export { WaterMark }