Jelajahi Sumber

合同、采购流程,以及相关代码调整

ouyj 3 minggu lalu
induk
melakukan
bd02373238

+ 244 - 0
api/contract.js

@@ -0,0 +1,244 @@
+import request from '@/utils/request'
+
+const preUrl = '/clientServices.do?iscrypt=1'
+
+/**
+ * 获取合同流程初始化数据
+ */
+export function getContractInitData(useId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getInitData',
+				useId: useId
+			}
+		}
+	})
+}
+
+/**
+ * 发起合同流程(包含表单、附件)
+ * @param {String} useId - 用户 ID
+ * @param {Object} formData - 表单数据(包含 baseForm、contractMaterialList 和 contractPaymentList)
+ * @param {Object} processInfo - 流程信息(包含 modelId, tmodelId, formId, fileIds 等)
+ */
+export function startContractProcess(useId, formData, processInfo) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'startContract',
+				useId: useId,
+				formData: formData,
+				flowInfo: processInfo // 流程信息(包含 modelId, tmodelId, formId, fileIds 等)
+			}
+		}
+	})
+}
+
+/**
+ * 选择物料列表(分页)
+ * @param {String} useId - 用户 ID
+ * @param {number} page - 页码(从 1 开始)
+ * @param {number} pageSize - 每页条数
+ * @param {string} itemName - 物料名称(可选)
+ */
+export function getMaterialList(useId, page = 1, pageSize = 20, itemName = '') {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'selectMaterial',
+				useId: useId,
+				page: page,
+				pageSize: pageSize,
+				itemName: itemName
+			}
+		}
+	})
+}
+
+/**
+ * 选择供应商列表(分页)
+ * @param {String} useId - 用户 ID
+ * @param {number} page - 页码(从 1 开始)
+ * @param {number} pageSize - 每页条数
+ * @param {string} supplierName - 供应商名称(可选)
+ */
+export function getSupplierList(useId, page = 1, pageSize = 20, supplierName = '') {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'selectSupplier',
+				useId: useId,
+				page: page,
+				pageSize: pageSize,
+				supplierName: supplierName
+			}
+		}
+	})
+}
+
+/**
+ * 获取合同类型列表
+ * @param {String} useId - 用户 ID
+ */
+export function getContractTypeList(useId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getContractTypeList',
+				useId: useId
+			}
+		}
+	})
+}
+
+/**
+ * 获取付款方式列表
+ * @param {String} useId - 用户 ID
+ */
+export function getPaymentTypeList(useId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getPaymentTypeList',
+				useId: useId
+			}
+		}
+	})
+}
+
+/**
+ * 选择供应商
+ * @param {String} useId - 用户 ID
+ * @param {string} supplierCode - 供应商编码
+ */
+export function selectSupplier(useId, supplierCode) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'selectSupplier',
+				useId: useId,
+				supplierCode: supplierCode
+			}
+		}
+	})
+}
+
+/**
+ * 选择经办人
+ * @param {String} useId - 用户 ID
+ * @param {string} salesmanId - 经办人 ID
+ */
+export function selectSalesman(useId, salesmanId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'selectSalesman',
+				useId: useId,
+				salesmanId: salesmanId
+			}
+		}
+	})
+}
+
+// 获取合同单数据(用于审批页面展示)
+// 根据 formInsId 获取合同单的详细信息
+export function getContractFormData(useId, formInsId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		isSession: true,
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getContractFormData',
+				useId,
+				formInsId
+			}
+		}
+	})
+}
+
+// 获取合同单数据(用于查看页面)
+// 根据流程实例 ID(insId)获取合同单的详细信息
+export function getContractDataByInsId(useId, insId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		isSession: true,
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getContractDataByInsId',
+				useId,
+				insId
+			}
+		}
+	})
+}
+
+/**
+ * 获取采购申请单列表(与 PC 端一致,不分页)
+ * @param {String} useId - 用户 ID
+ */
+export function getPurchaseApplyList(useId) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getPurchaseApplyList',
+				useId: useId
+			}
+		}
+	})
+}
+
+/**
+ * 获取采购申请单详情列表(物料明细,分页)
+ * @param {String} useId - 用户 ID
+ * @param {string} contractPurchaseFormId - 采购申请单 ID(可选,为空时查询所有物料)
+ * @param {number} page - 页码(从 1 开始)
+ * @param {number} pageSize - 每页条数
+ */
+export function getPurchaseDetailList(useId, contractPurchaseFormId = '', page = 1, pageSize = 10) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'miniapp_contractFlowCustom',
+			params: {
+				task: 'getPurchaseDetailList',
+				useId: useId,
+				contractPurchaseFormId: contractPurchaseFormId,
+				page: page,
+				pageSize: pageSize
+			}
+		}
+	})
+}

+ 21 - 4
api/process.js

@@ -46,7 +46,7 @@ export function getUserProcessed(params) {
 				page: params.page,
 				pageNum: params.pageNum,
 				modelId: params.modelId, //流程类型
-				control: params.control, //流程控制方式 0 手写 1 引擎
+				//control: params.control, //流程控制方式 0 手写 1 引擎
 				queryParams: params.queryParams,
 			}
 		}
@@ -65,7 +65,7 @@ export function getUserProcess(params) {
 				page: params.page,
 				pageNum: params.pageNum,
 				modelId: params.modelId, //流程类型
-				control: params.control, //流程控制方式 0 手写 1 引擎
+				//control: params.control, //流程控制方式 0 手写 1 引擎
 				queryParams: params.queryParams,
 			}
 		}
@@ -109,7 +109,7 @@ export function getUserProcessing(params) {
 				page: params.page,
 				pageNum: params.pageNum,
 				modelId: params.modelId, //流程类型
-				control: params.control, //流程控制方式 0 手写 1 引擎
+				//control: params.control, //流程控制方式 0 手写 1 引擎
 				queryParams: params.queryParams,
 			}
 		}
@@ -275,4 +275,21 @@ export function getTacheInfo(tinsId) {
 			}
 		}
 	})
-}
+}
+
+// 通用流程审批接口(支持通过、退回)
+// 参考 submitProcessFlow 实现
+export function commonProcessApproval(flow, form, control) {
+	return request({
+		url: preUrl,
+		method: 'post',
+		data: {
+			serviceId: 'bpm_common_process_approval',
+			params: {
+				flow,
+				form,
+				control
+			}
+		}
+	})
+}

+ 2363 - 0
components/processForms/contract-form.vue

@@ -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>

+ 35 - 17
components/processForms/purchase-form.vue

@@ -435,11 +435,6 @@ watch(() => props.formData, (newVal) => {
 		} else {
 			materialList.value = []
 		}
-				
-		// 处理 formElements,提取可编辑字段配置
-		if (props.formElements && props.formElements.length > 0) {
-			console.log('采购表单组件:处理 formElements 配置', props.formElements)
-		}
 	}
 }, { immediate: true, deep: true })
 
@@ -703,9 +698,23 @@ defineExpose({
 		if (!baseForm.value.contractPurchaseName) {
 			return Promise.reject(new Error('请输入采购单名称'))
 		}
+		// 验证采购单名称长度
+		if (baseForm.value.contractPurchaseName && baseForm.value.contractPurchaseName.length > 100) {
+			return Promise.reject(new Error('采购单名称长度不能超过 100 个字符'))
+		}
 		if (!materialList.value || materialList.value.length === 0) {
 			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`))
+				}
+			}
+		}
 		// 验证审批意见字段(如果在 table_fields 中)
 		const approvalFields = [
 			{ fieldName: 'departmental_opinion', msg: '部门意见' },
@@ -720,8 +729,7 @@ defineExpose({
 				// 检查该字段是否在当前环节的可编辑字段列表中
 				// editableFields 包含了当前环节可编辑的所有字段(即 table_fields)
 				if (props.editableFields && props.editableFields.includes(field.fieldName)) {
-					console.log('【验证】检查字段:', field.fieldName, ', defaultValue:', elem.defaultValue)
-					if (!elem.defaultValue || elem.defaultValue === '') {
+				  if (!elem.defaultValue || elem.defaultValue === '') {
 						return Promise.reject(new Error(field.msg + '不能为空!'))
 					}
 				}
@@ -765,13 +773,18 @@ defineExpose({
 						value: `${sealInfo.sealFileId}_${sealInfo.left}_${sealInfo.top}`,
 						type: '0'
 					})
-				} else if (elem.sealImgPath) {
-					// 兼容旧数据(如果没有印章信息,仅传递图片路径)
-					formElements.push({
-						name: fieldName + '_imgval',
-						value: elem.sealImgPath,
-						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'
+						})
+					}
 				}
 			}
 		})
@@ -805,9 +818,14 @@ defineExpose({
 				if (approvalSealInfo.value[fieldName]) {
 					const sealInfo = approvalSealInfo.value[fieldName]
 					formData[fieldName + '_imgval'] = `${sealInfo.sealFileId}_${sealInfo.left}_${sealInfo.top}`
-				} else if (elem && elem.sealImgPath) {
-					// 兼容旧数据(如果没有印章信息,仅传递图片路径)
-					formData[fieldName + '_imgval'] = elem.sealImgPath
+				} 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`
+					}
 				}
 			}
 		})

