605 lines
23 KiB
HTML
605 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>按摩计划控制</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 20px;
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
background-color: #f5f5f5;
|
|
}
|
|
.control-panel {
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
.mode-selector {
|
|
margin-bottom: 20px;
|
|
}
|
|
.mode-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
.mode-button {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
background-color: #e0e0e0;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.mode-button.active {
|
|
background-color: rgba(147, 112, 219, 0.8);
|
|
color: white;
|
|
}
|
|
.selected-points {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background-color: #f8f8f8;
|
|
border-radius: 5px;
|
|
}
|
|
.point-info {
|
|
margin: 5px 0;
|
|
color: #666;
|
|
}
|
|
.clear-button {
|
|
margin-top: 15px;
|
|
padding: 8px 15px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
background-color: #ff4444;
|
|
color: white;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.clear-button:hover {
|
|
background-color: #ff6666;
|
|
}
|
|
.ellipse-controls {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background-color: #f8f8f8;
|
|
border-radius: 5px;
|
|
display: none;
|
|
}
|
|
.ellipse-controls.active {
|
|
display: block;
|
|
}
|
|
.control-group {
|
|
margin: 10px 0;
|
|
}
|
|
.control-group label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
color: #666;
|
|
}
|
|
.control-group input[type="range"] {
|
|
width: 100%;
|
|
margin: 5px 0;
|
|
}
|
|
.control-group input[type="number"] {
|
|
width: 60px;
|
|
padding: 5px;
|
|
margin-left: 10px;
|
|
}
|
|
.direction-selector {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 5px;
|
|
}
|
|
.direction-button {
|
|
padding: 5px 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 3px;
|
|
background: white;
|
|
cursor: pointer;
|
|
}
|
|
.direction-button.active {
|
|
background-color: rgba(147, 112, 219, 0.8);
|
|
color: white;
|
|
border-color: rgba(147, 112, 219, 0.8);
|
|
}
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
.body-part-selector {
|
|
margin-bottom: 20px;
|
|
padding: 15px;
|
|
background-color: #f0f8ff;
|
|
border-radius: 5px;
|
|
}
|
|
.body-part-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
.body-part-button {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
background-color: #e0e0e0;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.body-part-button.active {
|
|
background-color: #4682b4;
|
|
color: white;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="control-panel">
|
|
<div class="body-part-selector">
|
|
<h3>身体部位选择</h3>
|
|
<div class="body-part-buttons">
|
|
<button class="body-part-button active" data-body-part="back">背部</button>
|
|
<button class="body-part-button" data-body-part="belly">腹部</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mode-selector">
|
|
<h3>绘制模式选择</h3>
|
|
<div class="mode-buttons">
|
|
<button class="mode-button active" data-mode="none">无</button>
|
|
<button class="mode-button" data-mode="line">直线</button>
|
|
<button class="mode-button" data-mode="ellipse">椭圆</button>
|
|
<button class="mode-button" data-mode="lemniscate">8字形</button>
|
|
<button class="mode-button" data-mode="cycloid">摆线</button>
|
|
<button class="mode-button" data-mode="in_spiral">内螺旋</button>
|
|
<button class="mode-button" data-mode="out_spiral">外螺旋</button>
|
|
</div>
|
|
</div>
|
|
<div class="ellipse-controls" id="ellipseControls">
|
|
<h4>椭圆参数设置</h4>
|
|
<div class="control-group">
|
|
<label>宽度像素 (10-100): <span id="widthValue">30</span>px</label>
|
|
<input type="range" id="widthSlider" min="10" max="100" step="5" value="30">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>圈数 (1-5): <span id="cyclesValue">1</span></label>
|
|
<input type="range" id="cyclesSlider" min="1" max="5" step="1" value="1">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>方向:</label>
|
|
<div class="direction-selector">
|
|
<button class="direction-button active" data-direction="CW">顺时针</button>
|
|
<button class="direction-button" data-direction="CCW">逆时针</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ellipse-controls" id="lemniscateControls">
|
|
<h4>8字形参数设置</h4>
|
|
<div class="control-group">
|
|
<label>宽度像素 (10-100): <span id="lemniscateWidthValue">30</span>px</label>
|
|
<input type="range" id="lemniscateWidthSlider" min="10" max="100" step="5" value="30">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>圈数 (1-5): <span id="lemniscateCyclesValue">1</span></label>
|
|
<input type="range" id="lemniscateCyclesSlider" min="1" max="5" step="1" value="1">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>方向:</label>
|
|
<div class="direction-selector">
|
|
<button class="direction-button active" data-direction="CW">顺时针</button>
|
|
<button class="direction-button" data-direction="CCW">逆时针</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ellipse-controls" id="cycloidControls">
|
|
<h4>摆线参数设置</h4>
|
|
<div class="control-group">
|
|
<label>宽度像素 (10-100): <span id="cycloidWidthValue">30</span>px</label>
|
|
<input type="range" id="cycloidWidthSlider" min="10" max="100" step="5" value="30">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>圈数 (1-5): <span id="cycloidCyclesValue">5</span></label>
|
|
<input type="range" id="cycloidCyclesSlider" min="1" max="10" step="1" value="5">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>方向:</label>
|
|
<div class="direction-selector">
|
|
<button class="direction-button active" data-direction="CW">顺时针</button>
|
|
<button class="direction-button" data-direction="CCW">逆时针</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ellipse-controls" id="inSpiralControls">
|
|
<h4>内螺旋参数设置</h4>
|
|
<div class="control-group">
|
|
<label>宽度像素 (10-100): <span id="inSpiralWidthValue">30</span>px</label>
|
|
<input type="range" id="inSpiralWidthSlider" min="10" max="100" step="5" value="30">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>圈数 (1-5): <span id="inSpiralCyclesValue">1</span></label>
|
|
<input type="range" id="inSpiralCyclesSlider" min="1" max="5" step="1" value="1">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>方向:</label>
|
|
<div class="direction-selector">
|
|
<button class="direction-button active" data-direction="CW">顺时针</button>
|
|
<button class="direction-button" data-direction="CCW">逆时针</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ellipse-controls" id="outSpiralControls">
|
|
<h4>外螺旋参数设置</h4>
|
|
<div class="control-group">
|
|
<label>宽度像素 (10-100): <span id="outSpiralWidthValue">30</span>px</label>
|
|
<input type="range" id="outSpiralWidthSlider" min="10" max="100" step="5" value="30">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>圈数 (1-5): <span id="outSpiralCyclesValue">1</span></label>
|
|
<input type="range" id="outSpiralCyclesSlider" min="1" max="5" step="1" value="1">
|
|
</div>
|
|
<div class="control-group">
|
|
<label>方向:</label>
|
|
<div class="direction-selector">
|
|
<button class="direction-button active" data-direction="CW">顺时针</button>
|
|
<button class="direction-button" data-direction="CCW">逆时针</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="selected-points">
|
|
<h4>已选择的点</h4>
|
|
<div class="point-info" id="point1">第一个点:未选择</div>
|
|
<div class="point-info" id="point2">第二个点:未选择</div>
|
|
<button class="clear-button" id="clearPoints">清除选择</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 创建广播通道
|
|
const channel = new BroadcastChannel('massage_plan_channel');
|
|
let currentMode = 'none';
|
|
let currentBodyPart = '{{ body_part }}'; // 使用服务器传递的参数
|
|
let selectedPoints = [];
|
|
let ellipseParams = {
|
|
width: 30,
|
|
cycles: 1,
|
|
direction: 'CW'
|
|
};
|
|
|
|
let lemniscateParams = {
|
|
width: 30,
|
|
cycles: 1,
|
|
direction: 'CW'
|
|
};
|
|
|
|
let cycloidParams = {
|
|
width: 30,
|
|
cycles: 5,
|
|
direction: 'CW'
|
|
};
|
|
|
|
let inSpiralParams = {
|
|
width: 30,
|
|
cycles: 1,
|
|
direction: 'CW'
|
|
};
|
|
|
|
let outSpiralParams = {
|
|
width: 30,
|
|
cycles: 1,
|
|
direction: 'CW'
|
|
};
|
|
|
|
// 初始化身体部位按钮状态
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// 设置初始身体部位按钮状态
|
|
document.querySelectorAll('.body-part-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.bodyPart === currentBodyPart);
|
|
});
|
|
});
|
|
|
|
// 初始化身体部位选择按钮事件
|
|
document.querySelectorAll('.body-part-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const bodyPart = button.dataset.bodyPart;
|
|
switchBodyPart(bodyPart);
|
|
});
|
|
});
|
|
|
|
// 切换身体部位
|
|
function switchBodyPart(bodyPart) {
|
|
// 腰部(waist)、肩膀(shoulder)和背部(back)都使用相同的图片和点位
|
|
if (bodyPart === 'waist' || bodyPart === 'shoulder') {
|
|
bodyPart = 'back';
|
|
}
|
|
|
|
if (bodyPart === currentBodyPart) return;
|
|
|
|
currentBodyPart = bodyPart;
|
|
|
|
// 更新UI
|
|
document.querySelectorAll('.body-part-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.bodyPart === bodyPart);
|
|
});
|
|
|
|
// 清除已选择的点
|
|
clearPoints();
|
|
|
|
// 发送身体部位变更消息
|
|
channel.postMessage({
|
|
type: 'change_body_part',
|
|
body_part: bodyPart
|
|
});
|
|
}
|
|
|
|
// 初始化按钮事件
|
|
document.querySelectorAll('.mode-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const mode = button.dataset.mode;
|
|
setMode(mode);
|
|
|
|
// 显示/隐藏椭圆控制面板
|
|
document.getElementById('ellipseControls').classList.toggle('active', mode === 'ellipse');
|
|
document.getElementById('lemniscateControls').classList.toggle('active', mode === 'lemniscate');
|
|
document.getElementById('cycloidControls').classList.toggle('active', mode === 'cycloid');
|
|
document.getElementById('inSpiralControls').classList.toggle('active', mode === 'in_spiral');
|
|
document.getElementById('outSpiralControls').classList.toggle('active', mode === 'out_spiral');
|
|
|
|
// 发送模式变更消息
|
|
channel.postMessage({
|
|
type: 'mode_change',
|
|
mode: mode
|
|
});
|
|
});
|
|
});
|
|
|
|
// 初始化椭圆参数控制
|
|
document.getElementById('widthSlider').addEventListener('input', (e) => {
|
|
ellipseParams.width = parseInt(e.target.value);
|
|
document.getElementById('widthValue').textContent = ellipseParams.width;
|
|
updateEllipse();
|
|
});
|
|
|
|
document.getElementById('cyclesSlider').addEventListener('input', (e) => {
|
|
ellipseParams.cycles = parseInt(e.target.value);
|
|
document.getElementById('cyclesValue').textContent = ellipseParams.cycles;
|
|
updateEllipse();
|
|
});
|
|
|
|
document.getElementById('ellipseControls').querySelectorAll('.direction-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const direction = button.dataset.direction;
|
|
ellipseParams.direction = direction;
|
|
document.getElementById('ellipseControls').querySelectorAll('.direction-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.direction === direction);
|
|
});
|
|
updateEllipse();
|
|
});
|
|
});
|
|
|
|
// 初始化8字形参数控制
|
|
document.getElementById('lemniscateWidthSlider').addEventListener('input', (e) => {
|
|
lemniscateParams.width = parseInt(e.target.value);
|
|
document.getElementById('lemniscateWidthValue').textContent = lemniscateParams.width;
|
|
updateLemniscate();
|
|
});
|
|
|
|
document.getElementById('lemniscateCyclesSlider').addEventListener('input', (e) => {
|
|
lemniscateParams.cycles = parseInt(e.target.value);
|
|
document.getElementById('lemniscateCyclesValue').textContent = lemniscateParams.cycles;
|
|
updateLemniscate();
|
|
});
|
|
|
|
document.getElementById('lemniscateControls').querySelectorAll('.direction-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const direction = button.dataset.direction;
|
|
lemniscateParams.direction = direction;
|
|
document.getElementById('lemniscateControls').querySelectorAll('.direction-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.direction === direction);
|
|
});
|
|
updateLemniscate();
|
|
});
|
|
});
|
|
|
|
// 初始化摆线参数控制
|
|
document.getElementById('cycloidWidthSlider').addEventListener('input', (e) => {
|
|
cycloidParams.width = parseInt(e.target.value);
|
|
document.getElementById('cycloidWidthValue').textContent = cycloidParams.width;
|
|
updateCycloid();
|
|
});
|
|
|
|
document.getElementById('cycloidCyclesSlider').addEventListener('input', (e) => {
|
|
cycloidParams.cycles = parseInt(e.target.value);
|
|
document.getElementById('cycloidCyclesValue').textContent = cycloidParams.cycles;
|
|
updateCycloid();
|
|
});
|
|
|
|
document.getElementById('cycloidControls').querySelectorAll('.direction-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const direction = button.dataset.direction;
|
|
cycloidParams.direction = direction;
|
|
document.getElementById('cycloidControls').querySelectorAll('.direction-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.direction === direction);
|
|
});
|
|
updateCycloid();
|
|
});
|
|
});
|
|
|
|
// 初始化内螺旋参数控制
|
|
document.getElementById('inSpiralWidthSlider').addEventListener('input', (e) => {
|
|
inSpiralParams.width = parseInt(e.target.value);
|
|
document.getElementById('inSpiralWidthValue').textContent = inSpiralParams.width;
|
|
updateInSpiral();
|
|
});
|
|
|
|
document.getElementById('inSpiralCyclesSlider').addEventListener('input', (e) => {
|
|
inSpiralParams.cycles = parseInt(e.target.value);
|
|
document.getElementById('inSpiralCyclesValue').textContent = inSpiralParams.cycles;
|
|
updateInSpiral();
|
|
});
|
|
|
|
document.getElementById('inSpiralControls').querySelectorAll('.direction-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const direction = button.dataset.direction;
|
|
inSpiralParams.direction = direction;
|
|
document.getElementById('inSpiralControls').querySelectorAll('.direction-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.direction === direction);
|
|
});
|
|
updateInSpiral();
|
|
});
|
|
});
|
|
|
|
// 初始化外螺旋参数控制
|
|
document.getElementById('outSpiralWidthSlider').addEventListener('input', (e) => {
|
|
outSpiralParams.width = parseInt(e.target.value);
|
|
document.getElementById('outSpiralWidthValue').textContent = outSpiralParams.width;
|
|
updateOutSpiral();
|
|
});
|
|
|
|
document.getElementById('outSpiralCyclesSlider').addEventListener('input', (e) => {
|
|
outSpiralParams.cycles = parseInt(e.target.value);
|
|
document.getElementById('outSpiralCyclesValue').textContent = outSpiralParams.cycles;
|
|
updateOutSpiral();
|
|
});
|
|
|
|
document.getElementById('outSpiralControls').querySelectorAll('.direction-button').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const direction = button.dataset.direction;
|
|
outSpiralParams.direction = direction;
|
|
document.getElementById('outSpiralControls').querySelectorAll('.direction-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.direction === direction);
|
|
});
|
|
updateOutSpiral();
|
|
});
|
|
});
|
|
|
|
// 设置模式
|
|
function setMode(mode) {
|
|
currentMode = mode;
|
|
document.querySelectorAll('.mode-button').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.mode === mode);
|
|
});
|
|
|
|
// 清除已选择的点
|
|
clearPoints();
|
|
}
|
|
|
|
// 清除选择的点
|
|
function clearPoints() {
|
|
selectedPoints = [];
|
|
document.getElementById('point1').textContent = '第一个点:未选择';
|
|
document.getElementById('point2').textContent = '第二个点:未选择';
|
|
|
|
// 发送清除点的消息
|
|
channel.postMessage({
|
|
type: 'clear_points'
|
|
});
|
|
}
|
|
|
|
// 更新椭圆
|
|
function updateEllipse() {
|
|
if (selectedPoints.length === 2) {
|
|
channel.postMessage({
|
|
type: 'draw_shape',
|
|
mode: 'ellipse',
|
|
points: selectedPoints,
|
|
params: ellipseParams
|
|
});
|
|
}
|
|
}
|
|
|
|
// 更新8字形
|
|
function updateLemniscate() {
|
|
if (selectedPoints.length === 2) {
|
|
channel.postMessage({
|
|
type: 'draw_shape',
|
|
mode: 'lemniscate',
|
|
points: selectedPoints,
|
|
params: lemniscateParams
|
|
});
|
|
}
|
|
}
|
|
|
|
// 更新摆线
|
|
function updateCycloid() {
|
|
if (selectedPoints.length === 2) {
|
|
channel.postMessage({
|
|
type: 'draw_shape',
|
|
mode: 'cycloid',
|
|
points: selectedPoints,
|
|
params: cycloidParams
|
|
});
|
|
}
|
|
}
|
|
|
|
// 更新内螺旋
|
|
function updateInSpiral() {
|
|
if (selectedPoints.length === 2) {
|
|
channel.postMessage({
|
|
type: 'draw_shape',
|
|
mode: 'in_spiral',
|
|
points: selectedPoints,
|
|
params: inSpiralParams
|
|
});
|
|
}
|
|
}
|
|
|
|
// 更新外螺旋
|
|
function updateOutSpiral() {
|
|
if (selectedPoints.length === 2) {
|
|
channel.postMessage({
|
|
type: 'draw_shape',
|
|
mode: 'out_spiral',
|
|
points: selectedPoints,
|
|
params: outSpiralParams
|
|
});
|
|
}
|
|
}
|
|
|
|
// 监听清除按钮
|
|
document.getElementById('clearPoints').addEventListener('click', clearPoints);
|
|
|
|
// 监听来自可视化页面的消息
|
|
channel.onmessage = (event) => {
|
|
const { type, data } = event.data;
|
|
|
|
if (type === 'point_selected') {
|
|
if (currentMode === 'none') return;
|
|
|
|
// 如果已经有两个点,清空选择
|
|
if (selectedPoints.length >= 2) {
|
|
selectedPoints = [];
|
|
document.getElementById('point1').textContent = '第一个点:未选择';
|
|
document.getElementById('point2').textContent = '第二个点:未选择';
|
|
}
|
|
|
|
// 添加新的点
|
|
selectedPoints.push(data);
|
|
const pointIndex = selectedPoints.length;
|
|
document.getElementById(`point${pointIndex}`).textContent =
|
|
`第${pointIndex}个点:${data.name} (${Math.round(data.x)}, ${Math.round(data.y)})`;
|
|
|
|
// 如果已选择两个点,发送绘制消息
|
|
if (selectedPoints.length === 2) {
|
|
channel.postMessage({
|
|
type: 'draw_shape',
|
|
mode: currentMode,
|
|
points: selectedPoints,
|
|
params: currentMode === 'ellipse' ? ellipseParams :
|
|
currentMode === 'lemniscate' ? lemniscateParams :
|
|
currentMode === 'cycloid' ? cycloidParams :
|
|
currentMode === 'in_spiral' ? inSpiralParams :
|
|
currentMode === 'out_spiral' ? outSpiralParams : undefined
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
</body>
|
|
</html> |