LossForm.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <template>
  2. <view>
  3. <cu-custom :backUrl="'/pages/index/index'" :isBack="true" bgColor="bg-gradual-blue">
  4. <block slot="content">报损申请</block>
  5. </cu-custom>
  6. <u--form :model="inputForm" labelWidth="100px" class="u-form" labelPosition="left" :rules="rules"
  7. ref="inputForm">
  8. <u-form-item label="报损编号" borderBottom prop="lossNo">
  9. <u--input placeholder="自动生成" v-model="inputForm.lossNo" disabled></u--input>
  10. </u-form-item>
  11. <u-form-item label="经办人" borderBottom prop="handledBy">
  12. <u--input v-model="inputForm.handledBy" disabled></u--input>
  13. </u-form-item>
  14. <u-form-item label="经办部门" borderBottom prop="handledByOfficeName">
  15. <u--input v-model="inputForm.handledByOfficeName" disabled></u--input>
  16. </u-form-item>
  17. <u-form-item label="报损时间" borderBottom prop="lossDate" :required="true">
  18. <el-date-picker v-model="inputForm.lossDate" type="date" placeholder="请选择报损时间" style="width:100%"
  19. placement="bottom-start" clearable :disabled="nodeFlag">
  20. </el-date-picker>
  21. </u-form-item>
  22. <u-form-item label="备注" borderBottom prop="remarks">
  23. <u--textarea placeholder="请填写备注信息" :rows="4" :maxlength="500" v-model="inputForm.remarks"
  24. :disabled="nodeFlag"></u--textarea>
  25. </u-form-item>
  26. <view class="section-wrap">
  27. <view class="section-title">报损明细</view>
  28. <view class="section-tip">报损数量按最小单位填写</view>
  29. <view v-for="(item, index) in inputForm.detailInfos" :key="item.id || index" class="detail-card">
  30. <view class="detail-card-title">报损详情 {{ index + 1 }}</view>
  31. <u-form-item label="报损人" :prop="'detailInfos[' + index + '].recipientAgent'" :required="true">
  32. <u--input v-model="inputForm.detailInfos[index].recipientAgent" placeholder="请选择报损人"
  33. :disabled="nodeFlag" @focus="openUserPullForm(index)" clearable></u--input>
  34. </u-form-item>
  35. <u-form-item label="报损人部门" :prop="'detailInfos[' + index + '].recipientOffice'">
  36. <u--input v-model="inputForm.detailInfos[index].recipientOffice" disabled></u--input>
  37. </u-form-item>
  38. <loss-goods-selector :rowIndex="index" :inputForm="inputForm" :disabled="nodeFlag"
  39. @selected="handleGoodsSelected"></loss-goods-selector>
  40. <u-form-item label="品牌" :prop="'detailInfos[' + index + '].brand'">
  41. <u--input v-model="inputForm.detailInfos[index].brand" disabled></u--input>
  42. </u-form-item>
  43. <u-form-item label="规格" :prop="'detailInfos[' + index + '].specification'">
  44. <u--input v-model="inputForm.detailInfos[index].specification" disabled></u--input>
  45. </u-form-item>
  46. <u-form-item label="单位" :prop="'detailInfos[' + index + '].company'">
  47. <u--input v-model="inputForm.detailInfos[index].company" disabled></u--input>
  48. </u-form-item>
  49. <u-form-item label="入库批次" :prop="'detailInfos[' + index + '].wareHouseNumber'">
  50. <u--input v-model="inputForm.detailInfos[index].wareHouseNumber" disabled></u--input>
  51. </u-form-item>
  52. <u-form-item label="入库日期" :prop="'detailInfos[' + index + '].wareHouseDate'">
  53. <u--input :value="formatDisplayDate(inputForm.detailInfos[index].wareHouseDate)" disabled>
  54. </u--input>
  55. </u-form-item>
  56. <u-form-item label="批次库存" :prop="'detailInfos[' + index + '].surplusNumber'">
  57. <u--input v-model="inputForm.detailInfos[index].surplusNumber" disabled></u--input>
  58. </u-form-item>
  59. <u-form-item label="报损数量" :prop="'detailInfos[' + index + '].lossNumber'" :required="true">
  60. <u--input v-model="inputForm.detailInfos[index].lossNumber" placeholder="请输入报损数量"
  61. :disabled="nodeFlag" @blur="handleLossNumberBlur(index)"></u--input>
  62. </u-form-item>
  63. <u-form-item label="报损原因" :prop="'detailInfos[' + index + '].lossReason'" :required="true">
  64. <u--textarea v-model="inputForm.detailInfos[index].lossReason" placeholder="请输入报损原因"
  65. :disabled="nodeFlag" :rows="3" :maxlength="200"></u--textarea>
  66. </u-form-item>
  67. <u-form-item label="附件上传">
  68. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  69. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, index, fileIndex, 'detail')"
  70. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, index, 'detail')"
  71. :fileList="inputForm.detailInfos[index].fileInfoLost" :limit="3" :isDelete="nodeFlag"
  72. :isUpload="nodeFlag">
  73. </UploadComponent>
  74. </u-form-item>
  75. <u-form-item label="" v-if="!nodeFlag">
  76. <el-button style="width: 100%" type="danger" plain @click="removeDetail(index)">
  77. 删除报损详情
  78. </el-button>
  79. </u-form-item>
  80. </view>
  81. <u-form-item label="" v-if="!nodeFlag">
  82. <el-button style="width: 100%" type="primary" @click="addDetail()" plain>新增报损详情</el-button>
  83. </u-form-item>
  84. </view>
  85. <u-form-item label="附件">
  86. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  87. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, '', fileIndex, '')"
  88. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, '', '')"
  89. :fileList="inputForm.files" :limit="3" :isDelete="nodeFlag" :isUpload="nodeFlag">
  90. </UploadComponent>
  91. </u-form-item>
  92. </u--form>
  93. <user-select ref="userPicker" @input="handleUserSelected"></user-select>
  94. <ba-tree-picker ref="treePicker" :multiple="false" @select-change="selectTypeChange" title="类型选择"
  95. :localdata="listData" valueKey="value" textKey="label" childrenKey="children" />
  96. </view>
  97. </template>
  98. <script>
  99. import { mapState } from 'vuex'
  100. import userSelect from '@/components/user-select/user-select-radio.vue'
  101. import baTreePicker from '@/components/ba-tree-picker/ba-tree-picker.vue'
  102. import UploadComponent from '@/pages/common/UploadComponent.vue'
  103. import upload from '@/utils/upload.js'
  104. import MaterialTypeService from '@/api/psi/MaterialTypeService'
  105. import WareHouseService from '@/api/psi/WareHouseService'
  106. import LossService from '@/api/psi/LossService'
  107. import CommonApi from '@/api/common/CommonApi'
  108. import LossGoodsSelector from './LossGoodsSelector.vue'
  109. export default {
  110. name: 'PsiLossForm',
  111. components: {
  112. userSelect,
  113. baTreePicker,
  114. UploadComponent,
  115. LossGoodsSelector
  116. },
  117. computed: mapState({
  118. userInfo: (state) => state.user.userInfo
  119. }),
  120. props: {
  121. businessId: {
  122. type: String,
  123. default: ''
  124. },
  125. formReadOnly: {
  126. type: Boolean,
  127. default: false
  128. },
  129. status: {
  130. type: String,
  131. default: ''
  132. },
  133. isCc: {
  134. type: Boolean,
  135. default: false
  136. }
  137. },
  138. data() {
  139. return {
  140. uploadUrl: upload.UPLOAD_URL,
  141. nodeFlag: false,
  142. loading: false,
  143. listData: [],
  144. materialList: [],
  145. inputForm: this.createInputForm(),
  146. rules: {
  147. lossDate: [
  148. {
  149. required: true,
  150. message: '报损时间不能为空',
  151. trigger: ['change']
  152. }
  153. ]
  154. }
  155. }
  156. },
  157. materialTypeService: null,
  158. wareHouseService: null,
  159. lossService: null,
  160. commonApi: null,
  161. created() {
  162. this.ensureServices()
  163. this.loadMaterialTypes()
  164. this.fillUserInfo()
  165. },
  166. watch: {
  167. businessId: {
  168. handler() {
  169. if (this.businessId) {
  170. this.init(this.businessId)
  171. } else {
  172. this.resetForm()
  173. this.nodeFlag = this.formReadOnly || this.status === 'taskFormDetail' || this.status === 'testSee'
  174. }
  175. },
  176. immediate: true,
  177. deep: false
  178. }
  179. },
  180. methods: {
  181. createInputForm() {
  182. return {
  183. id: '',
  184. procInsId: '',
  185. processDefinitionId: '',
  186. status: '',
  187. lossNo: '',
  188. lossDate: '',
  189. remarks: '',
  190. handledBy: '',
  191. handledById: '',
  192. handledByOffice: '',
  193. handledByOfficeName: '',
  194. userId: '',
  195. files: [],
  196. detailInfos: []
  197. }
  198. },
  199. createDetailRow() {
  200. return {
  201. id: '',
  202. recipientAgent: (this.userInfo && this.userInfo.name) || '',
  203. recipientAgentId: (this.userInfo && this.userInfo.id) || '',
  204. recipientOffice: (this.userInfo && this.userInfo.officeDTO && this.userInfo.officeDTO.name) || '',
  205. collectType: '',
  206. collectTypeId: '',
  207. goodsName: '',
  208. inventoryId: '',
  209. lossNumber: '',
  210. lossReason: '',
  211. company: '',
  212. brand: '',
  213. specification: '',
  214. wareHouseNumber: '',
  215. wareHouseDate: '',
  216. produceDate: '',
  217. shelfLife: '',
  218. shelfLifeUnit: '',
  219. spec: '1',
  220. surplusNumber: '',
  221. currentInventory: '',
  222. fileInfoLost: []
  223. }
  224. },
  225. ensureServices() {
  226. if (!this.materialTypeService) {
  227. this.materialTypeService = new MaterialTypeService()
  228. }
  229. if (!this.wareHouseService) {
  230. this.wareHouseService = new WareHouseService()
  231. }
  232. if (!this.lossService) {
  233. this.lossService = new LossService()
  234. }
  235. if (!this.commonApi) {
  236. this.commonApi = new CommonApi()
  237. }
  238. },
  239. async loadMaterialTypes() {
  240. try {
  241. const data = await this.materialTypeService.cgList()
  242. this.materialList = data || []
  243. this.listData = this.buildTree(this.materialList)
  244. } catch (e) {
  245. this.materialList = []
  246. this.listData = []
  247. }
  248. },
  249. fillUserInfo() {
  250. if (!this.userInfo) {
  251. return
  252. }
  253. this.inputForm.handledBy = this.inputForm.handledBy || this.userInfo.name
  254. this.inputForm.handledById = this.inputForm.handledById || this.userInfo.id
  255. this.inputForm.userId = this.inputForm.userId || this.userInfo.id
  256. this.inputForm.handledByOffice = this.inputForm.handledByOffice || (this.userInfo.officeDTO && this.userInfo.officeDTO.id) || ''
  257. this.inputForm.handledByOfficeName = this.inputForm.handledByOfficeName || (this.userInfo.officeDTO && this.userInfo.officeDTO.name) || ''
  258. },
  259. resetForm() {
  260. this.inputForm = this.createInputForm()
  261. this.fillUserInfo()
  262. },
  263. async init(id) {
  264. this.ensureServices()
  265. this.resetForm()
  266. this.inputForm.id = id
  267. this.loading = true
  268. try {
  269. const data = await this.lossService.findById(id)
  270. this.inputForm = this.normalizeFormData(this.recover(this.createInputForm(), data))
  271. this.fillUserInfo()
  272. this.nodeFlag = await this.resolveNodeFlag(data)
  273. } catch (e) {
  274. this.nodeFlag = true
  275. } finally {
  276. this.loading = false
  277. }
  278. },
  279. async resolveNodeFlag(data) {
  280. if (this.formReadOnly || this.status === 'taskFormDetail' || this.status === 'testSee') {
  281. return true
  282. }
  283. try {
  284. const taskName = await this.commonApi.getTaskNameByProcInsId((data && data.procInsId) || this.inputForm.procInsId)
  285. if (this.isNotEmpty(taskName)) {
  286. return taskName !== '发起人重新发起申请'
  287. }
  288. } catch (e) {
  289. }
  290. return false
  291. },
  292. normalizeFormData(data) {
  293. const form = data || this.createInputForm()
  294. if (this.isNotEmpty(form.lossDate)) {
  295. form.lossDate = new Date(form.lossDate)
  296. }
  297. form.files = form.files || []
  298. form.detailInfos = (form.detailInfos || []).map(item => ({
  299. ...item,
  300. fileInfoLost: item.fileInfoLost || [],
  301. produceDate: this.isNotEmpty(item.produceDate) ? new Date(item.produceDate) : '',
  302. surplusNumber: this.isNotEmpty(item.surplusNumber) ? this.formatNumber(item.surplusNumber) : ''
  303. }))
  304. return form
  305. },
  306. buildTree(nodes, parentId = '0') {
  307. const tree = []
  308. for (const node of nodes || []) {
  309. if (node.parentId === parentId) {
  310. const children = this.buildTree(nodes, node.id)
  311. if (children.length) {
  312. node.children = children
  313. }
  314. tree.push(node)
  315. }
  316. }
  317. return tree
  318. },
  319. isEmpty(value) {
  320. if (value === null || value === undefined) {
  321. return true
  322. }
  323. if (typeof value === 'string' && value.trim() === '') {
  324. return true
  325. }
  326. if (Array.isArray(value) && value.length === 0) {
  327. return true
  328. }
  329. return false
  330. },
  331. isNotEmpty(value) {
  332. return !this.isEmpty(value)
  333. },
  334. formatDate(date) {
  335. if (this.isEmpty(date)) {
  336. return ''
  337. }
  338. const dateValue = new Date(date)
  339. if (Number.isNaN(dateValue.getTime())) {
  340. return ''
  341. }
  342. const year = dateValue.getFullYear()
  343. const month = `${dateValue.getMonth() + 1}`.padStart(2, '0')
  344. const day = `${dateValue.getDate()}`.padStart(2, '0')
  345. return `${year}-${month}-${day}`
  346. },
  347. formatDisplayDate(date) {
  348. return this.formatDate(date)
  349. },
  350. normalizeDateFieldsBeforeValidate() {
  351. if (this.isNotEmpty(this.inputForm.lossDate)) {
  352. this.inputForm.lossDate = this.formatDate(this.inputForm.lossDate)
  353. }
  354. },
  355. formatNumber(value) {
  356. const numberValue = Number(value || 0)
  357. if (Number.isNaN(numberValue)) {
  358. return ''
  359. }
  360. return numberValue % 1 === 0 ? String(numberValue) : numberValue.toFixed(2).replace(/\.?0+$/, '')
  361. },
  362. formatBatchStock(currentInventory, spec) {
  363. const current = Number(currentInventory || 0)
  364. const specNumber = Number(spec || 1)
  365. if (Number.isNaN(current) || Number.isNaN(specNumber)) {
  366. return '0'
  367. }
  368. return this.formatNumber(current * (specNumber > 0 ? specNumber : 1))
  369. },
  370. formatNumberInput(inputValue, decimalLimit = 2) {
  371. if (this.isEmpty(inputValue)) {
  372. return ''
  373. }
  374. const valueText = String(inputValue)
  375. if (!/^\d*\.?\d*$/.test(valueText)) {
  376. return ''
  377. }
  378. let value = valueText.replace(/[^\d.]/g, '')
  379. const dotIndex = value.indexOf('.')
  380. if (dotIndex !== -1) {
  381. const substr = value.substr(dotIndex + 1)
  382. if (substr.indexOf('.') !== -1) {
  383. value = value.substr(0, dotIndex + 1) + substr.replace(/\./g, '')
  384. }
  385. }
  386. if (dotIndex !== -1) {
  387. const integerPart = value.substring(0, dotIndex)
  388. const decimalPart = value.substring(dotIndex + 1, dotIndex + 1 + decimalLimit)
  389. value = integerPart + '.' + decimalPart
  390. }
  391. return value
  392. },
  393. addDetail() {
  394. this.inputForm.detailInfos.push(this.createDetailRow())
  395. },
  396. removeDetail(index) {
  397. this.inputForm.detailInfos.splice(index, 1)
  398. },
  399. patchDetail(index, patch) {
  400. const detail = (this.inputForm.detailInfos || [])[index] || {}
  401. this.$set(this.inputForm.detailInfos, index, {
  402. ...detail,
  403. ...patch
  404. })
  405. },
  406. openUserPullForm(index) {
  407. if (this.nodeFlag) {
  408. return
  409. }
  410. this.$refs.userPicker.open(index, 'detail')
  411. },
  412. handleUserSelected(data, index, type) {
  413. if (type !== 'detail') {
  414. return
  415. }
  416. this.patchDetail(index, {
  417. recipientAgentId: data.id,
  418. recipientAgent: data.label,
  419. recipientOffice: data.parentLabel
  420. })
  421. },
  422. openTypePicker(index) {
  423. if (this.nodeFlag) {
  424. return
  425. }
  426. this.$refs.treePicker._show(index)
  427. },
  428. selectTypeChange(ids, names, index) {
  429. this.patchDetail(index, {
  430. collectType: names,
  431. collectTypeId: ids[0],
  432. goodsName: '',
  433. inventoryId: '',
  434. company: '',
  435. brand: '',
  436. specification: '',
  437. wareHouseNumber: '',
  438. wareHouseDate: '',
  439. produceDate: '',
  440. shelfLife: '',
  441. shelfLifeUnit: '',
  442. spec: '1',
  443. surplusNumber: '',
  444. currentInventory: '',
  445. lossNumber: ''
  446. })
  447. },
  448. async handleGoodsSelected({ index, item }) {
  449. const stock = this.formatBatchStock(item.currentInventory, item.spec)
  450. this.patchDetail(index, {
  451. inventoryId: item.id || '',
  452. collectType: item.tradeName || '',
  453. collectTypeId: item.wareHouseType || '',
  454. goodsName: item.tradeName || '',
  455. company: item.company || '',
  456. brand: item.brand || '',
  457. specification: item.specification || '',
  458. wareHouseNumber: item.wareHouseNumber || '',
  459. wareHouseDate: this.isNotEmpty(item.wareHouseDate) ? new Date(item.wareHouseDate) : '',
  460. produceDate: this.isNotEmpty(item.produceDate) ? new Date(item.produceDate) : '',
  461. shelfLife: item.shelfLife || '',
  462. shelfLifeUnit: item.shelfLifeUnit || '',
  463. spec: item.spec || '1',
  464. currentInventory: item.currentInventory || '',
  465. surplusNumber: stock
  466. })
  467. },
  468. async syncDetailStock(index) {
  469. const detail = (this.inputForm.detailInfos || [])[index]
  470. if (!detail || this.isEmpty(detail.goodsName) || this.isEmpty(detail.collectTypeId)) {
  471. return
  472. }
  473. try {
  474. const data = await this.wareHouseService.getByProduceDateNotMerge({
  475. current: 1,
  476. size: 1000,
  477. tradeName: detail.goodsName,
  478. wareHouseType: detail.collectTypeId
  479. })
  480. const records = (data && data.records) || []
  481. const total = records.reduce((sum, item) => {
  482. const currentInventory = Number(item.currentInventory || 0)
  483. const spec = Number(item.spec || 1)
  484. return sum + currentInventory * (spec > 0 ? spec : 1)
  485. }, 0)
  486. this.$set(this.inputForm.detailInfos[index], 'surplusNumber', this.formatNumber(total))
  487. } catch (e) {
  488. this.$set(this.inputForm.detailInfos[index], 'surplusNumber', '')
  489. uni.showToast({
  490. title: '库存数量加载失败',
  491. icon: 'none'
  492. })
  493. }
  494. },
  495. handleUploadSuccess(file, fileList, index, type) {
  496. if (type === 'detail') {
  497. this.inputForm.detailInfos[index].fileInfoLost = fileList
  498. } else {
  499. this.inputForm.files = fileList
  500. }
  501. },
  502. handleRemove(file, fileList, lineIndex, fileIndex, type) {
  503. if (type === 'detail') {
  504. this.inputForm.detailInfos[lineIndex].fileInfoLost.splice(fileIndex, 1)
  505. } else {
  506. this.inputForm.files.splice(fileIndex, 1)
  507. }
  508. },
  509. handleLossNumberBlur(index) {
  510. const detail = (this.inputForm.detailInfos || [])[index]
  511. if (!detail) {
  512. return
  513. }
  514. detail.lossNumber = this.formatNumberInput(detail.lossNumber)
  515. if (this.isNotEmpty(detail.surplusNumber) && this.isNotEmpty(detail.lossNumber) &&
  516. Number(detail.lossNumber) > Number(detail.surplusNumber)) {
  517. this.$message.error('报损数量不能大于当前库存')
  518. detail.lossNumber = ''
  519. }
  520. },
  521. validateDetailInfos(checkStock = true) {
  522. if (this.isEmpty(this.inputForm.detailInfos)) {
  523. this.$message.error('请填写报损明细')
  524. return false
  525. }
  526. for (let index = 0; index < this.inputForm.detailInfos.length; index++) {
  527. const detail = this.inputForm.detailInfos[index]
  528. const lineNo = index + 1
  529. if (this.isEmpty(detail.recipientAgentId)) {
  530. this.$message.error(`报损明细第${lineNo}行请选择报损人`)
  531. return false
  532. }
  533. if (this.isEmpty(detail.collectTypeId)) {
  534. this.$message.error(`报损明细第${lineNo}行请选择报损类型`)
  535. return false
  536. }
  537. if (this.isEmpty(detail.goodsName)) {
  538. this.$message.error(`报损明细第${lineNo}行请选择物品名称`)
  539. return false
  540. }
  541. if (this.isEmpty(detail.inventoryId)) {
  542. this.$message.error(`报损明细第${lineNo}行请选择报损库存批次`)
  543. return false
  544. }
  545. if (this.isEmpty(detail.lossNumber)) {
  546. this.$message.error(`报损明细第${lineNo}行请输入报损数量`)
  547. return false
  548. }
  549. if (this.isEmpty(detail.lossReason)) {
  550. this.$message.error(`报损明细第${lineNo}行请输入报损原因`)
  551. return false
  552. }
  553. if (checkStock && this.isNotEmpty(detail.surplusNumber) && Number(detail.lossNumber) > Number(detail.surplusNumber)) {
  554. this.$message.error(`报损明细第${lineNo}行报损数量不能大于当前库存`)
  555. return false
  556. }
  557. }
  558. return true
  559. },
  560. toSubmitData() {
  561. const data = JSON.parse(JSON.stringify(this.inputForm))
  562. data.lossDate = this.formatDate(this.inputForm.lossDate)
  563. data.detailInfos = (this.inputForm.detailInfos || []).map(item => ({
  564. ...JSON.parse(JSON.stringify(item)),
  565. produceDate: this.formatDate(item.produceDate)
  566. }))
  567. return data
  568. },
  569. async saveForm(callback) {
  570. this.loading = true
  571. try {
  572. const submitData = this.toSubmitData()
  573. submitData.status = '1'
  574. this.inputForm.status = '1'
  575. const data = await this.lossService.save(submitData)
  576. callback(data.businessTable, data.businessId, submitData)
  577. } finally {
  578. this.loading = false
  579. }
  580. },
  581. async startForm(callback) {
  582. this.loading = true
  583. try {
  584. if (this.isNotEmpty(this.inputForm.id)) {
  585. const data = await this.lossService.findById(this.inputForm.id)
  586. if (this.isNotEmpty(data.status) && !['0', '1', '3'].includes(data.status)) {
  587. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  588. throw new Error('invalid status')
  589. }
  590. }
  591. await this.startFormTrue(callback)
  592. } finally {
  593. this.loading = false
  594. }
  595. },
  596. async startFormTrue(callback) {
  597. this.normalizeDateFieldsBeforeValidate()
  598. await this.$refs.inputForm.validate()
  599. if (!this.validateDetailInfos(true)) {
  600. return
  601. }
  602. const submitData = this.toSubmitData()
  603. submitData.status = '2'
  604. this.inputForm.status = '2'
  605. const data = await this.lossService.save(submitData)
  606. this.inputForm.id = data.businessId
  607. callback(data.businessTable, data.businessId, submitData)
  608. },
  609. async reapplyForm(callback) {
  610. this.loading = true
  611. try {
  612. const data = await this.lossService.findById(this.inputForm.id)
  613. if (data.status !== '4') {
  614. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  615. throw new Error('invalid status')
  616. }
  617. await this.startFormTrue(callback)
  618. } finally {
  619. this.loading = false
  620. }
  621. },
  622. async agreeForm(callback) {
  623. this.loading = true
  624. try {
  625. this.normalizeDateFieldsBeforeValidate()
  626. await this.$refs.inputForm.validate()
  627. if (!this.validateDetailInfos(false)) {
  628. return
  629. }
  630. const submitData = this.toSubmitData()
  631. submitData.status = '5'
  632. this.inputForm.status = '5'
  633. const data = await this.lossService.save(submitData)
  634. callback(data.businessTable, data.businessId, submitData)
  635. } finally {
  636. this.loading = false
  637. }
  638. },
  639. async updateStatusById(type, callback) {
  640. this.loading = true
  641. try {
  642. if (type === 'reject' || type === 'reback') {
  643. const data = await this.lossService.findById(this.inputForm.id)
  644. if (data.status !== '2') {
  645. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  646. throw new Error('invalid status')
  647. }
  648. const nextStatus = type === 'reject' ? '4' : '3'
  649. this.inputForm.status = nextStatus
  650. await this.lossService.updateStatusById({
  651. status: nextStatus,
  652. id: this.inputForm.id
  653. })
  654. callback()
  655. } else if (type === 'hold') {
  656. const data = await this.lossService.findById(this.inputForm.id)
  657. if (data.status !== '4') {
  658. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  659. throw new Error('invalid status')
  660. }
  661. this.inputForm.status = '1'
  662. await this.lossService.updateStatusById({
  663. status: '1',
  664. id: this.inputForm.id
  665. })
  666. callback()
  667. }
  668. } finally {
  669. this.loading = false
  670. }
  671. }
  672. }
  673. }
  674. </script>
  675. <style scoped>
  676. .section-wrap {
  677. margin-top: 24rpx;
  678. }
  679. .section-title {
  680. margin-bottom: 8rpx;
  681. padding-left: 8rpx;
  682. font-size: 30rpx;
  683. font-weight: 700;
  684. color: #1f2937;
  685. border-left: 6rpx solid #2979ff;
  686. }
  687. .section-tip {
  688. margin-bottom: 16rpx;
  689. font-size: 24rpx;
  690. color: #64748b;
  691. }
  692. .detail-card {
  693. margin-bottom: 20rpx;
  694. padding: 24rpx;
  695. background: #fff;
  696. border-radius: 20rpx;
  697. box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
  698. }
  699. .detail-card-title {
  700. margin-bottom: 12rpx;
  701. font-size: 28rpx;
  702. font-weight: 600;
  703. color: #0f172a;
  704. }
  705. </style>