12 changed files with 610 additions and 613 deletions
@ -1,6 +1,7 @@ |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
import { withInstall } from '/@/utils/index'; |
|||
import basicDragVerify from './src/DragVerify.vue'; |
|||
import rotateDragVerify from './src/ImgRotate.vue'; |
|||
|
|||
export const BasicDragVerify = createAsyncComponent(() => import('./src/DragVerify')); |
|||
export const RotateDragVerify = createAsyncComponent(() => import('./src/ImgRotate')); |
|||
|
|||
export * from './src/types'; |
|||
export const BasicDragVerify = withInstall(basicDragVerify); |
|||
export const RotateDragVerify = withInstall(rotateDragVerify); |
|||
export * from './src/typing'; |
|||
|
|||
@ -1,87 +0,0 @@ |
|||
@radius: 4px; |
|||
|
|||
.darg-verify { |
|||
position: relative; |
|||
overflow: hidden; |
|||
text-align: center; |
|||
background-color: rgb(238, 238, 238); |
|||
border: 1px solid #ddd; |
|||
border-radius: @radius; |
|||
|
|||
&-bar { |
|||
position: absolute; |
|||
width: 0; |
|||
height: 36px; |
|||
background-color: @success-color; |
|||
border-radius: @radius; |
|||
|
|||
&.to-left { |
|||
width: 0 !important; |
|||
transition: width 0.3s; |
|||
} |
|||
} |
|||
|
|||
&-content { |
|||
position: absolute; |
|||
top: 0; |
|||
font-size: 12px; |
|||
-webkit-text-size-adjust: none; |
|||
background-color: -webkit-gradient( |
|||
linear, |
|||
left top, |
|||
right top, |
|||
color-stop(0, #333), |
|||
color-stop(0.4, #333), |
|||
color-stop(0.5, #fff), |
|||
color-stop(0.6, #333), |
|||
color-stop(1, #333) |
|||
); |
|||
animation: slidetounlock 3s infinite; |
|||
-webkit-background-clip: text; |
|||
-moz-user-select: none; |
|||
-webkit-user-select: none; |
|||
-o-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
-webkit-text-fill-color: transparent; |
|||
|
|||
&.success { |
|||
-webkit-text-fill-color: @white; |
|||
} |
|||
|
|||
& > * { |
|||
-webkit-text-fill-color: #333; |
|||
} |
|||
} |
|||
|
|||
&-action { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
display: flex; |
|||
cursor: move; |
|||
background-color: @white; |
|||
border-radius: @radius; |
|||
justify-content: center; |
|||
align-items: center; |
|||
|
|||
&__icon { |
|||
cursor: inherit; |
|||
} |
|||
|
|||
&.to-left { |
|||
left: 0 !important; |
|||
transition: left 0.3s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@-webkit-keyframes slidetounlock { |
|||
0% { |
|||
background-position: -120px 0; |
|||
} |
|||
|
|||
100% { |
|||
background-position: 120px 0; |
|||
} |
|||
} |
|||
@ -1,283 +0,0 @@ |
|||
import { defineComponent, ref, computed, unref, reactive, watch, watchEffect } from 'vue'; |
|||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
|||
import { useEventListener } from '/@/hooks/event/useEventListener'; |
|||
import { basicProps } from './props'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
import './DragVerify.less'; |
|||
import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; |
|||
import type { DragVerifyActionType } from './types'; |
|||
import { useExpose } from '/@/hooks/core/useExpose'; |
|||
export default defineComponent({ |
|||
name: 'BaseDargVerify', |
|||
props: basicProps, |
|||
emits: ['success', 'update:value', 'change', 'start', 'move', 'end'], |
|||
setup(props, { emit, slots }) { |
|||
const state = reactive({ |
|||
isMoving: false, |
|||
isPassing: false, |
|||
moveDistance: 0, |
|||
toLeft: false, |
|||
startTime: 0, |
|||
endTime: 0, |
|||
}); |
|||
|
|||
const wrapElRef = ref<HTMLDivElement | null>(null); |
|||
const barElRef = ref<HTMLDivElement | null>(null); |
|||
const contentElRef = ref<HTMLDivElement | null>(null); |
|||
const actionElRef = ref<HTMLDivElement | null>(null); |
|||
|
|||
watch( |
|||
() => state.isPassing, |
|||
(isPassing) => { |
|||
if (isPassing) { |
|||
const { startTime, endTime } = state; |
|||
const time = (endTime - startTime) / 1000; |
|||
emit('success', { isPassing, time: time.toFixed(1) }); |
|||
emit('update:value', isPassing); |
|||
emit('change', isPassing); |
|||
} |
|||
} |
|||
); |
|||
|
|||
watchEffect(() => { |
|||
state.isPassing = !!props.value; |
|||
}); |
|||
|
|||
const getActionStyleRef = computed(() => { |
|||
const { height, actionStyle } = props; |
|||
const h = `${parseInt(height as string)}px`; |
|||
return { |
|||
left: 0, |
|||
width: h, |
|||
height: h, |
|||
...actionStyle, |
|||
}; |
|||
}); |
|||
const getWrapStyleRef = computed(() => { |
|||
const { height, width, circle, wrapStyle } = props; |
|||
const h = parseInt(height as string); |
|||
const w = `${parseInt(width as string)}px`; |
|||
return { |
|||
width: w, |
|||
height: `${h}px`, |
|||
lineHeight: `${h}px`, |
|||
borderRadius: circle ? h / 2 + 'px' : 0, |
|||
...wrapStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getBarStyleRef = computed(() => { |
|||
const { height, circle, barStyle } = props; |
|||
const h = parseInt(height as string); |
|||
return { |
|||
height: `${h}px`, |
|||
borderRadius: circle ? h / 2 + 'px 0 0 ' + h / 2 + 'px' : 0, |
|||
...barStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getContentStyleRef = computed(() => { |
|||
const { height, width, contentStyle } = props; |
|||
const h = `${parseInt(height as string)}px`; |
|||
const w = `${parseInt(width as string)}px`; |
|||
|
|||
return { |
|||
height: h, |
|||
width: w, |
|||
...contentStyle, |
|||
}; |
|||
}); |
|||
|
|||
function getEventPageX(e: MouseEvent | TouchEvent) { |
|||
return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX; |
|||
} |
|||
|
|||
useEventListener({ |
|||
el: document, |
|||
name: 'mouseup', |
|||
listener: () => { |
|||
if (state.isMoving) { |
|||
resume(); |
|||
} |
|||
}, |
|||
}); |
|||
function handleDragStart(e: MouseEvent | TouchEvent) { |
|||
if (state.isPassing) { |
|||
return; |
|||
} |
|||
|
|||
const actionEl = unref(actionElRef); |
|||
if (!actionEl) return; |
|||
emit('start', e); |
|||
state.moveDistance = getEventPageX(e) - parseInt(actionEl.style.left.replace('px', ''), 10); |
|||
state.startTime = new Date().getTime(); |
|||
state.isMoving = true; |
|||
} |
|||
function getOffset(el: HTMLDivElement) { |
|||
const actionWidth = parseInt(el.style.width); |
|||
const { width } = props; |
|||
const widthNum = parseInt(width as string); |
|||
const offset = widthNum - actionWidth - 6; |
|||
return { offset, widthNum, actionWidth }; |
|||
} |
|||
function handleDragMoving(e: MouseEvent | TouchEvent) { |
|||
const { isMoving, moveDistance } = state; |
|||
if (isMoving) { |
|||
const actionEl = unref(actionElRef); |
|||
const barEl = unref(barElRef); |
|||
if (!actionEl || !barEl) return; |
|||
const { offset, widthNum, actionWidth } = getOffset(actionEl); |
|||
const moveX = getEventPageX(e) - moveDistance; |
|||
|
|||
emit('move', { |
|||
event: e, |
|||
moveDistance, |
|||
moveX, |
|||
}); |
|||
if (moveX > 0 && moveX <= offset) { |
|||
actionEl.style.left = `${moveX}px`; |
|||
barEl.style.width = `${moveX + actionWidth / 2}px`; |
|||
} else if (moveX > offset) { |
|||
actionEl.style.left = `${widthNum - actionWidth}px`; |
|||
barEl.style.width = `${widthNum - actionWidth / 2}px`; |
|||
if (!props.isSlot) { |
|||
checkPass(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function handleDragOver(e: MouseEvent | TouchEvent) { |
|||
const { isMoving, isPassing, moveDistance } = state; |
|||
if (isMoving && !isPassing) { |
|||
emit('end', e); |
|||
const actionEl = unref(actionElRef); |
|||
const barEl = unref(barElRef); |
|||
if (!actionEl || !barEl) return; |
|||
const moveX = getEventPageX(e) - moveDistance; |
|||
const { offset, widthNum, actionWidth } = getOffset(actionEl); |
|||
if (moveX < offset) { |
|||
if (!props.isSlot) { |
|||
resume(); |
|||
} else { |
|||
setTimeout(() => { |
|||
if (!props.value) { |
|||
resume(); |
|||
} else { |
|||
const contentEl = unref(contentElRef); |
|||
if (contentEl) { |
|||
contentEl.style.width = `${parseInt(barEl.style.width)}px`; |
|||
} |
|||
} |
|||
}, 0); |
|||
} |
|||
} else { |
|||
actionEl.style.left = `${widthNum - actionWidth}px`; |
|||
barEl.style.width = `${widthNum - actionWidth / 2}px`; |
|||
checkPass(); |
|||
} |
|||
state.isMoving = false; |
|||
} |
|||
} |
|||
|
|||
function checkPass() { |
|||
if (props.isSlot) { |
|||
resume(); |
|||
return; |
|||
} |
|||
state.endTime = new Date().getTime(); |
|||
state.isPassing = true; |
|||
state.isMoving = false; |
|||
} |
|||
|
|||
function resume() { |
|||
state.isMoving = false; |
|||
state.isPassing = false; |
|||
state.moveDistance = 0; |
|||
state.toLeft = false; |
|||
state.startTime = 0; |
|||
state.endTime = 0; |
|||
const actionEl = unref(actionElRef); |
|||
const barEl = unref(barElRef); |
|||
const contentEl = unref(contentElRef); |
|||
if (!actionEl || !barEl || !contentEl) return; |
|||
state.toLeft = true; |
|||
useTimeoutFn(() => { |
|||
state.toLeft = false; |
|||
actionEl.style.left = '0'; |
|||
barEl.style.width = '0'; |
|||
// The time is consistent with the animation time
|
|||
}, 300); |
|||
contentEl.style.width = unref(getContentStyleRef).width; |
|||
} |
|||
|
|||
useExpose<DragVerifyActionType>({ |
|||
resume, |
|||
}); |
|||
|
|||
return () => { |
|||
const renderBar = () => { |
|||
const cls = [`darg-verify-bar`]; |
|||
if (state.toLeft) { |
|||
cls.push('to-left'); |
|||
} |
|||
return <div class={cls} ref={barElRef} style={unref(getBarStyleRef)} />; |
|||
}; |
|||
|
|||
const renderContent = () => { |
|||
const cls = [`darg-verify-content`]; |
|||
const { isPassing } = state; |
|||
const { text, successText } = props; |
|||
|
|||
isPassing && cls.push('success'); |
|||
|
|||
return ( |
|||
<div class={cls} ref={contentElRef} style={unref(getContentStyleRef)}> |
|||
{getSlot(slots, 'text', isPassing) || (isPassing ? successText : text)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
const renderAction = () => { |
|||
const cls = [`darg-verify-action`]; |
|||
const { toLeft, isPassing } = state; |
|||
if (toLeft) { |
|||
cls.push('to-left'); |
|||
} |
|||
return ( |
|||
<div |
|||
class={cls} |
|||
onMousedown={handleDragStart} |
|||
onTouchstart={handleDragStart} |
|||
style={unref(getActionStyleRef)} |
|||
ref={actionElRef} |
|||
> |
|||
{getSlot(slots, 'actionIcon', isPassing) || |
|||
(isPassing ? ( |
|||
<CheckOutlined class={`darg-verify-action__icon`} /> |
|||
) : ( |
|||
<DoubleRightOutlined class={`darg-verify-action__icon`} /> |
|||
))} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
return ( |
|||
<div |
|||
class="darg-verify" |
|||
ref={wrapElRef} |
|||
style={unref(getWrapStyleRef)} |
|||
onMousemove={handleDragMoving} |
|||
onTouchmove={handleDragMoving} |
|||
onMouseleave={handleDragOver} |
|||
onMouseup={handleDragOver} |
|||
onTouchend={handleDragOver} |
|||
> |
|||
{renderBar()} |
|||
{renderContent()} |
|||
{renderAction()} |
|||
</div> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,375 @@ |
|||
<script lang="tsx"> |
|||
import { defineComponent, ref, computed, unref, reactive, watch, watchEffect } from 'vue'; |
|||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
|||
import { useEventListener } from '/@/hooks/event/useEventListener'; |
|||
import { basicProps } from './props'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
import { CheckOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'BaseDargVerify', |
|||
props: basicProps, |
|||
emits: ['success', 'update:value', 'change', 'start', 'move', 'end'], |
|||
setup(props, { emit, slots, expose }) { |
|||
const state = reactive({ |
|||
isMoving: false, |
|||
isPassing: false, |
|||
moveDistance: 0, |
|||
toLeft: false, |
|||
startTime: 0, |
|||
endTime: 0, |
|||
}); |
|||
|
|||
const wrapElRef = ref<HTMLDivElement | null>(null); |
|||
const barElRef = ref<HTMLDivElement | null>(null); |
|||
const contentElRef = ref<HTMLDivElement | null>(null); |
|||
const actionElRef = ref<HTMLDivElement | null>(null); |
|||
|
|||
useEventListener({ |
|||
el: document, |
|||
name: 'mouseup', |
|||
listener: () => { |
|||
if (state.isMoving) { |
|||
resume(); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
const getActionStyleRef = computed(() => { |
|||
const { height, actionStyle } = props; |
|||
const h = `${parseInt(height as string)}px`; |
|||
return { |
|||
left: 0, |
|||
width: h, |
|||
height: h, |
|||
...actionStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getWrapStyleRef = computed(() => { |
|||
const { height, width, circle, wrapStyle } = props; |
|||
const h = parseInt(height as string); |
|||
const w = `${parseInt(width as string)}px`; |
|||
return { |
|||
width: w, |
|||
height: `${h}px`, |
|||
lineHeight: `${h}px`, |
|||
borderRadius: circle ? h / 2 + 'px' : 0, |
|||
...wrapStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getBarStyleRef = computed(() => { |
|||
const { height, circle, barStyle } = props; |
|||
const h = parseInt(height as string); |
|||
return { |
|||
height: `${h}px`, |
|||
borderRadius: circle ? h / 2 + 'px 0 0 ' + h / 2 + 'px' : 0, |
|||
...barStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getContentStyleRef = computed(() => { |
|||
const { height, width, contentStyle } = props; |
|||
const h = `${parseInt(height as string)}px`; |
|||
const w = `${parseInt(width as string)}px`; |
|||
|
|||
return { |
|||
height: h, |
|||
width: w, |
|||
...contentStyle, |
|||
}; |
|||
}); |
|||
|
|||
watch( |
|||
() => state.isPassing, |
|||
(isPassing) => { |
|||
if (isPassing) { |
|||
const { startTime, endTime } = state; |
|||
const time = (endTime - startTime) / 1000; |
|||
emit('success', { isPassing, time: time.toFixed(1) }); |
|||
emit('update:value', isPassing); |
|||
emit('change', isPassing); |
|||
} |
|||
} |
|||
); |
|||
|
|||
watchEffect(() => { |
|||
state.isPassing = !!props.value; |
|||
}); |
|||
|
|||
function getEventPageX(e: MouseEvent | TouchEvent) { |
|||
return (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX; |
|||
} |
|||
|
|||
function handleDragStart(e: MouseEvent | TouchEvent) { |
|||
if (state.isPassing) { |
|||
return; |
|||
} |
|||
const actionEl = unref(actionElRef); |
|||
if (!actionEl) return; |
|||
emit('start', e); |
|||
state.moveDistance = getEventPageX(e) - parseInt(actionEl.style.left.replace('px', ''), 10); |
|||
state.startTime = new Date().getTime(); |
|||
state.isMoving = true; |
|||
} |
|||
|
|||
function getOffset(el: HTMLDivElement) { |
|||
const actionWidth = parseInt(el.style.width); |
|||
const { width } = props; |
|||
const widthNum = parseInt(width as string); |
|||
const offset = widthNum - actionWidth - 6; |
|||
return { offset, widthNum, actionWidth }; |
|||
} |
|||
|
|||
function handleDragMoving(e: MouseEvent | TouchEvent) { |
|||
const { isMoving, moveDistance } = state; |
|||
if (isMoving) { |
|||
const actionEl = unref(actionElRef); |
|||
const barEl = unref(barElRef); |
|||
if (!actionEl || !barEl) return; |
|||
const { offset, widthNum, actionWidth } = getOffset(actionEl); |
|||
const moveX = getEventPageX(e) - moveDistance; |
|||
|
|||
emit('move', { |
|||
event: e, |
|||
moveDistance, |
|||
moveX, |
|||
}); |
|||
if (moveX > 0 && moveX <= offset) { |
|||
actionEl.style.left = `${moveX}px`; |
|||
barEl.style.width = `${moveX + actionWidth / 2}px`; |
|||
} else if (moveX > offset) { |
|||
actionEl.style.left = `${widthNum - actionWidth}px`; |
|||
barEl.style.width = `${widthNum - actionWidth / 2}px`; |
|||
if (!props.isSlot) { |
|||
checkPass(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function handleDragOver(e: MouseEvent | TouchEvent) { |
|||
const { isMoving, isPassing, moveDistance } = state; |
|||
if (isMoving && !isPassing) { |
|||
emit('end', e); |
|||
const actionEl = unref(actionElRef); |
|||
const barEl = unref(barElRef); |
|||
if (!actionEl || !barEl) return; |
|||
const moveX = getEventPageX(e) - moveDistance; |
|||
const { offset, widthNum, actionWidth } = getOffset(actionEl); |
|||
if (moveX < offset) { |
|||
if (!props.isSlot) { |
|||
resume(); |
|||
} else { |
|||
setTimeout(() => { |
|||
if (!props.value) { |
|||
resume(); |
|||
} else { |
|||
const contentEl = unref(contentElRef); |
|||
if (contentEl) { |
|||
contentEl.style.width = `${parseInt(barEl.style.width)}px`; |
|||
} |
|||
} |
|||
}, 0); |
|||
} |
|||
} else { |
|||
actionEl.style.left = `${widthNum - actionWidth}px`; |
|||
barEl.style.width = `${widthNum - actionWidth / 2}px`; |
|||
checkPass(); |
|||
} |
|||
state.isMoving = false; |
|||
} |
|||
} |
|||
|
|||
function checkPass() { |
|||
if (props.isSlot) { |
|||
resume(); |
|||
return; |
|||
} |
|||
state.endTime = new Date().getTime(); |
|||
state.isPassing = true; |
|||
state.isMoving = false; |
|||
} |
|||
|
|||
function resume() { |
|||
state.isMoving = false; |
|||
state.isPassing = false; |
|||
state.moveDistance = 0; |
|||
state.toLeft = false; |
|||
state.startTime = 0; |
|||
state.endTime = 0; |
|||
const actionEl = unref(actionElRef); |
|||
const barEl = unref(barElRef); |
|||
const contentEl = unref(contentElRef); |
|||
if (!actionEl || !barEl || !contentEl) return; |
|||
state.toLeft = true; |
|||
useTimeoutFn(() => { |
|||
state.toLeft = false; |
|||
actionEl.style.left = '0'; |
|||
barEl.style.width = '0'; |
|||
// The time is consistent with the animation time |
|||
}, 300); |
|||
contentEl.style.width = unref(getContentStyleRef).width; |
|||
} |
|||
|
|||
expose({ |
|||
resume, |
|||
}); |
|||
|
|||
return () => { |
|||
const renderBar = () => { |
|||
const cls = [`darg-verify-bar`]; |
|||
if (state.toLeft) { |
|||
cls.push('to-left'); |
|||
} |
|||
return <div class={cls} ref={barElRef} style={unref(getBarStyleRef)} />; |
|||
}; |
|||
|
|||
const renderContent = () => { |
|||
const cls = [`darg-verify-content`]; |
|||
const { isPassing } = state; |
|||
const { text, successText } = props; |
|||
|
|||
isPassing && cls.push('success'); |
|||
|
|||
return ( |
|||
<div class={cls} ref={contentElRef} style={unref(getContentStyleRef)}> |
|||
{getSlot(slots, 'text', isPassing) || (isPassing ? successText : text)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
const renderAction = () => { |
|||
const cls = [`darg-verify-action`]; |
|||
const { toLeft, isPassing } = state; |
|||
if (toLeft) { |
|||
cls.push('to-left'); |
|||
} |
|||
return ( |
|||
<div |
|||
class={cls} |
|||
onMousedown={handleDragStart} |
|||
onTouchstart={handleDragStart} |
|||
style={unref(getActionStyleRef)} |
|||
ref={actionElRef} |
|||
> |
|||
{getSlot(slots, 'actionIcon', isPassing) || |
|||
(isPassing ? ( |
|||
<CheckOutlined class={`darg-verify-action__icon`} /> |
|||
) : ( |
|||
<DoubleRightOutlined class={`darg-verify-action__icon`} /> |
|||
))} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
return ( |
|||
<div |
|||
class="darg-verify" |
|||
ref={wrapElRef} |
|||
style={unref(getWrapStyleRef)} |
|||
onMousemove={handleDragMoving} |
|||
onTouchmove={handleDragMoving} |
|||
onMouseleave={handleDragOver} |
|||
onMouseup={handleDragOver} |
|||
onTouchend={handleDragOver} |
|||
> |
|||
{renderBar()} |
|||
{renderContent()} |
|||
{renderAction()} |
|||
</div> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@radius: 4px; |
|||
|
|||
.darg-verify { |
|||
position: relative; |
|||
overflow: hidden; |
|||
text-align: center; |
|||
background-color: rgb(238, 238, 238); |
|||
border: 1px solid #ddd; |
|||
border-radius: @radius; |
|||
|
|||
&-bar { |
|||
position: absolute; |
|||
width: 0; |
|||
height: 36px; |
|||
background-color: @success-color; |
|||
border-radius: @radius; |
|||
|
|||
&.to-left { |
|||
width: 0 !important; |
|||
transition: width 0.3s; |
|||
} |
|||
} |
|||
|
|||
&-content { |
|||
position: absolute; |
|||
top: 0; |
|||
font-size: 12px; |
|||
-webkit-text-size-adjust: none; |
|||
background-color: -webkit-gradient( |
|||
linear, |
|||
left top, |
|||
right top, |
|||
color-stop(0, #333), |
|||
color-stop(0.4, #333), |
|||
color-stop(0.5, #fff), |
|||
color-stop(0.6, #333), |
|||
color-stop(1, #333) |
|||
); |
|||
animation: slidetounlock 3s infinite; |
|||
-webkit-background-clip: text; |
|||
-moz-user-select: none; |
|||
-webkit-user-select: none; |
|||
-o-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
-webkit-text-fill-color: transparent; |
|||
|
|||
&.success { |
|||
-webkit-text-fill-color: @white; |
|||
} |
|||
|
|||
& > * { |
|||
-webkit-text-fill-color: #333; |
|||
} |
|||
} |
|||
|
|||
&-action { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
display: flex; |
|||
cursor: move; |
|||
background-color: @white; |
|||
border-radius: @radius; |
|||
justify-content: center; |
|||
align-items: center; |
|||
|
|||
&__icon { |
|||
cursor: inherit; |
|||
} |
|||
|
|||
&.to-left { |
|||
left: 0 !important; |
|||
transition: left 0.3s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@-webkit-keyframes slidetounlock { |
|||
0% { |
|||
background-position: -120px 0; |
|||
} |
|||
|
|||
100% { |
|||
background-position: 120px 0; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,51 +0,0 @@ |
|||
.ir-dv { |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
&-img__wrap { |
|||
position: relative; |
|||
overflow: hidden; |
|||
border-radius: 50%; |
|||
|
|||
img { |
|||
width: 100%; |
|||
border-radius: 50%; |
|||
|
|||
&.to-origin { |
|||
transition: transform 0.3s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
&-img__tip { |
|||
position: absolute; |
|||
bottom: 10px; |
|||
left: 0; |
|||
z-index: 1; |
|||
display: block; |
|||
width: 100%; |
|||
height: 30px; |
|||
font-size: 12px; |
|||
line-height: 30px; |
|||
color: @white; |
|||
text-align: center; |
|||
|
|||
&.success { |
|||
background-color: fade(@success-color, 60%); |
|||
} |
|||
|
|||
&.error { |
|||
background-color: fade(@error-color, 60%); |
|||
} |
|||
|
|||
&.normal { |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
} |
|||
} |
|||
|
|||
&-drag__bar { |
|||
margin-top: 20px; |
|||
} |
|||
} |
|||
@ -1,172 +0,0 @@ |
|||
import './ImgRotate.less'; |
|||
|
|||
import type { MoveData, DragVerifyActionType } from './types'; |
|||
|
|||
import { defineComponent, computed, unref, reactive, watch, ref, getCurrentInstance } from 'vue'; |
|||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
|||
|
|||
import BasicDragVerify from './DragVerify'; |
|||
|
|||
import { hackCss } from '/@/utils/domUtils'; |
|||
|
|||
import { rotateProps } from './props'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'ImgRotateDargVerify', |
|||
inheritAttrs: false, |
|||
props: rotateProps, |
|||
emits: ['success', 'change', 'update:value'], |
|||
setup(props, { emit, attrs }) { |
|||
const basicRef = ref<Nullable<DragVerifyActionType>>(null); |
|||
const state = reactive({ |
|||
showTip: false, |
|||
isPassing: false, |
|||
imgStyle: {}, |
|||
randomRotate: 0, |
|||
currentRotate: 0, |
|||
toOrigin: false, |
|||
startTime: 0, |
|||
endTime: 0, |
|||
draged: false, |
|||
}); |
|||
const { t } = useI18n(); |
|||
|
|||
watch( |
|||
() => state.isPassing, |
|||
(isPassing) => { |
|||
if (isPassing) { |
|||
const { startTime, endTime } = state; |
|||
const time = (endTime - startTime) / 1000; |
|||
emit('success', { isPassing, time: time.toFixed(1) }); |
|||
emit('change', isPassing); |
|||
emit('update:value', isPassing); |
|||
} |
|||
} |
|||
); |
|||
|
|||
const getImgWrapStyleRef = computed(() => { |
|||
const { imgWrapStyle, imgWidth } = props; |
|||
return { |
|||
width: `${imgWidth}px`, |
|||
height: `${imgWidth}px`, |
|||
...imgWrapStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getFactorRef = computed(() => { |
|||
const { minDegree, maxDegree } = props; |
|||
if (minDegree === maxDegree) { |
|||
return Math.floor(1 + Math.random() * 1) / 10 + 1; |
|||
} |
|||
return 1; |
|||
}); |
|||
function handleStart() { |
|||
state.startTime = new Date().getTime(); |
|||
} |
|||
|
|||
function handleDragBarMove(data: MoveData) { |
|||
state.draged = true; |
|||
const { imgWidth, height, maxDegree } = props; |
|||
const { moveX } = data; |
|||
const currentRotate = Math.ceil( |
|||
(moveX / (imgWidth! - parseInt(height as string))) * maxDegree! * unref(getFactorRef) |
|||
); |
|||
state.currentRotate = currentRotate; |
|||
state.imgStyle = hackCss('transform', `rotateZ(${state.randomRotate - currentRotate}deg)`); |
|||
} |
|||
|
|||
function handleImgOnLoad() { |
|||
const { minDegree, maxDegree } = props; |
|||
const ranRotate = Math.floor(minDegree! + Math.random() * (maxDegree! - minDegree!)); // 生成随机角度
|
|||
state.randomRotate = ranRotate; |
|||
state.imgStyle = hackCss('transform', `rotateZ(${ranRotate}deg)`); |
|||
} |
|||
|
|||
function handleDragEnd() { |
|||
const { randomRotate, currentRotate } = state; |
|||
const { diffDegree } = props; |
|||
|
|||
if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) { |
|||
state.imgStyle = hackCss('transform', `rotateZ(${randomRotate}deg)`); |
|||
state.toOrigin = true; |
|||
useTimeoutFn(() => { |
|||
state.toOrigin = false; |
|||
state.showTip = true; |
|||
// 时间与动画时间保持一致
|
|||
}, 300); |
|||
} else { |
|||
checkPass(); |
|||
} |
|||
state.showTip = true; |
|||
} |
|||
function checkPass() { |
|||
state.isPassing = true; |
|||
state.endTime = new Date().getTime(); |
|||
} |
|||
|
|||
function resume() { |
|||
state.showTip = false; |
|||
const basicEl = unref(basicRef); |
|||
if (!basicEl) { |
|||
return; |
|||
} |
|||
state.isPassing = false; |
|||
|
|||
basicEl.resume(); |
|||
handleImgOnLoad(); |
|||
} |
|||
|
|||
const instance = getCurrentInstance() as any; |
|||
if (instance) { |
|||
instance.resume = resume; |
|||
} |
|||
// handleImgOnLoad();
|
|||
return () => { |
|||
const { src } = props; |
|||
const { toOrigin, isPassing, startTime, endTime } = state; |
|||
const imgCls: string[] = []; |
|||
if (toOrigin) { |
|||
imgCls.push('to-origin'); |
|||
} |
|||
const time = (endTime - startTime) / 1000; |
|||
|
|||
return ( |
|||
<div class="ir-dv"> |
|||
<div class={`ir-dv-img__wrap`} style={unref(getImgWrapStyleRef)}> |
|||
<img |
|||
src={src} |
|||
onLoad={handleImgOnLoad} |
|||
width={parseInt(props.width as string)} |
|||
class={imgCls} |
|||
style={state.imgStyle} |
|||
onClick={() => { |
|||
resume(); |
|||
}} |
|||
/> |
|||
{state.showTip && ( |
|||
<span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}> |
|||
{state.isPassing |
|||
? t('component.verify.time', { time: time.toFixed(1) }) |
|||
: t('component.verify.error')} |
|||
</span> |
|||
)} |
|||
{!state.showTip && !state.draged && ( |
|||
<span class={[`ir-dv-img__tip`, 'normal']}>{t('component.verify.redoTip')}</span> |
|||
)} |
|||
</div> |
|||
<BasicDragVerify |
|||
class={`ir-dv-drag__bar`} |
|||
onMove={handleDragBarMove} |
|||
onEnd={handleDragEnd} |
|||
onStart={handleStart} |
|||
ref={basicRef} |
|||
{...{ ...attrs, ...props }} |
|||
value={isPassing} |
|||
isSlot={true} |
|||
/> |
|||
</div> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,221 @@ |
|||
<script lang="tsx"> |
|||
import type { MoveData, DragVerifyActionType } from './typing'; |
|||
import { defineComponent, computed, unref, reactive, watch, ref, getCurrentInstance } from 'vue'; |
|||
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; |
|||
import BasicDragVerify from './DragVerify.vue'; |
|||
import { hackCss } from '/@/utils/domUtils'; |
|||
import { rotateProps } from './props'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'ImgRotateDargVerify', |
|||
inheritAttrs: false, |
|||
props: rotateProps, |
|||
emits: ['success', 'change', 'update:value'], |
|||
setup(props, { emit, attrs }) { |
|||
const basicRef = ref<Nullable<DragVerifyActionType>>(null); |
|||
const state = reactive({ |
|||
showTip: false, |
|||
isPassing: false, |
|||
imgStyle: {}, |
|||
randomRotate: 0, |
|||
currentRotate: 0, |
|||
toOrigin: false, |
|||
startTime: 0, |
|||
endTime: 0, |
|||
draged: false, |
|||
}); |
|||
const { t } = useI18n(); |
|||
|
|||
watch( |
|||
() => state.isPassing, |
|||
(isPassing) => { |
|||
if (isPassing) { |
|||
const { startTime, endTime } = state; |
|||
const time = (endTime - startTime) / 1000; |
|||
emit('success', { isPassing, time: time.toFixed(1) }); |
|||
emit('change', isPassing); |
|||
emit('update:value', isPassing); |
|||
} |
|||
} |
|||
); |
|||
|
|||
const getImgWrapStyleRef = computed(() => { |
|||
const { imgWrapStyle, imgWidth } = props; |
|||
return { |
|||
width: `${imgWidth}px`, |
|||
height: `${imgWidth}px`, |
|||
...imgWrapStyle, |
|||
}; |
|||
}); |
|||
|
|||
const getFactorRef = computed(() => { |
|||
const { minDegree, maxDegree } = props; |
|||
if (minDegree === maxDegree) { |
|||
return Math.floor(1 + Math.random() * 1) / 10 + 1; |
|||
} |
|||
return 1; |
|||
}); |
|||
function handleStart() { |
|||
state.startTime = new Date().getTime(); |
|||
} |
|||
|
|||
function handleDragBarMove(data: MoveData) { |
|||
state.draged = true; |
|||
const { imgWidth, height, maxDegree } = props; |
|||
const { moveX } = data; |
|||
const currentRotate = Math.ceil( |
|||
(moveX / (imgWidth! - parseInt(height as string))) * maxDegree! * unref(getFactorRef) |
|||
); |
|||
state.currentRotate = currentRotate; |
|||
state.imgStyle = hackCss('transform', `rotateZ(${state.randomRotate - currentRotate}deg)`); |
|||
} |
|||
|
|||
function handleImgOnLoad() { |
|||
const { minDegree, maxDegree } = props; |
|||
const ranRotate = Math.floor(minDegree! + Math.random() * (maxDegree! - minDegree!)); // 生成随机角度 |
|||
state.randomRotate = ranRotate; |
|||
state.imgStyle = hackCss('transform', `rotateZ(${ranRotate}deg)`); |
|||
} |
|||
|
|||
function handleDragEnd() { |
|||
const { randomRotate, currentRotate } = state; |
|||
const { diffDegree } = props; |
|||
|
|||
if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) { |
|||
state.imgStyle = hackCss('transform', `rotateZ(${randomRotate}deg)`); |
|||
state.toOrigin = true; |
|||
useTimeoutFn(() => { |
|||
state.toOrigin = false; |
|||
state.showTip = true; |
|||
// 时间与动画时间保持一致 |
|||
}, 300); |
|||
} else { |
|||
checkPass(); |
|||
} |
|||
state.showTip = true; |
|||
} |
|||
function checkPass() { |
|||
state.isPassing = true; |
|||
state.endTime = new Date().getTime(); |
|||
} |
|||
|
|||
function resume() { |
|||
state.showTip = false; |
|||
const basicEl = unref(basicRef); |
|||
if (!basicEl) { |
|||
return; |
|||
} |
|||
state.isPassing = false; |
|||
|
|||
basicEl.resume(); |
|||
handleImgOnLoad(); |
|||
} |
|||
|
|||
const instance = getCurrentInstance() as any; |
|||
if (instance) { |
|||
instance.resume = resume; |
|||
} |
|||
// handleImgOnLoad(); |
|||
return () => { |
|||
const { src } = props; |
|||
const { toOrigin, isPassing, startTime, endTime } = state; |
|||
const imgCls: string[] = []; |
|||
if (toOrigin) { |
|||
imgCls.push('to-origin'); |
|||
} |
|||
const time = (endTime - startTime) / 1000; |
|||
|
|||
return ( |
|||
<div class="ir-dv"> |
|||
<div class={`ir-dv-img__wrap`} style={unref(getImgWrapStyleRef)}> |
|||
<img |
|||
src={src} |
|||
onLoad={handleImgOnLoad} |
|||
width={parseInt(props.width as string)} |
|||
class={imgCls} |
|||
style={state.imgStyle} |
|||
onClick={() => { |
|||
resume(); |
|||
}} |
|||
/> |
|||
{state.showTip && ( |
|||
<span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}> |
|||
{state.isPassing |
|||
? t('component.verify.time', { time: time.toFixed(1) }) |
|||
: t('component.verify.error')} |
|||
</span> |
|||
)} |
|||
{!state.showTip && !state.draged && ( |
|||
<span class={[`ir-dv-img__tip`, 'normal']}>{t('component.verify.redoTip')}</span> |
|||
)} |
|||
</div> |
|||
<BasicDragVerify |
|||
class={`ir-dv-drag__bar`} |
|||
onMove={handleDragBarMove} |
|||
onEnd={handleDragEnd} |
|||
onStart={handleStart} |
|||
ref={basicRef} |
|||
{...{ ...attrs, ...props }} |
|||
value={isPassing} |
|||
isSlot={true} |
|||
/> |
|||
</div> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
.ir-dv { |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
&-img__wrap { |
|||
position: relative; |
|||
overflow: hidden; |
|||
border-radius: 50%; |
|||
|
|||
img { |
|||
width: 100%; |
|||
border-radius: 50%; |
|||
|
|||
&.to-origin { |
|||
transition: transform 0.3s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
&-img__tip { |
|||
position: absolute; |
|||
bottom: 10px; |
|||
left: 0; |
|||
z-index: 1; |
|||
display: block; |
|||
width: 100%; |
|||
height: 30px; |
|||
font-size: 12px; |
|||
line-height: 30px; |
|||
color: @white; |
|||
text-align: center; |
|||
|
|||
&.success { |
|||
background-color: fade(@success-color, 60%); |
|||
} |
|||
|
|||
&.error { |
|||
background-color: fade(@error-color, 60%); |
|||
} |
|||
|
|||
&.normal { |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
} |
|||
} |
|||
|
|||
&-drag__bar { |
|||
margin-top: 20px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -1,9 +0,0 @@ |
|||
import { getCurrentInstance } from 'vue'; |
|||
|
|||
// expose public api
|
|||
export function useExpose<T>(apis: T) { |
|||
const instance = getCurrentInstance(); |
|||
if (instance) { |
|||
Object.assign(instance.proxy, apis); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue