流程关联会议纪要

master
weizong song 1 week ago
parent dad1afd3d3
commit 0eadaa96ab

@ -340,6 +340,15 @@ export function getRelationList(flowId, isLoading = false) {
})
}
export function getRelationMeetingMinuteOptions(params, isLoading = false) {
return request({
method: 'get',
url: '/api/oa/flow/relation-meeting-minute-options',
params,
isLoading
})
}
// 打捞流程至付款流程
export function salvageFlow(flowId, isLoading = true) {
return request({

@ -23,8 +23,20 @@
<!-- 搜索区域 -->
<div class="search-area" v-show="!collapsed && !readonly" @click.stop>
<el-row :gutter="10">
<el-col :span="4">
<el-select
v-model="relationCategory"
placeholder="关联类别"
style="width: 100%"
@change="handleCategoryChange"
>
<el-option label="流程" value="flow"></el-option>
<el-option label="会议纪要" value="meetingMinute"></el-option>
</el-select>
</el-col>
<el-col :span="6">
<el-select
v-if="relationCategory === 'flow'"
v-model="searchForm.custom_model_id"
placeholder="流程类型"
clearable
@ -46,7 +58,7 @@
<el-col :span="10">
<el-input
v-model="searchForm.keyword"
placeholder="输入标题或编号搜索"
:placeholder="relationCategory === 'flow' ? '输入标题或编号搜索' : '输入会议纪要标题搜索'"
clearable
@keyup.enter.native="handleSearch"
>
@ -86,8 +98,30 @@
</div>
</div>
<div class="selected-flows" v-show="!collapsed && selectedMeetingMinutes.length > 0">
<div class="selected-title">已关联的会议纪要{{ selectedMeetingMinutes.length }}</div>
<div class="selected-list">
<div v-for="item in selectedMeetingMinutes" :key="`mm-${item.id}`" class="selected-flow-block">
<el-tag
:closable="!readonly"
@close="handleRemoveMeetingMinute(item.id)"
@click.native.stop="$emit('open-meeting-minute', item.id)"
class="flow-tag"
type="warning"
effect="plain"
>
<span class="flow-title">{{ item.title || ('会议纪要' + item.id) }}</span>
</el-tag>
<div class="flow-meeting-minutes">
<span class="mm-label">创建时间</span>
<span>{{ formatDate(item.created_at) }}</span>
</div>
</div>
</div>
</div>
<!-- 可选流程列表 -->
<div class="available-flows" v-show="!collapsed && !readonly">
<div class="available-flows" v-show="!collapsed && !readonly && relationCategory === 'flow'">
<div class="available-title">可选流程</div>
<div v-loading="loading" class="flow-list">
<el-empty v-if="!loading && availableFlows.length === 0" description="暂无可用流程"></el-empty>
@ -161,6 +195,68 @@
</div>
</div>
<div class="available-flows" v-show="!collapsed && !readonly && relationCategory === 'meetingMinute'">
<div class="available-title">可选会议纪要</div>
<div v-loading="loading" class="flow-list">
<el-empty v-if="!loading && availableMeetingMinutes.length === 0" description="暂无可用会议纪要"></el-empty>
<div
v-for="item in availableMeetingMinutes"
:key="`meeting-minute-${item.id}`"
class="flow-item"
:class="{ 'is-selected': isMeetingMinuteSelected(item.id) }"
@click="handleToggleMeetingMinute(item)"
>
<div class="flow-content">
<div class="flow-header">
<span class="flow-title-text">{{ item.title || ('会议纪要' + item.id) }}</span>
<el-tag size="mini" type="warning">会议纪要</el-tag>
</div>
<div class="flow-info">
<span class="flow-no">ID{{ item.id }}</span>
<span class="flow-date">{{ formatDate(item.created_at) }}</span>
</div>
</div>
<div class="flow-actions">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click.stop="$emit('open-meeting-minute', item.id)"
>
查看
</el-button>
<el-button
v-if="!isMeetingMinuteSelected(item.id)"
size="mini"
type="primary"
@click.stop="handleToggleMeetingMinute(item)"
>
关联
</el-button>
<el-button
v-else
size="mini"
type="danger"
@click.stop="handleToggleMeetingMinute(item)"
>
取消
</el-button>
</div>
</div>
</div>
<div class="pagination" v-if="meetingMinutePagination.total > 0">
<el-pagination
@current-change="handleMeetingMinutePageChange"
:current-page="meetingMinutePagination.page"
:page-size="meetingMinutePagination.page_size"
:total="meetingMinutePagination.total"
layout="total, prev, pager, next"
small
></el-pagination>
</div>
</div>
<!-- 只读模式显示关联流程 -->
<div class="readonly-flows" v-show="readonly">
<div v-if="relatedFlows.length > 0" class="related-section">
@ -203,8 +299,19 @@
</div>
</div>
</div>
<div v-if="readonly && relatedFlows.length === 0 && relatedByFlows.length === 0" class="empty-tip">
<span class="empty-text">暂无关联流程</span>
<div v-if="relatedMeetingMinutes.length > 0" class="related-section">
<div class="section-title">关联的会议纪要{{ relatedMeetingMinutes.length }}</div>
<div class="related-list">
<div v-for="item in relatedMeetingMinutes" :key="`readonly-mm-${item.id}`" class="related-item">
<div class="related-item-flow" @click="$emit('open-meeting-minute', item.id)">
<span class="flow-title-text">{{ item.title || ('会议纪要' + item.id) }}</span>
<span class="flow-meta">ID: {{ item.id }}</span>
</div>
</div>
</div>
</div>
<div v-if="readonly && relatedFlows.length === 0 && relatedByFlows.length === 0 && relatedMeetingMinutes.length === 0" class="empty-tip">
<span class="empty-text">暂无关联项</span>
</div>
</div>
</el-card>
@ -228,7 +335,7 @@
</template>
<script>
import { getRelationOptions, getRelationList, flowList, flow as flowIndex } from '@/api/flow'
import { getRelationMeetingMinuteOptions, getRelationOptions, getRelationList, flowList, flow as flowIndex } from '@/api/flow'
import { getToken } from '@/utils/auth'
import moment from 'moment'
@ -247,6 +354,10 @@ export default {
type: [Number, String],
default: null
},
meetingMinuteValue: {
type: [String, Array],
default: () => []
},
collapsible: {
type: Boolean,
default: true
@ -256,20 +367,29 @@ export default {
return {
collapsed: this.readonly ? false : true, // readonly
loading: false,
relationCategory: 'flow',
searchForm: {
keyword: '',
custom_model_id: ''
},
availableFlows: [],
availableMeetingMinutes: [],
selectedFlows: [],
selectedMeetingMinutes: [],
relatedFlows: [],
relatedByFlows: [],
relatedMeetingMinutes: [],
customModels: [],
pagination: {
page: 1,
page_size: 20,
total: 0
},
meetingMinutePagination: {
page: 1,
page_size: 20,
total: 0
},
showFlowDetail: false,
currentFlow: null,
flowDetailUrl: ''
@ -290,6 +410,19 @@ export default {
}
}
},
meetingMinuteValue: {
immediate: true,
handler(newVal) {
if (newVal) {
const ids = Array.isArray(newVal) ? newVal : newVal.toString().split(',').filter(Boolean).map(Number)
if (ids.length > 0 && this.selectedMeetingMinutes.length === 0) {
this.loadSelectedMeetingMinutes(ids)
}
} else {
this.selectedMeetingMinutes = []
}
}
},
readonly: {
immediate: true,
handler(newVal) {
@ -322,7 +455,17 @@ export default {
mounted() {
this.loadCustomModels()
},
methods: {
methods: {
handleCategoryChange() {
this.searchForm.keyword = ''
this.searchForm.custom_model_id = ''
this.pagination.page = 1
this.meetingMinutePagination.page = 1
if (this.collapsed) {
return
}
this.handleSearch()
},
handleToggleCollapse(e) {
if (e) {
e.stopPropagation()
@ -347,10 +490,10 @@ export default {
const res = await flowList('all', { page: 1, page_size: 1, is_simple: 1 })
// customModels res res.data
if (res.customModels && res.customModels.length > 0) {
this.customModels = res.customModels
this.customModels = (res.customModels || []).filter(model => !model.url)
return
} else if (res.data && res.data.customModels) {
this.customModels = res.data.customModels
this.customModels = (res.data.customModels || []).filter(model => !model.url)
return
}
} catch (err) {
@ -369,7 +512,7 @@ export default {
models.push(...cate.customerModels)
}
})
this.customModels = models.map(m => ({ id: m.id, name: m.name }))
this.customModels = models.filter(m => !m.url).map(m => ({ id: m.id, name: m.name, url: m.url }))
}
} catch (e) {
console.error('备用加载流程类型失败', e)
@ -377,6 +520,9 @@ export default {
}
},
async handleSearch() {
if (this.relationCategory === 'meetingMinute') {
return this.handleMeetingMinuteSearch()
}
this.loading = true
try {
const params = {
@ -431,6 +577,40 @@ export default {
console.error('加载已选流程失败', err)
}
},
async handleMeetingMinuteSearch() {
this.loading = true
try {
const res = await getRelationMeetingMinuteOptions({
page: this.meetingMinutePagination.page,
page_size: this.meetingMinutePagination.page_size,
keyword: this.searchForm.keyword
})
if (res) {
this.availableMeetingMinutes = res.data || []
this.meetingMinutePagination.total = res.total || 0
this.meetingMinutePagination.page = res.page || 1
this.meetingMinutePagination.page_size = res.page_size || 20
}
} catch (err) {
this.$message.error('搜索失败:' + (err.message || '未知错误'))
} finally {
this.loading = false
}
},
async loadSelectedMeetingMinutes(ids) {
if (!ids || ids.length === 0) {
this.selectedMeetingMinutes = []
return
}
try {
const res = await getRelationMeetingMinuteOptions({
ids: ids.join(',')
})
this.selectedMeetingMinutes = res.data || []
} catch (err) {
console.error('加载已选会议纪要失败', err)
}
},
async loadRelationList() {
if (!this.flowId) return
try {
@ -439,6 +619,7 @@ export default {
if (res) {
this.relatedFlows = res.related_flows || []
this.relatedByFlows = res.related_by_flows || []
this.relatedMeetingMinutes = res.related_meeting_minutes || []
}
} catch (err) {
console.error('加载关联列表失败', err)
@ -460,18 +641,42 @@ export default {
this.selectedFlows = this.selectedFlows.filter(f => f.id !== flowId)
this.updateValue()
},
handleToggleMeetingMinute(item) {
if (this.isMeetingMinuteSelected(item.id)) {
this.handleRemoveMeetingMinute(item.id)
} else {
this.selectedMeetingMinutes.push(item)
this.updateMeetingMinuteValue()
}
},
handleRemoveMeetingMinute(id) {
this.selectedMeetingMinutes = this.selectedMeetingMinutes.filter(item => item.id !== id)
this.updateMeetingMinuteValue()
},
isSelected(flowId) {
return this.selectedFlows.some(f => f.id === flowId)
},
isMeetingMinuteSelected(id) {
return this.selectedMeetingMinutes.some(item => item.id === id)
},
updateValue() {
const ids = this.selectedFlows.map(f => f.id)
this.$emit('input', ids.join(','))
this.$emit('change', ids)
},
updateMeetingMinuteValue() {
const ids = this.selectedMeetingMinutes.map(item => item.id)
this.$emit('update:meetingMinuteValue', ids.join(','))
this.$emit('meeting-minute-change', ids)
},
handlePageChange(page) {
this.pagination.page = page
this.handleSearch()
},
handleMeetingMinutePageChange(page) {
this.meetingMinutePagination.page = page
this.handleMeetingMinuteSearch()
},
handleViewFlow(flow) {
this.currentFlow = flow
const baseUrl = process.env.VUE_APP_BASE_API || ''
@ -799,4 +1004,3 @@ export default {
}
}
</style>

@ -19,6 +19,7 @@
<!-- 关联流程组件 -->
<RelatedFlows
v-model="form.related_flow_ids"
:meeting-minute-value.sync="form.related_meeting_minute_ids"
:readonly="/\/detail/.test($route.path) || (!!$route.query.flow_id && !isFirstNode)"
:flow-id="$route.query.flow_id"
:collapsible="(!/\/detail/.test($route.path)) && (!$route.query.flow_id || isFirstNode)"
@ -1626,6 +1627,9 @@ export default {
if (!object.hasOwnProperty('related_flow_ids')) {
object['related_flow_ids'] = '';
}
if (!object.hasOwnProperty('related_meeting_minute_ids')) {
object['related_meeting_minute_ids'] = '';
}
// fill_flow_title=1
const fillFlowTitleField = (fields || []).find(f =>
@ -2445,6 +2449,10 @@ export default {
const relatedIds = res.flow.related_flows.map(f => f.id).join(',');
this.form.related_flow_ids = relatedIds;
}
if (res?.flow?.related_meeting_minutes && res.flow.related_meeting_minutes.length > 0) {
const relatedMeetingMinuteIds = res.flow.related_meeting_minutes.map(item => item.id).join(',');
this.form.related_meeting_minute_ids = relatedMeetingMinuteIds;
}
// form
const { data } = res?.flow;
for (let key in data) {
@ -2567,6 +2575,10 @@ export default {
const relatedIds = res.flow.related_flows.map(f => f.id).join(',');
this.form.related_flow_ids = relatedIds;
}
if (res?.flow?.related_meeting_minutes && res.flow.related_meeting_minutes.length > 0) {
const relatedMeetingMinuteIds = res.flow.related_meeting_minutes.map(item => item.id).join(',');
this.form.related_meeting_minute_ids = relatedMeetingMinuteIds;
}
this.handleDefaultJSON();
const { data } = res?.flow;
for (let key in data) {
@ -2674,7 +2686,7 @@ export default {
let myField = this.fields.find(i => i.name === key)
// ID使writeableFields
if (key === 'related_flow_ids') {
if (key === 'related_flow_ids' || key === 'related_meeting_minute_ids') {
continue;
}
@ -2703,8 +2715,10 @@ export default {
// related_flow_ids
// 便
copyForm.related_flow_ids = this.form.related_flow_ids || ''
copyForm.related_meeting_minute_ids = this.form.related_meeting_minute_ids || ''
console.log("copyForm",copyForm,this.$refs["desktopForm"].form)
console.log("related_flow_ids:", copyForm.related_flow_ids, "form.related_flow_ids:", this.form.related_flow_ids)
console.log("related_meeting_minute_ids:", copyForm.related_meeting_minute_ids, "form.related_meeting_minute_ids:", this.form.related_meeting_minute_ids)
// return
if (this.$route.query.flow_id) {
copyForm.id = this.$route.query.flow_id;

Loading…
Cancel
Save