mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-02-03 02:54:40 +08:00
refactor: remove useExpose
This commit is contained in:
parent
3b2c40bec8
commit
bd2039accb
@ -1,5 +1,9 @@
|
|||||||
## Wip
|
## Wip
|
||||||
|
|
||||||
|
### ✨ Refactor
|
||||||
|
|
||||||
|
- 移除`useExpose`,使用组件自身提供的`expose`代替
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
- **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
|
- **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
|
||||||
|
@ -59,7 +59,6 @@
|
|||||||
import { createTableContext } from './hooks/useTableContext';
|
import { createTableContext } from './hooks/useTableContext';
|
||||||
import { useTableFooter } from './hooks/useTableFooter';
|
import { useTableFooter } from './hooks/useTableFooter';
|
||||||
import { useTableForm } from './hooks/useTableForm';
|
import { useTableForm } from './hooks/useTableForm';
|
||||||
import { useExpose } from '/@/hooks/core/useExpose';
|
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
@ -91,7 +90,7 @@
|
|||||||
'change',
|
'change',
|
||||||
'columns-change',
|
'columns-change',
|
||||||
],
|
],
|
||||||
setup(props, { attrs, emit, slots }) {
|
setup(props, { attrs, emit, slots, expose }) {
|
||||||
const tableElRef = ref<ComponentRef>(null);
|
const tableElRef = ref<ComponentRef>(null);
|
||||||
const tableData = ref<Recordable[]>([]);
|
const tableData = ref<Recordable[]>([]);
|
||||||
|
|
||||||
@ -290,7 +289,7 @@
|
|||||||
};
|
};
|
||||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||||
|
|
||||||
useExpose<TableActionType>(tableAction);
|
expose(tableAction);
|
||||||
|
|
||||||
emit('register', tableAction, formActions);
|
emit('register', tableAction, formActions);
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
import { useTree } from './useTree';
|
import { useTree } from './useTree';
|
||||||
import { useContextMenu } from '/@/hooks/web/useContextMenu';
|
import { useContextMenu } from '/@/hooks/web/useContextMenu';
|
||||||
import { useExpose } from '/@/hooks/core/useExpose';
|
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
@ -44,7 +43,7 @@
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: basicProps,
|
props: basicProps,
|
||||||
emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change', 'check'],
|
emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change', 'check'],
|
||||||
setup(props, { attrs, slots, emit }) {
|
setup(props, { attrs, slots, emit, expose }) {
|
||||||
const state = reactive<State>({
|
const state = reactive<State>({
|
||||||
checkStrictly: props.checkStrictly,
|
checkStrictly: props.checkStrictly,
|
||||||
expandedKeys: props.expandedKeys || [],
|
expandedKeys: props.expandedKeys || [],
|
||||||
@ -277,7 +276,7 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
useExpose<TreeActionType>(instance);
|
expose(instance);
|
||||||
|
|
||||||
function renderAction(node: TreeItem) {
|
function renderAction(node: TreeItem) {
|
||||||
const { actionList } = props;
|
const { actionList } = props;
|
||||||
|
@ -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 BasicDragVerify = withInstall(basicDragVerify);
|
||||||
export const RotateDragVerify = createAsyncComponent(() => import('./src/ImgRotate'));
|
export const RotateDragVerify = withInstall(rotateDragVerify);
|
||||||
|
export * from './src/typing';
|
||||||
export * from './src/types';
|
|
||||||
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
375
src/components/Verify/src/DragVerify.vue
Normal file
375
src/components/Verify/src/DragVerify.vue
Normal file
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
221
src/components/Verify/src/ImgRotate.vue
Normal file
221
src/components/Verify/src/ImgRotate.vue
Normal file
@ -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
Block a user