BatchBaseForm.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. <template>
  2. <a-card v-show="visible" :title="modalTitle">
  3. <template #extra>
  4. <span class="table-page-search-submitButtons" style="float: right">
  5. <a-button :loading="confirmLoading" type="primary" @click="save()">保存</a-button>
  6. <a-button style="margin-left: 8px" @click="handleCancel()">返回</a-button>
  7. </span>
  8. </template>
  9. <a-form :form="form">
  10. <a-form-item v-show="false">
  11. <a-input v-decorator="['sbId']" type="hidden" />
  12. </a-form-item>
  13. <row-list :col="2">
  14. <row-item>
  15. <a-form-item label="设备编号" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  16. <a-input disabled style="width: 80%" v-decorator="['sbNo', {rules: [{required: true, message: '请选择设备'}]}]" />
  17. <a-button type="primary" style="width: 20%" @click="handleSbSelect">选择</a-button>
  18. </a-form-item>
  19. </row-item>
  20. <row-item>
  21. <a-form-item label="标准类型" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
  22. <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
  23. :treeData="treeData" :treeNodeFilterProp="'title'" :showSearch="true"
  24. v-decorator="['sbType', {rules: [{required: true, message: '请选择标准类型'}]}]"
  25. placeholder="请选择" @change="handleSbTypeChange">
  26. </a-tree-select>
  27. </a-form-item>
  28. </row-item>
  29. </row-list>
  30. </a-form>
  31. <div class="table-operator">
  32. <a-button style="margin:8px;" type="primary" @click="handleAdd">
  33. <a-icon type="plus" />
  34. 添加
  35. </a-button>
  36. </div>
  37. <a-table :data-source="data" :key="tableKey" :columns="columns" bordered ref="table" :scroll="{x: 1600 }">
  38. <span slot="level" slot-scope="text, record">
  39. <a-select style="width:100%" v-model="record.level" placeholder="请选择">
  40. <a-select-option v-for="(label,value) in levelMap" :key="value" :label="label" :value="parseInt(value)">{{ label }}
  41. </a-select-option>
  42. </a-select>
  43. </span>
  44. <span slot="checkUserType" slot-scope="text, record">
  45. <a-select style="width:100%" v-model="record.checkUserType" placeholder="请选择"
  46. @change="(val)=>handleCheckUserTypeChange(val, record)">
  47. <a-select-option v-for="(label,value) in checkUserTypeMap" :key="value" :label="label" :value="parseInt(value)">{{ label }}
  48. </a-select-option>
  49. </a-select>
  50. </span>
  51. <span slot="checkDeptId" slot-scope="text, record">
  52. <a-tree-select v-if="record.checkUserType === 4" style="width:100%"
  53. :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }" :treeData="deptTree"
  54. treeNodeFilterProp="title" :showSearch="true" v-model="record.checkDeptId" placeholder="请选择班组">
  55. </a-tree-select>
  56. <span v-else style="color:#bbb">-</span>
  57. </span>
  58. <span slot="period" slot-scope="text, record">
  59. <a-input-group compact>
  60. <a-input-number style="width: 50%" :min="1" v-model="record.period" />
  61. <a-select style="width: 50%" v-model="record.periodType" placeholder="请选择">
  62. <a-select-option v-for="(label,value) in periodTypeMap" :key="value" :label="label" :value="parseInt(value)">{{ label }}
  63. </a-select-option>
  64. </a-select>
  65. </a-input-group>
  66. </span>
  67. <span slot="standardHours" slot-scope="text, record">
  68. <a-input v-model="record.standardHours" suffix="小时" />
  69. </span>
  70. <span slot="sort" slot-scope="text, record">
  71. <a-input-number style="width: 100%" :step="1" v-model="record.sort" />
  72. </span>
  73. <span slot="lastDate" slot-scope="text, record">
  74. <a-date-picker style="width: 100%" :format="BaseTool.Date.PICKER_NORM_DATE_PATTERN" v-model="record.lastDate" />
  75. </span>
  76. <span slot="nextDate" slot-scope="text, record">
  77. <a-date-picker style="width: 100%" :format="BaseTool.Date.PICKER_NORM_DATE_PATTERN" v-model="record.nextDate" />
  78. </span>
  79. <span slot="requirementList" slot-scope="text, record">
  80. <a-select style="width: 100%" mode="multiple" v-model="record.requirementList"
  81. placeholder="请先选择标准类型" :filterOption="filterOption" :disabled="!sbType">
  82. <a-select-option v-for="item in currentRequirementOptions" :key="item.id" :label="item.stdName" :value="item.id">{{ item.stdName }}
  83. </a-select-option>
  84. </a-select>
  85. </span>
  86. <span slot="remark" slot-scope="text, record">
  87. <a-textarea :rows="2" v-model="record.remark" />
  88. </span>
  89. <span slot="action" slot-scope="_,record,index">
  90. <a-space>
  91. <a @click="handleCopy(record)">复制</a>
  92. <a-popconfirm title="是否要删除该条数据?" @confirm="handleDelOne(record,index)">
  93. <a>删除</a>
  94. </a-popconfirm>
  95. </a-space>
  96. </span>
  97. </a-table>
  98. <sb-info-select-modal ref="sbInfoSelectModal" @selected="handleSbSelectd" />
  99. </a-card>
  100. </template>
  101. <script>
  102. import SbInfoSelectModal from '@/views/sb/info/modules/SbInfoSelectModal'
  103. import { batch } from '@/api/check/checkstandard'
  104. import { queryCheckDetail } from '@/api/check/check-detail'
  105. import { fetchSbTypeTree } from '@/api/sb/type'
  106. import { getDeptTree } from '@/api/upms/dept'
  107. export default {
  108. components: {
  109. SbInfoSelectModal,
  110. },
  111. data() {
  112. return {
  113. visible: false,
  114. confirmLoading: false,
  115. modalTitle: '批量新增',
  116. form: this.$form.createForm(this),
  117. data: [{}],
  118. // 保养项字典:Map<sbType(设备类型ID), List<CheckRequirementVO>>
  119. checkDetailList: {},
  120. // 标准类型树
  121. treeData: [],
  122. // 部门(班组)树
  123. deptTree: [],
  124. // 当前选中的标准类型(设备类型ID),作用于所有行
  125. sbType: null,
  126. tableKey: 0,
  127. columns: [
  128. {
  129. title: '序号',
  130. dataIndex: 'index',
  131. width: '70px',
  132. checked: true,
  133. customRender: (text, record, index) => {
  134. return index + 1
  135. },
  136. },
  137. {
  138. title: () => {
  139. return <span style="color:red">维护等级</span>
  140. },
  141. dataIndex: 'level',
  142. width: '120px',
  143. checked: true,
  144. scopedSlots: { customRender: 'level' },
  145. },
  146. {
  147. title: () => {
  148. return <span style="color:red">执行人方式</span>
  149. },
  150. dataIndex: 'checkUserType',
  151. checked: true,
  152. width: '130px',
  153. scopedSlots: { customRender: 'checkUserType' },
  154. },
  155. {
  156. title: '执行班组',
  157. dataIndex: 'checkDeptId',
  158. checked: true,
  159. width: '180px',
  160. scopedSlots: { customRender: 'checkDeptId' },
  161. },
  162. {
  163. title: () => {
  164. return <span style="color:red">计划周期</span>
  165. },
  166. dataIndex: 'period',
  167. width: '160px',
  168. checked: true,
  169. scopedSlots: { customRender: 'period' },
  170. },
  171. {
  172. title: '标准工时',
  173. dataIndex: 'standardHours',
  174. checked: true,
  175. width: '120px',
  176. scopedSlots: { customRender: 'standardHours' },
  177. },
  178. {
  179. title: () => {
  180. return <span style="color:red">排序</span>
  181. },
  182. dataIndex: 'sort',
  183. checked: true,
  184. width: '80px',
  185. scopedSlots: { customRender: 'sort' },
  186. },
  187. {
  188. title: '上次实际执行日期',
  189. dataIndex: 'lastDate',
  190. checked: true,
  191. scopedSlots: { customRender: 'lastDate' },
  192. width: '150px',
  193. },
  194. {
  195. title: '下次预计执行日期',
  196. dataIndex: 'nextDate',
  197. checked: true,
  198. scopedSlots: { customRender: 'nextDate' },
  199. width: '150px',
  200. },
  201. {
  202. title: () => {
  203. return <span style="color:red">检查项目</span>
  204. },
  205. dataIndex: 'requirementList',
  206. checked: true,
  207. scopedSlots: { customRender: 'requirementList' },
  208. width: '300px',
  209. },
  210. {
  211. title: '检查标准',
  212. dataIndex: 'remark',
  213. checked: true,
  214. scopedSlots: { customRender: 'remark' },
  215. width: '300px',
  216. },
  217. {
  218. title: '操作',
  219. key: 'action',
  220. width: '160px',
  221. checked: true,
  222. fixed: 'right',
  223. align: 'center',
  224. scopedSlots: { customRender: 'action' },
  225. },
  226. ],
  227. levelMap: {},
  228. checkUserTypeMap: {},
  229. periodTypeMap: {},
  230. }
  231. },
  232. computed: {
  233. // 当前标准类型下可选的保养项列表
  234. currentRequirementOptions() {
  235. if (!this.sbType) {
  236. return []
  237. }
  238. return this.checkDetailList[this.sbType] || []
  239. },
  240. },
  241. created() {
  242. this.paramTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.CHECK_STANDARD_PARAM_TYPE)
  243. this.typeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.CHECK_STANDARD_TYPE)
  244. this.periodTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.CHECK_PLAN_PERIOD_TYPE)
  245. this.actionTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.CHECK_PLAN_ACTION_TYPE)
  246. this.enableMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.YES_NO)
  247. this.checkUserTypeMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.CHECK_USER_TYPE)
  248. this.levelMap = this.DictCache.getLabelByValueMapByType(this.DictCache.TYPE.CHECK_PLAN_LEVEL)
  249. fetchSbTypeTree().then((res) => {
  250. this.treeData = res.data
  251. })
  252. getDeptTree({}).then((res) => {
  253. this.deptTree = res.data || []
  254. })
  255. },
  256. methods: {
  257. filterOption(input, option) {
  258. const text = option.componentOptions.children[0].text || ''
  259. return text.toLowerCase().indexOf(input.toLowerCase()) >= 0
  260. },
  261. base(record) {
  262. this.visible = true
  263. this.sbType = null
  264. this.data = [{}]
  265. this.tableKey = new Date().getTime()
  266. this.form.resetFields()
  267. // 加载保养项字典(按设备类型分组)
  268. queryCheckDetail().then((res) => {
  269. this.checkDetailList = res.data || {}
  270. })
  271. if (this.BaseTool.Object.isBlank(record)) {
  272. return
  273. }
  274. const {
  275. form: { setFieldsValue },
  276. } = this
  277. this.$nextTick(() => {
  278. setFieldsValue(
  279. Object.assign({
  280. sbId: record.id,
  281. sbNo: record.no,
  282. })
  283. )
  284. })
  285. },
  286. handleCancel() {
  287. this.data = [{}]
  288. this.visible = false
  289. this.confirmLoading = false
  290. this.sbType = null
  291. this.$emit('ok')
  292. this.form.resetFields()
  293. },
  294. handleAdd() {
  295. this.data.push({})
  296. },
  297. handleCopy(record) {
  298. const newVal = this.BaseTool.Object.copy(record)
  299. delete newVal.id
  300. this.data.push(newVal)
  301. },
  302. handleDelOne(record, i) {
  303. this.data.splice(i, 1)
  304. },
  305. // 切换标准类型:检查项目数据源随之变化,清空各行已选检查项目
  306. handleSbTypeChange(value) {
  307. this.sbType = value
  308. this.data.forEach((item) => {
  309. this.$set(item, 'requirementList', [])
  310. })
  311. this.tableKey = new Date().getTime()
  312. },
  313. // 切换执行人方式:非班组时清空执行班组
  314. handleCheckUserTypeChange(value, record) {
  315. if (value !== 4) {
  316. this.$set(record, 'checkDeptId', null)
  317. }
  318. this.tableKey = new Date().getTime()
  319. },
  320. handleSbSelectd(keys, rows) {
  321. const [key] = keys
  322. const [row] = rows
  323. const {
  324. form: { setFieldsValue },
  325. } = this
  326. this.sbId = key
  327. this.$nextTick(() => {
  328. setFieldsValue(
  329. Object.assign({
  330. sbId: key,
  331. sbNo: row.no,
  332. })
  333. )
  334. })
  335. // 批量新增为纯新增语义:切换设备后重置为一条空行,
  336. // 不加载设备已有标准,避免已有标准被当作新增行重复创建。
  337. this.data = [{}]
  338. this.tableKey = new Date().getTime()
  339. },
  340. handleSbSelect() {
  341. this.$refs.sbInfoSelectModal.base()
  342. },
  343. save() {
  344. const {
  345. form: { validateFieldsAndScroll },
  346. } = this
  347. this.confirmLoading = true
  348. validateFieldsAndScroll((errors, values) => {
  349. if (errors) {
  350. this.confirmLoading = false
  351. return
  352. }
  353. if (!this.sbType) {
  354. this.$message.error('请选择标准类型')
  355. this.confirmLoading = false
  356. return
  357. }
  358. if (!this.data || this.data.length === 0) {
  359. this.$message.error('请至少添加一条保养标准')
  360. this.confirmLoading = false
  361. return
  362. }
  363. let isReturn = false
  364. this.data.forEach((item, i) => {
  365. if (!item.level) {
  366. this.$message.error(`第${i + 1}行维护等级不能为空!`)
  367. isReturn = true
  368. }
  369. if (!item.checkUserType) {
  370. this.$message.error(`第${i + 1}行执行人方式不能为空!`)
  371. isReturn = true
  372. }
  373. if (item.checkUserType === 4 && !item.checkDeptId) {
  374. this.$message.error(`第${i + 1}行执行人方式为班组时,必须选择执行班组!`)
  375. isReturn = true
  376. }
  377. if (!item.period) {
  378. this.$message.error(`第${i + 1}行计划周期不能为空!`)
  379. isReturn = true
  380. }
  381. if (!item.periodType) {
  382. this.$message.error(`第${i + 1}行周期类型不能为空!`)
  383. isReturn = true
  384. }
  385. if (item.sort === null || item.sort === undefined) {
  386. this.$message.error(`第${i + 1}行排序不能为空!`)
  387. isReturn = true
  388. }
  389. if (!item.requirementList || item.requirementList.length === 0) {
  390. this.$message.error(`第${i + 1}行检查项目不能为空!`)
  391. isReturn = true
  392. }
  393. })
  394. if (isReturn) {
  395. this.confirmLoading = false
  396. return
  397. }
  398. const allRequirements = this.checkDetailList[this.sbType] || []
  399. // 构造提交数据:requirementList 由 ID 数组转为对象数组(含 id、sbType),
  400. // 与单条新增保持一致,后端据此关联保养项并生成首期任务。
  401. values.list = this.data.map((item) => {
  402. const selectedRequirements = allRequirements.filter((r) => item.requirementList.includes(r.id))
  403. return {
  404. ...item,
  405. sbType: this.sbType,
  406. requirementList: selectedRequirements,
  407. lastDate: item.lastDate
  408. ? this.BaseTool.Date.formatter(item.lastDate, this.BaseTool.Date.PICKER_NORM_DATE_PATTERN)
  409. : null,
  410. nextDate: item.nextDate
  411. ? this.BaseTool.Date.formatter(item.nextDate, this.BaseTool.Date.PICKER_NORM_DATE_PATTERN)
  412. : null,
  413. }
  414. })
  415. batch(values)
  416. .then(() => {
  417. this.$message.success('批量新增成功')
  418. this.handleCancel(values)
  419. })
  420. .catch(() => {
  421. this.confirmLoading = false
  422. })
  423. })
  424. },
  425. },
  426. }
  427. </script>
  428. <style>
  429. </style>