finalize.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. <template>
  2. <el-dialog title="结单" v-model="visible" width="800px" append-to-body @close="handleClose">
  3. <el-alert type="info" :closable="false" style="border-color: #14b8a6; background-color: #f0fdfa; color: #0d9488; height: 35px;">
  4. <template #default>
  5. <i class="fa fa-file-text-o mr-2" style="color: #0d9488;"> 请选择信息录入方式,并上传相关附件完成结单。</i>
  6. </template>
  7. </el-alert>
  8. <h4 class="text-sm font-medium text-gray-800 mb-3"></h4>
  9. <el-form ref="finalizeFormRef" :model="formData" :rules="finalizeRules" label-width="120px" label-position="top">
  10. <el-row>
  11. <el-col :span="12">
  12. <el-form-item label="工单编码"><el-input v-model="formData.workOrderProjectNo" disabled /></el-form-item>
  13. </el-col>
  14. <el-col :span="12">
  15. <el-form-item label="风机编号"><el-input v-model="formData.pcsDeviceName" disabled /></el-form-item>
  16. </el-col>
  17. <el-col :span="12">
  18. <el-form-item label="工单状态">
  19. <el-select v-model="formData.workOrderStatus" style="width: 100%" disabled>
  20. <el-option
  21. v-for="dict in workOrderStatusOptions"
  22. :key="dict.value"
  23. :label="dict.label"
  24. :value="dict.value"
  25. />
  26. </el-select>
  27. </el-form-item>
  28. </el-col>
  29. <el-col :span="12">
  30. <el-form-item label="维保中心"><el-input v-model="formData.gxtCenter" disabled /> </el-form-item>
  31. </el-col>
  32. <el-col :span="12">
  33. <el-form-item label="场站"><el-input v-model="formData.pcsStationName" disabled /> </el-form-item>
  34. </el-col>
  35. <el-col :span="12">
  36. <el-form-item label="品牌"><el-input v-model="formData.brand" disabled /> </el-form-item>
  37. </el-col>
  38. <el-col :span="12">
  39. <el-form-item label="故障代码"><el-input v-model="formData.faultCode" disabled /> </el-form-item>
  40. </el-col>
  41. <el-col :span="24">
  42. <el-form-item label="故障信息">
  43. <el-input
  44. v-model="formData.faultBarcode"
  45. type="textarea"
  46. placeholder="请输入故障信息"
  47. maxlength="100"
  48. show-word-limit
  49. :rows="3"
  50. disabled
  51. />
  52. </el-form-item>
  53. </el-col>
  54. <el-col :span="12">
  55. <el-form-item label="下发人"><el-input v-model="formData.assignUserName" disabled /> </el-form-item>
  56. </el-col>
  57. <el-col :span="12">
  58. <el-form-item label="下发时间"><el-input v-model="formData.assignTime" disabled /> </el-form-item>
  59. </el-col>
  60. <el-col :span="12">
  61. <el-form-item label="接单人"><el-input v-model="formData.acceptUserName" disabled /> </el-form-item>
  62. </el-col>
  63. <el-col :span="12">
  64. <el-form-item label="接单时间"><el-input v-model="formData.acceptTime" disabled /> </el-form-item>
  65. </el-col>
  66. <el-col :span="12">
  67. <el-form-item label="工作负责人"><el-input v-model="formData.teamLeaderName" disabled /> </el-form-item>
  68. </el-col>
  69. </el-row>
  70. <el-row>
  71. <el-col :span="12">
  72. <el-form-item label="信息录入" prop="infoEntry">
  73. <el-radio-group v-model="formData.infoEntry" @change="handleInfoEntryChange" :disabled="infoEntryDisabled">
  74. <el-radio
  75. v-for="dict in infoEntryOptions"
  76. :key="dict.value"
  77. :label="dict.value"
  78. >
  79. {{ dict.label }}
  80. </el-radio>
  81. </el-radio-group>
  82. </el-form-item>
  83. </el-col>
  84. <el-col :span="12" v-if="formData.infoEntry == 1">
  85. <el-form-item label="MIS工单编码" prop="misOrderNo" >
  86. <el-input
  87. v-model="formData.misOrderNo"
  88. placeholder="请输入MIS工单编码或点击搜索选择"
  89. clearable
  90. @focus="handleMisNoInputFocus"
  91. @blur="handleMisNoInputBlur"
  92. @input="handleMisNoInput"
  93. @clear="handleMisNoClear"
  94. >
  95. <template #append>
  96. <el-button @click="handleSelectMisInfo" icon="Search"></el-button>
  97. </template>
  98. </el-input>
  99. <!-- 快速检索下拉框 -->
  100. <div class="quick-select-dropdown" v-show="showMisNoQuickSelect && quickMisNoList.length > 0">
  101. <div
  102. v-for="item in quickMisNoList"
  103. :key="item.misNo"
  104. class="quick-select-item"
  105. @click="handleMisNoQuickSelect(item)">
  106. <span class="mis-no">{{ item.misNo }}</span>
  107. </div>
  108. </div>
  109. <div class="quick-select-dropdown no-data" v-show="showMisNoQuickSelect && quickMisNoList.length === 0 && formData.misOrderNo">
  110. <div>未找到匹配的MIS工单</div>
  111. </div>
  112. </el-form-item>
  113. </el-col>
  114. <!-- MIS选择组件 -->
  115. <slot name="mis-info-select"></slot>
  116. <el-col :span="12">
  117. <el-form-item label="工作票编号" prop="workPermitNum">
  118. <el-input v-model="workPermitNumProxy" maxlength="20" show-word-limit :readonly="formData.infoEntry == '1'" />
  119. </el-form-item>
  120. </el-col>
  121. <el-col :span="12">
  122. <el-form-item label="开始时间" prop="realStartTime">
  123. <el-date-picker
  124. v-model="formData.realStartTime"
  125. type="datetime"
  126. format="YYYY-MM-DD HH:mm"
  127. value-format="YYYY-MM-DD HH:mm"
  128. placeholder="请选择开始时间"
  129. style="width: 100%"
  130. :disabled-date="disabledStartDate"
  131. @change="handleStartTimeChange"
  132. :readonly="formData.infoEntry == '1'"
  133. />
  134. </el-form-item>
  135. </el-col>
  136. <el-col :span="12">
  137. <el-form-item label="工作部位" prop="workArea">
  138. <el-select
  139. v-model="formData.workArea"
  140. multiple
  141. placeholder="请选择工作部位"
  142. style="width: 100%"
  143. >
  144. <el-option
  145. v-for="dict in workAreaOptions"
  146. :key="dict.value"
  147. :label="dict.label"
  148. :value="dict.value"
  149. />
  150. </el-select>
  151. </el-form-item>
  152. </el-col>
  153. <el-col :span="12">
  154. <el-form-item label="结束时间" prop="realEndTime">
  155. <el-date-picker
  156. v-model="formData.realEndTime"
  157. type="datetime"
  158. format="YYYY-MM-DD HH:mm"
  159. value-format="YYYY-MM-DD HH:mm"
  160. placeholder="请选择结束时间"
  161. style="width: 100%"
  162. :disabled-date="disabledEndDate"
  163. @change="handleEndTimeChange"
  164. :readonly="formData.infoEntry == '1'"
  165. />
  166. </el-form-item>
  167. </el-col>
  168. <el-col :span="12" v-if="resumeInfo && resumeShow">
  169. <el-form-item label="挂起结束时间" prop="resumeTime">
  170. <el-date-picker
  171. v-model="formData.resumeTime"
  172. type="datetime"
  173. format="YYYY-MM-DD HH:mm"
  174. value-format="YYYY-MM-DD HH:mm"
  175. placeholder="请选择挂起结束时间"
  176. style="width: 100%"
  177. />
  178. </el-form-item>
  179. </el-col>
  180. <el-col :span="12">
  181. <el-form-item label="检修人员" prop="workGroupMemberName">
  182. <el-input
  183. v-model="formData.workGroupMemberName"
  184. placeholder="请选择检修人员"
  185. :readonly="formData.infoEntry == '1'"
  186. >
  187. <template #append v-if="formData.infoEntry == '2'">
  188. <el-button @click="userSelectVisible = true" icon="User"></el-button>
  189. </template>
  190. </el-input>
  191. </el-form-item>
  192. </el-col>
  193. <el-col :span="12">
  194. <el-form-item label="外委人员数(人)" prop="wwryNum">
  195. <el-input-number
  196. v-model="formData.wwryNum"
  197. placeholder="请输入外委人员数"
  198. controls-position="right"
  199. style="width: 100%"
  200. class="input-number-left"
  201. :min="0"
  202. :step="1"
  203. :precision="0"
  204. />
  205. </el-form-item>
  206. </el-col>
  207. <el-col :span="12">
  208. <el-form-item label="外来人员数(人)" prop="wlryNum">
  209. <el-input-number
  210. v-model="formData.wlryNum"
  211. placeholder="请输入外来人员数"
  212. controls-position="right"
  213. style="width: 100%"
  214. class="input-number-left"
  215. :min="0"
  216. :step="1"
  217. :precision="0"
  218. />
  219. </el-form-item>
  220. </el-col>
  221. <el-col :span="24">
  222. <el-form-item label="真实故障原因" prop="realContent">
  223. <el-input v-model="formData.realFailureReason"
  224. type="textarea"
  225. placeholder="请输入真实故障原因,最多500字"
  226. maxlength="500"
  227. :rows="5"
  228. show-word-limit />
  229. </el-form-item>
  230. </el-col>
  231. <el-col :span="24">
  232. <el-form-item label="附件(可选)">
  233. <preview :limit="8" v-model="formData.attachmentUrls" :filesize="5"></preview>
  234. </el-form-item>
  235. </el-col>
  236. </el-row>
  237. </el-form>
  238. <template #footer>
  239. <div class="dialog-footer">
  240. <el-button @click="handleCancel">取 消</el-button>
  241. <el-button type="primary" @click="handleSubmit">确认结单</el-button>
  242. </div>
  243. </template>
  244. </el-dialog>
  245. <!-- 人员选择组件 -->
  246. <UserSelectMulti
  247. v-model="userSelectVisible"
  248. :pre-selected-users="selectedUsers"
  249. :sort-dept-id="formData.pcsStationPid"
  250. @onSelected="onUserSelected"
  251. />
  252. </template>
  253. <script setup>
  254. import { ref, defineProps, defineEmits, getCurrentInstance, computed, watch } from 'vue'
  255. import preview from '@/components/FileUpload/preview.vue'
  256. import UserSelectMulti from '@/components/userSelect/multi.vue'
  257. import { ElMessageBox } from 'element-plus'
  258. // 获取当前实例
  259. const { proxy } = getCurrentInstance()
  260. // 定义属性
  261. const props = defineProps({
  262. modelValue: {
  263. type: Boolean,
  264. default: false
  265. },
  266. data: {
  267. type: Object,
  268. default: () => ({})
  269. },
  270. workOrderStatusOptions: {
  271. type: Array,
  272. default: () => ([])
  273. },
  274. infoEntryOptions: {
  275. type: Array,
  276. default: () => ([])
  277. },
  278. workAreaOptions: {
  279. type: Array,
  280. default: () => ([])
  281. },
  282. onSubmit: {
  283. type: Function,
  284. default: null
  285. },
  286. listRepairOrder: {
  287. type: Function,
  288. default: null
  289. },
  290. listWorkPerson: {
  291. type: Function,
  292. default: null
  293. },
  294. listUserData: {
  295. type: Function,
  296. default: null
  297. },
  298. listMisInfo: {
  299. type: Function,
  300. default: null
  301. },
  302. infoEntryDisabled: {
  303. type: Boolean,
  304. default: false
  305. }
  306. })
  307. // 定义事件
  308. const emit = defineEmits(['update:modelValue', 'success', 'select-mis-info'])
  309. // 响应式数据
  310. const visible = ref(false)
  311. const formData = ref({})
  312. const finalizeFormRef = ref()
  313. const showMisNoQuickSelect = ref(false)
  314. const quickMisNoList = ref([])
  315. const misNoSearchTimer = ref(null)
  316. const userSelectVisible = ref(false) // 添加人员选择组件可见性
  317. const selectedUsers = ref([]) // 存储选中的用户
  318. const inputUsers = ref([]) // 存储选中的用户
  319. const flowList = ref([])
  320. const suspendInfo = ref(null)
  321. const resumeInfo = ref(null)
  322. const resumeShow = ref(false)
  323. // 计算属性
  324. const workPermitNumProxy = computed({
  325. get() {
  326. return formData.value.workPermitNum
  327. },
  328. set(val) {
  329. formData.value.workPermitNum = val
  330. }
  331. })
  332. // 表单验证规则
  333. const finalizeRules = ref({
  334. // repairMethod: [
  335. // { required: true, message: "请选择维修方式", trigger: "change" }
  336. // ],
  337. misOrderNo: [
  338. { required: true, message: "MIS工单编码不能为空", trigger: "change" }
  339. ],
  340. realStartTime: [
  341. { required: true, message: "请选择开始时间", trigger: "change" },
  342. {
  343. validator: (rule, value, callback) => {
  344. if (value && new Date(value) > new Date()) {
  345. callback(new Error('开始时间不能大于当前时间'));
  346. // } else if(value && new Date(value) < new Date(formData.value.acceptTime)) {
  347. //
  348. // callback(new Error('开始时间不能小于接单时间'));
  349. } else {
  350. callback();
  351. }
  352. },
  353. trigger: 'change'
  354. }
  355. ],
  356. realEndTime: [
  357. { required: true, message: "请选择结束时间", trigger: "change" },
  358. {
  359. validator: (rule, value, callback) => {
  360. if (value && new Date(value) > new Date() && formData.value.infoEntry == '2') {
  361. callback(new Error('结束时间不能大于当前时间'));
  362. } else if(value && new Date(value) < new Date(formData.value.realStartTime) && formData.value.infoEntry == '2') {
  363. callback(new Error('结束时间不能小于开始时间'));
  364. } else {
  365. callback();
  366. }
  367. },
  368. trigger: 'change'
  369. }
  370. ],
  371. workGroupMemberName: [
  372. { required: false, message: "请输入检修人员", trigger: "change" },
  373. {
  374. validator: async (rule, value, callback) => {
  375. // 如果值为空、关联MIS,直接通过验证
  376. if (!value || formData.value.infoEntry == '1') {
  377. selectedUsers.value = [];
  378. return callback();
  379. }
  380. try {
  381. inputUsers.value = []
  382. // 将输入的检修人员姓名按逗号分割
  383. const names = value.split(',').map(name => name.trim());
  384. // 验证每个检修人员是否存在于组织架构中
  385. for (const name of names) {
  386. if (name.length > 0) {
  387. // 检查输入中重复
  388. if (inputUsers.value.some(u => u.nickName === name)) {
  389. return callback(new Error(`检修人员"${name}"重复,请重新输入`));
  390. }
  391. // 使用从属性传入的listUserData方法
  392. if (!props.listUserData) {
  393. return callback(new Error('系统配置错误:缺少用户数据查询方法'));
  394. }
  395. const response = await props.listUserData({nickName: name});
  396. if (!response.rows || response.rows.length === 0) {
  397. return callback(new Error(`检修人员"${name}"非系统内人员,请重新输入`));
  398. }else{
  399. inputUsers.value.push(response.rows[0]);
  400. }
  401. } else {
  402. return callback(new Error(`请正确输入检修人员名单`));
  403. }
  404. }
  405. selectedUsers.value = inputUsers.value;
  406. callback();
  407. } catch (error) {
  408. callback(new Error('验证检修人员时发生错误'));
  409. }
  410. },
  411. trigger: 'blur'
  412. }
  413. ],
  414. workArea: [
  415. { required: true, message: "请选择工作部位", trigger: "change" }
  416. ],
  417. workPermitNum: [
  418. { required: true, message: "工作票编号不能为空", trigger: "change" },
  419. {
  420. validator: (rule, value, callback) => {
  421. if (formData.value.infoEntry == '2') {
  422. if (!value) {
  423. callback(new Error('工作票编号不能为空'))
  424. } else if (!/^[a-zA-Z0-9_\-\.]*$/.test(value)) {
  425. callback(new Error('仅支持英文、数字、下划线'))
  426. } else if (value.length > 20) {
  427. callback(new Error('不能超过20个字符'))
  428. } else {
  429. // 验证唯一性
  430. if (value && props.listRepairOrder) {
  431. props.listRepairOrder({pageNum: 1, pageSize: 10, workPermitNum: value}).then(response => {
  432. const gxtOrders = response.rows
  433. if (gxtOrders.length > 0) {
  434. if (gxtOrders[0].id != formData.value.id) {
  435. callback(new Error('工作票编号已存在!'))
  436. } else {
  437. callback()
  438. }
  439. } else {
  440. callback()
  441. }
  442. }).catch(() => {
  443. callback()
  444. })
  445. } else {
  446. callback()
  447. }
  448. }
  449. } else {
  450. callback()
  451. }
  452. },
  453. trigger: 'change'
  454. }
  455. ],
  456. resumeTime: [
  457. { required: true, message: "挂起结束时间不能为空", trigger: "change" },
  458. {
  459. validator: (rule, value, callback) => {
  460. const realStartTime = formData.value.realStartTime
  461. const realEndTime = formData.value.realEndTime
  462. const suspendTime = suspendInfo.value.actionTime
  463. debugger
  464. if (suspendTime && realStartTime && new Date(suspendTime) < new Date(realStartTime)) { // 开工前挂起
  465. if (value && new Date(value) > new Date(realStartTime)) {
  466. callback(new Error('开工前挂起结束时间晚于实际开始时间,请调整'));
  467. } else if(realEndTime && value && new Date(value) > new Date(realEndTime)) {
  468. callback(new Error('开工前挂起结束时间晚于实际结束时间,请调整'));
  469. } else {
  470. callback();
  471. }
  472. } else if(suspendTime && realStartTime && new Date(suspendTime) >= new Date(realStartTime)) { // 作业中挂起
  473. if (value && new Date(value) < new Date(realStartTime)) {
  474. callback(new Error('作业中挂起结束时间早于实际开始时间,请调整'));
  475. } else if(realEndTime && value && new Date(value) > new Date(realEndTime)) {
  476. callback(new Error('作业中挂起结束时间晚于实际结束时间,请调整'));
  477. } else {
  478. callback();
  479. }
  480. } else {
  481. callback();
  482. }
  483. },
  484. trigger: 'change'
  485. }
  486. ],
  487. })
  488. // 监听modelValue变化
  489. watch(() => props.modelValue, (val) => {
  490. visible.value = val
  491. if (val) {
  492. // 初始化表单数据
  493. formData.value = { ...props.data, selectedMembers: [] }
  494. }
  495. })
  496. // 监听props.data变化
  497. watch(() => props.data, (newData) => {
  498. if (visible.value) {
  499. // 只有在对话框打开时才更新数据
  500. formData.value = { ...newData, selectedMembers: [] }
  501. flowList.value = formData.value.repairOrderFlowList || []
  502. debugger
  503. if (formData.value.suspendReason && flowList.value.length > 0) {
  504. // 获取最后一个 actionType 等于 'resume' 的项
  505. const lastResumeItem = flowList.value.findLast(item => item.actionType === 'resume')
  506. if (lastResumeItem) {
  507. // 做你想做的事,比如记录时间、设置状态等
  508. console.log('最后一个 resume 项:', lastResumeItem)
  509. resumeInfo.value = lastResumeItem
  510. formData.value.resumeTime = lastResumeItem.actionTime
  511. }
  512. const lastSuspendItem = flowList.value.findLast(item => item.actionType === 'to_approve')
  513. if (lastSuspendItem) {
  514. // 做你想做的事,比如记录时间、设置状态等
  515. console.log('最后一个 to_approve 项:', lastSuspendItem)
  516. suspendInfo.value = lastSuspendItem
  517. }
  518. }
  519. }
  520. }, { deep: true })
  521. // 监听visible变化
  522. watch(visible, (val) => {
  523. emit('update:modelValue', val)
  524. if (val) {
  525. // 打开对话框后重置表单验证错误
  526. proxy.$nextTick(() => {
  527. if (finalizeFormRef.value) {
  528. finalizeFormRef.value.clearValidate()
  529. }
  530. })
  531. }
  532. })
  533. watch(
  534. () => props.data?.realStartTime, // 只监听 realStartTime
  535. (newStartTime, oldStartTime) => {
  536. // 判断是否真的变化了(避免 undefined === undefined 误判)
  537. debugger
  538. if (resumeInfo.value && newStartTime && newStartTime !== oldStartTime) {
  539. // 注意:此时对话框可能未打开,根据你的需求决定是否加 visible 判断
  540. if (visible.value) {
  541. handleStartTimeChange(newStartTime);
  542. }
  543. }
  544. },
  545. { immediate: true } // 如果需要初始化时也触发一次,可加;否则去掉
  546. );
  547. watch(
  548. () => props.data?.realEndTime, // 只监听 realEndTime
  549. (newEndTime, oldEndTime) => {
  550. // 判断是否真的变化了(避免 undefined === undefined 误判)
  551. debugger
  552. if (resumeInfo.value && newEndTime && oldEndTime !== oldEndTime) {
  553. // 注意:此时对话框可能未打开,根据你的需求决定是否加 visible 判断
  554. if (visible.value) {
  555. handleEndTimeChange(newEndTime);
  556. }
  557. }
  558. },
  559. { immediate: true } // 如果需要初始化时也触发一次,可加;否则去掉
  560. );
  561. // 时间禁用函数
  562. const disabledStartDate = (time) => {
  563. const getYYYYMMDD = (date) => {
  564. const y = date.getFullYear();
  565. const m = String(date.getMonth() + 1).padStart(2, '0');
  566. const d = String(date.getDate()).padStart(2, '0');
  567. return `${y}-${m}-${d}`;
  568. };
  569. const today = getYYYYMMDD(new Date());
  570. let acceptDateStr = null;
  571. const acceptStr = formData.value.acceptTime;
  572. if (acceptStr) {
  573. acceptDateStr = acceptStr.split(' ')[0];
  574. }
  575. const selectedDateStr = getYYYYMMDD(time);
  576. // 如果没有 acceptTime,只禁用未来日期
  577. if (!acceptDateStr) {
  578. return selectedDateStr > today;
  579. }
  580. // 要用:早于 acceptDate 或 晚于今天
  581. // return selectedDateStr < acceptDateStr || selectedDateStr > today;
  582. return selectedDateStr > today;
  583. };
  584. const disabledEndDate = (time) => {
  585. const getYYYYMMDD = (date) => {
  586. const y = date.getFullYear();
  587. const m = String(date.getMonth() + 1).padStart(2, '0');
  588. const d = String(date.getDate()).padStart(2, '0');
  589. return `${y}-${m}-${d}`;
  590. };
  591. const today = getYYYYMMDD(new Date());
  592. let acceptDateStr = null;
  593. const acceptStr = formData.value.acceptTime;
  594. const startStr = formData.value.realStartTime;
  595. if (startStr) {
  596. acceptDateStr = startStr.split(' ')[0];
  597. } else if(acceptStr) {
  598. acceptDateStr = acceptStr.split(' ')[0];
  599. }
  600. const selectedDateStr = getYYYYMMDD(time);
  601. // 如果没有 acceptTime,只禁用未来日期
  602. if (!acceptDateStr) {
  603. return selectedDateStr > today;
  604. }
  605. // 要用:早于 acceptDate 或 晚于今天
  606. return selectedDateStr < acceptDateStr || selectedDateStr > today;
  607. };
  608. // 关闭对话框
  609. const handleClose = () => {
  610. visible.value = false
  611. selectedUsers.value = []
  612. resumeInfo.value = null
  613. suspendInfo.value = null
  614. resumeShow.value = false
  615. }
  616. // 取消操作
  617. const handleCancel = () => {
  618. visible.value = false
  619. selectedUsers.value = []
  620. resumeInfo.value = null
  621. suspendInfo.value = null
  622. resumeShow.value = false
  623. }
  624. // 信息录入方式变化处理
  625. const handleInfoEntryChange = (val) => {
  626. // 选中2工作票编号时修改其他值
  627. if (val === '2') {
  628. formData.value.misOrderNo = undefined;
  629. formData.value.realStartTime = undefined;
  630. formData.value.realEndTime = undefined;
  631. formData.value.workGroupMemberName = undefined;
  632. formData.value.repairOrderPersonList = [];
  633. selectedUsers.value = [];
  634. } else {
  635. formData.value.workPermitNum = undefined;
  636. }
  637. }
  638. // 开始时间变化处理
  639. const handleStartTimeChange = (value) => {
  640. debugger
  641. if (resumeInfo.value && value) {
  642. const realStartTime = formData.value.realStartTime
  643. const realEndTime = formData.value.realEndTime
  644. const suspendTime = suspendInfo.value.actionTime
  645. const resumeTime = formData.value.resumeTime
  646. if (suspendTime && value && new Date(suspendTime) < new Date(value)) { // 开工前挂起
  647. if (value && resumeTime > new Date(value)) {
  648. resumeShow.value = true
  649. } else if(realEndTime && resumeTime && new Date(resumeTime) > new Date(realEndTime)) {
  650. resumeShow.value = true
  651. } else {
  652. resumeShow.value = false
  653. }
  654. } else if(suspendTime && value && new Date(suspendTime) >= new Date(value)) { // 作业中挂起
  655. if (value && new Date(resumeTime) < new Date(value)) {
  656. resumeShow.value = true
  657. } else if(realEndTime && resumeTime && new Date(resumeTime) > new Date(realEndTime)) {
  658. resumeShow.value = true
  659. } else {
  660. resumeShow.value = false
  661. }
  662. } else {
  663. resumeShow.value = false
  664. }
  665. }
  666. }
  667. // 结束时间变化处理
  668. const handleEndTimeChange = (value) => {
  669. debugger
  670. if (resumeInfo.value && value) {
  671. const realStartTime = formData.value.realStartTime
  672. const realEndTime = formData.value.realEndTime
  673. const suspendTime = suspendInfo.value.actionTime
  674. const resumeTime = formData.value.resumeTime
  675. if (suspendTime && realStartTime && new Date(suspendTime) < new Date(realStartTime)) { // 开工前挂起
  676. if (value && resumeTime > new Date(realStartTime)) {
  677. resumeShow.value = true
  678. } else if(value && resumeTime && new Date(resumeTime) > new Date(value)) {
  679. resumeShow.value = true
  680. } else {
  681. resumeShow.value = false
  682. }
  683. } else if(suspendTime && realStartTime && new Date(suspendTime) >= new Date(realStartTime)) { // 作业中挂起
  684. if (realStartTime && new Date(resumeTime) < new Date(realStartTime)) {
  685. resumeShow.value = true
  686. } else if(value && resumeTime && new Date(resumeTime) > new Date(value)) {
  687. resumeShow.value = true
  688. } else {
  689. resumeShow.value = false
  690. }
  691. } else {
  692. resumeShow.value = false
  693. }
  694. }
  695. }
  696. // MIS工单相关处理函数
  697. const handleMisNoInputFocus = () => {
  698. // showMisNoQuickSelect.value = true
  699. showMisNoQuickSelect.value = true
  700. // 如果已有输入内容,立即搜索
  701. if (formData.value.misOrderNo && formData.value.misOrderNo.trim()) {
  702. handleMisNoInput(formData.value.misOrderNo)
  703. }
  704. }
  705. const handleMisNoInputBlur = () => {
  706. // 延迟隐藏下拉框,确保点击选项能触发
  707. setTimeout(() => {
  708. showMisNoQuickSelect.value = false
  709. }, 200)
  710. }
  711. const handleMisNoInput = (value) => {
  712. const searchText = value.trim()
  713. // 这里应该调用搜索函数
  714. if (!searchText) {
  715. quickMisNoList.value = []
  716. showMisNoQuickSelect.value = false
  717. return
  718. }
  719. showMisNoQuickSelect.value = true
  720. // 清除之前的定时器
  721. if (misNoSearchTimer.value) {
  722. clearTimeout(misNoSearchTimer.value)
  723. }
  724. // 设置新的定时器,防抖处理(500ms)
  725. misNoSearchTimer.value = setTimeout(() => {
  726. searchMisNoList(searchText)
  727. }, 500)
  728. }
  729. const handleMisNoClear = () => {
  730. formData.value.misOrderNo = undefined
  731. }
  732. const handleSelectMisInfo = () => {
  733. emit('select-mis-info')
  734. }
  735. const handleMisNoQuickSelect = (item) => {
  736. formData.value.misOrderNo = item.misNo
  737. showMisNoQuickSelect.value = false
  738. }
  739. /** 搜索MIS工单列表 */
  740. const searchMisNoList = async (keyword) => {
  741. if (!keyword) {
  742. quickMisNoList.value = []
  743. return
  744. }
  745. debugger
  746. // 检查是否提供了listMisInfo函数
  747. if (!props.listMisInfo || typeof props.listMisInfo !== 'function') {
  748. console.error('未提供listMisInfo函数')
  749. quickMisNoList.value = []
  750. return
  751. }
  752. try {
  753. const response = await props.listMisInfo({
  754. pageNum: 1,
  755. misNo: keyword
  756. })
  757. quickMisNoList.value = response.rows || []
  758. } catch (error) {
  759. console.error('搜索MIS工单失败:', error)
  760. proxy.$modal.msgError('搜索失败,请重试')
  761. quickMisNoList.value = []
  762. }
  763. }
  764. // 用户选择回调函数
  765. const onUserSelected = (users) => {
  766. if (users && users.length > 0) {
  767. // 合并用户,避免重复
  768. const existingNames = selectedUsers.value.map(u => u.nickName);
  769. const newUsers = users.filter(u => !existingNames.includes(u.nickName));
  770. selectedUsers.value = [...selectedUsers.value, ...newUsers];
  771. // 构建用户姓名列表
  772. const userNames = selectedUsers.value.map(user => user.nickName).join(',');
  773. formData.value.workGroupMemberName = userNames;
  774. } else {
  775. selectedUsers.value = [];
  776. formData.value.workGroupMemberName = '';
  777. }
  778. formData.value.repairOrderPersonList = selectedUsers.value.map(user => ({
  779. userId: user.userId,
  780. nickName: user.nickName,
  781. orderId: formData.value.id,
  782. orderCode: formData.value.workOrderProjectNo,
  783. status: 1
  784. }));
  785. };
  786. // 提交操作
  787. const handleSubmit = async () => {
  788. if (!finalizeFormRef.value) return
  789. await finalizeFormRef.value.validate(async (valid) => {
  790. if (valid) {
  791. const { realStartTime, acceptTime } = formData.value;
  792. debugger
  793. // if (realStartTime && acceptTime && (new Date(realStartTime) < new Date(acceptTime))) {
  794. // formData.value.orderEntryType = '2'
  795. // try {
  796. // debugger
  797. // await ElMessageBox.confirm(
  798. // '检测到开始时间早于接单时间,系统将按“补录工单”处理,准备工时为0。是否继续?',
  799. // '提示',
  800. // {
  801. // confirmButtonText: '是',
  802. // cancelButtonText: '否',
  803. // type: 'warning',
  804. // distinguishCancelAndClose: true
  805. // }
  806. // );
  807. // formData.value.orderEntryType = '2'
  808. // // 用户点击“是”,继续提交
  809. // } catch (error) {
  810. // // 用户点击“否”或关闭弹窗
  811. // finalizeFormRef.value?.validateField('realStartTime');
  812. // return;
  813. // }
  814. // }
  815. try {
  816. debugger
  817. // 根据维修方式清除不需要的字段
  818. if (formData.value.repairMethod === '1') {
  819. // 正常维修时清除复位方式
  820. formData.value.resetMethod = undefined;
  821. formData.value.workArea = Array.isArray(formData.value.workArea) ? formData.value.workArea.join(',')
  822. : formData.value.workArea || ''
  823. } else if (formData.value.repairMethod === '2') {
  824. // 复位启机时清除MIS相关字段
  825. formData.value.misOrderNo = undefined;
  826. formData.value.realStartTime = undefined;
  827. formData.value.realEndTime = undefined;
  828. formData.value.workGroupMemberName = undefined;
  829. formData.value.infoEntry = undefined;
  830. formData.value.wwryNum = undefined;
  831. formData.value.wlryNum = undefined;
  832. formData.value.workArea = undefined;
  833. formData.value.repairOrderPersonList = [];
  834. }
  835. // 调用父组件传入的提交函数
  836. if (props.onSubmit && typeof props.onSubmit === 'function') {
  837. flowList.value = []
  838. if (formData.value.resumeTime && formData.value.resumeTime != resumeInfo.value.actionTime) { //存入新的挂起结束时间
  839. resumeInfo.value.actionTime = formData.value.resumeTime
  840. flowList.value.push(resumeInfo.value)
  841. }
  842. formData.value.repairOrderFlowList = flowList.value
  843. formData.value.finalizeMethod = '2'
  844. await props.onSubmit(formData.value)
  845. } else {
  846. throw new Error("未提供提交方法")
  847. }
  848. proxy.$modal.msgSuccess("结单成功")
  849. visible.value = false
  850. emit('success')
  851. } catch (error) {
  852. proxy.$modal.msgError("操作失败: " + (error.message || "未知错误"))
  853. }
  854. }
  855. })
  856. }
  857. </script>
  858. <style scoped>
  859. /* 表单中的列间距调整 */
  860. :deep(.el-col) {
  861. padding-left: 5px;
  862. padding-right: 5px;
  863. }
  864. .quick-select-dropdown {
  865. position: absolute;
  866. top: 100%;
  867. left: 0;
  868. right: 0;
  869. z-index: 1000;
  870. max-height: 200px;
  871. overflow-y: auto;
  872. background: white;
  873. border: 1px solid #dcdfe6;
  874. border-top: none;
  875. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  876. }
  877. .quick-select-item {
  878. padding: 8px 12px;
  879. cursor: pointer;
  880. border-bottom: 1px solid #f0f0f0;
  881. }
  882. .quick-select-item:hover {
  883. background-color: #f5f7fa;
  884. }
  885. .no-data {
  886. text-align: center;
  887. padding: 10px;
  888. color: #909399;
  889. }
  890. /* 穿透修改组件内部输入框的对齐方式(关键) */
  891. :deep(.input-number-left .el-input__inner) {
  892. text-align: left !important; /* !important 覆盖组件默认的居中 */
  893. }
  894. </style>