wbFinalize.uvue 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016
  1. <template>
  2. <view class="detail-page">
  3. <scroll-view class="detail-content" :scroll-y="true">
  4. <!-- 工单信息 -->
  5. <view class="info-section">
  6. <view class="section-title">
  7. <text class="section-title-text">工单信息</text>
  8. </view>
  9. <view class="info-card">
  10. <view class="info-item">
  11. <text class="info-label">工单编码</text>
  12. <text class="info-value">{{ workOrderProjectNo ?? '' }}</text>
  13. </view>
  14. <view class="info-item">
  15. <text class="info-label">工单类型</text>
  16. <text class="info-value">{{ orderType == '1' ? '维修工单' : '维保工单' }}</text>
  17. </view>
  18. <view class="info-item">
  19. <text class="info-label">风机编号</text>
  20. <text class="info-value">{{ pcsDeviceName ?? '' }}</text>
  21. </view>
  22. <view class="info-item">
  23. <text class="info-label">维保中心</text>
  24. <text class="info-value">{{ gxtCenter ?? '' }}</text>
  25. </view>
  26. <view class="info-item">
  27. <text class="info-label">场站</text>
  28. <text class="info-value">{{ pcsStationName ?? '' }}</text>
  29. </view>
  30. <!-- <view class="info-item">
  31. <text class="info-label">品牌</text>
  32. <text class="info-value">{{ detailData.model ?? '' }}</text>
  33. </view> -->
  34. <view class="info-item">
  35. <text class="info-label">机型</text>
  36. <text class="info-value">{{ brand ?? '' }} {{ model ?? '' }}</text>
  37. </view>
  38. <view class="info-item">
  39. <text class="info-label">接单时间</text>
  40. <text class="info-value">{{ acceptTime ?? '' }}</text>
  41. </view>
  42. <view class="info-item" v-if="pauseTime != ''">
  43. <text class="info-label">停机时间</text>
  44. <text class="info-value">{{ pauseTime ?? '' }}</text>
  45. </view>
  46. <view class="info-item" v-if="restartTime != ''">
  47. <text class="info-label">复运时间</text>
  48. <text class="info-value">{{ restartTime ?? '' }}</text>
  49. </view>
  50. </view>
  51. </view>
  52. <!-- 结单表单 -->
  53. <view class="info-section">
  54. <view class="section-title">
  55. <text class="section-title-text">结单信息</text>
  56. </view>
  57. <view class="info-card">
  58. <!-- 信息录入 -->
  59. <view class="info-item">
  60. <view class="info-label">
  61. <text class="form-label required">信息录入<text style="color: red;">*</text></text>
  62. </view>
  63. <view class="info-value">
  64. <radio-group @change="handleInfoEntryChange" :disabled="infoEntryDisabled" class="uni-flex uni-row radio-group">
  65. <view v-for="(option, index) in infoEntryOptions" :key="index" class="radio-label">
  66. <radio :value="option.dictValue" :checked="infoEntry == option.dictValue">{{ option.dictLabel }}</radio>
  67. <!-- <text></text> -->
  68. </view>
  69. </radio-group>
  70. </view>
  71. </view>
  72. <!-- MIS工单编码选择(当信息录入为1时显示) -->
  73. <view class="info-item" v-if="infoEntry == '1'">
  74. <view class="info-label">
  75. <text class="form-label required">MIS工单编码<text style="color: red;">*</text></text>
  76. </view>
  77. <view class="info-value">
  78. <view class="input-with-select">
  79. <input
  80. class="input-field"
  81. placeholder="请输入或选择MIS工单编码"
  82. v-model="misNo"
  83. @focus="handleMisNoInputFocus"
  84. @blur="handleMisNoInputBlur"
  85. @input="handleMisNoInput"
  86. :readonly="infoEntry == '1'"
  87. :style="{ paddingRight: '120rpx' }" />
  88. <text class="select-mis-btn" @click="openMisListModal">选择</text>
  89. </view>
  90. </view>
  91. </view>
  92. <!-- 工作票编号(当信息录入为2时可编辑) -->
  93. <view class="info-item">
  94. <view class="info-label">
  95. <text class="form-label required">工作票编号<text style="color: red;">*</text></text>
  96. </view>
  97. <view class="info-value">
  98. <input
  99. class="input-field"
  100. placeholder="请输入工作票编号"
  101. v-model="workPermitNum"
  102. maxlength="20"
  103. :disabled="infoEntry == '1'"
  104. @change="handleWorkPermitNumChange"
  105. />
  106. </view>
  107. </view>
  108. <!-- 开始时间 -->
  109. <view class="info-item">
  110. <view class="info-label">
  111. <text class="form-label required">开始时间<text style="color: red;">*</text></text>
  112. </view>
  113. <view class="info-value">
  114. <view class="form-picker" @click="infoEntry == '1' ? showStartTimePicker = false : showStartTimePicker = true">
  115. <input
  116. class="input-field"
  117. placeholder="请选择开始时间"
  118. v-model="realStartTime"
  119. type="none"
  120. style="pointer-events: none;"
  121. />
  122. </view>
  123. </view>
  124. </view>
  125. <!-- 结束时间 -->
  126. <view class="info-item">
  127. <view class="info-label">
  128. <text class="form-label required">结束时间<text style="color: red;">*</text></text>
  129. </view>
  130. <view class="info-value">
  131. <view class="form-picker" @click="infoEntry == '1' ? showEndTimePicker = false : showEndTimePicker = true">
  132. <input
  133. class="input-field"
  134. placeholder="请选择结束时间"
  135. v-model="realEndTime"
  136. type="none"
  137. style="pointer-events: none;"
  138. />
  139. </view>
  140. </view>
  141. </view>
  142. <!-- 挂起结束时间 -->
  143. <view class="info-item" v-if="resumeInfo != null && resumeShow">
  144. <view class="info-label">
  145. <text class="form-label required">挂起结束时间<text style="color: red;">*</text></text>
  146. </view>
  147. <view class="info-value">
  148. <view class="form-picker" @click="showResumeTimePicker = true">
  149. <input
  150. class="input-field"
  151. placeholder="请选择挂起结束时间"
  152. v-model="resumeTime"
  153. type="none"
  154. />
  155. </view>
  156. </view>
  157. </view>
  158. <!-- 外委人员数 -->
  159. <view class="info-item">
  160. <view class="info-label">
  161. <text class="form-label required">外委人员数(人)</text>
  162. </view>
  163. <view class="info-value">
  164. <input
  165. type="number"
  166. class="input-field"
  167. placeholder="请输入外委人员数"
  168. v-model="wwryNum"
  169. @input="onWwryNumInput"
  170. />
  171. </view>
  172. </view>
  173. <!-- 外来人员数 -->
  174. <view class="info-item">
  175. <view class="info-label">
  176. <text class="form-label required">外来人员数(人)</text>
  177. </view>
  178. <view class="info-value">
  179. <input
  180. type="number"
  181. class="input-field"
  182. placeholder="请输入外来人员数"
  183. v-model="wlryNum"
  184. @input="onWlryNumInput"
  185. />
  186. </view>
  187. </view>
  188. <!-- 工作负责人 -->
  189. <view class="info-item">
  190. <view class="info-label">
  191. <text class="form-label required">工作负责人<text style="color: red;">*</text></text>
  192. </view>
  193. <view class="info-value">
  194. <view class="input-with-clear">
  195. <input
  196. class="input-field"
  197. placeholder="请选择工作负责人"
  198. v-model="teamLeaderName"
  199. @click="showLeaderPicker = true"
  200. :disabled="infoEntry == '1'"
  201. />
  202. </view>
  203. </view>
  204. </view>
  205. <!-- 工作班成员选择(当信息录入为2时可编辑) -->
  206. <view class="info-item">
  207. <view class="info-label">
  208. <text class="form-label required">工作班成员<text style="color: red;">*</text></text>
  209. </view>
  210. <view class="info-value">
  211. <view class="input-with-clear">
  212. <input
  213. class="input-field"
  214. placeholder="请选择工作班成员"
  215. v-model="workGroupMemberName"
  216. @click="showUserSelect = true"
  217. :disabled="infoEntry == '1'"
  218. />
  219. <!-- <text class="select-users-count" v-if="selectedUserIds.length > 0" :style="{ marginRight: selectedUserIds.length > 0 ? '60rpx' : '0' }">({{ selectedUserIds.length }}人)</text> -->
  220. <text class="select-clear" v-if="selectedUserIds.length > 0" @click="clearSelectedUsers">×</text>
  221. </view>
  222. </view>
  223. </view>
  224. <!-- 维保内容 -->
  225. <view class="info-item full-width">
  226. <view class="info-label">
  227. <text class="form-label required">维保内容<text style="color: red;">*</text></text>
  228. </view>
  229. <view class="info-value">
  230. <textarea
  231. class="textarea-field"
  232. placeholder="请输入维保内容"
  233. v-model="content"
  234. maxlength="500"
  235. :disabled="infoEntry == '1'"
  236. :show-confirm-bar="false"
  237. auto-height
  238. ></textarea>
  239. </view>
  240. </view>
  241. <!-- 附件上传 -->
  242. <view class="info-item full-width">
  243. <view class="info-label">
  244. <text class="form-label">附件(可选)</text>
  245. </view>
  246. <view class="info-value">
  247. <upload-image
  248. :limit="8"
  249. :modelValue="uploadedFiles"
  250. :businessType="'workOrder'"
  251. @update:modelValue="uploadedFiles = $event as UTSArray<UploadResponse>"
  252. />
  253. </view>
  254. </view>
  255. </view>
  256. </view>
  257. <!-- 时间选择器弹窗 -->
  258. <!-- Start Date Picker -->
  259. <l-popup v-model="showStartTimePicker" position="bottom">
  260. <l-date-time-picker
  261. title="选择开始时间"
  262. :mode="1 | 2 | 4 | 8 | 16"
  263. format="YYYY-MM-DD HH:mm"
  264. :modelValue="realStartTime"
  265. confirm-btn="确定"
  266. cancel-btn="取消"
  267. @confirm="onStartDateConfirm"
  268. @cancel="showStartTimePicker = false">
  269. </l-date-time-picker>
  270. </l-popup>
  271. <!-- End Date Picker -->
  272. <l-popup v-model="showEndTimePicker" position="bottom">
  273. <l-date-time-picker
  274. title="选择结束时间"
  275. :mode="31"
  276. format="YYYY-MM-DD HH:mm"
  277. :modelValue="realEndTime"
  278. confirm-btn="确定"
  279. cancel-btn="取消"
  280. @confirm="onEndDateConfirm"
  281. @cancel="showEndTimePicker = false">
  282. </l-date-time-picker>
  283. </l-popup>
  284. <l-popup v-model="showResumeTimePicker" position="bottom" :safe-area-inset-bottom="true" :z-index="10000">
  285. <l-date-time-picker
  286. title="选择挂起结束时间"
  287. :mode="31"
  288. format="YYYY-MM-DD HH:mm"
  289. :modelValue="resumeTime"
  290. confirm-btn="确定"
  291. cancel-btn="取消"
  292. @confirm="onResumeTimeConfirm"
  293. @cancel="showResumeTimePicker = false">
  294. </l-date-time-picker>
  295. </l-popup>
  296. <!-- 人员选择弹窗 -->
  297. <view v-if="showUserSelect" class="picker-modal">
  298. <view class="modal-mask" @click="showUserSelect = false"></view>
  299. <view class="modal-content">
  300. <view class="modal-header">
  301. <text class="modal-title">选择工作班成员</text>
  302. <text class="modal-close" @click="confirmSelectedUsers">确定</text>
  303. </view>
  304. <view class="search-bar">
  305. <view class="search-box">
  306. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  307. <input class="search-input" type="text" placeholder="搜索姓名" v-model="userKeyword" @input="handleUserSearch" />
  308. <text v-if="userKeyword.length > 0" class="clear-icon" @click="clearUserSearch">✕</text>
  309. </view>
  310. </view>
  311. <scroll-view class="modal-body" scroll-y="true">
  312. <view
  313. v-for="(user, index) in userList"
  314. :key="index"
  315. class="picker-option"
  316. @click="toggleUserSelection(user)"
  317. >
  318. <text class="option-text">{{ (user['nickName'] as string | null) ?? '' }}</text>
  319. <text class="option-text">{{ ((user['dept'] as UTSJSONObject | null)?.['deptName'] as string | null) ?? '' }}</text>
  320. <text class="option-check" v-if="isSelected(user)">✓</text>
  321. </view>
  322. </scroll-view>
  323. </view>
  324. </view>
  325. <!-- MIS工单列表弹窗 -->
  326. <view v-if="showMisListModal" class="picker-modal">
  327. <view class="modal-mask" @click="closeMisListModal"></view>
  328. <view class="modal-content">
  329. <view class="modal-header">
  330. <text class="modal-title">选择MIS工单</text>
  331. <text class="modal-close" @click="closeMisListModal">取消</text>
  332. </view>
  333. <!-- 搜索栏 -->
  334. <view class="search-bar">
  335. <view class="search-box">
  336. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  337. <input class="search-input" type="text" placeholder="搜索 MIS工单编码" v-model="misListKeyword" @input="searchMisList" />
  338. <text v-if="misListKeyword.length > 0" class="clear-icon" @click="clearMisListSearch">✕</text>
  339. </view>
  340. </view>
  341. <!-- 列表内容 -->
  342. <scroll-view class="modal-body" scroll-y="true">
  343. <!-- 有数据时显示列表 -->
  344. <view v-if="misList.length > 0">
  345. <view
  346. v-for="(item, index) in misList"
  347. :key="index"
  348. class="picker-option"
  349. @click="selectMisItem(item, index)"
  350. >
  351. <view>
  352. <text class="option-text">MIS工单编码</text>
  353. <text class="option-text">工作票编号</text>
  354. </view>
  355. <view>
  356. <text class="option-text">{{ ((item['misNo'] as string | null) ?? '-')}}</text>
  357. <text class="option-text">{{ item['workPermitNum'] as string | null ?? '-' }}</text>
  358. </view>
  359. <text v-if="index === selectedMisInfoIndex" class="option-check">✓</text>
  360. </view>
  361. </view>
  362. <!-- 无数据时显示提示 -->
  363. <view v-else class="empty-tip">
  364. <text>未找到匹配的MIS工单</text>
  365. </view>
  366. </scroll-view>
  367. </view>
  368. </view>
  369. <!-- 工作负责人弹窗 -->
  370. <view v-if="showLeaderPicker" class="picker-modal">
  371. <view class="modal-mask" @click="showLeaderPicker = false"></view>
  372. <view class="modal-content">
  373. <view class="modal-header">
  374. <text class="modal-title">选择工作负责人</text>
  375. <text class="modal-close" @click="showLeaderPicker = false">取消</text>
  376. </view>
  377. <view class="search-bar">
  378. <view class="search-box">
  379. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  380. <input class="search-input" type="text" placeholder="搜索姓名" v-model="teamKeyword" @input="handleSearch" />
  381. <text v-if="teamKeyword.length > 0" class="clear-icon" @click="clearSearch">✕</text>
  382. </view>
  383. </view>
  384. <scroll-view class="modal-body" scroll-y="true">
  385. <!-- 有数据时显示列表 -->
  386. <view v-if="teamLeaderList.length > 0">
  387. <view
  388. v-for="(user, index) in teamLeaderList"
  389. :key="index"
  390. class="picker-option"
  391. :class="{ 'selected': index === selectedTeamLeaderIndex }"
  392. @click="selectLeaderManually(user, index)"
  393. >
  394. <view class="info-row">
  395. <text class="option-text">{{ (user['nickName'] as string | null) ?? '' }}</text>
  396. </view>
  397. <text class="option-text">{{ ((user['dept'] as UTSJSONObject | null)?.['deptName'] as string | null) ?? '' }}</text>
  398. <text v-if="index === selectedTeamLeaderIndex" class="option-check">✓</text>
  399. </view>
  400. </view>
  401. <!-- 无数据时显示提示 -->
  402. <view v-else class="empty-tip">
  403. <text>未找到匹配的人员</text>
  404. </view>
  405. </scroll-view>
  406. </view>
  407. </view>
  408. </scroll-view>
  409. <!-- 确认结单按钮 -->
  410. <view class="accept-button-container">
  411. <button class="accept-button" @click="handleSubmit" :loading="submitLoading">{{ submitLoading ? '提交中...' : '确认结单' }}</button>
  412. </view>
  413. <!-- 加载中状态 -->
  414. <view v-if="loading" class="loading-mask">
  415. <text class="loading-text">加载中...</text>
  416. </view>
  417. </view>
  418. </template>
  419. <script setup lang="uts">
  420. import { ref, watch } from 'vue'
  421. import type { acceptOrderInfo } from '../../../types/order'
  422. import type { WorkOrderFlow } from '../../../types/flow'
  423. import { getOrderInfoById, getRepairOrderInfoById, returnRepairOrder, finishOrder } from '../../../api/order/detail'
  424. import { getMisInfoList, listWorkPerson, getOrderList, listAutoMisInfo, allListOrder } from '../../../api/order/list'
  425. import type { SysDictData } from '../../../types/dict'
  426. import { getDictDataByType } from '../../../api/dict/index'
  427. import type { UserInfo } from '../../../types/user'
  428. import type { UploadResponse } from '../../../types/workbench'
  429. import {checkPermi} from '../../../utils/storage'
  430. import { getUserList, getLeaderList } from '../../../api/user/list'
  431. import uploadImage from '../../../components/upload-image/upload-image.uvue'
  432. // 工单信息
  433. const orderId = ref<string>('')
  434. const workOrderProjectNo = ref<string>('')
  435. const workOrderStatus = ref<string>('')
  436. const orderType = ref<string>('')
  437. const pcsDeviceName = ref<string>('')
  438. const gxtCenter = ref<string>('')
  439. const pcsStationName = ref<string>('')
  440. const brand = ref<string>('')
  441. const model = ref<string>('')
  442. const acceptTime = ref<string>('')
  443. const returnType = ref<string>('')
  444. const returnReason = ref<string>("")
  445. const returnTypeLabel = ref<string>("")
  446. const acceptReturnType = ref<string>('')
  447. const acceptReturnReason = ref<string>("")
  448. const teamLeaderId = ref<Number | null>(null)
  449. const teamLeaderName = ref<string>('')
  450. const pauseTime = ref<string>('')
  451. const restartTime = ref<string>('')
  452. // 添加字典加载状态
  453. const dictLoaded = ref<boolean>(false)
  454. // 结单表单相关变量
  455. const infoEntry = ref<string>('') // 信息录入
  456. const misNo = ref<string>('') // MIS工单编码
  457. const workPermitNum = ref<string>('') // 工作票编号
  458. const realStartTime = ref<string>('') // 开始时间
  459. const realEndTime = ref<string>('') // 结束时间
  460. const wwryNum = ref<string>('') // 外委人员数
  461. const wlryNum = ref<string>('') // 外来人员数
  462. const workGroupMemberName = ref<string>('') // 工作班成员
  463. const content = ref<string>('') // 维保内容
  464. const attachmentUrls = ref<string>('') // 附件URLs(逗号分隔的字符串格式)
  465. const uploadedFiles = ref<UploadResponse[]>([]) // 上传的文件对象数组
  466. const workOrderPersonList = ref<UTSJSONObject[]>([]) // 工作班成员数组
  467. const selectedUserIds = ref<string[]>([]) // 选中的用户ID数组
  468. const selectedUsers = ref<UTSJSONObject[]>([]) // 选中的用户对象数组
  469. // 时间选择器相关变量
  470. const showStartTimePicker = ref<boolean>(false)
  471. const showEndTimePicker = ref<boolean>(false)
  472. const startTimeDate = ref<string>('')
  473. const startTimeTime = ref<string>('')
  474. const endTimeDate = ref<string>('')
  475. const endTimeTime = ref<string>('')
  476. // MIS工单选择相关变量
  477. const showMisNoQuickSelect = ref<boolean>(false)
  478. const quickMisNoList = ref<UTSJSONObject[]>([])
  479. // 添加MIS工单列表弹窗显示状态
  480. const showMisListModal = ref<boolean>(false)
  481. // MIS工单列表数据
  482. const misList = ref<UTSJSONObject[]>([])
  483. const allMisList = ref<UTSJSONObject[]>([])
  484. // 分页信息
  485. const misListPage = ref<number>(1)
  486. const misListPageSize = ref<number>(999)
  487. const misListTotal = ref<number>(0)
  488. // 搜索关键词
  489. const misListKeyword = ref<string>('')
  490. const userKeyword = ref<string>('')
  491. // 人员选择相关变量
  492. const showUserSelect = ref<boolean>(false)
  493. const userList = ref<UTSJSONObject[]>([])
  494. const userAllList = ref<UTSJSONObject[]>([])
  495. // 信息录入选项
  496. const infoEntryOptions = ref<SysDictData[]>([])
  497. const selectedMisInfoIndex = ref<number>(-1)
  498. // 选中的负责人信息
  499. const selectedTeamLeaderName = ref<string>('')
  500. const selectedTeamLeaderIndex = ref<number>(-1)
  501. const showLeaderPicker = ref<boolean>(false)
  502. const teamLeaderList = ref<UTSJSONObject[]>([])
  503. const teamAllLeaderList = ref<UTSJSONObject[]>([])
  504. let teamKeyword = ref<string>("")
  505. //挂起结束时间
  506. const suspendReason = ref<string>('') // 挂起原因
  507. const flowList = ref<UTSJSONObject[]>([]) //流转过程
  508. const suspendInfo = ref<UTSJSONObject | null>(null)
  509. const resumeInfo = ref<UTSJSONObject | null>(null)
  510. const resumeShow = ref<boolean>(false)
  511. const resumeTime = ref<string>('') // 挂起结束时间
  512. const showResumeTimePicker = ref<boolean>(false)
  513. // 监听开始时间和结束时间变化
  514. watch(realStartTime, (value: string) => {
  515. console.log('开始时间变化:', value)
  516. // 在这里添加开始时间变化时的处理逻辑
  517. if (resumeInfo.value != null && value != '') {
  518. const suspendTime = (suspendInfo.value?.['actionTime'] as string | null) ?? ''
  519. if (suspendTime != '' && value != '') {
  520. const suspendDate = new Date(suspendTime)
  521. const startDate = new Date(value)
  522. if (suspendDate.getTime() < startDate.getTime()) { // 开工前挂起
  523. const resumeDate = new Date(resumeTime.value)
  524. const endDate = new Date(realEndTime.value)
  525. if (resumeDate.getTime() > startDate.getTime() || (realEndTime.value != '' && resumeDate.getTime() > endDate.getTime())) {
  526. resumeShow.value = true
  527. } else {
  528. resumeShow.value = false
  529. }
  530. } else if (suspendDate.getTime() >= startDate.getTime()) { // 作业中挂起
  531. const resumeDate = new Date(resumeTime.value)
  532. const endDate = new Date(realEndTime.value)
  533. if (resumeDate.getTime() < startDate.getTime() || (realEndTime.value != '' && resumeDate.getTime() > endDate.getTime())) {
  534. resumeShow.value = true
  535. } else {
  536. resumeShow.value = false
  537. }
  538. } else {
  539. resumeShow.value = false
  540. }
  541. } else {
  542. resumeShow.value = false
  543. }
  544. }
  545. })
  546. watch(realEndTime, (value: string) => {
  547. console.log('结束时间变化:', value)
  548. // 在这里添加结束时间变化时的处理逻辑
  549. if (resumeInfo.value != null && value != '') {
  550. const suspendTime = (suspendInfo.value?.['actionTime'] as string | null) ?? ''
  551. if (suspendTime != '' && realStartTime.value != '' && new Date(suspendTime).getTime() < new Date(realStartTime.value).getTime()) { // 开工前挂起
  552. if (value != '' && resumeTime.value != '' && new Date(resumeTime.value).getTime() > new Date(realStartTime.value).getTime()) {
  553. resumeShow.value = true
  554. } else if(value != '' && resumeTime.value != '' && new Date(resumeTime.value).getTime() > new Date(value).getTime()) {
  555. resumeShow.value = true
  556. } else {
  557. resumeShow.value = false
  558. }
  559. } else if(suspendTime != '' && realStartTime.value != '' && new Date(suspendTime).getTime() >= new Date(realStartTime.value).getTime()) { // 作业中挂起
  560. if (realStartTime.value != '' && resumeTime.value != '' && new Date(resumeTime.value).getTime() < new Date(realStartTime.value).getTime()) {
  561. resumeShow.value = true
  562. } else if(value != '' && resumeTime.value != '' && new Date(resumeTime.value).getTime() > new Date(value).getTime()) {
  563. resumeShow.value = true
  564. } else {
  565. resumeShow.value = false
  566. }
  567. } else {
  568. resumeShow.value = false
  569. }
  570. }
  571. })
  572. // 获取信息录入字典列表
  573. const loadInfoEntryDictList = async (): Promise<void> => {
  574. try {
  575. const result = await getDictDataByType('gxt_info_entry') // 假设信息录入类型字典类型为gxt_info_entry
  576. const resultObj = result as UTSJSONObject
  577. if (resultObj['code'] == 200) {
  578. const data = resultObj['data'] as any[]
  579. const dictData: SysDictData[] = []
  580. if (data.length > 0) {
  581. for (let i = 0; i < data.length; i++) {
  582. const item = data[i] as UTSJSONObject
  583. // 只提取需要的字段
  584. const dictItem: SysDictData = {
  585. dictValue: item['dictValue'] as string | null,
  586. dictLabel: item['dictLabel'] as string | null,
  587. dictCode: null,
  588. dictSort: null,
  589. dictType: null,
  590. cssClass: null,
  591. listClass: null,
  592. isDefault: null,
  593. status: null,
  594. default: null,
  595. createTime: null,
  596. remark: null
  597. }
  598. dictData.push(dictItem)
  599. }
  600. }
  601. infoEntryOptions.value = dictData
  602. }
  603. } catch (e: any) {
  604. console.error('获取信息录入类型字典失败:', e.message)
  605. // 设置默认值
  606. infoEntryOptions.value = [
  607. { dictValue: '1', dictLabel: 'MIS工单', dictCode: null, dictSort: null, dictType: null, cssClass: null, listClass: null, isDefault: null, status: null, default: null, createTime: null, remark: null },
  608. { dictValue: '2', dictLabel: '手工录入', dictCode: null, dictSort: null, dictType: null, cssClass: null, listClass: null, isDefault: null, status: null, default: null, createTime: null, remark: null }
  609. ];
  610. }
  611. }
  612. // 获取用户列表
  613. const getUserAllList = async (): Promise<void> => {
  614. try {
  615. // 这里应该调用获取用户列表的API
  616. const result = await getLeaderList(-1); // 空参数调用
  617. const resultObj = result as UTSJSONObject;
  618. const code = resultObj['code'] as number
  619. const users = resultObj['data'] as UTSJSONObject[] | null
  620. if (code == 200 && users != null ) {
  621. // 解析列表数据
  622. // const rows = resultObj['rows'] as UTSJSONObject[]
  623. userList.value = users;
  624. userAllList.value = users
  625. teamLeaderList.value = users
  626. teamAllLeaderList.value = users
  627. // userList.value = result.data || [];
  628. }
  629. } catch (error) {
  630. console.error('获取用户列表失败:', error);
  631. uni.showToast({
  632. title: '获取用户列表失败',
  633. icon: 'none'
  634. });
  635. }
  636. };
  637. // 外委人员数输入处理
  638. const onWwryNumInput = (): void => {
  639. let value = wwryNum.value;
  640. // 移除非数字字符,包括负号和小数点
  641. value = value.replace(/[^0-9]/g, '');
  642. wwryNum.value = value;
  643. };
  644. // 外来人员数输入处理
  645. const onWlryNumInput = (): void => {
  646. let value = wlryNum.value;
  647. // 移除非数字字符,包括负号和小数点
  648. value = value.replace(/[^0-9]/g, '');
  649. wlryNum.value = value;
  650. };
  651. // 验证和提交
  652. const submitLoading = ref<boolean>(false)
  653. // 信息录入禁用状态
  654. const infoEntryDisabled = ref<boolean>(false)
  655. // 接受用户名
  656. const acceptUserName = ref<string>('')
  657. // 选择器选项类型
  658. type PickerOption = {
  659. label: string
  660. value: string
  661. }
  662. // 手动选择负责人
  663. const selectLeaderManually = (user: UTSJSONObject, index: number): void => {
  664. selectedTeamLeaderIndex.value = index
  665. const uid = user['userId']
  666. if (uid != null) {
  667. teamLeaderId.value = uid as Number
  668. }
  669. const name = user['nickName']
  670. teamLeaderName.value = name != null ? name.toString() : ''
  671. showLeaderPicker.value = false
  672. }
  673. // 信息录入变化处理
  674. const handleInfoEntryChange = (e: UniRadioGroupChangeEvent): void => {
  675. // 兼容 radio-group 事件:detail 可能不存在,直接取 e.value
  676. const val = e.detail?.value as string | null
  677. infoEntry.value = val ?? ''
  678. if (infoEntry.value == '1') {
  679. // 当选择MIS工单时,清空手工录入的字段
  680. workPermitNum.value = '';
  681. realStartTime.value = '';
  682. realEndTime.value = '';
  683. workGroupMemberName.value = '';
  684. }
  685. };
  686. // 工作票编号变化处理
  687. const handleWorkPermitNumChange = async(): Promise<void> => {
  688. if (infoEntry.value == '2') {
  689. // 查询工作票编号是否已存在
  690. const queryParams = {
  691. workPermitNum: workPermitNum.value,
  692. } as UTSJSONObject;
  693. const response = await allListOrder(queryParams)
  694. const responseObj = response as UTSJSONObject
  695. const rows = responseObj['rows'] as UTSJSONObject[] | null
  696. if (rows != null && rows.length > 0) {
  697. uni.showToast({ title: '工作票编号:' + workPermitNum.value + '已存在', icon: 'none' })
  698. workPermitNum.value = ''
  699. return
  700. }
  701. }
  702. };
  703. // MIS工单编码输入处理
  704. const handleMisNoInputFocus = (): void => {
  705. showMisNoQuickSelect.value = true;
  706. };
  707. const handleMisNoInputBlur = (): void => {
  708. setTimeout(() => {
  709. showMisNoQuickSelect.value = false;
  710. }, 200);
  711. };
  712. const handleMisNoInput = (e: SysDictData): void => {
  713. // misNo.value = (e['detail'] as string | null) ?? '';
  714. // 这里可以添加实时搜索MIS工单的逻辑
  715. };
  716. const handleMisNoClear = (): void => {
  717. misNo.value = '';
  718. showMisNoQuickSelect.value = false;
  719. };
  720. const handleMisNoQuickSelect = (item: UTSJSONObject): void => {
  721. misNo.value = (item['misNo'] as string | null) ?? '';
  722. showMisNoQuickSelect.value = false;
  723. };
  724. // 获取MIS工单列表
  725. const getMisList = async (): Promise<void> => {
  726. try {
  727. const queryParams = {
  728. pcsDeviceName: pcsDeviceName.value,
  729. pcsStationName: pcsStationName.value,
  730. workOrderStatus: '结束'
  731. } as UTSJSONObject;
  732. // 调用获取MIS工单列表的API
  733. const result = await getMisInfoList(queryParams);
  734. const resultObj = result as UTSJSONObject;
  735. if (resultObj['code'] == 200) {
  736. // 解析列表数据
  737. const rows = resultObj['rows'] as UTSJSONObject[]
  738. misList.value = rows;
  739. allMisList.value = rows
  740. // 解析总数
  741. misListTotal.value = resultObj['total'] as number;
  742. } else {
  743. const msg = resultObj['msg'] as string | null
  744. uni.showToast({
  745. title: msg ?? '获取MIS工单列表失败',
  746. icon: 'none'
  747. })
  748. }
  749. } catch (error: any) {
  750. console.error('获取MIS工单列表失败:', error);
  751. uni.showToast({
  752. title: error.message ?? '获取MIS工单列表失败',
  753. icon: 'none'
  754. });
  755. }
  756. };
  757. // 打开MIS工单选择弹窗
  758. const openMisListModal = (): void => {
  759. showMisListModal.value = true;
  760. misListPage.value = 1; // 重置为第一页
  761. getMisList(); // 获取列表数据
  762. };
  763. // 选择MIS工单
  764. const selectMisItem = async (item: UTSJSONObject, index: number): Promise<void> => {
  765. selectedMisInfoIndex.value = index
  766. // 回填MIS工单相关信息
  767. misNo.value = item['misNo'] as string | '';
  768. // 查询MIS工单是否已存在
  769. const response = await getOrderList(1, 10, misNo.value, '')
  770. const responseObj = response as UTSJSONObject
  771. const rows = responseObj['rows'] as UTSJSONObject[] | null
  772. if (rows != null && rows.length > 0) {
  773. uni.showToast({ title: '该MIS工单已被其他工单关联!请重新选择!', icon: 'none' })
  774. misNo.value = ''
  775. return
  776. }
  777. // 查询相关工作班成员
  778. await listWorkPerson(misNo.value).then(response => {
  779. const responseObj = response as UTSJSONObject
  780. const rows = responseObj['rows'] as UTSJSONObject[] | null
  781. workOrderPersonList.value = rows ?? []
  782. if (rows != null && rows.length > 0) {
  783. // 查找 isLeader 等于 1 的负责人(优先获取第一个符合条件的,贴合常规单负责人场景)
  784. const leaderPerson = rows.find(person => person.isLeader == 1);
  785. if(leaderPerson != null) {
  786. teamLeaderId.value = (leaderPerson['userId'] as Number | null) ?? null
  787. teamLeaderName.value = (leaderPerson['nickName'] as string | null) ?? ''
  788. }
  789. const nickNames = rows
  790. .filter(person => person.isLeader != 1)
  791. .map(person => (person.nickName as string | null) ?? '')
  792. .join(',');
  793. workGroupMemberName.value = nickNames;
  794. }
  795. })
  796. workPermitNum.value = item['workPermitNum'] as string | '';
  797. realStartTime.value = item['realStartTime'] as string | '';
  798. realEndTime.value = item['realEndTime'] as string | '';
  799. // 关闭弹窗
  800. showMisListModal.value = false;
  801. };
  802. // 搜索
  803. const handleUserSearch = (): void => {
  804. const keyword = userKeyword.value
  805. userList.value = userAllList.value.filter(leader => {
  806. const nickName = leader['nickName'] as string | null
  807. return nickName != null && nickName.indexOf(keyword) >= 0
  808. })
  809. }
  810. // 关闭MIS工单选择弹窗
  811. const closeMisListModal = (): void => {
  812. showMisListModal.value = false;
  813. };
  814. // 搜索
  815. const searchMisList = (): void => {
  816. const keyword = misListKeyword.value
  817. misList.value = allMisList.value.filter(misInfo => {
  818. const misNo = misInfo['misNo'] as string | null
  819. // return misNo != null && misNo.indexOf(keyword) >= 0
  820. const workPermitNum = misInfo['workPermitNum'] as string | null
  821. // 逻辑或(||)连接两个条件,满足其一即可
  822. const misNoMatch = misNo != null && misNo.indexOf(keyword) >= 0
  823. const workPermitNumMatch = workPermitNum != null && workPermitNum.indexOf(keyword) >= 0
  824. return misNoMatch || workPermitNumMatch
  825. })
  826. }
  827. // 清除MIS工单搜索
  828. const clearMisListSearch = (): void => {
  829. misListKeyword.value = "";
  830. misList.value = allMisList.value
  831. };
  832. // 清除用户搜索
  833. const clearUserSearch = (): void => {
  834. userKeyword.value = "";
  835. userList.value = userAllList.value
  836. };
  837. // 搜索工作负责人
  838. const handleSearch = (): void => {
  839. const keyword = teamKeyword.value
  840. teamLeaderList.value = teamAllLeaderList.value.filter(leader => {
  841. const nickName = leader['nickName'] as string | null
  842. return nickName != null && nickName.indexOf(keyword) >= 0
  843. })
  844. }
  845. // 清空搜索工作负责人
  846. const clearSearch = (): void => {
  847. teamKeyword.value = ""
  848. teamLeaderList.value = teamAllLeaderList.value
  849. }
  850. function onStartDateConfirm(value: string) {
  851. // 检查结束时间是否小于新的开始时间
  852. if (realEndTime.value != '' && new Date(value) > new Date(realEndTime.value as string)) {
  853. uni.showToast({ title: '开始时间不能大于结束时间', icon: 'none' })
  854. return
  855. }
  856. realStartTime.value = value
  857. showStartTimePicker.value = false
  858. }
  859. function onEndDateConfirm(value: string) {
  860. // 检查新的结束时间是否小于开始时间
  861. if (realStartTime.value != '' && new Date(realStartTime.value as string) > new Date(value)) {
  862. uni.showToast({ title: '结束时间不能小于开始时间', icon: 'none' })
  863. return
  864. }
  865. realEndTime.value = value
  866. showEndTimePicker.value = false
  867. }
  868. function onResumeTimeConfirm(value: string) {
  869. // 检查新的结束时间是否小于开始时间
  870. if (resumeTime.value != '' && new Date(realStartTime.value as string) > new Date(value)) {
  871. uni.showToast({ title: '结束时间不能小于开始时间', icon: 'none' })
  872. return
  873. }
  874. resumeTime.value = value
  875. showResumeTimePicker.value = false
  876. }
  877. // 检查用户是否已被选中
  878. const isSelected = (user: UTSJSONObject): boolean => {
  879. const userId = user['userId'] as string | number | null;
  880. if (userId !== null) {
  881. return selectedUserIds.value.includes(userId.toString());
  882. }
  883. // 如果没有userId,则比较nickName
  884. const nickName = user['nickName'] as string | null;
  885. if (nickName !== null) {
  886. return selectedUsers.value.some(selected =>
  887. (selected['nickName'] as string | null) === nickName
  888. );
  889. }
  890. return false;
  891. };
  892. // 切换用户选择状态
  893. const toggleUserSelection = (user: UTSJSONObject): void => {
  894. const userId = user['userId'] as string | number | null;
  895. const nickName = user['nickName'] as string | null;
  896. if (userId !== null) {
  897. const userIdStr = userId.toString();
  898. const index = selectedUserIds.value.indexOf(userIdStr);
  899. if (index > -1) {
  900. // 取消选择
  901. selectedUserIds.value.splice(index, 1);
  902. selectedUsers.value = selectedUsers.value.filter(selected =>
  903. (selected['userId'] as string | number | null)?.toString() !== userIdStr
  904. );
  905. } else {
  906. // 添加选择
  907. selectedUserIds.value.push(userIdStr);
  908. selectedUsers.value.push(user);
  909. }
  910. } else if (nickName !== null) {
  911. // 如果没有userId,通过nickName来识别
  912. const index = selectedUsers.value.findIndex(selected =>
  913. (selected['nickName'] as string | null) === nickName
  914. );
  915. if (index > -1) {
  916. // 取消选择
  917. selectedUsers.value.splice(index, 1);
  918. // 同时移除对应的userId(如果有的话)
  919. const userToRemoveId = user['userId'] as string | number | null;
  920. if (userToRemoveId !== null) {
  921. const idIndex = selectedUserIds.value.indexOf(userToRemoveId.toString());
  922. if (idIndex > -1) {
  923. selectedUserIds.value.splice(idIndex, 1);
  924. }
  925. }
  926. } else {
  927. // 添加选择
  928. selectedUsers.value.push(user);
  929. const userToAddId = user['userId'] as string | number | null;
  930. if (userToAddId !== null) {
  931. selectedUserIds.value.push(userToAddId.toString());
  932. }
  933. }
  934. }
  935. };
  936. // 确认选择的用户
  937. const confirmSelectedUsers = (): void => {
  938. // 将选中的用户姓名拼接成字符串
  939. const nickNames = selectedUsers.value
  940. .map(user => (user['nickName'] as string | null) ?? '')
  941. .filter(name => name !== '')
  942. .join(',');
  943. workGroupMemberName.value = nickNames;
  944. // 更新workOrderPersonList为选中的用户
  945. workOrderPersonList.value = [...selectedUsers.value];
  946. showUserSelect.value = false;
  947. };
  948. // 清空已选择的用户
  949. const clearSelectedUsers = (): void => {
  950. // 清空选中的用户数组
  951. selectedUsers.value = [];
  952. selectedUserIds.value = [];
  953. // 清空显示的用户名
  954. workGroupMemberName.value = '';
  955. // 清空工作班成员列表
  956. workOrderPersonList.value = [];
  957. };
  958. // 表单验证
  959. const validateForm = (): boolean => {
  960. if (infoEntry.value == '') {
  961. uni.showToast({
  962. title: '请选择信息录入方式',
  963. icon: 'none'
  964. });
  965. return false;
  966. }
  967. if (infoEntry.value == '1' && (misNo.value == '')) {
  968. uni.showToast({
  969. title: '请输入MIS工单编码',
  970. icon: 'none'
  971. });
  972. return false;
  973. }
  974. if (workPermitNum.value == '') {
  975. uni.showToast({
  976. title: '请输入工作票编号',
  977. icon: 'none'
  978. });
  979. return false;
  980. }
  981. if (realStartTime.value == '') {
  982. uni.showToast({
  983. title: '请选择开始时间',
  984. icon: 'none'
  985. });
  986. return false;
  987. }
  988. if (realEndTime.value == '') {
  989. uni.showToast({
  990. title: '请选择结束时间',
  991. icon: 'none'
  992. });
  993. return false;
  994. }
  995. // 统一转换为时间戳(避免格式问题,对比更精准)
  996. const realStartTimeStamp = new Date(realStartTime.value).getTime();
  997. const currentTimeStamp = new Date().getTime(); // 当前时间戳
  998. const realEndTimeStamp = new Date(realEndTime.value).getTime(); // 结束时间戳
  999. // 校验1:开始时间不能大于当前时间
  1000. if (realStartTimeStamp > currentTimeStamp) {
  1001. uni.showToast({
  1002. title: '开始时间不能大于当前时间',
  1003. icon: 'none'
  1004. });
  1005. return false;
  1006. }
  1007. // 校验2:开始时间不能大于结束时间(优化原有逻辑,用时间戳更稳定)
  1008. if (realStartTimeStamp >= realEndTimeStamp) {
  1009. uni.showToast({
  1010. title: '开始时间不能大于结束时间',
  1011. icon: 'none'
  1012. });
  1013. return false;
  1014. }
  1015. // 校验3:开始时间不能大于停机时间(pauseTime非空时校验)
  1016. if (pauseTime.value != '' && pauseTime.value.trim() !== '') {
  1017. const pauseTimeStamp = new Date(pauseTime.value).getTime();
  1018. if (realStartTimeStamp > pauseTimeStamp) {
  1019. uni.showToast({
  1020. title: '开始时间不能大于停机时间',
  1021. icon: 'none'
  1022. });
  1023. return false;
  1024. }
  1025. if (realEndTimeStamp < pauseTimeStamp) {
  1026. uni.showToast({
  1027. title: '结束时间不能小于停机时间',
  1028. icon: 'none'
  1029. });
  1030. return false;
  1031. }
  1032. }
  1033. // 校验4:开始时间不能大于恢复运行时间(restartTime非空时校验)
  1034. if (restartTime.value != '' && restartTime.value.trim() !== '') {
  1035. const restartTimeStamp = new Date(restartTime.value).getTime();
  1036. if (realStartTimeStamp > restartTimeStamp) {
  1037. uni.showToast({
  1038. title: '开始时间不能大于复运时间',
  1039. icon: 'none'
  1040. });
  1041. return false;
  1042. }
  1043. }
  1044. // 1. 结束时间不能大于当前时间
  1045. if (realEndTimeStamp > currentTimeStamp) {
  1046. uni.showToast({
  1047. title: '结束时间不能大于当前时间',
  1048. icon: 'none'
  1049. });
  1050. return false;
  1051. }
  1052. // 2. 结束时间不能小于恢复运行时间(restartTime/复运时间非空时校验)
  1053. if (restartTime.value != '' && restartTime.value.trim() !== '') {
  1054. const restartTimeStamp = new Date(restartTime.value).getTime();
  1055. if (realEndTimeStamp < restartTimeStamp) {
  1056. uni.showToast({
  1057. title: '结束时间不能小于复运时间',
  1058. icon: 'none'
  1059. });
  1060. return false;
  1061. }
  1062. }
  1063. if (teamLeaderName.value == '') {
  1064. uni.showToast({
  1065. title: '请选择工作负责人',
  1066. icon: 'none'
  1067. });
  1068. return false;
  1069. }
  1070. if (workGroupMemberName.value == '') {
  1071. uni.showToast({
  1072. title: '请选择工作班成员',
  1073. icon: 'none'
  1074. });
  1075. return false;
  1076. }
  1077. if(resumeShow.value) {
  1078. if(resumeTime.value == '') {
  1079. uni.showToast({
  1080. title: '请选挂起结束时间',
  1081. icon: 'none'
  1082. });
  1083. return false;
  1084. } else {
  1085. const suspendTime = (suspendInfo.value?.['actionTime'] as string | null) ?? ''
  1086. if (suspendTime != '' && realStartTime.value != '' && new Date(suspendTime) < new Date(realStartTime.value)) { // 开工前挂起
  1087. if (resumeTime.value != '' && new Date(resumeTime.value) > new Date(realStartTime.value)) {
  1088. uni.showToast({
  1089. title: '开工前挂起结束时间晚于实际开始时间,请调整',
  1090. icon: 'none'
  1091. });
  1092. return false;
  1093. } else if(realEndTime.value != '' && resumeTime.value != '' && new Date(resumeTime.value) > new Date(realEndTime.value)) {
  1094. uni.showToast({
  1095. title: '开工前挂起结束时间晚于实际结束时间,请调整',
  1096. icon: 'none'
  1097. });
  1098. return false;
  1099. }
  1100. } else if(suspendTime != '' && realStartTime.value != '' && new Date(suspendTime) >= new Date(realStartTime.value)) { // 作业中挂起
  1101. if (resumeTime.value != '' && new Date(resumeTime.value) < new Date(realStartTime.value)) {
  1102. uni.showToast({
  1103. title: '作业中挂起结束时间早于实际开始时间,请调整',
  1104. icon: 'none'
  1105. });
  1106. return false;
  1107. } else if(realEndTime.value != '' && resumeTime.value != '' && new Date(resumeTime.value) > new Date(realEndTime.value)) {
  1108. uni.showToast({
  1109. title: '作业中挂起结束时间晚于实际结束时间,请调整',
  1110. icon: 'none'
  1111. });
  1112. return false;
  1113. }
  1114. }
  1115. }
  1116. }
  1117. return true;
  1118. };
  1119. const isDealing = ref(false)
  1120. const hasDealed = ref(false)
  1121. // 提交表单
  1122. const handleSubmit = async (): Promise<void> => {
  1123. if (!validateForm()) {
  1124. return;
  1125. }
  1126. submitLoading.value = true;
  1127. try {
  1128. if (isDealing.value || hasDealed.value) return // 双重保险
  1129. isDealing.value = true
  1130. // 确保附件URLs是最新的逗号分隔格式
  1131. attachmentUrls.value = uploadedFiles.value.map(file => file.fileName).join(',');
  1132. flowList.value = []
  1133. if (resumeTime.value != '' && resumeInfo.value != null && resumeTime.value != (resumeInfo.value['actionTime'] as string | null)) { //存入新的挂起结束时间
  1134. resumeInfo.value['actionTime'] = resumeTime.value
  1135. flowList.value.push(resumeInfo.value as UTSJSONObject)
  1136. }
  1137. const finishData = {
  1138. id: orderId.value,
  1139. orderType: orderType.value,
  1140. workOrderProjectNo: workOrderProjectNo.value,
  1141. infoEntry: infoEntry.value,
  1142. misNo: infoEntry.value == '1' ? misNo.value : null,
  1143. workPermitNum: workPermitNum.value,
  1144. realStartTime: realStartTime.value,
  1145. realEndTime: realEndTime.value,
  1146. wwryNum: wwryNum.value,
  1147. wlryNum: wlryNum.value,
  1148. workGroupMemberName: workGroupMemberName.value,
  1149. content: content.value,
  1150. attachmentUrls: attachmentUrls.value,
  1151. workOrderPersonList: workOrderPersonList.value,
  1152. teamLeaderId: teamLeaderId.value,
  1153. teamLeaderName: teamLeaderName.value,
  1154. finalizeMethod: '2',
  1155. workOrderStatus: 'completed',
  1156. workOrderFlowList: flowList.value
  1157. } as UTSJSONObject;
  1158. const result = await finishOrder(finishData);
  1159. const resultObj = result as UTSJSONObject;
  1160. const code = resultObj['code'] as number;
  1161. if (code == 200) {
  1162. uni.showToast({
  1163. title: '结单成功',
  1164. icon: 'success'
  1165. });
  1166. hasDealed.value = true
  1167. // 使用事件总线通知列表页面刷新
  1168. uni.$emit('refreshOrderList', {});
  1169. uni.$emit('refreshAssignedCount');
  1170. uni.$emit('refreshOverdueCount');
  1171. setTimeout(() => {
  1172. uni.navigateBack();
  1173. }, 800);
  1174. } else {
  1175. const msg = resultObj['msg'] as string;
  1176. uni.showToast({
  1177. title: msg.length > 0 ? msg : '结单失败',
  1178. icon: 'none'
  1179. });
  1180. }
  1181. } catch (error: Error) {
  1182. uni.showToast({
  1183. title: error.message,
  1184. icon: 'none'
  1185. });
  1186. } finally {
  1187. submitLoading.value = false;
  1188. isDealing.value = false // 无论成功失败都解锁
  1189. }
  1190. };
  1191. const loading = ref<boolean>(false)
  1192. // 加载详情数据
  1193. const loadDetail = async (id: string, orderType?: string): Promise<void> => {
  1194. try {
  1195. loading.value = true
  1196. let result: any;
  1197. // 根据orderType决定调用哪个API
  1198. if (orderType == '1') {
  1199. // 维修工单
  1200. result = await getRepairOrderInfoById(id)
  1201. } else {
  1202. // 维保工单
  1203. result = await getOrderInfoById(id)
  1204. }
  1205. // 提取响应数据
  1206. const resultObj = result as UTSJSONObject
  1207. const code = resultObj['code'] as number
  1208. const data = resultObj['data'] as UTSJSONObject | null
  1209. if (code == 200 && data != null) {
  1210. workOrderStatus.value = (data['workOrderStatus'] as string | null) ?? ''
  1211. workOrderProjectNo.value = (data['workOrderProjectNo'] as string | null) ?? ''
  1212. pcsDeviceName.value = (data['pcsDeviceName'] as string | null) ?? ''
  1213. gxtCenter.value = (data['gxtCenter'] as string | null) ?? ''
  1214. pcsStationName.value = (data['pcsStationName'] as string | null) ?? ''
  1215. brand.value = (data['brand'] as string | null) ?? ''
  1216. model.value = (data['model'] as string | null) ?? ''
  1217. acceptTime.value = (data['acceptTime'] as string | null) ?? ''
  1218. acceptUserName.value = (data['acceptUserName'] as string | null) ?? ''
  1219. // 初始化结单表单默认值
  1220. infoEntry.value = '1' // 默认为手工录入
  1221. teamLeaderId.value = (data['teamLeaderId'] as Number | null) ?? null
  1222. teamLeaderName.value = (data['teamLeaderName'] as string | null) ?? ''
  1223. returnType.value = workOrderStatus.value == 'to_finish' ? '1' : ''
  1224. content.value = (data['content'] as string | null) ?? ''
  1225. suspendReason.value = (data['suspendReason'] as string | null) ?? ''
  1226. // 初始化附件数据
  1227. const attachmentUrlsFromServer = (data['attachmentUrls'] as string | null) ?? ''
  1228. if (attachmentUrlsFromServer.length > 0) {
  1229. attachmentUrls.value = attachmentUrlsFromServer
  1230. // 将逗号分隔的URL字符串转换为UploadResponse对象数组
  1231. const urls = attachmentUrlsFromServer.split(',')
  1232. const fileArr : UploadResponse[] = []
  1233. for (let i = 0; i < urls.length; i++) {
  1234. const url = urls[i]
  1235. const item : UploadResponse = {
  1236. url: url,
  1237. fileId: '',
  1238. fileName: url.substring(url.lastIndexOf('/') + 1),
  1239. filePath: url,
  1240. fileSize: 0,
  1241. fileExt: url.substring(url.lastIndexOf('.') + 1),
  1242. businessType: 'workOrder'
  1243. }
  1244. fileArr.push(item)
  1245. }
  1246. uploadedFiles.value = fileArr
  1247. }
  1248. pauseTime.value = (data['pauseTime'] as string | null) ?? ''
  1249. restartTime.value = (data['restartTime'] as string | null) ?? ''
  1250. if(pauseTime.value != '' && restartTime.value != '') {
  1251. const queryParams = {
  1252. pauseTime: pauseTime.value,
  1253. restartTime: restartTime.value,
  1254. pcsDeviceName: pcsDeviceName.value,
  1255. pcsStationName: pcsStationName.value,
  1256. workOrderStatus: '结束',
  1257. orderType: 2
  1258. } as UTSJSONObject;
  1259. const result = await listAutoMisInfo(queryParams)
  1260. // 提取响应数据
  1261. const resultObj = result as UTSJSONObject
  1262. const code = resultObj['code'] as number
  1263. const misInfo = resultObj['rows'] as UTSJSONObject[] | null
  1264. if (code == 200 && misInfo != null) {
  1265. if(misInfo.length > 0 && misInfo.length == 1) {
  1266. // 有工作票号提示
  1267. const workPermitNum2 = misInfo[0]['workPermitNum'] as string | null
  1268. const misNo2 = misInfo[0]['misNo'] as string | null
  1269. if (workPermitNum2 != null && workPermitNum2.length > 0) {
  1270. const response = await getOrderList(1, 10, misNo2 ?? '', '')
  1271. const responseObj = response as UTSJSONObject
  1272. const rows = responseObj['rows'] as UTSJSONObject[] | null
  1273. if (rows != null && rows.length > 0) {
  1274. misNo.value = ''
  1275. // infoEntry.value = '2'
  1276. uni.showModal({
  1277. title: '提示',
  1278. content: '已匹配到' + misNo2 + '工单,但该MIS工单已被其他工单关联,请检查是否重复操作。',
  1279. showCancel: false, // 只显示确定按钮
  1280. confirmText: '确定',
  1281. confirmColor: '#007aff', // 自定义按钮颜色,匹配你的UI风格
  1282. success: (res) => {
  1283. }
  1284. });
  1285. } else {
  1286. misNo.value = (misInfo[0]['misNo'] as string | null) ?? ''
  1287. realStartTime.value = (misInfo[0]['realStartTime'] as string | null) ?? ''
  1288. realEndTime.value = (misInfo[0]['realEndTime'] as string | null) ?? ''
  1289. workPermitNum.value = (misInfo[0]['workPermitNum'] as string | null) ?? ''
  1290. // 查询相关工作班成员
  1291. listWorkPerson(misNo.value).then(response => {
  1292. const responseObj = response as UTSJSONObject
  1293. const rows = responseObj['rows'] as UTSJSONObject[] | null
  1294. workOrderPersonList.value = rows ?? []
  1295. if (rows != null && rows.length > 0) {
  1296. // 查询负责人信息并回填
  1297. for (const person of workOrderPersonList.value) {
  1298. // 严格判断isLeader为1(兼容数字/字符串类型)
  1299. if (person.isLeader == 1) {
  1300. teamLeaderId.value = (person.userId as Number | null) ?? null;
  1301. teamLeaderName.value = (person.nickName as string | null) ?? '';
  1302. break; // 找到后立即停止循环
  1303. }
  1304. }
  1305. const nickNames = rows
  1306. .filter(person => person.isLeader != 1)
  1307. .map(person => (person.nickName as string | null) ?? '')
  1308. .join(',');
  1309. workGroupMemberName.value = nickNames;
  1310. workOrderPersonList.value.map(person => {
  1311. // 构造查询参数:username 和 nickName
  1312. const queryParams = {
  1313. userName: person.userName, // 假设person对象有username字段
  1314. nickName: person.nickName // 假设person对象有nickName字段
  1315. };
  1316. getUserList(queryParams).then(response => {
  1317. const responseObj = response as UTSJSONObject
  1318. const rows = responseObj['rows'] as UTSJSONObject[] | null
  1319. if (rows == null || rows.length == 0) {
  1320. let msg = "已匹配到MIS工单,但工作班成员'" + person.nickName + "'在系统中不存在,系统无法自动结单,请检查"
  1321. if (person.isLeader == 1) {
  1322. msg = "已匹配到MIS工单,但工作负责人'" + person.nickName + "'在系统中不存在,系统无法自动结单,请检查"
  1323. }
  1324. uni.showModal({
  1325. title: '提示',
  1326. content: msg,
  1327. showCancel: false, // 只显示确定按钮
  1328. confirmText: '确定',
  1329. confirmColor: '#007aff',
  1330. success: (res) => {
  1331. }
  1332. });
  1333. }
  1334. });
  1335. })
  1336. }
  1337. })
  1338. }
  1339. } else {
  1340. misNo.value = ''
  1341. // infoEntry.value = '2'
  1342. uni.showModal({
  1343. title: '提示',
  1344. content: '已匹配到MIS工单,但未关联工作票号,系统无法自动结单,请进入工作票录入方式。',
  1345. showCancel: false, // 只显示确定按钮
  1346. confirmText: '确定',
  1347. confirmColor: '#007aff',
  1348. success: (res) => {
  1349. }
  1350. });
  1351. }
  1352. } else if(misInfo.length == 0) {
  1353. misNo.value = ''
  1354. // infoEntry.value = '2'
  1355. uni.showModal({
  1356. title: '提示',
  1357. content: '未找到匹配的MIS工单,请确认开始时间是否早于停机时间且与停机时间相差不超过60分钟,并且结束时间晚于恢复运行时间。',
  1358. showCancel: false, // 只显示确定按钮
  1359. confirmText: '确定',
  1360. confirmColor: '#007aff',
  1361. success: (res) => {
  1362. }
  1363. });
  1364. } else if(misInfo.length > 1) {
  1365. misNo.value = ''
  1366. // infoEntry.value = '2'
  1367. uni.showModal({
  1368. title: '提示',
  1369. content: '已匹配到多个MIS工单,请手动结单,信息录入选择关联MIS。',
  1370. showCancel: false, // 只显示确定按钮
  1371. confirmText: '确定',
  1372. confirmColor: '#007aff',
  1373. success: (res) => {
  1374. }
  1375. });
  1376. }
  1377. }
  1378. } else {
  1379. // infoEntryDisabled.value = true
  1380. misNo.value = ''
  1381. // infoEntry.value = '2'
  1382. uni.showModal({
  1383. title: '提示',
  1384. content: '未找到匹配的MIS工单,请确认风机停复机时间是否已录入工效通系统或请进入工作票录入方式。',
  1385. showCancel: false, // 只显示确定按钮
  1386. confirmText: '确定',
  1387. confirmColor: '#007aff',
  1388. success: (res) => {
  1389. }
  1390. });
  1391. }
  1392. flowList.value = data['workOrderFlowList'] as UTSJSONObject[]
  1393. if (suspendReason.value != '' && flowList.value.length > 0) {
  1394. // 获取最后一个 actionType 等于 'resume' 的项
  1395. const lastResumeItem = flowList.value.findLast(item => item['actionType'] === 'resume') as UTSJSONObject | null
  1396. if (lastResumeItem != null) {
  1397. resumeInfo.value = lastResumeItem
  1398. // 直接给 resumeTime 赋值,无需 formData
  1399. resumeTime.value = (lastResumeItem['actionTime'] as string | null) ?? ''
  1400. }
  1401. const lastSuspendItem = flowList.value.findLast(item => (item['actionType'] as string | null) === 'to_approve') as UTSJSONObject | null
  1402. if (lastSuspendItem != null) {
  1403. suspendInfo.value = lastSuspendItem
  1404. }
  1405. }
  1406. } else {
  1407. const msg = resultObj['msg'] as string | null
  1408. uni.showToast({
  1409. title: msg ?? '加载失败',
  1410. icon: 'none'
  1411. })
  1412. }
  1413. } catch (e: any) {
  1414. uni.showToast({
  1415. title: e.message ?? '加载失败',
  1416. icon: 'none'
  1417. })
  1418. } finally {
  1419. loading.value = false
  1420. }
  1421. }
  1422. // 页面加载
  1423. onLoad((options: any) => {
  1424. const params = options as UTSJSONObject
  1425. const id = params['id'] as string | null
  1426. const orderTypeParam = params['orderType'] as string | null
  1427. if (id != null && orderTypeParam != null) {
  1428. // 先尝试从参数中获取orderType
  1429. // const orderTypeNumber = parseInt(orderTypeParam)
  1430. orderType.value = orderTypeParam
  1431. orderId.value = id
  1432. loadDetail(id, orderTypeParam)
  1433. }
  1434. })
  1435. // 初始化
  1436. onMounted(() => {
  1437. getUserAllList();
  1438. loadInfoEntryDictList();
  1439. })
  1440. </script>
  1441. <style lang="scss">
  1442. .detail-page {
  1443. flex: 1;
  1444. background-color: #e8f0f9;
  1445. }
  1446. .detail-content {
  1447. flex: 1;
  1448. padding: 20rpx 0;
  1449. }
  1450. .info-section {
  1451. margin: 0 30rpx 24rpx;
  1452. .section-title {
  1453. position: relative;
  1454. padding-left: 20rpx;
  1455. margin-bottom: 20rpx;
  1456. &::before {
  1457. // content: '';
  1458. position: absolute;
  1459. left: 0;
  1460. top: 50%;
  1461. transform: translateY(-50%);
  1462. width: 8rpx;
  1463. height: 32rpx;
  1464. background-color: #007aff;
  1465. border-radius: 4rpx;
  1466. }
  1467. &-text {
  1468. font-size: 32rpx;
  1469. font-weight: bold;
  1470. color: #333333;
  1471. }
  1472. }
  1473. .info-card {
  1474. background-color: #ffffff;
  1475. border-radius: 16rpx;
  1476. padding: 30rpx;
  1477. .info-item {
  1478. flex-direction: row;
  1479. padding: 20rpx 0;
  1480. border-bottom: 1rpx solid #f0f0f0;
  1481. &:last-child {
  1482. border-bottom: none;
  1483. }
  1484. &.full-width {
  1485. flex-direction: column;
  1486. .info-label {
  1487. margin-bottom: 12rpx;
  1488. }
  1489. .info-value {
  1490. line-height: 44rpx;
  1491. }
  1492. }
  1493. .info-label {
  1494. width: 240rpx;
  1495. font-size: 28rpx;
  1496. color: #666666;
  1497. white-space: nowrap;
  1498. }
  1499. .info-value {
  1500. flex: 1;
  1501. font-size: 28rpx;
  1502. color: #333333;
  1503. text-align: left;
  1504. &.highlight {
  1505. color: #007aff;
  1506. font-weight: bold;
  1507. }
  1508. &.input {
  1509. text-align: left;
  1510. border: 1rpx solid #e0e0e0;
  1511. border-radius: 8rpx;
  1512. padding: 10rpx;
  1513. }
  1514. .input-with-clear {
  1515. position: relative;
  1516. display: flex;
  1517. align-items: center;
  1518. }
  1519. .select-clear {
  1520. position: absolute;
  1521. right: 20rpx;
  1522. color: #ccc;
  1523. text-align: center;
  1524. font-size: 40rpx;
  1525. font-weight: bold;
  1526. z-index: 1;
  1527. }
  1528. .input-with-select {
  1529. position: relative;
  1530. display: flex;
  1531. align-items: center;
  1532. }
  1533. .select-mis-btn {
  1534. line-height: 50rpx;
  1535. text-align: center;
  1536. position: absolute;
  1537. right: 0rpx;
  1538. padding: 6rpx 12rpx;
  1539. background-color: #007aff;
  1540. color: #fff;
  1541. border-radius: 6rpx;
  1542. font-size: 26rpx;
  1543. z-index: 1;
  1544. max-width: 100rpx;
  1545. overflow: hidden;
  1546. text-overflow: ellipsis;
  1547. white-space: nowrap;
  1548. }
  1549. }
  1550. }
  1551. .flow-item {
  1552. padding: 20rpx 0;
  1553. border-bottom: 1rpx solid #f0f0f0;
  1554. &:last-child {
  1555. border-bottom: none;
  1556. }
  1557. .flow-header {
  1558. flex-direction: row;
  1559. justify-content: space-between;
  1560. margin-bottom: 10rpx;
  1561. .flow-operator {
  1562. font-size: 28rpx;
  1563. color: #333333;
  1564. font-weight: bold;
  1565. }
  1566. .flow-time {
  1567. font-size: 24rpx;
  1568. color: #999999;
  1569. }
  1570. }
  1571. .flow-content {
  1572. flex-direction: column;
  1573. .flow-action {
  1574. font-size: 26rpx;
  1575. color: #666666;
  1576. margin-bottom: 8rpx;
  1577. }
  1578. .flow-remark {
  1579. font-size: 24rpx;
  1580. color: #999999;
  1581. background-color: #f5f5f5;
  1582. padding: 10rpx;
  1583. border-radius: 8rpx;
  1584. }
  1585. }
  1586. }
  1587. .no-data {
  1588. text-align: center;
  1589. padding: 40rpx 0;
  1590. font-size: 28rpx;
  1591. color: #999999;
  1592. }
  1593. }
  1594. }
  1595. .accept-button-container {
  1596. padding: 30rpx 30rpx 50rpx;
  1597. background-color: #ffffff;
  1598. .accept-button {
  1599. width: 100%;
  1600. height: 80rpx;
  1601. background-color: #007aff;
  1602. color: #ffffff;
  1603. font-size: 32rpx;
  1604. border-radius: 16rpx;
  1605. border: none;
  1606. &:active {
  1607. background-color: #0062cc;
  1608. }
  1609. }
  1610. }
  1611. .loading-mask {
  1612. position: absolute;
  1613. top: 0;
  1614. left: 0;
  1615. right: 0;
  1616. bottom: 0;
  1617. justify-content: center;
  1618. align-items: center;
  1619. background-color: rgba(0, 0, 0, 0.3);
  1620. .loading-text {
  1621. padding: 30rpx 60rpx;
  1622. background-color: rgba(0, 0, 0, 0.7);
  1623. color: #ffffff;
  1624. font-size: 28rpx;
  1625. border-radius: 12rpx;
  1626. }
  1627. }
  1628. .picker-modal {
  1629. position: fixed;
  1630. top: 0;
  1631. left: 0;
  1632. right: 0;
  1633. bottom: 0;
  1634. z-index: 1000;
  1635. }
  1636. .modal-mask {
  1637. position: absolute;
  1638. top: 0;
  1639. left: 0;
  1640. right: 0;
  1641. bottom: 0;
  1642. background-color: rgba(0, 0, 0, 0.5);
  1643. }
  1644. .modal-content {
  1645. position: absolute;
  1646. bottom: 0;
  1647. left: 0;
  1648. right: 0;
  1649. background-color: #ffffff;
  1650. border-top-left-radius: 16rpx;
  1651. border-top-right-radius: 16rpx;
  1652. min-height: 700rpx;
  1653. }
  1654. .modal-header {
  1655. flex-direction: row;
  1656. justify-content: space-between;
  1657. align-items: center;
  1658. padding: 30rpx;
  1659. border-bottom: 1rpx solid #f0f0f0;
  1660. }
  1661. .modal-title {
  1662. font-size: 32rpx;
  1663. font-weight: bold;
  1664. color: #333333;
  1665. }
  1666. .modal-close {
  1667. font-size: 28rpx;
  1668. color: #007aff;
  1669. }
  1670. .modal-body {
  1671. max-height: 800rpx;
  1672. min-height: 800rpx;
  1673. }
  1674. .picker-option {
  1675. flex-direction: row;
  1676. justify-content: space-between;
  1677. align-items: center;
  1678. padding: 24rpx 30rpx;
  1679. border-bottom: 1rpx solid #f0f0f0;
  1680. }
  1681. .picker-option.selected {
  1682. background-color: #f8f9fa;
  1683. }
  1684. .picker-option:last-child {
  1685. border-bottom: none;
  1686. }
  1687. .info-row {
  1688. flex-direction: row;
  1689. justify-content: space-between;
  1690. align-items: center;
  1691. }
  1692. .option-text {
  1693. font-size: 28rpx;
  1694. color: #333333;
  1695. margin-bottom: 10rpx;
  1696. }
  1697. .option-text:last-child {
  1698. margin-bottom: 0;
  1699. }
  1700. .option-check {
  1701. font-size: 28rpx;
  1702. color: #007aff;
  1703. }
  1704. .empty-tip {
  1705. justify-content: space-between;
  1706. padding: 24rpx 30rpx;
  1707. display: flex;
  1708. align-items: center;
  1709. justify-content: center;
  1710. color: #999;
  1711. }
  1712. .mis-list {
  1713. flex: 1;
  1714. height: 100%;
  1715. display: flex;
  1716. flex-direction: column;
  1717. }
  1718. .form-item {
  1719. flex-direction: row;
  1720. padding: 20rpx 0;
  1721. border-bottom: 1rpx solid #f0f0f0;
  1722. }
  1723. .reject-reason-textarea {
  1724. width: 100%;
  1725. min-height: 100rpx;
  1726. line-height: 1.5;
  1727. }
  1728. .input-field {
  1729. width: 100%;
  1730. height: 60rpx;
  1731. padding: 0 20rpx;
  1732. border: 1rpx solid #e0e0e0;
  1733. border-radius: 8rpx;
  1734. font-size: 28rpx;
  1735. background-color: #f8f9fa;
  1736. }
  1737. .select-mis-btn {
  1738. width: 150rpx;
  1739. height: 60rpx;
  1740. margin-left: 10rpx;
  1741. background-color: #007aff;
  1742. color: #fff;
  1743. border: none;
  1744. border-radius: 8rpx;
  1745. font-size: 24rpx;
  1746. }
  1747. .textarea-field {
  1748. width: 100%;
  1749. min-height: 120rpx;
  1750. padding: 20rpx;
  1751. border: 1rpx solid #e0e0e0;
  1752. border-radius: 8rpx;
  1753. font-size: 28rpx;
  1754. background-color: #f8f9fa;
  1755. }
  1756. .radio-label {
  1757. display: flex;
  1758. align-items: center;
  1759. margin-right: 30rpx;
  1760. }
  1761. .quick-select-dropdown {
  1762. position: absolute;
  1763. top: 100%;
  1764. left: 0;
  1765. right: 0;
  1766. background: #fff;
  1767. border: 1rpx solid #ddd;
  1768. border-top: none;
  1769. z-index: 1000;
  1770. max-height: 300rpx;
  1771. }
  1772. .quick-select-item {
  1773. padding: 20rpx;
  1774. border-bottom: 1rpx solid #eee;
  1775. }
  1776. .mis-no {
  1777. font-size: 28rpx;
  1778. color: #333;
  1779. }
  1780. .search-bar {
  1781. padding: 20rpx 30rpx;
  1782. background-color: #d7eafe;
  1783. }
  1784. .search-box {
  1785. flex-direction: row;
  1786. align-items: center;
  1787. height: 72rpx;
  1788. padding: 0 24rpx;
  1789. background-color: #f5f5f5;
  1790. border-radius: 36rpx;
  1791. .search-icon {
  1792. width: 32rpx;
  1793. height: 32rpx;
  1794. margin-right: 12rpx;
  1795. }
  1796. .search-input {
  1797. flex: 1;
  1798. font-size: 28rpx;
  1799. color: #333333;
  1800. }
  1801. .clear-icon {
  1802. margin-left: 12rpx;
  1803. font-size: 28rpx;
  1804. color: #999999;
  1805. }
  1806. }
  1807. .mis-list {
  1808. flex: 1;
  1809. height: 100%;
  1810. display: flex;
  1811. flex-direction: column;
  1812. }
  1813. .select-users-count {
  1814. margin-left: 10rpx;
  1815. font-size: 24rpx;
  1816. color: #666;
  1817. }
  1818. </style>