|
|
@@ -0,0 +1,2363 @@
|
|
|
+<!-- 合同流程专用表单组件 -->
|
|
|
+<template>
|
|
|
+ <view class="contract-form-component">
|
|
|
+ <!-- 基本信息 -->
|
|
|
+ <uni-card>
|
|
|
+ <uni-section titleFontSize="1.3rem" title="基本信息" type="line"></uni-section>
|
|
|
+ <uni-forms ref="baseFormRef" :modelValue="baseForm" :rules="baseFormRules" label-position="left" :label-width="100" :border="true">
|
|
|
+ <uni-forms-item name="contract_number" label="合同编号">
|
|
|
+ <uni-easyinput v-model="baseForm.contract_number" disabled placeholder="自动生成"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="contract_name" label="合同名称" required>
|
|
|
+ <uni-easyinput v-model="baseForm.contract_name"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('contract_name')"
|
|
|
+ :placeholder="'请输入合同名称'"
|
|
|
+ :clearable="false"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="contract_type" label="合同类型" required>
|
|
|
+ <!-- 合同类型:发起环节或字段可编辑时显示选择器 -->
|
|
|
+ <picker v-if="isInitiateOrFieldEditable('contract_type')"
|
|
|
+ @change="onContractTypeChange"
|
|
|
+ :range="contractTypeList"
|
|
|
+ range-key="contract_type_name">
|
|
|
+ <view class="picker">{{ baseForm.contract_type_name || '请选择合同类型' }}</view>
|
|
|
+ </picker>
|
|
|
+ <uni-easyinput v-else v-model="baseForm.contract_type_name" disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="applyDate" label="填报日期">
|
|
|
+ <uni-easyinput v-model="baseForm.applyDate" disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="department" label="填报部门">
|
|
|
+ <uni-easyinput v-model="baseForm.department" disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="salesman_name" label="经办人">
|
|
|
+ <!-- 经办人:发起环节或字段可编辑时显示选择器 -->
|
|
|
+<!-- <uni-easyinput v-if="isInitiateOrFieldEditable('salesman_name')"
|
|
|
+ v-model="baseForm.salesman_name"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('salesman_name')"
|
|
|
+ placeholder="自动生成"></uni-easyinput>-->
|
|
|
+ <uni-easyinput v-model="baseForm.salesman_name" disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="salesmanTel" label="联系电话">
|
|
|
+ <uni-easyinput v-model="baseForm.salesmanTel"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('salesmanTel')"
|
|
|
+ type="number"
|
|
|
+ placeholder="请输入联系电话"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="supplierName" label="供方" required>
|
|
|
+ <!-- 供方:发起环节或字段可编辑时显示选择器 -->
|
|
|
+ <view v-if="isInitiateOrFieldEditable('supplierName')"
|
|
|
+ class="selector-wrapper"
|
|
|
+ @click="openSupplierSelector">
|
|
|
+ <text v-if="baseForm.supplierName">{{ baseForm.supplierName }}</text>
|
|
|
+ <text v-else class="placeholder">请选择供应商</text>
|
|
|
+ </view>
|
|
|
+ <uni-easyinput v-else v-model="baseForm.supplierName" disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="firstparty_name" label="需方">
|
|
|
+ <uni-easyinput v-model="baseForm.firstparty_name"
|
|
|
+ disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="contract_money" label="合同金额">
|
|
|
+ <uni-easyinput v-model="baseForm.contract_money"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('contract_money')"
|
|
|
+ type="digit"
|
|
|
+ placeholder="请输入合同金额"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="contractContent" label="合同内容">
|
|
|
+ <uni-easyinput v-if="isInitiateOrFieldEditable('contractContent')"
|
|
|
+ v-model="baseForm.contractContent"
|
|
|
+ type="textarea"
|
|
|
+ placeholder="请输入合同内容"></uni-easyinput>
|
|
|
+ <text v-else class="field-value">{{ baseForm.contractContent || '-' }}</text>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="projectItem" label="所属项目或产品">
|
|
|
+ <uni-easyinput v-model="baseForm.projectItem"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('projectItem')"
|
|
|
+ placeholder="请输入所属项目或产品"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="usePosition" label="使用位置">
|
|
|
+ <uni-easyinput v-model="baseForm.usePosition"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('usePosition')"
|
|
|
+ placeholder="请输入使用位置"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="other_contractor" label="对方联系人及电话">
|
|
|
+ <uni-easyinput v-model="baseForm.other_contractor"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('other_contractor')"
|
|
|
+ placeholder="请输入对方联系人及电话"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="otherFile" label="随合同提交的其它材料">
|
|
|
+ <uni-easyinput v-if="isInitiateOrFieldEditable('otherFile')"
|
|
|
+ v-model="baseForm.otherFile"
|
|
|
+ type="textarea"
|
|
|
+ placeholder="请输入其它材料说明"></uni-easyinput>
|
|
|
+ <text v-else class="field-value">{{ baseForm.otherFile || '-' }}</text>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="salesmanSign" label="经办人签字">
|
|
|
+ <uni-easyinput v-model="baseForm.salesmanSign"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('salesmanSign')"
|
|
|
+ placeholder="请输入经办人签字"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 部门意见 -->
|
|
|
+ <uni-forms-item label="部门意见" name="departmentalOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(departmentalOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="departmentalOpinionElem && (!departmentalOpinionElem.defaultValue || departmentalOpinionElem.defaultValue == '')">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('departmental_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('departmental_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="departmentalOpinionElem && departmentalOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('departmental_opinion')"
|
|
|
+ :src="config.baseUrlPre + departmentalOpinionElem.sealImgPath"
|
|
|
+ :alt="departmentalOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="departmentalOpinionElem && typeof departmentalOpinionElem.sealImgPath === 'string' && departmentalOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + departmentalOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 财务意见 -->
|
|
|
+ <uni-forms-item label="财务意见" name="financeOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(financeOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="financeOpinionElem && (!financeOpinionElem.defaultValue || financeOpinionElem.defaultValue == '')">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('finance_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('finance_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="financeOpinionElem && financeOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('finance_opinion')"
|
|
|
+ :src="config.baseUrlPre + financeOpinionElem.sealImgPath"
|
|
|
+ :alt="financeOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="financeOpinionElem && typeof financeOpinionElem.sealImgPath === 'string' && financeOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + financeOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 分管副总意见 -->
|
|
|
+ <uni-forms-item label="分管副总" name="dgmOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(deputyGeneralManagerOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="deputyGeneralManagerOpinionElem && (!deputyGeneralManagerOpinionElem.defaultValue || deputyGeneralManagerOpinionElem.defaultValue == '')">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('deputy_general_manager_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('deputy_general_manager_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="deputyGeneralManagerOpinionElem && deputyGeneralManagerOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('deputy_general_manager_opinion')"
|
|
|
+ :src="config.baseUrlPre + deputyGeneralManagerOpinionElem.sealImgPath"
|
|
|
+ :alt="deputyGeneralManagerOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="deputyGeneralManagerOpinionElem && typeof deputyGeneralManagerOpinionElem.sealImgPath === 'string' && deputyGeneralManagerOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + deputyGeneralManagerOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 审核副总意见 -->
|
|
|
+ <uni-forms-item label="分管副总" name="auditDgmOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(auditDeputyGeneralManagerOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="auditDeputyGeneralManagerOpinionElem && (!auditDeputyGeneralManagerOpinionElem.defaultValue || auditDeputyGeneralManagerOpinionElem.defaultValue == '')">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('audit_deputy_general_manager_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('audit_deputy_general_manager_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="auditDeputyGeneralManagerOpinionElem && auditDeputyGeneralManagerOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('audit_deputy_general_manager_opinion')"
|
|
|
+ :src="config.baseUrlPre + auditDeputyGeneralManagerOpinionElem.sealImgPath"
|
|
|
+ :alt="auditDeputyGeneralManagerOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="auditDeputyGeneralManagerOpinionElem && typeof auditDeputyGeneralManagerOpinionElem.sealImgPath === 'string' && auditDeputyGeneralManagerOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + auditDeputyGeneralManagerOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 总经理意见 -->
|
|
|
+ <uni-forms-item label="总经理" name="gmOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(generalManagerOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="generalManagerOpinionElem && (!generalManagerOpinionElem.defaultValue || generalManagerOpinionElem.defaultValue == '')">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('general_manager_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('general_manager_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="generalManagerOpinionElem && generalManagerOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('general_manager_opinion')"
|
|
|
+ :src="config.baseUrlPre + generalManagerOpinionElem.sealImgPath"
|
|
|
+ :alt="generalManagerOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="generalManagerOpinionElem && typeof generalManagerOpinionElem.sealImgPath === 'string' && generalManagerOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + generalManagerOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 董事长意见 -->
|
|
|
+ <uni-forms-item label="董事长意见" name="chairmanOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(chairmanOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="chairmanOpinionElem && (!chairmanOpinionElem.defaultValue || chairmanOpinionElem.defaultValue == '')">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('chairman_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('chairman_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="chairmanOpinionElem && chairmanOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('chairman_opinion')"
|
|
|
+ :src="config.baseUrlPre + chairmanOpinionElem.sealImgPath"
|
|
|
+ :alt="chairmanOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="chairmanOpinionElem && typeof chairmanOpinionElem.sealImgPath === 'string' && chairmanOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + chairmanOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+ </uni-forms>
|
|
|
+ </uni-card>
|
|
|
+
|
|
|
+ <!-- 物料明细 -->
|
|
|
+ <uni-card>
|
|
|
+ <uni-section titleFontSize="1.3rem" title="物料明细" type="line"></uni-section>
|
|
|
+ <!-- 添加物料按钮:发起环节或字段可编辑时显示 -->
|
|
|
+ <view v-if="isSeModel" class="material-actions">
|
|
|
+ <button type="primary" size="mini" @click="openMaterialSelector" plain>添加物料</button>
|
|
|
+ </view>
|
|
|
+ <!-- 物料列表 - 卡片式展示 -->
|
|
|
+ <view v-if="materialList.length > 0" class="material-list">
|
|
|
+ <view v-for="(item, index) in materialList" :key="'material-' + item.itemCode + '-' + index" class="material-card">
|
|
|
+ <view class="material-header" @click="toggleMaterialExpand(index)">
|
|
|
+ <view class="material-main-info">
|
|
|
+ <text class="material-name">{{ item.itemName }}</text>
|
|
|
+ <text class="material-code">{{ item.itemCode }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="material-expand">
|
|
|
+ <uni-icons :type="item.expanded ? 'up' : 'down'" size="16" color="#999"></uni-icons>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 展开的详细信息 -->
|
|
|
+ <view v-show="item.expanded" class="material-detail">
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">采购申请单号:</text>
|
|
|
+ <text class="detail-value purchase-number">{{ item.purchaseNumber || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">规格型号:</text>
|
|
|
+ <text class="detail-value">{{ item.specification }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">单位:</text>
|
|
|
+ <text class="detail-value">{{ item.measureName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">数量:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.qty"
|
|
|
+ type="digit"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入数量"
|
|
|
+ style="width: 100px; display: inline-block; margin-right: 15px;"
|
|
|
+ @blur="onQuantityBlur(item)"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.qty }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">税前单价:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.price"
|
|
|
+ type="digit"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入税前单价"
|
|
|
+ style="width: 100px; display: inline-block; margin-right: 15px;"
|
|
|
+ @blur="onPriceBlur(item)"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.price }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">税率:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.cess"
|
|
|
+ type="digit"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入税率"
|
|
|
+ style="width: 80px; display: inline-block; margin-right: 15px;"
|
|
|
+ @blur="onCessBlur(item)"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.cess }}%</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">税后单价:</text>
|
|
|
+ <text class="detail-value price-value">{{ item.priceTax }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="isSeModel" class="detail-row delete-row">
|
|
|
+ <button type="warn" size="mini" @click="removeMaterial(index)">删除</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="materialList.length === 0" class="empty-materials">
|
|
|
+ <text>暂无物料</text>
|
|
|
+ </view>
|
|
|
+ </uni-card>
|
|
|
+
|
|
|
+ <!-- 付款明细 -->
|
|
|
+ <uni-card>
|
|
|
+ <uni-section titleFontSize="1.3rem" title="付款明细" type="line"></uni-section>
|
|
|
+ <!-- 添加付款按钮:发起环节或字段可编辑时显示 -->
|
|
|
+ <view v-if="isSeModel" class="payment-actions">
|
|
|
+ <button type="primary" size="mini" @click="addPayment" :disabled="materialList.length === 0" plain>添加付款</button>
|
|
|
+ </view>
|
|
|
+ <!-- 付款列表 - 卡片式展示 -->
|
|
|
+ <view v-if="paymentList.length > 0" class="payment-list">
|
|
|
+ <view v-for="(item, index) in paymentList" :key="'payment-' + item.payType + '-' + index" class="payment-card">
|
|
|
+ <view class="payment-header" @click="togglePaymentExpand(index)">
|
|
|
+ <view class="payment-main-info">
|
|
|
+ <text class="payment-name">{{ item.payTypeName || item.payType }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="payment-expand">
|
|
|
+ <uni-icons :type="item.expanded ? 'up' : 'down'" size="16" color="#999"></uni-icons>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 展开的详细信息 -->
|
|
|
+ <view v-show="item.expanded" class="payment-detail">
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">付款方式:</text>
|
|
|
+ <picker
|
|
|
+ v-if="isSeModel"
|
|
|
+ @change="(e) => onPaymentTypeChange(index, e)"
|
|
|
+ :range="paymentTypeList"
|
|
|
+ range-key="payTypeName">
|
|
|
+ <view class="picker">
|
|
|
+ {{ item.payTypeName || '请选择付款方式' }}
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.payTypeName || item.payType }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">付款比例:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.proportion"
|
|
|
+ type="digit"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入比例"
|
|
|
+ style="width: 80px; display: inline-block; margin-right: 10px;"
|
|
|
+ @blur="onProportionBlur(item)"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.proportion }}</text>
|
|
|
+ <text>%</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">付款金额:</text>
|
|
|
+ <text class="detail-value price-value">{{ item.amount }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">已付金额:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.amountPaid"
|
|
|
+ type="digit"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入已付金额"
|
|
|
+ style="width: 100px; display: inline-block; margin-right: 10px;"
|
|
|
+ @blur="onAmountPaidBlur(item)"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.amountPaid || 0 }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">付款时间:</text>
|
|
|
+ <uni-datetime-picker
|
|
|
+ v-if="isSeModel"
|
|
|
+ v-model="item.payTime"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择付款时间"
|
|
|
+ style="display: inline-block;"
|
|
|
+ @change="(e) => onPayTimeChange(item, e)"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.payTime || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">付款状态:</text>
|
|
|
+ <text class="detail-value status-tag" :class="'status-' + (item.payStatus || '0')">{{ getPayStatusName(item) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">备注:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.remark"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入备注"
|
|
|
+ style="flex: 1; display: inline-block;"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.remark || '-' }}</text>
|
|
|
+ <button v-if="isSeModel" type="warn" size="mini" @click="removePayment(index)" style="margin-left: 10px;">删除</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="paymentList.length === 0" class="empty-payments">
|
|
|
+ <text>暂无付款明细</text>
|
|
|
+ </view>
|
|
|
+ </uni-card>
|
|
|
+
|
|
|
+ <!-- 签名板弹出层 -->
|
|
|
+ <uni-popup ref="signaturePopup" @maskClick="closeSignature">
|
|
|
+ <view class="signature_container" :class="{ 'signature_container_landscape': isLandscape }">
|
|
|
+ <view class="signature_content">
|
|
|
+ <l-signature ref="signatureRef" v-if="signaturePopupShow" :landscape="isLandscape" :penSize="8"
|
|
|
+ :minLineWidth="4" :maxLineWidth="12" :openSmooth="true" :preferToDataURL="true"
|
|
|
+ backgroundColor="#ffffff" penColor="black"></l-signature>
|
|
|
+ </view>
|
|
|
+ <view class="signature_button_container">
|
|
|
+ <uni-row :gutter="10">
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button type="warn" @click="onclickSignatureButton('undo')">撤销</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button type="warn" @click="onclickSignatureButton('clear')">清空</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button type="primary" @click="onclickSignatureButton('save')">保存</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button @click="onclickSignatureButton('landscape')">全屏</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+
|
|
|
+ <!-- 选择器弹出层(物料、供应商) -->
|
|
|
+ <uni-popup ref="selectorPopup" type="center">
|
|
|
+ <view class="selector-popup">
|
|
|
+ <view class="popup-header">
|
|
|
+ <text class="popup-title">{{ popupTitle }}</text>
|
|
|
+ <uni-icons type="closeempty" size="20" @click="closePopup"></uni-icons>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 物料来源选择(仅物料选择时显示) -->
|
|
|
+ <view v-if="selectorType === 'material'" class="material-source-selector">
|
|
|
+ <view class="source-options">
|
|
|
+ <view
|
|
|
+ :class="['source-option', materialSource === 'mes' ? 'active' : '']"
|
|
|
+ @click="materialSource = 'mes'; onMaterialSourceChange()">
|
|
|
+ MES 系统
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ :class="['source-option', materialSource === 'purchase' ? 'active' : '']"
|
|
|
+ @click="materialSource = 'purchase'; onMaterialSourceChange()">
|
|
|
+ 采购申请
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <view v-if="(selectorType === 'material' && materialSource === 'mes') || selectorType === 'supplier'" class="search-bar">
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="searchKeyword"
|
|
|
+ :placeholder="selectorType === 'material' ? '输入物料名称搜索' : '输入供应商名称搜索'"
|
|
|
+ clearable
|
|
|
+ @confirm="handleSearch"
|
|
|
+ />
|
|
|
+ <button type="primary" size="mini" @click="handleSearch">搜索</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 采购申请单选择器(采购申请模式) -->
|
|
|
+ <view v-if="selectorType === 'material' && materialSource === 'purchase'" class="purchase-selector">
|
|
|
+ <picker @change="onPurchaseChange" :range="purchaseApplyList" range-key="contractPurchaseFormNumber">
|
|
|
+ <view class="picker">
|
|
|
+ {{ selectedPurchaseApply ? selectedPurchaseApply.contractPurchaseFormNumber : '全部采购申请单' }}
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+ <button type="primary" size="mini" @click="loadPurchaseMaterials(true)">查询</button>
|
|
|
+ <button type="warn" size="mini" @click="clearPurchaseSelection" v-if="selectedPurchaseApply">清空</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view
|
|
|
+ scroll-y
|
|
|
+ class="popup-content"
|
|
|
+ refresher-enabled
|
|
|
+ :refresher-triggered="isRefreshing"
|
|
|
+ @refresherrefresh="onRefresh"
|
|
|
+ @scrolltolower="loadMore"
|
|
|
+ >
|
|
|
+ <view v-for="(item, index) in selectorList" :key="index"
|
|
|
+ class="selector-item"
|
|
|
+ @click="selectItem(item)">
|
|
|
+ <view class="selector-item-content">
|
|
|
+ <view class="item-info">
|
|
|
+ <text class="item-name">{{ getSelectorItemName(item) }}</text>
|
|
|
+ <!-- 采购申请模式显示采购单号 -->
|
|
|
+ <text v-if="materialSource === 'purchase' && item.purchaseNumber" class="item-purchase">
|
|
|
+ 采购单:{{ item.purchaseNumber }}
|
|
|
+ </text>
|
|
|
+ <text v-if="getItemCode(item)" class="item-code">{{ getItemCode(item) }}</text>
|
|
|
+ <text v-if="getItemSpec(item)" class="item-spec">{{ getItemSpec(item) }}</text>
|
|
|
+ <!-- 显示单位和数量 -->
|
|
|
+ <text v-if="item.measureName || item.qty" class="item-extra">
|
|
|
+ <text v-if="item.measureName">{{ item.measureName }}</text>
|
|
|
+ <text v-if="item.qty"> 可用:{{ item.qty }}</text>
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <text v-if="isSelected(item)" class="selected-tag">已选择</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <view v-if="isLoading && selectorList.length > 0" class="loading-text">
|
|
|
+ <uni-load-more status="loading" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 没有更多数据 -->
|
|
|
+ <view v-else-if="!hasMore && selectorList.length > 0" class="no-more-text">
|
|
|
+ <text>没有更多了</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 空数据提示 -->
|
|
|
+ <view v-if="selectorList.length === 0 && !isLoading" class="empty-data">
|
|
|
+ <text>{{ searchKeyword ? '暂无相关数据' : '暂无数据' }}</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch, computed, nextTick, onMounted } from 'vue'
|
|
|
+import { useUserStore } from '@/store/user.js'
|
|
|
+import config from '@/config.js'
|
|
|
+import { uploadSignatureBoardImg, getSeal } from '@/api/process.js'
|
|
|
+import { getContractTypeList, getPaymentTypeList, getMaterialList, getSupplierList, getPurchaseApplyList, getPurchaseDetailList } from '@/api/contract.js'
|
|
|
+import $modal from '@/plugins/modal.js'
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+// 组件挂载时加载合同类型和付款方式列表
|
|
|
+onMounted(() => {
|
|
|
+ loadContractTypeList()
|
|
|
+ loadPaymentTypeList()
|
|
|
+})
|
|
|
+
|
|
|
+// 加载合同类型列表
|
|
|
+function loadContractTypeList() {
|
|
|
+ getContractTypeList(userStore.user.useId).then(({ returnParams }) => {
|
|
|
+ if (returnParams && Array.isArray(returnParams.typeList)) {
|
|
|
+ contractTypeList.value = returnParams.typeList
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ console.error('加载合同类型列表失败:', err)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 加载付款方式列表
|
|
|
+const paymentTypeList = ref<any[]>([])
|
|
|
+
|
|
|
+function loadPaymentTypeList() {
|
|
|
+ getPaymentTypeList(userStore.user.useId).then(({ returnParams }) => {
|
|
|
+ if (returnParams && Array.isArray(returnParams.paymentList)) {
|
|
|
+ paymentTypeList.value = returnParams.paymentList
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ console.error('加载付款方式列表失败:', err)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ formData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({})
|
|
|
+ },
|
|
|
+ formElements: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ repeatingForm: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({ elementItem: [], elements: [] })
|
|
|
+ },
|
|
|
+ // 是否为发起环节(seModel == '1')
|
|
|
+ isInitiate: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ // 当前环节可编辑的字段列表
|
|
|
+ editableFields: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ // 当前环节的审批意见配置
|
|
|
+ currentTacheOpinion: {
|
|
|
+ type: Object,
|
|
|
+ default: null
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emits = defineEmits(['update', 'signature-change'])
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const baseForm = ref<any>({
|
|
|
+ contract_number: '',
|
|
|
+ contract_name: '',
|
|
|
+ contract_type: '',
|
|
|
+ contract_type_name: '',
|
|
|
+ applyDate: '',
|
|
|
+ department: '',
|
|
|
+ salesman: '',
|
|
|
+ salesman_name: '',
|
|
|
+ salesmanTel: '',
|
|
|
+ supplierCode: '',
|
|
|
+ supplierName: '',
|
|
|
+ firstparty_name: '',
|
|
|
+ contract_money: '',
|
|
|
+ contractContent: '',
|
|
|
+ projectItem: '',
|
|
|
+ usePosition: '',
|
|
|
+ other_contractor: '',
|
|
|
+ otherFile: '',
|
|
|
+ salesmanSign: '',
|
|
|
+ // 录入人信息
|
|
|
+ contract_entrying_operator: '',
|
|
|
+ contract_entrying_operator_name: '',
|
|
|
+ // 组织 ID
|
|
|
+ unit_id: ''
|
|
|
+})
|
|
|
+
|
|
|
+const baseFormRules = ref({
|
|
|
+ contract_name: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ errorMessage: '请输入合同名称'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+const materialList = ref<any[]>([])
|
|
|
+const paymentList = ref<any[]>([])
|
|
|
+const signaturePopup = ref(null)
|
|
|
+const signatureRef = ref(null)
|
|
|
+const signatureImg = ref('')
|
|
|
+const currentApprovalText = ref('')
|
|
|
+const approvals = ref<any[]>([]) // 历史审批意见列表
|
|
|
+const signaturePopupShow = ref(false)
|
|
|
+const isLandscape = ref(false)
|
|
|
+// 合同类型列表
|
|
|
+const contractTypeList = ref<any[]>([])
|
|
|
+
|
|
|
+// 选择器相关变量
|
|
|
+const selectorPopup = ref(null)
|
|
|
+const selectorList = ref<any[]>([])
|
|
|
+const selectorType = ref('') // 'material', 'supplier'
|
|
|
+const materialSource = ref('mes') // 'mes' - MES 系统,'purchase' - 采购申请
|
|
|
+const purchaseApplyList = ref<any[]>([]) // 采购申请单列表
|
|
|
+const selectedPurchaseApply = ref<any>(null) // 选中的采购申请单
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = 20
|
|
|
+const hasMore = ref(true)
|
|
|
+const isLoading = ref(false)
|
|
|
+const isRefreshing = ref(false)
|
|
|
+const searchKeyword = ref('')
|
|
|
+
|
|
|
+// 选择器弹出层标题
|
|
|
+const popupTitle = computed(() => {
|
|
|
+ if (selectorType.value === 'material') return '选择物料'
|
|
|
+ if (selectorType.value === 'supplier') return '选择供应商'
|
|
|
+ return '选择'
|
|
|
+})
|
|
|
+
|
|
|
+// 获取指定的审批意见元素
|
|
|
+const getApprovalElement = (fieldName: string) => {
|
|
|
+ if (!props.formElements || !Array.isArray(props.formElements)) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ // 从 formElements 中找到对应的字段
|
|
|
+ const elem = props.formElements.find(e => e.tableField === fieldName || e.elementName.includes(fieldName.replace(/_/g, '')))
|
|
|
+ return elem || null
|
|
|
+}
|
|
|
+
|
|
|
+// 获取各部门意见元素(计算属性,避免重复调用)
|
|
|
+const departmentalOpinionElem = computed(() => getApprovalElement('departmental_opinion'))
|
|
|
+const deputyGeneralManagerOpinionElem = computed(() => getApprovalElement('deputy_general_manager_opinion'))
|
|
|
+const auditDeputyGeneralManagerOpinionElem = computed(() => getApprovalElement('audit_deputy_general_manager_opinion'))
|
|
|
+const generalManagerOpinionElem = computed(() => getApprovalElement('general_manager_opinion'))
|
|
|
+const financeOpinionElem = computed(() => getApprovalElement('finance_opinion'))
|
|
|
+const chairmanOpinionElem = computed(() => getApprovalElement('chairman_opinion'))
|
|
|
+
|
|
|
+// 是否发起环节
|
|
|
+const isSeModel = computed(() => props.isInitiate)
|
|
|
+
|
|
|
+// 计算各部门意见是否可编辑
|
|
|
+const isApprovalFieldEditable = (elem: any) => {
|
|
|
+ if (!elem) return false
|
|
|
+ return props.editableFields && props.editableFields.includes(elem.tableField)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理审批意见签名
|
|
|
+let lastFormInsId = ''
|
|
|
+
|
|
|
+// 计算字段是否可编辑 (只需要该字段在 table_fields 中即可)
|
|
|
+const getFieldEditable = (fieldName: string) => {
|
|
|
+ // 如果没有配置 editableFields,则都不可编辑
|
|
|
+ if (!props.editableFields || props.editableFields.length === 0) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查该字段是否在可编辑字段列表中 (table_fields 包含的字段才可以编辑)
|
|
|
+ return props.editableFields.includes(fieldName)
|
|
|
+}
|
|
|
+
|
|
|
+// 计算是否为发起环节或字段可编辑 (满足任一条件即可)
|
|
|
+const isInitiateOrFieldEditable = (fieldName: string) => {
|
|
|
+ return props.isInitiate || getFieldEditable(fieldName)
|
|
|
+}
|
|
|
+
|
|
|
+// 监听表单数据变化
|
|
|
+watch(() => props.formData, (newVal) => {
|
|
|
+ if (!newVal || Object.keys(newVal).length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 使用 formInsId 判断是否是新的数据(formInsId 肯定存在)
|
|
|
+ const currentFormInsId = newVal.lFormInsId || newVal.universalid || ''
|
|
|
+
|
|
|
+ // 只有当 formInsId 变化时,才认为是新的数据需要加载
|
|
|
+ if (currentFormInsId && currentFormInsId !== lastFormInsId) {
|
|
|
+ lastFormInsId = currentFormInsId
|
|
|
+
|
|
|
+ // 填充基本信息 - 直接使用后端返回的字段
|
|
|
+ baseForm.value = {
|
|
|
+ contract_number: newVal.contract_number || '',
|
|
|
+ contract_name: newVal.contract_name || '',
|
|
|
+ contract_type: newVal.contract_type || '',
|
|
|
+ contract_type_name: newVal.contract_type_name || '', // 直接使用后端返回的名称
|
|
|
+ applyDate: newVal.applyDate || '',
|
|
|
+ department: newVal.department || '',
|
|
|
+ salesman: newVal.salesman || '',
|
|
|
+ salesman_name: newVal.salesman_name || '',
|
|
|
+ salesmanTel: newVal.salesmanTel || '',
|
|
|
+ supplierCode: newVal.supplierCode || '',
|
|
|
+ supplierName: newVal.supplierName || '',
|
|
|
+ firstparty_name: newVal.firstparty_name || '',
|
|
|
+ contract_money: newVal.contract_money || '',
|
|
|
+ contractContent: newVal.contractContent || '',
|
|
|
+ projectItem: newVal.projectItem || '',
|
|
|
+ usePosition: newVal.usePosition || '',
|
|
|
+ other_contractor: newVal.other_contractor || '',
|
|
|
+ otherFile: newVal.otherFile || '',
|
|
|
+ salesmanSign: newVal.salesmanSign || '',
|
|
|
+ // 录入人信息
|
|
|
+ contract_entrying_operator: newVal.contract_entrying_operator || '',
|
|
|
+ contract_entrying_operator_name: newVal.contract_entrying_operator_name || '',
|
|
|
+ // 组织 ID
|
|
|
+ unit_id: newVal.unit_id || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从 contractMaterialList 中获取物料列表
|
|
|
+ const rawMaterialList = newVal.details || newVal.contractMaterialList || []
|
|
|
+
|
|
|
+ // 加载物料数据
|
|
|
+ if (Array.isArray(rawMaterialList)) {
|
|
|
+ materialList.value = rawMaterialList.map((item: any) => ({
|
|
|
+ itemCode: item.itemCode || item.materialCode,
|
|
|
+ itemName: item.itemName || item.materialName,
|
|
|
+ specification: item.specification || item.materialModel,
|
|
|
+ measureName: item.measureName || item.unit,
|
|
|
+ qty: item.qty || 0,
|
|
|
+ price: item.price || 0,
|
|
|
+ cess: item.cess || 0,
|
|
|
+ priceTax: item.priceTax || 0,
|
|
|
+ purchaseId: item.purchaseId || '',
|
|
|
+ purchaseNumber: item.purchaseNumber || '',
|
|
|
+ unit_id: item.unit_id || newVal.unit_id || '', // 从明细或主表获取 unit_id
|
|
|
+ expanded: true // 默认展开
|
|
|
+ }))
|
|
|
+ } else {
|
|
|
+ materialList.value = []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从 contractPaymentList 中获取付款列表
|
|
|
+ const rawPaymentList = newVal.contractPaymentList || []
|
|
|
+
|
|
|
+ // 加载付款数据
|
|
|
+ if (Array.isArray(rawPaymentList)) {
|
|
|
+ paymentList.value = rawPaymentList.map((item: any) => ({
|
|
|
+ payType: item.payType || '',
|
|
|
+ payTypeName: item.payTypeName || '',
|
|
|
+ proportion: item.proportion || 0,
|
|
|
+ amount: item.amount || 0,
|
|
|
+ amountPaid: item.amountPaid || 0,
|
|
|
+ payTime: item.payTime || '',
|
|
|
+ payStatus: item.payStatus || '0',
|
|
|
+ payStatusName: item.payStatusName || getPayStatusName(item),
|
|
|
+ remark: item.remark || '',
|
|
|
+ unit_id: item.unit_id || newVal.unit_id || '', // 从明细或主表获取 unit_id
|
|
|
+ expanded: true // 默认展开
|
|
|
+ }))
|
|
|
+
|
|
|
+ // 如果付款方式名称为空,尝试从列表中查找
|
|
|
+ if (paymentTypeList.value.length > 0) {
|
|
|
+ paymentList.value.forEach(payment => {
|
|
|
+ if (!payment.payTypeName && payment.payType) {
|
|
|
+ const payType = paymentTypeList.value.find(p => p.payType == payment.payType)
|
|
|
+ if (payType) {
|
|
|
+ payment.payTypeName = payType.payTypeName
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ paymentList.value = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+}, { immediate: true, deep: true })
|
|
|
+
|
|
|
+// 切换物料展开/收起状态
|
|
|
+function toggleMaterialExpand(index: number) {
|
|
|
+ if (materialList.value[index]) {
|
|
|
+ materialList.value[index].expanded = !materialList.value[index].expanded
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 切换付款展开/收起状态
|
|
|
+function togglePaymentExpand(index: number) {
|
|
|
+ if (paymentList.value[index]) {
|
|
|
+ paymentList.value[index].expanded = !paymentList.value[index].expanded
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开物料选择器
|
|
|
+async function openMaterialSelector() {
|
|
|
+ selectorType.value = 'material'
|
|
|
+ materialSource.value = 'mes' // 默认 MES 系统模式
|
|
|
+ currentPage.value = 1
|
|
|
+ hasMore.value = true
|
|
|
+ selectorList.value = []
|
|
|
+ await loadMaterials(1, false)
|
|
|
+ openPopup()
|
|
|
+}
|
|
|
+
|
|
|
+// 打开供应商选择器
|
|
|
+async function openSupplierSelector() {
|
|
|
+ selectorType.value = 'supplier'
|
|
|
+ currentPage.value = 1
|
|
|
+ hasMore.value = true
|
|
|
+ selectorList.value = []
|
|
|
+ await loadSuppliers(1, false)
|
|
|
+ openPopup()
|
|
|
+}
|
|
|
+
|
|
|
+// 打开弹出层
|
|
|
+function openPopup() {
|
|
|
+ if (selectorPopup.value) {
|
|
|
+ ;(selectorPopup.value as any).open()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭弹出层
|
|
|
+function closePopup() {
|
|
|
+ if (selectorPopup.value) {
|
|
|
+ ;(selectorPopup.value as any).close()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载物料列表(分页)
|
|
|
+async function loadMaterials(page: number, append: boolean = false) {
|
|
|
+ if (isLoading.value) return
|
|
|
+
|
|
|
+ isLoading.value = true
|
|
|
+ if (page === 1) {
|
|
|
+ isRefreshing.value = true
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await getMaterialList(
|
|
|
+ userStore.user.useId,
|
|
|
+ page,
|
|
|
+ pageSize,
|
|
|
+ searchKeyword.value
|
|
|
+ )
|
|
|
+
|
|
|
+ if (res.returnCode === '1') {
|
|
|
+ const result = res.returnParams
|
|
|
+ const newList = result.list || []
|
|
|
+
|
|
|
+ if (append) {
|
|
|
+ // 追加模式(加载更多)
|
|
|
+ selectorList.value = [...selectorList.value, ...newList]
|
|
|
+ } else {
|
|
|
+ // 覆盖模式(刷新/搜索)
|
|
|
+ selectorList.value = newList
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否还有更多数据
|
|
|
+ hasMore.value = selectorList.value.length < result.total
|
|
|
+ } else {
|
|
|
+ $modal.msgError('加载物料失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载物料失败', error)
|
|
|
+ $modal.msgError('加载失败')
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
+ isRefreshing.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载供应商列表(分页)
|
|
|
+async function loadSuppliers(page: number, append: boolean = false) {
|
|
|
+ if (isLoading.value) return
|
|
|
+
|
|
|
+ isLoading.value = true
|
|
|
+ if (page === 1) {
|
|
|
+ isRefreshing.value = true
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await getSupplierList(
|
|
|
+ userStore.user.useId,
|
|
|
+ page,
|
|
|
+ pageSize,
|
|
|
+ searchKeyword.value
|
|
|
+ )
|
|
|
+
|
|
|
+ if (res.returnCode === '1') {
|
|
|
+ const result = res.returnParams
|
|
|
+ const newList = result.list || []
|
|
|
+
|
|
|
+ if (append) {
|
|
|
+ selectorList.value = [...selectorList.value, ...newList]
|
|
|
+ } else {
|
|
|
+ selectorList.value = newList
|
|
|
+ }
|
|
|
+
|
|
|
+ hasMore.value = selectorList.value.length < result.total
|
|
|
+ } else {
|
|
|
+ $modal.msgError('加载供应商失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载供应商失败', error)
|
|
|
+ $modal.msgError('加载失败')
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
+ isRefreshing.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载采购申请单列表
|
|
|
+async function loadPurchaseApplyList() {
|
|
|
+ if (isLoading.value) return
|
|
|
+
|
|
|
+ isLoading.value = true
|
|
|
+ isRefreshing.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await getPurchaseApplyList(userStore.user.useId)
|
|
|
+
|
|
|
+ if (res.returnCode === '1') {
|
|
|
+ const result = res.returnParams
|
|
|
+ purchaseApplyList.value = result.list || []
|
|
|
+ selectedPurchaseApply.value = null
|
|
|
+ selectorList.value = [] // 清空物料列表
|
|
|
+ } else {
|
|
|
+ $modal.msgError('加载采购申请单失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载采购申请单失败', error)
|
|
|
+ $modal.msgError('加载失败')
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
+ isRefreshing.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载采购申请物料
|
|
|
+async function loadPurchaseMaterials(resetPage: boolean = false) {
|
|
|
+ if (isLoading.value) return
|
|
|
+
|
|
|
+ // 如果是分页加载,不清空列表
|
|
|
+ if (resetPage) {
|
|
|
+ currentPage.value = 1
|
|
|
+ selectorList.value = []
|
|
|
+ }
|
|
|
+
|
|
|
+ isLoading.value = true
|
|
|
+ isRefreshing.value = resetPage
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 如果不指定采购申请单,则查询所有物料
|
|
|
+ const purchaseFormId = selectedPurchaseApply.value
|
|
|
+ ? selectedPurchaseApply.value.contractPurchaseFormId.toString()
|
|
|
+ : ''
|
|
|
+
|
|
|
+ const res = await getPurchaseDetailList(
|
|
|
+ userStore.user.useId,
|
|
|
+ purchaseFormId,
|
|
|
+ currentPage.value,
|
|
|
+ 10 // 每页 10 条,与 PC 端一致
|
|
|
+ )
|
|
|
+
|
|
|
+ if (res.returnCode === '1') {
|
|
|
+ const result = res.returnParams
|
|
|
+ const newList = result.list || []
|
|
|
+
|
|
|
+ if (resetPage) {
|
|
|
+ selectorList.value = newList
|
|
|
+ } else {
|
|
|
+ // 追加数据
|
|
|
+ selectorList.value.push(...newList)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否还有更多数据
|
|
|
+ hasMore.value = selectorList.value.length < result.total
|
|
|
+
|
|
|
+ if (!resetPage) {
|
|
|
+ currentPage.value++
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $modal.msgError('加载物料失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载物料失败', error)
|
|
|
+ $modal.msgError('加载失败')
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
+ isRefreshing.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 物料来源切换时
|
|
|
+async function onMaterialSourceChange() {
|
|
|
+ currentPage.value = 1
|
|
|
+ hasMore.value = true
|
|
|
+ selectorList.value = []
|
|
|
+ searchKeyword.value = ''
|
|
|
+
|
|
|
+ if (materialSource.value === 'mes') {
|
|
|
+ await loadMaterials(1, false)
|
|
|
+ } else {
|
|
|
+ // 采购申请模式,先加载采购申请单列表,然后加载所有物料
|
|
|
+ await loadPurchaseApplyList()
|
|
|
+ // 加载完采购申请单后,立即查询物料(默认不指定具体采购单)
|
|
|
+ await loadPurchaseMaterials(true) // true 表示重置分页
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 采购申请单选择变化
|
|
|
+function onPurchaseChange(e: any) {
|
|
|
+ const selectedIndex = e.detail.value
|
|
|
+ selectedPurchaseApply.value = purchaseApplyList.value[selectedIndex]
|
|
|
+ loadPurchaseMaterials(true)
|
|
|
+}
|
|
|
+
|
|
|
+// 清空采购申请单选择
|
|
|
+function clearPurchaseSelection() {
|
|
|
+ selectedPurchaseApply.value = null
|
|
|
+ selectorList.value = []
|
|
|
+ loadPurchaseMaterials(true)
|
|
|
+}
|
|
|
+
|
|
|
+// 加载更多
|
|
|
+function loadMore() {
|
|
|
+ if (!hasMore.value || isLoading.value) return
|
|
|
+ currentPage.value++
|
|
|
+
|
|
|
+ if (selectorType.value === 'material') {
|
|
|
+ if (materialSource.value === 'mes') {
|
|
|
+ loadMaterials(currentPage.value, true)
|
|
|
+ } else {
|
|
|
+ loadPurchaseMaterials(false)
|
|
|
+ }
|
|
|
+ } else if (selectorType.value === 'supplier') {
|
|
|
+ loadSuppliers(currentPage.value, true)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 下拉刷新
|
|
|
+function onRefresh() {
|
|
|
+ currentPage.value = 1
|
|
|
+ if (selectorType.value === 'material') {
|
|
|
+ if (materialSource.value === 'mes') {
|
|
|
+ loadMaterials(1, false)
|
|
|
+ } else {
|
|
|
+ loadPurchaseMaterials(true)
|
|
|
+ }
|
|
|
+ } else if (selectorType.value === 'supplier') {
|
|
|
+ loadSuppliers(1, false)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索
|
|
|
+function handleSearch() {
|
|
|
+ currentPage.value = 1
|
|
|
+ if (selectorType.value === 'material') {
|
|
|
+ loadMaterials(1, false)
|
|
|
+ } else if (selectorType.value === 'supplier') {
|
|
|
+ loadSuppliers(1, false)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取物料/供应商名称
|
|
|
+function getSelectorItemName(item: any): string {
|
|
|
+ return item.itemName || item.vendorName || ''
|
|
|
+}
|
|
|
+
|
|
|
+// 获取编码
|
|
|
+function getItemCode(item: any): string {
|
|
|
+ return item.itemCode || ''
|
|
|
+}
|
|
|
+
|
|
|
+// 获取规格型号
|
|
|
+function getItemSpec(item: any): string {
|
|
|
+ return item.specification || ''
|
|
|
+}
|
|
|
+
|
|
|
+// 判断是否已选中
|
|
|
+// 注意:只有当物料编码和采购申请单编号都相同时,才认为是重复选择
|
|
|
+function isSelected(item: any): boolean {
|
|
|
+ if (selectorType.value === 'material') {
|
|
|
+ const itemCode = item.itemCode || ''
|
|
|
+ const purchaseNumber = item.purchaseNumber || ''
|
|
|
+
|
|
|
+ // 检查物料列表中是否存在相同的物料(物料编码 + 采购申请单编号都相同)
|
|
|
+ return materialList.value.some(m => {
|
|
|
+ const existItemCode = m.itemCode || ''
|
|
|
+ const existPurchaseNumber = m.purchaseNumber || ''
|
|
|
+
|
|
|
+ // 只有当物料编码和采购申请单编号都相同时,才认为是重复
|
|
|
+ return existItemCode === itemCode &&
|
|
|
+ ((existPurchaseNumber === purchaseNumber) ||
|
|
|
+ (!existPurchaseNumber && !purchaseNumber))
|
|
|
+ })
|
|
|
+ } else if (selectorType.value === 'supplier') {
|
|
|
+ const code = item.vendorCode || item.supplierCode
|
|
|
+ return baseForm.value.supplierCode === code
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// 选择物料/供应商
|
|
|
+function selectItem(item: any) {
|
|
|
+ if (selectorType.value === 'material') {
|
|
|
+ // 检查是否已存在(根据物料编码 + 采购申请单编号判断)
|
|
|
+ const exists = isSelected(item)
|
|
|
+ if (exists) {
|
|
|
+ $modal.msg('该物料已添加')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加物料到列表
|
|
|
+ materialList.value.push({
|
|
|
+ itemCode: item.itemCode,
|
|
|
+ itemName: item.itemName,
|
|
|
+ specification: item.specification,
|
|
|
+ measureName: item.measureName,
|
|
|
+ qty: item.qty || 0,
|
|
|
+ price: item.price || 0,
|
|
|
+ cess: item.cess || 0,
|
|
|
+ priceTax: item.priceTax || 0,
|
|
|
+ purchaseId: item.purchaseId || selectedPurchaseApply.value?.contractPurchaseFormId || '',
|
|
|
+ purchaseNumber: item.purchaseNumber || selectedPurchaseApply.value?.contractPurchaseFormNumber || '',
|
|
|
+ expanded: true
|
|
|
+ })
|
|
|
+
|
|
|
+ // 重新计算总价
|
|
|
+ calculateTotalPrice()
|
|
|
+ } else if (selectorType.value === 'supplier') {
|
|
|
+ // 选择供应商
|
|
|
+ baseForm.value.supplierCode = item.vendorCode
|
|
|
+ baseForm.value.supplierName = item.vendorName
|
|
|
+ }
|
|
|
+
|
|
|
+ closePopup()
|
|
|
+}
|
|
|
+
|
|
|
+// 合同类型变化
|
|
|
+function onContractTypeChange(e: any) {
|
|
|
+ const index = e.detail.value
|
|
|
+ if (contractTypeList.value[index]) {
|
|
|
+ baseForm.value.contract_type = contractTypeList.value[index].contract_type
|
|
|
+ baseForm.value.contract_type_name = contractTypeList.value[index].contract_type_name
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 删除物料
|
|
|
+function removeMaterial(index: number) {
|
|
|
+ $modal.confirm('', '确定删除该物料?')
|
|
|
+ .then(() => {
|
|
|
+ materialList.value.splice(index, 1)
|
|
|
+ // 重新计算总价
|
|
|
+ calculateTotalPrice()
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+// 物料价格计算 (与 start.vue 保持一致)
|
|
|
+function calculateMaterialPrice(item: any) {
|
|
|
+ const price = parseFloat(item.price) || 0
|
|
|
+ const cess = parseFloat(item.cess) || 0
|
|
|
+
|
|
|
+ // 计算税后单价:税前单价 * (1 + 税率 / 100)
|
|
|
+ item.priceTax = (price * (1 + cess / 100)).toFixed(2)
|
|
|
+
|
|
|
+ // 重新计算总价
|
|
|
+ calculateTotalPrice()
|
|
|
+}
|
|
|
+
|
|
|
+// 计算合同总价
|
|
|
+function calculateTotalPrice() {
|
|
|
+ let total = 0
|
|
|
+ materialList.value.forEach(item => {
|
|
|
+ const qty = parseFloat(item.qty) || 0
|
|
|
+ const priceTax = parseFloat(item.priceTax) || 0
|
|
|
+ total += qty * priceTax
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果合同金额为空或小于总价,自动更新
|
|
|
+ const currentMoney = parseFloat(baseForm.value.contract_money) || 0
|
|
|
+ if (!currentMoney || currentMoney < total) {
|
|
|
+ baseForm.value.contract_money = total.toFixed(2)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新付款信息中的金额
|
|
|
+ calculatePaymentAmount()
|
|
|
+}
|
|
|
+
|
|
|
+// 计算物料总价值(仅返回数值,不更新合同金额)
|
|
|
+function calculateTotalPriceValue(): number {
|
|
|
+ let total = 0
|
|
|
+ materialList.value.forEach(item => {
|
|
|
+ const qty = parseFloat(item.qty) || 0
|
|
|
+ const priceTax = parseFloat(item.priceTax) || 0
|
|
|
+ total += qty * priceTax
|
|
|
+ })
|
|
|
+ return total
|
|
|
+}
|
|
|
+
|
|
|
+// 数量变化时
|
|
|
+function onQuantityBlur(item: any) {
|
|
|
+ calculateTotalPrice()
|
|
|
+}
|
|
|
+
|
|
|
+// 合同金额失焦验证 (已移除,改为提交前统一验证)
|
|
|
+// function onContractMoneyBlur() {}
|
|
|
+
|
|
|
+// 税前单价变化时
|
|
|
+function onPriceBlur(item: any) {
|
|
|
+ calculateMaterialPrice(item)
|
|
|
+}
|
|
|
+
|
|
|
+// 税率变化时
|
|
|
+function onCessBlur(item: any) {
|
|
|
+ calculateMaterialPrice(item)
|
|
|
+}
|
|
|
+
|
|
|
+// 添加付款
|
|
|
+function addPayment() {
|
|
|
+ paymentList.value.push({
|
|
|
+ payType: '',
|
|
|
+ payTypeName: '',
|
|
|
+ proportion: '0',
|
|
|
+ amount: '0',
|
|
|
+ amountPaid: '0',
|
|
|
+ payTime: '',
|
|
|
+ payStatus: '0',
|
|
|
+ payStatusName: '未支付',
|
|
|
+ remark: '',
|
|
|
+ expanded: true
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 比例变化时重新计算金额
|
|
|
+function onProportionBlur(item: any) {
|
|
|
+ calculatePaymentAmount()
|
|
|
+}
|
|
|
+
|
|
|
+// 付款方式类型变化
|
|
|
+function onPaymentTypeChange(index: number, e: any) {
|
|
|
+ const selectedIndex = e.detail.value
|
|
|
+ const selected = paymentTypeList.value[selectedIndex]
|
|
|
+ if (selected) {
|
|
|
+ paymentList.value[index].payType = selected.payType
|
|
|
+ paymentList.value[index].payTypeName = selected.payTypeName
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 已付金额变化时更新付款状态
|
|
|
+function onAmountPaidBlur(item: any) {
|
|
|
+ updatePaymentStatus(item)
|
|
|
+}
|
|
|
+
|
|
|
+// 付款时间变化
|
|
|
+function onPayTimeChange(item: any, e: any) {
|
|
|
+ // uni-datetime-picker 返回的可能是时间戳或 Date 对象,需要转换为字符串
|
|
|
+ const dateValue = e
|
|
|
+ if (dateValue) {
|
|
|
+ // 如果是时间戳或数字,转换为日期字符串
|
|
|
+ if (typeof dateValue === 'number' || typeof dateValue === 'string') {
|
|
|
+ const date = new Date(dateValue)
|
|
|
+ if (!isNaN(date.getTime())) {
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ item.payTime = `${year}-${month}-${day}`
|
|
|
+ }
|
|
|
+ } else if (typeof dateValue === 'object') {
|
|
|
+ // 如果已经是 Date 对象
|
|
|
+ const year = dateValue.getFullYear()
|
|
|
+ const month = String(dateValue.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(dateValue.getDate()).padStart(2, '0')
|
|
|
+ item.payTime = `${year}-${month}-${day}`
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 计算付款金额(根据比例)
|
|
|
+function calculatePaymentAmount() {
|
|
|
+ const contractMoney = parseFloat(baseForm.value.contract_money) || 0
|
|
|
+
|
|
|
+ paymentList.value.forEach(item => {
|
|
|
+ const proportion = parseFloat(item.proportion) || 0
|
|
|
+ const amount = (contractMoney * proportion / 100).toFixed(2)
|
|
|
+ item.amount = amount
|
|
|
+
|
|
|
+ // 更新付款状态
|
|
|
+ updatePaymentStatus(item)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 更新付款状态
|
|
|
+function updatePaymentStatus(item: any) {
|
|
|
+ const amount = parseFloat(item.amount) || 0
|
|
|
+ const amountPaid = parseFloat(item.amountPaid) || 0
|
|
|
+
|
|
|
+ if (amountPaid === 0) {
|
|
|
+ item.payStatus = '0'
|
|
|
+ item.payStatusName = '未支付'
|
|
|
+ } else if (amountPaid < amount) {
|
|
|
+ item.payStatus = '1'
|
|
|
+ item.payStatusName = '部分支付'
|
|
|
+ } else if (amountPaid >= amount) {
|
|
|
+ item.payStatus = '2'
|
|
|
+ item.payStatusName = '已支付'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取付款状态名称
|
|
|
+function getPayStatusName(item: any): string {
|
|
|
+ const amount = parseFloat(item.amount) || 0
|
|
|
+ const amountPaid = parseFloat(item.amountPaid) || 0
|
|
|
+
|
|
|
+ if (amountPaid === 0) {
|
|
|
+ return '未支付'
|
|
|
+ } else if (amountPaid < amount) {
|
|
|
+ return '部分支付'
|
|
|
+ } else if (amountPaid >= amount) {
|
|
|
+ return '已支付'
|
|
|
+ }
|
|
|
+ return '未支付'
|
|
|
+}
|
|
|
+
|
|
|
+// 删除付款
|
|
|
+function removePayment(index: number) {
|
|
|
+ $modal.confirm('', '确定删除该付款明细?')
|
|
|
+ .then(() => {
|
|
|
+ paymentList.value.splice(index, 1)
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+// 处理审批意见签名
|
|
|
+const currentApprovalFieldName = ref('')
|
|
|
+// 存储每个签名字段的印章信息
|
|
|
+const approvalSealInfo = ref<Record<string, any>>({})
|
|
|
+
|
|
|
+function handleApprovalSignature(fieldName: string) {
|
|
|
+ currentApprovalFieldName.value = fieldName
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ nextTick(() => {
|
|
|
+ signaturePopupShow.value = true
|
|
|
+ })
|
|
|
+ signaturePopup.value.open()
|
|
|
+}
|
|
|
+
|
|
|
+// 处理一键签名
|
|
|
+function handleAutoSeal(fieldName: string) {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ if (elem) {
|
|
|
+ getSeal(userStore.user.useId).then(({ returnParams }) => {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ elem.defaultValue = returnParams.sealFileId.universalid
|
|
|
+ elem.sealImgPath = returnParams.sealFileId.path
|
|
|
+ // 保存印章信息(用于后端处理)
|
|
|
+ // 一键签名时,sealFileId.universalid 就是印章模板 ID
|
|
|
+ approvalSealInfo.value[fieldName] = {
|
|
|
+ sealInsId: returnParams.sealFileId.universalid, // 签名实例 ID(这里与印章模板 ID 相同)
|
|
|
+ sealFileId: returnParams.sealFileId.universalid, // 印章模板 ID(用于 imgval)
|
|
|
+ left: 0, // 默认左边距为 0
|
|
|
+ top: 0 // 默认上边距为 0
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ $modal.msgError('获取签名失败:' + err)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化签字板
|
|
|
+function initSignature() {
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ setTimeout(() => {
|
|
|
+ signaturePopupShow.value = true
|
|
|
+ }, 100)
|
|
|
+}
|
|
|
+
|
|
|
+// 点击签字板按钮
|
|
|
+function onclickSignatureButton(event: string) {
|
|
|
+ switch (event) {
|
|
|
+ case 'undo':
|
|
|
+ signatureRef.value.undo()
|
|
|
+ break
|
|
|
+ case 'clear':
|
|
|
+ signatureRef.value.clear()
|
|
|
+ break
|
|
|
+ case 'save':
|
|
|
+ signatureRef.value.canvasToTempFilePath({
|
|
|
+ success: (res: any) => {
|
|
|
+ if (res.isEmpty) {
|
|
|
+ $modal.msgError('签名不能为空!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 判断上传文件是否是 base64
|
|
|
+ if (res.tempFilePath.substring(0, 'data:image/png;base64,'.length) == 'data:image/png;base64,') {
|
|
|
+ const _fileData = res.tempFilePath
|
|
|
+ uploadSignatureBoardImg(userStore.user.useId, _fileData, getApprovalElement(currentApprovalFieldName.value).tableField)
|
|
|
+ .then(({ returnParams }) => {
|
|
|
+ const elem = getApprovalElement(currentApprovalFieldName.value)
|
|
|
+ elem.defaultValue = returnParams.sealInsID
|
|
|
+ elem.sealImgPath = returnParams.path
|
|
|
+ // 保存印章信息(用于后端处理)
|
|
|
+ // sealInsID 是手写签名的实例 ID
|
|
|
+ approvalSealInfo.value[currentApprovalFieldName.value] = {
|
|
|
+ sealInsId: returnParams.sealInsID, // 签名实例 ID
|
|
|
+ sealFileId: returnParams.sealInsID, // 手写签名图片 ID
|
|
|
+ left: 0, // 默认左边距为 0
|
|
|
+ top: 0 // 默认上边距为 0
|
|
|
+ }
|
|
|
+ currentApprovalFieldName.value = ''
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ signaturePopup.value.close()
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 转 base64
|
|
|
+ uni.getFileSystemManager().readFile({
|
|
|
+ filePath: res.tempFilePath,
|
|
|
+ encoding: 'base64',
|
|
|
+ success: (fileData: any) => {
|
|
|
+ const _fileData = 'data:image/png;base64,' + fileData.data
|
|
|
+ uploadSignatureBoardImg(userStore.user.useId, _fileData, getApprovalElement(currentApprovalFieldName.value).tableField)
|
|
|
+ .then(({ returnParams }) => {
|
|
|
+ const elem = getApprovalElement(currentApprovalFieldName.value)
|
|
|
+ elem.defaultValue = returnParams.sealInsID
|
|
|
+ elem.sealImgPath = returnParams.path
|
|
|
+ // 保存印章信息(用于后端处理)
|
|
|
+ // sealInsID 是手写签名的实例 ID
|
|
|
+ approvalSealInfo.value[currentApprovalFieldName.value] = {
|
|
|
+ sealInsId: returnParams.sealInsID, // 签名实例 ID
|
|
|
+ sealFileId: returnParams.sealInsID, // 手写签名图片 ID
|
|
|
+ left: 0, // 默认左边距为 0
|
|
|
+ top: 0 // 默认上边距为 0
|
|
|
+ }
|
|
|
+ currentApprovalFieldName.value = ''
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ signaturePopup.value.close()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ break
|
|
|
+ case 'landscape':
|
|
|
+ isLandscape.value = !isLandscape.value
|
|
|
+ initSignature()
|
|
|
+ break
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 清空签名
|
|
|
+function clearSignature() {
|
|
|
+ onclickSignatureButton('clear')
|
|
|
+}
|
|
|
+
|
|
|
+// 保存签名
|
|
|
+function saveSignature() {
|
|
|
+ onclickSignatureButton('save')
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭签名
|
|
|
+function closeSignature() {
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ signaturePopup.value.close()
|
|
|
+}
|
|
|
+
|
|
|
+// 暴露验证方法给父组件
|
|
|
+defineExpose({
|
|
|
+ validate: async () => {
|
|
|
+ // 基本信息验证
|
|
|
+ if (!baseForm.value.contract_name || !baseForm.value.contract_name.trim()) {
|
|
|
+ return Promise.reject(new Error('合同名称不能为空!'))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合同名称长度验证(最多 100 字符)
|
|
|
+ if (baseForm.value.contract_name && baseForm.value.contract_name.length > 100) {
|
|
|
+ return Promise.reject(new Error('合同名称长度不能超过 100!'))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 合同类型验证 (发起环节或字段可编辑时需要验证)
|
|
|
+ if (isSeModel.value || getFieldEditable('contract_type')) {
|
|
|
+ const contractType = baseForm.value.contract_type
|
|
|
+ if (!contractType || (typeof contractType === 'string' && !contractType.trim())) {
|
|
|
+ return Promise.reject(new Error('合同类型不能为空!'))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 经办人验证(发起环节或字段可编辑时需要验证)
|
|
|
+ /*if (isSeModel.value || getFieldEditable('salesman_name')) {
|
|
|
+ if (!baseForm.value.salesman || !baseForm.value.salesman.trim()) {
|
|
|
+ return Promise.reject(new Error('经办人不能为空!'))
|
|
|
+ }
|
|
|
+ }*/
|
|
|
+
|
|
|
+ // 合同金额验证(发起环节或字段可编辑时需要验证)
|
|
|
+ if (isSeModel.value || getFieldEditable('contract_money')) {
|
|
|
+ if (baseForm.value.contract_money) {
|
|
|
+ // 验证是否为数字
|
|
|
+ const moneyRegex = /^\d+(\.\d{1,2})?$/
|
|
|
+ if (!moneyRegex.test(baseForm.value.contract_money)) {
|
|
|
+ return Promise.reject(new Error('合同金额只能是数字!'))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证合同金额不能小于物料总金额
|
|
|
+ const totalPrice = calculateTotalPriceValue()
|
|
|
+ const contractMoney = parseFloat(baseForm.value.contract_money)
|
|
|
+ if (contractMoney < totalPrice) {
|
|
|
+ return Promise.reject(new Error('合同金额不能小于物料总金额:' + totalPrice.toFixed(2)))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 供方验证(发起环节或字段可编辑时需要验证)
|
|
|
+ if (isSeModel.value || getFieldEditable('supplierName')) {
|
|
|
+ if (!baseForm.value.supplierCode || !baseForm.value.supplierName || !baseForm.value.supplierName.trim()) {
|
|
|
+ return Promise.reject(new Error('供方不能为空!'))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 物料明细验证(发起环节时验证)
|
|
|
+ if (isSeModel.value && materialList.value.length > 0) {
|
|
|
+ for (let i = 0; i < materialList.value.length; i++) {
|
|
|
+ const item = materialList.value[i]
|
|
|
+
|
|
|
+ // 数量验证:不能为空且必须大于 0
|
|
|
+ if (!item.qty || Number(item.qty) <= 0) {
|
|
|
+ return Promise.reject(new Error(`请填写第${i + 1}个物料的数量,且必须大于 0`))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 税前单价验证:不能为空且不能为负数
|
|
|
+ if (!item.price || Number(item.price) < 0) {
|
|
|
+ return Promise.reject(new Error(`请填写第${i + 1}个物料的税前单价,且不能为负数`))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 税率验证:不能为空且不能为负数
|
|
|
+ if (!item.cess || Number(item.cess) < 0) {
|
|
|
+ return Promise.reject(new Error(`请填写第${i + 1}个物料的税率,且不能为负数`))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 税后单价验证:不能为空且不能为负数
|
|
|
+ if (!item.priceTax || Number(item.priceTax) < 0) {
|
|
|
+ return Promise.reject(new Error(`请填写第${i + 1}个物料的税后单价,且不能为负数`))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 付款明细验证(发起环节时验证)
|
|
|
+ if (isSeModel.value && paymentList.value.length > 0) {
|
|
|
+ for (let i = 0; i < paymentList.value.length; i++) {
|
|
|
+ const item = paymentList.value[i]
|
|
|
+
|
|
|
+ // 付款方式验证
|
|
|
+ if (!item.payType || !item.payTypeName) {
|
|
|
+ return Promise.reject(new Error(`第${i + 1}行付款方式不能为空!`))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 比例验证:不能为空且必须大于 0
|
|
|
+ if (!item.proportion || Number(item.proportion) <= 0) {
|
|
|
+ return Promise.reject(new Error(`请填写第${i + 1}个付款比例,且必须大于 0`))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 金额验证
|
|
|
+ /*if (!item.amount || item.amount.toString().trim() === '') {
|
|
|
+ return Promise.reject(new Error(`第${i + 1}行付款金额不能为空!`))
|
|
|
+ }*/
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证审批意见字段(如果在 table_fields 中)
|
|
|
+ const approvalFields = [
|
|
|
+ { fieldName: 'departmental_opinion', msg: '部门意见' },
|
|
|
+ { fieldName: 'deputy_general_manager_opinion', msg: '分管副总意见' },
|
|
|
+ { fieldName: 'audit_deputy_general_manager_opinion', msg: '分管副总意见' },
|
|
|
+ { fieldName: 'general_manager_opinion', msg: '总经理意见' },
|
|
|
+ { fieldName: 'finance_opinion', msg: '财务意见' },
|
|
|
+ { fieldName: 'chairman_opinion', msg: '董事长意见' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ for (const field of approvalFields) {
|
|
|
+ const elem = getApprovalElement(field.fieldName)
|
|
|
+ if (elem) {
|
|
|
+ // 检查该字段是否在当前环节的可编辑字段列表中
|
|
|
+ // editableFields 包含了当前环节可编辑的所有字段(即 table_fields)
|
|
|
+ if (props.editableFields && props.editableFields.includes(field.fieldName)) {
|
|
|
+ if (!elem.defaultValue || elem.defaultValue === '') {
|
|
|
+ return Promise.reject(new Error(field.msg + '不能为空!'))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return Promise.resolve()
|
|
|
+ },
|
|
|
+ // 获取表单数据(返回 formElements 格式)
|
|
|
+ getFormElements: () => {
|
|
|
+ const formElements: any[] = []
|
|
|
+
|
|
|
+ // 添加基本信息字段
|
|
|
+ Object.keys(baseForm.value).forEach(key => {
|
|
|
+ const value = baseForm.value[key]
|
|
|
+ if (value !== undefined && value !== null) {
|
|
|
+ formElements.push({
|
|
|
+ name: key,
|
|
|
+ value: String(value),
|
|
|
+ type: '0' // 普通文本类型
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加审批意见字段
|
|
|
+ const approvalFields = ['departmental_opinion', 'deputy_general_manager_opinion', 'audit_deputy_general_manager_opinion', 'general_manager_opinion', 'finance_opinion', 'chairman_opinion']
|
|
|
+ approvalFields.forEach(fieldName => {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ if (elem) {
|
|
|
+ formElements.push({
|
|
|
+ name: fieldName,
|
|
|
+ value: elem.defaultValue || '',
|
|
|
+ type: elem.type || '0'
|
|
|
+ })
|
|
|
+ // 添加签名图片的 imgval 值(用于后端处理)
|
|
|
+ // 格式:sealFileId_left_top(必须使用印章模板 ID,而不是签名实例 ID)
|
|
|
+ if (approvalSealInfo.value[fieldName]) {
|
|
|
+ const sealInfo = approvalSealInfo.value[fieldName]
|
|
|
+ formElements.push({
|
|
|
+ name: fieldName + '_imgval',
|
|
|
+ value: `${sealInfo.sealFileId}_${sealInfo.left}_${sealInfo.top}`,
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ } else if (elem.sealImgPath && elem.defaultValue) {
|
|
|
+ // 兼容旧数据:如果有 sealImgPath 和 defaultValue,提取 sealId 构建 imgval
|
|
|
+ // sealImgPath 格式:/shares/document/seal/593268258724500.png
|
|
|
+ const sealIdMatch = elem.sealImgPath.match(/\/seal\/(\d+)\.png$/)
|
|
|
+ if (sealIdMatch) {
|
|
|
+ const sealId = sealIdMatch[1]
|
|
|
+ formElements.push({
|
|
|
+ name: fieldName + '_imgval',
|
|
|
+ value: `${sealId}_0_0`,
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加物料明细(JSON 字符串格式)
|
|
|
+ if (materialList.value && materialList.value.length > 0) {
|
|
|
+ formElements.push({
|
|
|
+ name: 'contractMaterialList',
|
|
|
+ value: JSON.stringify(materialList.value),
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加付款明细(JSON 字符串格式)
|
|
|
+ if (paymentList.value && paymentList.value.length > 0) {
|
|
|
+ formElements.push({
|
|
|
+ name: 'contractPaymentList',
|
|
|
+ value: JSON.stringify(paymentList.value),
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return formElements
|
|
|
+ },
|
|
|
+ // 获取表单数据(兼容旧版本,返回键值对格式)
|
|
|
+ getFormData: () => {
|
|
|
+ const formData: any = {
|
|
|
+ ...baseForm.value,
|
|
|
+ contractMaterialList: materialList.value || [],
|
|
|
+ contractPaymentList: paymentList.value || []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加审批意见字段数据
|
|
|
+ const approvalFields = ['departmental_opinion', 'deputy_general_manager_opinion', 'audit_deputy_general_manager_opinion', 'general_manager_opinion', 'finance_opinion', 'chairman_opinion']
|
|
|
+ approvalFields.forEach(fieldName => {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ if (elem) {
|
|
|
+ formData[fieldName] = elem.defaultValue || ''
|
|
|
+ // 添加签名图片的 imgval 值(用于后端处理)
|
|
|
+ // 格式:sealFileId_left_top(必须使用印章模板 ID,而不是签名实例 ID)
|
|
|
+ if (approvalSealInfo.value[fieldName]) {
|
|
|
+ const sealInfo = approvalSealInfo.value[fieldName]
|
|
|
+ formData[fieldName + '_imgval'] = `${sealInfo.sealFileId}_${sealInfo.left}_${sealInfo.top}`
|
|
|
+ } else if (elem.sealImgPath && elem.defaultValue) {
|
|
|
+ // 兼容旧数据:如果有 sealImgPath 和 defaultValue,提取 sealId 构建 imgval
|
|
|
+ // sealImgPath 格式:/shares/document/seal/593268258724500.png
|
|
|
+ const sealIdMatch = elem.sealImgPath.match(/\/seal\/(\d+)\.png$/)
|
|
|
+ if (sealIdMatch) {
|
|
|
+ const sealId = sealIdMatch[1]
|
|
|
+ formData[fieldName + '_imgval'] = `${sealId}_0_0`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return formData
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+/* 基本信息中的禁用字段样式优化 */
|
|
|
+::v-deep .uni-forms {
|
|
|
+ .uni-forms-item__content {
|
|
|
+ .uni-easyinput__content-input {
|
|
|
+ font-size: calc(14px + 1.2*(1rem - 16px)) !important;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .uni-date {
|
|
|
+ .uni-icons {
|
|
|
+ font-size: calc(22px + 1.2*(1rem - 16px)) !important;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .uni-date__x-input {
|
|
|
+ height: auto;
|
|
|
+ font-size: calc(14px + 1.2*(1rem - 16px)) !important;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 审批意见字段样式(与 purchase-form 保持一致)
|
|
|
+.element_value_container {
|
|
|
+ .signature_img {
|
|
|
+ width: 180px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 选择器样式
|
|
|
+.selector-wrapper {
|
|
|
+ padding: 10px 0;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .placeholder {
|
|
|
+ color: #c0c4cc;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.picker {
|
|
|
+ padding: 10px 0;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+// 签名弹窗样式(与 purchase-form 保持一致)
|
|
|
+.signature_container {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ height: 40vh;
|
|
|
+ width: 90vw;
|
|
|
+
|
|
|
+ .signature_content {
|
|
|
+ height: 80%;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .signature_button_container {
|
|
|
+ height: 20%;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ button {
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.signature_container_landscape {
|
|
|
+ height: 100vh;
|
|
|
+ width: 100vw;
|
|
|
+
|
|
|
+ .signature_content {
|
|
|
+ height: 85%;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .signature_button_container {
|
|
|
+ margin-top: 10%;
|
|
|
+ height: 15%;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ button {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 物料列表 - 卡片式展示
|
|
|
+.material-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.material-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+
|
|
|
+ button {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.material-card {
|
|
|
+ border: 1px solid #e5e5e5;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: #fff;
|
|
|
+ margin-bottom: 8px;
|
|
|
+
|
|
|
+ .material-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .material-main-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .material-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-code {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-expand {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-detail {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-top: 1px solid #e5e5e5;
|
|
|
+
|
|
|
+ .detail-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .detail-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-value {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除按钮行的特殊样式
|
|
|
+ &.delete-row {
|
|
|
+ button {
|
|
|
+ margin-left: auto;
|
|
|
+ padding: 2px 10px;
|
|
|
+ height: auto;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 价格样式
|
|
|
+ .price-value {
|
|
|
+ color: #f56c6c;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 采购申请单编号样式
|
|
|
+ .purchase-number {
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: normal;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.empty-materials {
|
|
|
+ text-align: center;
|
|
|
+ padding: 30px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+// 付款状态标签样式
|
|
|
+.status-tag {
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+
|
|
|
+ &.status-0 {
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.status-1 {
|
|
|
+ background-color: #fff7e6;
|
|
|
+ color: #fa8c16;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.status-2 {
|
|
|
+ background-color: #f6ffed;
|
|
|
+ color: #52c41a;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 付款列表 - 卡片式展示
|
|
|
+.payment-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.payment-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+
|
|
|
+ button {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 付款列表 - 卡片式展示
|
|
|
+.payment-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.payment-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+
|
|
|
+ button {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.payment-card {
|
|
|
+ border: 1px solid #e5e5e5;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: #fff;
|
|
|
+ margin-bottom: 8px;
|
|
|
+
|
|
|
+ .payment-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .payment-main-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .payment-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .payment-expand {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .payment-detail {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-top: 1px solid #e5e5e5;
|
|
|
+
|
|
|
+ .detail-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .detail-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-value {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.empty-payments {
|
|
|
+ text-align: center;
|
|
|
+ padding: 30px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+// 付款状态标签样式
|
|
|
+.status-tag {
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+
|
|
|
+ &.status-0 {
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.status-1 {
|
|
|
+ background-color: #ffecdb;
|
|
|
+ color: #ff8800;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.status-2 {
|
|
|
+ background-color: #e8f5e9;
|
|
|
+ color: #4caf50;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 采购申请单编号样式
|
|
|
+.purchase-number {
|
|
|
+ color: #007aff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+// 选择器弹窗样式
|
|
|
+.selector-popup {
|
|
|
+ // ✅ 关键:使用固定宽度
|
|
|
+ width: 580px;
|
|
|
+ max-width: 95vw;
|
|
|
+ min-width: 300px;
|
|
|
+ max-height: 70vh;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
+
|
|
|
+ position: relative;
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ .popup-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 15px;
|
|
|
+ border-bottom: 1px solid #e5e5e5;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .popup-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ max-width: calc(100% - 30px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 物料来源选择
|
|
|
+ .material-source-selector {
|
|
|
+ padding: 10px 15px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .source-options {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .source-option {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ padding: 8px 12px;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background-color: #007aff;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 采购申请单选择器
|
|
|
+ .purchase-selector {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px 15px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .picker {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ padding: 8px 12px;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ button[type="warn"] {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 0 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-bar {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px 15px;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ button {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-content {
|
|
|
+ max-height: 50vh;
|
|
|
+ width: 100%;
|
|
|
+ overflow-x: hidden;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .selector-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .selector-item-content {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ .item-info {
|
|
|
+ flex: 1 1 auto !important;
|
|
|
+ min-width: 0 !important;
|
|
|
+ max-width: none !important;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .item-name {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ flex-basis: 100%;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ word-break: break-all;
|
|
|
+ overflow-wrap: break-word;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-purchase {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #007aff;
|
|
|
+ flex-basis: 100%;
|
|
|
+ margin-bottom: 2px;
|
|
|
+ word-break: break-all;
|
|
|
+ overflow-wrap: break-word;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-code,
|
|
|
+ .item-spec,
|
|
|
+ .item-extra {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-weight: 500;
|
|
|
+ flex-shrink: 0;
|
|
|
+ max-width: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-code::after,
|
|
|
+ .item-spec::after {
|
|
|
+ content: ' | ';
|
|
|
+ margin: 0 4px;
|
|
|
+ color: #ddd;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-spec:last-child::after,
|
|
|
+ .item-extra:last-child::after {
|
|
|
+ content: '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .selected-tag {
|
|
|
+ color: #409eff;
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ background-color: #ecf5ff;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-text,
|
|
|
+ .no-more-text {
|
|
|
+ padding: 16px;
|
|
|
+ text-align: center;
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-data {
|
|
|
+ padding: 40px 16px;
|
|
|
+ text-align: center;
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|