super-yimizi 3 weeks ago
parent
commit
94b2e23247

+ 232 - 251
application/admin/controller/BodyProfile.php

@@ -10,16 +10,47 @@ use app\common\model\BodyTypeSelection;
 use app\common\model\BodyAiReport;
 use think\Db;
 use think\exception\ValidateException;
+use think\exception\PDOException;
+use think\exception\DbException;
+use Exception;
 
 /**
  * 身体档案管理
+ *
+ * @icon fa fa-user
+ * @remark 管理用户身体档案信息,包括基础数据、测量记录、体型选择等
  */
 class BodyProfile extends Backend
 {
+    /**
+     * BodyProfile模型对象
+     * @var \app\common\model\BodyProfile
+     */
     protected $model = null;
+    
+    /**
+     * 无需登录的方法,同时也就无需鉴权了
+     * @var array
+     */
     protected $noNeedLogin = [];
+    
+    /**
+     * 无需鉴权的方法,但需要登录
+     * @var array
+     */
     protected $noNeedRight = [];
+    
+    /**
+     * 快速搜索时执行查找的字段
+     * @var string
+     */
     protected $searchFields = 'profile_name,relation';
+    
+    /**
+     * 关联查询
+     * @var array
+     */
+    protected $relationSearch = true;
 
     public function _initialize()
     {
@@ -28,99 +59,97 @@ class BodyProfile extends Backend
     }
 
     /**
-     * 查看列表
+     * 默认生成的控制器所继承的父类中有index方法,在这里重写下
      */
     public function index()
     {
+        //当前是否为关联查询
+        $this->relationSearch = true;
+        //设置过滤方法
+        $this->request->filter(['strip_tags', 'trim']);
         if ($this->request->isAjax()) {
-            // 分页参数
-            $page = $this->request->get('page/d', 1);
-            $limit = $this->request->get('limit/d', 10);
-            $search = $this->request->get('search', '');
-            $gender = $this->request->get('gender', '');
-            $is_own = $this->request->get('is_own', '');
-
-            $where = [];
-            
-            // 搜索条件
-            if ($search) {
-                $where[] = ['profile_name|relation', 'like', '%' . $search . '%'];
-            }
-            
-            if ($gender !== '') {
-                $where[] = ['gender', '=', $gender];
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
             }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
             
-            if ($is_own !== '') {
-                $where[] = ['is_own', '=', $is_own];
-            }
-
-            // 查询数据
             $list = $this->model
-                ->with(['user', 'latestMeasurement'])
+                ->with(['user'])
                 ->where($where)
-                ->order('id DESC')
-                ->paginate($limit)
-                ->each(function($item) {
-                    // 添加额外信息
-                    $item['bmi'] = $item->calculateBMI();
-                    $item['bmi_level'] = $item->getBMILevel();
-                    return $item;
-                });
+                ->order($sort, $order)
+                ->paginate($limit);
+            
+            foreach ($list as $row) {
+                $row->visible(['id','profile_name','user_id','gender','is_own','relation','age','height','weight','createtime','updatetime']);
+                $row->visible(['user']);
+                $row->getRelation('user')->visible(['username','nickname']);
+                
+                // 计算BMI
+                $row['bmi'] = $row->calculateBMI();
+                $row['bmi_level'] = $row->getBMILevel();
+            }
+            
+            $result = array("total" => $list->total(), "rows" => $list->items());
 
-            return json(['code' => 0, 'msg' => '', 'count' => $list->total(), 'data' => $list->items()]);
+            return json($result);
         }
-
         return $this->view->fetch();
     }
 
     /**
-     * 添加档案
+     * 添加
      */
     public function add()
     {
         if ($this->request->isPost()) {
-            $params = $this->request->post('row/a');
-            
-            if (empty($params)) {
-                $this->error(__('Parameter %s can not be empty', ''));
-            }
-
-            // 验证必填字段
-            if (empty($params['profile_name']) || empty($params['user_id'])) {
-                $this->error('档案名称和用户ID不能为空');
-            }
-
-            // 处理身体照片
-            if (isset($params['body_photos']) && is_array($params['body_photos'])) {
-                $params['body_photos'] = json_encode($params['body_photos']);
-            }
-
-            Db::startTrans();
-            try {
-                $result = $this->model->save($params);
-                if ($result === false) {
-                    throw new \Exception($this->model->getError());
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                
+                // 处理身体照片
+                if (isset($params['body_photos']) && is_array($params['body_photos'])) {
+                    $params['body_photos'] = json_encode($params['body_photos']);
+                }
+                
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
+                        $this->model->validateFailException(true)->validate($validate);
+                    }
+                    $result = $this->model->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were inserted'));
                 }
-
-                Db::commit();
-            } catch (\Throwable $e) {
-                Db::rollback();
-                $this->error($e->getMessage());
             }
-
-            $this->success();
+            $this->error(__('Parameter %s can not be empty', ''));
         }
-
-        // 获取用户列表
-        $userList = \app\common\model\User::field('id,username,nickname')->select();
-        $this->assign('userList', $userList);
+        
+        $this->view->assign('genderList', ['1' => __('Male'), '2' => __('Female')]);
+        $this->view->assign('isOwnList', ['1' => __('Own profile'), '0' => __('Others profile')]);
         
         return $this->view->fetch();
     }
 
     /**
-     * 编辑档案
+     * 编辑
      */
     public function edit($ids = null)
     {
@@ -128,99 +157,116 @@ class BodyProfile extends Backend
         if (!$row) {
             $this->error(__('No Results were found'));
         }
-
-        if ($this->request->isPost()) {
-            $params = $this->request->post('row/a');
-            
-            if (empty($params)) {
-                $this->error(__('Parameter %s can not be empty', ''));
-            }
-
-            // 处理身体照片
-            if (isset($params['body_photos']) && is_array($params['body_photos'])) {
-                $params['body_photos'] = json_encode($params['body_photos']);
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds)) {
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
+                $this->error(__('You have no permission'));
             }
-
-            Db::startTrans();
-            try {
-                $result = $row->save($params);
-                if ($result === false) {
-                    throw new \Exception($row->getError());
+        }
+        if ($this->request->isPost()) {
+            $params = $this->request->post("row/a");
+            if ($params) {
+                $params = $this->preExcludeFields($params);
+                
+                // 处理身体照片
+                if (isset($params['body_photos']) && is_array($params['body_photos'])) {
+                    $params['body_photos'] = json_encode($params['body_photos']);
+                }
+                
+                $result = false;
+                Db::startTrans();
+                try {
+                    //是否采用模型验证
+                    if ($this->modelValidate) {
+                        $name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
+                        $row->validateFailException(true)->validate($validate);
+                    }
+                    $result = $row->allowField(true)->save($params);
+                    Db::commit();
+                } catch (ValidateException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (PDOException $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                if ($result !== false) {
+                    $this->success();
+                } else {
+                    $this->error(__('No rows were updated'));
                 }
-
-                Db::commit();
-            } catch (\Throwable $e) {
-                Db::rollback();
-                $this->error($e->getMessage());
             }
-
-            $this->success();
+            $this->error(__('Parameter %s can not be empty', ''));
         }
-
-        // 获取用户列表
-        $userList = \app\common\model\User::field('id,username,nickname')->select();
-        $this->assign('userList', $userList);
-        $this->assign('row', $row);
+        
+        $this->view->assign('row', $row);
+        $this->view->assign('genderList', ['1' => __('Male'), '2' => __('Female')]);
+        $this->view->assign('isOwnList', ['1' => __('Own profile'), '0' => __('Others profile')]);
         
         return $this->view->fetch();
     }
 
     /**
-     * 删除档案
+     * 删除
      */
     public function del($ids = null)
     {
-        if (!$this->request->isPost()) {
+        if (false === $this->request->isPost()) {
             $this->error(__("Invalid parameters"));
         }
-
-        $ids = $ids ? $ids : $this->request->post("ids");
+        $ids = $ids ?: $this->request->post("ids");
         if (empty($ids)) {
-            $this->error(__('Parameter %s can not be empty', 'ids'));
+            $this->error(__("Parameter %s can not be empty", "ids"));
         }
-
         $pk = $this->model->getPk();
         $adminIds = $this->getDataLimitAdminIds();
         if (is_array($adminIds)) {
             $this->model->where($this->dataLimitField, 'in', $adminIds);
         }
-
         $list = $this->model->where($pk, 'in', $ids)->select();
-        $count = 0;
 
+        $count = 0;
         Db::startTrans();
         try {
             foreach ($list as $item) {
-                // 删除相关数据
-                BodyMeasurements::where('profile_id', $item->id)->delete();
-                BodyTypeSelection::where('profile_id', $item->id)->delete();
-                BodyAiReport::where('profile_id', $item->id)->delete();
+                // 删除相关的测量数据
+                Db::name('body_measurements')->where('profile_id', $item[$pk])->delete();
+                // 删除相关的体型选择数据
+                Db::name('body_type_selections')->where('profile_id', $item[$pk])->delete();
+                // 删除相关的AI报告
+                Db::name('ai_reports')->where('profile_id', $item[$pk])->delete();
                 
                 $count += $item->delete();
             }
             Db::commit();
-        } catch (\Throwable $e) {
+        } catch (PDOException | Exception $e) {
             Db::rollback();
             $this->error($e->getMessage());
         }
-
-        $this->success();
+        if ($count) {
+            $this->success();
+        }
+        $this->error(__("No rows were deleted"));
     }
 
     /**
-     * 查看档案详情
+     * 查看详情
      */
     public function detail($ids = null)
     {
-        $profile = $this->model->with(['user', 'latestMeasurement', 'bodyTypeSelections.typeConfig', 'latestAiReport'])->find($ids);
-        if (!$profile) {
-            $this->error('档案不存在');
+        $row = $this->model->get($ids);
+        if (!$row) {
+            $this->error(__("No Results were found"));
         }
-
-        // 获取完整档案数据
-        $profileData = $profile->getFullProfileData();
-        
-        $this->assign('profile', $profileData);
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
+            $this->error(__("You have no permission"));
+        }
+        $this->view->assign("row", $row);
         return $this->view->fetch();
     }
 
@@ -229,25 +275,15 @@ class BodyProfile extends Backend
      */
     public function measurements($profile_id = null)
     {
-        if (!$profile_id) {
-            $this->error('档案ID不能为空');
-        }
-
-        $profile = $this->model->find($profile_id);
+        $profile = $this->model->get($profile_id);
         if (!$profile) {
-            $this->error('档案不存在');
+            $this->error(__("No Results were found"));
         }
-
-        if ($this->request->isAjax()) {
-            // 获取测量记录
-            $list = BodyMeasurements::where('profile_id', $profile_id)
-                ->order('measurement_date DESC')
-                ->paginate(10);
-
-            return json(['code' => 0, 'msg' => '', 'count' => $list->total(), 'data' => $list->items()]);
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds) && !in_array($profile[$this->dataLimitField], $adminIds)) {
+            $this->error(__("You have no permission"));
         }
-
-        $this->assign('profile', $profile);
+        $this->view->assign("profile", $profile);
         return $this->view->fetch();
     }
 
@@ -256,46 +292,25 @@ class BodyProfile extends Backend
      */
     public function addMeasurement($profile_id = null)
     {
-        if (!$profile_id) {
-            $this->error('档案ID不能为空');
-        }
-
-        $profile = $this->model->find($profile_id);
+        $profile = $this->model->get($profile_id);
         if (!$profile) {
-            $this->error('档案不存在');
+            $this->error(__("No Results were found"));
         }
-
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds) && !in_array($profile[$this->dataLimitField], $adminIds)) {
+            $this->error(__("You have no permission"));
+        }
+        
         if ($this->request->isPost()) {
             $params = $this->request->post('row/a');
-            
             if (empty($params)) {
-                $this->error(__('Parameter %s can not be empty', ''));
+                $this->error(__("Parameter %s can not be empty", ""));
             }
-
-            $params['profile_id'] = $profile_id;
-            
-            // 处理测量日期
-            if (isset($params['measurement_date']) && $params['measurement_date']) {
-                $params['measurement_date'] = strtotime($params['measurement_date']);
-            } else {
-                $params['measurement_date'] = time();
-            }
-
-            $measurement = new BodyMeasurements();
-            $result = $measurement->save($params);
-            
-            if ($result === false) {
-                $this->error($measurement->getError());
-            }
-
+            // TODO: 实现添加测量数据逻辑
             $this->success();
         }
-
-        // 获取测量字段
-        $measurementFields = BodyMeasurements::getMeasurementFields($profile->gender);
         
-        $this->assign('profile', $profile);
-        $this->assign('measurementFields', $measurementFields);
+        $this->view->assign("profile", $profile);
         return $this->view->fetch();
     }
 
@@ -304,40 +319,15 @@ class BodyProfile extends Backend
      */
     public function bodyTypes($profile_id = null)
     {
-        if (!$profile_id) {
-            $this->error('档案ID不能为空');
-        }
-
-        $profile = $this->model->find($profile_id);
+        $profile = $this->model->get($profile_id);
         if (!$profile) {
-            $this->error('档案不存在');
+            $this->error(__("No Results were found"));
         }
-
-        if ($this->request->isPost()) {
-            $selections = $this->request->post('selections/a');
-            
-            if (empty($selections)) {
-                $this->error('请选择体型');
-            }
-
-            $result = BodyTypeSelection::saveUserSelections($profile_id, $selections);
-            
-            if (!$result) {
-                $this->error('保存失败');
-            }
-
-            $this->success();
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds) && !in_array($profile[$this->dataLimitField], $adminIds)) {
+            $this->error(__("You have no permission"));
         }
-
-        // 获取所有体型分类和选项
-        $bodyTypeCategories = BodyTypeConfig::getAllCategories($profile->gender);
-        
-        // 获取用户已选择的体型
-        $userSelections = BodyTypeSelection::getUserSelections($profile_id);
-
-        $this->assign('profile', $profile);
-        $this->assign('bodyTypeCategories', $bodyTypeCategories);
-        $this->assign('userSelections', $userSelections);
+        $this->view->assign("profile", $profile);
         return $this->view->fetch();
     }
 
