| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743 |
- <!-- 合同流程专用表单组件 -->
- <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-if="isInitiateOrFieldEditable('contract_number')"
- v-model="baseForm.contract_number"
- placeholder="请输入合同编号"></uni-easyinput>
- <!-- 否则显示只读文本 -->
- <uni-easyinput v-else v-model="baseForm.contract_number" disabled></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="firstparty_name" label="需方" required>
- <!-- 销售合同:显示选择客户按钮 -->
- <view v-if="isSalesContract && isInitiateOrFieldEditable('firstparty_name')"
- class="selector-wrapper"
- @click="openClientSelector">
- <text v-if="baseForm.firstparty_name">{{ baseForm.firstparty_name }}</text>
- <text v-else class="placeholder">请选择客户</text>
- </view>
- <!-- 其他合同类型或不可编辑时:显示公司名称(只读) -->
- <uni-easyinput v-else v-model="baseForm.firstparty_name" disabled></uni-easyinput>
- </uni-forms-item>
- <uni-forms-item name="secondparty_name" label="供方" required>
- <!-- 销售合同:显示公司名称(只读) -->
- <uni-easyinput v-if="isSalesContract" v-model="baseForm.secondparty_name" disabled></uni-easyinput>
- <!-- 其他合同类型:发起环节或字段可编辑时显示选择器 -->
- <view v-else-if="isInitiateOrFieldEditable('secondparty_name')"
- class="selector-wrapper"
- @click="openSupplierSelector">
- <text v-if="baseForm.secondparty_name">{{ baseForm.secondparty_name }}</text>
- <text v-else class="placeholder">请选择供应商</text>
- </view>
- <uni-easyinput v-else v-model="baseForm.secondparty_name" disabled></uni-easyinput>
- </uni-forms-item>
- <uni-forms-item name="contract_money" label="合同金额">
- <view>
- <uni-easyinput v-model="baseForm.contract_money"
- :disabled="!isInitiateOrFieldEditable('contract_money')"
- type="digit"
- placeholder="请输入合同金额"
- @blur="onContractMoneyBlur"></uni-easyinput>
- <view class="contract-money-uppercase">
- <text>{{ contractMoneyUppercase }}</text>
- </view>
- </view>
- </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 v-if="!isSalesContract && 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">
- <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="!canAddPayment" 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;"
- @click.native.stop
- @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 v-if="!canAddPayment">请先输入合同金额</text>
- <text v-else>暂无付款明细</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' && !isSalesContract" 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' || selectorType === 'client'" class="search-bar">
- <uni-easyinput
- v-model="searchKeyword"
- :placeholder="selectorType === 'material' ? '输入物料名称搜索' : (selectorType === 'supplier' ? '输入供应商名称搜索' : '输入客户名称搜索')"
- clearable
- @confirm="handleSearch"
- />
- <button type="primary" size="mini" @click="handleSearch">搜索</button>
- </view>
- <!-- 采购申请单选择器(采购申请模式,且非销售合同) -->
- <view v-if="selectorType === 'material' && materialSource === 'purchase' && !isSalesContract" 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="!isSalesContract && 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, getClientList, getPurchaseApplyList, getPurchaseDetailList, checkContractNumber } 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: '',
- // 合同ID(用于审批流程中排除自己)
- universalid: ''
- })
- 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 isSalesContract = computed(() => {
- return baseForm.value.contract_type === '1' || baseForm.value.contract_type === 1
- })
- // 选择器相关变量
- const selectorPopup = ref(null)
- const selectorList = ref<any[]>([])
- const selectorType = ref('') // 'material', 'supplier', 'client'
- 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 '选择供应商'
- if (selectorType.value === 'client') 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 !== undefined && newVal.contract_type !== null ? 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 || '',
- secondparty_name: newVal.secondparty_name || '',
- // 合同金额:将数字转换为字符串,0 也要显示
- contract_money: (newVal.contract_money !== undefined && newVal.contract_money !== null) ? String(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 || '',
- // 保存公司名称,用于销售合同时对调需方供方(从后端获取)
- companyName: newVal.companyName || '',
- // 合同ID(用于审批流程中排除自己)
- universalid: newVal.universalid || ''
- }
-
- // 初始化合同金额大写
- updateContractMoneyUppercase()
-
- // 初始化 lastContractType
- lastContractType = String(newVal.contract_type || '')
-
- // 从 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 = []
- }
-
- // 如果是销售合同,立即清理来自采购申请单的物料(与 onContractTypeChange 逻辑一致)
- if ((newVal.contract_type === '1' || newVal.contract_type === 1) && materialList.value.length > 0) {
- const invalidMaterials: number[] = []
- materialList.value.forEach((item, idx) => {
- const purchaseId = item.purchaseId || ''
- const purchaseNumber = item.purchaseNumber || ''
- // 排除空值、null字符串、0等无效值
- if ((purchaseId && purchaseId !== 'null' && purchaseId !== '0' && purchaseId !== 'undefined') ||
- (purchaseNumber && purchaseNumber !== 'null' && purchaseNumber !== 'undefined')) {
- invalidMaterials.push(idx)
- }
- })
-
- if (invalidMaterials.length > 0) {
- // 直接删除不符合要求的物料(从后往前删,避免索引错乱)
- for (let i = invalidMaterials.length - 1; i >= 0; i--) {
- materialList.value.splice(invalidMaterials[i], 1)
- }
- // 重新计算总价
- calculateTotalPrice()
- console.log(`销售合同初始化:已清除 ${invalidMaterials.length} 条来自采购申请单的物料`)
- }
- }
-
- // 从 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()
- }
- // 打开客户选择器(销售合同时使用)
- async function openClientSelector() {
- selectorType.value = 'client'
- currentPage.value = 1
- hasMore.value = true
- selectorList.value = []
- await loadClients(1, false)
- openPopup()
- }
- // 加载客户列表(分页)
- async function loadClients(page: number, append: boolean = false) {
- if (isLoading.value) return
- isLoading.value = true
- if (page === 1) {
- isRefreshing.value = true
- }
- try {
- const res = await getClientList(
- 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 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)
- } else if (selectorType.value === 'client') {
- loadClients(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)
- } else if (selectorType.value === 'client') {
- loadClients(1, false)
- }
- }
- // 搜索
- function handleSearch() {
- currentPage.value = 1
- if (selectorType.value === 'material') {
- loadMaterials(1, false)
- } else if (selectorType.value === 'supplier') {
- loadSuppliers(1, false)
- } else if (selectorType.value === 'client') {
- loadClients(1, false)
- }
- }
- // 获取物料/供应商/客户名称
- function getSelectorItemName(item: any): string {
- if (selectorType.value === 'material') {
- return item.itemName || ''
- } else if (selectorType.value === 'supplier') {
- return item.vendorName || ''
- } else if (selectorType.value === 'client') {
- return item.clientName || ''
- }
- return ''
- }
- // 获取编码
- function getItemCode(item: any): string {
- if (selectorType.value === 'material') {
- return item.itemCode || ''
- } else if (selectorType.value === 'supplier') {
- return item.vendorCode || ''
- } else if (selectorType.value === 'client') {
- return item.clientCode || ''
- }
- return ''
- }
- // 获取规格型号(仅物料有)
- function getItemSpec(item: any): string {
- if (selectorType.value === 'material') {
- return item.specification || ''
- }
- return ''
- }
- // 判断是否已选中
- // 注意:只有当物料编码和采购申请单编号都相同时,才认为是重复选择
- 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
- } else if (selectorType.value === 'client') {
- const code = item.clientCode
- return baseForm.value.supplierCode === code
- }
- return false
- }
- // 选择物料/供应商/客户
- function selectItem(item: any) {
- if (selectorType.value === 'material') {
- // 检查是否已存在(根据物料编码 + 采购申请单编号判断)
- const exists = isSelected(item)
- if (exists) {
- $modal.msg('该物料已添加')
- return
- }
-
- // 销售合同不允许选择有采购申请单的物料
- if (isSalesContract.value) {
- const purchaseId = item.purchaseId || ''
- const purchaseNumber = item.purchaseNumber || ''
- if ((purchaseId && purchaseId !== 'null' && purchaseId !== '0' && purchaseId !== 'undefined') ||
- (purchaseNumber && purchaseNumber !== 'null' && purchaseNumber !== 'undefined')) {
- $modal.msgWarn('销售合同不允许选择来自采购申请单的物料')
- return
- }
- }
-
- // 确保数值字段正确转换(防止后端返回对象或字符串)
- const qty = parseFloat(item.qty) || 0
- const price = parseFloat(item.price) || 0
- const cess = parseFloat(item.cess) || 0
- const priceTax = parseFloat(item.priceTax) || 0
-
- // 添加物料到列表
- materialList.value.push({
- itemCode: item.itemCode,
- itemName: item.itemName,
- specification: item.specification,
- measureName: item.measureName,
- qty: qty,
- price: price,
- cess: cess,
- priceTax: priceTax,
- purchaseId: isSalesContract.value ? '' : (item.purchaseId || selectedPurchaseApply.value?.contractPurchaseFormId || ''),
- purchaseNumber: isSalesContract.value ? '' : (item.purchaseNumber || selectedPurchaseApply.value?.contractPurchaseFormNumber || ''),
- expanded: true
- })
-
- // 重新计算总价
- calculateTotalPrice()
- } else if (selectorType.value === 'supplier') {
- // 选择供应商:供方=供应商名称,supplierCode/supplierName 保存供应商信息
- baseForm.value.secondparty_name = item.vendorName
- baseForm.value.supplierCode = item.vendorCode
- baseForm.value.supplierName = item.vendorName
- } else if (selectorType.value === 'client') {
- // 选择客户:需方=客户名称,supplierCode/supplierName 保存客户信息
- baseForm.value.firstparty_name = item.clientName
- baseForm.value.supplierCode = item.clientCode
- baseForm.value.supplierName = item.clientName
- }
-
- closePopup()
- }
- // 合同类型变化
- let lastContractType = '' // 保存上一次的合同类型
- function onContractTypeChange(e: any) {
- const index = e.detail.value
- if (contractTypeList.value[index]) {
- const oldContractType = lastContractType
- const newContractType = contractTypeList.value[index].contract_type
-
- // 如果合同类型发生变化,检查是否需要清理物料
- if (oldContractType !== String(newContractType)) {
- // 切换到销售合同时,检查是否有来自采购申请单的物料
- if (newContractType === '1' || newContractType === 1) {
- const invalidMaterials: number[] = []
- materialList.value.forEach((item, idx) => {
- const purchaseId = item.purchaseId || ''
- const purchaseNumber = item.purchaseNumber || ''
- // 排除空值、null字符串、0等无效值
- if ((purchaseId && purchaseId !== 'null' && purchaseId !== '0' && purchaseId !== 'undefined') ||
- (purchaseNumber && purchaseNumber !== 'null' && purchaseNumber !== 'undefined')) {
- invalidMaterials.push(idx)
- }
- })
-
- if (invalidMaterials.length > 0) {
- $modal.confirm('', `检测到已选择的物料中有 ${invalidMaterials.length} 条来自采购申请单的数据,销售合同不允许使用采购申请单物料,是否清除这些不符合要求的物料?`)
- .then(() => {
- // 用户确认,删除不符合要求的物料(从后往前删,避免索引错乱)
- for (let i = invalidMaterials.length - 1; i >= 0; i--) {
- materialList.value.splice(invalidMaterials[i], 1)
- }
- // 重新计算总价
- calculateTotalPrice()
- // 更新合同类型为销售合同
- baseForm.value.contract_type = newContractType
- baseForm.value.contract_type_name = contractTypeList.value[index].contract_type_name
- lastContractType = String(newContractType)
- // 调整需方供方显示
- adjustPartyDisplay()
- $modal.msgSuccess(`已清除 ${invalidMaterials.length} 条不符合要求的物料`)
- })
- .catch(() => {
- // 用户取消,恢复原来的合同类型
- // 不需要做任何事,因为还没有更新 contract_type
- })
- return // 等待用户确认后再继续
- }
- }
- }
-
- // 更新合同类型
- baseForm.value.contract_type = newContractType
- baseForm.value.contract_type_name = contractTypeList.value[index].contract_type_name
- lastContractType = String(newContractType)
-
- // 根据合同类型调整需方和供方的显示
- adjustPartyDisplay()
- }
- }
- // 根据合同类型调整需方和供方的显示
- function adjustPartyDisplay() {
- // 从 baseForm 中获取保存的公司名称
- const companyName = (baseForm.value as any).companyName || ''
-
- if (isSalesContract.value) {
- // 销售合同:需方=选择客户,供方=公司名称(只读)
- baseForm.value.secondparty_name = companyName
- baseForm.value.supplierCode = ''
- baseForm.value.supplierName = ''
- baseForm.value.firstparty_name = ''
- } else {
- // 其他合同类型:需方=公司名称(只读),供方=选择供应商
- baseForm.value.firstparty_name = companyName
- baseForm.value.secondparty_name = ''
- baseForm.value.supplierCode = ''
- baseForm.value.supplierName = ''
- }
- }
- // 删除物料
- 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()
- // 更新大写金额
- updateContractMoneyUppercase()
- }
- // 计算合同总价
- let manualInput = false // 标记用户是否手动输入过合同金额
- function calculateTotalPrice() {
- let total = 0
- const hasMaterial = materialList.value.length > 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)
-
- if (hasMaterial) {
- // 有物料时,检查是否需要更新合同金额
- // 如果是手动输入且大于物料总额,保留用户输入
- // 否则,使用物料总额
- if (manualInput && currentMoney && currentMoney > total) {
- // 保留用户手动输入的较大值
- // 不更新合同金额
- } else {
- // 自动更新为物料总额(包括非手动输入,或手动输入但物料总额更大的情况)
- baseForm.value.contract_money = total.toFixed(2)
- // 重置手动输入标记
- manualInput = false
- }
- } else {
- // 没有物料时,只有非手动输入才清空
- if (!manualInput) {
- baseForm.value.contract_money = ''
- // 合同金额被清空时,删除所有付款信息
- clearAllPaymentInfo()
- }
- // 注意:如果是手动输入,即使没有物料也保留用户输入
- }
- // 更新付款信息中的金额
- calculatePaymentAmount()
- // 更新大写金额
- updateContractMoneyUppercase()
- }
- // 计算物料总价值(仅返回数值,不更新合同金额)
- 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
- }
- // 合同金额大写
- const contractMoneyUppercase = ref('')
- // 数字转中文大写函数
- function numberToChineseUppercase(n) {
- const fraction = ['角', '分']
- const digit = [
- '零', '壹', '贰', '叁', '肆',
- '伍', '陆', '柒', '捌', '玖'
- ]
- const unit = [
- ['元', '万', '亿'],
- ['', '拾', '佰', '仟']
- ]
- const head = n < 0 ? '欠' : ''
- n = Math.abs(n)
- let s = ''
- for (let i = 0; i < fraction.length; i++) {
- s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '')
- }
- s = s || '整'
- n = Math.floor(n)
- for (let i = 0; i < unit[0].length && n > 0; i++) {
- let p = ''
- for (let j = 0; j < unit[1].length && n > 0; j++) {
- p = digit[n % 10] + unit[1][j] + p
- n = Math.floor(n / 10)
- }
- s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s
- }
- return head + s.replace(/(零.)*零元/, '元')
- .replace(/(零.)+/g, '零')
- .replace(/^整$/, '零元整')
- }
- // 更新合同金额大写显示
- function updateContractMoneyUppercase() {
- const contractMoneyVal = baseForm.value.contract_money
- // 如果金额为空(未输入),显示为空字符串
- if (!contractMoneyVal || String(contractMoneyVal).trim() === '') {
- contractMoneyUppercase.value = ''
- } else {
- const contractMoney = parseFloat(contractMoneyVal) || 0
- contractMoneyUppercase.value = numberToChineseUppercase(contractMoney)
- }
- }
- // 监听合同金额变化(检测用户手动输入)- 失焦时触发
- function onContractMoneyBlur() {
- // 当用户手动修改合同金额时,设置手动输入标记
- const calculatedTotal = calculateTotalPriceValue()
- const currentMoney = parseFloat(baseForm.value.contract_money)
-
- if (currentMoney && currentMoney !== calculatedTotal) {
- manualInput = true
- }
-
- // 更新大写金额
- updateContractMoneyUppercase()
- calculatePaymentAmount()
- }
- // 数量变化时
- function onQuantityBlur(item: any) {
- calculateTotalPrice()
- }
- // 合同金额失焦验证 (已移除,改为提交前统一验证)
- // function onContractMoneyBlur() {}
- // 税前单价变化时
- function onPriceBlur(item: any) {
- calculateMaterialPrice(item)
- }
- // 税率变化时
- function onCessBlur(item: any) {
- calculateMaterialPrice(item)
- }
- // 判断是否可以添加付款(有合同金额即可)
- const canAddPayment = computed(() => {
- const contractMoney = parseFloat(baseForm.value.contract_money) || 0
- return contractMoney > 0
- })
- // 添加付款
- function addPayment() {
- if (!canAddPayment.value) {
- $modal.msgWarn('请先输入合同金额')
- return
- }
-
- 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 clearAllPaymentInfo() {
- paymentList.value = []
- }
- // 删除付款
- 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 (isSeModel.value || getFieldEditable('contract_number')) {
- if (!baseForm.value.contract_number || !baseForm.value.contract_number.trim()) {
- return Promise.reject(new Error('合同编号不能为空!'))
- }
-
- // 验证合同编号唯一性(异步调用后端接口)
- try {
- const res = await checkContractNumber(userStore.user.useId, baseForm.value.contract_number, baseForm.value.universalid || '')
- if (res.returnCode !== '1') {
- return Promise.reject(new Error(res.returnMsg || '合同编号验证失败'))
- }
- } catch (error) {
- console.error('合同编号验证失败', error)
- return Promise.reject(new Error('合同编号验证失败,请重试'))
- }
- }
-
- // 基本信息验证
- 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(isSalesContract.value ? 'firstparty_name' : 'secondparty_name')) {
- if (isSalesContract.value) {
- // 销售合同:验证需方(客户)
- if (!baseForm.value.firstparty_name || !baseForm.value.firstparty_name.trim()) {
- return Promise.reject(new Error('需方不能为空!'))
- }
- } else {
- // 其他合同:验证供方(供应商)
- if (!baseForm.value.secondparty_name || !baseForm.value.secondparty_name.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;
- }
- }
- // 合同金额大写样式
- .contract-money-uppercase {
- font-size: 12px;
- color: #666;
- margin-top: 5px;
- padding-left: 2px;
- }
- .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>
|