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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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