You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cz-hjjc-budget/docs/planned-expenditure-templat...

320 lines
11 KiB

4 months ago
# 计划支出模板设置页面 - 可视化预览区域默认配置数据来源
## 页面路径
`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` 响应式变量实时反映在预览区域
- 保存后更新数据库,下次加载时优先使用数据库配置