weizong song 4 weeks ago
parent f5e6288840
commit 4c4bcaf37e

@ -260,8 +260,49 @@
</el-tree>
</div>
<div v-if="tradeUnionTree.length" class="tree-block" style="margin-top: 16px">
<div class="tree-title">工会</div>
<el-tree
ref="tradeUnionTreeRef"
:data="tradeUnionTree"
node-key="id"
:props="treeProps"
highlight-current
default-expand-all
:expand-on-click-node="false"
show-checkbox
check-strictly
:check-on-click-node="false"
@check-change="onTreeCheckChange"
@node-click="onNodeClickToggle"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span class="node-label" :class="{ 'node-label--clickable': data.is_leaf }" @click.stop="onLabelClick(data, node, $refs.tradeUnionTreeRef)">{{ node.label }}</span>
<span v-if="data.is_leaf" class="node-usage-info">
<span class="usage-item">
<span class="usage-label">预算:</span>
<span class="usage-value">{{ formatAmount(data.budget_amount) }}</span>
</span>
<span class="usage-item">
<span class="usage-label">已用:</span>
<span class="usage-value usage-value--used">{{ formatAmount(data.used_amount || 0) }}</span>
</span>
<span class="usage-item">
<span class="usage-label">执行率:</span>
<span
class="usage-value usage-value--rate"
:class="getExecutionRateClass(data.execution_rate || 0)"
>
{{ formatExecutionRate(data.execution_rate || 0) }}
</span>
</span>
</span>
</span>
</el-tree>
</div>
<div
v-if="!treeLoading && departmentTree.length === 0 && projectTree.length === 0 && specialFundTree.length === 0 && lastYearCarryoverTree.length === 0 && offsetPrepaidTree.length === 0"
v-if="!treeLoading && departmentTree.length === 0 && projectTree.length === 0 && specialFundTree.length === 0 && lastYearCarryoverTree.length === 0 && offsetPrepaidTree.length === 0 && tradeUnionTree.length === 0"
style="text-align: center; color: #909399; padding: 32px 0"
>
暂无数据
@ -506,6 +547,9 @@ export default {
offsetPrepaidTree() {
return (this.treeData || []).filter((n) => n?.budget_type === "offset_prepaid");
},
tradeUnionTree() {
return (this.treeData || []).filter((n) => n?.budget_type === "trade_union");
},
budgetTypeMap() {
return {
department: "部门预算",
@ -513,6 +557,7 @@ export default {
special_fund: "专项资金",
last_year_carryover: "上一年结转资金",
offset_prepaid: "抵消预付账款",
trade_union: "工会",
};
},
//
@ -670,6 +715,7 @@ export default {
this.$refs.specialFundTreeRef,
this.$refs.lastYearCarryoverTreeRef,
this.$refs.offsetPrepaidTreeRef,
this.$refs.tradeUnionTreeRef,
].filter(Boolean);
const nodes = [];
refs.forEach((tree) => {
@ -693,6 +739,7 @@ export default {
this.$refs.specialFundTreeRef,
this.$refs.lastYearCarryoverTreeRef,
this.$refs.offsetPrepaidTreeRef,
this.$refs.tradeUnionTreeRef,
].filter(Boolean);
refs.forEach((tree) => {
if (typeof tree.setCheckedKeys === "function") {

@ -99,15 +99,18 @@
<el-option
v-for="flow in availableFlows"
:key="flow.id"
:label="(flow.title || flow.no || ('流程' + flow.id)) + ' (' + (flow.no || '') + ')'"
:label="(flow.title || flow.no || ('流程' + flow.id)) + (flow.meeting_minutes_count > 0 ? ' 【已关联 ' + flow.meeting_minutes_count + ' 次】' : '') + ' (' + (flow.no || '') + ')'"
:value="flow.id"
>
<span style="float: left">{{ flow.title || flow.no || ('流程' + flow.id) }}</span>
<span style="float: left">
{{ flow.title || flow.no || ('流程' + flow.id) }}
<span v-if="flow.meeting_minutes_count > 0" style="margin-left: 4px; color: #333;"> {{ flow.meeting_minutes_count }} </span>
</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ flow.no || '' }}</span>
</el-option>
</el-select>
<div style="margin-top: 8px; color: #909399; font-size: 12px;">
提示可以关联多个上会审议审批流程
提示可以关联多个上会审议审批流程同一条流程可被多个会议纪要关联
</div>
</el-form-item>
<el-form-item label="附件上传" prop="files">
@ -140,7 +143,7 @@
placeholder="请选择类型"
style="width: 100%;"
>
<el-option label="""" value=""""></el-option>
<el-option label="“三重一大”事项" value="“三重一大”事项"></el-option>
<el-option label="资金申请" value="资金申请"></el-option>
<el-option label="资金支付" value="资金支付"></el-option>
<el-option label="其他" value="其他"></el-option>
@ -315,12 +318,7 @@ export default {
return //
}
try {
const params = {}
// ID
if (this.type === 'edit' && this.form.id) {
params.meeting_minute_id = this.form.id
}
const res = await getFlows(params)
const res = await getFlows({})
this.availableFlows = res || []
} catch (err) {
console.error('加载流程列表失败:', err)

@ -63,19 +63,26 @@
<div class="selected-flows" v-show="!collapsed && selectedFlows.length > 0">
<div class="selected-title">已关联的流程{{ selectedFlows.length }}</div>
<div class="selected-list">
<el-tag
v-for="flow in selectedFlows"
:key="flow.id"
:closable="!readonly"
@close="handleRemove(flow.id)"
@click="handleViewFlow(flow)"
class="flow-tag"
type="info"
effect="plain"
>
<span class="flow-title">{{ flow.title }}</span>
<span class="flow-meta">{{ flow.no }}</span>
</el-tag>
<div v-for="flow in selectedFlows" :key="flow.id" class="selected-flow-block">
<el-tag
:closable="!readonly"
@close="handleRemove(flow.id)"
@click="handleViewFlow(flow)"
class="flow-tag"
type="info"
effect="plain"
>
<span class="flow-title">{{ flow.title }}</span>
<span class="flow-meta">{{ flow.no }}</span>
</el-tag>
<div v-if="flow.meeting_minutes && flow.meeting_minutes.length" class="flow-meeting-minutes">
<span class="mm-label">会议纪要</span>
<span v-for="(mm, idx) in (flow.meeting_minutes || [])" :key="mm.id" class="mm-link-wrap">
<el-link type="primary" size="mini" @click.stop="$emit('open-meeting-minute', mm.id)">{{ mm.title }}</el-link>
<span v-if="idx < (flow.meeting_minutes.length - 1)" class="mm-sep"></span>
</span>
</div>
</div>
</div>
</div>
@ -104,6 +111,13 @@
<span class="flow-creator">发起人{{ flow.creator_name }}</span>
<span class="flow-date">{{ formatDate(flow.created_at) }}</span>
</div>
<div v-if="flow.meeting_minutes && flow.meeting_minutes.length" class="flow-meeting-minutes">
<span class="mm-label">会议纪要</span>
<span v-for="(mm, idx) in (flow.meeting_minutes || [])" :key="mm.id" class="mm-link-wrap">
<el-link type="primary" size="mini" @click.stop="$emit('open-meeting-minute', mm.id)">{{ mm.title }}</el-link>
<span v-if="idx < (flow.meeting_minutes.length - 1)" class="mm-sep"></span>
</span>
</div>
</div>
<div class="flow-actions">
<el-button
@ -152,28 +166,40 @@
<div v-if="relatedFlows.length > 0" class="related-section">
<div class="section-title">关联的流程{{ relatedFlows.length }}</div>
<div class="related-list">
<div
v-for="flow in relatedFlows"
:key="flow.id"
class="related-item"
@click="handleViewFlow(flow)"
>
<span class="flow-title-text">{{ flow.title }}</span>
<span class="flow-meta">{{ flow.no }} - {{ flow.custom_model_name }}</span>
<div v-for="flow in relatedFlows" :key="flow.id" class="related-item">
<div class="related-item-flow" @click="handleViewFlow(flow)">
<span class="flow-title-text">{{ flow.title }}</span>
<span class="flow-meta">{{ flow.no }} - {{ flow.custom_model_name }}</span>
</div>
<div v-if="flow.meeting_minutes && flow.meeting_minutes.length" class="related-item-meeting-minutes">
<span class="mm-label">会议纪要</span>
<span
v-for="mm in (flow.meeting_minutes || [])"
:key="mm.id"
class="mm-block"
@click="$emit('open-meeting-minute', mm.id)"
>{{ mm.title }}</span>
</div>
</div>
</div>
</div>
<div v-if="relatedByFlows.length > 0" class="related-section">
<div class="section-title">被关联的流程{{ relatedByFlows.length }}</div>
<div class="related-list">
<div
v-for="flow in relatedByFlows"
:key="flow.id"
class="related-item"
@click="handleViewFlow(flow)"
>
<span class="flow-title-text">{{ flow.title }}</span>
<span class="flow-meta">{{ flow.no }} - {{ flow.custom_model_name }}</span>
<div v-for="flow in relatedByFlows" :key="flow.id" class="related-item">
<div class="related-item-flow" @click="handleViewFlow(flow)">
<span class="flow-title-text">{{ flow.title }}</span>
<span class="flow-meta">{{ flow.no }} - {{ flow.custom_model_name }}</span>
</div>
<div v-if="flow.meeting_minutes && flow.meeting_minutes.length" class="related-item-meeting-minutes">
<span class="mm-label">会议纪要</span>
<span
v-for="mm in (flow.meeting_minutes || [])"
:key="mm.id"
class="mm-block"
@click="$emit('open-meeting-minute', mm.id)"
>{{ mm.title }}</span>
</div>
</div>
</div>
</div>
@ -397,7 +423,8 @@ export default {
status_text: this.getStatusText(flow.status),
created_by: flow.created_by,
creator_name: flow.creator ? flow.creator.name : (flow.creator_name || ''),
created_at: flow.created_at
created_at: flow.created_at,
meeting_minutes: flow.meeting_minutes || []
}))
}
} catch (err) {
@ -536,6 +563,13 @@ export default {
flex-wrap: wrap;
gap: 8px;
.selected-flow-block {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.flow-tag {
cursor: pointer;
padding: 5px 10px;
@ -554,6 +588,36 @@ export default {
background-color: #ecf5ff;
}
}
.flow-meeting-minutes {
font-size: 12px;
color: #606266;
padding-left: 4px;
.mm-label {
color: #909399;
margin-right: 2px;
}
.mm-sep {
margin: 0 2px;
color: #909399;
}
}
}
}
.flow-meeting-minutes {
font-size: 12px;
color: #606266;
margin-top: 6px;
.mm-label {
color: #909399;
margin-right: 4px;
}
.mm-sep {
margin: 0 2px;
color: #909399;
}
}
@ -644,26 +708,63 @@ export default {
.related-list {
.related-item {
padding: 8px 12px;
padding: 10px 12px;
margin-bottom: 8px;
background: #f5f7fa;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #ecf5ff;
}
.related-item-flow {
display: block;
padding: 6px 10px;
margin: -4px -6px 8px -6px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: #ecf5ff;
}
.flow-title-text {
color: #409eff;
font-weight: 500;
.flow-title-text {
color: #409eff;
font-weight: 500;
}
.flow-meta {
color: #909399;
font-size: 12px;
margin-left: 8px;
}
}
.flow-meta {
color: #909399;
font-size: 12px;
margin-left: 8px;
.related-item-meeting-minutes {
margin-top: 4px;
.mm-label {
display: inline-block;
color: #909399;
font-size: 12px;
margin-right: 8px;
vertical-align: middle;
}
.mm-block {
display: inline-block;
padding: 4px 10px;
margin: 2px 6px 2px 0;
color: #409eff;
font-size: 13px;
background: #ecf5ff;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: #d9ecff;
color: #66b1ff;
}
}
}
}
}

@ -23,6 +23,7 @@
:flow-id="$route.query.flow_id"
:collapsible="(!/\/detail/.test($route.path)) && (!$route.query.flow_id || isFirstNode)"
style="margin-bottom: 20px;"
@open-meeting-minute="openMeetingMinutesDrawer"
></RelatedFlows>
<!-- 关联的支付信息查看/办理只要有 flow_id 就展示没数据时展示空态 -->
@ -136,16 +137,20 @@
</div>
</div>
<!-- 会议纪要点击抽屉查看 -->
<!-- 会议纪要点击抽屉查看一对多多条链接 -->
<div v-else-if="el.type === 'meeting_minutes'">
<el-link
v-if="extractMeetingMinuteId(getPaymentTemplateElementValue(el, payment))"
type="primary"
:underline="false"
@click="openMeetingMinutesDrawer(extractMeetingMinuteId(getPaymentTemplateElementValue(el, payment)))"
>
{{ (getPaymentTemplateElementValue(el, payment) && getPaymentTemplateElementValue(el, payment).title) ? getPaymentTemplateElementValue(el, payment).title : '查看会议纪要' }}
</el-link>
<template v-if="getPaymentMeetingMinutesList(el, payment).length">
<span v-for="(item, idx) in getPaymentMeetingMinutesList(el, payment)" :key="item.id">
<el-link
type="primary"
:underline="false"
@click="openMeetingMinutesDrawer(item.id)"
>
{{ item.title || '查看会议纪要' }}
</el-link>
<span v-if="idx < getPaymentMeetingMinutesList(el, payment).length - 1"></span>
</span>
</template>
<span v-else>-</span>
</div>
@ -305,16 +310,20 @@
<span v-if="getPlannedFileItems(expId, f).length === 0">-</span>
</template>
<!-- meeting_minutes点击抽屉查看 -->
<!-- meeting_minutes点击抽屉查看一对多多条链接 -->
<template v-else-if="f.element_type === 'meeting_minutes'">
<el-link
v-if="extractMeetingMinuteId(getIndirectFieldValue(expId, getTplFieldKey(f)))"
type="primary"
:underline="false"
@click="openMeetingMinutesDrawer(extractMeetingMinuteId(getIndirectFieldValue(expId, getTplFieldKey(f))))"
>
查看会议纪要
</el-link>
<template v-if="getIndirectMeetingMinutesList(expId, getTplFieldKey(f)).length">
<span v-for="(item, idx) in getIndirectMeetingMinutesList(expId, getTplFieldKey(f))" :key="item.id">
<el-link
type="primary"
:underline="false"
@click="openMeetingMinutesDrawer(item.id)"
>
{{ item.title || '查看会议纪要' }}
</el-link>
<span v-if="idx < getIndirectMeetingMinutesList(expId, getTplFieldKey(f)).length - 1"></span>
</span>
</template>
<span v-else>-</span>
</template>
@ -782,28 +791,54 @@
/>
</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 label="会议纪要标题">
<span style="font-weight: 500;">{{ meetingMinutesDetail.title || '-' }}</span>
</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>
<el-descriptions-item label="关联上会审议流程">
<div v-if="meetingMinutesDetail.flows && meetingMinutesDetail.flows.length">
<el-tag
v-for="flow in meetingMinutesDetail.flows"
:key="flow.id"
type="info"
style="margin-right: 8px; margin-bottom: 4px;"
>
{{ flow.title || flow.no || ('流程' + flow.id) }}
<span v-if="flow.no" style="color: #909399; margin-left: 4px;">({{ flow.no }})</span>
</el-tag>
</div>
<span v-else style="color: #909399;">未关联</span>
</el-descriptions-item>
<el-descriptions-item label="附件">
<div v-if="meetingMinutesDetail.files_details && meetingMinutesDetail.files_details.length">
<div v-for="(f, idx) in meetingMinutesDetail.files_details" :key="idx" style="margin-bottom: 8px;">
<el-link type="primary" :underline="false" :href="f.url" target="_blank" style="margin-right: 10px;">
<i class="el-icon-view"></i> {{ f.original_name || f.name || '附件' }}
</el-link>
<el-link type="primary" :underline="false" :href="f.url" target="_blank" download>
<i class="el-icon-download"></i> 下载
</el-link>
</div>
</div>
<span v-else></span>
<span v-else style="color: #909399;">无附件</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>
<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: 12px; padding: 12px; background-color: #f5f7fa; border-radius: 4px;"
>
<div style="margin-bottom: 4px;">
<el-tag size="small" type="primary">{{ it.type || '未分类' }}</el-tag>
</div>
<div style="color: #606266; line-height: 1.6;">{{ it.content || '-' }}</div>
</div>
</div>
<span v-else style="color: #909399;">无内容清单</span>
</el-descriptions-item>
</el-descriptions>
</div>
@ -962,12 +997,9 @@ export default {
return this.getOaCustomModelBindings(expenditureId, field).length > 0;
}
// meeting_minutes ID
// meeting_minutes
if (field.element_type === 'meeting_minutes') {
const meetingMinuteId = this.extractMeetingMinuteId(
this.getIndirectFieldValue(expenditureId, this.getTplFieldKey(field))
);
return meetingMinuteId !== null && meetingMinuteId !== undefined;
return this.getIndirectMeetingMinutesList(expenditureId, this.getTplFieldKey(field)).length > 0;
}
// attachment/file
@ -1049,6 +1081,9 @@ export default {
if (el.type === 'form_element' && el.field_type === 'attachment') {
return Array.isArray(val) && val.length > 0;
}
if (el.type === 'meeting_minutes') {
return this.normalizeMeetingMinutesValue(val).length > 0;
}
return !this.isEmptyValue(val);
});
},
@ -1894,6 +1929,33 @@ export default {
return null;
},
/** 将会议纪要值归一化为 [{ id, title }, ...],兼容单条与多条 */
normalizeMeetingMinutesValue(val) {
if (val === null || val === undefined) return [];
if (Array.isArray(val)) {
return val
.map((item) => {
const id = this.extractMeetingMinuteId(item);
if (!id) return null;
const title = (item && typeof item === 'object' && item.title) ? item.title : '';
return { id, title };
})
.filter(Boolean);
}
const id = this.extractMeetingMinuteId(val);
if (!id) return [];
const title = (val && typeof val === 'object' && val.title) ? val.title : '';
return [{ id, title }];
},
getPaymentMeetingMinutesList(el, payment) {
return this.normalizeMeetingMinutesValue(this.getPaymentTemplateElementValue(el, payment));
},
getIndirectMeetingMinutesList(expenditureId, fieldKey) {
return this.normalizeMeetingMinutesValue(this.getIndirectFieldValue(expenditureId, fieldKey));
},
async openFlowDrawer({ flowId, customModelId, title }) {
const fid = Number(flowId);
if (!fid || Number.isNaN(fid)) return;
@ -1934,7 +1996,7 @@ export default {
this.loadingMeetingMinutes = true;
try {
// meetingMinutesShow res.data
const detail = await meetingMinutesShow({ id: meetingMinuteId });
const detail = await meetingMinutesShow({ id: meetingMinuteId, show_relation: ['flows', 'items'] });
this.meetingMinutesDetail = detail || null;
this.rightDrawerTitle = detail?.title || '查看会议纪要';
} catch (e) {

@ -573,18 +573,6 @@
@click="destroy(row)"
>删除</el-button
>
<el-button
v-if="
$route.params.type === 'handled' &&
row.custom_model_id === 99 &&
row.status === 1 &&
row.fund_log_is_end === 0
"
type="primary"
size="mini"
@click="toOutPay(row)"
>支付</el-button
>
</template>
<template v-else>
<el-button
@ -891,12 +879,6 @@ export default {
console.error(err);
}
},
//
async toOutPay(row) {
// this.$refs.payMx.payId = row.id;
this.$refs.payMx.payList = [row];
this.isShowPay = true;
},
//
multiPay() {
const records = this.$refs["table"]?.getCheckboxRecords(true) || [];

Loading…
Cancel
Save