提交 f17e1c0f 编写于 作者: Q qiang

feat(App): cover-view, cover-image

上级 d77013f1
import { computed, Ref, reactive, watch } from 'vue'
import { CustomEventTrigger } from '@dcloudio/uni-components'
import { Position, useNative } from './useNative'
let id = 0
export function useCover(
rootRef: Ref<HTMLElement | null>,
trigger: CustomEventTrigger,
content: { src?: string; text?: string }
) {
const { position, hidden, onParentReady } = useNative(rootRef)
onParentReady((parentPosition) => {
const viewPosition = computed(() => {
const object: Position = {} as Position
for (const key in position) {
let val = position[key as keyof Position]
const valNumber = parseFloat(val)
const parentValNumber = parseFloat(
parentPosition[key as keyof Position]
)
if (key === 'top' || key === 'left') {
val = Math.max(valNumber, parentValNumber) + 'px'
} else if (key === 'width' || key === 'height') {
const base = key === 'width' ? 'left' : 'top'
const parentStart = parseFloat(parentPosition[base])
const viewStart = parseFloat(position[base])
const diff1 = Math.max(parentStart - viewStart, 0)
const diff2 = Math.max(
viewStart + valNumber - (parentStart + parentValNumber),
0
)
val = Math.max(valNumber - diff1 - diff2, 0) + 'px'
}
object[key as keyof Position] = val as any
}
return object
})
const baseStyle: Array<keyof CSSStyleDeclaration> = [
'borderRadius',
'borderColor',
'borderWidth',
'backgroundColor',
]
const textStyle: Array<keyof CSSStyleDeclaration> = [
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
'color',
'textAlign',
'lineHeight',
'fontSize',
'fontWeight',
'textOverflow',
'whiteSpace',
]
const imageStyle: Array<keyof CSSStyleDeclaration> = []
const textAlign = { start: 'left', end: 'right' }
function updateStyle(style: Record<keyof CSSStyleDeclaration, any>) {
const computedStyle = getComputedStyle(rootRef.value as HTMLElement)
baseStyle.concat(textStyle, imageStyle).forEach((key) => {
style[key] = computedStyle[key]
})
return style
}
const style = reactive(updateStyle({} as CSSStyleDeclaration))
let request: null | number = null
function requestStyleUpdate() {
if (request) {
cancelAnimationFrame(request)
}
request = requestAnimationFrame(() => {
request = null
updateStyle(style)
})
}
window.addEventListener('updateview', requestStyleUpdate)
function getTagPosition() {
const position: Partial<Position> = {}
for (const key in position) {
let val = position[key as keyof Position]!
if (key === 'top' || key === 'left') {
val =
Math.min(parseFloat(val) - parseFloat(parentPosition[key]), 0) +
'px'
}
position[key as keyof Position] = val as any
}
return position
}
const tags = computed(() => {
const position: Position = getTagPosition() as Position
const tags: PlusNativeObjViewDrawTagStyles[] = [
{
tag: 'rect',
position,
rectStyles: {
color: style.backgroundColor,
radius: style.borderRadius,
borderColor: style.borderColor,
borderWidth: style.borderWidth,
},
},
]
if ('src' in content) {
if (content.src) {
tags.push({
tag: 'img',
position,
src: content.src,
})
}
} else {
const lineSpacing =
parseFloat(style.lineHeight) - parseFloat(style.fontSize)
let width =
parseFloat(position.width) -
parseFloat(style.paddingLeft) -
parseFloat(style.paddingRight)
width = width < 0 ? 0 : width
let height =
parseFloat(position.height) -
parseFloat(style.paddingTop) -
lineSpacing / 2 -
parseFloat(style.paddingBottom)
height = height < 0 ? 0 : height
tags.push({
tag: 'font',
position: {
top: `${
parseFloat(position.top) +
parseFloat(style.paddingTop) +
lineSpacing / 2
}px`,
left: `${
parseFloat(position.left) + parseFloat(style.paddingLeft)
}px`,
width: `${width}px`,
height: `${height}px`,
},
textStyles: {
align:
textAlign[style.textAlign as keyof typeof textAlign] ||
style.textAlign,
color: style.color,
decoration: 'none',
lineSpacing: `${lineSpacing}px`,
margin: '0px',
overflow: style.textOverflow,
size: style.fontSize,
verticalAlign: 'top',
weight: style.fontWeight,
whiteSpace: style.whiteSpace,
},
text: content.text,
})
}
return tags
})
const cover = new plus.nativeObj.View!(
`cover-${Date.now()}-${id++}`,
viewPosition.value,
tags.value
)
plus.webview.currentWebview().append(cover)
if (hidden.value) {
cover.hide()
}
cover.addEventListener('click', () => {
trigger('click', {} as Event, {})
})
watch(
() => hidden.value,
(val) => {
cover[val ? 'hide' : 'show']()
}
)
watch(
() => viewPosition.value,
(val) => {
cover.setStyle(val)
},
{ deep: true }
)
watch(
() => tags.value,
() => {
cover.reset()
cover.draw(tags.value)
},
{ deep: true }
)
})
}
import {
Ref,
ref,
reactive,
ExtractPropTypes,
watch,
onBeforeUnmount,
} from 'vue'
import { plusReady } from '@dcloudio/uni-shared'
import {
defineBuiltInComponent,
useCustomEvent,
CustomEventTrigger,
EmitEvent,
} from '@dcloudio/uni-components'
import { useCover } from '../../../helpers/useCover'
import { getRealPath } from '../../../platform/getRealPath'
// TODO 从 service 层传入
const TEMP_PATH = '_doc/uniapp_temp/'
const props = {
src: {
type: String,
default: '',
},
autoSize: {
type: [Boolean, String],
default: false,
},
}
type Props = ExtractPropTypes<typeof props>
function useImageLoad(
props: Props,
content: { src: string },
trigger: CustomEventTrigger
) {
const style = ref('')
let downloaTask: PlusDownloaderDownload
function loadImage() {
content.src = ''
style.value = props.autoSize ? 'width:0;height:0;' : ''
const realPath = props.src ? getRealPath(props.src) : ''
if (
realPath.indexOf('http://') === 0 ||
realPath.indexOf('https://') === 0
) {
plusReady(() => {
downloaTask = plus.downloader.createDownload(
realPath,
{
filename: TEMP_PATH + '/download/',
},
(task, status) => {
if (status === 200) {
getImageInfo(task.filename!)
} else {
trigger('error', {} as Event, {
errMsg: 'error',
})
}
}
)
downloaTask.start()
})
} else if (realPath) {
getImageInfo(realPath)
}
}
function getImageInfo(src: string) {
content.src = src
plusReady(() => {
plus.io.getImageInfo({
src,
success: ({ width, height }) => {
if (props.autoSize) {
style.value = `width:${width}px;height:${height}px;`
window.dispatchEvent(new CustomEvent('updateview'))
}
trigger('load', {} as Event, { width, height })
},
fail: () => {
trigger('error', {} as Event, {
errMsg: 'error',
})
},
})
})
}
if (props.src) {
loadImage()
}
watch(() => props.src, loadImage)
onBeforeUnmount(() => {
if (downloaTask) {
downloaTask.abort()
}
})
return style
}
export default /*#__PURE__*/ defineBuiltInComponent({
name: 'CoverImage',
props,
emits: ['click', 'load', 'error'],
setup(props, { emit }) {
const rootRef: Ref<HTMLElement | null> = ref(null)
const trigger = useCustomEvent<EmitEvent<typeof emit>>(rootRef, emit)
let content = reactive({ src: '' })
const style = useImageLoad(props, content, trigger)
useCover(rootRef, trigger, content)
return () => {
return (
<uni-cover-image ref={rootRef} style={style.value}>
<div class="uni-cover-image"></div>
</uni-cover-image>
)
}
},
})
import { Ref, ref, reactive } from 'vue'
import {
defineBuiltInComponent,
useCustomEvent,
EmitEvent,
flatVNode,
Text,
} from '@dcloudio/uni-components'
import { useCover } from '../../../helpers/useCover'
export default /*#__PURE__*/ defineBuiltInComponent({
name: 'CoverView',
emits: ['click'],
setup(_, { emit, slots }) {
const rootRef: Ref<HTMLElement | null> = ref(null)
const trigger = useCustomEvent<EmitEvent<typeof emit>>(rootRef, emit)
let content = reactive({ text: '' })
useCover(rootRef, trigger, content)
return () => {
const defaultSlots = slots.default ? flatVNode(slots.default()) : []
let text = ''
defaultSlots.forEach((node) => {
if (!node.type === Text) {
text += node.children || ''
}
})
content.text = text
return (
<uni-cover-view ref={rootRef}>
<div class="uni-cover-view">{text}</div>
</uni-cover-view>
)
}
},
})
...@@ -20,5 +20,6 @@ export { ...@@ -20,5 +20,6 @@ export {
defineBuiltInComponent, defineBuiltInComponent,
defineSystemComponent, defineSystemComponent,
} from './helpers/component' } from './helpers/component'
export { flatVNode } from './helpers/flatVNode'
export { uniFormKey } from './components/form' export { uniFormKey } from './components/form'
export type { UniFormCtx } from './components/form' export type { UniFormCtx } from './components/form'
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册