+ 1 - 1
components/ygoa/processList.vue

@@ -276,7 +276,7 @@ function getLabelBgColor(label) {
  * @returns 键值对数组,格式如[{key: '项目名称', value: '测试测试'}, ...]
  */
 function parseInsSubName2(data) {
-	debugger
+	//debugger
   if (!data || data == null) {
     return [];
   }

+ 8 - 0
pages.json

@@ -195,6 +195,14 @@
 				"enablePullDownRefresh" : false
 			}
 		},
+		{
+			"path" : "pages/work/contract/start",
+			"style" :
+			{
+				"navigationBarTitleText" : "合同申请",
+				"enablePullDownRefresh" : false
+			}
+		},
 		{
 			"path": "pages/process/common/detail",
 			"style": {

+ 38 - 6
pages/message/index.vue

@@ -147,8 +147,22 @@ function getProcessPage({ pSize, pageNo }, callback) {
 	});
 }
 // 点击待办消息列表项
-function handleToProcessDetail({ insId, tinsId, insName, control }) {
-	$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&tinsId=' + tinsId + '&insName=' + insName + '&control=' + control)
+function handleToProcessDetail({ insId, tinsId, insName, control, modelId }) {
+  if (control == '0' && modelId) {
+    if(modelId == '200001' || modelId == '200002'){
+      // 跳转到通用审批页面
+      let url = '/pages/process/common/detail?insId=' + insId + '&insName=' + insName + '&control=' + control + '&modelId=' + modelId
+      if (tinsId) { // 排除抄送流程的 tinsId
+        url = url + '&tinsId=' + tinsId
+      }
+      $tab.navigateTo(url)
+      return
+    }else{
+      $tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&tinsId=' + tinsId + '&insName=' + insName + '&control=' + control)
+    }
+  }else{
+    $tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&tinsId=' + tinsId + '&insName=' + insName + '&control=' + control)
+  }
 }
 
 // 获取公告列表数据
@@ -246,7 +260,7 @@ function handleToMessageDetail({ messageid, universalid, jump_id, title }) {
 			}).catch(() => { })
 		}
 	})
