backfillAddEdit.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. <template>
  2. <!-- 下发对话框 -->
  3. <el-dialog title="下发" v-model="visible" width="800px" append-to-body @close="cancel">
  4. <div style="max-height: 500px; overflow-y: auto; padding-right: 10px;">
  5. <el-form ref="orderRef" :model="form" :rules="rules" label-width="120px" label-position="top">
  6. <el-row :gutter="20">
  7. <el-col :span="12">
  8. <el-form-item label="工单编码" prop="workOrderProjectNo">
  9. <el-input v-model="form.workOrderProjectNo" placeholder="自动生成" maxlength="50" show-word-limit v-chinese-limit readonly/>
  10. </el-form-item>
  11. </el-col>
  12. <el-col :span="12">
  13. <el-form-item label="风机编号" prop="pcsDeviceName">
  14. <el-input v-model="form.pcsDeviceName" readonly>
  15. <template #append>
  16. <el-button @click="handleSelectEquipment" icon="Search"></el-button>
  17. </template>
  18. </el-input>
  19. </el-form-item>
  20. </el-col>
  21. </el-row>
  22. <el-row :gutter="20">
  23. <el-col :span="12">
  24. <el-form-item label="维保中心" prop="gxtCenter">
  25. <el-input v-model="form.gxtCenter" readonly />
  26. </el-form-item>
  27. </el-col>
  28. <el-col :span="12">
  29. <el-form-item label="场站" prop="pcsStationName">
  30. <el-input v-model="form.pcsStationName" readonly />
  31. </el-form-item>
  32. </el-col>
  33. </el-row>
  34. <el-row :gutter="20">
  35. <el-col :span="12">
  36. <el-form-item label="品牌" prop="brand">
  37. <el-input v-model="form.brand" readonly />
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="12">
  41. <el-form-item label="机型" prop="model">
  42. <el-input v-model="form.model" readonly />
  43. </el-form-item>
  44. </el-col>
  45. </el-row>
  46. <el-row :gutter="20">
  47. <el-col :span="12">
  48. <el-form-item label="信息录入" prop="infoEntry">
  49. <el-radio-group v-model="form.infoEntry" @change="handleInfoEntryChange">
  50. <el-radio
  51. v-for="dict in infoEntryOptions"
  52. :key="dict.value"
  53. :label="dict.value"
  54. >
  55. {{ dict.label }}
  56. </el-radio>
  57. </el-radio-group>
  58. </el-form-item>
  59. </el-col>
  60. <el-col :span="12" v-if="form.infoEntry == '1'">
  61. <el-form-item label="MIS工单编码" prop="misNo">
  62. <el-input v-model="form.misNo"
  63. placeholder="请输入MIS工单编码或点击搜索选择"
  64. clearable
  65. @focus="handleMisNoInputFocus"
  66. @blur="handleMisNoInputBlur"
  67. @input="handleMisNoInput"
  68. @clear="handleMisNoClear"
  69. >
  70. <template #append>
  71. <el-button @click="handleSelectMisInfo" icon="Search"></el-button>
  72. </template>
  73. </el-input>
  74. <!-- 快速检索下拉框 -->
  75. <div class="quick-select-dropdown" v-show="showMisNoQuickSelect && quickMisNoList.length > 0">
  76. <div
  77. v-for="item in quickMisNoList"
  78. :key="item.misNo"
  79. class="quick-select-item"
  80. @click="handleMisNoQuickSelect(item)">
  81. <span class="mis-no">{{ item.misNo }}</span>
  82. <!-- <span class="mis-info">-->
  83. <!-- <span class="device">{{ item.pcsDeviceName }}</span>-->
  84. <!-- <span class="station">{{ item.pcsStationName }}</span>-->
  85. <!-- </span>-->
  86. </div>
  87. </div>
  88. <div class="quick-select-dropdown no-data" v-show="showMisNoQuickSelect && quickMisNoList.length === 0 && form.misNo">
  89. <div>未找到匹配的MIS工单</div>
  90. </div>
  91. </el-form-item>
  92. </el-col>
  93. <el-col :span="12">
  94. <el-form-item label="工作票编号" prop="workPermitNum">
  95. <el-input v-model="workPermitNumProxy" maxlength="20" show-word-limit />
  96. </el-form-item>
  97. </el-col>
  98. </el-row>
  99. <el-row :gutter="20">
  100. <el-col :span="24">
  101. <el-form-item label="维保内容" prop="content" :required="form.infoEntry == '2'">
  102. <el-input v-model="form.content" type="textarea" :rows="3" :readonly="form.infoEntry == '1'" maxlength="500" show-word-limit />
  103. </el-form-item>
  104. </el-col>
  105. </el-row>
  106. </el-form>
  107. </div>
  108. <template #footer>
  109. <div class="dialog-footer">
  110. <el-button @click="cancel">取 消</el-button>
  111. <el-button type="primary" @click="submitForm('assigned')">确认下发</el-button>
  112. </div>
  113. </template>
  114. </el-dialog>
  115. <!-- MIS选择组件 -->
  116. <MisInfoSelectSingle :key="commonKey" v-model="misInfoSelectVisible" @onSelected="onMisInfoSelected" :pcsStationName="form.pcsStationName" :pcsDeviceName="form.pcsDeviceName"></MisInfoSelectSingle>
  117. <!-- 设备选择对话框 -->
  118. <equipment-select
  119. v-model="equipmentSelectVisible"
  120. @onSelected="onEquipmentSelected"
  121. />
  122. </template>
  123. <script setup>
  124. import {ref, reactive, nextTick, watch, getCurrentInstance, defineProps} from 'vue';
  125. import EquipmentSelect from '@/components/equipmentSelect/single.vue'; // 根据实际情况调整路径
  126. import {getOrderList, listGxtOrder, updateGxtOrder} from '@/api/gxt/gxtOrder';
  127. import {listMisInfo} from "@/api/gxt/misInfo.js";
  128. import MisInfoSelectSingle from "@/components/misInfoSelect/single.vue";
  129. // 获取当前实例
  130. const { proxy } = getCurrentInstance()
  131. // 定义组件属性
  132. const props = defineProps({
  133. modelValue: {
  134. type: Boolean,
  135. default: false
  136. },
  137. data: {
  138. type: Object,
  139. default: () => ({})
  140. },
  141. // 信息录入方式
  142. infoEntryOptions: {
  143. type: Array,
  144. default: () => ([])
  145. },
  146. // commonKey: {
  147. // type: Number,
  148. // default: 0
  149. // }
  150. });
  151. // 定义事件
  152. const emit = defineEmits(['update:modelValue', 'success']);
  153. // 响应式数据
  154. const visible = ref(false);
  155. const orderRef = ref()
  156. const form = ref({});
  157. const equipmentSelectVisible = ref(false);
  158. const misInfoSelectVisible = ref(false)
  159. let commonKey = 0
  160. // 表单验证规则
  161. const rules = ref({
  162. misNo: [{ required: true, message: "MIS编码不能为空", trigger: "change" }],
  163. pcsDeviceName: [
  164. { required: true, message: "风机编号不能为空", trigger: "change" }
  165. ],
  166. inspectionType: [
  167. { required: true, message: "请选择维保类型", trigger: "change" }
  168. ],
  169. content: [
  170. { required: true, message: "维保内容不能为空", trigger: "blur" },
  171. { max: 500, message: "维保内容长度不能超过500个字符", trigger: "blur" }
  172. ],
  173. workPermitNum: [
  174. { required: true, message: "工作票编号不能为空", trigger: "change" },
  175. {
  176. validator: (rule, value, callback) => {
  177. if (form.value.infoEntry == '2') {
  178. if (!value) {
  179. callback(new Error('工作票编号不能为空'))
  180. } else if (!/^[a-zA-Z0-9_\-\.]*$/.test(value)) {
  181. callback(new Error('仅支持英文、数字、下划线'))
  182. } else if (value.length > 20) {
  183. callback(new Error('不能超过20个字符'))
  184. } else {
  185. // 验证唯一性
  186. if (value) {
  187. listGxtOrder({pageNum: 1, pageSize: 10, workPermitNum: value}).then(response => {
  188. const gxtOrders = response.rows
  189. if (gxtOrders.length > 0) {
  190. if (form.value.id == null) {
  191. callback(new Error('工作票编号已存在!'))
  192. } else {
  193. if (gxtOrders[0].id != form.value.id) {
  194. callback(new Error('工作票编号已存在!'))
  195. } else {
  196. callback()
  197. }
  198. }
  199. } else {
  200. callback()
  201. }
  202. })
  203. } else {
  204. callback()
  205. }
  206. }
  207. } else {
  208. callback()
  209. }
  210. },
  211. trigger: 'blur'
  212. }
  213. ],
  214. });
  215. // 监听modelValue变化
  216. watch(() => props.modelValue, (val) => {
  217. visible.value = val
  218. if (val) {
  219. // 初始化表单数据
  220. form.value = { ...props.data, selectedMembers: [] }
  221. commonKey++
  222. }
  223. })
  224. // 监听visible变化
  225. watch(visible, (val) => {
  226. emit('update:modelValue', val)
  227. if (val) {
  228. // 打开对话框后重置表单验证错误
  229. proxy.$nextTick(() => {
  230. if (orderRef.value) {
  231. orderRef.value.clearValidate()
  232. }
  233. })
  234. }
  235. })
  236. // 重置表单
  237. function reset() {
  238. form.value = {};
  239. // 重置验证
  240. nextTick(() => {
  241. const orderRef = getCurrentInstance()?.refs?.orderRef;
  242. if (orderRef && orderRef.resetFields) {
  243. orderRef.resetFields();
  244. }
  245. });
  246. }
  247. // 取消操作
  248. function cancel() {
  249. visible.value = false;
  250. }
  251. // 提交表单
  252. async function submitForm(status) {
  253. if (!orderRef.value) return
  254. await orderRef.value.validate((valid) => {
  255. if (valid) {
  256. form.value.workOrderStatus = status;
  257. form.value.inspectionType = Array.isArray(form.value.inspectionType) ? form.value.inspectionType.join(',')
  258. : form.value.inspectionType || ''
  259. updateGxtOrder(form.value).then(response => {
  260. proxy.$modal.msgSuccess("下发成功")
  261. visible.value = false;
  262. emit('success');
  263. }).catch(error => {
  264. });
  265. }
  266. });
  267. }
  268. // 设备选择相关方法
  269. function handleSelectEquipment() {
  270. equipmentSelectVisible.value = true;
  271. }
  272. function onEquipmentSelected(row) {
  273. if (row) {
  274. // 检查维保中心ID和场站ID是否存在
  275. if (!row.maintenanceCenterId) {
  276. proxy.$modal.msgError("该设备的维保中心没有对应的部门,请完善部门信息后再更新设备数据!");
  277. return;
  278. }
  279. if (!row.stationId) {
  280. proxy.$modal.msgError("该设备的场站没有对应的部门,请完善部门信息后再更新设备数据!");
  281. return;
  282. }
  283. // 更新表单中的设备信息
  284. form.value.pcsDeviceId = row.equipmentId
  285. form.value.pcsDeviceName = row.equipmentCode
  286. form.value.gxtCenterId = row.maintenanceCenterId
  287. form.value.gxtCenter = row.maintenanceCenter
  288. form.value.pcsStationId = row.stationId
  289. form.value.pcsStationName = row.station
  290. form.value.brand = row.brand
  291. form.value.model = row.model
  292. }
  293. equipmentSelectVisible.value = false;
  294. commonKey++
  295. }
  296. function handleSelectMisInfo() {
  297. misInfoSelectVisible.value = true
  298. }
  299. // MIS工单快速检索相关响应式数据
  300. const showMisNoQuickSelect = ref(false)
  301. const quickMisNoList = ref([])
  302. const misNoLoading = ref(false)
  303. const misNoSearchTimer = ref(null)
  304. // 快速检索方法
  305. /** MIS工单编码输入框获取焦点 */
  306. const handleMisNoInputFocus = () => {
  307. showMisNoQuickSelect.value = true
  308. // 如果已有输入内容,立即搜索
  309. if (form.misNo && form.misNo.trim()) {
  310. handleMisNoInput(form.misNo)
  311. }
  312. }
  313. /** MIS工单编码输入框失去焦点 */
  314. const handleMisNoInputBlur = () => {
  315. // 延迟隐藏下拉框,确保点击选项能触发
  316. setTimeout(() => {
  317. showMisNoQuickSelect.value = false
  318. }, 200)
  319. }
  320. /** MIS工单编码输入事件 - 实时搜索 */
  321. const handleMisNoInput = (value) => {
  322. const searchText = value.trim()
  323. if (!searchText) {
  324. quickMisNoList.value = []
  325. showMisNoQuickSelect.value = false
  326. return
  327. }
  328. showMisNoQuickSelect.value = true
  329. // 清除之前的定时器
  330. if (misNoSearchTimer.value) {
  331. clearTimeout(misNoSearchTimer.value)
  332. }
  333. // 设置新的定时器,防抖处理(500ms)
  334. misNoSearchTimer.value = setTimeout(() => {
  335. searchMisNoList(searchText)
  336. }, 500)
  337. }
  338. /** 搜索MIS工单列表 */
  339. const searchMisNoList = async (keyword) => {
  340. if (!keyword) {
  341. quickMisNoList.value = []
  342. return
  343. }
  344. misNoLoading.value = true
  345. try {
  346. const response = await listMisInfo({
  347. pageNum: 1,
  348. // pageSize: 10, // 只显示前10条结果
  349. misNo: keyword,
  350. pcsDeviceName: form.value.pcsDeviceName,
  351. // pcsStationName: keyword
  352. // 注意:这里假设后端API支持这些字段的模糊查询
  353. // 如果后端不支持多字段同时查询,可能需要调整
  354. })
  355. quickMisNoList.value = response.rows || []
  356. // 如果没有找到结果,可以尝试更宽松的搜索
  357. if (quickMisNoList.value.length === 0 && keyword.length >= 2) {
  358. // 可以尝试只按MIS工单编码搜索
  359. const retryResponse = await listMisInfo({
  360. pageNum: 1,
  361. // pageSize: 10,
  362. misNo: keyword
  363. })
  364. quickMisNoList.value = retryResponse.rows || []
  365. }
  366. } catch (error) {
  367. console.error('搜索MIS工单失败:', error)
  368. proxy.$modal.msgError('搜索失败,请重试')
  369. quickMisNoList.value = []
  370. } finally {
  371. misNoLoading.value = false
  372. }
  373. }
  374. /** 快速选择MIS工单 */
  375. const handleMisNoQuickSelect = (item) => {
  376. onMisInfoSelected(item)
  377. showMisNoQuickSelect.value = false
  378. }
  379. /** 清空MIS工单编码 */
  380. const handleMisNoClear = () => {
  381. // 清空与MIS工单相关的字段
  382. form.value.misNo = null
  383. form.value.content = null
  384. form.value.workPermitNum = null
  385. // form.value.pcsDeviceName = null
  386. // form.value.pcsStationName = null
  387. // form.value.pcsDeviceId = null
  388. // form.value.gxtCenter = null
  389. // form.value.brand = null
  390. // form.value.model = null
  391. quickMisNoList.value = []
  392. showMisNoQuickSelect.value = false
  393. }
  394. const handleInfoEntryChange = (val) => {
  395. form.value.content = undefined;
  396. // 选中2工作票编号时修改其他值
  397. if (val === '2') {
  398. form.value.misOrderNo = undefined;
  399. form.value.realStartTime = undefined;
  400. form.value.realEndTime = undefined;
  401. } else {
  402. form.value.workPermitNum = undefined;
  403. }
  404. };
  405. // 计算属性:双向绑定代理
  406. const workPermitNumProxy = computed({
  407. get() {
  408. return form.value.workPermitNum
  409. },
  410. set(val) {
  411. // 1. 去掉首尾空格
  412. let trimmed = val.trim()
  413. // 2. 只保留允许的字符:中文、英文字母、数字、下划线
  414. // 如果你希望更宽松(比如允许横杠 -),可调整正则
  415. trimmed = trimmed.replace(/[^a-zA-Z0-9_\-\.]/g, '')
  416. // 3. 截断到 20 个字符(按字符数,不是字节)
  417. if (trimmed.length > 20) {
  418. trimmed = trimmed.substring(0, 20)
  419. }
  420. // 4. 写回表单
  421. form.value.workPermitNum = trimmed
  422. }
  423. })
  424. /** 设备MIS信息回调 */
  425. function onMisInfoSelected(row) {
  426. if (row) {
  427. // listGxtOrder({pageNum: 1, pageSize: 10, misNo: row.misNo }).then(response => {
  428. getOrderList(row.misNo).then(response => {
  429. const gxtOrders= response.rows
  430. debugger
  431. if (gxtOrders.length > 0) {
  432. if (form.value.id == null) {
  433. proxy.$modal.msgWarning('选择工单已存在!请重新选择!')
  434. return
  435. } else {
  436. if (gxtOrders[0].id != form.value.id) {
  437. proxy.$modal.msgWarning('选择工单已存在!请重新选择!')
  438. return
  439. }
  440. }
  441. }
  442. form.value.misNo = row.misNo
  443. form.value.content = row.content
  444. form.value.realStartTime = row.realStartTime
  445. form.value.realEndTime = row.realEndTime
  446. form.value.workPermitNum = row.workPermitNum
  447. // form.value.planStartTime = row.planStartTime
  448. // form.value.planEndTime = row.planEndTime
  449. if (!form.value.pcsDeviceId) {
  450. form.value.pcsDeviceName = row.pcsDeviceName
  451. form.value.pcsStationName = row.pcsStationName
  452. listEquipment({station: row.pcsStationName, equipmentCode: row.pcsDeviceName}).then(response => {
  453. const equipments = response.rows
  454. if (equipments) {
  455. form.value.pcsDeviceId = equipments[0].equipmentId
  456. form.value.gxtCenter = equipments[0].maintenanceCenter
  457. form.value.brand = equipments[0].brand
  458. form.value.model = equipments[0].model
  459. }
  460. });
  461. }
  462. misInfoSelectVisible.value = false
  463. })
  464. }
  465. }
  466. defineExpose({
  467. visible,
  468. form,
  469. reset,
  470. submitForm
  471. });
  472. </script>
  473. <style scoped>
  474. .quick-select-dropdown {
  475. position: absolute;
  476. top: calc(100% + 2px);
  477. left: 0;
  478. right: 0;
  479. background: #fff;
  480. border: 1px solid #dcdfe6;
  481. border-radius: 4px;
  482. max-height: 200px;
  483. overflow-y: auto;
  484. z-index: 1000;
  485. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  486. }
  487. .quick-select-item {
  488. padding: 8px 12px;
  489. cursor: pointer;
  490. transition: background-color 0.2s;
  491. }
  492. .quick-select-item:hover {
  493. background-color: #f5f7fa;
  494. }
  495. .quick-select-item .mis-no {
  496. display: inline-block;
  497. margin-right: 10px;
  498. font-weight: bold;
  499. color: #409eff;
  500. }
  501. .quick-select-item .mis-info {
  502. display: flex;
  503. flex-direction: column;
  504. align-items: flex-end;
  505. flex: 1;
  506. margin-left: 10px;
  507. }
  508. .quick-select-item .mis-info .device {
  509. color: #606266;
  510. font-size: 12px;
  511. margin-bottom: 2px;
  512. }
  513. .quick-select-item .mis-info .station {
  514. color: #909399;
  515. font-size: 11px;
  516. }
  517. .no-data {
  518. text-align: center;
  519. color: #909399;
  520. padding: 10px;
  521. }
  522. /* 输入框样式调整 */
  523. :deep(.el-input-group__append) {
  524. background-color: #409eff;
  525. border-color: #409eff;
  526. color: white;
  527. }
  528. </style>