@@ -346,22 +336,16 @@ class BodyProfile extends Backend
      */
     public function generateReport($profile_id = null)
     {
-        if (!$profile_id) {
-            $this->error('档案ID不能为空');
-        }
-
-        $profile = $this->model->find($profile_id);
+        $profile = $this->model->get($profile_id);
         if (!$profile) {
-            $this->error('档案不存在');
+            $this->error(__("No Results were found"));
         }
-
-        $report = BodyAiReport::generateReport($profile_id);
-        
-        if (!$report) {
-            $this->error('生成报告失败');
+        $adminIds = $this->getDataLimitAdminIds();
+        if (is_array($adminIds) && !in_array($profile[$this->dataLimitField], $adminIds)) {
+            $this->error(__("You have no permission"));
         }
-
-        $this->success('报告生成成功', null, ['report_id' => $report->id]);
+        // TODO: 实现AI报告生成逻辑
+        $this->success("AI报告生成功能开发中...");
     }
 
     /**
@@ -370,65 +354,62 @@ class BodyProfile extends Backend
     public function viewReport($report_id = null)
     {
         if (!$report_id) {
-            $this->error('报告ID不能为空');
-        }
-
-        $report = BodyAiReport::with(['profile'])->find($report_id);
-        if (!$report) {
-            $this->error('报告不存在');
+            $this->error(__("Parameter %s can not be empty", "report_id"));
         }
-
-        $this->assign('report', $report);
+        // TODO: 实现AI报告查看逻辑
+        $this->view->assign("report_id", $report_id);
         return $this->view->fetch();
     }
 
     /**
-     * AI测量页面
+     * AI测量
      */
     public function aiMeasurement($profile_id = null)
     {
-        if (!$profile_id) {
-            $this->error('档案ID不能为空');
+        if ($this->request->isPost()) {
+            $profile = $this->model->get($profile_id);
+            if (!$profile) {
+                $this->error(__("No Results were found"));
+            }
+            // TODO: 实现AI测量逻辑
+            $this->success("AI测量功能开发中...");
         }
-
-        $profile = $this->model->with(['user'])->find($profile_id);
+        
+        $profile = $this->model->get($profile_id);
         if (!$profile) {
-            $this->error('档案不存在');
+            $this->error(__("No Results were found"));
         }
-
-        $this->assign('profile', $profile);
+        $this->view->assign("profile", $profile);
         return $this->view->fetch();
     }
 
     /**
-     * 统计数据
+     * 统计分析
      */
     public function statistics()
     {
-        // 档案统计
-        $profileStats = [
-            'total' => $this->model->count(),
-            'male' => $this->model->where('gender', 1)->count(),
-            'female' => $this->model->where('gender', 2)->count(),
-            'own' => $this->model->where('is_own', 1)->count(),
-            'others' => $this->model->where('is_own', 0)->count(),
-        ];
-
-        // BMI分布统计
-        $bmiStats = [];
-        $profiles = $this->model->where('height', '>', 0)->where('weight', '>', 0)->select();
-        foreach ($profiles as $profile) {
-            $bmi = $profile->calculateBMI();
-            $level = $profile->getBMILevel();
-            $bmiStats[$level] = isset($bmiStats[$level]) ? $bmiStats[$level] + 1 : 1;
+        $adminIds = $this->getDataLimitAdminIds();
+        $where = [];
+        if (is_array($adminIds)) {
+            $where[$this->dataLimitField] = ['in', $adminIds];
         }
-
-        // 体型选择统计
-        $bodyTypeStats = BodyTypeSelection::getSelectionStatistics();
-
-        $this->assign('profileStats', $profileStats);
-        $this->assign('bmiStats', $bmiStats);
-        $this->assign('bodyTypeStats', $bodyTypeStats);
+        
+        // 基础统计
+        $totalProfiles = $this->model->where($where)->count();
+        $maleCount = $this->model->where($where)->where('gender', 1)->count();
+        $femaleCount = $this->model->where($where)->where('gender', 2)->count();
+        $ownProfiles = $this->model->where($where)->where('is_own', 1)->count();
+        $otherProfiles = $this->model->where($where)->where('is_own', 0)->count();
+        
+        $statistics = [
+            'total_profiles' => $totalProfiles,
+            'male_count' => $maleCount,
+            'female_count' => $femaleCount,
+            'own_profiles' => $ownProfiles,
+            'other_profiles' => $otherProfiles,
+        ];
+        
+        $this->view->assign("statistics", $statistics);
         return $this->view->fetch();
     }
 }

+ 76 - 279
application/admin/view/body_profile/add.html

@@ -1,286 +1,83 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <title>新增身体档案</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">
-</head>
-<body>
-    <div class="layui-fluid" style="padding: 15px;">
-        <form class="layui-form" action="{:url('add')}" method="post" lay-filter="profile-form">
-            <div class="layui-row layui-col-space15">
-                <div class="layui-col-md6">
-                    <div class="layui-card">
-                        <div class="layui-card-header">基础信息</div>
-                        <div class="layui-card-body">
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">用户</label>
-                                <div class="layui-input-block">
-                                    <select name="row[user_id]" lay-verify="required">
-                                        <option value="">请选择用户</option>
-                                        {foreach $userList as $user}
-                                        <option value="{$user.id}">{$user.username}({$user.nickname|default='未设置昵称'})</option>
-                                        {/foreach}
-                                    </select>
-                                </div>
-                            </div>
+<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
 
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">档案名称</label>
-                                <div class="layui-input-block">
-                                    <input type="text" name="row[profile_name]" lay-verify="required" placeholder="请输入档案名称" autocomplete="off" class="layui-input">
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">档案类型</label>
-                                <div class="layui-input-block">
-                                    <input type="radio" name="row[is_own]" value="1" title="本人档案" checked>
-                                    <input type="radio" name="row[is_own]" value="0" title="他人档案">
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">关系</label>
-                                <div class="layui-input-block">
-                                    <input type="text" name="row[relation]" placeholder="如:本人、父亲、母亲、儿子、女儿等" autocomplete="off" class="layui-input">
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">性别</label>
-                                <div class="layui-input-block">
-                                    <input type="radio" name="row[gender]" value="1" title="男" lay-verify="required">
-                                    <input type="radio" name="row[gender]" value="2" title="女" lay-verify="required">
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="layui-col-md6">
-                    <div class="layui-card">
-                        <div class="layui-card-header">身体数据</div>
-                        <div class="layui-card-body">
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">年龄</label>
-                                <div class="layui-input-block">
-                                    <input type="number" name="row[age]" placeholder="请输入年龄" min="1" max="120" autocomplete="off" class="layui-input">
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">身高(cm)</label>
-                                <div class="layui-input-block">
-                                    <input type="number" name="row[height]" placeholder="请输入身高(厘米)" min="50" max="250" step="0.1" autocomplete="off" class="layui-input">
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">体重(kg)</label>
-                                <div class="layui-input-block">
-                                    <input type="number" name="row[weight]" placeholder="请输入体重(公斤)" min="10" max="300" step="0.1" autocomplete="off" class="layui-input">
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <label class="layui-form-label">档案照片</label>
-                                <div class="layui-input-block">
-                                    <div class="layui-upload">
-                                        <button type="button" class="layui-btn" id="uploadProfilePhoto">上传照片</button>
-                                        <div class="layui-upload-list">
-                                            <img class="layui-upload-img" id="profilePhotoPreview" style="width: 100px; height: 100px; display: none;">
-                                            <input type="hidden" name="row[profile_photo]" id="profilePhotoValue">
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('User')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-user_id" data-rule="required" data-source="user/user/index" data-field="username" class="form-control selectpage" name="row[user_id]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Profile name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-profile_name" data-rule="required" class="form-control" name="row[profile_name]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Is own')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <div class="radio">
+            {foreach name="isOwnList" item="vo"}
+                <label for="row[is_own]-{$key}"><input id="row[is_own]-{$key}" name="row[is_own]" type="radio" value="{$key}" {in name="key" value="1"}checked{/in} /> {$vo}</label> 
+            {/foreach}
             </div>
 
-            <div class="layui-row layui-col-space15" style="margin-top: 15px;">
-                <div class="layui-col-md12">
-                    <div class="layui-card">
-                        <div class="layui-card-header">身体照片</div>
-                        <div class="layui-card-body">
-                            <div class="layui-row layui-col-space15">
-                                <div class="layui-col-md4">
-                                    <div class="layui-form-item">
-                                        <label class="layui-form-label">正面照</label>
-                                        <div class="layui-input-block">
-                                            <div class="layui-upload">
-                                                <button type="button" class="layui-btn layui-btn-sm" id="uploadFrontPhoto">上传正面照</button>
-                                                <div class="layui-upload-list">
-                                                    <img class="layui-upload-img" id="frontPhotoPreview" style="width: 120px; height: 160px; display: none;">
-                                                    <input type="hidden" name="row[body_photos][front]" id="frontPhotoValue">
-                                                </div>
-                                            </div>
-                                        </div>
-                                    </div>
-                                </div>
-
-                                <div class="layui-col-md4">
-                                    <div class="layui-form-item">
-                                        <label class="layui-form-label">侧面照</label>
-                                        <div class="layui-input-block">
-                                            <div class="layui-upload">
-                                                <button type="button" class="layui-btn layui-btn-sm" id="uploadSidePhoto">上传侧面照</button>
-                                                <div class="layui-upload-list">
-                                                    <img class="layui-upload-img" id="sidePhotoPreview" style="width: 120px; height: 160px; display: none;">
-                                                    <input type="hidden" name="row[body_photos][side]" id="sidePhotoValue">
-                                                </div>
-                                            </div>
-                                        </div>
-                                    </div>
-                                </div>
-
-                                <div class="layui-col-md4">
-                                    <div class="layui-form-item">
-                                        <label class="layui-form-label">背面照</label>
-                                        <div class="layui-input-block">
-                                            <div class="layui-upload">
-                                                <button type="button" class="layui-btn layui-btn-sm" id="uploadBackPhoto">上传背面照</button>
-                                                <div class="layui-upload-list">
-                                                    <img class="layui-upload-img" id="backPhotoPreview" style="width: 120px; height: 160px; display: none;">
-                                                    <input type="hidden" name="row[body_photos][back]" id="backPhotoValue">
-                                                </div>
-                                            </div>
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-
-                            <div class="layui-form-item">
-                                <div class="layui-input-block">
-                                    <blockquote class="layui-elem-quote layui-quote-nm">
-                                        <p>提示:身体照片用于AI分析,建议穿着贴身衣物拍摄,确保身体轮廓清晰可见。</p>
-                                    </blockquote>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Relation')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-relation" class="form-control" name="row[relation]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Gender')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <div class="radio">
+            {foreach name="genderList" item="vo"}
+                <label for="row[gender]-{$key}"><input id="row[gender]-{$key}" name="row[gender]" type="radio" value="{$key}" {in name="key" value="1"}checked{/in} /> {$vo}</label> 
+            {/foreach}
             </div>
 
-            <div class="layui-form-item" style="margin-top: 30px; text-align: center;">
-                <button class="layui-btn" lay-submit lay-filter="submit-btn">立即创建</button>
-                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Age')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-age" class="form-control" name="row[age]" type="number" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Height')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-height" class="form-control" name="row[height]" type="number" step="0.1" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Weight')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-weight" class="form-control" name="row[weight]" type="number" step="0.1" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Body photos')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group">
+                <input id="c-body_photos" class="form-control" size="50" name="row[body_photos]" type="text" value="">
+                <div class="input-group-addon no-border no-padding">
+                    <span><button type="button" id="plupload-body_photos" class="btn btn-danger plupload" data-input-id="c-body_photos" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="true" data-preview-id="p-body_photos"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                </div>
+                <span class="msg-box n-right" for="c-body_photos"></span>
             </div>
