wxFinalize.uvue 74 KB

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