123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>AI身体测量</title>
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
- <link rel="stylesheet" href="/assets/css/layui.css">
- <link rel="stylesheet" href="/assets/css/admin.css">
- <style>
- .measurement-container {
- display: flex;
- gap: 30px;
- margin-top: 20px;
- }
- .figure-container {
- flex: 1;
- position: relative;
- background: #f8f9fa;
- border-radius: 10px;
- padding: 20px;
- text-align: center;
- }
- .body-figure {
- position: relative;
- width: 300px;
- height: 600px;
- margin: 0 auto;
- background: url('/assets/images/body_figure_male.png') no-repeat center center;
- background-size: contain;
- }
- .body-figure.female {
- background-image: url('/assets/images/body_figure_female.png');
- }
- .measurement-point {
- position: absolute;
- background: #1E9FFF;
- color: white;
- padding: 4px 8px;
- border-radius: 15px;
- font-size: 12px;
- white-space: nowrap;
- cursor: pointer;
- z-index: 10;
- }
- .measurement-point::before {
- content: '';
- position: absolute;
- width: 2px;
- height: 20px;
- background: #1E9FFF;
- top: 50%;
- transform: translateY(-50%);
- }
- .measurement-point.left::before {
- right: 100%;
- }
- .measurement-point.right::before {
- left: 100%;
- }
- .measurement-line {
- position: absolute;
- height: 1px;
- background: #1E9FFF;
- z-index: 5;
- }
- .data-container {
- flex: 1;
- }
- .basic-info {
- background: #fff;
- border-radius: 8px;
- padding: 20px;
- margin-bottom: 20px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .measurement-table {
- background: #fff;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .measurement-table table {
- width: 100%;
- border-collapse: collapse;
- }
- .measurement-table th,
- .measurement-table td {
- padding: 12px;
- text-align: left;
- border-bottom: 1px solid #eee;
- }
- .measurement-table th {
- background: #f8f9fa;
- font-weight: bold;
- }
- .measurement-value {
- font-weight: bold;
- color: #1E9FFF;
- }
- .measurement-value.empty {
- color: #ccc;
- }
- .confidence-badge {
- display: inline-block;
- background: #52c41a;
- color: white;
- padding: 2px 8px;
- border-radius: 10px;
- font-size: 12px;
- margin-left: 10px;
- }
- .confidence-badge.medium {
- background: #fa8c16;
- }
- .confidence-badge.low {
- background: #f5222d;
- }
- .ai-controls {
- text-align: center;
- margin: 20px 0;
- }
- .loading-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(255,255,255,0.8);
- z-index: 1000;
- display: none;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- }
- .loading-content {
- text-align: center;
- }
- .loading-spinner {
- width: 60px;
- height: 60px;
- border: 4px solid #f3f3f3;
- border-top: 4px solid #1E9FFF;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- margin-bottom: 20px;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- .progress-bar {
- width: 300px;
- height: 6px;
- background: #f0f0f0;
- border-radius: 3px;
- overflow: hidden;
- margin: 10px auto;
- }
- .progress-fill {
- height: 100%;
- background: #1E9FFF;
- border-radius: 3px;
- transition: width 0.3s ease;
- }
- .warning-item {
- background: #fff7e6;
- border: 1px solid #ffd591;
- padding: 8px 12px;
- border-radius: 4px;
- margin-bottom: 5px;
- font-size: 12px;
- color: #fa8c16;
- }
- </style>
- </head>
- <body>
- <div class="layui-fluid" style="padding: 15px;">
- <!-- 头部信息 -->
- <div class="layui-card">
- <div class="layui-card-header">
- <h3>AI身体测量 - {$profile.profile_name}</h3>
- <span class="layui-badge layui-bg-blue">{$profile.gender_text}</span>
- <span class="layui-badge layui-bg-green">{$profile.is_own_text}</span>
- </div>
- <div class="layui-card-body">
- <!-- AI控制按钮 -->
- <div class="ai-controls">
- <button class="layui-btn layui-btn-lg" id="startAnalysis">
- <i class="layui-icon layui-icon-play"></i> 开始AI分析
- </button>
- <button class="layui-btn layui-btn-normal layui-btn-lg" id="retryAnalysis" style="display: none;">
- <i class="layui-icon layui-icon-refresh"></i> 重新分析
- </button>
- <button class="layui-btn layui-btn-warm layui-btn-lg" id="saveResult" style="display: none;">
- <i class="layui-icon layui-icon-ok"></i> 保存结果
- </button>
- </div>
- <!-- 置信度和警告信息 -->
- <div id="analysisInfo" style="display: none;">
- <div style="text-align: center; margin: 10px 0;">
- <span>分析置信度:</span>
- <span id="confidenceValue">85%</span>
- <span id="confidenceBadge" class="confidence-badge">高</span>
- </div>
- <div id="warningsContainer"></div>
- </div>
- <!-- 测量结果展示 -->
- <div class="measurement-container">
- <!-- 人体示意图 -->
- <div class="figure-container">
- <h4>身体示意图</h4>
- <div id="bodyFigure" class="body-figure {if $profile.gender == 2}female{/if}">
- <!-- 测量点将通过JavaScript动态添加 -->
- </div>
- </div>
- <!-- 数据表格 -->
- <div class="data-container">
- <!-- 基础信息 -->
- <div class="basic-info">
- <h4>身体数据</h4>
- <table>
- <tr>
- <td><strong>身高</strong></td>
- <td>{$profile.height|default='--'}cm</td>
- <td><strong>体重</strong></td>
- <td>{$profile.weight|default='--'}kg</td>
- </tr>
- </table>
- </div>
- <!-- 测量数据表格 -->
- <div class="measurement-table">
- <h4>测量数据</h4>
- <table id="measurementTable">
- <thead>
- <tr>
- <th>部位</th>
- <th>数值(cm)</th>
- <th>部位</th>
- <th>数值(cm)</th>
- </tr>
- </thead>
- <tbody id="measurementTableBody">
- <!-- 数据将通过JavaScript动态填充 -->
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 加载遮罩 -->
- <div class="loading-overlay" id="loadingOverlay">
- <div class="loading-content">
- <div class="loading-spinner"></div>
- <div id="loadingText">AI正在分析您的身体照片...</div>
- <div class="progress-bar">
- <div class="progress-fill" id="progressFill" style="width: 0%"></div>
- </div>
- <div id="progressText">0%</div>
- </div>
- </div>
- <script src="/assets/js/layui.js"></script>
- <script>
- layui.use(['layer'], function(){
- var layer = layui.layer;
- var profileId = '{$profile.id}';
- var currentTaskId = null;
- var analysisInterval = null;
- // 初始化页面
- initializePage();
- // 开始AI分析
- $('#startAnalysis').click(function() {
- startAiAnalysis();
- });
- // 重新分析
- $('#retryAnalysis').click(function() {
- retryAnalysis();
- });
- // 保存结果
- $('#saveResult').click(function() {
- saveAnalysisResult();
- });
- // 初始化页面数据
- function initializePage() {
- // 加载默认的空数据表格
- renderMeasurementTable({});
-
- // 检查是否有进行中的任务
- checkExistingTask();
- }
- // 检查现有任务
- function checkExistingTask() {
- $.get('/api/ai_measurement/getResult', {
- profile_id: profileId
- }, function(response) {
- if (response.code === 1 && response.data) {
- switch (response.data.status) {
- case 'processing':
- case 'pending':
- showLoadingOverlay();
- startPolling();
- break;
- case 'completed':
- renderAnalysisResult(response.data.data);
- break;
- case 'failed':
- if (response.data.can_retry) {
- $('#retryAnalysis').show();
- }
- break;
- }
- }
- });
- }
- // 开始AI分析
- function startAiAnalysis() {
- // 获取档案的身体照片
- $.get('/api/body_profile/detail', {
- profile_id: profileId
- }, function(response) {
- if (response.code === 1) {
- var photos = response.data.body_photos_array;
-
- if (!photos.front || !photos.side || !photos.back) {
- layer.msg('请先上传完整的身体照片(正面、侧面、背面)');
- return;
- }
- // 发起AI分析请求
- $.post('/api/ai_measurement/startAnalysis', {
- profile_id: profileId,
- photos: photos
- }, function(res) {
- if (res.code === 1) {
- currentTaskId = res.data.task_id;
- showLoadingOverlay();
- startPolling();
- } else {
- layer.msg(res.msg);
- }
- });
- } else {
- layer.msg(response.msg);
- }
- });
- }
- // 显示加载遮罩
- function showLoadingOverlay() {
- $('#loadingOverlay').show();
- $('#startAnalysis').hide();
- }
- // 隐藏加载遮罩
- function hideLoadingOverlay() {
- $('#loadingOverlay').hide();
- $('#startAnalysis').show();
- }
- // 开始轮询检查结果
- function startPolling() {
- if (analysisInterval) {
- clearInterval(analysisInterval);
- }
- analysisInterval = setInterval(function() {
- checkAnalysisResult();
- }, 2000); // 每2秒检查一次
- }
- // 检查分析结果
- function checkAnalysisResult() {
- var params = currentTaskId ? {task_id: currentTaskId} : {profile_id: profileId};
-
- $.get('/api/ai_measurement/getResult', params, function(response) {
- if (response.code === 1) {
- switch (response.data.status) {
- case 'pending':
- updateProgress(10, '任务排队中...');
- break;
-
- case 'processing':
- var progress = response.data.progress || 50;
- updateProgress(progress, 'AI正在分析您的身体照片...');
- break;
-
- case 'completed':
- clearInterval(analysisInterval);
- hideLoadingOverlay();
- renderAnalysisResult(response.data.data);
- break;
-
- case 'failed':
- clearInterval(analysisInterval);
- hideLoadingOverlay();
- layer.msg('分析失败: ' + response.data.message);
- if (response.data.can_retry) {
- $('#retryAnalysis').show();
- }
- break;
- }
- }
- });
- }
- // 更新进度
- function updateProgress(progress, text) {
- $('#progressFill').css('width', progress + '%');
- $('#progressText').text(progress + '%');
- $('#loadingText').text(text);
- }
- // 渲染分析结果
- function renderAnalysisResult(data) {
- // 显示置信度
- if (data.confidence) {
- var confidence = Math.round(data.confidence * 100);
- $('#confidenceValue').text(confidence + '%');
-
- var badge = $('#confidenceBadge');
- if (confidence >= 80) {
- badge.removeClass('medium low').addClass('high').text('高');
- } else if (confidence >= 60) {
- badge.removeClass('high low').addClass('medium').text('中');
- } else {
- badge.removeClass('high medium').addClass('low').text('低');
- }
-
- $('#analysisInfo').show();
- }
- // 显示警告信息
- if (data.warnings && data.warnings.length > 0) {
- var warningsHtml = '';
- data.warnings.forEach(function(warning) {
- warningsHtml += '<div class="warning-item">' + warning + '</div>';
- });
- $('#warningsContainer').html(warningsHtml);
- }
- // 渲染测量点
- renderMeasurementPoints(data.measurements);
-
- // 渲染数据表格
- renderMeasurementTable(data.measurements);
- // 显示保存按钮
- $('#saveResult').show();
- }
- // 渲染测量点
- function renderMeasurementPoints(measurements) {
- var figure = $('#bodyFigure');
- figure.find('.measurement-point, .measurement-line').remove();
- if (!measurements) return;
- Object.keys(measurements).forEach(function(field) {
- var measurement = measurements[field];
- if (!measurement.value || !measurement.position) return;
- var point = $('<div class="measurement-point ' + measurement.side + '">')
- .text(measurement.label + ': ' + measurement.value + measurement.unit)
- .css({
- left: (measurement.position.x * 100) + '%',
- top: (measurement.position.y * 100) + '%'
- });
- figure.append(point);
- });
- }
- // 渲染测量数据表格
- function renderMeasurementTable(measurements) {
- var tbody = $('#measurementTableBody');
- tbody.empty();
- // 默认字段配置
- var defaultFields = [
- ['胸围', 'chest'], ['腰围', 'waist'],
- ['臀围', 'hip'], ['大腿围', 'thigh'],
- ['小腿围', 'calf'], ['上臂围', 'upper_arm'],
- ['肩宽', 'shoulder_width'], ['颈围', 'neck']
- ];
- // 按两列排列
- for (var i = 0; i < defaultFields.length; i += 2) {
- var row = '<tr>';
-
- for (var j = 0; j < 2; j++) {
- if (i + j < defaultFields.length) {
- var field = defaultFields[i + j];
- var label = field[0];
- var key = field[1];
- var value = measurements[key] ? measurements[key].value : null;
- var displayValue = value ? value + 'cm' : '--';
- var cssClass = value ? 'measurement-value' : 'measurement-value empty';
-
- row += '<td>' + label + '</td>';
- row += '<td><span class="' + cssClass + '">' + displayValue + '</span></td>';
- } else {
- row += '<td></td><td></td>';
- }
- }
-
- row += '</tr>';
- tbody.append(row);
- }
- }
- // 重新分析
- function retryAnalysis() {
- if (!currentTaskId) {
- layer.msg('没有可重试的任务');
- return;
- }
- $.post('/api/ai_measurement/retryAnalysis', {
- task_id: currentTaskId
- }, function(response) {
- if (response.code === 1) {
- $('#retryAnalysis').hide();
- showLoadingOverlay();
- startPolling();
- } else {
- layer.msg(response.msg);
- }
- });
- }
- // 保存分析结果
- function saveAnalysisResult() {
- if (!currentTaskId) {
- layer.msg('没有可保存的结果');
- return;
- }
- layer.confirm('确定要保存这次AI测量结果吗?', {
- icon: 3,
- title: '确认保存'
- }, function(index) {
- $.post('/api/ai_measurement/saveResult', {
- task_id: currentTaskId
- }, function(response) {
- if (response.code === 1) {
- layer.msg('测量结果已保存', {icon: 1});
- $('#saveResult').hide();
-
- // 可以跳转到测量历史页面
- setTimeout(function() {
- parent.layer.close(parent.layer.getFrameIndex(window.name));
- }, 1500);
- } else {
- layer.msg(response.msg);
- }
- });
- layer.close(index);
- });
- }
- });
- </script>
- </body>
- </html>
|