-        </form>
+            <ul class="row list-inline plupload-preview" id="p-body_photos"></ul>
+        </div>
     </div>
-
-    <script src="/assets/js/layui.js"></script>
-    <script>
-    layui.use(['form', 'upload', 'layer'], function(){
-        var form = layui.form;
-        var upload = layui.upload;
-        var layer = layui.layer;
-
-        // 档案照片上传
-        upload.render({
-            elem: '#uploadProfilePhoto',
-            url: '{:url("common/upload/api")}',
-            accept: 'images',
-            size: 2048,
-            done: function(res){
-                if(res.code == 1){
-                    $('#profilePhotoPreview').attr('src', res.data.url).show();
-                    $('#profilePhotoValue').val(res.data.url);
-                } else {
-                    layer.msg(res.msg);
-                }
-            }
-        });
-
-        // 正面照上传
-        upload.render({
-            elem: '#uploadFrontPhoto',
-            url: '{:url("common/upload/api")}',
-            accept: 'images',
-            size: 2048,
-            done: function(res){
-                if(res.code == 1){
-                    $('#frontPhotoPreview').attr('src', res.data.url).show();
-                    $('#frontPhotoValue').val(res.data.url);
-                } else {
-                    layer.msg(res.msg);
-                }
-            }
-        });
-
-        // 侧面照上传
-        upload.render({
-            elem: '#uploadSidePhoto',
-            url: '{:url("common/upload/api")}',
-            accept: 'images',
-            size: 2048,
-            done: function(res){
-                if(res.code == 1){
-                    $('#sidePhotoPreview').attr('src', res.data.url).show();
-                    $('#sidePhotoValue').val(res.data.url);
-                } else {
-                    layer.msg(res.msg);
-                }
-            }
-        });
-
-        // 背面照上传
-        upload.render({
-            elem: '#uploadBackPhoto',
-            url: '{:url("common/upload/api")}',
-            accept: 'images',
-            size: 2048,
-            done: function(res){
-                if(res.code == 1){
-                    $('#backPhotoPreview').attr('src', res.data.url).show();
-                    $('#backPhotoValue').val(res.data.url);
-                } else {
-                    layer.msg(res.msg);
-                }
-            }
-        });
-
-        // 监听档案类型变化
-        form.on('radio()', function(data){
-            if(data.elem.name === 'row[is_own]') {
-                var relationInput = $('input[name="row[relation]"]');
-                if(data.value === '1') {
-                    relationInput.val('本人').prop('readonly', true);
-                } else {
-                    relationInput.val('').prop('readonly', false);
-                }
-            }
-        });
-
-        // 初始化关系字段
-        if($('input[name="row[is_own]"]:checked').val() === '1') {
-            $('input[name="row[relation]"]').val('本人').prop('readonly', true);
-        }
-
-        // 监听提交
-        form.on('submit(submit-btn)', function(data){
-            var loading = layer.load(2, {content: '正在创建档案...'});
-            
-            $.post('{:url("add")}', data.field, function(res){
-                layer.close(loading);
-                if(res.code === 1){
-                    layer.msg('档案创建成功!', {icon: 1}, function(){
-                        // 关闭当前页面
-                        var index = parent.layer.getFrameIndex(window.name);
-                        parent.layer.close(index);
-                    });
-                } else {
-                    layer.msg(res.msg, {icon: 2});
-                }
-            });
-            
-            return false;
-        });
-    });
-    </script>
-</body>
-</html>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 189 - 0
application/admin/view/body_profile/aiMeasurement.html

@@ -0,0 +1,189 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#one" data-toggle="tab">{:__('AI Measurement')}</a></li>
+        </ul>
+    </div>
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div class="alert alert-info">
+                        <i class="fa fa-info-circle"></i> {:__('Upload body photos for AI measurement analysis')}
+                    </div>
+                    
+                    <form id="ai-measurement-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+                        <input type="hidden" name="profile_id" value="{$profile_id}">
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Front Photo')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="input-group">
+                                    <input id="c-front_photo" class="form-control" size="50" name="front_photo" type="text" value="">
+                                    <div class="input-group-addon no-border no-padding">
+                                        <span><button type="button" id="plupload-front_photo" class="btn btn-danger plupload" data-input-id="c-front_photo" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-front_photo"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                    </div>
+                                    <span class="msg-box n-right" for="c-front_photo"></span>
+                                </div>
+                                <ul class="row list-inline plupload-preview" id="p-front_photo"></ul>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Side Photo')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="input-group">
+                                    <input id="c-side_photo" class="form-control" size="50" name="side_photo" type="text" value="">
+                                    <div class="input-group-addon no-border no-padding">
+                                        <span><button type="button" id="plupload-side_photo" class="btn btn-danger plupload" data-input-id="c-side_photo" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-side_photo"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                    </div>
+                                    <span class="msg-box n-right" for="c-side_photo"></span>
+                                </div>
+                                <ul class="row list-inline plupload-preview" id="p-side_photo"></ul>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Back Photo')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="input-group">
+                                    <input id="c-back_photo" class="form-control" size="50" name="back_photo" type="text" value="">
+                                    <div class="input-group-addon no-border no-padding">
+                                        <span><button type="button" id="plupload-back_photo" class="btn btn-danger plupload" data-input-id="c-back_photo" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="false" data-preview-id="p-back_photo"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                                    </div>
+                                    <span class="msg-box n-right" for="c-back_photo"></span>
+                                </div>
+                                <ul class="row list-inline plupload-preview" id="p-back_photo"></ul>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Measurement Notes')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <textarea id="c-notes" class="form-control" rows="3" name="notes" cols="50" placeholder="{:__('Optional measurement notes')}"></textarea>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group layer-footer">
+                            <label class="control-label col-xs-12 col-sm-2"></label>
+                            <div class="col-xs-12 col-sm-8">
+                                <button type="submit" class="btn btn-success btn-embossed">{:__('Start AI Measurement')}</button>
+                                <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                            </div>
+                        </div>
+                    </form>
+                    
+                    <div id="measurement-progress" class="hide">
+                        <div class="alert alert-warning">
+                            <i class="fa fa-spinner fa-spin"></i> {:__('AI is analyzing your photos, please wait...')}
+                        </div>
+                        <div class="progress">
+                            <div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 0%">
+                                <span class="sr-only">0% Complete</span>
+                            </div>
+                        </div>
+                    </div>
+                    
+                    <div id="measurement-result" class="hide">
+                        <div class="alert alert-success">
+                            <i class="fa fa-check-circle"></i> {:__('AI measurement completed successfully!')}
+                        </div>
+                        <div class="panel panel-default">
+                            <div class="panel-heading">
+                                <h4 class="panel-title">{:__('Measurement Results')}</h4>
+                            </div>
+                            <div class="panel-body">
+                                <table class="table table-striped">
+                                    <tbody id="result-table">
+                                        <!-- Results will be populated here -->
+                                    </tbody>
+                                </table>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script>
+$(function () {
+    var form = $('#ai-measurement-form');
+    var progressDiv = $('#measurement-progress');
+    var resultDiv = $('#measurement-result');
+    
+    // 表单提交
+    form.on('submit', function(e) {
+        e.preventDefault();
+        
+        // 检查是否至少上传了一张照片
+        var hasPhoto = false;
+        ['front_photo', 'side_photo', 'back_photo'].forEach(function(field) {
+            if ($('#c-' + field).val()) {
+                hasPhoto = true;
+            }
+        });
+        
+        if (!hasPhoto) {
+            Toastr.error('{:__("Please upload at least one photo")}');
+            return false;
+        }
+        
+        // 显示进度条
+        form.hide();
+        progressDiv.removeClass('hide');
+        resultDiv.addClass('hide');
+        
+        // 模拟进度
+        var progress = 0;
+        var progressBar = progressDiv.find('.progress-bar');
+        var progressInterval = setInterval(function() {
+            progress += Math.random() * 20;
+            if (progress > 90) progress = 90;
+            progressBar.css('width', progress + '%');
+            progressBar.find('.sr-only').text(Math.round(progress) + '% Complete');
+        }, 500);
+        
+        // 提交数据
+        Fast.api.ajax({
+            url: '{:url("aiMeasurement_process")}',
+            data: form.serialize()
+        }, function(data, ret) {
+            clearInterval(progressInterval);
+            progressBar.css('width', '100%');
+            progressBar.find('.sr-only').text('100% Complete');
+            
+            setTimeout(function() {
+                progressDiv.addClass('hide');
+                
+                // 显示结果
+                if (data.measurements) {
+                    var resultHtml = '';
+                    for (var key in data.measurements) {
+                        var label = key.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
+                        resultHtml += '<tr><td>' + label + '</td><td>' + data.measurements[key] + ' cm</td></tr>';
+                    }
+                    $('#result-table').html(resultHtml);
+                    resultDiv.removeClass('hide');
+                } else {
+                    Toastr.success(ret.msg || '{:__("AI measurement completed")}');
+                    parent.layer.closeAll();
+                    parent.$(".btn-refresh").trigger("click");
+                }
+            }, 1000);
+        }, function(data, ret) {
+            clearInterval(progressInterval);
+            progressDiv.addClass('hide');
+            form.show();
+        });
+    });
+    
+    // 重置表单
+    $('button[type="reset"]').on('click', function() {
+        form.show();
+        progressDiv.addClass('hide');
+        resultDiv.addClass('hide');
+    });
+});
+</script>

