|
|
@@ -0,0 +1,324 @@
|
|
|
+import Taro from '@tarojs/taro';
|
|
|
+import { Canvas, View } from '@tarojs/components';
|
|
|
+import { useEffect, useRef, useState } from 'react';
|
|
|
+
|
|
|
+export default function Component(props: { current: { start: string, end: string }, updateDuration: (start: string, end: string) => void }) {
|
|
|
+ const canvasRef = useRef<any>(null);
|
|
|
+ const [currentContext, setCurrentContext] = useState(null)
|
|
|
+ const canvasWidth: number = 200;
|
|
|
+ const canvasHeight: number = 200;
|
|
|
+ const circleRadius: number = 100;
|
|
|
+ const ringWidth: number = 30;
|
|
|
+ const dotRadius: number = 10;
|
|
|
+ const dpr = Taro.getSystemInfoSync().pixelRatio; // 获取设备的像素比
|
|
|
+
|
|
|
+ const canvasId = 'canvasDialId';
|
|
|
+
|
|
|
+ var canStartDrag = false;
|
|
|
+ var canEndDrag = false;
|
|
|
+ var canRingDrag = false;
|
|
|
+
|
|
|
+ var startCount = parseInt((props.current.start.split(':') as any)[0]) * 60 + parseInt((props.current.start.split(':') as any)[1]);
|
|
|
+ var endCount = parseInt((props.current.end.split(':') as any)[0]) * 60 + parseInt((props.current.end.split(':') as any)[1]);
|
|
|
+ var startAngle1 = (startCount / 1440) * 2 * Math.PI - Math.PI / 2;
|
|
|
+ var endAngle1 = (endCount / 1440) * 2 * Math.PI - Math.PI / 2;
|
|
|
+ endAngle1 = endAngle1 > startAngle1 ? endAngle1 : endAngle1 + 2 * Math.PI;
|
|
|
+
|
|
|
+
|
|
|
+ const [startAngle,setStartAngle] = useState(startAngle1)
|
|
|
+ const [endAngle,setEndAngle] = useState(endAngle1)
|
|
|
+
|
|
|
+ var lastAngle;
|
|
|
+ var lastDuration = 0;
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+
|
|
|
+ const query = Taro.createSelectorQuery();
|
|
|
+ query.select(`.${canvasId}`).fields({ node: true, size: true });
|
|
|
+ query.exec((res) => {
|
|
|
+ if (res[0] == null) return;
|
|
|
+ const _canvas = res[0].node;
|
|
|
+ _canvas.width = res[0].width * dpr;
|
|
|
+ _canvas.height = res[0].height * dpr;
|
|
|
+ const ctx = _canvas.getContext('2d');
|
|
|
+ setCurrentContext(ctx)
|
|
|
+
|
|
|
+ // 设置画布尺寸
|
|
|
+ ctx.scale(dpr, dpr);
|
|
|
+
|
|
|
+
|
|
|
+ drawCanvas(ctx);
|
|
|
+ });
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const drawCanvas = (ctx: any) => {
|
|
|
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
|
+
|
|
|
+ // 绘制圆环
|
|
|
+ const centerX = canvasWidth / 2;
|
|
|
+ const centerY = canvasHeight / 2;
|
|
|
+ const radius = Math.min(centerX, centerY) - 10;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
|
+
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
|
|
+ ctx.strokeStyle = "rgba(0,0,0,0.5)";
|
|
|
+ ctx.lineWidth = 10;
|
|
|
+ ctx.stroke();
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(centerX, centerY, radius, startAngle, endAngle);
|
|
|
+ ctx.strokeStyle = canRingDrag ? "red" : "rgba(255,0,0,0.5)";
|
|
|
+ ctx.lineWidth = 10;
|
|
|
+ ctx.stroke();
|
|
|
+
|
|
|
+ const pointX = centerX + Math.cos(startAngle) * radius;
|
|
|
+ const pointY = centerY + Math.sin(startAngle) * radius;
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(pointX, pointY, 5, 0, 2 * Math.PI);
|
|
|
+ ctx.fillStyle = '#00ff00';
|
|
|
+ ctx.fill();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 绘制蓝色终点圆点
|
|
|
+ const pointX2 = centerX + Math.cos(endAngle) * radius;
|
|
|
+ const pointY2 = centerY + Math.sin(endAngle) * radius;
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(pointX2, pointY2, 5, 0, 2 * Math.PI);
|
|
|
+ ctx.fillStyle = 'blue';
|
|
|
+ ctx.fill();
|
|
|
+ };
|
|
|
+
|
|
|
+ function twoPointDistance(p1, p2) {
|
|
|
+ let dep = Math.sqrt(Math.pow((p1.x - p2.x), 2) + Math.pow((p1.y - p2.y), 2));
|
|
|
+ return dep;
|
|
|
+ }
|
|
|
+
|
|
|
+ function limitAngle(start, end) {
|
|
|
+ var angle1 = start < 0 ? start + 2 * Math.PI : start;
|
|
|
+ var angle2 = end < 0 ? end + 2 * Math.PI : end;
|
|
|
+ if (angle2 < angle1) {
|
|
|
+ angle2 = angle2 + 2 * Math.PI;
|
|
|
+ }
|
|
|
+
|
|
|
+ var leftAngle = angle2 - angle1;
|
|
|
+ if (leftAngle > Math.PI * 23 / 12) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ else if (leftAngle < Math.PI / 12) {
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ function durationAngle(end, start) {
|
|
|
+ var angle1 = start < 0 ? start + 2 * Math.PI : start;
|
|
|
+ var angle2 = end < 0 ? end + 2 * Math.PI : end;
|
|
|
+ if (angle2 < angle1) {
|
|
|
+ angle2 = angle2 + 2 * Math.PI;
|
|
|
+ }
|
|
|
+
|
|
|
+ return angle2 - angle1;
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleTouchMove = (e: any) => {
|
|
|
+ const ctx = currentContext;//canvasRef.current.getContext('2d');
|
|
|
+ if (!canStartDrag && !canEndDrag && !canRingDrag) {
|
|
|
+ drawCanvas(ctx);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { x, y } = e.touches[0];
|
|
|
+
|
|
|
+ let angle = Math.atan2(y - canvasWidth / 2.0, x - canvasWidth / 2.0)
|
|
|
+
|
|
|
+ if (canStartDrag) {
|
|
|
+ if (Math.abs(durationAngle(endAngle, angle) - lastDuration) > Math.PI) {
|
|
|
+ //禁止跨越
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var result = limitAngle(angle, endAngle);
|
|
|
+ switch (result) {
|
|
|
+ case 0:
|
|
|
+ startAngle = angle;
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ startAngle = endAngle - 23 * Math.PI / 12;
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ startAngle = endAngle - Math.PI / 12;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (canEndDrag) {
|
|
|
+ if (Math.abs(durationAngle(angle, startAngle) - lastDuration) > Math.PI) {
|
|
|
+ //禁止跨越
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var result = limitAngle(startAngle, angle);
|
|
|
+ switch (result) {
|
|
|
+ case 0:
|
|
|
+ endAngle = angle;
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ endAngle = startAngle + 23 * Math.PI / 12;
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ endAngle = startAngle + Math.PI / 12;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (canRingDrag) {
|
|
|
+ let delta = angle - lastAngle;
|
|
|
+ startAngle += delta;
|
|
|
+ endAngle += delta;
|
|
|
+ lastAngle = angle;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ startAngle = normalizeAngle(startAngle);
|
|
|
+ endAngle = normalizeAngle(endAngle);
|
|
|
+
|
|
|
+ angleToTime();
|
|
|
+
|
|
|
+ drawCanvas(ctx);
|
|
|
+
|
|
|
+ lastDuration = durationAngle(endAngle, startAngle);
|
|
|
+ };
|
|
|
+
|
|
|
+ function angleToTime() {
|
|
|
+ var startCount = (startAngle + Math.PI / 2) / (2 * Math.PI) * 1440;
|
|
|
+ var endCount = (endAngle + Math.PI / 2) / (2 * Math.PI) * 1440;
|
|
|
+ startCount = startCount > 0 ? startCount : startCount + 1440;
|
|
|
+ endCount = endCount > 0 ? endCount : endCount + 1440;
|
|
|
+
|
|
|
+ startCount = roundToNearestFive(startCount);
|
|
|
+ endCount = roundToNearestFive(endCount);
|
|
|
+
|
|
|
+ var startHour = Math.floor(startCount / 60);
|
|
|
+ var startMinute = Math.floor(startCount % 60);
|
|
|
+ var endHour = Math.floor(endCount / 60);
|
|
|
+ var endMinute = Math.floor(endCount % 60);
|
|
|
+ startHour = startHour < 24 ? startHour : startHour - 24;
|
|
|
+ endHour = endHour < 24 ? endHour : endHour - 24;
|
|
|
+ var start = (startHour < 10 ? '0' + startHour : startHour) + ':' + (startMinute < 10 ? '0' + startMinute : startMinute);
|
|
|
+ var end = (endHour < 10 ? '0' + endHour : endHour) + ':' + (endMinute < 10 ? '0' + endMinute : endMinute);
|
|
|
+ props.updateDuration(start, end);
|
|
|
+ }
|
|
|
+
|
|
|
+ function roundToNearestFive(minutes: number): number {
|
|
|
+ const remainder = minutes % 5;
|
|
|
+ if (remainder === 0) {
|
|
|
+ return minutes;
|
|
|
+ } else if (remainder < 3) {
|
|
|
+ return minutes - remainder;
|
|
|
+ } else {
|
|
|
+ return minutes + (5 - remainder);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleClick = (e) => {
|
|
|
+
|
|
|
+ const scaleX = 1//dpr//canvasRect.width / canvasRect.width;
|
|
|
+ const scaleY = 1//dpr//canvasRect.height / canvasRect.height;
|
|
|
+ const canvasX = (e.touches[0].x - 0) * scaleX;
|
|
|
+ const canvasY = (e.touches[0].y - 0) * scaleY;
|
|
|
+
|
|
|
+ // 判断点击位置是否在圆点范围内
|
|
|
+ const centerX = canvasWidth / 2;
|
|
|
+ const centerY = canvasWidth / 2;
|
|
|
+ const radius = Math.min(centerX, centerY) - 10;
|
|
|
+
|
|
|
+
|
|
|
+ const pointX = centerX + Math.cos(startAngle) * radius;
|
|
|
+ const pointY = centerY + Math.sin(startAngle) * radius;
|
|
|
+ const distance = Math.sqrt(Math.pow(canvasX - pointX, 2) + Math.pow(canvasY - pointY, 2));
|
|
|
+
|
|
|
+ if (distance <= 12) {
|
|
|
+ canStartDrag = true;
|
|
|
+ } else {
|
|
|
+ canStartDrag = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const pointX2 = centerX + Math.cos(endAngle) * radius;
|
|
|
+ const pointY2 = centerY + Math.sin(endAngle) * radius;
|
|
|
+ const distance2 = Math.sqrt(Math.pow(canvasX - pointX2, 2) + Math.pow(canvasY - pointY2, 2));
|
|
|
+
|
|
|
+ if (distance2 <= 12) {
|
|
|
+ canEndDrag = true;
|
|
|
+ } else {
|
|
|
+ canEndDrag = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (canStartDrag || canEndDrag) {
|
|
|
+ canRingDrag = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const distance3 = twoPointDistance({ x: canvasX, y: canvasY }, { x: centerX, y: centerY });
|
|
|
+ let angle = Math.atan2(canvasY - canvasWidth / 2.0, canvasX - canvasWidth / 2.0)
|
|
|
+
|
|
|
+ if (distance3 > 70 && distance3 < 105 && isCenterAngle(startAngle, angle, endAngle)) {
|
|
|
+ canRingDrag = true;
|
|
|
+ }
|
|
|
+ lastAngle = angle;
|
|
|
+ startAngle = normalizeAngle(startAngle);
|
|
|
+ endAngle = normalizeAngle(endAngle);
|
|
|
+
|
|
|
+ drawCanvas(currentContext);
|
|
|
+ };
|
|
|
+
|
|
|
+ const normalizeAngle = (angle) => {
|
|
|
+ if (angle < 0) {
|
|
|
+ angle = angle + 2 * Math.PI;
|
|
|
+ normalizeAngle(angle);
|
|
|
+ }
|
|
|
+ if (angle > 2 * Math.PI) {
|
|
|
+ angle = angle - 2 * Math.PI;
|
|
|
+ normalizeAngle(angle);
|
|
|
+ }
|
|
|
+ return angle;
|
|
|
+ }
|
|
|
+
|
|
|
+ const isCenterAngle = (start, center, end) => {
|
|
|
+ start = normalizeAngle(start);
|
|
|
+ center = normalizeAngle(center);
|
|
|
+ end = normalizeAngle(end);
|
|
|
+ if (start > end) {
|
|
|
+ end += 2 * Math.PI;
|
|
|
+ }
|
|
|
+ if (end > 2 * Math.PI && center < start) {
|
|
|
+ center += 2 * Math.PI;
|
|
|
+ }
|
|
|
+ return start < center && center < end;
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleTouchEnd = (e) => {
|
|
|
+ canStartDrag = false;
|
|
|
+ canEndDrag = false;
|
|
|
+ canRingDrag = false;
|
|
|
+ handleTouchMove(e)
|
|
|
+ }
|
|
|
+
|
|
|
+ return <View style={{ width: 200, height: 200, }}>
|
|
|
+ <Canvas canvasId={canvasId} id={canvasId} className={canvasId} type="2d"
|
|
|
+ style={{ width: 200, height: 200, zIndex: 0 }}
|
|
|
+ onTouchMove={handleTouchMove}
|
|
|
+ onTouchEnd={handleTouchEnd}
|
|
|
+ onTouchStart={handleClick}
|
|
|
+ ref={canvasRef} />
|
|
|
+ </View>
|
|
|
+}
|