weizong song 3 months ago
parent ced13f44ed
commit b6f2278fcb

@ -111,3 +111,5 @@ echo ""

@ -0,0 +1,83 @@
import request from '@/utils/request'
/**
* 根据OA流程ID获取关联的支付列表
* @param {number} oaFlowId - OA流程ID
* @param {object} params - 其他查询参数 status, keyword
* @param {boolean} isLoading - 是否显示加载动画
* @returns {Promise}
*/
export function getPaymentsByFlowId(oaFlowId, params = {}, isLoading = false) {
return request({
method: 'get',
url: '/api/budget/payments',
params: {
oa_flow_id: oaFlowId,
...params
},
isLoading
})
}
/**
* 获取付款分类绑定的模板元素用于还原非直接支付动态字段
* 后端路由GET /api/budget/payment-categories/{id}/template-elements
*/
export function getPaymentCategoryTemplateElements(categoryId, isLoading = false) {
return request({
method: 'get',
url: `/api/budget/payment-categories/${categoryId}/template-elements`,
isLoading
})
}
/**
* 获取明细表格字段定义
* 后端路由GET /api/budget/detail-table-fields/{elementId}
*/
export function getDetailTableFields(elementId, isLoading = false) {
return request({
method: 'get',
url: `/api/budget/detail-table-fields/${elementId}`,
isLoading
})
}
/**
* 获取非直接支出账单详情包含 element_valueskey element_{id} field_key
* 后端路由GET /api/budget/planned-expenditures/{id}
*/
export function getPlannedExpenditure(id, isLoading = false) {
return request({
method: 'get',
url: `/api/budget/planned-expenditures/${id}`,
isLoading
})
}
/**
* 获取某个非直接支出分类下的模板画布配置
* 后端路由GET /api/budget/planned-expenditure-templates/by-category/{categoryId}
*/
export function getPlannedExpenditureTemplatesByCategory(categoryId, isLoading = false) {
return request({
method: 'get',
url: `/api/budget/planned-expenditure-templates/by-category/${categoryId}`,
isLoading
})
}
/**
* 批量获取 OA 流程详情用于显示 no/title 而不是流程ID
* 后端路由POST /api/budget/oa-flow-details
* body: { flow_ids: number[] }
*/
export function getOaFlowDetails(flowIds, isLoading = false) {
return request({
method: 'post',
url: '/api/budget/oa-flow-details',
data: { flow_ids: flowIds },
isLoading
})
}