+ 278 - 0
application/admin/view/body_profile/bodyTypes.html

@@ -0,0 +1,278 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#one" data-toggle="tab">{:__('Body Type Selection')}</a></li>
+        </ul>
+    </div>
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <form id="body-type-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+                        <input type="hidden" name="profile_id" value="{$profile_id}">
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Shoulder Type')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="row body-type-selection" data-category="shoulder">
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="narrow">
+                                            <img src="/assets/images/body_types/shoulder_narrow.png" alt="窄肩">
+                                            <div class="type-name">窄肩</div>
+                                            <input type="radio" name="shoulder_type" value="narrow">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="normal">
+                                            <img src="/assets/images/body_types/shoulder_normal.png" alt="正常肩">
+                                            <div class="type-name">正常肩</div>
+                                            <input type="radio" name="shoulder_type" value="normal">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="broad">
+                                            <img src="/assets/images/body_types/shoulder_broad.png" alt="宽肩">
+                                            <div class="type-name">宽肩</div>
+                                            <input type="radio" name="shoulder_type" value="broad">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Chest Type')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="row body-type-selection" data-category="chest">
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="flat">
+                                            <img src="/assets/images/body_types/chest_flat.png" alt="平胸">
+                                            <div class="type-name">平胸</div>
+                                            <input type="radio" name="chest_type" value="flat">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="normal">
+                                            <img src="/assets/images/body_types/chest_normal.png" alt="正常胸">
+                                            <div class="type-name">正常胸</div>
+                                            <input type="radio" name="chest_type" value="normal">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="full">
+                                            <img src="/assets/images/body_types/chest_full.png" alt="丰满胸">
+                                            <div class="type-name">丰满胸</div>
+                                            <input type="radio" name="chest_type" value="full">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Waist Type')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="row body-type-selection" data-category="waist">
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="thin">
+                                            <img src="/assets/images/body_types/waist_thin.png" alt="细腰">
+                                            <div class="type-name">细腰</div>
+                                            <input type="radio" name="waist_type" value="thin">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="normal">
+                                            <img src="/assets/images/body_types/waist_normal.png" alt="正常腰">
+                                            <div class="type-name">正常腰</div>
+                                            <input type="radio" name="waist_type" value="normal">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="thick">
+                                            <img src="/assets/images/body_types/waist_thick.png" alt="粗腰">
+                                            <div class="type-name">粗腰</div>
+                                            <input type="radio" name="waist_type" value="thick">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Hip Type')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="row body-type-selection" data-category="hip">
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="flat">
+                                            <img src="/assets/images/body_types/hip_flat.png" alt="扁臀">
+                                            <div class="type-name">扁臀</div>
+                                            <input type="radio" name="hip_type" value="flat">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="normal">
+                                            <img src="/assets/images/body_types/hip_normal.png" alt="正常臀">
+                                            <div class="type-name">正常臀</div>
+                                            <input type="radio" name="hip_type" value="normal">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="full">
+                                            <img src="/assets/images/body_types/hip_full.png" alt="丰满臀">
+                                            <div class="type-name">丰满臀</div>
+                                            <input type="radio" name="hip_type" value="full">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group">
+                            <label class="control-label col-xs-12 col-sm-2">{:__('Leg Type')}:</label>
+                            <div class="col-xs-12 col-sm-8">
+                                <div class="row body-type-selection" data-category="leg">
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="straight">
+                                            <img src="/assets/images/body_types/leg_straight.png" alt="直腿">
+                                            <div class="type-name">直腿</div>
+                                            <input type="radio" name="leg_type" value="straight">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="curved">
+                                            <img src="/assets/images/body_types/leg_curved.png" alt="弯腿">
+                                            <div class="type-name">弯腿</div>
+                                            <input type="radio" name="leg_type" value="curved">
+                                        </div>
+                                    </div>
+                                    <div class="col-md-3">
+                                        <div class="body-type-item" data-type="thick">
+                                            <img src="/assets/images/body_types/leg_thick.png" alt="粗腿">
+                                            <div class="type-name">粗腿</div>
+                                            <input type="radio" name="leg_type" value="thick">
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="form-group layer-footer">
+                            <label class="control-label col-xs-12 col-sm-2"></label>
+                            <div class="col-xs-12 col-sm-8">
+                                <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+                                <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<style>
+.body-type-selection {
+    margin-bottom: 20px;
+}
+.body-type-item {
+    text-align: center;
+    padding: 15px;
+    border: 2px solid #e6e6e6;
+    border-radius: 8px;
+    cursor: pointer;
+    transition: all 0.3s;
+    margin-bottom: 10px;
+}
+.body-type-item:hover {
+    border-color: #1E9FFF;
+    box-shadow: 0 2px 8px rgba(30, 159, 255, 0.2);
+}
+.body-type-item.selected {
+    border-color: #1E9FFF;
+    background-color: #f0f8ff;
+}
+.body-type-item img {
+    width: 60px;
+    height: 80px;
+    object-fit: cover;
+    margin-bottom: 8px;
+}
+.body-type-item .type-name {
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 8px;
+}
+.body-type-item input[type="radio"] {
+    display: none;
+}
+</style>
+
+<script>
+$(function () {
+    var form = $('#body-type-form');
+    
+    // 体型选择事件
+    $('.body-type-item').on('click', function() {
+        var $this = $(this);
+        var $parent = $this.closest('.body-type-selection');
+        var $radio = $this.find('input[type="radio"]');
+        
+        // 清除同类型的选择
+        $parent.find('.body-type-item').removeClass('selected');
+        $parent.find('input[type="radio"]').prop('checked', false);
+        
+        // 选中当前项
+        $this.addClass('selected');
+        $radio.prop('checked', true);
+        
+        // 检查是否可以提交
+        checkFormValid();
+    });
+    
+    // 检查表单是否有效
+    function checkFormValid() {
+        var hasSelection = false;
+        $('input[type="radio"]:checked').each(function() {
+            hasSelection = true;
+        });
+        
+        if (hasSelection) {
+            $('button[type="submit"]').removeClass('disabled');
+        } else {
+            $('button[type="submit"]').addClass('disabled');
+        }
+    }
+    
+    // 表单提交
+    form.on('submit', function(e) {
+        e.preventDefault();
+        
+        if ($('button[type="submit"]').hasClass('disabled')) {
+            return false;
+        }
+        
+        Fast.api.ajax({
+            url: '{:url("bodyTypes_save")}',
+            data: form.serialize()
+        }, function(data, ret) {
+            parent.layer.closeAll();
+            parent.$(".btn-refresh").trigger("click");
+        });
+    });
+    
+    // 重置表单
+    $('button[type="reset"]').on('click', function() {
+        $('.body-type-item').removeClass('selected');
+        $('input[type="radio"]').prop('checked', false);
+        checkFormValid();
+    });
+    
+    // 初始化已选择的体型
+    {if isset($selected_types)}
+    {foreach $selected_types as $category => $type}
+    $('input[name="{$category}_type"][value="{$type}"]').prop('checked', true).closest('.body-type-item').addClass('selected');
+    {/foreach}
+    checkFormValid();
+    {/if}
+});
+</script>

+ 75 - 501
application/admin/view/body_profile/detail.html

