BaseForm.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <template>
  2. <a-modal :title="modalTitle" :width="900" :visible="visible" :confirmLoading="confirmLoading" class="ant-modal2"
  3. @cancel="handleCancel">
  4. <a-form :form="form">
  5. <a-form-item v-show="false">
  6. <a-input v-decorator="['id']" type="hidden" />
  7. <a-input v-decorator="['sbId']" type="hidden" />
  8. <a-input v-decorator="['deptId', { initialValue: userInfo.deptId }]" type="hidden" />
  9. </a-form-item>
  10. <row-list :col="2">
  11. <row-item>
  12. <a-form-item label="设备名称" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  13. <a-input style="width: 70%" disabled
  14. v-decorator="['sbName', { rules: [{ required: true, message: '设备名称不能为空' }] }]" />
  15. <a-button style="width: 30%" type="primary" @click="handleSbSelect">选择</a-button>
  16. </a-form-item>
  17. </row-item>
  18. <row-item>
  19. <a-form-item label="使用位置" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  20. <!-- <a-input v-decorator="['sbCph', { rules: [{ required: true, message: '设备使用位置不能为空' }] }]" /> -->
  21. <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
  22. :treeData="sbPositionData" :replaceFields="{
  23. value: 'title', title: 'title', key: 'id'
  24. }" :treeNodeFilterProp="'title'" :showSearch="true"
  25. v-decorator="['sbCph', { rules: [{ required: true, message: '设备使用位置不能为空' }] }]" placeholder="请选择">
  26. </a-tree-select>
  27. </a-form-item>
  28. </row-item>
  29. <row-item>
  30. <a-form-item label="工单类别" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  31. <a-select @change="changePlanFlag"
  32. v-decorator="['category', { rules: [{ required: true, message: '计划性维修不能为空' }] }]" placeholder="请选择">
  33. <a-select-option v-for="(label, value) in categoryMap" :key="value" :label="label"
  34. :value="parseInt(value)">{{ label }}
  35. </a-select-option>
  36. </a-select>
  37. </a-form-item>
  38. </row-item>
  39. <row-item>
  40. <a-form-item label="是否停机" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  41. <a-select v-decorator="['needStop', { rules: [{ required: true, message: '是否停机不能为空' }] }]"
  42. placeholder="请选择">
  43. <a-select-option v-for="(label, value) in needStopMap" :key="value" :label="label"
  44. :value="parseInt(value)">{{ label }}
  45. </a-select-option>
  46. </a-select>
  47. </a-form-item>
  48. </row-item>
  49. <row-item>
  50. <a-form-item label="要求日期" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  51. <a-date-picker style="width: 100%" :format="BaseTool.Date.PICKER_NORM_DATE_PATTERN"
  52. v-decorator="['limitDate']" />
  53. </a-form-item>
  54. </row-item>
  55. <row-item>
  56. <a-form-item label="要求时间" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  57. <a-input style="width: 100%" suffix="小时" v-decorator="['limitHours']" />
  58. </a-form-item>
  59. </row-item>
  60. <row-item>
  61. <a-form-item label="报修单号" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  62. <a-input disabled v-decorator="['no', { rules: [{ required: false, message: '报修单号不能为空' }] }]" />
  63. </a-form-item>
  64. </row-item>
  65. <!-- <row-item>
  66. <a-form-item
  67. label="报修来源"
  68. :labelCol="BaseTool.Constant.labelCol"
  69. :wrapperCol="BaseTool.Constant.wrapperCol"
  70. >
  71. <a-select v-decorator="['source', {initialValue: DictCache.VALUE.REPAIR_APPLICATION_FORM_SOURCE.SCENE,rules: [{required: true, message: '报修来源不能为空'}]}]" placeholder="请选择">
  72. <a-select-option
  73. v-for="(label,value) in sourceMap"
  74. :key="value"
  75. :label="label"
  76. :value="parseInt(value)">{{ label }}
  77. </a-select-option>
  78. </a-select>
  79. </a-form-item>
  80. </row-item>-->
  81. <row-item>
  82. <a-form-item label="报修人" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  83. <!-- 报修人账号下拉带搜索,提交 userId,避免手输同名歧义 -->
  84. <a-select
  85. showSearch
  86. :filterOption="(input, option) => option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0"
  87. v-decorator="['userId', { initialValue: userInfo.userId, rules: [{ required: true, message: '报修人不能为空' }] }]"
  88. placeholder="请选择报修人"
  89. >
  90. <a-select-option v-for="user in userList" :key="user.userId" :value="user.userId" :label="user.realName">
  91. {{ user.realName }}<template v-if="user.username">({{ user.username }})</template>
  92. </a-select-option>
  93. </a-select>
  94. </a-form-item>
  95. </row-item>
  96. <row-item>
  97. <a-form-item label="紧急程度" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  98. <a-select v-decorator="['level', { rules: [{ required: true, message: '紧急程度不能为空' }] }]" placeholder="请选择">
  99. <a-select-option v-for="(label, value) in levelMap" :key="value" :label="label"
  100. :value="parseInt(value)">{{
  101. label }}
  102. </a-select-option>
  103. </a-select>
  104. </a-form-item>
  105. </row-item>
  106. <row-item>
  107. <a-form-item label="故障类型" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  108. <a-select style="width: 100%"
  109. v-decorator="['repairErrorTypeId', { rules: [{ required: true, message: '故障类型不能为空' }] }]"
  110. placeholder="请选择" allowClear>
  111. <a-select-option v-for="(label, value) in faultTypeMap" :key="value" :label="label" :value="value">{{
  112. label
  113. }}
  114. </a-select-option>
  115. </a-select>
  116. </a-form-item>
  117. </row-item>
  118. <row-item>
  119. <a-form-item label="通知班长" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  120. <a-select mode="multiple" allowClear :maxTagCount="1" placeholder="请选择通知班长(可多选,非必填)"
  121. v-decorator="['notifyTeamLeaders']">
  122. <a-select-option v-for="leader in teamLeaderList" :key="leader.userId" :value="leader.userId"
  123. :label="leader.realName">{{ leader.realName }}
  124. </a-select-option>
  125. </a-select>
  126. </a-form-item>
  127. </row-item>
  128. <row-item>
  129. <a-form-item label="报修图片" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  130. <a-upload :action="uploadUrl" :multiple="true" list-type="picture"
  131. :file-list="this.defaultApplicationFileList" @change="handleApplicationFileChange" accept="image/*"
  132. :headers="headers">
  133. <a-button> <a-icon type="upload" /> 上传图片 </a-button>
  134. </a-upload>
  135. </a-form-item>
  136. </row-item>
  137. </row-list>
  138. <row-list :col="1">
  139. <row-item v-if="false">
  140. <a-form-item label="报修状态" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  141. <a-select disabled
  142. v-decorator="['status', { initialValue: DictCache.VALUE.REPAIR_APPLICATION_FORM_STATUS.NOT_ALLOCATED, rules: [{ required: true, message: '报修状态不能为空' }] }]"
  143. placeholder="请选择">
  144. <a-select-option v-for="(label, value) in statusMap" :key="value" :label="label"
  145. :value="parseInt(value)">{{ label }}
  146. </a-select-option>
  147. </a-select>
  148. </a-form-item>
  149. </row-item>
  150. <!-- <row-item>
  151. <a-form-item
  152. label="故障描述"
  153. :labelCol="BaseTool.Constant.labelCol2"
  154. :wrapperCol="BaseTool.Constant.wrapperCol2"
  155. >
  156. <a-select v-decorator="['content']" placeholder="请选择">
  157. <a-select-option
  158. v-for="(label,value) in questionMap"
  159. :key="value"
  160. :label="label"
  161. :value="parseInt(value)">{{ label }}
  162. </a-select-option>
  163. </a-select>
  164. </a-form-item>
  165. </row-item>-->
  166. <row-item>
  167. <a-form-item label="故障描述" :labelCol="BaseTool.Constant.labelCol2" :wrapperCol="BaseTool.Constant.wrapperCol2">
  168. <a-textarea :rows="4" v-decorator="['content', { rules: [{ required: true, message: '故障描述不能为空' }] }]" />
  169. </a-form-item>
  170. </row-item>
  171. </row-list>
  172. </a-form>
  173. <template slot="footer">
  174. <a-button :loading="confirmLoading" type="primary" @click="save()">保存</a-button>
  175. </template>
  176. <sb-info-select-modal ref="sbInfoSelectModal" @selected="handleSbSelectd" />
  177. <part-info-select-modal ref="partInfoSelectModal" @selected="handlePartSelected" />
  178. </a-modal>
  179. </template>
  180. <script>
  181. import pick from 'lodash.pick'
  182. import BaseTool from '../../../../utils/tool'
  183. import { addRepairApplicationForm, updateRepairApplicationForm, queryUncompletedRepairs } from '@/api/repair/application-form'
  184. import PartInfoSelectModal from '@/views/part/info/modules/PartInfoSelectModal'
  185. import SbInfoSelectModal from '@/views/sb/info/modules/SbInfoSelectModal'
  186. import { queryUser, queryRepairTeamLeaders } from '@/api/upms/user'
  187. import { uploadUrl } from '@/api/upms/file'
  188. import Vue from 'vue'
  189. import { ACCESS_TOKEN } from '@/store/mutation-types'
  190. import { getSbPositionTree } from '@/api/sb/position'
  191. export default {
  192. name: 'BaseRepairApplicationForm',
  193. data() {
  194. return {
  195. confirmLoading: false,
  196. modalTitle: null,
  197. form: this.$form.createForm(this),
  198. visible: false,
  199. // 下拉框map
  200. sourceMap: {},
  201. levelMap: {},
  202. questionMap: {},
  203. needStop: null,
  204. sbPositionData: [],
  205. needStopMap: {},
  206. faultTypeMap: {},
  207. category: 0,
  208. categoryMap: {},
  209. statusMap: {},
  210. userInfo: this.$store.getters.userInfo,
  211. userList: [],
  212. // 通知班长下拉:全部班长候选人 + 当前用户班组下的班长(默认勾选)
  213. teamLeaderList: [],
  214. defaultLeaderIds: [],
  215. uploadUrl: uploadUrl,
  216. defaultRepairFileList: [],
  217. defaultApplicationFileList: [],
  218. defaultCheckFileList: [],
  219. applicationFileList: [], // 报修图片
  220. checkFileList: [], // 审核图片
  221. repairFileList: [], // 维修图片,
  222. headers: {
  223. Authorization: 'Bearer ' + Vue.ls.get(ACCESS_TOKEN),
  224. },
  225. }
  226. },
  227. components: {
  228. SbInfoSelectModal,
  229. PartInfoSelectModal,
  230. },
  231. props: {},
  232. created() {
  233. // 下拉框map
  234. this.sourceMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_APPLICATION_FORM_SOURCE)
  235. this.levelMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_APPLICATION_FORM_LEVEL)
  236. this.statusMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_APPLICATION_FORM_STATUS)
  237. this.questionMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_QUESTION)
  238. this.faultTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_ERROR_TYPE)
  239. this.needStopMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.YES_NO)
  240. this.categoryMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.REPAIR_APPLICATION_FORM_CATEGORY)
  241. this.getUsers()
  242. this.getTeamLeaders()
  243. },
  244. methods: {
  245. base(record) {
  246. this.visible = true
  247. getSbPositionTree().then(res => {
  248. this.sbPositionData = res.data
  249. })
  250. // 如果是空标识添加
  251. if (this.BaseTool.Object.isBlank(record)) {
  252. this.modalTitle = '添加'
  253. this.setDefaultTeamLeaders()
  254. if (!this.BaseTool.Object.isBlank(record) && !this.BaseTool.Object.isBlank(record.sbId)) {
  255. const {
  256. form: { setFieldsValue },
  257. } = this
  258. this.$nextTick(() => {
  259. setFieldsValue(Object.assign(pick(record, ['sbId', 'sbName'])))
  260. })
  261. }
  262. return
  263. }
  264. if (this.BaseTool.Object.isBlank(record.id)) {
  265. this.modalTitle = '添加'
  266. this.setDefaultTeamLeaders()
  267. if (!this.BaseTool.Object.isBlank(record.sbId)) {
  268. const {
  269. form: { setFieldsValue },
  270. } = this
  271. this.$nextTick(() => {
  272. setFieldsValue(Object.assign(pick(record, ['sbId', 'sbName', 'sbCph'])))
  273. })
  274. }
  275. return
  276. }
  277. this.modalTitle = '编辑'
  278. // 通知班长回显:逗号分隔字符串转数组
  279. record.notifyTeamLeaders = this.BaseTool.Object.isBlank(record.notifyTeamLeaders)
  280. ? []
  281. : record.notifyTeamLeaders.split(',')
  282. this.applicationFileList = record.applicationFileList
  283. this.repairFileList = record.repairFileList
  284. this.checkFileList = record.checkFileList
  285. this.defaultApplicationFileList = this.BaseTool.UPLOAD.transImg(this.applicationFileList)
  286. this.defaultRepairFileList = this.BaseTool.UPLOAD.transImg(this.repairFileList)
  287. this.defaultCheckFileList = this.BaseTool.UPLOAD.transImg(this.checkFileList)
  288. const {
  289. form: { setFieldsValue },
  290. } = this
  291. // 日期处理
  292. record.applyTime = this.BaseTool.Moment(record.applyTime, this.BaseTool.Date.PICKER_NORM_DATETIME_PATTERN)
  293. if (record.limitDate != null) {
  294. record.limitDate = this.BaseTool.Moment(record.limitDate, this.BaseTool.Date.PICKER_NORM_DATE_PATTERN)
  295. this.$nextTick(() => {
  296. setFieldsValue(Object.assign(pick(record, ['limitDate'])))
  297. })
  298. }
  299. this.$nextTick(() => {
  300. setFieldsValue(
  301. Object.assign(
  302. pick(record, [
  303. 'id',
  304. 'sbId',
  305. 'sbCph',
  306. 'partId',
  307. 'repairUserId',
  308. 'no',
  309. 'category',
  310. 'limitDate',
  311. 'limitHours',
  312. 'source',
  313. 'level',
  314. 'content',
  315. 'status',
  316. 'sbName',
  317. 'needStop',
  318. 'category',
  319. 'partName',
  320. 'remark',
  321. 'repairErrorTypeId',
  322. 'notifyTeamLeaders',
  323. 'userId',
  324. 'actualUser',
  325. 'deptId'
  326. ])
  327. )
  328. )
  329. })
  330. },
  331. getUsers() {
  332. queryUser({ status: 1 }).then((res) => {
  333. this.userList = res.data
  334. })
  335. },
  336. getTeamLeaders() {
  337. queryRepairTeamLeaders().then((res) => {
  338. const data = res.data || {}
  339. this.teamLeaderList = data.leaders || []
  340. this.defaultLeaderIds = data.defaultIds || []
  341. })
  342. },
  343. // 默认带出当前用户班组下的班长,找不到则空着
  344. setDefaultTeamLeaders() {
  345. this.$nextTick(() => {
  346. this.form.setFieldsValue({ notifyTeamLeaders: this.defaultLeaderIds })
  347. })
  348. },
  349. save() {
  350. const {
  351. form: { validateFieldsAndScroll },
  352. } = this
  353. this.confirmLoading = true
  354. validateFieldsAndScroll((errors, values) => {
  355. if (errors) {
  356. this.confirmLoading = false
  357. return
  358. }
  359. // 日期处理
  360. values.applyTime = this.BaseTool.Date.formatter(
  361. values.applyTime,
  362. this.BaseTool.Date.PICKER_NORM_DATETIME_PATTERN
  363. )
  364. values.limitDate = BaseTool.Date.formatter(values.limitDate, BaseTool.Date.PICKER_NORM_DATE_PATTERN)
  365. // 上传文件
  366. values.repairFileList = this.repairFileList
  367. values.applicationFileList = this.applicationFileList
  368. values.checkFileList = this.checkFileList
  369. // 通知班长:多选数组转逗号分隔字符串
  370. values.notifyTeamLeaders = (values.notifyTeamLeaders || []).join(',')
  371. // 报修人:根据下拉选中的 userId 同步姓名,后端以 userId 为准
  372. const chosenUser = this.userList.find((user) => user.userId === values.userId)
  373. values.actualUser = chosenUser ? chosenUser.realName : this.userInfo.realName
  374. if (this.BaseTool.String.isBlank(values.id)) {
  375. // 重复报修检查:该设备存在未完结报修单时需先确认再提交(允许一台设备多个报修单)
  376. this.confirmDuplicateAdd(values)
  377. } else {
  378. updateRepairApplicationForm(values)
  379. .then(() => {
  380. this.handleCancel(values)
  381. })
  382. .catch(() => {
  383. this.confirmLoading = false
  384. })
  385. }
  386. })
  387. },
  388. // 提交前检查该设备是否存在未完结报修单,存在则弹确认框,确认后仍允许提交
  389. confirmDuplicateAdd(values) {
  390. queryUncompletedRepairs({ sbId: values.sbId })
  391. .then((res) => {
  392. const list = res.data || []
  393. if (list.length === 0) {
  394. this.doAddRepair(values)
  395. return
  396. }
  397. this.confirmLoading = false
  398. const [first] = list
  399. const h = this.$createElement
  400. // 展示最新一笔未完结报修单的状态与报修内容,内容过长截断
  401. const desc = (first.content || '').length > 30 ? `${first.content.slice(0, 30)}…` : first.content || ''
  402. const detail = `${first.no} ${first.statusLabel || ''}${desc ? ',内容:' + desc : ''}`
  403. const content = values.sbName
  404. ? `【${values.sbName}】已有 ${list.length} 个未完结报修单(最新:${detail}),是否继续提交新报修单?`
  405. : `该设备已有 ${list.length} 个未完结报修单(最新:${detail}),是否继续提交新报修单?`
  406. this.$confirm({
  407. title: '重复报修提醒',
  408. content: h('div', content),
  409. okText: '继续提交',
  410. cancelText: '取消',
  411. onOk: () => {
  412. this.confirmLoading = true
  413. this.doAddRepair(values)
  414. },
  415. })
  416. })
  417. .catch(() => {
  418. this.confirmLoading = false
  419. })
  420. },
  421. doAddRepair(values) {
  422. this.confirmLoading = true
  423. addRepairApplicationForm(values)
  424. .then(() => {
  425. this.handleCancel(values)
  426. })
  427. .catch(() => {
  428. this.confirmLoading = false
  429. })
  430. },
  431. changePlanFlag(value) {
  432. // 选择工单类别不再自动联动“是否停机”:停机与否必须由报修人在“是否停机”处显式选择
  433. this.category = value
  434. },
  435. handleCancel(values) {
  436. this.visible = false
  437. this.confirmLoading = false
  438. this.applicationFileList = []
  439. this.defaultApplicationFileList = []
  440. this.form.resetFields()
  441. if (this.BaseTool.Object.isNotBlank(values)) {
  442. this.$emit('ok', values)
  443. }
  444. },
  445. handleSbSelect() {
  446. this.$refs.sbInfoSelectModal.base({}, {})
  447. },
  448. handleSbSelectd(keys, rows) {
  449. const [key] = keys
  450. const [row] = rows
  451. const {
  452. form: { setFieldsValue },
  453. } = this
  454. // 日期处理
  455. this.$nextTick(() => {
  456. setFieldsValue(
  457. Object.assign({
  458. sbId: key,
  459. sbName: row.name,
  460. sbCph: row.positionName,
  461. })
  462. )
  463. })
  464. },
  465. handlePartSelect() {
  466. const sbId = this.form.getFieldValue('sbId')
  467. this.$refs.partInfoSelectModal.base({ sbId })
  468. },
  469. handlePartSelected(keys, rows) {
  470. const [key] = keys
  471. const [row] = rows
  472. const {
  473. form: { setFieldsValue },
  474. } = this
  475. this.$nextTick(() => {
  476. setFieldsValue(
  477. Object.assign({
  478. partId: key,
  479. partName: row.name,
  480. })
  481. )
  482. })
  483. },
  484. handleApplicationFileChange(info) {
  485. this.defaultApplicationFileList = info.fileList
  486. this.applicationFileList = this.setFileList(info, 11)
  487. },
  488. setFileList(info, type) {
  489. const file = info.file
  490. const fileList = info.fileList
  491. if (file.status === 'done') {
  492. return this.BaseTool.UPLOAD.getUploadFileDTO(fileList, type)
  493. } else if (file.status === 'removed') {
  494. return this.BaseTool.UPLOAD.getUploadFileDTO(fileList, type)
  495. } else if (file.status === 'error') {
  496. this.$message.error('上传失败')
  497. return []
  498. }
  499. },
  500. },
  501. }
  502. </script>