| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 | <?phpnamespace 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());            });        });    }}
 |