@@ -1,506 +1,80 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <title>档案详情</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>
-        .profile-header {
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            padding: 30px;
-            border-radius: 8px;
-            margin-bottom: 20px;
-        }
-        .profile-avatar {
-            width: 100px;
-            height: 100px;
-            border-radius: 50%;
-            border: 4px solid rgba(255,255,255,0.3);
-            object-fit: cover;
-            float: left;
-            margin-right: 20px;
-        }
-        .profile-info {
-            overflow: hidden;
-        }
-        .profile-name {
-            font-size: 24px;
-            font-weight: bold;
-            margin-bottom: 10px;
-        }
-        .profile-meta {
-            font-size: 14px;
-            opacity: 0.9;
-        }
-        .profile-meta span {
-            margin-right: 20px;
-        }
-        .info-card {
-            margin-bottom: 20px;
-        }
-        .info-grid {
-            display: grid;
-            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-            gap: 15px;
-        }
-        .info-item {
-            background: #f8f9fa;
-            padding: 15px;
-            border-radius: 6px;
-            border-left: 4px solid #1E9FFF;
-        }
-        .info-item .label {
-            font-size: 12px;
-            color: #666;
-            margin-bottom: 5px;
-        }
-        .info-item .value {
-            font-size: 16px;
-            font-weight: bold;
-            color: #333;
-        }
-        .body-photos {
-            display: flex;
-            gap: 15px;
-            margin-top: 15px;
-        }
-        .photo-item {
-            text-align: center;
-            flex: 1;
-        }
-        .photo-item img {
-            width: 120px;
-            height: 160px;
-            object-fit: cover;
-            border-radius: 6px;
-            border: 2px solid #e6e6e6;
-        }
-        .photo-item .photo-label {
-            margin-top: 8px;
-            font-size: 14px;
-            color: #666;
-        }
-        .no-photo {
-            width: 120px;
-            height: 160px;
-            background: #f5f5f5;
-            border: 2px dashed #ccc;
-            border-radius: 6px;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            color: #999;
-            font-size: 12px;
-        }
-        .body-type-display {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 15px;
-            margin-top: 15px;
-        }
-        .body-type-item {
-            background: white;
-            border: 1px solid #e6e6e6;
-            border-radius: 6px;
-            padding: 15px;
-            text-align: center;
-            min-width: 120px;
-        }
-        .body-type-item img {
-            width: 60px;
-            height: 80px;
-            object-fit: cover;
-            margin-bottom: 8px;
-        }
-        .body-type-item .type-category {
-            font-size: 12px;
-            color: #666;
-            margin-bottom: 5px;
-        }
-        .body-type-item .type-name {
-            font-weight: bold;
-            color: #333;
-        }
-        .measurements-table {
-            margin-top: 15px;
-        }
-        .measurements-table .layui-table td {
-            padding: 8px 15px;
-        }
-        .bmi-indicator {
-            display: inline-block;
-            padding: 4px 12px;
-            border-radius: 20px;
-            color: white;
-            font-size: 12px;
-            font-weight: bold;
-        }
-        .bmi-normal { background: #52c41a; }
-        .bmi-thin { background: #13c2c2; }
-        .bmi-overweight { background: #fa8c16; }
-        .bmi-obese { background: #f5222d; }
-        .health-score {
-            text-align: center;
-            padding: 20px;
-        }
-        .health-score .score {
-            font-size: 48px;
-            font-weight: bold;
-            color: #1E9FFF;
-            margin-bottom: 10px;
-        }
-        .health-score .level {
-            font-size: 18px;
-            color: #666;
-        }
-    </style>
-</head>
-<body>
-    <div class="layui-fluid" style="padding: 15px;">
-        <!-- 档案头部 -->
-        <div class="profile-header">
-            {if $profile.profile_photo}
-            <img src="{$profile.profile_photo}" alt="档案照片" class="profile-avatar">
-            {else}
-            <div class="profile-avatar" style="background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 36px;">
-                <i class="layui-icon layui-icon-username"></i>
-            </div>
-            {/if}
-            <div class="profile-info">
-                <div class="profile-name">{$profile.profile_name}</div>
-                <div class="profile-meta">
-                    <span><i class="layui-icon layui-icon-username"></i> {$profile.user.username|default='未知用户'}</span>
-                    <span><i class="layui-icon layui-icon-group"></i> {$profile.relation|default='未设置'}</span>
-                    <span><i class="layui-icon layui-icon-date"></i> 创建于 {$profile.createtime|date='Y-m-d H:i'}</span>
-                </div>
-            </div>
-        </div>
-
-        <div class="layui-row layui-col-space15">
-            <!-- 基础信息 -->
-            <div class="layui-col-md6">
-                <div class="layui-card info-card">
-                    <div class="layui-card-header">
-                        <i class="layui-icon layui-icon-user"></i> 基础信息
-                    </div>
-                    <div class="layui-card-body">
-                        <div class="info-grid">
-                            <div class="info-item">
-                                <div class="label">性别</div>
-                                <div class="value">{$profile.gender_text}</div>
-                            </div>
-                            <div class="info-item">
-                                <div class="label">年龄</div>
-                                <div class="value">{$profile.age|default='未设置'} {if $profile.age}岁{/if}</div>
-                            </div>
-                            <div class="info-item">
-                                <div class="label">身高</div>
-                                <div class="value">{$profile.height|default='未测量'} {if $profile.height}cm{/if}</div>
-                            </div>
-                            <div class="info-item">
-                                <div class="label">体重</div>
-                                <div class="value">{$profile.weight|default='未测量'} {if $profile.weight}kg{/if}</div>
-                            </div>
-                            <div class="info-item">
-                                <div class="label">BMI指数</div>
-                                <div class="value">
-                                    {if $profile.height && $profile.weight}
-                                        {php}
-                                        $bmi = round($profile['weight'] / pow($profile['height']/100, 2), 1);
-                                        $bmiLevel = '';
-                                        $bmiClass = '';
-                                        if ($bmi < 18.5) {
-                                            $bmiLevel = '偏瘦';
-                                            $bmiClass = 'bmi-thin';
-                                        } elseif ($bmi < 24) {
-                                            $bmiLevel = '正常';
-                                            $bmiClass = 'bmi-normal';
-                                        } elseif ($bmi < 28) {
-                                            $bmiLevel = '超重';
-                                            $bmiClass = 'bmi-overweight';
-                                        } else {
-                                            $bmiLevel = '肥胖';
-                                            $bmiClass = 'bmi-obese';
-                                        }
-                                        echo $bmi;
-                                        {/php}
-                                        <span class="bmi-indicator {$bmiClass}">{$bmiLevel}</span>
-                                    {else}
-                                        未计算
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#one" data-toggle="tab">{:__('Profile Details')}</a></li>
+        </ul>
+    </div>
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <table class="table table-striped">
+                        <tbody>
+                            <tr>
+                                <td>{:__('ID')}</td>
+                                <td>{$row.id}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Profile name')}</td>
+                                <td>{$row.profile_name}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('User')}</td>
+                                <td>{$row.user_id}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Is own')}</td>
+                                <td>{$row.is_own ? __('Own profile') : __('Others profile')}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Relation')}</td>
+                                <td>{$row.relation}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Gender')}</td>
+                                <td>{$row.gender == 1 ? __('Male') : __('Female')}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Age')}</td>
+                                <td>{$row.age}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Height')}</td>
+                                <td>{$row.height} cm</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Weight')}</td>
+                                <td>{$row.weight} kg</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Body photos')}</td>
+                                <td>
+                                    {if condition="$row.body_photos"}
+                                        {php}$photos = json_decode($row['body_photos'], true);{/php}
+                                        {if condition="is_array($photos)"}
+                                            {foreach name="photos" item="photo"}
+                                                <img src="{$photo}" style="max-width: 100px; max-height: 100px; margin: 5px;" class="img-thumbnail">
+                                            {/foreach}
+                                        {else/}
+                                            <img src="{$row.body_photos}" style="max-width: 100px; max-height: 100px;" class="img-thumbnail">
+                                        {/if}
+                                    {else/}
+                                        {:__('No photos')}
                                     {/if}
-                                </div>
-                            </div>
-                            <div class="info-item">
-                                <div class="label">档案类型</div>
-                                <div class="value">{$profile.is_own_text}</div>
-                            </div>
-                        </div>
-                    </div>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Create time')}</td>
+                                <td>{$row.createtime|datetime}</td>
+                            </tr>
+                            <tr>
+                                <td>{:__('Update time')}</td>
+                                <td>{$row.updatetime|datetime}</td>
+                            </tr>
+                        </tbody>
+                    </table>
                 </div>
             </div>
-
-            <!-- 健康评分 -->
-            <div class="layui-col-md6">
-                <div class="layui-card info-card">
-                    <div class="layui-card-header">
-                        <i class="layui-icon layui-icon-chart"></i> 健康评估
-                    </div>
-                    <div class="layui-card-body">
-                        {if $profile.ai_report}
-                        <div class="health-score">
-                            <div class="score">{$profile.ai_report.health_score}</div>
-                            <div class="level">{$profile.ai_report.health_level_text}</div>
-                            <div style="margin-top: 15px; font-size: 14px; color: #666;">
-                                身体年龄: {$profile.ai_report.body_age}岁
-                            </div>
-                            <div style="margin-top: 10px; font-size: 12px; color: #999;">
-                                更新时间: {$profile.ai_report.generated_time_text}
-                            </div>
-                        </div>
-                        {else}
-                        <div style="text-align: center; padding: 40px; color: #999;">
-                            <i class="layui-icon layui-icon-file" style="font-size: 48px; margin-bottom: 15px;"></i>
-                            <div>暂无AI分析报告</div>
-                            <div style="margin-top: 10px;">
-                                <button class="layui-btn layui-btn-sm" onclick="generateReport()">
-                                    <i class="layui-icon layui-icon-add-1"></i> 生成报告
-                                </button>
-                            </div>
-                        </div>
-                        {/if}
-                    </div>
-                </div>
-            </div>
-        </div>
-
-        <!-- 身体照片 -->
-        <div class="layui-card info-card">
-            <div class="layui-card-header">
-                <i class="layui-icon layui-icon-picture"></i> 身体照片
-            </div>
-            <div class="layui-card-body">
-                <div class="body-photos">
-                    <div class="photo-item">
-                        {if $profile.body_photos_array.front}
-                        <img src="{$profile.body_photos_array.front}" alt="正面照">
-                        {else}
-                        <div class="no-photo">暂无正面照</div>
-                        {/if}
-                        <div class="photo-label">正面照</div>
-                    </div>
-                    <div class="photo-item">
-                        {if $profile.body_photos_array.side}
-                        <img src="{$profile.body_photos_array.side}" alt="侧面照">
-                        {else}
-                        <div class="no-photo">暂无侧面照</div>
-                        {/if}
-                        <div class="photo-label">侧面照</div>
-                    </div>
-                    <div class="photo-item">
-                        {if $profile.body_photos_array.back}
-                        <img src="{$profile.body_photos_array.back}" alt="背面照">
-                        {else}
-                        <div class="no-photo">暂无背面照</div>
-                        {/if}
-                        <div class="photo-label">背面照</div>
-                    </div>
-                </div>
-            </div>
-        </div>
-
-        <!-- 最新测量数据 -->
-        {if $profile.measurements}
-        <div class="layui-card info-card">
-            <div class="layui-card-header">
-                <i class="layui-icon layui-icon-form"></i> 最新测量数据
-                <span style="float: right; font-size: 12px; color: #666;">
-                    测量日期: {$profile.measurements.measurement_date_text}
-                </span>
-            </div>
-            <div class="layui-card-body">
-                <table class="layui-table measurements-table">
-                    <thead>
-                        <tr>
-                            <th>测量部位</th>
-                            <th>数值(cm)</th>
-                            <th>测量部位</th>
-                            <th>数值(cm)</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                        {php}
-                        $measurements = $profile['measurements'];
-                        $fields = [
-                            'chest' => '胸围',
-                            'waist' => '腰围',
-                            'hip' => '臀围',
-                            'thigh' => '大腿围',
-                            'calf' => '小腿围',
-                            'upper_arm' => '上臂围',
-                            'shoulder_width' => '肩宽',
-                            'neck' => '颈围'
-                        ];
-                        if ($profile['gender'] == 2) {
-                            $fields['bust'] = '胸围/乳围';
-                            $fields['underbust'] = '下胸围';
-                        }
-                        $fieldsArray = array_chunk($fields, 2, true);
-                        {/php}
-                        {foreach $fieldsArray as $rowFields}
-                        <tr>
-                            {foreach $rowFields as $field => $label}
-                            <td>{$label}</td>
-                            <td>{$measurements[$field]|default='-'}</td>
-                            {/foreach}
-                            {if count($rowFields) == 1}
-                            <td></td>
-                            <td></td>
-                            {/if}
-                        </tr>
-                        {/foreach}
-                    </tbody>
-                </table>
-            </div>
-        </div>
-        {/if}
-
-        <!-- 体型选择 -->
-        {if $profile.body_types}
-        <div class="layui-card info-card">
-            <div class="layui-card-header">
-                <i class="layui-icon layui-icon-template-1"></i> 体型选择
-            </div>
-            <div class="layui-card-body">
-                <div class="body-type-display">
-                    {foreach $profile.body_types as $category => $bodyType}
-                    <div class="body-type-item">
-                        {if $bodyType.type_image}
-                        <img src="{$bodyType.type_image}" alt="{$bodyType.type_name}">
-                        {else}
-                        <div style="width: 60px; height: 80px; background: #f5f5f5; margin-bottom: 8px; border-radius: 4px;"></div>
-                        {/if}
-                        <div class="type-category">
-                            {php}
-                            $categoryNames = [
-                                'shoulder' => '肩型',
-                                'chest' => '胸型',
-                                'bust' => '胸型',
-                                'waist' => '腰型',
-                                'hip' => '臀型',
-                                'leg' => '腿型'
-                            ];
-                            echo $categoryNames[$category] ?? $category;
-                            {/php}
-                        </div>
-                        <div class="type-name">{$bodyType.type_name}</div>
-                    </div>
-                    {/foreach}
-                </div>
-            </div>
-        </div>
-        {/if}
-
-        <!-- 操作按钮 -->
-        <div style="text-align: center; margin-top: 30px;">
-            <button class="layui-btn" onclick="editProfile()">
-                <i class="layui-icon layui-icon-edit"></i> 编辑档案
-            </button>
-            <button class="layui-btn layui-btn-normal" onclick="manageMeasurements()">
-                <i class="layui-icon layui-icon-form"></i> 测量数据
-            </button>
-            <button class="layui-btn layui-btn-cyan" onclick="aiMeasurement()">
-                <i class="layui-icon layui-icon-light"></i> AI测量
-            </button>
-            <button class="layui-btn layui-btn-warm" onclick="manageBodyTypes()">
-                <i class="layui-icon layui-icon-template-1"></i> 体型选择
-            </button>
-            {if $profile.ai_report}
-            <button class="layui-btn layui-btn-hot" onclick="viewReport()">
-                <i class="layui-icon layui-icon-file"></i> 查看报告
-            </button>
-            {else}
-            <button class="layui-btn layui-btn-hot" onclick="generateReport()">
-                <i class="layui-icon layui-icon-add-1"></i> 生成报告
-            </button>
-            {/if}
         </div>
     </div>
-
-    <script src="/assets/js/layui.js"></script>
-    <script>
-    layui.use(['layer'], function(){
-        var layer = layui.layer;
-        
-        window.editProfile = function() {
-            parent.layer.open({
-                type: 2,
-                title: '编辑档案',
-                area: ['800px', '600px'],
-                content: '{:url("edit")}?ids={$profile.id}',
-                end: function() {
-                    location.reload();
-                }
-            });
-        };
-
-        window.manageMeasurements = function() {
-            parent.layer.open({
-                type: 2,
-                title: '测量数据管理',
-                area: ['1000px', '700px'],
-                content: '{:url("measurements")}?profile_id={$profile.id}'
-            });
-        };
-
-        window.aiMeasurement = function() {
-            parent.layer.open({
-                type: 2,
-                title: 'AI身体测量',
-                area: ['1200px', '800px'],
-                content: '{:url("aiMeasurement")}?profile_id={$profile.id}'
-            });
-        };
-
-        window.manageBodyTypes = function() {
-            parent.layer.open({
-                type: 2,
-                title: '体型选择管理',
-                area: ['900px', '600px'],
-                content: '{:url("bodyTypes")}?profile_id={$profile.id}'
-            });
-        };
-
-        window.generateReport = function() {
-            layer.confirm('确定要生成AI分析报告吗?', {
-                icon: 3,
-                title: '提示'
-            }, function(index) {
-                var loading = layer.load(2, {content: '正在生成报告...'});
-                $.post('{:url("generateReport")}', {profile_id: '{$profile.id}'}, function(res) {
-                    layer.close(loading);
-                    if (res.code === 1) {
-                        layer.msg('报告生成成功');
-                        location.reload();
-                    } else {
-                        layer.msg(res.msg);
-                    }
-                });
-                layer.close(index);
-            });
-        };
-
-        window.viewReport = function() {
-            parent.layer.open({
-                type: 2,
-                title: 'AI分析报告',
-                area: ['1000px', '700px'],
-                content: '{:url("viewReport")}?report_id={$profile.ai_report.id|default=0}'
-            });
-        };
-    });
-    </script>
-</body>
-</html>
+</div>

+ 83 - 0
application/admin/view/body_profile/edit.html

@@ -0,0 +1,83 @@
+<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('User')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-user_id" data-rule="required" data-source="user/user/index" data-field="username" class="form-control selectpage" name="row[user_id]" type="text" value="{$row.user_id|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Profile name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-profile_name" data-rule="required" class="form-control" name="row[profile_name]" type="text" value="{$row.profile_name|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Is own')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <div class="radio">
+            {foreach name="isOwnList" item="vo"}
+                <label for="row[is_own]-{$key}"><input id="row[is_own]-{$key}" name="row[is_own]" type="radio" value="{$key}" {in name="key" value="$row.is_own"}checked{/in} /> {$vo}</label> 
+            {/foreach}
+            </div>
+
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Relation')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-relation" class="form-control" name="row[relation]" type="text" value="{$row.relation|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Gender')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            
+            <div class="radio">
+            {foreach name="genderList" item="vo"}
+                <label for="row[gender]-{$key}"><input id="row[gender]-{$key}" name="row[gender]" type="radio" value="{$key}" {in name="key" value="$row.gender"}checked{/in} /> {$vo}</label> 
+            {/foreach}
+            </div>
+
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Age')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-age" class="form-control" name="row[age]" type="number" value="{$row.age|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Height')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-height" class="form-control" name="row[height]" type="number" step="0.1" value="{$row.height|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Weight')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-weight" class="form-control" name="row[weight]" type="number" step="0.1" value="{$row.weight|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Body photos')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group">
+                <input id="c-body_photos" class="form-control" size="50" name="row[body_photos]" type="text" value="{$row.body_photos|htmlentities}">
+                <div class="input-group-addon no-border no-padding">
+                    <span><button type="button" id="plupload-body_photos" class="btn btn-danger plupload" data-input-id="c-body_photos" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp" data-multiple="true" data-preview-id="p-body_photos"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
+                </div>
+                <span class="msg-box n-right" for="c-body_photos"></span>
+            </div>
+            <ul class="row list-inline plupload-preview" id="p-body_photos"></ul>
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>

+ 28 - 362
application/admin/view/body_profile/index.html

@@ -1,367 +1,33 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <title>身体档案管理</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">
-</head>
-<body>
-    <div class="layui-fluid">
-        <div class="layui-card">
-            <div class="layui-card-header">
-                <h3>身体档案管理</h3>
-            </div>
-            <div class="layui-card-body">
-                <!-- 搜索条件 -->
-                <div class="layui-form layui-form-pane" lay-filter="search-form">
-                    <div class="layui-form-item">
-                        <div class="layui-inline">
-                            <label class="layui-form-label">关键词</label>
-                            <div class="layui-input-inline">
-                                <input type="text" name="search" placeholder="档案名称/关系" autocomplete="off" class="layui-input">
-                            </div>
-                        </div>
-                        <div class="layui-inline">
-                            <label class="layui-form-label">性别</label>
-                            <div class="layui-input-inline">
-                                <select name="gender">
-                                    <option value="">全部</option>
-                                    <option value="1">男</option>
-                                    <option value="2">女</option>
-                                </select>
-                            </div>
-                        </div>
-                        <div class="layui-inline">
-                            <label class="layui-form-label">档案类型</label>
-                            <div class="layui-input-inline">
-                                <select name="is_own">
-                                    <option value="">全部</option>
-                                    <option value="1">本人档案</option>
-                                    <option value="0">他人档案</option>
-                                </select>
-                            </div>
-                        </div>
-                        <div class="layui-inline">
-                            <button class="layui-btn" lay-submit lay-filter="search-btn">
-                                <i class="layui-icon layui-icon-search"></i> 搜索
-                            </button>
-                            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
-                        </div>
-                    </div>
-                </div>
-
-                <!-- 工具栏 -->
-                <div class="layui-row layui-col-space15">
-                    <div class="layui-col-md12">
-                        <div class="layui-btn-group">
-                            <button class="layui-btn" onclick="addProfile()">
-                                <i class="layui-icon layui-icon-add-1"></i> 新增档案
-                            </button>
-                            <button class="layui-btn layui-btn-danger" onclick="batchDelete()">
-                                <i class="layui-icon layui-icon-delete"></i> 批量删除
-                            </button>
-                            <button class="layui-btn layui-btn-normal" onclick="exportData()">
-                                <i class="layui-icon layui-icon-export"></i> 导出数据
-                            </button>
-                            <button class="layui-btn layui-btn-warm" onclick="showStatistics()">
-                                <i class="layui-icon layui-icon-chart"></i> 统计分析
-                            </button>
-                        </div>
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('body_profile/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('body_profile/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('body_profile/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        <a href="javascript:;" class="btn btn-info btn-statistics" title="{:__('Statistics')}" ><i class="fa fa-bar-chart"></i> {:__('Statistics')}</a>
+                        
+                        <div class="dropdown btn-group {:$auth->check('body_profile/multi')?'':'hide'}">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
+                            </ul>
+                        </div> -->
                     </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('body_profile/edit')}"
+                           data-operate-del="{:$auth->check('body_profile/del')}"
+                           width="100%">
+                    </table>
                 </div>
-
-                <!-- 数据表格 -->
-                <table class="layui-hide" id="profileTable" lay-filter="profileTable"></table>
             </div>
-        </div>
-    </div>
 
-    <!-- 操作按钮模板 -->
-    <script type="text/html" id="operateBar">
-        <div class="layui-btn-group">
-            <a class="layui-btn layui-btn-xs" lay-event="detail">
-                <i class="layui-icon layui-icon-list"></i> 详情
-            </a>
-            <a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="measurements">
-                <i class="layui-icon layui-icon-form"></i> 测量数据
-            </a>
-            <a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="bodyTypes">
-                <i class="layui-icon layui-icon-template-1"></i> 体型选择
-            </a>
-            <a class="layui-btn layui-btn-xs layui-btn-hot" lay-event="generateReport">
-                <i class="layui-icon layui-icon-file"></i> 生成报告
-            </a>
-            <a class="layui-btn layui-btn-xs" lay-event="edit">
-                <i class="layui-icon layui-icon-edit"></i> 编辑
-            </a>
-            <a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete">
-                <i class="layui-icon layui-icon-delete"></i> 删除
-            </a>
         </div>
-    </script>
-
-    <!-- 性别模板 -->
-    <script type="text/html" id="genderTpl">
-        {{# if(d.gender == 1) { }}
-            <span class="layui-badge layui-bg-blue">男</span>
-        {{# } else if(d.gender == 2) { }}
-            <span class="layui-badge layui-bg-red">女</span>
-        {{# } else { }}
-            <span class="layui-badge">未知</span>
-        {{# } }}
-    </script>
-
-    <!-- 档案类型模板 -->
-    <script type="text/html" id="isOwnTpl">
-        {{# if(d.is_own == 1) { }}
-            <span class="layui-badge layui-bg-green">本人档案</span>
-        {{# } else { }}
-            <span class="layui-badge layui-bg-orange">他人档案</span>
-        {{# } }}
-    </script>
-
-    <!-- BMI模板 -->
-    <script type="text/html" id="bmiTpl">
-        {{# if(d.bmi > 0) { }}
-            <span class="layui-badge 
-                {{# if(d.bmi_level == '偏瘦') { }}layui-bg-cyan
-                {{# } else if(d.bmi_level == '正常') { }}layui-bg-green
-                {{# } else if(d.bmi_level == '超重') { }}layui-bg-orange
-                {{# } else { }}layui-bg-red{{# } }}">
-                {{d.bmi}} ({{d.bmi_level}})
-            </span>
-        {{# } else { }}
-            <span class="layui-badge">未测量</span>
-        {{# } }}
-    </script>
-
-    <script src="/assets/js/layui.js"></script>
-    <script>
-    layui.use(['table', 'form', 'layer'], function(){
-        var table = layui.table;
-        var form = layui.form;
-        var layer = layui.layer;
-
-        // 渲染表格
-        table.render({
-            elem: '#profileTable',
-            url: '{:url("index")}',
-            toolbar: false,
-            defaultToolbar: ['filter', 'exports', 'print'],
-            cols: [[
-                {type: 'checkbox', fixed: 'left'},
-                {field: 'id', title: 'ID', width: 80, sort: true},
-                {field: 'profile_name', title: '档案名称', width: 120},
-                {field: 'user.username', title: '用户', width: 100},
-                {field: 'gender', title: '性别', width: 80, templet: '#genderTpl'},
-                {field: 'is_own', title: '类型', width: 100, templet: '#isOwnTpl'},
-                {field: 'relation', title: '关系', width: 100},
-                {field: 'age', title: '年龄', width: 80},
-                {field: 'height', title: '身高(cm)', width: 100},
-                {field: 'weight', title: '体重(kg)', width: 100},
-                {field: 'bmi', title: 'BMI', width: 120, templet: '#bmiTpl'},
-                {field: 'createtime', title: '创建时间', width: 160, templet: function(d){
-                    return layui.util.toDateString(d.createtime * 1000, 'yyyy-MM-dd HH:mm');
-                }},
-                {title: '操作', width: 350, align: 'center', fixed: 'right', toolbar: '#operateBar'}
-            ]],
-            page: true,
-            limit: 15,
-            limits: [15, 30, 50, 100],
-            loading: true,
-            text: {
-                none: '暂无相关数据'
-            }
-        });
-
-        // 监听搜索
-        form.on('submit(search-btn)', function(data){
-            table.reload('profileTable', {
-                where: data.field,
-                page: {curr: 1}
-            });
-            return false;
-        });
-
-        // 监听工具栏事件
-        table.on('tool(profileTable)', function(obj){
-            var data = obj.data;
-            var layEvent = obj.event;
-
-            switch(layEvent) {
-                case 'detail':
-                    viewDetail(data.id);
-                    break;
-                case 'measurements':
-                    manageMeasurements(data.id);
-                    break;
-                case 'bodyTypes':
-                    manageBodyTypes(data.id);
-                    break;
-                case 'generateReport':
-                    generateReport(data.id);
-                    break;
-                case 'edit':
-                    editProfile(data.id);
-                    break;
-                case 'delete':
-                    deleteProfile(data.id);
-                    break;
-            }
-        });
-
-        // 监听行双击
-        table.on('rowDouble(profileTable)', function(obj){
-            viewDetail(obj.data.id);
-        });
-    });
-
-    // 新增档案
-    function addProfile() {
-        layer.open({
-            type: 2,
-            title: '新增档案',
-            area: ['800px', '600px'],
-            content: '{:url("add")}',
-            end: function() {
-                layui.table.reload('profileTable');
-            }
-        });
-    }
-
-    // 查看详情
-    function viewDetail(id) {
-        layer.open({
-            type: 2,
-            title: '档案详情',
-            area: ['1000px', '700px'],
-            content: '{:url("detail")}?ids=' + id
-        });
-    }
-
-    // 编辑档案
-    function editProfile(id) {
-        layer.open({
-            type: 2,
-            title: '编辑档案',
-            area: ['800px', '600px'],
-            content: '{:url("edit")}?ids=' + id,
-            end: function() {
-                layui.table.reload('profileTable');
-            }
-        });
-    }
-
-    // 删除档案
-    function deleteProfile(id) {
-        layer.confirm('确定要删除这个档案吗?删除后无法恢复!', {
-            icon: 3,
-            title: '提示'
-        }, function(index) {
-            layui.$.post('{:url("del")}', {ids: id}, function(res) {
-                if (res.code === 1) {
-                    layer.msg('删除成功');
-                    layui.table.reload('profileTable');
-                } else {
-                    layer.msg(res.msg);
-                }
-            });
-            layer.close(index);
-        });
-    }
-
-    // 管理测量数据
-    function manageMeasurements(id) {
-        layer.open({
-            type: 2,
-            title: '测量数据管理',
-            area: ['1000px', '700px'],
-            content: '{:url("measurements")}?profile_id=' + id
-        });
-    }
-
-    // 管理体型选择
-    function manageBodyTypes(id) {
-        layer.open({
-            type: 2,
-            title: '体型选择管理',
-            area: ['900px', '600px'],
-            content: '{:url("bodyTypes")}?profile_id=' + id
-        });
-    }
-
-    // 生成AI报告
-    function generateReport(id) {
-        layer.confirm('确定要生成AI分析报告吗?', {
-            icon: 3,
-            title: '提示'
-        }, function(index) {
-            var loading = layer.load(2, {content: '正在生成报告...'});
-            layui.$.post('{:url("generateReport")}', {profile_id: id}, function(res) {
-                layer.close(loading);
-                if (res.code === 1) {
-                    layer.msg('报告生成成功');
-                    // 打开报告页面
-                    layer.open({
-                        type: 2,
-                        title: 'AI分析报告',
-                        area: ['1000px', '700px'],
-                        content: '{:url("viewReport")}?report_id=' + res.data.report_id
-                    });
-                } else {
-                    layer.msg(res.msg);
-                }
-            });
-            layer.close(index);
-        });
-    }
-
-    // 批量删除
-    function batchDelete() {
-        var checkStatus = layui.table.checkStatus('profileTable');
-        var data = checkStatus.data;
-        
-        if (data.length === 0) {
-            layer.msg('请先选择要删除的档案');
-            return;
-        }
-
-        var ids = data.map(function(item) {
-            return item.id;
-        }).join(',');
-
-        layer.confirm('确定要删除选中的 ' + data.length + ' 个档案吗?', {
-            icon: 3,
-            title: '批量删除确认'
-        }, function(index) {
-            layui.$.post('{:url("del")}', {ids: ids}, function(res) {
-                if (res.code === 1) {
-                    layer.msg('删除成功');
-                    layui.table.reload('profileTable');
-                } else {
-                    layer.msg(res.msg);
-                }
-            });
-            layer.close(index);
-        });
-    }
-
-    // 导出数据
-    function exportData() {
-        layer.msg('导出功能开发中...');
-    }
-
-    // 显示统计
-    function showStatistics() {
-        layer.open({
-            type: 2,
-            title: '统计分析',
-            area: ['1000px', '700px'],
-            content: '{:url("statistics")}'
-        });
-    }
-    </script>
-</body>
-</html>
+    </div>
+</div>

+ 100 - 0
application/admin/view/body_profile/measurements.html

@@ -0,0 +1,100 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#one" data-toggle="tab">{:__('Measurement Management')}</a></li>
+        </ul>
+    </div>
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('body_profile/measurements_edit')}"
+                           data-operate-del="{:$auth->check('body_profile/measurements_del')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script>
+$(function () {
+    var table = $("#table");
+    
+    // 初始化表格
+    table.bootstrapTable({
+        url: '{:url("measurements_index")}',
+        pk: 'id',
+        sortName: 'id',
+        queryParams: function(params) {
+            params.profile_id = '{$profile_id}';
+            return params;
+        },
+        columns: [
+            [
+                {checkbox: true},
+                {field: 'id', title: __('Id')},
+                {field: 'measurement_date', title: __('Measurement date'), operate:'RANGE', addclass:'datetimerange', formatter: Table.api.formatter.datetime},
+                {field: 'chest', title: __('Chest')},
+                {field: 'waist', title: __('Waist')},
+                {field: 'hip', title: __('Hip')},
+                {field: 'thigh', title: __('Thigh')},
+                {field: 'calf', title: __('Calf')},
+                {field: 'upper_arm', title: __('Upper arm')},
+                {field: 'shoulder_width', title: __('Shoulder width')},
+                {field: 'neck', title: __('Neck')},
+                {field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', formatter: Table.api.formatter.datetime},
+                {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+            ]
+        ]
+    });
+    
+    // 为表格绑定事件
+    Table.api.bindevent(table);
+    
+    // 添加按钮事件
+    $('.btn-add').on('click', function() {
+        Fast.api.open('{:url("measurements_add")}?profile_id={$profile_id}', __('Add'), {
+            area: ['800px', '600px']
+        });
+    });
+    
+    // 编辑按钮事件
+    $('.btn-edit').on('click', function() {
+        var ids = Table.api.selectedids(table);
+        if (ids.length === 0) {
+            Toastr.error(__('Please select at least one record'));
+            return;
+        }
+        Fast.api.open('{:url("measurements_edit")}?ids=' + ids.join(','), __('Edit'), {
+            area: ['800px', '600px']
+        });
+    });
+    
+    // 删除按钮事件
+    $('.btn-del').on('click', function() {
+        var ids = Table.api.selectedids(table);
+        if (ids.length === 0) {
+            Toastr.error(__('Please select at least one record'));
+            return;
+        }
+        Layer.confirm(__('Are you sure you want to delete this item?'), function(index) {
+            Fast.api.ajax({
+                url: '{:url("measurements_del")}',
+                data: {ids: ids.join(',')}
+            }, function(data, ret) {
+                table.bootstrapTable('refresh');
+                Layer.close(index);
+            });
+        });
+    });
+});
+</script>

+ 271 - 0
application/admin/view/body_profile/statistics.html

@@ -0,0 +1,271 @@
+<div class="panel panel-default panel-intro">
+    <div class="panel-heading">
+        <ul class="nav nav-tabs" data-field="status">
+            <li class="active"><a href="#one" data-toggle="tab">{:__('Statistics Analysis')}</a></li>
+        </ul>
+    </div>
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <!-- 统计卡片 -->
+                    <div class="row">
+                        <div class="col-md-3 col-sm-6">
+                            <div class="panel panel-primary">
+                                <div class="panel-body">
+                                    <div class="row">
+                                        <div class="col-xs-3">
+                                            <i class="fa fa-users fa-3x"></i>
+                                        </div>
+                                        <div class="col-xs-9 text-right">
+                                            <div class="huge">{$total_count}</div>
+                                            <div>{:__('Total Profiles')}</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="col-md-3 col-sm-6">
+                            <div class="panel panel-info">
+                                <div class="panel-body">
+                                    <div class="row">
+                                        <div class="col-xs-3">
+                                            <i class="fa fa-male fa-3x"></i>
+                                        </div>
+                                        <div class="col-xs-9 text-right">
+                                            <div class="huge">{$male_count}</div>
+                                            <div>{:__('Male Profiles')}</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="col-md-3 col-sm-6">
+                            <div class="panel panel-warning">
+                                <div class="panel-body">
+                                    <div class="row">
+                                        <div class="col-xs-3">
+                                            <i class="fa fa-female fa-3x"></i>
+                                        </div>
+                                        <div class="col-xs-9 text-right">
+                                            <div class="huge">{$female_count}</div>
+                                            <div>{:__('Female Profiles')}</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="col-md-3 col-sm-6">
+                            <div class="panel panel-success">
+                                <div class="panel-body">
+                                    <div class="row">
+                                        <div class="col-xs-3">
+                                            <i class="fa fa-user fa-3x"></i>
+                                        </div>
+                                        <div class="col-xs-9 text-right">
+                                            <div class="huge">{$own_count}</div>
+                                            <div>{:__('Own Profiles')}</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    
+                    <!-- 图表区域 -->
+                    <div class="row">
+                        <div class="col-md-6">
+                            <div class="panel panel-default">
+                                <div class="panel-heading">
+                                    <h4 class="panel-title">{:__('Gender Distribution')}</h4>
+                                </div>
+                                <div class="panel-body">
+                                    <canvas id="genderChart" width="400" height="200"></canvas>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="col-md-6">
+                            <div class="panel panel-default">
+                                <div class="panel-heading">
+                                    <h4 class="panel-title">{:__('Profile Type Distribution')}</h4>
+                                </div>
+                                <div class="panel-body">
+                                    <canvas id="typeChart" width="400" height="200"></canvas>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    
+                    <div class="row">
+                        <div class="col-md-12">
+                            <div class="panel panel-default">
+                                <div class="panel-heading">
+                                    <h4 class="panel-title">{:__('Age Distribution')}</h4>
+                                </div>
+                                <div class="panel-body">
+                                    <canvas id="ageChart" width="800" height="300"></canvas>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    
+                    <div class="row">
+                        <div class="col-md-6">
+                            <div class="panel panel-default">
+                                <div class="panel-heading">
+                                    <h4 class="panel-title">{:__('BMI Distribution')}</h4>
+                                </div>
+                                <div class="panel-body">
+                                    <canvas id="bmiChart" width="400" height="200"></canvas>
+                                </div>
+                            </div>
+                        </div>
+                        
+                        <div class="col-md-6">
+                            <div class="panel panel-default">
+                                <div class="panel-heading">
+                                    <h4 class="panel-title">{:__('Monthly Creation Trend')}</h4>
+                                </div>
+                                <div class="panel-body">
+                                    <canvas id="trendChart" width="400" height="200"></canvas>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<style>
+.huge {
+    font-size: 40px;
+    font-weight: bold;
+}
+.panel-body {
+    padding: 20px;
+}
+</style>
+
+<script src="{:cdnurl('assets/libs/chart/Chart.min.js')}"></script>
+<script>
+$(function () {
+    // 性别分布饼图
+    var genderCtx = document.getElementById('genderChart').getContext('2d');
+    var genderChart = new Chart(genderCtx, {
+        type: 'pie',
+        data: {
+            labels: ['{:__("Male")}', '{:__("Female")}'],
+            datasets: [{
+                data: [{$male_count}, {$female_count}],
+                backgroundColor: ['#36A2EB', '#FF6384'],
+                borderWidth: 1
+            }]
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            legend: {
+                position: 'bottom'
+            }
+        }
+    });
+    
+    // 档案类型分布饼图
+    var typeCtx = document.getElementById('typeChart').getContext('2d');
+    var typeChart = new Chart(typeCtx, {
+        type: 'pie',
+        data: {
+            labels: ['{:__("Own Profile")}', '{:__("Others Profile")}'],
+            datasets: [{
+                data: [{$own_count}, {$other_count}],
+                backgroundColor: ['#4BC0C0', '#FFCE56'],
+                borderWidth: 1
+            }]
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            legend: {
+                position: 'bottom'
+            }
+        }
+    });
+    
+    // 年龄分布柱状图
+    var ageCtx = document.getElementById('ageChart').getContext('2d');
+    var ageChart = new Chart(ageCtx, {
+        type: 'bar',
+        data: {
+            labels: ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '60+'],
+            datasets: [{
+                label: '{:__("Number of Profiles")}',
+                data: [0, 5, 15, 25, 20, 10, 5], // 示例数据
+                backgroundColor: '#36A2EB',
+                borderWidth: 1
+            }]
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            scales: {
+                y: {
+                    beginAtZero: true
+                }
+            }
+        }
+    });
+    
+    // BMI分布饼图
+    var bmiCtx = document.getElementById('bmiChart').getContext('2d');
+    var bmiChart = new Chart(bmiCtx, {
+        type: 'doughnut',
+        data: {
+            labels: ['{:__("Underweight")}', '{:__("Normal")}', '{:__("Overweight")}', '{:__("Obese")}'],
+            datasets: [{
+                data: [5, 60, 25, 10], // 示例数据
+                backgroundColor: ['#13c2c2', '#52c41a', '#fa8c16', '#f5222d'],
+                borderWidth: 1
+            }]
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            legend: {
+                position: 'bottom'
+            }
+        }
+    });
+    
+    // 月度创建趋势线图
+    var trendCtx = document.getElementById('trendChart').getContext('2d');
+    var trendChart = new Chart(trendCtx, {
+        type: 'line',
+        data: {
+            labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
+            datasets: [{
+                label: '{:__("New Profiles")}',
+                data: [12, 19, 15, 25, 22, 30], // 示例数据
+                borderColor: '#36A2EB',
+                backgroundColor: 'rgba(54, 162, 235, 0.1)',
+                tension: 0.4,
+                fill: true
+            }]
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            scales: {
+                y: {
+                    beginAtZero: true
+                }
+            }
+        }
+    });
+});
+</script>

+ 1 - 1
application/common/model/BodyTypeConfig.php

@@ -53,7 +53,7 @@ class BodyTypeConfig extends Model
         }
         
         // 拼接完整URL
-        return request()->domain() . $data['type_image'];
+        return cdnurl($data['type_image']);
     }
 
     /**

+ 291 - 0
public/assets/js/backend/body_profile.js

@@ -0,0 +1,291 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'body_profile/index' + location.search,
+                    add_url: 'body_profile/add',
+                    edit_url: 'body_profile/edit',
+                    del_url: 'body_profile/del',
+                    multi_url: 'body_profile/multi',
+                    import_url: 'body_profile/import',
+                    table: 'body_profile',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                fixedColumns: true,
+                fixedRightNumber: 1,
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id'), sortable: true},
+                        {
+                            field: 'profile_name', 
+                            title: __('Profile name'), 
+                            operate: 'LIKE',
+                            table: table, 
+                            class: 'autocontent', 
+                            formatter: Table.api.formatter.content
+                        },
+                        {
+                            field: 'user.username', 
+                            title: __('User'), 
+                            operate: false,
+                            formatter: function(value, row, index) {
+                                return row.user ? row.user.username : '-';
+                            }
+                        },
+                        {
+                            field: 'gender', 
+                            title: __('Gender'), 
+                            searchList: {"1": __("Male"), "2": __("Female")}, 
+                            formatter: Table.api.formatter.normal
+                        },
+                        {
+                            field: 'is_own', 
+                            title: __('Is own'), 
+                            searchList: {"1": __("Yes"), "0": __("No")}, 
+                            formatter: Table.api.formatter.normal
+                        },
+                        {
+                            field: 'relation', 
+                            title: __('Relation'), 
+                            operate: 'LIKE'
+                        },
+                        {
+                            field: 'age', 
+                            title: __('Age'), 
+                            operate: 'BETWEEN'
+                        },
+                        {
+                            field: 'height', 
+                            title: __('Height'), 
+                            operate: 'BETWEEN',
+                            formatter: function(value, row, index) {
+                                return value ? value + 'cm' : '-';
+                            }
+                        },
+                        {
+                            field: 'weight', 
+                            title: __('Weight'), 
+                            operate: 'BETWEEN',
+                            formatter: function(value, row, index) {
+                                return value ? value + 'kg' : '-';
+                            }
+                        },
+                        {
+                            field: 'bmi', 
+                            title: __('BMI'), 
+                            operate: false,
+                            formatter: function(value, row, index) {
+                                if (row.bmi && row.bmi > 0) {
+                                    var level = row.bmi_level || '';
+                                    var color = 'success';
+                                    if (level === '偏瘦') color = 'info';
+                                    else if (level === '超重') color = 'warning';
+                                    else if (level === '肥胖') color = 'danger';
+                                    return '<span class="label label-' + color + '">' + row.bmi + ' (' + level + ')</span>';
+                                }
+                                return '-';
+                            }
+                        },
+                        {
+                            field: 'createtime', 
+                            title: __('Createtime'), 
+                            operate: 'RANGE', 
+                            addclass: 'datetimerange', 
+                            autocomplete: false, 
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate', 
+                            title: __('Operate'), 
+                            table: table, 
+                            events: Table.api.events.operate, 
+                            formatter: function(value, row, index) {
+                                var that = this;
+                                var buttons = [];
+                                
+                                // 详情按钮
+                                buttons.push('<a href="javascript:;" class="btn btn-xs btn-success btn-detail" data-id="' + row.id + '" title="' + __('Detail') + '"><i class="fa fa-list"></i></a>');
+                                
+                                // 测量数据按钮
+                                buttons.push('<a href="javascript:;" class="btn btn-xs btn-info btn-measurements" data-id="' + row.id + '" title="' + __('Measurements') + '"><i class="fa fa-bar-chart"></i></a>');
+                                
+                                // 体型选择按钮
+                                buttons.push('<a href="javascript:;" class="btn btn-xs btn-warning btn-body-types" data-id="' + row.id + '" title="' + __('Body Types') + '"><i class="fa fa-user"></i></a>');
+                                
+                                // AI测量按钮
+                                buttons.push('<a href="javascript:;" class="btn btn-xs btn-primary btn-ai-measurement" data-id="' + row.id + '" title="' + __('AI Measurement') + '"><i class="fa fa-camera"></i></a>');
+                                
+                                // 生成报告按钮
+                                buttons.push('<a href="javascript:;" class="btn btn-xs btn-purple btn-generate-report" data-id="' + row.id + '" title="' + __('Generate Report') + '"><i class="fa fa-file-text"></i></a>');
+                                
+                                // 标准操作按钮
+                                buttons.push(Table.api.formatter.operate.call(that, value, row, index));
+                                
+                                return buttons.join(' ');
+                            },
+                            events: $.extend({}, Table.api.events.operate, {
+                                'click .btn-detail': function (e, value, row, index) {
+                                    e.stopPropagation();
+                                    e.preventDefault();
+                                    Fast.api.open('body_profile/detail?ids=' + row.id, __('Detail'), {
+                                        area: ['90%', '90%']
+                                    });
+                                },
+                                'click .btn-measurements': function (e, value, row, index) {
+                                    e.stopPropagation();
+                                    e.preventDefault();
+                                    Fast.api.open('body_profile/measurements?profile_id=' + row.id, __('Measurements'), {
+                                        area: ['90%', '90%']
+                                    });
+                                },
+                                'click .btn-body-types': function (e, value, row, index) {
+                                    e.stopPropagation();
+                                    e.preventDefault();
+                                    Fast.api.open('body_profile/bodyTypes?profile_id=' + row.id, __('Body Types'), {
+                                        area: ['90%', '90%']
+                                    });
+                                },
+                                'click .btn-ai-measurement': function (e, value, row, index) {
+                                    e.stopPropagation();
+                                    e.preventDefault();
+                                    Fast.api.open('body_profile/aiMeasurement?profile_id=' + row.id, __('AI Measurement'), {
+                                        area: ['90%', '90%']
+                                    });
+                                },
+                                'click .btn-generate-report': function (e, value, row, index) {
+                                    e.stopPropagation();
+                                    e.preventDefault();
+                                    Layer.confirm(__('Are you sure you want to generate AI report?'), function(index) {
+                                        Fast.api.ajax({
+                                            url: 'body_profile/generateReport',
+                                            data: {profile_id: row.id}
+                                        }, function(data, ret) {
+                                            Layer.close(index);
+                                            if (ret.data && ret.data.report_id) {
+                                                Fast.api.open('body_profile/viewReport?report_id=' + ret.data.report_id, __('AI Report'), {
+                                                    area: ['90%', '90%']
+                                                });
+                                            }
+                                        });
+                                    });
+                                }
+                            })
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+            
+            // 绑定统计按钮事件
+            $(document).on('click', '.btn-statistics', function() {
+                Fast.api.open('body_profile/statistics', __('Statistics'), {
+                    area: ['90%', '90%']
+                });
+            });
+        },
+        
+        add: function () {
+            Controller.api.bindevent();
+        },
+        
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        
+        detail: function () {
+            // 详情页面初始化
+        },
+        
+        measurements: function () {
+            // 测量数据管理页面初始化
+            Table.api.init({
+                extend: {
+                    index_url: 'body_profile/measurements' + location.search,
+                    add_url: 'body_profile/addMeasurement' + location.search,
+                    edit_url: 'body_profile/editMeasurement',
+                    del_url: 'body_profile/delMeasurement',
+                    table: 'body_measurements',
+                }
+            });
+
+            var table = $("#table");
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'measurement_date',
+                sortOrder: 'desc',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {
+                            field: 'measurement_date', 
+                            title: __('Measurement Date'), 
+                            operate: 'RANGE', 
+                            addclass: 'datetimerange', 
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {field: 'height', title: __('Height'), formatter: function(value) { return value ? value + 'cm' : '-'; }},
+                        {field: 'weight', title: __('Weight'), formatter: function(value) { return value ? value + 'kg' : '-'; }},
+                        {field: 'chest', title: __('Chest'), formatter: function(value) { return value ? value + 'cm' : '-'; }},
+                        {field: 'waist', title: __('Waist'), formatter: function(value) { return value ? value + 'cm' : '-'; }},
+                        {field: 'hip', title: __('Hip'), formatter: function(value) { return value ? value + 'cm' : '-'; }},
+                        {
+                            field: 'operate', 
+                            title: __('Operate'), 
+                            table: table, 
+                            events: Table.api.events.operate, 
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+            Table.api.bindevent(table);
+        },
+        
+        bodyTypes: function () {
+            // 体型选择页面初始化
+            Form.api.bindevent($("form[role=form]"));
+        },
+        
+        aiMeasurement: function () {
+            // AI测量页面初始化
+        },
+        
+        statistics: function () {
+            // 统计页面初始化
+        },
+        
+        api: {
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+                
+                // 身体照片上传
+                $(document).on('change', 'input[name="row[body_photos][]"]', function() {
+                    // 处理多图片上传预览
+                });
+                
+                // 档案照片上传
+                $(document).on('change', 'input[name="row[profile_photo]"]', function() {
+                    // 处理单图片上传预览
+                });
+            }
+        }
+    };
+    
+    return Controller;
+});