| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <!-- 报销金额(仅当 approveType 为 4 时显示) --> |
| | | <el-row v-if="props.approveType == 4"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="报销金额:" prop="price"> |
| | | <el-input-number |
| | | v-model="form.price" |
| | | placeholder="请输入报销金额" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="0.01" |
| | | style="width: 100%" |
| | | disabled |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="!isPurchaseApproval && !isSalesApproval"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="审批事由:" prop="approveReason"> |
| | | <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" disabled/> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">采购详情</el-divider> |
| | | <el-skeleton :loading="purchaseLoading" animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" style="width: 30%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!purchaseDetail || !purchaseDetail.purchaseContractNumber" description="未查询到对应采购详情" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="采购合同号">{{ purchaseDetail.purchaseContractNumber }}</el-descriptions-item> |
| | | <el-descriptions-item label="供应商名称">{{ purchaseDetail.supplierName }}</el-descriptions-item> |
| | | <el-descriptions-item label="项目名称">{{ purchaseDetail.projectName }}</el-descriptions-item> |
| | | <el-descriptions-item label="销售合同号">{{ purchaseDetail.salesContractNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="签订日期">{{ purchaseDetail.executionDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="录入日期">{{ purchaseDetail.entryDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="付款方式">{{ purchaseDetail.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="合同金额" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(purchaseDetail.contractAmount ?? 0).toFixed(2) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产品明细</h4> |
| | | <el-table :data="purchaseProducts" border style="width: 100%"> |
| | | <el-table-column prop="productCategory" label="产品名称" /> |
| | | <el-table-column prop="specificationModel" label="规格型号" /> |
| | | <el-table-column prop="unit" label="单位" /> |
| | | <el-table-column prop="quantity" label="数量" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" label="含税单价"> |
| | | <template #default="scope">¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxInclusiveTotalPrice" label="含税总价"> |
| | | <template #default="scope">¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | <div v-if="isSalesApproval" style="margin: 10px 0 18px;"> |
| | | <el-divider content-position="left">销售详情</el-divider> |
| | | <el-skeleton :loading="salesLoading" animated> |
| | | <template #template> |
| | | <el-skeleton-item variant="h3" style="width: 30%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | <el-skeleton-item variant="text" style="width: 100%" /> |
| | | </template> |
| | | <template #default> |
| | | <el-empty v-if="!salesDetail || !salesDetail.salesContractNo" description="未查询到对应销售详情" /> |
| | | <template v-else> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="销售合同号">{{ salesDetail.salesContractNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="客户合同号">{{ salesDetail.customerContractNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="客户名称">{{ salesDetail.customerName }}</el-descriptions-item> |
| | | <el-descriptions-item label="业务员">{{ salesDetail.salesman }}</el-descriptions-item> |
| | | <el-descriptions-item label="项目名称">{{ salesDetail.projectName }}</el-descriptions-item> |
| | | <el-descriptions-item label="付款方式">{{ salesDetail.paymentMethod }}</el-descriptions-item> |
| | | <el-descriptions-item label="录入人">{{ salesEntryPersonDisplay }}</el-descriptions-item> |
| | | <el-descriptions-item label="录入日期">{{ salesDetail.entryDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="签订日期">{{ salesDetail.executionDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="合同金额" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(salesDetail.contractAmount ?? 0).toFixed(2) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <h4>产品明细</h4> |
| | | <el-table :data="salesProducts" border style="width: 100%"> |
| | | <el-table-column prop="productCategory" label="产品名称" /> |
| | | <el-table-column prop="specificationModel" label="规格型号" /> |
| | | <el-table-column prop="unit" label="单位" /> |
| | | <el-table-column prop="quantity" label="数量" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" label="含税单价"> |
| | | <template #default="scope">¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxInclusiveTotalPrice" label="含税总价"> |
| | | <template #default="scope">¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </el-skeleton> |
| | | </div> |
| | | <el-form :model="{ activities }" ref="formRef" label-position="top"> |
| | | <el-steps :active="getActiveStep()" finish-status="success" process-status="process" align-center direction="vertical"> |
| | | <el-step |
| | |
| | | <div v-if="!activity.isShen" class="node-reason"> |
| | | <span>审批意见:</span>{{ activity.approveNodeReason }} |
| | | </div> |
| | | <div v-if="!activity.isShen" class="node-reason"> |
| | | <span>签名:</span> |
| | | <img :src="activity.urlTem" class="signImg" alt="" v-if="activity.urlTem"/> |
| | | </div> |
| | | <div v-else-if="activity.isShen"> |
| | | <el-form-item |
| | | :prop="'activities.' + index + '.approveNodeReason'" |
| | |
| | | </el-form> |
| | | <template #footer v-if="operationType === 'approval'"> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="openSignatureDialog(2)">不通过</el-button> |
| | | <el-button type="primary" @click="submitForm(2)">不通过</el-button> |
| | | <el-button type="primary" @click="openSignatureDialog(1)">通过</el-button> |
| | | <el-button @click="closeDia">取消</el-button> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getCurrentInstance, reactive, ref, toRefs } from "vue"; |
| | | import { computed, getCurrentInstance, reactive, ref, toRefs } from "vue"; |
| | | import vueEsign from "vue-esign"; |
| | | import { |
| | | approveProcessDetails, |
| | | getDept, |
| | | updateApproveNode |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { getSalesByCode } from "@/api/salesManagement/salesLedger.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' |
| | | import { getToken } from "@/utils/auth"; |
| | | const emit = defineEmits(['close']) |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | |
| | | const userStore = useUserStore() |
| | | const productOptions = ref([]); |
| | | const userList = ref([]) |
| | | const purchaseLoading = ref(false) |
| | | const currentPurchase = ref({}) |
| | | const isPurchaseApproval = computed(() => Number(props.approveType) === 5) |
| | | const salesLoading = ref(false) |
| | | const currentSales = ref({}) |
| | | const isSalesApproval = computed(() => Number(props.approveType) === 6) |
| | | const purchaseDetail = computed(() => currentPurchase.value?.data || currentPurchase.value || {}) |
| | | const purchaseProducts = computed(() => { |
| | | const detail = purchaseDetail.value || {} |
| | | return detail.productData || detail.products || detail.children || [] |
| | | }) |
| | | const salesDetail = computed(() => currentSales.value?.data || currentSales.value || {}) |
| | | const salesProducts = computed(() => { |
| | | const detail = salesDetail.value || {} |
| | | return detail.productData || detail.products || detail.children || [] |
| | | }) |
| | | /** 销售详情:录入人(接口可能只返 entryPerson 用户ID,需用 userList 反查昵称) */ |
| | | const salesEntryPersonDisplay = computed(() => { |
| | | const d = salesDetail.value || {} |
| | | if (d.entryPersonName) return d.entryPersonName |
| | | if (d.recorderName) return d.recorderName |
| | | if (d.createByName) return d.createByName |
| | | if (d.createUserName) return d.createUserName |
| | | const id = d.entryPerson |
| | | if (id != null && id !== "" && userList.value?.length) { |
| | | const u = userList.value.find( |
| | | (x) => x.userId == id || String(x.userId) === String(id) |
| | | ) |
| | | if (u) return u.nickName |
| | | } |
| | | return id != null && id !== "" ? String(id) : "-" |
| | | }) |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | |
| | | const esign = ref(null); |
| | | const lineWidth = ref(0); |
| | | const lineColor = ref("#000000"); |
| | | const props = defineProps({ |
| | | approveType: { |
| | | type: [Number, String], |
| | | default: 0 |
| | | } |
| | | }) |
| | | |
| | | // 上传配置 |
| | | const upload = reactive({ |
| | | // 上传的地址 |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置上传的请求头部 |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | |
| | | // 节点标题 |
| | | const getNodeTitle = (index, len) => { |
| | |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | currentPurchase.value = {} |
| | | currentSales.value = {} |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {...row} |
| | | getProductOptions() |
| | | if (isPurchaseApproval.value) { |
| | | const purchaseContractNumber = row?.approveReason |
| | | if (purchaseContractNumber) { |
| | | purchaseLoading.value = true |
| | | getPurchaseByCode({ purchaseContractNumber }).then((res) => { |
| | | // 兼容后端返回 {code,data} 或直接返回详情对象两种结构 |
| | | currentPurchase.value = res || {} |
| | | }).catch(() => { |
| | | proxy.$modal.msgError("查询采购详情失败") |
| | | }).finally(() => { |
| | | purchaseLoading.value = false |
| | | }) |
| | | } |
| | | } |
| | | if (isSalesApproval.value) { |
| | | const salesContractNo = row?.approveReason |
| | | if (salesContractNo) { |
| | | salesLoading.value = true |
| | | getSalesByCode({ salesContractNo }).then((res) => { |
| | | currentSales.value = res || {} |
| | | }).catch(() => { |
| | | proxy.$modal.msgError("查询销售详情失败") |
| | | }).finally(() => { |
| | | salesLoading.value = false |
| | | }) |
| | | } |
| | | } |
| | | approveProcessDetails(row.approveId).then((res) => { |
| | | activities.value = res.data |
| | | // 增加isApproval字段 |
| | | activities.value.forEach(item => { |
| | | if (item.url && item.url.includes('word')) { |
| | | item.urlTem = item.url.replaceAll('word', 'img') |
| | | } else { |
| | | item.urlTem = item.url |
| | | } |
| | | if (item.approveNodeStatus === 2) { |
| | | item.isApproval = '已驳回'; |
| | | } else if (item.approveNodeStatus === 1) { |
| | |
| | | const confirmSignature = () => { |
| | | esign.value.generate().then((res) => { |
| | | console.log(res); |
| | | signatureImg.value = res; |
| | | signatureDialogVisible.value = false; |
| | | clearSignature() |
| | | submitForm(submitStatus); |
| | | // 将base64转换为二进制 |
| | | const base64Data = res.split(',')[1]; // 移除data:image/png;base64,前缀 |
| | | const binaryString = atob(base64Data); |
| | | const bytes = new Uint8Array(binaryString.length); |
| | | for (let i = 0; i < binaryString.length; i++) { |
| | | bytes[i] = binaryString.charCodeAt(i); |
| | | } |
| | | signatureImg.value = bytes; |
| | | |
| | | // 创建文件对象用于上传 |
| | | const blob = new Blob([bytes], { type: 'image/png' }); |
| | | const file = new File([blob], 'signature.png', { type: 'image/png' }); |
| | | |
| | | // 创建FormData |
| | | const formData = new FormData(); |
| | | formData.append('file', file); |
| | | |
| | | // 上传签名图片 |
| | | fetch(upload.url, { |
| | | method: 'POST', |
| | | headers: upload.headers, |
| | | body: formData |
| | | }) |
| | | .then(response => response.json()) |
| | | .then(data => { |
| | | if (data.code === 200) { |
| | | console.log('data---', data) |
| | | let tempFileIds = []; |
| | | tempFileIds.push(data.data.tempId); |
| | | signatureDialogVisible.value = false; |
| | | clearSignature(); |
| | | // 只有通过时才传递签名文件ID |
| | | if (submitStatus === 1) { |
| | | submitForm(submitStatus, tempFileIds); |
| | | } else { |
| | | submitForm(submitStatus); |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError("签名图片上传失败:" + data.msg); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error('上传失败:', error); |
| | | proxy.$modal.msgError("签名图片上传失败"); |
| | | }); |
| | | }).catch((err) => { |
| | | console.log(err); |
| | | proxy.$modal.msgWarning("请先签名!"); |
| | | }) |
| | | }; |
| | | // 提交审批 |
| | | const submitForm = (status) => { |
| | | const submitForm = (status, tempFileIds) => { |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen); |
| | | filteredActivities[0].approveNodeStatus = status; |
| | | filteredActivities[0].signatureImg = signatureImg.value; // 新增签名图片字段 |
| | | // 只有通过时才需要签名 |
| | | if (status === 1 && tempFileIds) { |
| | | filteredActivities[0].tempFileIds = tempFileIds; |
| | | } |
| | | // 判断是否为最后一步 |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1; |
| | | updateApproveNode({ ...filteredActivities[0], isLast, signatureImg: signatureImg.value }).then(() => { |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }); |
| | |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | purchaseLoading.value = false |
| | | currentPurchase.value = {} |
| | | salesLoading.value = false |
| | | currentSales.value = {} |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | |
| | | height: 30px; |
| | | border-radius: 50px; |
| | | } |
| | | .signImg { |
| | | cursor: pointer; |
| | | width: 200px; |
| | | height: 60px; |
| | | } |
| | | </style> |