123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- <?php
- namespace App\Admin\Controllers;
- use App\Http\Controllers\Controller;
- use App\Model\MedicalCalculator;
- use App\Model\MedicalQuestion;
- use App\Model\MedicalOption;
- use Encore\Admin\Form;
- use Encore\Admin\Grid;
- use Encore\Admin\Facades\Admin;
- use Encore\Admin\Layout\Content;
- use Encore\Admin\Controllers\ModelForm;
- use Illuminate\Http\Request;
- use App\Admin\Actions\BatchDeleteAction;
- class MedicalCalculatorController extends Controller
- {
- use ModelForm;
- /**
- * 显示医学计算器列表
- *
- * @OA\Get(
- * path="/api/medical-calculators",
- * summary="获取医学计算器列表",
- * tags={"医学计算器"},
- * @OA\Response(
- * response=200,
- * description="成功",
- * @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/MedicalCalculator"))
- * )
- * )
- */
- public function index()
- {
- return Admin::content(function (Content $content) {
- $content->header('医学计算器');
- $content->description('列表');
- $content->body($this->grid());
- });
- }
- /**
- * 创建新的医学计算器
- *
- * @OA\Post(
- * path="/api/medical-calculators",
- * summary="创建新的医学计算器",
- * tags={"医学计算器"},
- * @OA\RequestBody(
- * required=true,
- * @OA\JsonContent(ref="#/components/schemas/MedicalCalculator")
- * ),
- * @OA\Response(
- * response=201,
- * description="创建成功",
- * @OA\JsonContent(ref="#/components/schemas/MedicalCalculator")
- * )
- * )
- */
- public function create()
- {
- return Admin::content(function (Content $content) {
- $content->header('医学计算器');
- $content->description('新增');
- $content->body($this->form());
- });
- }
- /**
- * 编辑现有的医学计算器
- *
- * @OA\Get(
- * path="/api/medical-calculators/{id}",
- * summary="编辑现有的医学计算器",
- * tags={"医学计算器"},
- * @OA\Parameter(
- * name="id",
- * in="path",
- * required=true,
- * @OA\Schema(type="integer")
- * ),
- * @OA\Response(
- * response=200,
- * description="成功",
- * @OA\JsonContent(ref="#/components/schemas/MedicalCalculator")
- * )
- * )
- *
- * @param int $id 医学计算器ID
- */
- public function edit($id)
- {
- return Admin::content(function (Content $content) use ($id) {
- $content->header('医学计算器');
- $content->description('编辑');
- $content->body($this->form($id)->edit($id)); // 传递 $id 给 form 方法
- });
- }
- /**
- * 计算医学计算器的结果
- *
- * @OA\Post(
- * path="/api/medical-calculators/{id}/calculate",
- * summary="计算医学计算器的结果",
- * tags={"医学计算器"},
- * @OA\Parameter(
- * name="id",
- * in="path",
- * required=true,
- * @OA\Schema(type="integer")
- * ),
- * @OA\RequestBody(
- * required=true,
- * @OA\JsonContent(type="object")
- * ),
- * @OA\Response(
- * response=200,
- * description="计算结果",
- * @OA\JsonContent(type="object", @OA\Property(property="result", type="string"))
- * )
- * )
- *
- * @param Request $request 请求对象
- * @param int $id 医学计算器ID
- * @return \Illuminate\Http\JsonResponse 计算结果的JSON响应
- */
- public function calculate(Request $request, $id)
- {
- $calculator = MedicalCalculator::findOrFail($id);
- $inputs = $request->all();
- try {
- $result = $calculator->calculateResult($inputs);
- return response()->json(['result' => $result]);
- } catch (\Exception $e) {
- return response()->json(['error' => $e->getMessage()], 400);
- }
- }
- /**
- * 获取指定计算器的问题
- *
- * @OA\Get(
- * path="/api/medical-calculators/{id}/questions",
- * summary="获取指定计算器的问题",
- * tags={"医学计算器"},
- * @OA\Parameter(
- * name="id",
- * in="path",
- * required=true,
- * @OA\Schema(type="integer")
- * ),
- * @OA\Response(
- * response=200,
- * description="成功",
- * @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/MedicalQuestion"))
- * )
- * )
- *
- * @param int $id 医学计算器ID
- * @return \Illuminate\Http\JsonResponse 问题列表的JSON响应
- */
- public function getQuestions($id)
- {
- $questions = MedicalQuestion::where('medical_calculator_id', $id)->with('options')->get();
- return response()->json($questions);
- }
- /**
- * 定义医学计算器的表单
- *
- * @param int|null $id 医学计算器ID
- * @return \Encore\Admin\Form 表单对象
- */
- protected function form($id = null)
- {
- return Admin::form(MedicalCalculator::class, function (Form $form) use ($id) {
- $form->text('name', '计算器名称');
- $form->text('disease_name', '疾病名称')->rules('nullable');
- // 确保传递正确的 calculatorId
- $calculatorId = $id ?? $form->model()->id;
- $form->html($this->questionsForm($calculatorId));
- $form->textarea('formula', '计算公式')->help('直接使用问题的变量名称,例如: weight / (height * height)');
- $form->table('results', '结果对照表', function ($table) {
- $table->decimal('min_score', 8, 2, '最小分数')->rules('required|numeric');
- $table->decimal('max_score', 8, 2, '最大分数')->rules('required|numeric');
- $table->text('result', '结果描述');
- })->default([])->customFormat(function ($value) {
- if (empty($value)) {
- return [];
- }
- if (is_string($value)) {
- $decoded = json_decode($value, true);
- return is_array($decoded) ? array_values($decoded) : [];
- }
- return is_array($value) ? array_values($value) : [];
- });
- $form->textarea('instructions', '使用说明')->rules('nullable');
- $form->saving(function (Form $form) {
- if (empty($form->results)) {
- $form->results = [];
- } elseif (is_array($form->results)) {
- $form->results = array_values($form->results);
- }
- });
- $form->saved(function (Form $form) {
- $this->saveQuestions($form);
- admin_toastr('保存成功', 'success');
- return redirect()->route('admin.medical-calculators.index');
- });
- // 在表单显示之前处理 results 字段
- if ($form->isEditing()) {
- $form->editing(function (Form $form) {
- $results = $form->model()->results;
- if (is_string($results)) {
- $decoded = json_decode($results, true);
- $form->results = is_array($decoded) ? array_values($decoded) : [];
- } elseif (!is_array($results)) {
- $form->results = [];
- } else {
- $form->results = array_values($results);
- }
- });
- }
- Admin::script($this->questionsScript());
- });
- }
- /**
- * 生成问题表单的HTML
- *
- * @param int $calculatorId 医学计算器ID
- * @return string 问题表单的HTML
- */
- protected function questionsForm($calculatorId)
- {
- if (!$calculatorId) {
- return '<div id="questions"></div><button type="button" id="add-question" class="btn btn-success">添加问题</button>';
- }
- $questions = MedicalQuestion::where('medical_calculator_id', $calculatorId)->with('options')->get();
- $html = '<div id="questions">';
- foreach ($questions as $index => $question) {
- $html .= $this->questionTemplate($index, $question);
- }
- $html .= '</div>';
- $html .= '<button type="button" id="add-question" class="btn btn-success">添加问题</button>';
- return $html;
- }
- /**
- * 生成单个问题的HTML模板
- *
- * @param int|string $index 问题索引
- * @param MedicalQuestion|null $question 问题对象
- * @return string 单个问题的HTML模板
- */
- protected function questionTemplate($index, $question = null)
- {
- $index = is_numeric($index) ? $index : "' + questionIndex + '";
- $html = '<div class="question" data-index="' . $index . '">';
- $html .= '<input type="hidden" name="questions[' . $index . '][id]" value="' . ($question ? htmlspecialchars($question->id) : '') . '">';
- $html .= '<div class="form-group">';
- $html .= '<label>问题内容:</label>';
- $html .= '<input type="text" name="questions[' . $index . '][question]" value="' . ($question ? htmlspecialchars($question->question) : '') . '" placeholder="请输入问题内容" class="form-control">';
- $html .= '</div>';
- $html .= '<div class="form-group">';
- $html .= '<label>变量名称:</label>';
- $html .= '<input type="text" name="questions[' . $index . '][variable_name]" value="' . ($question ? htmlspecialchars($question->variable_name) : '') . '" placeholder="请输入变量名称" class="form-control">';
- $html .= '</div>';
- $html .= '<div class="form-group">';
- $html .= '<label>问题类型:</label>';
- $html .= '<select name="questions[' . $index . '][type]" class="form-control question-type">';
- $html .= '<option value="text" ' . ($question && $question->type == 'text' ? 'selected' : '') . '>填空</option>';
- $html .= '<option value="radio" ' . ($question && $question->type == 'radio' ? 'selected' : '') . '>单选</option>';
- $html .= '<option value="checkbox" ' . ($question && $question->type == 'checkbox' ? 'selected' : '') . '>多选</option>';
- $html .= '</select>';
- $html .= '</div>';
- $html .= '<div class="form-group score-formula-group" ' . ($question && ($question->type == 'radio' || $question->type == 'checkbox') ? 'style="display:none;"' : '') . '>';
- $html .= '<label>分数:</label>';
- $html .= '<input type="text" name="questions[' . $index . '][score]" value="' . ($question ? $question->score : '') . '" placeholder="请输入分数" class="form-control score-formula">';
- $html .= '</div>';
- $html .= '<div class="options" ' . ($question && ($question->type == 'radio' || $question->type == 'checkbox') ? '' : 'style="display:none;"') . '>';
- if ($question && $question->options) {
- foreach ($question->options as $optionIndex => $option) {
- $html .= $this->optionTemplate($index, $optionIndex, $option);
- }
- }
- $html .= '</div>';
- $html .= '<button type="button" class="btn btn-info add-option" ' . ($question && ($question->type == 'radio' || $question->type == 'checkbox') ? '' : 'style="display:none;"') . '>添加选项</button>';
- $html .= '<button type="button" class="btn btn-danger remove-question">删除问题</button>';
- $html .= '</div>';
- return $html;
- }
- /**
- * 生成单个选项的HTML模板
- *
- * @param int|string $questionIndex 问题索引
- * @param int|string $optionIndex 选项索引
- * @param MedicalOption|null $option 选项对象
- * @return string 单个选项的HTML模板
- */
- protected function optionTemplate($questionIndex, $optionIndex, $option = null)
- {
- $questionIndex = is_numeric($questionIndex) ? $questionIndex : "' + questionIndex + '";
- $optionIndex = is_numeric($optionIndex) ? $optionIndex : "' + optionIndex + '";
- $html = '<div class="option">';
- $html .= '<input type="hidden" name="questions[' . $questionIndex . '][options][' . $optionIndex . '][id]" value="' . ($option ? $option->id : '') . '">';
- $html .= '<div class="form-group">';
- $html .= '<label>选项内容:</label>';
- $html .= '<input type="text" name="questions[' . $questionIndex . '][options][' . $optionIndex . '][content]" value="' . ($option ? $option->content : '') . '" placeholder="请输入选项内容" class="form-control">';
- $html .= '</div>';
- $html .= '<div class="form-group">';
- $html .= '<label>选项分数:</label>';
- $html .= '<input type="number" step="0.01" name="questions[' . $questionIndex . '][options][' . $optionIndex . '][score]" value="' . ($option ? $option->score : '') . '" placeholder="请输入选项分数" class="form-control">';
- $html .= '</div>';
- $html .= '<button type="button" class="btn btn-warning remove-option">删除选项</button>';
- $html .= '</div>';
- return $html;
- }
- /**
- * 生成问题和选项的JavaScript
- *
- * @return string JavaScript代码
- */
- protected function questionsScript()
- {
- $questionTemplate = json_encode($this->questionTemplate("' + questionIndex + '"));
- $optionTemplate = json_encode($this->optionTemplate("' + questionIndex + '", "' + optionIndex + '"));
- return <<<EOT
- $(document).ready(function() {
- let questionIndex = $('.question').length;
- // 移除所有现有的事件处理程序
- $(document).off('click', '#add-question');
- $(document).off('click', '.add-option');
- $(document).off('click', '.remove-question');
- $(document).off('click', '.remove-option');
- $(document).off('change', '.question-type');
- // 重新绑定事件处理程序
- $('#add-question').on('click', function() {
- let newQuestion = $($questionTemplate.replace(/' \+ questionIndex \+ '/g, questionIndex));
- $('#questions').append(newQuestion);
- questionIndex++;
- // 触发新添加问题的类型选择变化事件
- newQuestion.find('.question-type').trigger('change');
- });
- $(document).on('click', '.add-option', function() {
- let questionDiv = $(this).closest('.question');
- let questionIndex = questionDiv.data('index');
- let optionIndex = questionDiv.find('.option').length;
- let newOption = $($optionTemplate.replace(/' \+ questionIndex \+ '/g, questionIndex).replace(/' \+ optionIndex \+ '/g, optionIndex));
- questionDiv.find('.options').append(newOption);
- });
- $(document).on('click', '.remove-question', function() {
- $(this).closest('.question').remove();
- });
- $(document).on('click', '.remove-option', function() {
- $(this).closest('.option').remove();
- });
- $(document).on('change', '.question-type', function() {
- let questionDiv = $(this).closest('.question');
- let optionsDiv = questionDiv.find('.options');
- let scoreFormulaGroup = questionDiv.find('.score-formula-group');
- let addOptionButton = questionDiv.find('.add-option');
- if (this.value === 'text') {
- optionsDiv.hide();
- scoreFormulaGroup.show();
- addOptionButton.hide();
- } else {
- optionsDiv.show();
- scoreFormulaGroup.hide();
- addOptionButton.show();
- }
- });
- // 初始化时触发一次
- $('.question-type').trigger('change');
- });
- EOT;
- }
- /**
- * 获取所有计算器
- *
- * @return \Illuminate\Http\JsonResponse 所有计算器的JSON响应
- */
- public function getAllCalculators()
- {
- $calculators = MedicalCalculator::all();
- return response()->json($calculators);
- }
- /**
- * 获取指定计算器
- *
- * @param int $id 医学计算器ID
- * @return \Illuminate\Http\JsonResponse 计算器详情
- */
- public function show($id)
- {
- $calculator = MedicalCalculator::findOrFail($id);
- return response()->json($calculator);
- }
- /**
- * 更新指定计算器
- *
- * @param Request $request 请求对象
- * @param int $id 医学计算器ID
- * @return \Illuminate\Http\JsonResponse 更新后的计算器详情
- */
- public function update(Request $request, $id)
- {
- $calculator = MedicalCalculator::findOrFail($id);
- $calculator->update($request->all());
- $this->saveQuestions($calculator);
- admin_toastr('更新成功', 'success');
- return redirect()->route('admin.medical-calculators.index');
- }
- /**
- * 删除指定计算器(支持批量删除)
- *
- * @param Request $request
- * @return \Illuminate\Http\JsonResponse 删除结果
- */
- public function destroy($id)
- {
- $ids = is_array($id) ? $id : [$id];
- $successCount = 0;
- $failCount = 0;
- \Illuminate\Support\Facades\DB::transaction(function () use ($ids, &$successCount, &$failCount) {
- // 批量删除关联的问题和选项
- $deletedOptionsCount = MedicalOption::whereIn('medical_question_id', function($query) use ($ids) {
- $query->select('id')
- ->from('medical_questions')
- ->whereIn('medical_calculator_id', $ids);
- })->delete();
- $deletedQuestionsCount = MedicalQuestion::whereIn('medical_calculator_id', $ids)->delete();
- // 删除计算器
- $deletedCalculatorsCount = MedicalCalculator::whereIn('id', $ids)->delete();
- $successCount = $deletedCalculatorsCount;
- $failCount = count($ids) - $deletedCalculatorsCount;
- });
- $message = "成功删除 {$successCount} 个计算器";
- if ($failCount > 0) {
- $message .= ",{$failCount} 个计算器删除失败";
- }
- return response()->json(['message' => $message, 'status' => true]);
- }
- /**
- * 保存问题和选项
- *
- * @param Form|MedicalCalculator $formOrModel 表单对象或模型实例
- */
- protected function saveQuestions($formOrModel)
- {
- if ($formOrModel instanceof Form) {
- $formModelId = $formOrModel->model()->id;
- } elseif ($formOrModel instanceof MedicalCalculator) {
- $formModelId = $formOrModel->id;
- } else {
- throw new \InvalidArgumentException('Invalid argument type');
- }
- $questionsData = request()->input('questions');
- if (!is_array($questionsData)) {
- return; // 如果 $questionsData 不是数组,直接返回
- }
- foreach ($questionsData as $index => $questionData) {
- if (!isset($questionData['question']) || !isset($questionData['type'])) {
- continue;
- }
- try {
- $variableName = !empty($questionData['variable_name']) ? $questionData['variable_name'] : 'VAR_' . ($index + 1);
- $question = MedicalQuestion::updateOrCreate(
- ['id' => $questionData['id'] ?? null],
- [
- 'medical_calculator_id' => $formModelId,
- 'question' => $questionData['question'],
- 'variable_name' => $variableName,
- 'type' => $questionData['type'],
- 'score' => isset($questionData['score']) ? (float)$questionData['score'] : null,
- ]
- );
- // 删除旧的选项
- $question->options()->delete();
- if (isset($questionData['options']) && is_array($questionData['options'])) {
- foreach ($questionData['options'] as $optionData) {
- if (!isset($optionData['content'])) {
- continue;
- }
- $option = new MedicalOption([
- 'content' => $optionData['content'],
- 'score' => isset($optionData['score']) ? (float)$optionData['score'] : 0,
- ]);
- $question->options()->save($option);
- }
- }
- } catch (\Exception $e) {
- \Illuminate\Support\Facades\Log::error('Error saving question: ' . $e->getMessage());
- }
- }
- }
- /**
- * 定义医学计算器的表格
- *
- * @return \Encore\Admin\Grid 表格对象
- */
- protected function grid()
- {
- return Admin::grid(MedicalCalculator::class, function (Grid $grid) {
- $grid->name('计算器名称');
- $grid->disease_name('疾病名称');
- $grid->column('questions_count', '问题数量');
- $grid->paginate(20);
- $grid->disableExport();
- $grid->actions(function ($actions) {
- $actions->disableView();
- });
- $grid->filter(function ($filter) {
- $filter->disableIdFilter();
- $filter->like('name', '输入名称搜索');
- });
- // 启用批量删除
- $grid->batchActions(function ($batch) {
- $batch->disableDelete();
- $batch->add(new BatchDeleteAction());
- });
- });
- }
- }
|