add.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <template>
  2. <view class="uni-app">
  3. <view class="status-bar" />
  4. <view class="main-container">
  5. <wk-nav-bar :title="navTitle">
  6. <!-- #ifndef MP-WEIXIN -->
  7. <button class="button white-btn" @click="handleSave">
  8. 保存
  9. </button>
  10. <!-- #endif -->
  11. </wk-nav-bar>
  12. <view class="container">
  13. <view class="scroll-content">
  14. <wk-form
  15. v-if="fieldArr.length > 0"
  16. ref="form"
  17. :batch-id="batchId"
  18. :fields="fieldArr"
  19. @change="handleValueChange" />
  20. <view v-if="showCauseForm" class="travel">
  21. <add-travel
  22. v-for="(item, c_index) in causeList"
  23. ref="addTravel"
  24. :key="c_index"
  25. :index="c_index"
  26. :length="causeList.length"
  27. :type="causeType"
  28. :item-data="item"
  29. @change="handleChangeTravel"
  30. @delete="handleDeleteTravel" />
  31. <view class="travel-item-footer">
  32. <text class="wk wk-plus add-icon" />
  33. <text @click="handleAddTravel">
  34. 添加明细
  35. </text>
  36. </view>
  37. </view>
  38. <wk-form
  39. v-if="fieldArr2.length > 0"
  40. ref="form2"
  41. :batch-id="batchId"
  42. :fields="fieldArr2"
  43. @change="handleValueChange" />
  44. <view class="attachment">
  45. <!--图片-->
  46. <view class="attachment-box">
  47. <view class="attachment-title" @click.stop="addFile('img')">
  48. <view class="left">
  49. <image :src="$static('images/icon/image.png')" class="pic-icon" />
  50. 图片
  51. </view>
  52. <view class="right add-icon">
  53. <text class="wk wk-plus" />
  54. </view>
  55. </view>
  56. <wk-image-content
  57. show-delete
  58. :list="imgList"
  59. @delete="handleDeleteImg" />
  60. </view>
  61. <!--附件-->
  62. <view class="attachment-box">
  63. <view class="attachment-title" @click.stop="addFile('file')">
  64. <view class="left">
  65. <image :src="$static('images/icon/clip.png')" class="pic-icon" />
  66. 附件
  67. </view>
  68. <view class="right add-icon">
  69. <text class="wk wk-plus" />
  70. </view>
  71. </view>
  72. <wk-file-content
  73. show-delete
  74. :list="fileList"
  75. @delete="handleDeleteFile" />
  76. </view>
  77. <!--关联业务-->
  78. <view class="attachment-box">
  79. <view class="attachment-title" @click.stop="getRelevancePopOptions">
  80. <view class="left">
  81. <image :src="$static('images/icon/relate.png')" class="pic-icon" />
  82. 关联业务
  83. </view>
  84. <view class="right add-icon">
  85. <text class="wk wk-plus" />
  86. </view>
  87. </view>
  88. <relevance-section
  89. v-if="showRelevance"
  90. is-add
  91. :relevance-data="relevanceData"
  92. @delete="baseDelete" />
  93. </view>
  94. </view>
  95. <wk-audit-add ref="wkAuditAdd" />
  96. </view>
  97. </view>
  98. <!-- #ifdef MP-WEIXIN -->
  99. <view class="footer-btn-group">
  100. <button class="button" @click="handleSave">
  101. 保存
  102. </button>
  103. </view>
  104. <!-- #endif -->
  105. </view>
  106. <uni-popup ref="popup">
  107. <view class="pop-wrapper">
  108. <view
  109. v-for="(item, index) in popOptions"
  110. :key="index"
  111. class="pop-item"
  112. @click.stop="handleSelectOptions(item)">
  113. {{ item.label }}
  114. </view>
  115. </view>
  116. </uni-popup>
  117. </view>
  118. </template>
  119. <script>
  120. import {
  121. SetOaExamine,
  122. QueryExamineInfo,
  123. GetField,
  124. QueryField
  125. } from 'API/oa/examine'
  126. import { FileDeleteById } from 'API/file'
  127. import { PreviewFiledName } from 'API/examine'
  128. import RelevanceSection from '@/components/base/relevance-section.vue'
  129. import AddTravel from './components/addTravel'
  130. import formMixins from '@/mixins/formMixins.js'
  131. import WkFile from '@/utils/file.js'
  132. import { addOperation } from '@/utils/lib.js'
  133. export default {
  134. name: 'AddOaExamine',
  135. components: {
  136. RelevanceSection,
  137. AddTravel
  138. },
  139. mixins: [formMixins],
  140. data() {
  141. return {
  142. guid: null,
  143. id: null,
  144. examineId: null,
  145. routerQuery: {},
  146. fieldArr: [],
  147. fieldArr2: [],
  148. batchId: null,
  149. fileList: [],
  150. imgList: [],
  151. popOptions: [],
  152. causeList: [],
  153. showCauseForm: false,
  154. causeType: null,
  155. auditConditionFields: [],
  156. timer: null,
  157. customerList: [],
  158. contactsList: [],
  159. businessList: [],
  160. contractList: [],
  161. }
  162. },
  163. computed: {
  164. navTitle() {
  165. if (this.id) return `编辑${this.routerQuery.title}`
  166. else return `新建${this.routerQuery.title}`
  167. },
  168. showRelevance() {
  169. return this.customerList.length > 0 ||
  170. this.contactsList.length > 0 ||
  171. this.businessList.length > 0 ||
  172. this.contractList.length > 0
  173. },
  174. relevanceData() {
  175. return {
  176. customerList: this.customerList || [],
  177. contactsList: this.contactsList || [],
  178. businessList: this.businessList || [],
  179. contractList: this.contractList || [],
  180. }
  181. }
  182. },
  183. watch: {
  184. causeList: {
  185. handler(val) {
  186. const that = this
  187. let total = 0
  188. console.log('change case: ', val)
  189. if (that.causeType === 'travel') {
  190. // 自动计算出差总天数
  191. that.causeList.forEach(cause => {
  192. if (cause.hasOwnProperty('duration') && !that.$isEmpty(cause.duration)) {
  193. let v = isNaN(cause.duration) ? 0 : Number(cause.duration)
  194. console.log('vv:', v)
  195. total = addOperation(total, v)
  196. }
  197. })
  198. that.$nextTick(function() {
  199. that.$refs.form.setFormVal('duration', total)
  200. if (that.fieldArr2.length > 0) {
  201. that.$refs.form2.setFormVal('duration', total)
  202. }
  203. })
  204. } else if (that.causeType === 'examine') {
  205. // 自动计算差旅报销总金额
  206. let arr = ['traffic', 'stay', 'diet', 'other']
  207. that.causeList.forEach(cause => {
  208. if (!that.$isEmpty(cause)) {
  209. arr.forEach(item => {
  210. if (cause.hasOwnProperty(item) && !that.$isEmpty(cause[item])) {
  211. let v = isNaN(Number(cause[item])) ? 0 : Number(cause[item])
  212. total = addOperation(total, v)
  213. }
  214. })
  215. }
  216. })
  217. that.$nextTick(function() {
  218. that.$refs.form.setFormVal('money', total)
  219. if (that.fieldArr2.length > 0) {
  220. that.$refs.form2.setFormVal('money', total)
  221. }
  222. })
  223. }
  224. },
  225. deep: true
  226. }
  227. },
  228. created() {
  229. this.guid = this.$guid()
  230. },
  231. onLoad(options) {
  232. this.routerQuery = options || {}
  233. this.examineId = this.routerQuery.examineId || null // 审批类型id
  234. this.id = this.routerQuery.id || null // 审批id
  235. this.getData()
  236. },
  237. onUnload() {
  238. getApp().globalData.selectedValBridge = {}
  239. },
  240. methods: {
  241. /**
  242. * 通过id获取审批详情, 和自定义字段数据
  243. */
  244. getData() {
  245. let list = []
  246. if (this.id) {
  247. list = [
  248. GetField({examineId: this.id, isDetail: 2, label: 10, id: this.examineId, type: 1 }),
  249. QueryExamineInfo({ examineId: this.id }),
  250. ]
  251. } else {
  252. list = [
  253. QueryField({ examineId: this.examineId, type: 1 })
  254. ]
  255. }
  256. Promise.all(list).then(res => {
  257. // this.fieldArr = this.formatFieldArr(res[0])
  258. const obj = this.formatFieldArr(res[0])
  259. this.fieldArr = obj.list1
  260. this.fieldArr2 = obj.list2
  261. if (res.length > 1) {
  262. this.detailData = res[1]
  263. this.batchId = this.detailData.batchId || null
  264. this.setDefaultForm()
  265. }
  266. this.getAuthList()
  267. // console.log('detailRes', res[1])
  268. // console.log('fieldArr', this.fieldArr)
  269. console.log('add ', this)
  270. }).catch()
  271. },
  272. /**
  273. * 格式化自定义字段
  274. */
  275. formatFieldArr(response) {
  276. let index = response.findIndex(item => ['business_cause', 'examine_cause'].includes(item.formType))
  277. if (index !== -1) {
  278. // let res = response.splice(index, 1)
  279. let res = response.slice(index, index + 1)
  280. if (res[0].formType === 'business_cause') this.causeType = 'travel'
  281. else if (res[0].formType === 'examine_cause') this.causeType = 'examine'
  282. this.causeList = []
  283. this.showCauseForm = true
  284. this.causeList.push({})
  285. let findRes = response.find(item => {
  286. return item.fieldName === 'money' || item.fieldName === 'duration'
  287. })
  288. if (findRes) {
  289. findRes.disabled = true
  290. findRes.inputTips = '根据明细自动生成'
  291. }
  292. }
  293. response.forEach(field => {
  294. field.value = this.mixinsFormatFieldValue(field)
  295. })
  296. console.log('respon', response)
  297. if (index !== -1) {
  298. return {
  299. list1: response.slice(0, index),
  300. list2: response.slice(index + 1)
  301. }
  302. } else {
  303. return {
  304. list1: response,
  305. list2: []
  306. }
  307. }
  308. },
  309. setDefaultForm() {
  310. const lib = {
  311. img: 'imgList',
  312. file: 'fileList',
  313. customerList: 'customerList',
  314. businessList: 'businessList',
  315. contractList: 'contractList',
  316. contactsList: 'contactsList'
  317. }
  318. Object.keys(lib).forEach(key => {
  319. this[lib[key]] = this.detailData[key] || []
  320. })
  321. this.causeList = this.detailData.examineTravelList || []
  322. if (this.causeList.length === 0) this.causeList.push({})
  323. this.$nextTick(() => {
  324. console.log(this.detailData)
  325. // this.fieldArr.forEach(field => {
  326. // if (field.value instanceof Array) field.value = field.value.join(',')
  327. // this.$refs.form.setForm(null, field.fieldName, field.value)
  328. // })
  329. })
  330. },
  331. /**
  332. * 获取审批流程条件
  333. */
  334. getAuthList() {
  335. PreviewFiledName({
  336. label: 0,
  337. examineId: this.examineId
  338. }).then(res => {
  339. const mapIns = new Map()
  340. this.auditConditionFields = res.filter(o => !mapIns.has(o.fieldId) && mapIns.set(o.fieldId, 1))
  341. console.log('PreviewFiledName: ', this.auditConditionFields)
  342. this.$nextTick(() => {
  343. const dataMap = {}
  344. this.auditConditionFields.forEach(o => {
  345. const findRes = this.fieldArr.find(f => f.fieldName === o.fieldName)
  346. if (findRes) {
  347. let value = findRes.value
  348. switch (findRes.formType) {
  349. case 'select':
  350. if (this.$isEmpty(value)) value = ''
  351. if (isArray(value)) {
  352. value = value[0].value
  353. }
  354. break
  355. case 'checkbox':
  356. if (this.$isEmpty(value)) value = ''
  357. if (isArray(value)) {
  358. value = value.map(o => o.value).join(',')
  359. }
  360. break
  361. default:
  362. value = this.$isEmpty(value) ? '' : value
  363. }
  364. o._val = value
  365. dataMap[o.fieldName] = value
  366. }
  367. })
  368. this.$refs.wkAuditAdd.getAuditInfo({
  369. label: 0,
  370. examineId: this.examineId,
  371. dataMap
  372. })
  373. })
  374. }).catch(() => {})
  375. },
  376. /**
  377. * 根据审批条件变更审批流
  378. */
  379. changeAuditFlow(field, value) {
  380. const findRes = this.auditConditionFields.find(o => o.fieldName === field.fieldName)
  381. if (!findRes) return
  382. if (this.timer) {
  383. clearTimeout(this.timer)
  384. this.timer = null
  385. }
  386. this.timer = setTimeout(() => {
  387. // number floatnumber select checkbox
  388. switch (field.formType) {
  389. case 'select':
  390. if (this.$isEmpty(value)) value = ''
  391. if (isArray(value)) {
  392. value = value[0].value
  393. }
  394. break
  395. case 'checkbox':
  396. if (this.$isEmpty(value)) value = ''
  397. if (isArray(value)) {
  398. value = value.map(o => o.value).join(',')
  399. }
  400. break
  401. }
  402. findRes._val = value
  403. const dataMap = {}
  404. this.auditConditionFields.forEach(o => {
  405. if (o.hasOwnProperty('_val')) {
  406. dataMap[o.fieldName] = this.$isEmpty(o._val) ? '' : o._val
  407. } else {
  408. dataMap[o.fieldName] = ''
  409. }
  410. })
  411. this.$nextTick(() => {
  412. this.$refs.wkAuditAdd.getAuditInfo({
  413. label: 0,
  414. examineId: this.examineId,
  415. dataMap
  416. })
  417. clearTimeout(this.timer)
  418. this.timer = null
  419. })
  420. }, 500)
  421. },
  422. handleValueChange(data) {
  423. console.log('value change: ', data)
  424. this.changeAuditFlow(data.field, data.value)
  425. },
  426. /**
  427. * 添加附件和图片
  428. */
  429. addFile(type) {
  430. let params = { type: type }
  431. if (this.batchId) {
  432. params.batchId = this.batchId
  433. }
  434. let fileObj = new WkFile(params)
  435. fileObj.choose().then(response => {
  436. this.batchId = response[0].batchId
  437. if (type === 'img') {
  438. this.imgList = this.imgList.concat(response)
  439. } else {
  440. this.fileList = this.fileList.concat(response)
  441. }
  442. fileObj = null
  443. })
  444. },
  445. /**
  446. * 弹出选择关联业务
  447. */
  448. getRelevancePopOptions() {
  449. const map = [
  450. { label: '客户', value: 'customer', auth: 'crm.customer.index' },
  451. { label: '联系人', value: 'contacts', auth: 'crm.contacts.index' },
  452. { label: '商机', value: 'business', auth: 'crm.business.index' },
  453. { label: '合同', value: 'contract', auth: 'crm.contract.index' }
  454. ]
  455. this.popOptions = map.filter(o => {
  456. return this.$auth(o.auth)
  457. })
  458. this.$refs.popup.open()
  459. },
  460. /**
  461. * 去选择关联项
  462. * @param {Object} opt
  463. */
  464. handleSelectOptions(opt) {
  465. this.$refs.popup.close()
  466. console.log(opt)
  467. const bridge = getApp().globalData.selectedValBridge
  468. bridge[opt.value] = {
  469. guid: this.guid,
  470. defaultVal: this[`${opt.value}List`] || []
  471. }
  472. uni.$on('selected-relevance', this.selectedRelevance)
  473. this.$Router.navigateTo({
  474. url: '/pages_common/selectList/relevance',
  475. query: {
  476. type: opt.value
  477. }
  478. })
  479. },
  480. /**
  481. * 选中关联项
  482. * @param {Object} data
  483. */
  484. selectedRelevance(data) {
  485. if (this.guid === data.guid) {
  486. console.log('add log on: ', data)
  487. this[`${data.type}List`] = data.data
  488. }
  489. uni.$off('selected-relevance')
  490. },
  491. /**
  492. * 行程添加
  493. */
  494. handleAddTravel() {
  495. this.causeList.push({})
  496. },
  497. handleChangeTravel(formData, index) {
  498. this.$set(this.causeList, index, formData)
  499. },
  500. handleDeleteTravel(index) {
  501. this.showCauseForm = false
  502. this.causeList.splice(index, 1)
  503. this.$nextTick(() => {
  504. this.showCauseForm = true
  505. })
  506. },
  507. /**
  508. * 删除附件
  509. */
  510. handleDeleteFile(index, item) {
  511. const fileId = item.fileId
  512. this.fileList.splice(index, 1)
  513. FileDeleteById({ id: fileId }).then().catch()
  514. },
  515. /**
  516. * 删除图片
  517. */
  518. handleDeleteImg(index, item) {
  519. const fileId = item.fileId
  520. this.imgList.splice(index, 1)
  521. FileDeleteById({ id: fileId }).then().catch()
  522. },
  523. /**
  524. * 公用删除
  525. */
  526. baseDelete(type, index) {
  527. console.log('delete: ', type, index)
  528. let arr = ['customer', 'contacts', 'business', 'contract', 'img', 'file']
  529. if (type === 'img' || type === 'file') {
  530. let fileId = this[`${type}List`][index].fileId
  531. FileDeleteById({ id: fileId }).then().catch()
  532. }
  533. if (arr.includes(type)) {
  534. this[`${type}List`].splice(index, 1)
  535. }
  536. },
  537. checkTravel() {
  538. for (let i = 0; i < this.causeList.length; i++) {
  539. const travel = this.causeList[i]
  540. let startTime = travel.startTime || null
  541. let endTime = travel.endTime || null
  542. if (startTime && endTime && startTime > endTime) {
  543. this.$toast('开始时间不能大于结束时间')
  544. return false
  545. }
  546. }
  547. return true
  548. },
  549. handleSave() {
  550. let params = {
  551. field: [],
  552. oaExamineTravelList: [],
  553. oaExamine: {
  554. categoryId: this.examineId
  555. },
  556. oaExamineRelation: { // 关联业务
  557. customerIds: this.customerList.map(o => o.customerId).join(',') || '',
  558. contactsIds: this.contactsList.map(o => o.contactsId).join(',') || '',
  559. businessIds: this.businessList.map(o => o.businessId).join(',') || '',
  560. contractIds: this.contractList.map(o => o.contractId).join(',') || '',
  561. }
  562. }
  563. if (this.batchId) params.oaExamine.batchId = this.batchId // 文件
  564. if (this.id) {
  565. params.oaExamine.categoryId = this.examineId
  566. params.oaExamine.examineId = this.id
  567. params.oaExamine.batchId = this.detailData.batchId
  568. }
  569. const promiseArr = [this.$refs.form.getForm()]
  570. if (this.fieldArr2.length > 0) {
  571. promiseArr.push(this.$refs.form2.getForm())
  572. }
  573. Promise.all(promiseArr).then(resArr => {
  574. const res1 = resArr[0]
  575. const res2 = resArr.length > 1 ? resArr[1] : { field: [], entity: {}}
  576. params.field = [...res1.field, ...res2.field]
  577. params.oaExamine = {
  578. ...params.oaExamine,
  579. ...res1.entity,
  580. ...res2.entity
  581. }
  582. let startTime = params.oaExamine.startTime || params.oaExamine.start_time || null
  583. let endTime = params.oaExamine.endTime || params.oaExamine.end_time || null
  584. if (startTime && endTime && startTime > endTime) {
  585. this.$toast('审批开始时间不能大于结束时间')
  586. return;
  587. }
  588. // 行程/差旅明细
  589. if (this.showCauseForm) {
  590. if (!this.checkTravel()) return;
  591. params.oaExamineTravelList = this.causeList.map(o => {
  592. const item = {...o}
  593. delete o.img
  594. return item
  595. })
  596. }
  597. // 审批信息
  598. const data = this.$refs.wkAuditAdd.getSaveData()
  599. if (data === null) return
  600. const dataMap = {}
  601. this.auditConditionFields.forEach(o => {
  602. if (o.hasOwnProperty('_val')) {
  603. dataMap[o.fieldName] = this.$isEmpty(o._val) ? '' : o._val
  604. }
  605. })
  606. params.examineFlowData = {
  607. ...data,
  608. label: 0,
  609. examineId: this.examineId,
  610. dataMap
  611. }
  612. params.field = params.field
  613. .filter(o => o.formType !== 'desc_text')
  614. .map(o => {
  615. return {
  616. fieldName: o.fieldName,
  617. name: o.name,
  618. type: o.type,
  619. fieldId: o.fieldId,
  620. value: o.value,
  621. fieldType: o.fieldType
  622. }
  623. })
  624. console.log('save: ', params)
  625. SetOaExamine(params).then(() => {
  626. this.$toast(this.id ? '修改成功' : '添加成功')
  627. this.$refreshAndToPrev(this)
  628. }).catch()
  629. })
  630. }
  631. }
  632. }
  633. </script>
  634. <style scoped lang="scss">
  635. .main-container {
  636. .container {
  637. flex: 1;
  638. // padding: 20rpx 0;
  639. overflow: hidden;
  640. .scroll-content {
  641. width: 100%;
  642. height: 100%;
  643. overflow: auto;
  644. }
  645. .travel-item-footer {
  646. width: 100%;
  647. height: 80rpx;
  648. font-size: 30rpx;
  649. color: $theme-color;
  650. background-color: #F3F5FA;
  651. @include center;
  652. .add-icon {
  653. font-size: 32rpx;
  654. color: $theme-color;
  655. margin-right: 15rpx;
  656. }
  657. }
  658. .attachment {
  659. margin-top: 20rpx;
  660. background-color: #fff;
  661. padding: 0 30rpx;
  662. color: $dark;
  663. font-size: 30rpx;
  664. .attachment-box {
  665. padding: 20rpx 0;
  666. border-bottom: 1rpx solid $border-color;
  667. .attachment-title {
  668. width: 100%;
  669. font-size: 30rpx;
  670. @include left;
  671. .left {
  672. flex: 1;
  673. @include left;
  674. .pic-icon {
  675. width: 38rpx;
  676. height: 38rpx;
  677. margin-right: 15rpx;
  678. }
  679. }
  680. .add-icon {
  681. width: 38rpx;
  682. height: 38rpx;
  683. background-color: #E1E1E1;
  684. border-radius: 50%;
  685. @include center;
  686. .wk {
  687. font-size: $wk-font-mini;
  688. line-height: normal;
  689. color: white;
  690. }
  691. }
  692. }
  693. .attachment-content {
  694. padding-top: 20rpx;
  695. @include left;
  696. .attachment-content-item {
  697. @include center;
  698. flex-direction: column;
  699. position: relative;
  700. }
  701. .icon {
  702. width: 22rpx;
  703. height: 22rpx;
  704. position: absolute;
  705. right: 0;
  706. top: 0;
  707. }
  708. .realname {
  709. padding-top: 15rpx;
  710. }
  711. }
  712. }
  713. }
  714. }
  715. }
  716. </style>