<template>
|
<el-dialog v-model="visible" :title="operationType === 'add' ? `新增${typeName}` : `编辑${typeName}`" width="760px" @close="closeDialog">
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
<el-form-item label="流程编号">
|
<el-input v-model="form.approveId" disabled placeholder="提交后自动生成" />
|
</el-form-item>
|
|
<el-form-item v-if="approveTypeNumber === 3" label="销售合同" prop="businessId">
|
<el-select v-model="form.businessId" filterable placeholder="请选择销售合同" style="width: 100%" @change="handleContractChange">
|
<el-option v-for="item in contractOptions" :key="item.id" :value="item.id"
|
:label="`${item.salesContractNo || '-'} / ${item.customerName || '-'} / ¥${item.contractAmount || 0}`" />
|
</el-select>
|
</el-form-item>
|
|
<el-form-item v-if="approveTypeNumber === 3 && selectedContract" label="合同信息">
|
<el-descriptions :column="2" border style="width: 100%">
|
<el-descriptions-item label="客户">{{ selectedContract.customerName || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="项目">{{ selectedContract.projectName || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="业务员">{{ selectedContract.salesman || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="合同金额">¥{{ selectedContract.contractAmount || 0 }}</el-descriptions-item>
|
</el-descriptions>
|
</el-form-item>
|
|
<el-form-item v-if="[1, 5].includes(approveTypeNumber)" label="审批标题" prop="businessTitle">
|
<el-input v-model="form.businessTitle" maxlength="200" show-word-limit />
|
</el-form-item>
|
|
<template v-if="approveTypeNumber === 1">
|
<el-form-item label="事项类型" prop="category">
|
<el-select v-model="form.category" style="width: 100%">
|
<el-option label="临时费用报销" value="临时费用报销" />
|
<el-option label="部门协作" value="部门协作" />
|
<el-option label="其他" value="其他" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="涉及金额">
|
<el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%" />
|
</el-form-item>
|
</template>
|
|
<template v-if="approveTypeNumber === 5">
|
<el-row :gutter="16">
|
<el-col :span="12">
|
<el-form-item label="采购类别" prop="category"><el-input v-model="form.category" /></el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="紧急程度" prop="urgency">
|
<el-select v-model="form.urgency" style="width: 100%">
|
<el-option label="普通" value="普通" /><el-option label="紧急" value="紧急" /><el-option label="特急" value="特急" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="采购内容" prop="purchaseContent"><el-input v-model="form.purchaseContent" type="textarea" /></el-form-item>
|
<el-row :gutter="16">
|
<el-col :span="8"><el-form-item label="数量" prop="quantity"><el-input-number v-model="form.quantity" :min="0.01" :precision="2" style="width: 100%" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="单位" prop="unit"><el-input v-model="form.unit" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="预算金额" prop="amount"><el-input-number v-model="form.amount" :min="0.01" :precision="2" style="width: 100%" /></el-form-item></el-col>
|
</el-row>
|
<el-form-item label="期望到货日期" prop="expectedDate">
|
<el-date-picker v-model="form.expectedDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
|
</el-form-item>
|
</template>
|
|
<el-row v-if="approveTypeNumber === 2" :gutter="16">
|
<el-col :span="12"><el-form-item label="请假开始日期" prop="startDate"><el-date-picker v-model="form.startDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
|
<el-col :span="12"><el-form-item label="请假结束日期" prop="endDate"><el-date-picker v-model="form.endDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
|
</el-row>
|
<el-form-item v-if="approveTypeNumber === 4" label="报销金额" prop="amount">
|
<el-input-number v-model="form.amount" :min="0.01" :precision="2" style="width: 100%" />
|
</el-form-item>
|
|
<el-form-item label="申请事由" prop="approveReason">
|
<el-input v-model="form.approveReason" type="textarea" :rows="3" maxlength="1000" show-word-limit />
|
</el-form-item>
|
|
<el-form-item label="申请部门" prop="approveDeptId">
|
<el-select v-model="form.approveDeptId" style="width: 100%">
|
<el-option v-for="dept in departmentOptions" :key="dept.deptId" :label="dept.deptName" :value="dept.deptId" />
|
</el-select>
|
</el-form-item>
|
|
<el-form-item v-if="[3, 5].includes(approveTypeNumber) && flowOptions.length" label="预设审批流程">
|
<el-select v-model="form.flowDefinitionId" clearable placeholder="请选择预设流程;留空可手动设置节点" style="width: 100%">
|
<el-option v-for="flow in flowOptions" :key="flow.id" :label="flow.flowName" :value="flow.id" />
|
</el-select>
|
</el-form-item>
|
|
<el-divider content-position="left">审批节点</el-divider>
|
<el-alert v-if="form.flowDefinitionId && operationType === 'add'" title="提交后将按预设流程生成审批节点" type="info" :closable="false" style="margin-bottom: 12px" />
|
<div v-for="(node, index) in approverNodes" v-show="!form.flowDefinitionId || operationType === 'edit'" :key="node.key" class="node-row">
|
<span class="node-order">{{ index + 1 }}</span>
|
<el-input v-model="node.nodeName" placeholder="节点名称" style="width: 150px" />
|
<el-select v-model="node.userId" filterable placeholder="审批人" style="width: 150px">
|
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
|
</el-select>
|
<el-select v-model="node.conditionOperator" style="width: 130px" @change="handleConditionChange(node)">
|
<el-option label="始终执行" value="ALWAYS" />
|
<el-option label="金额大于" value="GT" />
|
<el-option label="金额大于等于" value="GTE" />
|
<el-option label="金额小于" value="LT" />
|
<el-option label="金额小于等于" value="LTE" />
|
</el-select>
|
<el-input-number v-if="node.conditionOperator !== 'ALWAYS'" v-model="node.conditionValue" :min="0" :precision="2" style="width: 130px" />
|
<el-button type="danger" link :disabled="approverNodes.length === 1" @click="removeNode(index)">删除</el-button>
|
<el-button link :disabled="index === 0" @click="moveNode(index, -1)">上移</el-button>
|
<el-button link :disabled="index === approverNodes.length - 1" @click="moveNode(index, 1)">下移</el-button>
|
</div>
|
<el-button v-if="!form.flowDefinitionId || operationType === 'edit'" type="primary" plain class="add-node" @click="addNode">新增审批节点</el-button>
|
|
<el-form-item label="申请日期">
|
<el-date-picker v-model="form.approveTime" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
|
</el-form-item>
|
<el-form-item label="附件材料">
|
<el-upload v-model:file-list="fileList" :action="upload.url" :headers="upload.headers" multiple
|
:before-upload="beforeUpload" :on-success="uploadSuccess" :on-error="uploadError" :on-remove="removeFile">
|
<el-button type="primary">上传文件</el-button>
|
</el-upload>
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="closeDialog">取消</el-button>
|
<el-button type="primary" :loading="submitting" @click="submit">提交</el-button>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { computed, getCurrentInstance, reactive, ref } from 'vue'
|
import { approveFlowDefinitionList, approveProcessAdd, approveProcessDetails, approveProcessGetInfo, approveProcessUpdate, getDept } from '@/api/collaborativeApproval/approvalProcess'
|
import { delLedgerFile, ledgerListNoPage } from '@/api/salesManagement/salesLedger'
|
import { userListNoPageByTenantId } from '@/api/system/user'
|
import { getToken } from '@/utils/auth'
|
import { getCurrentDate } from '@/utils'
|
import useUserStore from '@/store/modules/user'
|
|
const props = defineProps({ approveType: { type: [Number, String], default: 1 } })
|
const emit = defineEmits(['close'])
|
const { proxy } = getCurrentInstance()
|
const userStore = useUserStore()
|
const approveTypeNumber = computed(() => Number(props.approveType))
|
const typeName = computed(() => ({ 1: '自由协同审批', 3: '销售审批', 5: '采购审批' }[approveTypeNumber.value] || '审批流程'))
|
|
const visible = ref(false)
|
const submitting = ref(false)
|
const operationType = ref('add')
|
const formRef = ref()
|
const userList = ref([])
|
const departmentOptions = ref([])
|
const contractOptions = ref([])
|
const flowOptions = ref([])
|
const selectedContract = ref(null)
|
const fileList = ref([])
|
let nodeKey = 1
|
const approverNodes = ref([])
|
const upload = reactive({ url: import.meta.env.VITE_APP_BASE_API + '/file/upload', headers: { Authorization: 'Bearer ' + getToken() } })
|
const emptyForm = () => ({
|
id: null, approveId: '', approveUser: userStore.id, approveDeptId: userStore.currentDeptId,
|
approveTime: getCurrentDate(), approveReason: '', businessId: null, businessNo: '', businessTitle: '',
|
amount: null, businessData: '', flowDefinitionId: null, category: '', urgency: '普通', purchaseContent: '', quantity: null,
|
unit: '', expectedDate: '', startDate: '', endDate: '', tempFileIds: []
|
})
|
const form = ref(emptyForm())
|
const rules = {
|
approveDeptId: [{ required: true, message: '请选择申请部门', trigger: 'change' }],
|
approveReason: [{ required: true, message: '请输入申请事由', trigger: 'blur' }],
|
businessId: [{ required: true, message: '请选择销售合同', trigger: 'change' }],
|
businessTitle: [{ required: true, message: '请输入审批标题', trigger: 'blur' }],
|
category: [{ required: true, message: '请输入或选择类别', trigger: 'blur' }],
|
urgency: [{ required: true, message: '请选择紧急程度', trigger: 'change' }],
|
purchaseContent: [{ required: true, message: '请输入采购内容', trigger: 'blur' }],
|
quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }],
|
unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
|
amount: [{ required: true, message: '请输入预算金额', trigger: 'blur' }],
|
expectedDate: [{ required: true, message: '请选择期望到货日期', trigger: 'change' }],
|
startDate: [{ required: true, message: '请选择请假开始日期', trigger: 'change' }],
|
endDate: [{ required: true, message: '请选择请假结束日期', trigger: 'change' }]
|
}
|
|
const createNode = (data = {}) => ({ key: nodeKey++, userId: data.userId || null, nodeName: data.nodeName || '审批', conditionField: data.conditionField || null, conditionOperator: data.conditionOperator || 'ALWAYS', conditionValue: data.conditionValue == null ? null : Number(data.conditionValue) })
|
const addNode = () => approverNodes.value.push(createNode())
|
const removeNode = index => approverNodes.value.splice(index, 1)
|
const moveNode = (index, offset) => {
|
const target = index + offset
|
if (target < 0 || target >= approverNodes.value.length) return
|
const item = approverNodes.value.splice(index, 1)[0]
|
approverNodes.value.splice(target, 0, item)
|
}
|
const handleConditionChange = node => {
|
node.conditionField = node.conditionOperator === 'ALWAYS' ? null : 'amount'
|
if (node.conditionOperator === 'ALWAYS') node.conditionValue = null
|
}
|
|
const loadOptions = async () => {
|
const tasks = [userListNoPageByTenantId(), getDept()]
|
if (approveTypeNumber.value === 3) tasks.push(ledgerListNoPage({}))
|
const flowPromise = [3, 5].includes(approveTypeNumber.value) ? approveFlowDefinitionList({ approveType: approveTypeNumber.value }) : Promise.resolve({ data: [] })
|
tasks.push(flowPromise)
|
const results = await Promise.all(tasks)
|
userList.value = results[0].data || []
|
departmentOptions.value = results[1].data || []
|
if (approveTypeNumber.value === 3) {
|
contractOptions.value = results[2]?.data || []
|
flowOptions.value = results[3]?.data || []
|
} else {
|
contractOptions.value = []
|
flowOptions.value = results[2]?.data || []
|
}
|
const defaultFlow = flowOptions.value.find(item => item.isDefault === 1 && item.enabled === 1)
|
if (defaultFlow && operationType.value === 'add') form.value.flowDefinitionId = defaultFlow.id
|
}
|
|
const openDialog = async (type, row) => {
|
operationType.value = type
|
visible.value = true
|
form.value = emptyForm()
|
approverNodes.value = [createNode()]
|
fileList.value = []
|
selectedContract.value = null
|
await loadOptions()
|
if (type === 'edit' && row?.approveId) {
|
const [processRes, nodeRes] = await Promise.all([approveProcessGetInfo({ id: row.approveId }), approveProcessDetails(row.approveId)])
|
const process = processRes.data || {}
|
let business = {}
|
try { business = process.businessData ? JSON.parse(process.businessData) : {} } catch (_) { business = {} }
|
form.value = { ...emptyForm(), ...process, ...business, tempFileIds: [] }
|
fileList.value = process.commonFileList || []
|
approverNodes.value = (nodeRes.data || []).map(item => createNode({
|
userId: item.approveNodeUserId,
|
nodeName: item.approveNodeName,
|
conditionField: item.conditionField,
|
conditionOperator: item.conditionOperator,
|
conditionValue: item.conditionValue
|
}))
|
if (!approverNodes.value.length) approverNodes.value = [createNode()]
|
if (approveTypeNumber.value === 3) {
|
selectedContract.value = contractOptions.value.find(item => item.id === process.businessId) || business
|
}
|
}
|
}
|
|
const handleContractChange = id => {
|
selectedContract.value = contractOptions.value.find(item => item.id === id) || null
|
if (selectedContract.value) form.value.businessTitle = `销售合同审批-${selectedContract.value.salesContractNo || ''}`
|
}
|
|
const buildBusinessData = () => {
|
if (approveTypeNumber.value === 5) return JSON.stringify({ category: form.value.category, urgency: form.value.urgency, purchaseContent: form.value.purchaseContent, quantity: form.value.quantity, unit: form.value.unit, expectedDate: form.value.expectedDate })
|
if (approveTypeNumber.value === 1) return JSON.stringify({ category: form.value.category })
|
if (approveTypeNumber.value === 2) return JSON.stringify({ startDate: form.value.startDate, endDate: form.value.endDate })
|
if (approveTypeNumber.value === 4) return JSON.stringify({ amount: form.value.amount })
|
return form.value.businessData || ''
|
}
|
|
const submit = async () => {
|
await formRef.value.validate()
|
if (approveTypeNumber.value === 2 && new Date(form.value.endDate) < new Date(form.value.startDate)) return proxy.$modal.msgError('请假结束日期不能早于开始日期')
|
const usePresetFlow = operationType.value === 'add' && !!form.value.flowDefinitionId
|
if (!usePresetFlow && approverNodes.value.some(item => !item.userId || !item.nodeName)) return proxy.$modal.msgError('请完整填写审批节点')
|
if (!usePresetFlow && approverNodes.value.some((item, index) => index > 0 && item.userId === approverNodes.value[index - 1].userId)) return proxy.$modal.msgError('相邻审批节点不能选择同一审批人')
|
if (!usePresetFlow && approverNodes.value.some(item => item.conditionOperator !== 'ALWAYS' && item.conditionValue == null)) return proxy.$modal.msgError('请填写节点条件金额')
|
const nodes = usePresetFlow ? [] : approverNodes.value.map(item => ({ userId: item.userId, nodeName: item.nodeName, conditionField: item.conditionOperator === 'ALWAYS' ? null : 'amount', conditionOperator: item.conditionOperator, conditionValue: item.conditionValue == null ? null : String(item.conditionValue) }))
|
const payload = { ...form.value, approveType: approveTypeNumber.value, approveUser: userStore.id, nodes, approveUserIds: nodes.map(item => item.userId).join(','), businessData: buildBusinessData() }
|
submitting.value = true
|
try {
|
if (operationType.value === 'add') await approveProcessAdd(payload)
|
else await approveProcessUpdate(payload)
|
proxy.$modal.msgSuccess('提交成功')
|
closeDialog()
|
} finally {
|
submitting.value = false
|
}
|
}
|
|
const beforeUpload = () => { proxy.$modal.loading('正在上传文件,请稍候...'); return true }
|
const uploadSuccess = (res, file) => {
|
proxy.$modal.closeLoading()
|
if (res.code === 200) form.value.tempFileIds.push(res.data.tempId)
|
else { proxy.$modal.msgError(res.msg); proxy.$refs.fileUpload?.handleRemove(file) }
|
}
|
const uploadError = () => { proxy.$modal.closeLoading(); proxy.$modal.msgError('上传失败') }
|
const removeFile = file => {
|
if (file.id) delLedgerFile([file.id])
|
if (file.response?.data?.tempId) form.value.tempFileIds = form.value.tempFileIds.filter(id => id !== file.response.data.tempId)
|
}
|
const closeDialog = () => { visible.value = false; emit('close') }
|
|
defineExpose({ openDialog })
|
</script>
|
|
<style scoped>
|
.node-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
|
.node-order { display: inline-flex; width: 26px; height: 26px; align-items: center; justify-content: center; border-radius: 50%; color: #fff; background: #409eff; }
|
.add-node { margin: 0 0 20px 120px; }
|
</style>
|