-	debugger
+	//debugger
 	if(jump_id) {
 		try {
 			// 解析jump_id JSON字符串
@@ -257,16 +271,34 @@ function handleToMessageDetail({ messageid, universalid, jump_id, title }) {
 				getTacheInfo(tinsId).then(({returnParams}) => {
 					if(returnParams.oldTacheStatus == 1) {
 						//跳到流程办理页面
-						$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&tinsId=' + tinsId + '&insName=' + insName + '&control=1')
+            if(modelId == '200001' || modelId == '200002'){
+              let url = '/pages/process/common/detail?insId=' + insId + '&insName=' + insName + '&control=0&modelId=' + modelId
+              if (tinsId) {
+                url = url + '&tinsId=' + tinsId
+              }
+              $tab.navigateTo(url)
+            }else{
+              $tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&tinsId=' + tinsId + '&insName=' + insName + '&control=1')
+            }
 					} else {
 						//跳到流程到流程查看页面
-						$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=0')
+            if(modelId == '200001' || modelId == '200002'){
+              let url = '/pages/process/common/detail?insId=' + insId + '&insName=' + insName + '&control=0&modelId=' + modelId
+              $tab.navigateTo(url)
+            }else{
+              $tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=0')
+            }
 					}
 				})
 				
 			} else if (opType === 'view') {
 				//跳到流程到流程查看页面
-				$tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=0')
+        if(modelId == '200001' || modelId == '200002'){
+          let url = '/pages/process/common/detail?insId=' + insId + '&insName=' + insName + '&control=0&modelId=' + modelId
+          $tab.navigateTo(url)
+        }else{
+          $tab.navigateTo('/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=0')
+        }
 			} else {
 				// 未知操作类型,跳转到消息详情页
 				$tab.navigateTo('/pages/message/detail/index?messageId=' + messageid + '&universalId=' + universalid)

+ 195 - 70
pages/process/common/detail.vue

@@ -16,6 +16,19 @@
 				@update="handleFormUpdate"
 				@signature-change="handleSignatureChange" />
 			
+			<!-- 合同流程表单 -->
+			<contract-form 
+				v-else-if="formComponentName === 'contract-form'"
+				ref="dynamicFormRef" 
+				:formData="{...formData, ...contractData}" 
+				:formElements="formElements"
+				:repeatingForm="repeatingForm"
+				:is-initiate="flowInfo.seModel == '1'"
+				:editable-fields="editableFields"
+				:current-tache-opinion="currentTacheOpinion"
+				@update="handleFormUpdate"
+				@signature-change="handleSignatureChange" />
+			
 			<!-- 默认表单 -->
 			<default-form 
 				v-else
@@ -147,6 +160,34 @@
 				</uni-card>
 			</view>
 		</view>
+		
+		<!-- 撤销区域(查看页面专用) -->
+		<view v-if="!processInfo.tinsId && isCancel">
+			<view class="remark_container">
+				<uni-card>
+					<uni-section titleFontSize="1.3rem" title="撤销备注" type="line"></uni-section>
+					<view class="remark_content">
+						<uni-easyinput 
+							type="textarea" 
+							autoHeight 
+							v-model="remark" 
+							placeholder="请输入"
+							placeholderStyle="font-size: calc(14px + 1.2*(1rem - 16px))"></uni-easyinput>
+					</view>
+				</uni-card>
+			</view>
+			<view class="cancel_button_container">
+				<uni-card spacing="0" padding="0">
+					<button 
+						:disabled="!button_state" 
+						:loading="!button_state" 
+						type="warn"
+						@click="handleCancelProcess">
+						撤销
+					</button>
+				</uni-card>
+			</view>
+		</view>
 	</view>
 </template>
 
@@ -155,6 +196,7 @@ import { computed, onMounted, reactive, ref, nextTick } from 'vue'
 import { onLoad, onShow } from '@dcloudio/uni-app'
 import attachmentList from '@/components/ygoa/attachmentList.vue'
 import purchaseForm from '@/components/processForms/purchase-form.vue'
+import contractForm from '@/components/processForms/contract-form.vue'
 import defaultForm from '@/components/processForms/default-form.vue'
 import config from '@/config.js'
 import $modal from '@/plugins/modal.js'
@@ -166,10 +208,12 @@ import {
 	getProcessFlow, 
 	uploadFile, 
 	commonProcessApproval,
+	cancelProcessFlow // 新增:撤销流程
 } from '@/api/process.js'
 import { useUserStore } from '@/store/user.js'
 import { keepSession } from '@/api/login.js'
 import { getPurchaseFormData, getPurchaseDataByInsId   } from '@/api/purchase.js'
+import { getContractFormData, getContractDataByInsId } from '@/api/contract.js'
 
 const userStore = useUserStore()
 
@@ -186,17 +230,21 @@ const processInfo = reactive({
 })
 
 onLoad((options) => {
-	processInfo.insId = options.insId || ''
-	processInfo.insName = options.insName || ''
+  processInfo.insId = options.insId || ''
+	// 注意:options.insName 可能是字符串 "undefined",需要特殊处理
+	if (options.insName && options.insName !== 'undefined' && options.insName !== 'null') {
+		processInfo.insName = options.insName
+	}
 	processInfo.control = options.control || '1'
 	processInfo.modelId = options.modelId || ''
 	if (options.tinsId) {
 		processInfo.tinsId = options.tinsId
 	}
-	
-	// 设置导航栏标题
+
+  const title = processInfo.insName || '流程信息';
+  // 设置导航栏标题
 	uni.setNavigationBarTitle({
-		title: processInfo.insName
+		title: title
 	});
 })
 
@@ -205,6 +253,7 @@ const formComponentName = computed(() => {
 	// 根据 modelId 映射到对应的组件
 	const componentMap: Record<string, string> = {
 		'200001': 'purchase-form', // 采购流程表单组件
+		'200002': 'contract-form', // 合同流程表单组件
 		// 后续添加更多映射
 	}
 	return componentMap[processInfo.modelId] || 'default-form'
@@ -213,10 +262,12 @@ const formComponentName = computed(() => {
 // 表单数据(传递给自定义组件)
 const formData = ref<any>({}) // BPM 流程表单数据
 const purchaseData = ref<any>({}) // 采购单业务数据
+const contractData = ref<any>({}) // 合同业务数据
 const formElements = ref<any[]>([])
 const repeatingForm = ref<any>({ elementItem: [], elements: [] })
 const fileList = ref<any[]>([]) // 已有附件列表
 const dynamicFormRef = ref(null)
+const isCancel = ref(false) // 是否可撤销
 
 // 采购流程专用字段
 const editableFields = ref<string[]>([]) // 当前环节可编辑的字段列表
@@ -254,11 +305,15 @@ async function loadProcessDetail() {
 				
 			if (formRes.returnCode === '0' || formRes.returnCode === '1') {
 				const formInfo = formRes.returnParams
-								
+										
 				formElements.value = formInfo.formElements || []
 				repeatingForm.value = formInfo.repeatingForm || { elementItem: [], elements: [] }
 				formData.value = formInfo
-							
+			// 判断是否可撤销
+			if (formInfo.isCancel == 1) {
+				isCancel.value = true
+			}
+									
 				// 处理附件列表 - 从后端接口获取(如果有)
 				if (formInfo.fileList) {
 					fileList.value = formInfo.fileList
@@ -283,6 +338,10 @@ async function loadProcessDetail() {
 				formElements.value = formInfo.formElements || []
 				repeatingForm.value = formInfo.repeatingForm || { elementItem: [], elements: [] }
 				formData.value = formInfo
+			// 判断是否可撤销
+			if (formInfo.isCancel == 1) {
+				isCancel.value = true
+			}
 					
 				// 处理附件列表
 				if (formInfo.fileList) {
@@ -297,9 +356,11 @@ async function loadProcessDetail() {
 			await loadFlowSteps()
 		}
 			
-		// 如果是采购流程,额外加载采购单数据(需要在 flowInfo 加载完成后才能获取 table_fields)
+		// 如果是采购流程或合同流程,额外加载业务数据(需要在 flowInfo 加载完成后才能获取 table_fields)
 		if (processInfo.modelId === '200001') {
 			await loadPurchaseFormData()
+		} else if (processInfo.modelId === '200002') {
+			await loadContractFormData()
 		}
 	} catch (error) {
 		console.error('加载流程详情失败:', error)
@@ -393,6 +454,52 @@ async function loadPurchaseFormData() {
 	}
 }
 
+// 加载合同数据(用于审批页面展示)
+async function loadContractFormData() {
+	try {
+		// 优先使用 flowInfo 中的 formInsId
+		let formInsId = flowInfo.value.formInsId || ''
+		
+		if (!formInsId) {
+			// 如果 flowInfo 中没有,尝试从 formData 中获取
+			formInsId = formData.value.formInsId || formData.value.universalid || ''
+		}
+		
+		if (formInsId) {
+			// ✅ 有 formInsId:调用 getContractFormData
+			const res = await getContractFormData(userStore.user.useId, formInsId)
+			
+			if (res.returnCode === '1') {
+				const contractDataRes = res.returnParams
+				
+				// 将合同数据单独存储
+				contractData.value = contractDataRes
+				
+				// 保存当前环节的审批意见配置
+				currentTacheOpinion.value = flowInfo.value.tmodel
+			}
+		} else if (processInfo.insId && !processInfo.tinsId) {
+			// TODO: 如果需要查看页面,调用 getContractDataByInsId
+			const res = await getContractDataByInsId(userStore.user.useId, processInfo.insId)
+			
+			if (res.returnCode === '1') {
+				const contractDataRes = res.returnParams
+				
+				// 将合同数据单独存储
+				contractData.value = contractDataRes
+				
+				// 注意:查看场景没有 table_fields,所以不设置 editableFields
+				editableFields.value = [] // 查看场景不可编辑
+				currentTacheOpinion.value = null
+			}
+		} else {
+			console.warn('缺少 formInsId 和 insId,无法加载合同数据')
+		}
+	} catch (error) {
+		console.error('加载合同数据失败:', error)
+	}
+}
+
 // 获取流程控制信息
 async function loadFlowInfo() {
 	try {
@@ -486,19 +593,13 @@ function deleteFile(file: any) {
 
 // 提交审批
 function handleSubmitProcess(result) {
-	console.log('=== 点击审批按钮 ===')
-	console.log('result:', result)
-	
 	let content = '确认退回'
 	if (result == "1") {
 		content = '确认通过'
 	}
-	console.log('确认内容:', content)
 	
 	$modal.confirm(content).then(() => {
-		console.log('=== 用户点击确认 ===')
 		button_state.value = false
-		console.log('准备调用 submitProcess, result:', result)
 		if (result == "1") {
 			submitProcess(result)
 		} else {
@@ -510,11 +611,6 @@ function handleSubmitProcess(result) {
 }
 
 function submitProcess(result) {
-	console.log('=== 进入 submitProcess ===')
-	console.log('result:', result)
-	console.log('modelId:', processInfo.modelId)
-	console.log('dynamicFormRef:', dynamicFormRef.value)
-	
 	let flow = Object.assign({}, flowInfo.value)
 	flow['staffId'] = userStore.user.useId
 	flow['gxId'] = userStore.user.gxId
@@ -545,71 +641,84 @@ function submitProcess(result) {
 	// 构建 form 参数
 	const form = {
 		formId: flowInfo.value.formId || '',
-		formInsId: purchaseData.value.lFormInsId || purchaseData.value.universalid || formData.value.formInsId || formData.value.universalid || '',
+		formInsId: '', // 稍后根据 modelId 设置
 		formElements: formElements.value
 	}
 	
-	console.log('构建的 form 对象:', form)
-	console.log('是否为采购流程:', processInfo.modelId === '200001')
-	
-	// 如果是采购流程,直接传递 purchaseFormData
-	if (processInfo.modelId === '200001' && dynamicFormRef.value) {
-		console.log('进入采购流程验证分支')
-			
-		// 【关键修复】只有“通过”操作才需要验证表单,退回不需要
+	// 根据不同的 modelId,从对应的业务数据中获取 formInsId
+	if (processInfo.modelId === '200001') {
+		// 采购流程:从 purchaseData 获取
+		form.formInsId = purchaseData.value.lFormInsId || purchaseData.value.universalid || ''
+	} else if (processInfo.modelId === '200002') {
+		// 合同流程:从 contractData 获取
+		form.formInsId = contractData.value.lFormInsId || contractData.value.universalid || ''
+	} else {
+		// 其他流程:从 formData 获取
+		form.formInsId = formData.value.formInsId || formData.value.universalid || ''
+	}
+		
+	// 如果是采购流程或合同流程,需要特殊处理
+	if ((processInfo.modelId === '200001' || processInfo.modelId === '200002') && dynamicFormRef.value) {
+		// 只有“通过”操作才需要验证表单,退回不需要
 		if (result == "1") {
-			// 调用采购表单组件的 validate 方法进行验证
+			// 调用表单组件的 validate 方法进行验证
 			dynamicFormRef.value.validate()
 				.then(() => {
-					console.log('表单验证通过')
-					console.log('formData.value:', formData.value)
-					console.log('formData.value.table_fields:', formData.value.table_fields)
-					console.log('formElements.value:', formElements.value)
-						
-					// 【关键修复】从 formElements 中提取可编辑字段的 tableField
+					// 从 formElements 中提取可编辑字段的 tableField
 					// table_fields 应该只包含当前环节可编辑的字段 (canEdit == '1')
 					const tableFieldsList = formElements.value
 						.filter(elem => elem.canEdit == '1')  // ✅ 只提取可编辑字段
 						.map(elem => elem.tableField)
 						.filter(f => f)  // 过滤掉空值
 					const tableFieldsStr = ',' + tableFieldsList.join(',') + ','
-					console.log('从 formElements 提取的可编辑 table_fields:', tableFieldsStr)
-					console.log('可编辑字段列表:', tableFieldsList)
-						
-					// 验证通过,获取采购表单数据(包含审批意见和印章)
-					const purchaseFormData = dynamicFormRef.value.getFormData()
+							
+					// 验证通过,获取表单数据(包含审批意见和印章)
+					const businessFormData = dynamicFormRef.value.getFormData()
 									
-					// 【关键修复】添加 table_fields 字段,后端需要它来判断哪些审批意见字段需要处理
-					purchaseFormData.table_fields = tableFieldsStr
+					// 添加 table_fields 字段,后端需要它来判断哪些审批意见字段需要处理
+					businessFormData.table_fields = tableFieldsStr
 									
-					// 直接将 purchaseFormData 转换为 formElements 格式
-					const purchaseFormElements = []
-					Object.keys(purchaseFormData).forEach(key => {
+					// 直接将 businessFormData 转换为 formElements 格式
+					const businessFormElements = []
+					Object.keys(businessFormData).forEach(key => {
 						// 跳过不需要提交的字段
-						if (key !== 'details' && key !== 'detailList') {
-							const value = purchaseFormData[key]
-							purchaseFormElements.push({
+						if (key !== 'details' && key !== 'detailList' && key !== 'contractMaterialList' && key !== 'contractPaymentList') {
+							const value = businessFormData[key]
+							businessFormElements.push({
 								name: key,
 								value: String(value),
 								type: '0'
 							})
 						}
 					})
-						
-					// 物料明细需要转成 JSON 字符串
-					if (purchaseFormData.detailList && Array.isArray(purchaseFormData.detailList)) {
-						purchaseFormElements.push({
+									
+					// 物料明细和付款明细需要转成 JSON 字符串
+					if (businessFormData.detailList && Array.isArray(businessFormData.detailList)) {
+						businessFormElements.push({
 							name: 'detailList',
-							value: JSON.stringify(purchaseFormData.detailList),
+							value: JSON.stringify(businessFormData.detailList),
+							type: '0'
+						})
+					}
+					if (businessFormData.contractMaterialList && Array.isArray(businessFormData.contractMaterialList)) {
+						businessFormElements.push({
+							name: 'contractMaterialList',
+							value: JSON.stringify(businessFormData.contractMaterialList),
 							type: '0'
 						})
 					}
-						
+					if (businessFormData.contractPaymentList && Array.isArray(businessFormData.contractPaymentList)) {
+						businessFormElements.push({
+							name: 'contractPaymentList',
+							value: JSON.stringify(businessFormData.contractPaymentList),
+							type: '0'
+						})
+					}
+									
 					// 合并到 formElements 中
-					form.formElements = [...formElements.value, ...purchaseFormElements]
-						
+					form.formElements = [...formElements.value, ...businessFormElements]
+									
 					// 提交审批
-					console.log('调用 submitApproval (采购流程)')
 					submitApproval(flow, form)
 				})
 				.catch(err => {
@@ -619,30 +728,18 @@ function submitProcess(result) {
 				})
 		} else {
 			// 退回操作,不验证表单,直接提交
-			console.log('退回操作,不验证表单,直接提交')
 			submitApproval(flow, form)
 		}
 	} else {
-		console.log('进入非采购流程分支')
-		// 非采购流程,直接提交
+		// 非采购/合同流程,直接提交
 		submitApproval(flow, form)
 	}
 }
 
 // 执行审批提交
 function submitApproval(flow, form) {
-	console.log('=== 开始调用审批接口 ===')
-	console.log('flow 参数:', JSON.stringify(flow, null, 2))
-	console.log('form 参数:', JSON.stringify(form, null, 2))
-	console.log('control:', processInfo.control)
-	
 	// 调用通用审批接口
 	commonProcessApproval(flow, form, processInfo.control).then(({ returnCode, returnMsg, returnParams }) => {
-		console.log('=== 接口响应 ===')
-		console.log('returnCode:', returnCode)
-		console.log('returnMsg:', returnMsg)
-		console.log('returnParams:', returnParams)
-		
 		if (returnMsg.includes('提交失败')) {
 			// 启用提交按钮
 			button_state.value = true
@@ -663,6 +760,34 @@ function submitApproval(flow, form) {
 	})
 }
 
+// 取消流程
+function handleCancelProcess() {
+	if (remark.value.trim() == '') {
+		$modal.msgError('备注不能为空!')
+		return
+	}
+	$modal.confirm('确认撤销').then(() => {
+		cancelProcess()
+	}).catch(() => { })
+}
+
+function cancelProcess() {
+	cancelProcessFlow(userStore.user.useId, remark.value, processInfo).then(({ returnMsg }) => {
+		if (returnMsg.includes('success')) {
+			$modal.msgSuccess('撤销成功')
+			// 通知列表刷新数据
+			uni.$emit('ReloadProcessData');
+			setTimeout(() => {
+				$tab.navigateBack();
+			}, 1000)
+		} else {
+			// 启用按钮
+			button_state.value = true
+			$modal.msgError(returnMsg)
+		}
+	})
+}
+
 // 保持会话
 onShow(() => {
 	keepSession().catch(err => {

+ 2 - 2
pages/process/detail/index.vue

@@ -477,7 +477,7 @@
 	const isCancel = ref(false)
 	// 获取流程表单
 	function initProcessForm() {
-		debugger
+		//debugger
 		if (processInfo.tinsId) {
 			// 待办审批流程表单数据todo 
 			getProcessFormInfoInFlow(userStore.user.useId, processInfo).then(({ returnParams }) => {
@@ -524,7 +524,7 @@
 	})
 	// 获取流程信息
 	function initProcessInfo() {
-		debugger
+		//debugger
 		getProcessFlow(userStore.user.useId, processInfo).then(({ returnParams }) => {
 			options.value = returnParams.list.map((item, index) => {
 				const { tmodelName, name, createdate, finishdate, remark, state, task, groupName, positionName } = item

+ 28 - 10
pages/process/index.vue

@@ -141,7 +141,7 @@
 		uni.$emit('showTabBarBadge')
 	})
 	const userStore = useUserStore()
-	onLoad(({ insId, tinsId, insName, control }) => {
+	onLoad(({ insId, tinsId, insName, control, modelId }) => {
 		if (insId) {
 			const username = userStore.user.name
 			getProcessFormInfo( userStore.user.useId, insId ).then(({ returnParams }) => {
@@ -151,7 +151,7 @@
 					//流程流转到当前用户且流程未撤销
 					tinsId = lastChecker.l_tins_id
 				}
-				handleToProcessDetail({ username, insId, tinsId, insName, control })
+				handleToProcessDetail({ username, insId, tinsId, insName, control, modelId})
 			})
 		}
 		const staffId = userStore.user.useId
@@ -188,7 +188,7 @@
 	}
 	// 选中搜索项
 	function clickSearchItem(item) {
-		debugger
+		//debugger
 		// searchItem.value = item
 		// if (item == 1 && pickerItems.value.length == 1) initPickerItems()
 		if(item == "createUser") {
@@ -293,7 +293,7 @@
 			page: 1,
 			pageNum: 9999, // 获取所有待办流程
 			modelId: "",
-			control: 1,
+			//control: 1,
 			queryParams: {}
 		}
 		
@@ -365,7 +365,7 @@
 			page: pageNo,
 			pageNum: pSize,
 			modelId: "",
-			control: 1,
+			//control: 1,
 			queryParams: queryParams.value
 		}
 		requestMap[current.value](params).then(( { returnParams, Rows, Total } ) => {
@@ -386,7 +386,7 @@
 			page: pageNo,
 			pageNum: pSize,
 			modelId: "",
-			control: 1,
+			//control: 1,
 			queryParams: queryParams.value
 		}
 		requestMap[current.value](params).then(( { returnParams, Rows, Total } ) => {
@@ -398,13 +398,31 @@
 		})
 	}
 	// 跳转到流程详情页
-	function handleToProcessDetail({ username, insId, tinsId, insName, control }) {
-		debugger
+	function handleToProcessDetail({ username, insId, tinsId, insName, control, modelId }) {
+		//debugger
+		// 判断是否为特殊流程的审批
+		if (control == '0' && modelId) {
+      if(modelId == '200001' || modelId == '200002'){
+        // 跳转到通用审批页面
+        let url = '/pages/process/common/detail?insId=' + insId + '&insName=' + insName + '&control=' + control + '&modelId=' + modelId
+        if (tinsId && current.value != 1) { // 排除抄送流程的 tinsId
+          url = url + '&tinsId=' + tinsId
+        }
+        $tab.navigateTo(url)
+        return
+      }else{
+        let url = '/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=' + control
+        if (tinsId && current.value != 1) { // 抄送流程的 tinsId
+          url = url + '&tinsId=' + tinsId
+        }
+        $tab.navigateTo(url)
+      }
+		}
+		// 原有通用流程审批逻辑
 		let url = '/pages/process/detail/index?insId=' + insId + '&insName=' + insName + '&control=' + control
-		if (tinsId && current.value != 1) { // 排除抄送流程的tinsId
+		if (tinsId && current.value != 1) { // 抄送流程的 tinsId
 			url = url + '&tinsId=' + tinsId
 		}
-		// console.log('url', url)
 		$tab.navigateTo(url)
 	}
 	function handleToCancelProcess(process) {

+ 1842 - 0
pages/work/contract/start.vue

@@ -0,0 +1,1842 @@
+<template>
+  <view class="contract-container">
+    <!-- 基本信息 -->
+    <uni-card title="基本信息" spacing="0">
+      <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" placeholder="请输入合同名称"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="contract_type" label="合同类型" required>
+          <picker @change="onContractTypeChange" :range="contractTypeList" range-key="contract_type_name">
+            <view class="picker">{{ baseForm.contract_type_name || '请选择合同类型' }}</view>
+          </picker>
+        </uni-forms-item>
+        <uni-forms-item name="applyDate" label="申请日期">
+          <uni-easyinput v-model="baseForm.applyDate" type="date" disabled />
+        </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-model="baseForm.salesman_name" disabled placeholder="自动生成"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="salesmanTel" label="联系电话">
+          <uni-easyinput v-model="baseForm.salesmanTel" type="number" placeholder="请输入联系电话"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="supplierName" label="供方" required>
+          <view class="selector-wrapper" @click="openSupplierSelector">
+            <text v-if="baseForm.supplierName">{{ baseForm.supplierName }}</text>
+            <text v-else class="placeholder">请选择供应商</text>
+          </view>
+        </uni-forms-item>
+        <uni-forms-item name="firstparty_name" label="需方">
+          <uni-easyinput v-model="baseForm.firstparty_name" disabled placeholder="自动生成"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="contract_money" label="合同金额">
+          <uni-easyinput
+              v-model="baseForm.contract_money"
+              type="digit"
+              placeholder="请输入合同金额(自动计算或手动输入)"
+              :readonly="!canEditContractMoney"
+          ></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="contractContent" label="合同内容">
+          <uni-easyinput v-model="baseForm.contractContent" type="textarea" placeholder="请输入合同内容"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="projectItem" label="所属项目或产品">
+          <uni-easyinput v-model="baseForm.projectItem" placeholder="请输入所属项目或产品"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="usePosition" label="使用位置">
+          <uni-easyinput v-model="baseForm.usePosition" placeholder="请输入使用位置"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="other_contractor" label="对方联系人及电话">
+          <uni-easyinput v-model="baseForm.other_contractor" placeholder="请输入对方联系人及电话"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="otherFile" label="随合同提交的其它材料">
+          <uni-easyinput v-model="baseForm.otherFile" type="textarea" placeholder="请输入其它材料说明"></uni-easyinput>
+        </uni-forms-item>
+        <uni-forms-item name="salesmanSign" label="经办人签字">
+          <uni-easyinput v-model="baseForm.salesmanSign" placeholder="请输入经办人签字"></uni-easyinput>
+        </uni-forms-item>
+      </uni-forms>
+    </uni-card>
+
+    <!-- 物料明细 -->
+    <uni-card title="物品信息" spacing="0">
+      <view 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="index" class="material-card">
+          <view class="material-header" @click="toggleExpand(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">{{ 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"
+                  placeholder="请输入数量"
+                  style="width: 100px; display: inline-block;"
+                  @blur="onQuantityBlur(item)"
+              />
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">税前单价:</text>
+              <uni-easyinput
+                  v-model="item.price"
+                  type="digit"
+                  placeholder="请输入税前单价"
+                  style="width: 120px; display: inline-block;"
+                  @blur="onPriceBlur(item)"
+              />
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">税率 (%):</text>
+              <uni-easyinput
+                  v-model="item.cess"
+                  type="digit"
+                  placeholder="请输入税率"
+                  style="width: 80px; display: inline-block;"
+                  @blur="onCessBlur(item)"
+              />
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">税后单价:</text>
+              <text class="detail-value price-value">{{ item.priceTax }}</text>
+            </view>
+            <!-- 采购申请单编号(仅当从采购申请单添加时显示) -->
+            <view v-if="item.purchaseNumber" class="detail-row purchase-number-row">
+              <text class="detail-label">采购申请单:</text>
+              <text class="detail-value purchase-number">{{ item.purchaseNumber }}</text>
+            </view>
+            <view class="detail-row delete-row">
+              <button type="warn" size="mini" @click="removeMaterial(index)">删除</button>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <view v-else class="empty-materials">
+        <text>暂无物料,请点击上方按钮添加</text>
+      </view>
+    </uni-card>
+
+    <!-- 付款信息 -->
+    <uni-card title="付款信息" spacing="0">
+      <view class="payment-actions">
+        <button type="primary" size="mini" @click="addPaymentInfo" :disabled="materialList.length === 0" plain>添加付款方式</button>
+      </view>
+
+      <!-- 付款方式列表 -->
+      <view v-if="paymentList.length > 0" class="payment-list">
+        <view v-for="(item, index) in paymentList" :key="index" class="payment-card">
+          <view class="payment-header" @click="togglePaymentExpand(index)">
+            <view class="payment-main-info">
+              <text class="payment-type">{{ item.payTypeName }}</text>
+              <text class="payment-proportion">比例:{{ item.proportion }}%</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 @change="(e) => onPaymentTypeChange(index, e)" :range="paymentTypeList" range-key="payTypeName">
+                <view class="picker">
+                  {{ item.payTypeName || '请选择付款方式' }}
+                </view>
+              </picker>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">比例:</text>
+              <uni-easyinput
+                  v-model="item.proportion"
+                  type="digit"
+                  placeholder="请输入比例"
+                  style="width: 80px; display: inline-block;"
+                  @blur="onProportionBlur(item)"
+              />
+              <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"
+                  placeholder="请输入已付金额"
+                  style="width: 120px; display: inline-block;"
+                  @blur="onAmountPaidBlur(item)"
+              />
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">付款状态:</text>
+              <text class="detail-value status-tag" :class="'status-' + item.payStatus">{{ item.payStatusName }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">付款时间:</text>
+              <uni-datetime-picker
+                  ref="payTimePicker"
+                  v-model="item.payTime"
+                  type="date"
+                  placeholder="选择付款时间"
+                  style="display: inline-block;"
+                  @confirm="(e) => onPayTimeConfirm(item, e)"
+              />
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">备注:</text>
+              <uni-easyinput
+                  v-model="item.remark"
+                  placeholder="请输入备注"
+                  style="flex: 1; display: inline-block;"
+              />
+            </view>
+            <view class="detail-row delete-row">
+              <button type="warn" size="mini" @click="removePayment(index)">删除</button>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <view v-else class="empty-payments">
+        <text v-if="materialList.length === 0">请先添加物料信息</text>
+        <text v-else>暂无付款方式,请点击上方按钮添加</text>
+      </view>
+    </uni-card>
+
+    <!-- 上传附件 -->
+    <uni-card title="上传附件" :extra="`${fileList.length}/50`" spacing="0">
+      <uni-file-picker
+          ref="filePicker"
+          v-model="fileList"
+          :auto-upload="true"
+          mode="list"
+          :limit="50"
+          :limit-length="50"
+          file-mediatype="all"
+          @select="handleFileSelect"
+          @progress="handleFileProgress"
+          @success="handleFileSuccess"
+          @fail="handleFileFail"
+          @delete="handleFileDelete"
+      />
+    </uni-card>
+
+    <!-- 提交按钮 -->
+    <view class="submit-btn-wrapper">
+      <button type="primary" :loading="isSubmitting" :disabled="isSubmitting" @click="submitForm">提 交</button>
+    </view>
+
+    <!-- 选择器弹出层 -->
+    <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">查询</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 { onMounted, reactive, ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import { useUserStore } from '@/store/user.js'
+import $modal from '@/plugins/modal.js'
+import $tab from '@/plugins/tab.js'
+import { getContractInitData, startContractProcess, getMaterialList, getSupplierList, getPurchaseApplyList, getPurchaseDetailList } from '@/api/contract.js'
+import { uploadFile, getProcessInfo } from '@/api/work.js'
+import config from '@/config.js'
+
+const userStore = useUserStore()
+
+// 流程信息(参考通用流程)
+let processInfo = reactive({
+  modelName: '合同审批',
+  reqOffice: 0,
+  control: '',
+  formId: '',
+  modelId: '',
+  tmodelId: '',
+  isMoreIns: '',
+  pathJudgeType: '',
+  form: []
+})
+
+// 接收页面参数
+onLoad((options) => {
+  const { modelName, modelId, control } = options
+  if (modelName) processInfo.modelName = modelName
+  if (modelId) processInfo.modelId = modelId
+  if (control) processInfo.control = control
+})
+
+// 初始化流程信息(获取 formId, tmodelId 等)
+onMounted(() => {
+  initProcessInfo().then(() => {
+    // 初始化基本信息(流水号、部门等)
+    initBaseForm()
+  })
+})
+
+function initProcessInfo() {
+  return new Promise<void>((resolve, reject) => {
+    // 如果没有传入 modelId,使用默认值或提示错误
+    if (!processInfo.modelId) {
+      $modal.msgError('缺少流程模型')
+      reject(new Error('缺少流程模型 ID'))
+      return
+    }
+
+    getProcessInfo(processInfo)
+        .then(({ returnParams }) => {
+          const { formId, tmodelId, isMoreIns, pathJudgeType, reqOffice } = returnParams.flow[0]
+          processInfo.formId = formId
+          processInfo.tmodelId = tmodelId
+          processInfo.isMoreIns = isMoreIns
+          processInfo.pathJudgeType = pathJudgeType
+          processInfo.reqOffice = reqOffice || 0 // 是否需要附件(0-不需要,1-需要)
+          resolve()
+        })
+        .catch(err => {
+          console.error('获取流程信息失败:', err)
+          reject(err)
+        })
+  })
+}
+
+function initBaseForm() {
+  // 获取初始化数据 (流水号、部门信息等)
+  getContractInitData(userStore.user.useId).then(res => {
+    if (res.returnCode === '1') {
+      const params = res.returnParams
+      
+      baseForm.contract_number = params.contract_number || ''
+      baseForm.initiator = params.initiator || userStore.user.name
+      baseForm.department = params.department || ''
+      baseForm.depid = params.depid || null
+      baseForm.applyDate = params.applyDate || ''
+      baseForm.salesman_name = params.salesman_name || userStore.user.name
+      baseForm.salesmanTel = params.salesmanTel || ''
+      baseForm.salesman = params.salesman || userStore.user.useId.toString()
+      baseForm.firstparty_name = params.firstparty_name || ''
+      // 录入人信息 (与 PC 端保持一致)
+      baseForm.contract_entrying_operator = params.contract_entrying_operator || userStore.user.useId.toString()
+      baseForm.contract_entrying_operator_name = params.contract_entrying_operator_name || userStore.user.name
+
+      // 合同类型列表
+      if (params.typeList && params.typeList.length > 0) {
+        contractTypeList.value = params.typeList
+      }
+
+      // 付款方式列表(用于付款信息选择)
+      if (params.paymentList && params.paymentList.length > 0) {
+        paymentTypeList.value = params.paymentList
+      }
+    } else {
+      console.error('获取初始化数据失败,returnCode:', res.returnCode)
+      $modal.msgError('获取初始化数据失败:' + (res.returnMsg || '未知错误'))
+    }
+  }).catch(err => {
+    console.error('获取初始化数据异常:', err)
+    $modal.msgError('获取初始化数据失败:' + (err.message || '未知错误'))
+  })
+}
+
+const baseForm = reactive({
+  contract_number: '',
+  contract_name: '',
+  contract_type: '',
+  contract_type_name: '',
+  applyDate: '',
+  initiator: '',
+  department: '',
+  depid: null,
+  salesman: '',
+  salesman_name: '',
+  salesmanTel: '',
+  supplierCode: '',
+  supplierName: '',
+  contract_money: '',
+  contractContent: '',
+  projectItem: '',
+  usePosition: '',
+  other_contractor: '',
+  otherFile: '',
+  salesmanSign: '',
+  firstparty_name: '',
+  // 录入人信息 (与 PC 端保持一致)
+  contract_entrying_operator: '',
+  contract_entrying_operator_name: ''
+})
+
+const contractTypeList = ref<any[]>([])
+const paymentTypeList = ref<any[]>([])
+const materialList = ref<any[]>([])
+const paymentList = ref<any[]>([])
+const isSubmitting = ref(false)
+
+// 文件上传相关
+const fileList = ref<any[]>([])
+const fileSeqs = ref<any[]>([])
+
+// 表单校验规则
+const baseFormRef = ref(null)
+const baseFormRules = computed(() => {
+  return {
+    contract_name: {
+      rules: [{ required: true, message: '请输入合同名称' }],
+      label: '合同名称'
+    },
+    contract_type: {
+      rules: [{ required: true, message: '请选择合同类型' }],
+      label: '合同类型'
+    },
+    supplierName: {
+      rules: [{ required: true, message: '请选择供应商' }],
+      label: '供应商'
+    }
+  }
+})
+
+// 选择器相关
+const selectorPopup = ref(null)
+const selectorList = ref<any[]>([])
+const selectorType = ref('') // 'material', 'supplier', 'salesman'
+
+// 物料来源:'mes' - MES 系统,'purchase' - 采购申请
+const materialSource = ref('mes')
+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 canEditContractMoney = computed(() => {
+  return materialList.value.length > 0
+})
+
+const popupTitle = computed(() => {
+  if (selectorType.value === 'material') return '选择物料'
+  if (selectorType.value === 'supplier') return '选择供应商'
+  if (selectorType.value === 'salesman') return '选择经办人'
+  return '选择'
+})
+
+// 切换展开/收起状态
+function toggleExpand(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
+  }
+}
+
+// 物料价格计算
+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.contract_money) || 0
+  if (!currentMoney || currentMoney < total) {
+    baseForm.contract_money = total.toFixed(2)
+  }
+
+  // 更新付款信息中的金额
+  calculatePaymentAmount()
+}
+
+// 数量变化时
+function onQuantityBlur(item: any) {
+  calculateTotalPrice()
+}
+
+// 税前单价变化时
+function onPriceBlur(item: any) {
+  calculateMaterialPrice(item)
+}
+
+// 税率变化时
+function onCessBlur(item: any) {
+  calculateMaterialPrice(item)
+}
+
+// 添加付款方式
+function addPaymentInfo() {
+  if (materialList.value.length === 0) {
+    $modal.msgWarn('请先添加物料信息')
+    return
+  }
+
+  paymentList.value.push({
+    payType: '',
+    payTypeName: '',
+    proportion: '0',
+    amount: '0',
+    amountPaid: '0',
+    payTime: '',
+    payStatus: '0',
+    payStatusName: '未支付',
+    remark: '',
+    expanded: true
+  })
+}
+
+// 付款方式类型变化
+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 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 onProportionBlur(item: any) {
+  calculatePaymentAmount()
+}
+
+// 计算付款金额(根据比例)
+function calculatePaymentAmount() {
+  const contractMoney = parseFloat(baseForm.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 onAmountPaidBlur(item: any) {
+  updatePaymentStatus(item)
+}
+
+// 监听合同金额变化
+function onContractMoneyChange() {
+  calculatePaymentAmount()
+}
+
+// 打开物料选择器
+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()
+}
+
+// 加载物料列表(分页)
+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
+  }
+}
+
+// 加载更多
+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)
+  }
+}
+
+// 物料来源切换时
+async function onMaterialSourceChange() {
+  currentPage.value = 1
+  hasMore.value = true
+  selectorList.value = []
+  searchKeyword.value = ''
+  
+  if (materialSource.value === 'mes') {
+    // MES 系统模式,加载物料列表
+    await loadMaterials(1, false)
+  } else {
+    // 采购申请模式,先加载采购申请单列表,然后加载所有物料
+    await loadPurchaseApplyList()
+    // 加载完采购申请单后,立即查询物料(默认不指定具体采购单)
+    await loadPurchaseMaterials(true) // true 表示重置分页
+  }
+}
+
+// 加载采购申请单列表(与 PC 端一致,不分页)
+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
+  }
+}
+
+// 采购申请单选择变化
+function onPurchaseChange(e: any) {
+  const selectedIndex = e.detail.value
+  selectedPurchaseApply.value = purchaseApplyList.value[selectedIndex]
+  // 选择采购单后,重新加载物料(重置分页)
+  loadPurchaseMaterials(true)
+}
+
+// 清空采购申请单选择
+function clearPurchaseSelection() {
+  selectedPurchaseApply.value = null
+  // 清空后重新加载所有物料
+  loadPurchaseMaterials(true)
+}
+
+// 加载采购申请单的物料
+async function loadPurchaseMaterials(resetPage = 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
+  }
+}
+
+// 判断是否已选中
+// 注意:只有当物料编码和采购申请单编号都相同时,才认为是重复选择
+function isSelected(item: any): boolean {
+  if (selectorType.value === 'material') {
+    const itemCode = item.itemCode || item.materialCode || ''
+    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.supplierCode === code
+  }
+  return false
+}
+
+function openPopup() {
+  ;(selectorPopup.value as any).open()
+}
+
+function closePopup() {
+  ;(selectorPopup.value as any).close()
+}
+
+function getSelectorItemName(item: any): string {
+  if (selectorType.value === 'material') {
+    return item.itemName || item.materialName || ''
+  } else if (selectorType.value === 'supplier') {
+    return item.vendorName || item.supplierName || ''
+  }
+  return ''
+}
+
+function getItemCode(item: any): string {
+  if (selectorType.value === 'material') {
+    return item.itemCode || item.materialCode || ''
+  } else if (selectorType.value === 'supplier') {
+    return item.vendorCode || item.supplierCode || ''
+  }
+  return ''
+}
+
+function getItemSpec(item: any): string {
+  if (selectorType.value === 'material') {
+    return item.specification || item.materialModel || ''
+  }
+  return ''
+}
+
+function selectItem(item: any) {
+  if (selectorType.value === 'material') {
+    addMaterial(item)
+  } else if (selectorType.value === 'supplier') {
+    selectSupplier(item)
+  }
+  closePopup()
+}
+
+function addMaterial(item: any) {
+  // 检查是否已存在(根据物料编码 + 采购申请单编号判断)
+  const exists = isSelected(item)
+  if (exists) {
+    $modal.msgWarn('该物料已存在')
+    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()
+}
+
+function selectSupplier(item: any) {
+  baseForm.supplierCode = item.vendorCode
+  baseForm.supplierName = item.vendorName
+}
+
+function removeMaterial(index: number) {
+  $modal.confirm('', '确认删除该物料?')
+      .then(() => {
+        materialList.value.splice(index, 1)
+        // 重新计算总价
+        calculateTotalPrice()
+      })
+      .catch(() => {})
+}
+
+function removePayment(index: number) {
+  $modal.confirm('', '确认删除该付款方式?')
+      .then(() => {
+        paymentList.value.splice(index, 1)
+      })
+      .catch(() => {})
+}
+
+// 合同类型变化
+function onContractTypeChange(e: any) {
+  const index = e.detail.value
+  baseForm.contract_type = contractTypeList.value[index].contract_type
+  baseForm.contract_type_name = contractTypeList.value[index].contract_type_name
+}
+
+// 文件上传相关函数
+async function handleFileSelect(files: any) {
+  files.tempFiles.forEach(async (file: any) => {
+    const data = {
+      name: file.name,
+      filePath: file.path,
+    }
+    try {
+      const res = await uploadFile(data)
+      file.seq = res.returnParams
+      fileSeqs.value.push({ 'seq': res.returnParams, 'path': file.path })
+      fileList.value.push(file)
+      $modal.msgSuccess('文件' + data.name + '上传成功')
+    } catch (err) {
+      $modal.msgError('文件' + data.name + '上传失败,请删除重新上传')
+      console.error('文件上传失败:', err)
+    }
+  })
+}
+
+function handleFileProgress(file: any, progress: any) {
+  //console.log('handleFileProgress', file, progress)
+}
+
+function handleFileSuccess(file: any, res: any) {
+  //console.log('handleFileSuccess', file, res)
+}
+
+function handleFileFail(file: any, err: any) {
+  //console.log('handleFileFail', file, err)
+}
+
+function handleFileDelete(file: any) {
+  const index = fileSeqs.value.findIndex(({ path }) => path === file.tempFilePath)
+  if (index !== -1) {
+    fileSeqs.value.splice(index, 1)
+  }
+}
+
+async function submitForm() {
+  // 先进行表单校验
+  if (!baseFormRef.value) {
+    $modal.msgError('表单未初始化')
+    return
+  }
+
+  try {
+    await baseFormRef.value.validate()
+  } catch (error) {
+    console.error('表单校验失败', error)
+    $modal.msgError('请填写必填项')
+    return
+  }
+
+  // 1. 校验基本信息
+  if (!baseForm.contract_name || baseForm.contract_name.trim() === '') {
+    $modal.msgError('合同名称不能为空')
+    return
+  }
+  if (baseForm.contract_name.length > 100) {
+    $modal.msgError('合同名称长度不能超过 100')
+    return
+  }
+
+  // 校验合同类型不能为空(可能是数字或字符串)
+  if (!baseForm.contract_type && baseForm.contract_type !== 0) {
+    $modal.msgError('合同类型不能为空')
+    return
+  }
+
+  // 经办人验证(与 PC 端一致)
+  /*if (!baseForm.salesman || !baseForm.salesman.trim()) {
+    $modal.msgError('经办人不能为空')
+    return
+  }*/
+
+  // 合同金额验证(与 PC 端一致,验证是否为数字)
+  if (baseForm.contract_money && isNaN(Number(baseForm.contract_money))) {
+    $modal.msgError('合同金额只能是数字')
+    return
+  }
+
+  if (!baseForm.supplierName || baseForm.supplierName.trim() === '') {
+    $modal.msgError('供方不能为空')
+    return
+  }
+
+  // 2. 校验物料列表(如果添加了的话)
+  for (let i = 0; i < materialList.value.length; i++) {
+    const item = materialList.value[i]
+    if (!item.qty || Number(item.qty) <= 0) {
+      $modal.msgError(`请填写第${i + 1}个物料的数量,且必须大于 0`)
+      return
+    }
+    if (!item.price || Number(item.price) < 0) {
+      $modal.msgError(`请填写第${i + 1}个物料的税前单价,且不能为负数`)
+      return
+    }
+    if (!item.cess || Number(item.cess) < 0) {
+      $modal.msgError(`请填写第${i + 1}个物料的税率,且不能为负数`)
+      return
+    }
+    if (!item.priceTax || Number(item.priceTax) < 0) {
+      $modal.msgError(`请填写第${i + 1}个物料的税后单价,且不能为负数`)
+      return
+    }
+  }
+
+  // 3. 校验付款信息(如果有添加的话)
+  if (paymentList.value.length > 0) {
+    // 校验每个付款信息的必填字段
+    for (let i = 0; i < paymentList.value.length; i++) {
+      const item = paymentList.value[i]
+      if (!item.payType || item.payType.trim() === '') {
+        $modal.msgError(`请填写第${i + 1}个付款方式的付款方式`)
+        return
+      }
+      if (!item.proportion || Number(item.proportion) <= 0) {
+        $modal.msgError(`请填写第${i + 1}个付款方式的比例,且必须大于 0`)
+        return
+      }
+     /* if (!item.amount || Number(item.amount) < 0) {
+        $modal.msgError(`请填写第${i + 1}个付款方式的金额,且不能为负数`)
+        return
+      }*/
+    }
+  }
+
+  // 4. 校验合同金额不能小于物料总金额
+  if (materialList.value.length > 0) {
+    const contractMoney = parseFloat(baseForm.contract_money) || 0
+    let totalPrice = 0
+    materialList.value.forEach(item => {
+      const qty = parseFloat(item.qty) || 0
+      const priceTax = parseFloat(item.priceTax) || 0
+      totalPrice += qty * priceTax
+    })
+
+    if (contractMoney && contractMoney < totalPrice) {
+      $modal.msgError('合同金额不能小于物料总金额:' + totalPrice.toFixed(2))
+      return
+    }
+  }
+
+  isSubmitting.value = true
+
+  try {
+    // 构建表单数据 (注意字段命名要与后端 Java 模型一致)
+    const formData = {
+      contract_number: baseForm.contract_number,
+      contract_name: baseForm.contract_name,
+      contract_type: baseForm.contract_type,
+      applyDate: baseForm.applyDate,
+      initiator: baseForm.initiator,
+      department: baseForm.department,
+      depid: baseForm.depid,
+      salesman: baseForm.salesman,
+      salesman_name: baseForm.salesman_name,
+      salesmanTel: baseForm.salesmanTel,
+      supplierCode: baseForm.supplierCode,
+      supplierName: baseForm.supplierName,
+      contract_money: baseForm.contract_money,
+      contractContent: baseForm.contractContent,
+      projectItem: baseForm.projectItem,
+      usePosition: baseForm.usePosition,
+      other_contractor: baseForm.other_contractor,
+      otherFile: baseForm.otherFile,
+      salesmanSign: baseForm.salesmanSign,
+      firstparty_name: baseForm.firstparty_name,
+      // 录入人信息 (与 PC 端保持一致)
+      contract_entrying_operator: baseForm.contract_entrying_operator,
+      contract_entrying_operator_name: baseForm.contract_entrying_operator_name,
+      materialList: materialList.value.map(item => ({
+        itemCode: item.itemCode,
+        itemName: item.itemName,
+        specification: item.specification,
+        measureName: item.measureName,
+        qty: item.qty,
+        price: item.price,
+        cess: item.cess,
+        priceTax: item.priceTax,
+        purchaseId: item.purchaseId,
+        purchaseNumber: item.purchaseNumber
+      })),
+      paymentList: paymentList.value.map(item => ({
+        payType: item.payType,
+        payTypeName: item.payTypeName,
+        proportion: item.proportion,
+        amount: item.amount,
+        amountPaid: item.amountPaid,
+        payTime: item.payTime,
+        payStatus: item.payStatus,
+        payStatusName: item.payStatusName,
+        remark: item.remark
+      }))
+    }
+
+    // 准备提交流程参数
+    const processInfoData = {
+      staffId: userStore.user.useId,
+      staffName: userStore.user.name,
+      gxId: userStore.user.gxId,
+      groupId: userStore.user.groupid,
+      insName: userStore.user.name + '的' + processInfo.modelName,
+      modelId: processInfo.modelId,
+      tmodelId: processInfo.tmodelId,
+      formId: processInfo.formId,
+      fileIds: fileSeqs.value.length === 0 ? '' : fileSeqs.value.map(f => f.seq)
+    }
+
+    // 检查是否需要附件
+    if (processInfo.reqOffice == 1 && fileSeqs.value.length === 0) {
+      $modal.msgError('该流程需要上传附件')
+      isSubmitting.value = false
+      return
+    }
+
+    // 一次性提交表单和流程数据
+    const res = await startContractProcess(userStore.user.useId, formData, processInfoData)
+
+    if (res.returnCode !== '1') {
+      $modal.msgError(res.returnMsg || '提交失败')
+      isSubmitting.value = false
+      return
+    }
+
+    $modal.msgSuccess('提交成功')
+    setTimeout(() => {
+      $tab.navigateBack()
+    }, 1000)
+  } catch (error) {
+    console.error('提交失败', error)
+    $modal.msgError('提交失败,请重试')
+  } finally {
+    isSubmitting.value = false
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.contract-container {
+  padding-bottom: 80px;
+}
+
+.selector-wrapper {
+  padding: 10px 0;
+  cursor: pointer;
+
+  .placeholder {
+    color: #c0c4cc;
+  }
+}
+
+.payment-actions,
+.material-actions {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 15px;
+
+  button {
+    flex: 1;
+  }
+}
+
+// 物料列表 - 卡片式展示
+.material-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.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;
+      margin-bottom: 8px;
+
+      .detail-label {
+        font-size: 13px;
+        color: #666;
+        flex-shrink: 0;
+      }
+
+      .detail-value {
+        flex: 0 0 auto;
+        font-size: 13px;
+        color: #333;
+      }
+
+      .price-value {
+        font-weight: bold;
+        color: #f76560;
+      }
+      
+      // ✅ 采购申请单编号样式
+      &.purchase-number-row {
+        background-color: #f9f9f9;
+        padding: 6px 8px;
+        border-radius: 4px;
+        margin-top: 4px;
+        
+        .detail-label {
+          color: #999;
+          font-size: 12px;
+        }
+        
+        .purchase-number {
+          color: #007aff;
+          font-weight: 500;
+          font-size: 13px;
+        }
+      }
+    }
+
+    .delete-row {
+      margin-top: 10px;
+      text-align: center;
+      padding-top: 8px;
+      border-top: 1px dashed #e5e5e5;
+    }
+  }
+}
+
+// 付款方式列表
+.payment-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.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-type {
+        font-size: 14px;
+        font-weight: bold;
+        color: #333;
+      }
+
+      .payment-proportion {
+        font-size: 12px;
+        color: #999;
+      }
+    }
+
+    .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;
+      margin-bottom: 8px;
+
+      .detail-label {
+        font-size: 13px;
+        color: #666;
+        flex-shrink: 0;
+      }
+
+      .detail-value {
+        flex: 0 0 auto;
+        font-size: 13px;
+        color: #333;
+      }
+
+      .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;
+        }
+      }
+    }
+
+    .delete-row {
+      margin-top: 10px;
+      text-align: center;
+      padding-top: 8px;
+      border-top: 1px dashed #e5e5e5;
+    }
+  }
+}
+
+.empty-materials,
+.empty-payments {
+  text-align: center;
+  padding: 30px;
+  color: #909399;
+  font-size: 14px;
+}
+
+.submit-btn-wrapper {
+  position: sticky;
+  width: 100%;
+  bottom: 10px;
+  padding: 10px 15px;
+  background-color: #fff;
+  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
+  z-index: 10;
+
+  button {
+    background-color: #007aff !important;
+    color: #fff !important;
+  }
+}
+
+.selector-popup {
+  // ✅ 关键:使用固定宽度,不设置 width,只使用 max-width
+  width: 580px; // 固定宽度
+  max-width: 95vw; // 最大为屏幕宽度的 95%
+  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;
+  }
+
+  .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;
+          flex-shrink: 0;
+          flex-grow: 0;
+          white-space: nowrap;
+          max-width: 50px; // ✅ 限制最大宽度
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+
+    .loading-text {
+      text-align: center;
+      padding: 12px;
+      color: #909399;
+      font-size: 13px;
+    }
+
+    .no-more-text {
+      text-align: center;
+      padding: 12px;
+      color: #909399;
+      font-size: 13px;
+    }
+
+    .empty-data {
+      text-align: center;
+      padding: 30px;
+      color: #909399;
+    }
+  }
+}
+
+// 基本信息中的禁用字段样式优化(参考 edit/index.vue)
+::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;
+      }
+    }
+  }
+}
+</style>

+ 19 - 1
pages/work/index.vue

@@ -213,7 +213,25 @@ function changeProcessGrid(item) {
 	// const modelName = item.value.modelName
 	// const modelId = item.value.modelId
 	// const control = item.value.control
-	// 跳转流程申请页面
+	// 判断是否为特殊流程
+	if (control == '0') {
+		// control 为 0 时,根据 modelId 判断
+		if (modelId == '200001') {
+			// 采购发起流程
+			$tab.navigateTo('/pages/work/purchase/start?modelName=' + modelName + '&modelId=' + modelId + '&control=' + control)
+			return
+		}else if (modelId == '200002') {
+      // 合同发起流程
+      $tab.navigateTo('/pages/work/contract/start?modelName=' + modelName + '&modelId=' + modelId + '&control=' + control)
+      return
+    }else{
+      $tab.navigateTo('/pages/work/edit/index?modelName=' + modelName + '&modelId=' + modelId + '&control=' + control)
+    }
+		// 其他特殊流程可以跳转到通用发起页面(后续扩展)
+		// $tab.navigateTo('/pages/process/common/start?modelName=' + modelName + '&modelId=' + modelId + '&control=' + control)
+		// return
+	}
+	// 跳转流程申请页面(原有逻辑)
 	$tab.navigateTo('/pages/work/edit/index?modelName=' + modelName + '&modelId=' + modelId + '&control=' + control)
 }
 // 点击考勤宫格

+ 28 - 27
pages/work/purchase/start.vue

@@ -435,15 +435,15 @@ async function handleFileSelect(files: any) {
 }
 
 function handleFileProgress(file: any, progress: any) {
-	console.log('handleFileProgress', file, progress)
+	//console.log('handleFileProgress', file, progress)
 }
 
 function handleFileSuccess(file: any, res: any) {
-	console.log('handleFileSuccess', file, res)
+	//console.log('handleFileSuccess', file, res)
 }
 
 function handleFileFail(file: any, err: any) {
-	console.log('handleFileFail', file, err)
+	//console.log('handleFileFail', file, err)
 }
 
 function handleFileDelete(file: any) {
@@ -475,6 +475,12 @@ async function submitForm() {
 		$modal.msgError('请填写必填项')
 		return
 	}
+			
+	// 验证采购单名称长度
+	if (baseForm.contractPurchaseName && baseForm.contractPurchaseName.length > 100) {
+		$modal.msgError('采购单名称长度不能超过 100 个字符')
+		return
+	}
 	
 	// 校验物料列表和数量
 	if (materialList.value.length === 0) {
@@ -482,18 +488,14 @@ async function submitForm() {
 		return
 	}
 	
-	// 检查每个物料的数量是否填写
-	for (let i = 0; i < materialList.value.length; i++) {
-		const item = materialList.value[i]
-		if (!item.qty || Number(item.qty) <= 0) {
-			uni.showToast({
-				title: `请填写第${i + 1}个物料的采购数量`,
-				icon: 'none',
-				duration: 2000
-			})
-			return
+		// 检查每个物料的数量是否填写且为大于 0 的数字
+		for (let i = 0; i < materialList.value.length; i++) {
+			const item = materialList.value[i]
+			if (!item.qty || Number(item.qty) <= 0) {
+				$modal.msgError(`请填写第${i + 1}个物料的数量,且必须大于 0`)
+				return
+			}
 		}
-	}
 
 	isSubmitting.value = true
 	
@@ -683,19 +685,18 @@ async function submitForm() {
 }
 
 .submit-btn-wrapper {
-	position: fixed;
-	bottom: 0;
-	left: 0;
-	right: 0;
-	padding: 10px 15px;
-	background-color: #fff;
-	box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
-	z-index: 100;
-	
-	button {
-		background-color: #007aff !important;
-		color: #fff !important;
-	}
+  position: sticky;
+  width: 100%;
+  bottom: 10px;
+  padding: 10px 15px;
+  background-color: #fff;
+  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
+  z-index: 10;
+
+  button {
+    background-color: #007aff !important;
+    color: #fff !important;
+  }
 }
 
 .selector-popup {