@ -196,8 +196,8 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="申请科室经办人">
<el-select v-model="form.apply_handler_id" placeholder="请选择" filterable clearable style="width: 100%">
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id" />
<el-select v-model="form.apply_handler_id_array" placeholder="请选择" filterable multiple clearable style="width: 100%">
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id.toString()" />
</el-select>
</el-form-item>
</el-col>
@ -330,7 +330,8 @@ export default {
purchase_category: "",
handler_admin_ids: "",
handler_admin_ids_array: [],
apply_handler_id: null,
apply_handler_id: "",
apply_handler_id_array: [],
purchase_handler_id: null,
owner_department_id: null,
owner_department_ids: "",
@ -516,7 +517,8 @@ export default {
purchase_category: "",
handler_admin_ids: "",
handler_admin_ids_array: [],
apply_handler_id: null,
apply_handler_id: "",
apply_handler_id_array: [],
purchase_handler_id: null,
owner_department_id: null,
owner_department_ids: "",
@ -580,7 +582,7 @@ export default {
this.form.perform_status = contractData.perform_status || "";
this.form.is_accepted = contractData.is_accepted || false;
this.form.purchase_category = contractData.purchase_category || "";
this.form.apply_handler_id = contractData.apply_handler_id || null;
this.form.apply_handler_id = contractData.apply_handler_id || "";
this.form.purchase_handler_id = contractData.purchase_handler_id || null;
this.form.owner_department_id = contractData.owner_department_id || null;
this.form.remark = contractData.remark || "";
@ -606,6 +608,16 @@ export default {
this.form.handler_admin_ids_array = [];
}
// value
if (contractData.apply_handler_id) {
const ids = typeof contractData.apply_handler_id === 'string'
? contractData.apply_handler_id.split(',').filter(id => id)
: (Array.isArray(contractData.apply_handler_id) ? contractData.apply_handler_id : []);
this.form.apply_handler_id_array = ids.map(id => String(id));
} else {
this.form.apply_handler_id_array = [];
}
//
if (contractData.attachment_id && contractData.attachment) {
this.attachmentFileList = [{
@ -762,6 +774,11 @@ export default {
? this.form.handler_admin_ids_array.join(',')
: (this.form.handler_admin_ids || '');
//
const applyHandlerIds = Array.isArray(this.form.apply_handler_id_array)
? this.form.apply_handler_id_array.filter(id => id).join(',')
: (this.form.apply_handler_id || '');
// amount_total null
const amountTotal = this.form.amount_type === 'open' ? null : (this.form.amount_total || 0);
@ -793,7 +810,7 @@ export default {
is_accepted: this.form.is_accepted || false,
purchase_category: this.form.purchase_category || "",
handler_admin_ids: handlerAdminIds,
apply_handler_id: this.form.apply_handler_id,
apply_handler_id: applyHandlerIds,
purchase_handler_id: this.form.purchase_handler_id,
owner_department_id: this.form.owner_department_id,
owner_department_ids: ownerDepartmentIds,

@ -25,6 +25,237 @@
style="margin-bottom: 20px;"
></RelatedFlows>
<!-- 关联的支付信息查看/办理只要有 flow_id 就展示没数据时展示空态 -->
<el-card
v-if="$route.query.flow_id"
shadow="never"
style="margin-bottom: 20px;"
v-loading="loadingPayments"
>
<div slot="header" class="clearfix">
<span style="font-weight: bold;">关联的支付信息</span>
<span v-if="relatedPayments.length > 0" style="float: right; color: #909399; font-size: 12px;">
{{ relatedPayments.length }}
</span>
</div>
<div v-if="relatedPayments.length === 0" style="color:#909399; padding: 8px 0;">
暂无关联的支付信息
</div>
<div v-for="(payment, index) in relatedPayments" v-else :key="payment.id" style="margin-bottom: 20px;">
<el-divider v-if="index > 0"></el-divider>
<div class="payment-info">
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<span class="label">支付编号</span>
<span class="value">{{ payment.serial_number || '-' }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">支付状态</span>
<el-tag :type="getPaymentStatusType(payment.status)" size="small">
{{ payment.status_text || '-' }}
</el-tag>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="12">
<div class="info-item">
<span class="label">支付金额</span>
<span class="value" style="color: #F56C6C; font-weight: bold;">
¥{{ formatAmount(payment.total_amount) }}
</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">支付日期</span>
<span class="value">{{ formatDate(payment.payment_date) || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;" v-if="payment.payment_type_info">
<el-col :span="12">
<div class="info-item">
<span class="label">支付类型</span>
<span class="value">{{ payment.payment_type_info.payment_type_text || '-' }}</span>
</div>
</el-col>
<el-col :span="12" v-if="payment.payment_type_info.breadcrumb">
<div class="info-item">
<span class="label">支付分类</span>
<span class="value">{{ formatBreadcrumb(payment.payment_type_info) }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;" v-if="payment.description">
<el-col :span="24">
<div class="info-item">
<span class="label">支付说明</span>
<span class="value">{{ payment.description }}</span>
</div>
</el-col>
</el-row>
<!-- 支付明细 -->
<div v-if="payment.details && payment.details.length > 0" style="margin-top: 15px;">
<div style="font-weight: bold; margin-bottom: 10px; color: #606266;">支付明细</div>
<el-table
:data="payment.details"
border
size="small"
style="width: 100%"
>
<el-table-column prop="amount" label="金额" width="120" align="right">
<template slot-scope="scope">
<span style="color: #F56C6C; font-weight: bold;">
¥{{ formatAmount(scope.row.amount) }}
</span>
</template>
</el-table-column>
<el-table-column prop="description" label="说明" min-width="150" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.description || '-' }}
</template>
</el-table-column>
<el-table-column label="关联信息" min-width="200">
<template slot-scope="scope">
<div v-if="scope.row.contract">
<span style="color: #409EFF;">合同</span>
{{ scope.row.contract.title || scope.row.contract.contract_no || '-' }}
</div>
<div v-else-if="scope.row.expenditure">
<span style="color: #409EFF;">账单</span>
{{ scope.row.expenditure.title || scope.row.expenditure.serial_number || '-' }}
</div>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 非直接支付还原非直接支出(PlannedExpenditure)的元素模板与内容 -->
<el-card
v-if="payment.payment_type_info && payment.payment_type_info.payment_type === 'indirect' && payment.details && payment.details.some(d => d.expenditure_type === 'indirect')"
shadow="never"
style="margin-top: 15px;"
v-loading="loadingIndirectExpenditures"
>
<div slot="header" class="clearfix">
<span style="font-weight: bold;">非直接支付相关信息</span>
</div>
<div v-for="(d, di) in payment.details.filter(i => i && i.expenditure_type === 'indirect' && i.expenditure_id)" :key="d.id" style="margin-bottom: 20px;">
<el-divider v-if="di > 0"></el-divider>
<div v-if="(indirectExpenditureMap[d.expenditure_id] && indirectExpenditureMap[d.expenditure_id].title) || (d.expenditure && d.expenditure.title)" style="margin-bottom: 10px; font-weight: 600; color:#303133;">
{{ (indirectExpenditureMap[d.expenditure_id] && indirectExpenditureMap[d.expenditure_id].title) || (d.expenditure && d.expenditure.title) }}
</div>
<el-descriptions :column="1" border class="indirect-payment-descriptions">
<template v-for="f in getIndirectTemplateFields((indirectExpenditureMap[d.expenditure_id] && indirectExpenditureMap[d.expenditure_id].category_id) || (d.expenditure && d.expenditure.category_id))">
<el-descriptions-item
v-if="f.element_type !== 'oa_custom_model' || hasOaCustomModelValue(d.expenditure_id, f)"
:key="f.key || (f.element_id || f.label)"
:label="f.label || f.name || '字段'"
>
<!-- oa_custom_model显示标题 + 点击抽屉查看流程 -->
<template v-if="f.element_type === 'oa_custom_model'">
<span v-if="getOaCustomModelBinding(d.expenditure_id, f.key)">
<el-link
type="primary"
:underline="false"
@click="openFlowDrawer({
flowId: getOaCustomModelBinding(d.expenditure_id, f.key).flow_instance_id,
customModelId: getOaCustomModelBinding(d.expenditure_id, f.key).flow_custom_model_id,
title: getFlowDisplayName(getOaCustomModelBinding(d.expenditure_id, f.key).flow_instance_id, getOaCustomModelBinding(d.expenditure_id, f.key).flow_display_name)
})"
>
{{ getFlowDisplayName(getOaCustomModelBinding(d.expenditure_id, f.key).flow_instance_id, getOaCustomModelBinding(d.expenditure_id, f.key).flow_display_name) }}
</el-link>
</span>
<span v-else>
<el-link
v-if="extractFlowId(getIndirectFieldValue(d.expenditure_id, f.key)) && (f.model_id || f.flow_custom_model_id)"
type="primary"
:underline="false"
@click="openFlowDrawer({
flowId: extractFlowId(getIndirectFieldValue(d.expenditure_id, f.key)),
customModelId: f.model_id || f.flow_custom_model_id,
title: extractDisplayName(getIndirectFieldValue(d.expenditure_id, f.key))
})"
>
{{ getFlowDisplayName(extractFlowId(getIndirectFieldValue(d.expenditure_id, f.key)), extractDisplayName(getIndirectFieldValue(d.expenditure_id, f.key))) }}
</el-link>
<span v-else>{{ formatReadonlyValue(getIndirectFieldValue(d.expenditure_id, f.key)) }}</span>
</span>
</template>
<!-- meeting_minutes点击抽屉查看 -->
<template v-else-if="f.element_type === 'meeting_minutes'">
<el-link
v-if="extractMeetingMinuteId(getIndirectFieldValue(d.expenditure_id, f.key))"
type="primary"
:underline="false"
@click="openMeetingMinutesDrawer(extractMeetingMinuteId(getIndirectFieldValue(d.expenditure_id, f.key)))"
>
查看会议纪要
</el-link>
<span v-else>-</span>
</template>
<!-- detail_table渲染为表格 -->
<template v-else-if="f.element_type === 'detail_table'">
<div style="overflow-x: auto;">
<el-table
:data="Array.isArray(getIndirectFieldValue(d.expenditure_id, f.key)) ? getIndirectFieldValue(d.expenditure_id, f.key) : []"
border
size="small"
style="width: 100%; min-width: 600px;"
>
<el-table-column
v-for="col in (indirectDetailTableFieldsMap[f.element_id] || [])"
:key="col.id || col.field_key"
:prop="col.field_key"
:label="col.field_name || col.field_key"
min-width="120"
show-overflow-tooltip
/>
</el-table>
</div>
</template>
<!-- 其他类型通用展示 -->
<template v-else>
{{ formatReadonlyValue(getIndirectFieldValue(d.expenditure_id, f.key)) }}
</template>
</el-descriptions-item>
</template>
</el-descriptions>
</div>
</el-card>
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="12">
<div class="info-item">
<span class="label">创建时间</span>
<span class="value">{{ formatDateTime(payment.created_at) || '-' }}</span>
</div>
</el-col>
<el-col :span="12" v-if="payment.processed_at">
<div class="info-item">
<span class="label">处理时间</span>
<span class="value">{{ formatDateTime(payment.processed_at) || '-' }}</span>
</div>
</el-col>
</el-row>
</div>
</div>
<div v-if="loadingPayments" style="text-align: center; padding: 20px;">
<i class="el-icon-loading"></i> 加载中...
</div>
</el-card>
<div v-if="/\/detail/.test($route.path) && config && config.flow">
<div v-if="config.flow.hasOwnProperty('out_contracts') && config.flow.out_contracts && config.flow.out_contracts.length > 0" style="margin-bottom: 10px;color:#F56C6C;">
<!-- 单条数据 -->
@ -332,6 +563,52 @@
frameborder="0"
/>
</vxe-modal>
<!-- 右侧抽屉流程详情 / 会议纪要 -->
<el-drawer
:visible.sync="rightDrawerVisible"
:title="rightDrawerTitle"
direction="rtl"
size="60%"
:with-header="true"
>
<!-- 流程详情iframe -->
<div v-if="rightDrawerType === 'flow'" style="width: 100%; height: 100%;">
<iframe
:src="rightDrawerUrl"
style="display: block; width: 100%; height: calc(100vh - 120px); border: 0;"
frameborder="0"
/>
</div>
<!-- 会议纪要请求数据展示 -->
<div v-else-if="rightDrawerType === 'meetingMinutes'" v-loading="loadingMeetingMinutes">
<div v-if="meetingMinutesDetail">
<el-descriptions :column="1" border>
<el-descriptions-item label="标题">
{{ meetingMinutesDetail.title || '-' }}
</el-descriptions-item>
<el-descriptions-item label="内容清单">
<div v-if="meetingMinutesDetail.items && meetingMinutesDetail.items.length">
<div v-for="(it, idx) in meetingMinutesDetail.items" :key="idx" style="margin-bottom: 8px;">
<span style="color:#409EFF;">{{ it.type || '类型' }}</span>
<span>{{ it.content || '-' }}</span>
</div>
</div>
<span v-else></span>
</el-descriptions-item>
<el-descriptions-item label="附件" v-if="meetingMinutesDetail.files_details && meetingMinutesDetail.files_details.length">
<div v-for="(f, idx) in meetingMinutesDetail.files_details" :key="idx" style="margin-bottom: 6px;">
<el-link type="primary" :underline="false" :href="f.url" target="_blank">
{{ f.original_name || f.name || '附件' }}
</el-link>
</div>
</el-descriptions-item>
</el-descriptions>
</div>
<div v-else style="color:#909399;">未获取到会议纪要数据</div>
</div>
</el-drawer>
</div>
</template>
@ -354,6 +631,8 @@ import {
updateNodeTime,
view,
} from "@/api/flow";
import { getPaymentsByFlowId, getPlannedExpenditure, getPlannedExpenditureTemplatesByCategory, getDetailTableFields, getOaFlowDetails } from "@/api/payment";
import { show as meetingMinutesShow } from "@/api/meetingMinutes";
import { deepCopy } from "@/utils";
import { validation, validationName } from "@/utils/validate";
import { print } from "@/utils/print";
@ -413,6 +692,23 @@ export default {
subRules: {},
flows: [],
csrf_token: '',
//
relatedPayments: [],
loadingPayments: false,
// (PlannedExpenditure)
indirectExpenditureMap: {}, // { [expenditureId]: PlannedExpenditureDetail }
indirectTemplateConfigMap: {}, // { [categoryId]: templateConfig }
indirectDetailTableFieldsMap: {}, // { [elementId]: columns[] }
oaFlowDetailsMap: {}, // { [flowId]: {display_name,no,title,custom_model_id,...} }
loadingIndirectExpenditures: false,
// /
rightDrawerVisible: false,
rightDrawerType: '', // 'flow' | 'meetingMinutes'
rightDrawerTitle: '',
rightDrawerUrl: '',
meetingMinutesDetail: null,
loadingMeetingMinutes: false,
};
},
watch:{
@ -677,6 +973,286 @@ export default {
hours > 0 ? hours + "时" : ""
}${minutes}分${seconds}`;
},
//
getPaymentStatusType(status) {
const statusMap = {
'pending': 'warning',
'processing': 'primary',
'completed': 'success',
'cancelled': 'danger',
};
return statusMap[status] || 'info';
},
//
formatAmount(amount) {
if (!amount && amount !== 0) return '0.00';
return parseFloat(amount).toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
//
formatDate(date) {
if (!date) return '';
return this.$moment(date).format('YYYY-MM-DD');
},
//
formatDateTime(dateTime) {
if (!dateTime) return '';
return this.$moment(dateTime).format('YYYY-MM-DD HH:mm:ss');
},
// /
formatBreadcrumb(paymentTypeInfo) {
if (!paymentTypeInfo || !paymentTypeInfo.breadcrumb) return '';
const bc = paymentTypeInfo.breadcrumb;
if (Array.isArray(bc)) {
return bc.join(' / ');
}
return bc;
},
// PlannedExpenditure &
async loadIndirectExpenditures(payment) {
this.indirectExpenditureMap = {};
this.indirectDetailTableFieldsMap = {};
this.oaFlowDetailsMap = {};
this.loadingIndirectExpenditures = true;
try {
const details = Array.isArray(payment?.details) ? payment.details : [];
const indirectDetails = details.filter(d => d && d.expenditure_type === 'indirect' && d.expenditure_id);
if (indirectDetails.length === 0) return;
// PlannedExpenditure
const ids = Array.from(new Set(indirectDetails.map(d => d.expenditure_id).filter(Boolean)));
const expenditureResults = await Promise.all(ids.map(id => getPlannedExpenditure(id).catch(() => null)));
expenditureResults.forEach(exp => {
if (exp && exp.id) {
this.$set(this.indirectExpenditureMap, exp.id, exp);
}
});
// category_id /
const categoryIds = Array.from(new Set(Object.values(this.indirectExpenditureMap).map(exp => exp.category_id).filter(Boolean)));
const tplResults = await Promise.all(categoryIds.map(cid => getPlannedExpenditureTemplatesByCategory(cid).catch(() => null)));
tplResults.forEach((tplList, idx) => {
const cid = categoryIds[idx];
// getByCategory 1
const firstTpl = Array.isArray(tplList) ? tplList[0] : null;
const config = firstTpl && firstTpl.config ? firstTpl.config : null;
this.$set(this.indirectTemplateConfigMap, cid, config || null);
});
// detail_table element_id
const detailTableElementIds = new Set();
categoryIds.forEach(cid => {
const fields = this.getIndirectTemplateFields(cid);
fields.forEach(f => {
if (f && f.element_type === 'detail_table' && f.element_id) {
detailTableElementIds.add(f.element_id);
}
});
});
const idsToLoad = Array.from(detailTableElementIds);
const colResults = await Promise.all(idsToLoad.map(id => getDetailTableFields(id).catch(() => [])));
colResults.forEach((cols, i) => {
const id = idsToLoad[i];
const arr = Array.isArray(cols) ? cols.slice() : [];
// field_name, field_key, sort_order
arr.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0));
this.$set(this.indirectDetailTableFieldsMap, id, arr);
});
// oa_custom_model ID
const flowIds = [];
Object.values(this.indirectExpenditureMap).forEach(exp => {
const bindings = Array.isArray(exp?.flow_bindings) ? exp.flow_bindings : [];
bindings.forEach(b => {
if (b && b.flow_instance_id) {
flowIds.push(b.flow_instance_id);
}
});
});
const uniqFlowIds = Array.from(new Set(flowIds.filter(Boolean)));
if (uniqFlowIds.length) {
try {
// request.js res.data map
const map = await getOaFlowDetails(uniqFlowIds);
if (map && typeof map === 'object') {
this.oaFlowDetailsMap = map;
}
} catch (e) {
this.oaFlowDetailsMap = {};
}
}
} finally {
this.loadingIndirectExpenditures = false;
}
},
getFlowDisplayName(flowId, fallback = '') {
const f = this.oaFlowDetailsMap?.[flowId];
if (!f) return fallback || (flowId ? `流程ID: ${flowId}` : '-');
return f.display_name || [f.no, f.title].filter(Boolean).join(' - ') || fallback || (flowId ? `流程ID: ${flowId}` : '-');
},
// config
getIndirectTemplateFields(categoryId) {
const cfg = this.indirectTemplateConfigMap?.[categoryId];
if (!cfg || typeof cfg !== 'object') return [];
const sections = Object.values(cfg);
const fields = [];
sections.forEach(sec => {
if (!sec) return;
if (Array.isArray(sec.fields)) {
sec.fields.forEach(f => f && fields.push(f));
}
if (Array.isArray(sec.rounds)) {
sec.rounds.forEach(r => {
if (r && Array.isArray(r.fields)) {
r.fields.forEach(f => f && fields.push(f));
}
});
}
});
return fields;
},
// element_values
getIndirectFieldValue(expenditureId, fieldKey) {
const exp = this.indirectExpenditureMap?.[expenditureId];
const map = exp?.element_values || {};
return map?.[fieldKey];
},
// oa_custom_model
getOaCustomModelBinding(expenditureId, fieldKey) {
const exp = this.indirectExpenditureMap?.[expenditureId];
const bindings = Array.isArray(exp?.flow_bindings) ? exp.flow_bindings : [];
return bindings.find(b => b && b.field_key === fieldKey) || null;
},
// oa_custom_model
hasOaCustomModelValue(expenditureId, field) {
//
if (this.getOaCustomModelBinding(expenditureId, field.key)) {
return true;
}
// ID
const fieldValue = this.getIndirectFieldValue(expenditureId, field.key);
if (this.extractFlowId(fieldValue) && (field.model_id || field.flow_custom_model_id)) {
return true;
}
return false;
},
extractFlowId(val) {
if (val === null || val === undefined) return null;
if (typeof val === 'number' && val > 0) return val;
if (typeof val === 'string' && /^\d+$/.test(val)) return parseInt(val, 10);
if (typeof val === 'object') {
const id = val.instance_id || val.flow_instance_id || val.id;
if (id && /^\d+$/.test(String(id))) return parseInt(String(id), 10);
}
return null;
},
extractDisplayName(val) {
if (!val) return '';
if (typeof val === 'string') return val;
if (typeof val === 'object') {
return val.display_name || val.name || val.title || '';
}
return '';
},
extractMeetingMinuteId(val) {
if (val === null || val === undefined) return null;
if (typeof val === 'number' && val > 0) return val;
if (typeof val === 'string' && /^\d+$/.test(val)) return parseInt(val, 10);
if (typeof val === 'object') {
const id = val.meeting_minute_id || val.id;
if (id && /^\d+$/.test(String(id))) return parseInt(String(id), 10);
}
return null;
},
openFlowDrawer({ flowId, customModelId, title }) {
if (!flowId || !customModelId) return;
this.rightDrawerType = 'flow';
this.rightDrawerTitle = title || `流程 ${flowId}`;
this.rightDrawerUrl = `/oa/#/flow/detail?module_id=${customModelId}&flow_id=${flowId}&isSinglePage=1&auth_token=${encodeURIComponent(getToken())}`;
this.rightDrawerVisible = true;
},
async openMeetingMinutesDrawer(meetingMinuteId) {
if (!meetingMinuteId) return;
this.rightDrawerType = 'meetingMinutes';
this.rightDrawerTitle = '查看会议纪要';
this.rightDrawerUrl = '';
this.meetingMinutesDetail = null;
this.rightDrawerVisible = true;
this.loadingMeetingMinutes = true;
try {
// meetingMinutesShow res.data
const detail = await meetingMinutesShow({ id: meetingMinuteId });
this.meetingMinutesDetail = detail || null;
this.rightDrawerTitle = detail?.title || '查看会议纪要';
} catch (e) {
console.error('加载会议纪要失败:', e);
this.meetingMinutesDetail = null;
} finally {
this.loadingMeetingMinutes = false;
}
},
//
formatReadonlyValue(val) {
if (val === undefined || val === null || val === '') return '-';
if (Array.isArray(val)) return val.join('');
if (typeof val === 'object') return JSON.stringify(val);
return String(val);
},
//
async loadRelatedPayments() {
if (!this.$route.query.flow_id) {
return;
}
this.loadingPayments = true;
try {
// request.js res.data payment null
const payment = await getPaymentsByFlowId(this.$route.query.flow_id, { all: true });
// (null)
if (payment) {
// breadcrumb
if (payment.payment_type_info && payment.payment_type_info.breadcrumb) {
const bc = payment.payment_type_info.breadcrumb;
if (Array.isArray(bc)) {
payment.payment_type_info.breadcrumb = bc;
} else if (typeof bc === 'string') {
payment.payment_type_info.breadcrumb = bc.split(/\s*>\s*|\s*\/\s*/).filter(Boolean);
}
}
this.relatedPayments = [payment];
// PlannedExpenditure
if (payment.payment_type_info && payment.payment_type_info.payment_type === 'indirect') {
await this.loadIndirectExpenditures(payment);
} else {
this.indirectExpenditureMap = {};
}
} else {
this.relatedPayments = [];
this.indirectExpenditureMap = {};
}
} catch (err) {
console.error('加载支付信息失败:', err);
this.relatedPayments = [];
this.indirectExpenditureMap = {};
} finally {
this.loadingPayments = false;
}
},
async getConfig() {
const loading = this.$loading({
@ -761,6 +1337,11 @@ export default {
}
this.form = Object.assign({}, this.form);
loading.close();
//
if (this.$route.query.flow_id) {
this.loadRelatedPayments();
}
} catch (err) {
console.error(err);
this.$message.error("配置失败");
@ -872,6 +1453,11 @@ export default {
}
this.form = Object.assign({}, this.form);
loading.close();
//
if (this.$route.query.flow_id) {
this.loadRelatedPayments();
}
} catch (err) {
console.error(err);
this.$message.error("配置失败");
@ -1210,6 +1796,34 @@ export default {
flex-wrap: wrap;
}
}
/* 支付信息展示样式 */
.payment-info {
.info-item {
margin-bottom: 8px;
.label {
color: #606266;
font-size: 14px;
margin-right: 8px;
}
.value {
color: #303133;
font-size: 14px;
}
}
}
/* 非直接支付相关信息样式 */
.indirect-payment-descriptions {
::v-deep .el-descriptions-item__label {
white-space: nowrap;
color: #303133;
font-weight: 500;
}
::v-deep .el-descriptions-item__content {
color: #303133;
}
}
</style>
<style lang="scss">
.log-table-scroll {

@ -182,3 +182,5 @@ A: 这是正常的Vue CLI使用内容哈希来生成文件名。只要HTML文

Loading…
Cancel
Save