detail.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. <template>
  2. <page-meta root-font-size="system" />
  3. <view class="process_detail_container">
  4. <!-- 动态表单组件区域(包含表单、重复表、签名等所有表单相关内容) -->
  5. <view class="dynamic-form-section">
  6. <!-- 采购流程表单 -->
  7. <purchase-form
  8. v-if="formComponentName === 'purchase-form'"
  9. ref="dynamicFormRef"
  10. :formData="{...formData, ...purchaseData}"
  11. :formElements="formElements"
  12. :repeatingForm="repeatingForm"
  13. :is-initiate="flowInfo.seModel == '1'"
  14. :editable-fields="editableFields"
  15. :current-tache-opinion="currentTacheOpinion"
  16. @update="handleFormUpdate"
  17. @signature-change="handleSignatureChange" />
  18. <!-- 合同流程表单 -->
  19. <contract-form
  20. v-else-if="formComponentName === 'contract-form'"
  21. ref="dynamicFormRef"
  22. :formData="{...formData, ...contractData}"
  23. :formElements="formElements"
  24. :repeatingForm="repeatingForm"
  25. :is-initiate="flowInfo.seModel == '1'"
  26. :editable-fields="editableFields"
  27. :current-tache-opinion="currentTacheOpinion"
  28. @update="handleFormUpdate"
  29. @signature-change="handleSignatureChange" />
  30. <!-- 付款流程表单 -->
  31. <payment-form
  32. v-else-if="formComponentName === 'payment-form'"
  33. ref="dynamicFormRef"
  34. :formData="{...formData, ...paymentData}"
  35. :formElements="formElements"
  36. :repeatingForm="repeatingForm"
  37. :is-initiate="flowInfo.seModel == '1'"
  38. :editable-fields="editableFields"
  39. :current-tache-opinion="currentTacheOpinion"
  40. @update="handleFormUpdate"
  41. @signature-change="handleSignatureChange" />
  42. <!-- 默认表单 -->
  43. <default-form
  44. v-else
  45. ref="dynamicFormRef"
  46. :formData="formData"
  47. :formElements="formElements"
  48. :repeatingForm="repeatingForm"
  49. @update="handleFormUpdate" />
  50. </view>
  51. <!-- 已有附件列表 -->
  52. <view v-for="(item, index) in fileList" :key="index">
  53. <uni-card v-if="item.files && item.files.length > 0">
  54. <uni-section titleFontSize="1.3rem" title="附件" type="line"></uni-section>
  55. <attachment-list
  56. @clickDelete="deleteFile"
  57. :canEdit="flowInfo.seModel == '1'"
  58. :attachments="item.files">
  59. </attachment-list>
  60. </uni-card>
  61. </view>
  62. <!-- 上传附件(审批时) -->
  63. <view v-if="processInfo.tinsId || flowInfo.seModel == '1'" class="file_picker_container">
  64. <uni-card title="上传附件" :extra="`${subFileList.length}/50`" spacing="0">
  65. <uni-file-picker
  66. ref="filePicker"
  67. v-model="subFileList"
  68. :auto-upload="true"
  69. mode="list"
  70. :limit="50"
  71. file-mediatype="all"
  72. @select="handleFileSelect"
  73. @progress="handleFileProgress"
  74. @success="handleFileSuccess"
  75. @fail="handleFileFail"
  76. @delete="handleFileDelete" />
  77. </uni-card>
  78. </view>
  79. <!-- 流转过程 -->
  80. <uni-card>
  81. <view class="flow_step_container">
  82. <uni-section titleFontSize="1.3rem" title="流转过程" type="line"></uni-section>
  83. <up-steps :current="stepActive" activeColor="#18bc37" inactiveColor="#2979ff" direction="column">
  84. <view v-for="(step, index) in options" :key="index">
  85. <up-steps-item
  86. :contentClass="'redcontent'"
  87. v-if="step.state == 3"
  88. :title="step.title + ' 退回'"
  89. :desc="step.desc"
  90. error></up-steps-item>
  91. <up-steps-item
  92. :contentClass="'redcontent'"
  93. v-else-if="step.state == 0"
  94. :title="step.title + ' 撤销'"
  95. :desc="step.desc"
  96. error></up-steps-item>
  97. <up-steps-item
  98. v-else-if="index == stepActive"
  99. :title="step.title"
  100. :desc="step.desc">
  101. <template #icon>
  102. <view class="active_step_circle">
  103. <text class="active_step_text">{{ index + 1 }}</text>
  104. </view>
  105. </template>
  106. </up-steps-item>
  107. <up-steps-item
  108. v-else
  109. :title="step.title"
  110. :desc="step.desc"></up-steps-item>
  111. </view>
  112. </up-steps>
  113. </view>
  114. </uni-card>
  115. <!-- 环节备注 -->
  116. <view v-if="processInfo.tinsId">
  117. <uni-card>
  118. <uni-section titleFontSize="1.3rem" title="环节备注" type="line"></uni-section>
  119. <view class="remark_content">
  120. <uni-easyinput
  121. type="textarea"
  122. autoHeight
  123. v-model="remark"
  124. placeholder="请输入" />
  125. </view>
  126. </uni-card>
  127. </view>
  128. <!-- 通过按钮 -->
  129. <view v-if="processInfo.tinsId" class="approve_button">
  130. <uni-card spacing="0" padding="0">
  131. <button
  132. :disabled="!button_state"
  133. :loading="!button_state"
  134. type="primary"
  135. @click="handleSubmitProcess('1')">
  136. {{ flowInfo.seModel == '0' ? '通过' : '提交' }}
  137. </button>
  138. </uni-card>
  139. </view>
  140. <!-- 退回按钮组 -->
  141. <view v-if="processInfo.tinsId && flowInfo.seModel == '0'">
  142. <view class="reject_button">
  143. <uni-card spacing="0" padding="0" :is-shadow="false" :border="false">
  144. <uni-row>
  145. <uni-col :span="11">
  146. <button
  147. :disabled="!button_state"
  148. :loading="!button_state"
  149. type="warn"
  150. @click="handleSubmitProcess('0')">
  151. 退回上一级
  152. </button>
  153. </uni-col>
  154. <uni-col :span="11" :offset="2">
  155. <button
  156. :disabled="!button_state"
  157. :loading="!button_state"
  158. type="warn"
  159. @click="handleSubmitProcess('2')">
  160. 退回发起人
  161. </button>
  162. </uni-col>
  163. </uni-row>
  164. </uni-card>
  165. </view>
  166. </view>
  167. <!-- 撤销区域(查看页面专用) -->
  168. <view v-if="!processInfo.tinsId && isCancel">
  169. <view class="remark_container">
  170. <uni-card>
  171. <uni-section titleFontSize="1.3rem" title="撤销备注" type="line"></uni-section>
  172. <view class="remark_content">
  173. <uni-easyinput
  174. type="textarea"
  175. autoHeight
  176. v-model="remark"
  177. placeholder="请输入"
  178. placeholderStyle="font-size: calc(14px + 1.2*(1rem - 16px))"></uni-easyinput>
  179. </view>
  180. </uni-card>
  181. </view>
  182. <view class="cancel_button_container">
  183. <uni-card spacing="0" padding="0">
  184. <button
  185. :disabled="!button_state"
  186. :loading="!button_state"
  187. type="warn"
  188. @click="handleCancelProcess">
  189. 撤销
  190. </button>
  191. </uni-card>
  192. </view>
  193. </view>
  194. </view>
  195. </template>
  196. <script setup lang="ts">
  197. import { computed, onMounted, reactive, ref, nextTick } from 'vue'
  198. import { onLoad, onShow } from '@dcloudio/uni-app'
  199. import attachmentList from '@/components/ygoa/attachmentList.vue'
  200. import purchaseForm from '@/components/processForms/purchase-form.vue'
  201. import contractForm from '@/components/processForms/contract-form.vue'
  202. import paymentForm from '@/components/processForms/payment-form.vue'
  203. import defaultForm from '@/components/processForms/default-form.vue'
  204. import config from '@/config.js'
  205. import $modal from '@/plugins/modal.js'
  206. import $tab from '@/plugins/tab.js'
  207. import {
  208. getProcessFlowInfo,
  209. getProcessFormInfoInFlow,
  210. getProcessFormInfo, // 新增:没有 tinsId 时使用
  211. getProcessFlow,
  212. uploadFile,
  213. commonProcessApproval,
  214. cancelProcessFlow // 新增:撤销流程
  215. } from '@/api/process.js'
  216. import { useUserStore } from '@/store/user.js'
  217. import { keepSession } from '@/api/login.js'
  218. import { getPurchaseFormData, getPurchaseDataByInsId } from '@/api/purchase.js'
  219. import { getContractFormData, getContractDataByInsId } from '@/api/contract.js'
  220. import { getPaymentFormData, getPaymentDataByInsId } from '@/api/payment.js'
  221. const userStore = useUserStore()
  222. // 页面参数
  223. const processInfo = reactive({
  224. insId: '',
  225. insName: '',
  226. control: '1',
  227. username: '',
  228. reqOffice: 0, // 是否需要附件:0-不需要,1-需要
  229. reqRemark: 0, // 是否需要备注:0-不需要,1-需要
  230. tinsId: undefined as any,
  231. modelId: ''
  232. })
  233. onLoad((options) => {
  234. processInfo.insId = options.insId || ''
  235. // 注意:options.insName 可能是字符串 "undefined",需要特殊处理
  236. if (options.insName && options.insName !== 'undefined' && options.insName !== 'null') {
  237. processInfo.insName = options.insName
  238. }
  239. processInfo.control = options.control || '1'
  240. processInfo.modelId = options.modelId || ''
  241. if (options.tinsId) {
  242. processInfo.tinsId = options.tinsId
  243. }
  244. const title = processInfo.insName || '流程信息';
  245. // 设置导航栏标题
  246. uni.setNavigationBarTitle({
  247. title: title
  248. });
  249. })
  250. // 动态表单组件名称
  251. const formComponentName = computed(() => {
  252. // 根据 modelId 映射到对应的组件
  253. const componentMap: Record<string, string> = {
  254. '200001': 'purchase-form', // 采购流程表单组件
  255. '200002': 'contract-form', // 合同流程表单组件
  256. '200003': 'payment-form', // 付款流程表单组件
  257. // 后续添加更多映射
  258. }
  259. return componentMap[processInfo.modelId] || 'default-form'
  260. })
  261. // 表单数据(传递给自定义组件)
  262. const formData = ref<any>({}) // BPM 流程表单数据
  263. const purchaseData = ref<any>({}) // 采购单业务数据
  264. const contractData = ref<any>({}) // 合同业务数据
  265. const paymentData = ref<any>({}) // 付款申请业务数据
  266. const formElements = ref<any[]>([])
  267. const repeatingForm = ref<any>({ elementItem: [], elements: [] })
  268. const fileList = ref<any[]>([]) // 已有附件列表
  269. const dynamicFormRef = ref(null)
  270. const isCancel = ref(false) // 是否可撤销
  271. // 采购流程专用字段
  272. const editableFields = ref<string[]>([]) // 当前环节可编辑的字段列表
  273. const currentTacheOpinion = ref<any>(null) // 当前环节的审批意见配置
  274. const signatureData = ref<string>('') // 签名图片 base64
  275. // 上传附件相关
  276. const subFileList = ref<any[]>([]) // 新上传的附件
  277. const filePicker = ref(null)
  278. // 流转过程相关
  279. const flowInfo = ref<any>({
  280. seModel: '0' // 0-审批模式,1-办理模式
  281. })
  282. const stepActive = ref(0)
  283. const options = ref<any[]>([])
  284. // 环节备注
  285. const remark = ref('')
  286. // 按钮状态
  287. const button_state = ref(true)
  288. // 获取流程详情
  289. async function loadProcessDetail() {
  290. try {
  291. // 判断是否有 tinsId,选择不同的接口
  292. if (processInfo.tinsId) {
  293. // ✅ 有待办环节:调用 getProcessFormInfoInFlow(审批场景)
  294. const formRes = await getProcessFormInfoInFlow(userStore.user.useId, {
  295. tinsId: processInfo.tinsId,
  296. insId: processInfo.insId,
  297. control: processInfo.control
  298. })
  299. if (formRes.returnCode === '0' || formRes.returnCode === '1') {
  300. const formInfo = formRes.returnParams
  301. formElements.value = formInfo.formElements || []
  302. repeatingForm.value = formInfo.repeatingForm || { elementItem: [], elements: [] }
  303. formData.value = formInfo
  304. // 判断是否可撤销
  305. if (formInfo.isCancel == 1) {
  306. isCancel.value = true
  307. }
  308. // 处理附件列表 - 从后端接口获取(如果有)
  309. if (formInfo.fileList) {
  310. fileList.value = formInfo.fileList
  311. }
  312. // 从 formElements 中提取可编辑字段列表 (canEdit == '1')
  313. editableFields.value = formElements.value
  314. .filter(elem => elem.canEdit == '1')
  315. .map(elem => elem.tableField)
  316. }
  317. // 获取流转信息(必须在 loadPurchaseFormData 之前调用,因为需要 flowInfo 数据)
  318. await loadFlowSteps()
  319. await loadFlowInfo()
  320. } else {
  321. const formRes = await getProcessFormInfo(userStore.user.useId, processInfo.insId)
  322. if (formRes.returnCode === '0' || formRes.returnCode === '1') {
  323. const formInfo = formRes.returnParams
  324. formElements.value = formInfo.formElements || []
  325. repeatingForm.value = formInfo.repeatingForm || { elementItem: [], elements: [] }
  326. formData.value = formInfo
  327. // 判断是否可撤销
  328. if (formInfo.isCancel == 1) {
  329. isCancel.value = true
  330. }
  331. // 处理附件列表
  332. if (formInfo.fileList) {
  333. fileList.value = formInfo.fileList
  334. }
  335. editableFields.value = []
  336. }
  337. // 获取流转信息(不需要 tinsId 的版本)
  338. await loadFlowSteps()
  339. }
  340. // 如果是采购流程、合同流程或付款流程,额外加载业务数据(需要在 flowInfo 加载完成后才能获取 table_fields)
  341. if (processInfo.modelId === '200001') {
  342. await loadPurchaseFormData()
  343. } else if (processInfo.modelId === '200002') {
  344. await loadContractFormData()
  345. } else if (processInfo.modelId === '200003') {
  346. await loadPaymentFormData()
  347. }
  348. } catch (error) {
  349. console.error('加载流程详情失败:', error)
  350. $modal.msgError('加载失败')
  351. }
  352. }
  353. // 加载流转步骤
  354. async function loadFlowSteps() {
  355. try {
  356. const flowRes = await getProcessFlow(userStore.user.useId, {
  357. insId: processInfo.insId,
  358. control: processInfo.control
  359. })
  360. if (flowRes.returnCode === '1' || flowRes.returnCode === '0') {
  361. const flowList = flowRes.returnParams.list || []
  362. options.value = flowList.map((item: any, index: number) => {
  363. const { tmodelName, name, createdate, finishdate, remark, state, task, groupName, positionName } = item
  364. if (state == 1) {
  365. stepActive.value = index
  366. }
  367. const title = tmodelName + (name == '' ? '' : ' ( ' + name + ' ' + groupName + '-' + positionName + ' )')
  368. let desc = '创建时间:' + createdate
  369. + (finishdate == '' ? '\n' : '\n办理时间:' + finishdate)
  370. + (remark == '' ? '\n' : '\n环节意见:' + remark)
  371. if (task && task.length > 0) {
  372. desc += '\n抄送信息'
  373. task.forEach((taskitem: any) => {
  374. desc += `\n抄送对象:(${taskitem.username})抄送时间:${taskitem.createdate}`
  375. })
  376. }
  377. return {
  378. title,
  379. desc,
  380. state
  381. }
  382. })
  383. if (stepActive.value === -1) {
  384. stepActive.value = flowList.length
  385. }
  386. }
  387. } catch (error) {
  388. console.error('加载流转信息失败:', error)
  389. }
  390. }
  391. // 加载采购单数据(用于审批页面展示)
  392. async function loadPurchaseFormData() {
  393. try {
  394. // 优先使用 flowInfo 中的 formInsId
  395. let formInsId = flowInfo.value.formInsId || ''
  396. if (!formInsId) {
  397. // 如果 flowInfo 中没有,尝试从 formData 中获取
  398. formInsId = formData.value.formInsId || formData.value.universalid || ''
  399. }
  400. if (formInsId) {
  401. // ✅ 有 formInsId:调用 getPurchaseFormData
  402. const res = await getPurchaseFormData(userStore.user.useId, formInsId)
  403. if (res.returnCode === '1') {
  404. const purchaseDataRes = res.returnParams
  405. // 将采购单数据单独存储
  406. purchaseData.value = purchaseDataRes
  407. // 保存当前环节的审批意见配置
  408. currentTacheOpinion.value = flowInfo.value.tmodel
  409. }
  410. } else if (processInfo.insId && !processInfo.tinsId) {
  411. const res = await getPurchaseDataByInsId(userStore.user.useId, processInfo.insId)
  412. if (res.returnCode === '1') {
  413. const purchaseDataRes = res.returnParams
  414. // 将采购单数据单独存储
  415. purchaseData.value = purchaseDataRes
  416. // 注意:查看场景没有 table_fields,所以不设置 editableFields
  417. editableFields.value = [] // 查看场景不可编辑
  418. currentTacheOpinion.value = null
  419. }
  420. } else {
  421. console.warn('缺少 formInsId 和 insId,无法加载采购单数据')
  422. }
  423. } catch (error) {
  424. console.error('加载采购单数据失败:', error)
  425. }
  426. }
  427. // 加载合同数据(用于审批页面展示)
  428. async function loadContractFormData() {
  429. try {
  430. // 优先使用 flowInfo 中的 formInsId
  431. let formInsId = flowInfo.value.formInsId || ''
  432. if (!formInsId) {
  433. // 如果 flowInfo 中没有,尝试从 formData 中获取
  434. formInsId = formData.value.formInsId || formData.value.universalid || ''
  435. }
  436. if (formInsId) {
  437. // ✅ 有 formInsId:调用 getContractFormData
  438. const res = await getContractFormData(userStore.user.useId, formInsId)
  439. if (res.returnCode === '1') {
  440. const contractDataRes = res.returnParams
  441. // 将合同数据单独存储
  442. contractData.value = contractDataRes
  443. // 保存当前环节的审批意见配置
  444. currentTacheOpinion.value = flowInfo.value.tmodel
  445. }
  446. } else if (processInfo.insId && !processInfo.tinsId) {
  447. // TODO: 如果需要查看页面,调用 getContractDataByInsId
  448. const res = await getContractDataByInsId(userStore.user.useId, processInfo.insId)
  449. if (res.returnCode === '1') {
  450. const contractDataRes = res.returnParams
  451. // 将合同数据单独存储
  452. contractData.value = contractDataRes
  453. // 注意:查看场景没有 table_fields,所以不设置 editableFields
  454. editableFields.value = [] // 查看场景不可编辑
  455. currentTacheOpinion.value = null
  456. }
  457. } else {
  458. console.warn('缺少 formInsId 和 insId,无法加载合同数据')
  459. }
  460. } catch (error) {
  461. console.error('加载合同数据失败:', error)
  462. }
  463. }
  464. // 加载付款申请数据(用于审批页面展示)
  465. async function loadPaymentFormData() {
  466. try {
  467. // 优先使用 flowInfo 中的 formInsId
  468. let formInsId = flowInfo.value.formInsId || ''
  469. if (!formInsId) {
  470. // 如果 flowInfo 中没有,尝试从 formData 中获取
  471. formInsId = formData.value.formInsId || formData.value.universalid || ''
  472. }
  473. if (formInsId) {
  474. // ✅ 有 formInsId:调用 getPaymentFormData
  475. const res = await getPaymentFormData(userStore.user.useId, formInsId)
  476. if (res.returnCode === '1') {
  477. const paymentDataRes = res.returnParams
  478. // 将付款申请数据单独存储
  479. paymentData.value = paymentDataRes
  480. // 保存当前环节的审批意见配置
  481. currentTacheOpinion.value = flowInfo.value.tmodel
  482. }
  483. } else if (processInfo.insId && !processInfo.tinsId) {
  484. // 查看场景:调用 getPaymentDataByInsId
  485. const res = await getPaymentDataByInsId(userStore.user.useId, processInfo.insId)
  486. if (res.returnCode === '1') {
  487. const paymentDataRes = res.returnParams
  488. // 将付款申请数据单独存储
  489. paymentData.value = paymentDataRes
  490. // 注意:查看场景没有 table_fields,所以不设置 editableFields
  491. editableFields.value = [] // 查看场景不可编辑
  492. currentTacheOpinion.value = null
  493. }
  494. } else {
  495. console.warn('缺少 formInsId 和 insId,无法加载付款申请数据')
  496. }
  497. } catch (error) {
  498. console.error('加载付款申请数据失败:', error)
  499. }
  500. }
  501. // 获取流程控制信息
  502. async function loadFlowInfo() {
  503. try {
  504. const flowInfoRes = await getProcessFlowInfo(userStore.user.useId, {
  505. tinsId: processInfo.tinsId,
  506. control: processInfo.control
  507. })
  508. if (flowInfoRes.returnCode === '1' || flowInfoRes.returnCode === '0') {
  509. const params = flowInfoRes.returnParams
  510. if (params.flow && params.flow.length > 0) {
  511. flowInfo.value = params.flow[0]
  512. // 设置附件列表(从流转信息中获取)
  513. if (params.flow[0].files && params.flow[0].files.length > 0) {
  514. const newFileList = [{ files: params.flow[0].files }]
  515. // 使用 splice 清空再 push,确保响应式更新
  516. fileList.value.splice(0, fileList.value.length)
  517. newFileList.forEach(item => fileList.value.push(item))
  518. }
  519. // 添加下一环节到流转步骤
  520. if (params.nextTmodels && params.nextTmodels.length > 0) {
  521. options.value.push({
  522. title: params.nextTmodels[0].nextTmodelName,
  523. desc: '',
  524. state: -1
  525. })
  526. }
  527. // 保存其他必要信息
  528. processInfo.reqOffice = params.tmodel?.reqOffice || 0
  529. processInfo.reqRemark = params.tmodel?.reqRemark || 0
  530. }
  531. }
  532. } catch (error) {
  533. console.error('加载流程控制信息失败:', error)
  534. }
  535. }
  536. // 表单更新回调
  537. function handleFormUpdate(data: any) {
  538. // 只更新 BPM 流程表单数据,不影响采购单数据
  539. formData.value = data
  540. }
  541. // 处理签名变化
  542. function handleSignatureChange(signatureBase64: string) {
  543. signatureData.value = signatureBase64
  544. }
  545. // 文件上传相关
  546. async function handleFileSelect(files: any) {
  547. files.tempFiles.forEach((file: any) => {
  548. const data = {
  549. name: file.name,
  550. filePath: file.path,
  551. }
  552. uploadFile(data)
  553. .then(res => {
  554. file.seq = res.returnParams
  555. subFileList.value.push({ seq: res.returnParams, path: file.path })
  556. $modal.msgSuccess('文件' + data.name + '上传成功')
  557. })
  558. .catch(err => {
  559. $modal.msgError('文件' + data.name + '上传失败,请删除重新上传')
  560. })
  561. })
  562. }
  563. function handleFileProgress(file: any, progress: any) {}
  564. function handleFileSuccess(file: any, res: any) {}
  565. function handleFileFail(file: any, err: any) {}
  566. function handleFileDelete(file: any) {
  567. const index = subFileList.value.findIndex(({ path }) => path == file.tempFilePath)
  568. if (index !== -1) {
  569. subFileList.value.splice(index, 1)
  570. }
  571. }
  572. // 删除已有附件
  573. function deleteFile(file: any) {
  574. // TODO: 调用删除附件接口
  575. $modal.msgSuccess('删除成功')
  576. }
  577. // 提交审批
  578. function handleSubmitProcess(result) {
  579. let content = '确认退回'
  580. if (result == "1") {
  581. content = '确认通过'
  582. }
  583. $modal.confirm(content).then(() => {
  584. // 如果是退回操作且备注为"同意",则清空备注
  585. if ((result == '0' || result == '2') && remark.value == '同意') {
  586. remark.value = ''
  587. }
  588. button_state.value = false
  589. if (result == "1") {
  590. submitProcess(result)
  591. } else {
  592. submitProcess(result)
  593. }
  594. }).catch((err) => {
  595. console.log('用户取消操作', err)
  596. })
  597. }
  598. function submitProcess(result) {
  599. let flow = Object.assign({}, flowInfo.value)
  600. flow['staffId'] = userStore.user.useId
  601. flow['gxId'] = userStore.user.gxId
  602. flow['groupId'] = flowInfo.value.groupid
  603. if (result == "1") {
  604. const seqs = subFileList.value.map(({ seq }) => seq)
  605. if (flowInfo.value.seModel == '0' && processInfo.reqOffice == 1 && seqs.length == 0) {
  606. button_state.value = true
  607. $modal.msgError('请上传附件')
  608. return
  609. } else {
  610. flow['fileIds'] = seqs
  611. }
  612. if (processInfo.reqRemark == 1 && remark.value == '') {
  613. button_state.value = true
  614. $modal.msgError('请填写备注')
  615. return
  616. }
  617. } else {
  618. flow.fileIds = []
  619. flow.files = []
  620. }
  621. flow['remark'] = remark.value
  622. // result: 1 通过 2 退回发起人 0 退回上一级
  623. flow['result'] = result
  624. flow['nextTmodelId'] = 'undefined'
  625. // 构建 form 参数
  626. const form = {
  627. formId: flowInfo.value.formId || '',
  628. formInsId: '', // 稍后根据 modelId 设置
  629. formElements: formElements.value
  630. }
  631. // 根据不同的 modelId,从对应的业务数据中获取 formInsId
  632. if (processInfo.modelId === '200001') {
  633. // 采购流程:从 purchaseData 获取
  634. form.formInsId = purchaseData.value.lFormInsId || purchaseData.value.universalid || ''
  635. } else if (processInfo.modelId === '200002') {
  636. // 合同流程:从 contractData 获取
  637. form.formInsId = contractData.value.lFormInsId || contractData.value.universalid || ''
  638. } else if (processInfo.modelId === '200003') {
  639. // 付款流程:从 paymentData 获取
  640. form.formInsId = paymentData.value.lFormInsId || paymentData.value.universalid || ''
  641. } else {
  642. // 其他流程:从 formData 获取
  643. form.formInsId = formData.value.formInsId || formData.value.universalid || ''
  644. }
  645. // 如果是采购流程、合同流程或付款流程,需要特殊处理
  646. if ((processInfo.modelId === '200001' || processInfo.modelId === '200002' || processInfo.modelId === '200003') && dynamicFormRef.value) {
  647. // 只有“通过”操作才需要验证表单,退回不需要
  648. if (result == "1") {
  649. // 调用表单组件的 validate 方法进行验证
  650. dynamicFormRef.value.validate()
  651. .then(() => {
  652. // 从 formElements 中提取可编辑字段的 tableField
  653. // table_fields 应该只包含当前环节可编辑的字段 (canEdit == '1')
  654. const tableFieldsList = formElements.value
  655. .filter(elem => elem.canEdit == '1') // ✅ 只提取可编辑字段
  656. .map(elem => elem.tableField)
  657. .filter(f => f) // 过滤掉空值
  658. const tableFieldsStr = ',' + tableFieldsList.join(',') + ','
  659. // 验证通过,获取表单数据(包含审批意见和印章)
  660. const businessFormData = dynamicFormRef.value.getFormData()
  661. // 添加 table_fields 字段,后端需要它来判断哪些审批意见字段需要处理
  662. businessFormData.table_fields = tableFieldsStr
  663. // 直接将 businessFormData 转换为 formElements 格式
  664. const businessFormElements = []
  665. Object.keys(businessFormData).forEach(key => {
  666. // 跳过不需要提交的字段
  667. if (key !== 'details' && key !== 'detailList' && key !== 'contractMaterialList' && key !== 'contractPaymentList') {
  668. const value = businessFormData[key]
  669. businessFormElements.push({
  670. name: key,
  671. value: String(value),
  672. type: '0'
  673. })
  674. }
  675. })
  676. // 物料明细和付款明细需要转成 JSON 字符串
  677. if (businessFormData.detailList && Array.isArray(businessFormData.detailList)) {
  678. businessFormElements.push({
  679. name: 'detailList',
  680. value: JSON.stringify(businessFormData.detailList),
  681. type: '0'
  682. })
  683. }
  684. if (businessFormData.contractMaterialList && Array.isArray(businessFormData.contractMaterialList)) {
  685. businessFormElements.push({
  686. name: 'contractMaterialList',
  687. value: JSON.stringify(businessFormData.contractMaterialList),
  688. type: '0'
  689. })
  690. }
  691. if (businessFormData.contractPaymentList && Array.isArray(businessFormData.contractPaymentList)) {
  692. businessFormElements.push({
  693. name: 'contractPaymentList',
  694. value: JSON.stringify(businessFormData.contractPaymentList),
  695. type: '0'
  696. })
  697. }
  698. // 合并到 formElements 中
  699. form.formElements = [...formElements.value, ...businessFormElements]
  700. // 提交审批
  701. submitApproval(flow, form)
  702. })
  703. .catch(err => {
  704. console.error('表单验证失败:', err)
  705. button_state.value = true
  706. $modal.msgError(err.message || '表单验证失败')
  707. })
  708. } else {
  709. // 退回操作,不验证表单,直接提交
  710. submitApproval(flow, form)
  711. }
  712. } else {
  713. // 非采购/合同流程,直接提交
  714. submitApproval(flow, form)
  715. }
  716. }
  717. // 执行审批提交
  718. function submitApproval(flow, form) {
  719. // 调用通用审批接口
  720. commonProcessApproval(flow, form, processInfo.control).then(({ returnCode, returnMsg, returnParams }) => {
  721. if (returnMsg.includes('提交失败')) {
  722. // 启用提交按钮
  723. button_state.value = true
  724. $modal.msgError(returnMsg)
  725. } else {
  726. $modal.msgSuccess(returnMsg)
  727. // 通知列表刷新数据
  728. uni.$emit('ReloadProcessData');
  729. setTimeout(() => {
  730. $tab.navigateBack();
  731. }, 1000)
  732. }
  733. }).catch(error => {
  734. console.error('=== 接口调用失败 ===')
  735. console.error('错误信息:', error)
  736. button_state.value = true
  737. $modal.msgError('网络请求失败:' + (error.message || '未知错误'))
  738. })
  739. }
  740. // 取消流程
  741. function handleCancelProcess() {
  742. if (remark.value.trim() == '') {
  743. $modal.msgError('备注不能为空!')
  744. return
  745. }
  746. $modal.confirm('确认撤销').then(() => {
  747. cancelProcess()
  748. }).catch(() => { })
  749. }
  750. function cancelProcess() {
  751. cancelProcessFlow(userStore.user.useId, remark.value, processInfo).then(({ returnMsg }) => {
  752. if (returnMsg.includes('success')) {
  753. $modal.msgSuccess('撤销成功')
  754. // 通知列表刷新数据
  755. uni.$emit('ReloadProcessData');
  756. setTimeout(() => {
  757. $tab.navigateBack();
  758. }, 1000)
  759. } else {
  760. // 启用按钮
  761. button_state.value = true
  762. $modal.msgError(returnMsg)
  763. }
  764. })
  765. }
  766. // 保持会话
  767. onShow(() => {
  768. keepSession().catch(err => {
  769. console.error('保持会话失败:', err)
  770. })
  771. })
  772. onMounted(() => {
  773. loadProcessDetail()
  774. })
  775. </script>
  776. <style lang="scss" scoped>
  777. .process_detail_container {
  778. padding-bottom: 80px;
  779. }
  780. .dynamic-form-section {
  781. margin-top: 15px;
  782. }
  783. // 流转过程样式
  784. .flow_step_container {
  785. min-height: 100px;
  786. ::v-deep .u-steps {
  787. .u-steps-item {
  788. padding-bottom: 11px;
  789. .redcontent {
  790. .u-text__value--content {
  791. color: #ff4500;
  792. }
  793. }
  794. .active_step_circle {
  795. width: 20px;
  796. height: 20px;
  797. box-sizing: border-box;
  798. flex-shrink: 0;
  799. border-radius: 100px;
  800. border-width: 1px;
  801. border-color: #A78BFA;
  802. background-color: #A78BFA;
  803. border-style: solid;
  804. display: flex;
  805. flex-direction: row;
  806. align-items: center;
  807. justify-content: center;
  808. transition: background-color .3s;
  809. .active_step_text {
  810. color: #fff;
  811. font-size: 0.6875rem;
  812. display: flex;
  813. flex-direction: row;
  814. align-items: center;
  815. justify-content: center;
  816. text-align: center;
  817. line-height: 0.6875rem;
  818. }
  819. }
  820. }
  821. }
  822. }
  823. // 环节备注样式
  824. .remark_container {
  825. .remark_content {
  826. margin-top: 10px;
  827. }
  828. }
  829. // 审批按钮样式
  830. .approve_button {
  831. position: sticky;
  832. z-index: 10;
  833. width: 100%;
  834. bottom: 10px;
  835. margin-top: 15px;
  836. button {
  837. background-color: #007aff !important;
  838. color: #fff !important;
  839. }
  840. }
  841. // 退回按钮样式
  842. .reject_button {
  843. margin-top: 10px;
  844. button {
  845. font-size: calc(18px + .5*(1rem - 16px));
  846. }
  847. }
  848. // 附件上传容器
  849. .file_picker_container {
  850. margin-top: 15px;
  851. ::v-deep .uni-card {
  852. .uni-card__header-content-title {
  853. font-size: calc(15px + .5*(1rem - 16px));
  854. font-weight: 700;
  855. }
  856. .uni-card__header-extra-text {
  857. font-size: calc(15px + .5*(1rem - 16px));
  858. }
  859. }
  860. }
  861. // 基本信息中的禁用字段样式优化
  862. ::v-deep .uni-forms {
  863. .uni-forms-item__content {
  864. .uni-easyinput__content-input {
  865. font-size: calc(14px + 1.2*(1rem - 16px)) !important;
  866. font-weight: 500;
  867. color: #333;
  868. }
  869. .uni-date {
  870. .uni-icons {
  871. font-size: calc(22px + 1.2*(1rem - 16px)) !important;
  872. font-weight: 500;
  873. }
  874. .uni-date__x-input {
  875. height: auto;
  876. font-size: calc(14px + 1.2*(1rem - 16px)) !important;
  877. font-weight: 500;
  878. color: #333;
  879. }
  880. }
  881. }
  882. }
  883. </style>