# 计划支出模板设置页面 - 可视化预览区域默认配置数据来源 ## 页面路径 `http://localhost:3000/#/settings/planned-expenditure-template-settings?categoryId=32` ## 路由配置 - **路由路径**: `/settings/planned-expenditure-template-settings` - **路由名称**: `CanvasSettings` - **组件位置**: `czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue` ## 可视化预览区域 可视化预览区域位于组件模板的第 **36-150行**,包含以下四个区域: 1. **区域a:基本信息** (`basic_info`) 2. **区域b:资金申请上会** (`meeting_info`) 3. **区域c:事前流程** (`pre_approval`) 4. **区域d:支付流程** (`payment`) ## 默认配置数据来源 ### 1. 前端硬编码默认配置 **位置**: `czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue` 第 **286-328行** ```286:328:czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue const defaultSectionConfig = { basic_info: { title: '基本信息', fields: [ { key: 'title', label: '事项标题', type: 'text', required: true, visible: true }, { key: 'amount', label: '金额', type: 'number', required: true, visible: true }, { key: 'project_name', label: '项目名称', type: 'text', required: false, visible: true }, { key: 'budget_source', label: '资金来源', type: 'text', required: false, visible: true }, { key: 'apply_date', label: '申请日期', type: 'date', required: false, visible: true } ] }, meeting_info: { title: '拟支上会', fields: [ { key: 'meeting_date', label: '上会日期', type: 'date', required: false, visible: true }, { key: 'meeting_result', label: '上会结果', type: 'text', required: false, visible: true }, { key: 'meeting_amount', label: '上会金额', type: 'number', required: false, visible: true }, { key: 'meeting_notes', label: '上会备注', type: 'textarea', required: false, visible: true } ] }, pre_approval: { title: '事前流程', fields: [ { key: 'procurement_approval', label: '采购审批', type: 'checkbox', required: false, visible: true }, { key: 'contract_approval', label: '合同审批', type: 'checkbox', required: false, visible: true }, { key: 'bidding_process', label: '招标流程', type: 'checkbox', required: false, visible: true }, { key: 'attachments', label: '附件清单', type: 'textarea', required: false, visible: true } ] }, payment: { title: '支付流程', rounds: [ { round: 1, fields: [ { key: 'payment_amount_1', label: '支付金额', type: 'number', required: false, visible: true }, { key: 'payment_date_1', label: '支付日期', type: 'date', required: false, visible: true }, { key: 'payment_status_1', label: '支付状态', type: 'text', required: false, visible: true } ] } ] } } ``` ### 2. 数据库模板配置 **数据表**: `budget_planned_expenditure_templates` - **字段**: `config` (JSON类型,自动转换为数组) - **关联**: 通过 `category_id` 关联到 `budget_planned_expenditure_categories` 表 **模型位置**: `backend/Modules/Budget/app/Models/PlannedExpenditureTemplate.php` ```25:31:backend/Modules/Budget/app/Models/PlannedExpenditureTemplate.php protected $casts = [ 'category_id' => 'integer', 'config' => 'array', // JSON自动转换为数组 'sort_order' => 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', ]; ``` ## 数据加载流程 ### 初始化流程 1. **组件挂载** (`onMounted` 钩子,第 **730-741行**) ```javascript onMounted(async () => { // 先加载分类树以构建映射表 await loadCategoryTree() // 从URL参数获取categoryId const categoryIdFromQuery = route.query.categoryId if (categoryIdFromQuery) { await loadCategoryAndTemplate(categoryIdFromQuery) } }) ``` 2. **加载分类树** (`loadCategoryTree` 函数,第 **397-407行**) - API调用: `plannedExpenditureCategoryAPI.getCategoryTree()` - 后端路由: `GET /budget/planned-expenditure-categories-tree` - 用途: 构建分类映射表 (`categoryMap`),用于快速查找分类名称 3. **加载分类和模板** (`loadCategoryAndTemplate` 函数,第 **409-472行**) **关键逻辑**: ```javascript // 加载该分类的模板(每个分类只有一个模板) const templateResponse = await plannedExpenditureTemplateAPI.getByCategory(categoryId.value) if (templateResponse.code === 0) { const templates = templateResponse.data || [] const template = templates.length > 0 ? templates[0] : null if (template) { // 使用模板的配置 currentTemplateId.value = template.id const config = template.config || JSON.parse(JSON.stringify(defaultSectionConfig)) normalizeFieldConfig(config) currentConfig.value = config } else { // 如果没有模板,使用默认配置 currentTemplateId.value = null const newConfig = JSON.parse(JSON.stringify(defaultSectionConfig)) normalizeFieldConfig(newConfig) currentConfig.value = newConfig } } ``` ### 数据优先级 1. **数据库模板配置** (优先级最高) - 如果该分类在数据库中存在模板记录,使用 `template.config` - 如果 `template.config` 为空,回退到默认配置 2. **前端默认配置** (兜底方案) - 当数据库中没有模板记录时使用 - 当API调用失败时使用 - 当模板配置为空时使用 ## API接口 ### 获取分类模板 **前端API定义**: `czemc-budget-execution-frontend/src/utils/api.js` 第 **162-164行** ```javascript getByCategory: (categoryId) => { return request.get(`/budget/planned-expenditure-templates/by-category/${categoryId}`) } ``` **后端路由**: `backend/Modules/Budget/routes/api.php` 第 **98行** ```php Route::get('planned-expenditure-templates/by-category/{categoryId}', [PlannedExpenditureTemplateController::class, 'getByCategory']); ``` **后端控制器**: `backend/Modules/Budget/app/Http/Controllers/Api/PlannedExpenditureTemplateController.php` 第 **222-241行** ```222:241:backend/Modules/Budget/app/Http/Controllers/Api/PlannedExpenditureTemplateController.php public function getByCategory($categoryId) { try { $templates = PlannedExpenditureTemplate::with('category') ->byCategory($categoryId) ->whereHas('category', function ($q) { $q->where('is_active', true); }) ->orderBy('sort_order') ->orderBy('id') ->get() ->map(function ($template) { return $this->formatTemplate($template); }); return $this->success($templates); } catch (\Exception $e) { return $this->fail([500, '获取模板列表失败:' . $e->getMessage()]); } } ``` **返回数据格式** (`formatTemplate` 方法,第 **307-330行**): ```307:330:backend/Modules/Budget/app/Http/Controllers/Api/PlannedExpenditureTemplateController.php private function formatTemplate($template, $includeRelations = false) { $data = [ 'id' => $template->id, 'category_id' => $template->category_id, 'name' => $template->name, 'config' => $template->config, 'sort_order' => $template->sort_order, 'description' => $template->description, 'created_at' => $template->created_at, 'updated_at' => $template->updated_at, ]; if ($includeRelations && $template->relationLoaded('category') && $template->category) { $data['category'] = [ 'id' => $template->category->id, 'name' => $template->category->name, 'is_leaf' => $template->category->is_leaf, 'is_active' => $template->category->is_active, ]; } return $data; } ``` ## 配置规范化 在加载配置后,会调用 `normalizeFieldConfig` 函数(第 **344-377行**)确保所有字段都有 `visible` 和 `required` 属性: ```344:377:czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue const normalizeFieldConfig = (config) => { Object.keys(config).forEach(sectionKey => { const section = config[sectionKey] // 处理普通区域的 fields if (section.fields && Array.isArray(section.fields)) { section.fields.forEach(field => { if (field.visible === undefined) { field.visible = true } if (field.required === undefined) { field.required = false } }) } // 处理 payment 区域的 rounds if (section.rounds && Array.isArray(section.rounds)) { section.rounds.forEach(round => { if (round.fields && Array.isArray(round.fields)) { round.fields.forEach(field => { if (field.visible === undefined) { field.visible = true } if (field.required === undefined) { field.required = false } }) } }) } }) } ``` ## 当前配置状态 配置数据存储在组件的响应式变量 `currentConfig` 中(第 **334行**): ```javascript const currentConfig = ref(JSON.parse(JSON.stringify(defaultSectionConfig))) ``` 可视化预览区域通过 `v-for` 指令遍历 `currentConfig` 的各个区域来渲染字段: - **基本信息区域**: `currentConfig.basic_info?.fields` - **上会信息区域**: `currentConfig.meeting_info?.fields` - **事前流程区域**: `currentConfig.pre_approval?.fields` - **支付流程区域**: `currentConfig.payment?.rounds` ## 数据保存 当用户修改配置并点击"保存"按钮时,会调用 `saveConfig` 函数(第 **608-641行**),将配置保存到数据库: ```javascript const templateData = { category_id: categoryId.value, name: currentCategoryName.value || '默认模板', config: currentConfig.value } // 如果已有模板ID,则更新;否则创建 if (currentTemplateId.value) { result = await plannedExpenditureTemplateAPI.update(currentTemplateId.value, templateData) } else { result = await plannedExpenditureTemplateAPI.findOrCreate(templateData) } ``` ## 总结 可视化预览区域的默认配置数据来源优先级: 1. **数据库模板配置** (`budget_planned_expenditure_templates.config`) - 通过API `GET /budget/planned-expenditure-templates/by-category/{categoryId}` 获取 - 每个分类只有一个模板 - 配置以JSON格式存储在数据库的 `config` 字段中 2. **前端硬编码默认配置** (`defaultSectionConfig`) - 定义在 `CanvasSettings.vue` 组件中 - 当数据库中没有模板时使用 - 包含四个区域的完整字段定义 3. **配置规范化处理** - 确保所有字段都有 `visible` 和 `required` 属性 - 处理普通区域和支付流程区域的不同数据结构 4. **实时更新** - 配置修改后通过 `currentConfig` 响应式变量实时反映在预览区域 - 保存后更新数据库,下次加载时优先使用数据库配置