index.vue 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439
  1. <template>
  2. <el-dialog v-model="visible" :title="isView ? '查看' : isCollect ? '领取' : '归还'" width="75%" :close-on-click-modal="false" @close="onCancel">
  3. <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
  4. <el-tab-pane label="借用单信息" name="first">
  5. <el-form :model="form" label-width="110px" label-position="top" :rules="rules" ref="formRef">
  6. <el-row :gutter="16">
  7. <el-col :span="8">
  8. <el-form-item label="借用单编号" prop="application_no">
  9. <el-input v-model="form.application_no" placeholder="自动生成" disabled />
  10. </el-form-item>
  11. </el-col>
  12. <el-col :span="8">
  13. <el-form-item label="借用人类型" prop="borrower_type">
  14. <el-select v-model="form.borrower_type" :placeholder="isView?'':'请选择借用人角色'" :disabled="true">
  15. <el-option label="管理员用户" :value=0 />
  16. <el-option label="应用用户" :value=1 />
  17. <el-option label="外部用户" :value=2 />
  18. </el-select>
  19. </el-form-item>
  20. </el-col>
  21. <el-col :span="8" >
  22. <!-- <el-form-item label="借用人" prop="external_borrower_name" v-if="form.borrower_type !== 2">
  23. <el-input v-model="form.borrower_info.name" placeholder="请输入借用人" :disabled="true" />
  24. </el-form-item> -->
  25. <el-form-item label="借用人" prop="admin_borrower" v-if="form.borrower_type == 0">
  26. <el-select v-model="form.admin_borrower" :placeholder="isView?'':'请选择借用人'" :disabled="true" >
  27. <el-option :label="adminList.name" :value="adminList.id" />
  28. </el-select>
  29. </el-form-item>
  30. <el-form-item label="借用人" prop="app_user_borrower" v-if="form.borrower_type == 1">
  31. <el-select v-model="form.app_user_borrower.id" :placeholder="isView?'':'请选择借用人'" :disabled="true" >
  32. <el-option v-for="item in allUserList" :key="item.id" :label="item.name" :value="item.id" />
  33. </el-select>
  34. </el-form-item>
  35. <el-form-item label="借用人" prop="external_borrower_name" v-if="form.borrower_type == 2">
  36. <el-input v-model="form.external_borrower_name" :placeholder="isView?'':'请输入借用人'" :disabled="true" />
  37. </el-form-item>
  38. </el-col>
  39. <el-col :span="8" v-if="form.borrower_type !== 2">
  40. <el-form-item label="学号/工号" prop="user_code" >
  41. <!-- {{ form.borrower_info.user_code }} -->
  42. <el-input v-model="form.app_user_borrower.user_code" :placeholder="isView?'':'请输入学号/工号'" :disabled="true"/>
  43. </el-form-item>
  44. </el-col>
  45. <el-col :span="8">
  46. <el-form-item label="联系电话" v-if="form.borrower_type !== 2">
  47. <el-input v-model="form.app_user_borrower.mobile" :placeholder="isView?'':'请输入联系电话'" :disabled="true" />
  48. </el-form-item>
  49. <el-form-item label="联系电话" v-if="form.borrower_type == 2">
  50. <el-input v-model="form.external_borrower_phone" :placeholder="isView?'':'请输入联系电话'" :disabled="true" />
  51. </el-form-item>
  52. </el-col>
  53. <!-- <el-col :span="8">
  54. <el-form-item label="借用部门" prop="borrower_dept">
  55. <el-input v-model="form.borrower_dept" placeholder="请输入借用部门" :disabled="true" />
  56. </el-form-item>
  57. </el-col> -->
  58. <el-col :span="8">
  59. <el-form-item label="预计归还时间" prop="expected_end_time">
  60. <el-date-picker v-model="form.expected_end_time" type="date"
  61. format="YYYY-MM-DD"
  62. value-format="YYYY-MM-DD"
  63. :disabledDate="disabledDate" placeholder="请选择预计归还时间" style="width: 100%" :disabled="true"/>
  64. </el-form-item>
  65. </el-col>
  66. <el-col :span="4">
  67. <el-form-item label="个人/团队" prop="team_type" >
  68. <el-select v-model="form.team_type" placeholder="请选择" :disabled="true">
  69. <el-option label="个人" :value="0" />
  70. <el-option label="团队" :value="1" />
  71. </el-select>
  72. </el-form-item>
  73. </el-col>
  74. <el-col :span="4" v-show="form.team_type===1">
  75. <el-form-item label="团队人数" prop="team_members_count">
  76. <el-input v-model="form.team_members_count" :placeholder="isView?'':'请输入'" :disabled="true"/>
  77. </el-form-item>
  78. </el-col>
  79. <el-col :span="16" v-show="form.team_type===1">
  80. <el-form-item label="团队其他人员名单" prop="team_members">
  81. <el-input v-model="form.team_members" :placeholder="isView?'':'请输入团队其他人员名单'" :disabled="true"/>
  82. </el-form-item>
  83. </el-col>
  84. <el-col :span="24">
  85. <el-form-item label="借用原因" prop="purpose">
  86. <el-input v-model="form.purpose" type="textarea" :placeholder="isView?'':'请输入借用原因'" :disabled="true" />
  87. </el-form-item>
  88. </el-col>
  89. <el-col :span="4">
  90. <!-- ||form.approver_info.name -->
  91. <el-form-item label="审批人" prop="applicant_remark_type">
  92. <el-select v-model="form.approver_info.name" placeholder="请选择" :disabled="true" >
  93. <el-option label="管理员" value="1"/>
  94. </el-select>
  95. </el-form-item>
  96. </el-col>
  97. <el-col :span="20">
  98. <el-form-item label="备注" prop="applicant_remark">
  99. <el-input v-model="form.applicant_remark" :placeholder="isView?'':'请输入备注'" :disabled="true"/>
  100. </el-form-item>
  101. </el-col>
  102. </el-row>
  103. <div style="margin: 16px 0 8px 0; font-weight: bold"> {{ isView ? '借用列表' : (isCollect ? '借用列表' : '归还列表') }}</div>
  104. <el-table :data="form.items.filter(item => item.device === null)" border style="width: 50%; margin-bottom: 12px">
  105. <el-table-column type="index" label="序号" width="60" />
  106. <el-table-column prop="device_category_name" label="设备分类" />
  107. <el-table-column :label="isView ? '数量' : (isCollect ? '借用数量' : '归还数量')">
  108. <template #default="{ row }">
  109. <el-input v-model="row.quantity" type="number" :min="1" prop="quantity" size="small" :disabled="true" />
  110. </template>
  111. </el-table-column>
  112. <el-table-column label="操作" width="80">
  113. <template #default="{ $index }">
  114. <el-button type="text" style="color: red" @click="removeItem($index)" :disabled="true">移除</el-button>
  115. </template>
  116. </el-table-column>
  117. </el-table>
  118. <el-button type="primary" @click="addItem" v-if="!isView" :disabled="true">选择设备分类</el-button>
  119. <div style="margin: 16px 0 8px 0; font-weight: bold">配件信息</div>
  120. <el-input v-model="form.accessories" :placeholder="isView?'':'请输入配件信息'" :disabled="true"/>
  121. <div style="margin: 16px 0 8px 0; font-weight: bold" >附件</div>
  122. <!-- <el-upload
  123. action="#"
  124. drag
  125. :list-type="'picture'"
  126. accept=".bmp,.gif,.jpeg,.jpg,.png,‌.webp,.svg"
  127. multiple
  128. :show-file-list="true"
  129. :file-list="attachmentFileList"
  130. :on-success="handleUploadSuccess"
  131. :on-error="handleUploadError"
  132. :disabled="true"
  133. >
  134. <i class="el-icon-upload"></i>
  135. <div class="el-upload__text">将文件拖到此处,或 <em>点击上传</em></div>
  136. </el-upload> -->
  137. <el-upload
  138. :disabled="true"
  139. action="#"
  140. list-type="picture-card"
  141. :on-preview="handlePictureCardPreview"
  142. :on-remove="handleRemoves"
  143. :file-list="attachmentFileList"
  144. >
  145. <el-icon><Plus /></el-icon>
  146. </el-upload>
  147. </el-form>
  148. <!-- 审批信息流程展示 -->
  149. <!-- <div v-if="isView" style="margin-top: 24px; padding: 0 12px">
  150. <div style="font-weight: bold; margin-bottom: 12px">审批流程进度</div>
  151. <el-steps :active="activeStep" align-center>
  152. <el-step
  153. title="创建申请"
  154. :description="`创建时间:${ timeline.create }`"
  155. icon="CircleCheck"
  156. :status="activeStep >= 1 ? 'finish' : 'wait'"
  157. />
  158. <el-step
  159. title="待审批"
  160. description="待审批人审批"
  161. icon="CircleCheck"
  162. :status="activeStep >= 2 ? 'finish' : 'wait'"
  163. />
  164. <el-step
  165. title="审批通过"
  166. :description="`审批人:${timeline.approver},时间:${timeline.approve}`"
  167. icon="CircleCheck"
  168. :status="activeStep >= 3 ? 'finish' : 'wait'"
  169. />
  170. <el-step
  171. title="流程完成"
  172. description="流程已结束"
  173. icon="CircleCheck"
  174. :status="activeStep >= 4 ? 'finish' : 'wait'"
  175. />
  176. </el-steps>
  177. </div> -->
  178. <div v-if="isView" style="margin-top: 24px; padding: 0 12px">
  179. <div style="font-weight: bold; margin-bottom: 12px">审批流程进度</div>
  180. <el-steps :active="steps.length" align-center>
  181. <el-step
  182. v-for="(step, index) in steps"
  183. :key="index"
  184. :title="step.title"
  185. :description="step.description"
  186. icon="CircleCheck"
  187. :status="step.status"
  188. />
  189. </el-steps>
  190. </div>
  191. </el-tab-pane>
  192. <el-tab-pane v-if="isCollect && !shouldHideDeviceList" label="设备清单" name="second">
  193. <el-form :model="form" label-width="110px">
  194. <el-row :gutter="16">
  195. <el-col :span="24">
  196. <div style="margin: 8px 0">
  197. <el-input v-model="searchText" placeholder="请扫描设备条码添加" style="width: 300px" @keyup.enter="handleScanSearch" />
  198. <el-button type="primary" style="margin-left: 16px" @click="handleAdd">手动借用</el-button>
  199. </div>
  200. <el-table :data="deviceList" border style="width: 100%">
  201. <el-table-column type="index" label="序号" width="60" align="center" />
  202. <el-table-column prop="device_code" label="设备编号" align="center" />
  203. <el-table-column prop="device_type" label="设备分类" align="center" />
  204. <el-table-column prop="device_name" label="设备名称" align="center" />
  205. <el-table-column prop="borrow_count" label="借用数量" align="center" width="150">
  206. <template #default="{ row }">
  207. <el-input-number v-model="row.borrow_count" :disabled="isView" :min="1" :max="row.available_quantity" size="small" />
  208. </template>
  209. </el-table-column>
  210. <el-table-column prop="brand" label="品牌" align="center" />
  211. <el-table-column prop="device_specification" label="规格型号" align="center" />
  212. <!-- <el-table-column prop="warehouse" label="存放仓库" align="center" /> -->
  213. <el-table-column label="操作" width="80" align="center">
  214. <template #default="{ $index }">
  215. <el-button type="text" style="color: red" :disabled="isView" @click="handleRemove($index)">移除</el-button>
  216. </template>
  217. </el-table-column>
  218. </el-table>
  219. </el-col>
  220. </el-row>
  221. <el-row :gutter="16" style="margin-top: 16px">
  222. <el-col :span="24">
  223. <div style="font-weight: bold; margin-bottom: 8px">配件信息</div>
  224. <el-input v-model="form.accessories" :disabled="isView" type="textarea" :placeholder="isView?'':'请输入配件信息'" />
  225. </el-col>
  226. <el-col :span="24" style="margin-top: 16px">
  227. <div style="font-weight: bold; margin-bottom: 8px">备注</div>
  228. <el-input v-model="remark" type="textarea" :disabled="isView" :placeholder="isView?'':'请输入备注'" />
  229. </el-col>
  230. </el-row>
  231. <el-row :gutter="16" style="margin-top: 16px">
  232. <el-col :span="24">
  233. <div style="font-weight: bold; margin-bottom: 8px">异常记录 <span>{{
  234. abnormalList?.length ?
  235. abnormalList.reduce((sum, item) => sum + (Number(item?.condition) || 0), 0)
  236. : 0
  237. }}</span> 次</div>
  238. <el-table v-if="abnormalList.length > 0" :data="abnormalList" border style="width: 100%">
  239. <!-- <el-table-column type="index" label="序号" width="60" align="center" />
  240. <el-table-column prop="application_no" label="借用单编号" align="center" /> -->
  241. <el-table-column prop="operator_name" label="借用人" align="center" />
  242. <el-table-column prop="user_code" label="学号" align="center" />
  243. <el-table-column prop="emergency_phone" label="借用人电话" align="center" />
  244. <!-- <el-table-column prop="time" label="发生时间" align="center" /> -->
  245. <el-table-column prop="type" label="异常" align="center">
  246. <template #default="{ row }">
  247. {{ row.type }}
  248. </template>
  249. </el-table-column>
  250. <el-table-column prop="condition" label="数量" align="center">
  251. <template #default="{ row }">
  252. {{ row.condition }}
  253. </template>
  254. </el-table-column>
  255. </el-table>
  256. </el-col>
  257. </el-row>
  258. </el-form>
  259. </el-tab-pane>
  260. <el-tab-pane v-else-if="!shouldHideDeviceList" label="设备清单" name="third">
  261. <el-form :model="form" label-width="110px">
  262. <el-row :gutter="16">
  263. <el-col :span="24">
  264. <div style="margin: 8px 0" v-if="!isView" >
  265. <el-input :disabled="isView" v-model="returnSearchText" placeholder="请扫描设备条码添加" style="width: 300px" @keyup.enter="handleReturnScanSearch" />
  266. <el-button type="primary" style="margin-left: 16px" @click="handleAdd" :disabled="isView">手动归还</el-button>
  267. </div>
  268. <el-table :data="returnDeviceList" border style="width: 100%" >
  269. <!-- <el-table-column type="selection" width="55" align="center" />@selection-change="handleSelectionChange" -->
  270. <el-table-column type="index" label="序号" width="60" align="center" />
  271. <el-table-column prop="device_code" label="设备编号" align="center" />
  272. <!-- <el-table-column prop="device_type" label="设备分类" align="center" /> -->
  273. <el-table-column prop="device_name" label="设备名称" align="center" />
  274. <el-table-column prop="borrow_count" label="借用数量" align="center" width="150">
  275. <template #default="{ row }">
  276. <el-input-number v-model="row.borrow_count" :min="1" :max="row.available_quantity" size="small" :disabled="true" />
  277. </template>
  278. </el-table-column>
  279. <el-table-column prop="brand" label="品牌" align="center" />
  280. <el-table-column prop="model" label="规格型号" align="center" />
  281. <!-- <el-table-column prop="warehouse" label="存放仓库" align="center" /> -->
  282. <el-table-column prop="status" label="状态" align="center">
  283. <template #default="{ row }">
  284. {{ row.return_status.label }}
  285. <!-- <el-tag v-if="!isView" :type="row.status === '已归还' ? 'success' : 'warning'">{{ row.status }}</el-tag>
  286. <el-tag v-else-if="['已领取', '已归还'].includes(form.status_label)">{{ form.status_label }}</el-tag> -->
  287. <!-- <span v-else>{{ form.status_label }}</span> -->
  288. </template>
  289. </el-table-column>
  290. <el-table-column prop="status" label="归还时间" align="center">
  291. <template #default="{ row }">
  292. {{ row.return_time ? dayjs(row.return_time).format('YYYY-MM-DD HH:mm:ss') : (row.return_status?.label === '已归还' ? dayjs().format('YYYY-MM-DD HH:mm:ss') : '') }}
  293. </template>
  294. </el-table-column>
  295. <el-table-column label="操作" width="150" align="center">
  296. <template #default="{ $index,row }">
  297. <el-button :disabled="isView" type="text" style="color: red"
  298. v-if="!isView && (!returnDeviceList[$index]?.condition || returnDeviceList[$index]?.photos?.length==0)&& row.problem_records?.length ==0"
  299. @click="handleAbnormal($index)">异常操作</el-button>
  300. <el-button
  301. type="text"
  302. style="color: #409EFF"
  303. @click="handleViewAbnormal($index)"
  304. v-if="row.problem_records?.length || (returnDeviceList[$index]?.condition || returnDeviceList[$index]?.photos?.length)"
  305. >查看异常</el-button>
  306. <!-- <el-button type="text" v-if="!isView" :disabled="isView" style="color: red" @click="handleReturnRemove($index)">移除</el-button> -->
  307. </template>
  308. </el-table-column>
  309. </el-table>
  310. </el-col>
  311. </el-row>
  312. <el-row :gutter="16" style="margin-top: 16px">
  313. <el-col :span="16">
  314. <div style="font-weight: bold; margin-bottom: 8px">配件信息</div>
  315. <el-input v-model="form.accessories" :disabled="isView" :placeholder="isView?'':'请输入配件信息'" />
  316. </el-col>
  317. <el-col :span="8">
  318. <div style="font-weight: bold; margin-bottom: 8px">当前状态</div>
  319. <el-input v-model="returnStatus" v-if="!isView" :disabled="true" placeholder="" style="width: 150px" />
  320. <el-input value="已归还" v-else :disabled="true" placeholder="" style="width: 150px" />
  321. <el-button type="primary" v-if="!isView" style="margin-left: 16px" @click="clickReturn"
  322. :disabled="returnStatus=='已归还'||isView">归还</el-button>
  323. </el-col>
  324. <el-col :span="24" style="margin-top: 16px">
  325. <div style="font-weight: bold; margin-bottom: 8px">备注</div>
  326. <el-input v-model="returnRemark" type="textarea" :disabled="isView" :placeholder="isView?'':'请输入备注'" />
  327. </el-col>
  328. </el-row>
  329. <el-row :gutter="16" style="margin-top: 16px">
  330. <el-col :span="24">
  331. <div style="font-weight: bold; margin-bottom: 8px">异常记录 <span>{{
  332. returnAbnormalList?.length ?
  333. returnAbnormalList.reduce((sum, item) => sum + (Number(item?.condition) || 0), 0)
  334. : 0
  335. }}</span> 次</div>
  336. <el-table v-if="returnAbnormalList?.length > 0" :data="returnAbnormalList" border style="width: 100%">
  337. <!-- <el-table-column type="index" label="序号" width="60" align="center" />
  338. <el-table-column prop="application_no" label="借用单编号" align="center" /> -->
  339. <el-table-column prop="operator_name" label="借用人" align="center" />
  340. <el-table-column prop="user_code" label="学号" align="center" />
  341. <el-table-column prop="emergency_phone" label="借用人电话" align="center" />
  342. <!-- <el-table-column prop="time" label="发生时间" align="center" /> -->
  343. <el-table-column prop="type" label="异常" align="center">
  344. <template #default="{ row }">
  345. {{ row.type }}
  346. </template>
  347. </el-table-column>
  348. <el-table-column prop="condition" label="数量" align="center">
  349. <template #default="{ row }">
  350. {{ row.condition }}
  351. </template>
  352. </el-table-column>
  353. </el-table>
  354. </el-col>
  355. </el-row>
  356. </el-form>
  357. </el-tab-pane>
  358. </el-tabs>
  359. <template #footer>
  360. <el-button @click="onCancel">关闭</el-button>
  361. <el-button type="primary" @click="onSubmit" v-if="!isView"> {{ isCollect ? '提交' : '确认' }}</el-button>
  362. </template>
  363. </el-dialog>
  364. <SelectDeviceDialog
  365. v-model:visible="showSelectDeviceDialog"
  366. @confirm="onDeviceSelected"
  367. :device-ids="returnDeviceList.filter(item => item.return_status.label !== '已归还').map(item => item.device_no)"
  368. />
  369. <SettlementDialog v-model:visible="showSettlementDialog" :settlement-data="settlementData" />
  370. <AbnormalDialog v-model:visible="showAbnormalDialog" @confirm="onAbnormalConfirm" />
  371. <ViewAbnormalDialog
  372. v-model:visible="showViewAbnormalDialog"
  373. :abnormal-data="currentAbnormalData"
  374. />
  375. <!-- 图片预览对话框 -->
  376. <el-dialog v-model="dialogVisible" title="图片预览" width="auto" center>
  377. <img width="100%" :src="dialogImageUrl" alt="Preview Image" style="max-width: 100%; max-height: 80vh;" />
  378. </el-dialog>
  379. </template>
  380. <script setup lang="ts">
  381. import { ref, watch, defineProps, defineEmits, onMounted, computed, nextTick } from 'vue';
  382. import SelectDeviceDialog from './SelectDeviceDialog/index.vue';
  383. import SettlementDialog from './SettlementDialog.vue';
  384. import AbnormalDialog from './AbnormalDialog/index.vue';
  385. import ViewAbnormalDialog from './ViewAbnormalDialog/index.vue';
  386. import dayjs from 'dayjs';
  387. import { ElMessage } from 'element-plus';
  388. import * as api from '../../api';
  389. import * as deviceApi from '../../../device/api';
  390. import { CollectEquipment, ReturnEquipment } from '../../api';
  391. import type { TabsPaneContext } from 'element-plus';
  392. import { getUserInfo } from '../../../login/api';
  393. import { request } from '/@/utils/service';
  394. // 禁止选择今天之前的日期
  395. const disabledDate = (time: Date) => {
  396. // 获取今天的开始时间(准确到日)
  397. const today = new Date()
  398. today.setHours(0, 0, 0, 0)
  399. // 返回true表示禁用,禁用今天之前的所有日期
  400. return time.getTime() < today.getTime()
  401. }
  402. /* 获取管理员列表 */
  403. const adminList = ref([]);
  404. async function getAdminList() {
  405. const res = await getUserInfo();
  406. adminList.value = res.data;
  407. console.log("adminList.value:::",adminList.value)
  408. }
  409. /* */
  410. /* 获取所有用户 */
  411. const allUserList = ref([]);
  412. async function getAllUserList() {
  413. const res = await api.GetAllUser({page:1,limit:60});
  414. allUserList.value = res.data;
  415. // 设置默认选择第一个用户
  416. if (allUserList.value.length > 0) {
  417. form.value.app_user_borrower = allUserList.value[0].id;
  418. }
  419. console.log("allUserList.value:::",allUserList.value)
  420. }
  421. interface ApiResponse {
  422. code: number;
  423. data?: any;
  424. message?: string;
  425. }
  426. // 定义接口
  427. interface Device {
  428. id: string | number;
  429. category_name: string;
  430. name?: string;
  431. brand?: string;
  432. model?: string;
  433. warehouse: string;
  434. device_specification?: string;
  435. device_name?: string;
  436. }
  437. interface DeviceListItem {
  438. device_no: string | number;
  439. device_type: string;
  440. device_name?: string; // 改为可选
  441. borrow_count: number;
  442. brand: string;
  443. model?: string; // 改为可选
  444. warehouse: string;
  445. status?: string;
  446. condition?: string;
  447. photos?: Array<{
  448. filename: string;
  449. data: string;
  450. }>;
  451. device_specification?: string;
  452. available_quantity?:number;
  453. }
  454. interface FormItem {
  455. device: string | number;
  456. remark: string;
  457. quantity: number;
  458. location: string;
  459. device_name?: string;
  460. device_specification?: string;
  461. condition?: string;
  462. photos?: Array<{
  463. filename: string;
  464. data: string;
  465. }>;
  466. }
  467. interface FormData {
  468. application_no: string;
  469. emergency_contact: string;
  470. emergency_phone: string;
  471. app_user_borrower: number;
  472. team_type: number;
  473. team_members_count: number;
  474. team_members: string;
  475. expected_start_time: string;
  476. expected_end_time: string;
  477. borrower_dept: string;
  478. purpose: string;
  479. applicant_remark_type: string;
  480. applicant_remark: string;
  481. items: FormItem[];
  482. accessory_info: string;
  483. borrow_type: number;
  484. return_location: string;
  485. status_label?: string;
  486. id?: number;
  487. records?: any[];
  488. borrower_info?: {
  489. user_code: string;
  490. };
  491. borrower_damage_count?: number;
  492. accessories?: string;
  493. }
  494. interface AbnormalData {
  495. damage_type:number;
  496. condition: string;
  497. photos: Array<{
  498. filename: string;
  499. data: string;
  500. }>;
  501. }
  502. interface AbnormalRecord {
  503. application_no: string;
  504. operator_name: string;
  505. user_code: string;
  506. emergency_phone: string;
  507. type: '损坏' | '逾期';
  508. condition: number;
  509. create_time: string;
  510. }
  511. const now = dayjs().format('YYYY-MM-DD HH:mm:ss');
  512. const props = defineProps<{ visible: boolean; modelValue?: any }>();
  513. const isEdit = computed(() => props.modelValue?.mode === 'edit');
  514. const isView = computed(() => props.modelValue?.mode === 'view');
  515. const isAdd = computed(() => props.modelValue?.mode === 'add');
  516. const isCollect = computed(() => props.modelValue?.mode === 'collect');
  517. const isReturn = computed(() => props.modelValue?.mode === 'return');
  518. // 计算是否应该隐藏设备清单标签页
  519. const shouldHideDeviceList = computed(() => {
  520. if (!isView.value) return false;
  521. // 检查状态是否已批准或待审批(通过数值或标签判断)
  522. const status = (form.value as any).status;
  523. const statusLabel = (form.value as any).status_label;
  524. // 已批准状态:status >= 2 或状态标签为已批准相关
  525. const isApproved = status >= 2 ||
  526. statusLabel === '已批准' ;
  527. // 待审批状态:status < 2 或状态标签为待审批
  528. const isPendingApproval = status < 2 ||
  529. statusLabel === '待审批';
  530. // 只有在已批准或待审批状态时才隐藏设备清单
  531. return isApproved || isPendingApproval;
  532. });
  533. const emit = defineEmits(['update:visible', 'update:modelValue', 'submit']);
  534. const visible = ref(props.visible);
  535. const formRef = ref();
  536. // 图片预览相关
  537. const dialogImageUrl = ref('');
  538. const dialogVisible = ref(false);
  539. const timeline = ref({
  540. create: '',
  541. approve: '',
  542. approver: ''
  543. });
  544. // const activeStep = ref(1);
  545. const activeName = ref('first');
  546. const returnStatus = ref('未归还'); // 添加独立的状态变量
  547. // 借用列表相关数据
  548. const searchText = ref('');
  549. const deviceList = ref<DeviceListItem[]>([]);
  550. const accessoryInfo = ref('');
  551. const remark = ref('');
  552. // 归还设备相关数据
  553. const returnSearchText = ref('');
  554. const returnDeviceList = ref<DeviceListItem[]>([]);
  555. const returnAccessoryInfo = ref('');
  556. const returnRemark = ref('');
  557. const selectedDevices = ref<DeviceListItem[]>([]); // 添加选中设备列表
  558. //审批步骤
  559. const steps = ref<any[]>([]);
  560. // 仓库映射(id -> name)
  561. const warehouseMap = ref<Record<string, string>>({});
  562. async function loadWarehouses() {
  563. try {
  564. const res = await request({
  565. url: '/api/system/warehouse/',
  566. method: 'get',
  567. params: { page: 1, limit: 999 }
  568. });
  569. if (res?.code === 2000 && Array.isArray(res.data)) {
  570. warehouseMap.value = Object.fromEntries(
  571. res.data.map((w: any) => [String(w.id), w.name])
  572. );
  573. }
  574. } catch (e) {
  575. /* empty */
  576. }
  577. }
  578. function getWarehouseName(id: any) {
  579. const key = id != null ? String(id) : '';
  580. return warehouseMap.value[key] ?? id;
  581. }
  582. // 添加结算单相关的状态
  583. const showSettlementDialog = ref(false);
  584. const settlementData = ref<any>(null);
  585. // 添加异常操作相关的状态
  586. const showAbnormalDialog = ref(false);
  587. const currentAbnormalIndex = ref(-1);
  588. // 添加查看异常相关的状态
  589. const showViewAbnormalDialog = ref(false);
  590. const currentAbnormalData = ref<AbnormalData>({
  591. damage_type:0,
  592. condition: '',
  593. photos: []
  594. });
  595. //审批
  596. function buildSteps(data: any) {
  597. steps.value = [
  598. {
  599. title: '创建申请',
  600. description: `创建时间:${data.create_datetime}`,
  601. status: 'finish'
  602. },
  603. {
  604. title: '待审批',
  605. description: `待审批人:${data.approver_info.name} 审批`,
  606. status: data.status >= 2 ? 'finish' : 'process'
  607. }
  608. ];
  609. const approverinfo = data.approver_info ?? data.admin_approver_info
  610. if (data.status >= 2&&data.status!=3 && data.borrow_type!=1) {
  611. steps.value.push({
  612. title: '审批通过',
  613. description: `审批人:${approverinfo.name},时 间:${data.approve_time},领取仓库:${getWarehouseName(data.warehouse)||'暂无仓库'}`,
  614. status: 'finish'
  615. });
  616. } else if (data.status === 3) {
  617. // console.log("data.approver_name:::",data.approver_info.name)
  618. steps.value.push({
  619. title: '审批拒绝',
  620. description: `审批人:${approverinfo.name},拒绝原因: ${data.approve_remark},时间:${data.approve_time}`,
  621. status: 'error'
  622. });
  623. }
  624. if (data.status >= 4 ||data.borrow_type==1) {
  625. steps.value.push({
  626. title: '流程完成',
  627. description: '流程已结束',
  628. status: 'finish'
  629. });
  630. }
  631. }
  632. // 处理异常操作
  633. const handleAbnormal = (index: number) => {
  634. currentAbnormalIndex.value = index;
  635. showAbnormalDialog.value = true;
  636. console.log("showAbnormalDialog:::", showAbnormalDialog.value, "currentAbnormalIndex:::", currentAbnormalIndex.value);
  637. };
  638. // 处理异常操作确认
  639. const onAbnormalConfirm = (data: { condition: string; photos: any[] }) => {
  640. console.log("异常操作确认数据:", data);
  641. if (currentAbnormalIndex.value !== -1) {
  642. const device = returnDeviceList.value[currentAbnormalIndex.value];
  643. device.condition = data.condition;
  644. device.photos = data.photos;
  645. device.damage_type = data.damage_type;
  646. ElMessage.success('异常信息已保存');
  647. }
  648. };
  649. // 处理查看异常
  650. const handleViewAbnormal = (index: number) => {
  651. const device = returnDeviceList.value[index];
  652. console.log("device:::",device);
  653. if (device) {
  654. if(device.problem_records?.length){
  655. currentAbnormalData.value = {
  656. damage_type:device.problem_records?.[0].damage_type ||'',
  657. condition: device.problem_records?.[0].device_condition_after ||'',
  658. photos: device.problem_records?.[0].returned_images.map((item:any)=>item.image_url) ||[]
  659. };
  660. }else{
  661. currentAbnormalData.value = {
  662. damage_type: device.damage_type ||'',
  663. condition: device.condition ||'',
  664. photos: device.photos ||[]
  665. };
  666. }
  667. showViewAbnormalDialog.value = true;
  668. }
  669. };
  670. // 处理扫码搜索
  671. const handleScanSearch = async () => {
  672. if (!searchText.value.trim()) return;
  673. try {
  674. const res = await deviceApi.GetList({
  675. page: 1,
  676. limit: 1,
  677. code: searchText.value.trim(),
  678. status: 1
  679. });
  680. if (res.data && res.data.length > 0) {
  681. const device = res.data[0];
  682. if (device.available_quantity <= 0) {
  683. ElMessage.warning('该设备当前无可用数量');
  684. return;
  685. }
  686. // 检查是否已存在
  687. const existingDevice = deviceList.value.find(d => d.device_no === device.id);
  688. if (existingDevice) {
  689. existingDevice.borrow_count++;
  690. ElMessage.success('设备数量已更新');
  691. } else {
  692. // 添加新设备到列表
  693. deviceList.value.push({
  694. device_no: device.id,
  695. device_code: device.device_no,
  696. device_type: device.category_name,
  697. device_name: device.name || '',
  698. borrow_count: 1,
  699. brand: device.brand || '',
  700. device_specification: device.specification,
  701. model: device.model || '',
  702. warehouse: device.warehouse,
  703. available_quantity:device.available_quantity
  704. });
  705. ElMessage.success('设备已添加到列表');
  706. }
  707. } else {
  708. ElMessage.warning('未找到对应的设备');
  709. }
  710. } catch (error) {
  711. ElMessage.error('搜索设备失败');
  712. } finally {
  713. searchText.value = ''; // 清空搜索框
  714. }
  715. };
  716. // 处理归还扫码搜索
  717. const handleReturnScanSearch = async () => {
  718. if (!returnSearchText.value.trim()) return;
  719. try {
  720. const res = await deviceApi.GetList({
  721. page: 1,
  722. limit: 1,
  723. code: returnSearchText.value.trim(),
  724. status: 1
  725. });
  726. if (res.data && res.data.length > 0) {
  727. const device = res.data[0];
  728. // 检查是否已存在
  729. const existingDeviceIndex = returnDeviceList.value.findIndex(d => d.device_no === device.id);
  730. if (existingDeviceIndex !== -1) {
  731. // 如果设备已存在,更新其状态
  732. returnDeviceList.value[existingDeviceIndex].status = '已归还';
  733. ElMessage.success(`设备 ${device.category_name} 状态已更新为已归还`);
  734. } else {
  735. // 添加新设备到列表
  736. returnDeviceList.value.push({
  737. device_no: device.id,
  738. device_code: device.device_no,
  739. device_type: device.category_name,
  740. device_name: device.name || '',
  741. borrow_count: 1,
  742. brand: device.brand || '',
  743. model: device.model || '',
  744. warehouse: device.warehouse,
  745. return_status:device.return_status,
  746. return_time:device.return_times
  747. });
  748. console.log("returnDeviceList.value:::",returnDeviceList.value);
  749. ElMessage.success('设备已添加到列表');
  750. }
  751. } else {
  752. ElMessage.warning('未找到对应的设备');
  753. }
  754. } catch (error) {
  755. ElMessage.error('搜索设备失败');
  756. } finally {
  757. returnSearchText.value = ''; // 清空搜索框
  758. }
  759. };
  760. // 异常记录列表数据
  761. const abnormalList = ref([
  762. /* {
  763. borrowNo: '202409002',
  764. borrower: '张同学',
  765. time: '2024-09-12 10:21:32',
  766. type: '逾期'
  767. },
  768. {
  769. borrowNo: '202505013',
  770. borrower: '张同学',
  771. time: '2025-05-13 15:12:35',
  772. type: '损坏'
  773. } */
  774. ]);
  775. /* 归还设备异常记录 */
  776. const returnAbnormalList = ref([
  777. ]);
  778. // 附件文件列表
  779. const attachmentFileList = ref<any[]>([]);
  780. // 查看异常记录详情
  781. const handleView = (row: any) => {
  782. console.log('查看异常记录:', row);
  783. // 这里可以添加查看详情的逻辑
  784. };
  785. // 借用列表相关方法
  786. const handleAdd = () => {
  787. showSelectDeviceDialog.value = true;
  788. };
  789. const handleRemove = (index: number) => {
  790. deviceList.value.splice(index, 1);
  791. };
  792. const handleReturnRemove = (index: number) => {
  793. returnDeviceList.value.splice(index, 1);
  794. };
  795. // 处理表格多选变化
  796. const handleSelectionChange = (selection: DeviceListItem[]) => {
  797. selectedDevices.value = selection;
  798. };
  799. // 设备选择处理
  800. function onDeviceSelected(devices: Device[]) {
  801. console.log("devices:::",devices,"activeName:::",activeName.value);
  802. if (activeName.value === 'first') {
  803. form.value.items.push(
  804. ...devices.map((d) => ({
  805. device: d.id,
  806. device_code: d.code,
  807. remark: d.category_name,
  808. quantity: 1,
  809. location: d.warehouse,
  810. device_name: d.name,
  811. device_specification: d.device_specification
  812. }))
  813. );
  814. } else if (activeName.value === 'second') {
  815. deviceList.value.push(
  816. ...devices.map(d => ({
  817. device_no: d.id,
  818. device_code: d.code,
  819. device_type: d.category_name,
  820. device_name: d.name || '',
  821. borrow_count: 1,
  822. brand: d.brand || '',
  823. model: d.model,
  824. device_specification: d.specification,
  825. warehouse: d.warehouse,
  826. available_quantity:d.available_quantity
  827. }))
  828. );
  829. }else if(activeName.value === 'third'){
  830. // 在添加前检查每个设备是否已存在
  831. devices.forEach(d => {
  832. const existingDeviceIndex = returnDeviceList.value.findIndex(item => item.device_no === d.id);
  833. if (existingDeviceIndex !== -1) {
  834. // 如果设备已存在,更新其状态
  835. returnDeviceList.value[existingDeviceIndex].return_status.value = 2;
  836. returnDeviceList.value[existingDeviceIndex].return_status.label = '已归还';
  837. returnDeviceList.value[existingDeviceIndex].is_return = true;
  838. returnDeviceList.value[existingDeviceIndex].return_time = dayjs().format('YYYY-MM-DD HH:mm:ss');
  839. ElMessage.success(`设备 ${d.category_name} 状态已更新为已归还`);
  840. } else {
  841. // 如果设备不存在,添加新记录
  842. /* returnDeviceList.value.push({
  843. device_no: d.id,
  844. device_type: d.category_name,
  845. device_name: d.name || '',
  846. borrow_count: 1,
  847. brand: d.brand || '',
  848. model: d.model || '',
  849. warehouse: d.warehouse,
  850. status: '已归还'
  851. }); */
  852. /* ElMessage.success(`设备 ${d.category_name} 已添加到归还列表`); */
  853. }
  854. });
  855. }
  856. }
  857. // 移除同步相关的 watch
  858. // const handleClick = (tab: TabsPaneContext, event: Event) => {
  859. // if (tab.props.name === 'second') {
  860. // deviceList.value = form.value.items.map(item => ({
  861. // device_no: item.device,
  862. // device_type: item.remark,
  863. // device_name: '',
  864. // borrow_count: item.quantity,
  865. // brand: '',
  866. // model: '',
  867. // warehouse: item.location
  868. // }));
  869. // }
  870. // }
  871. function handleClick(tab: TabsPaneContext) {
  872. console.log('Tab clicked:', tab.props.name);
  873. }
  874. function handleUploadSuccess(response: any, file: any, fileList: any) {
  875. // eslint-disable-next-line no-console
  876. console.log('上传成功:', file.name);
  877. }
  878. function handleUploadError(err: any, file: any, fileList: any) {
  879. // eslint-disable-next-line no-console
  880. console.error('上传失败:', file.name);
  881. }
  882. // 图片预览处理函数
  883. const handlePictureCardPreview = (uploadFile: any) => {
  884. dialogImageUrl.value = uploadFile.url || uploadFile.response?.url || form.value.attachments;
  885. dialogVisible.value = true;
  886. };
  887. // 处理附件删除
  888. const handleRemoves = (file: any, fileList: any) => {
  889. // 由于是禁用状态,这里只是示例,实际不会执行删除
  890. console.log('删除附件:', file.name);
  891. };
  892. const form = ref<FormData>({
  893. borrower_info:{
  894. user_code:'',
  895. mobile:''
  896. },
  897. application_no: '',
  898. emergency_contact: '',
  899. emergency_phone: '',
  900. app_user_borrower: 1,
  901. team_type: 0,
  902. team_members_count: 1,
  903. team_members: '',
  904. expected_start_time:'',
  905. expected_end_time: '',
  906. borrower_dept: '',
  907. purpose: '',
  908. applicant_remark_type: "1",
  909. applicant_remark: '',
  910. items: [],
  911. accessory_info: '',
  912. borrow_type: 2,
  913. return_location:'',
  914. approver_info: {
  915. name: ''
  916. }
  917. });
  918. const showSelectDeviceDialog = ref(false);
  919. const rules = {
  920. emergency_contact: [
  921. { required: true, message: '请输入借用人', trigger: 'change' },
  922. ],
  923. app_user_borrower: [
  924. { required: true, message: '请输入学号或工号', trigger: 'change' },
  925. ],
  926. emergency_phone: [
  927. { required: true, message: '请输入联系电话', trigger: 'change' },
  928. ],
  929. borrower_dept: [
  930. { required: true, message: '请输入借用部门', trigger: 'change' },
  931. ],
  932. expected_end_time: [
  933. { required: true, message: '请输选择归还时间', trigger: 'change' },
  934. ],
  935. team_type: [
  936. { required: true, message: '请选择个人或者团队', trigger: 'change' },
  937. ],
  938. purpose: [
  939. { required: true, message: '请输入借用原因', trigger: 'change' },
  940. ],
  941. applicant_remark_type: [
  942. { required: true, message: '请选择审批人', trigger: 'change' },
  943. ],
  944. applicant_remark: [
  945. { required: false, message: '请输入备注', trigger: 'change' },
  946. ],
  947. };
  948. // 自动填充用户信息
  949. onMounted(() => {
  950. loadWarehouses()
  951. getAdminList()
  952. getAllUserList()
  953. try {
  954. const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
  955. if (userInfo) {
  956. form.value.emergency_contact = userInfo.username || '';
  957. form.value.app_user_borrower = Number(userInfo.dept_info?.dept_id )|| 1;
  958. form.value.emergency_phone = userInfo.mobile || '';
  959. form.value.borrower_dept = userInfo.dept_info?.dept_name || '';
  960. }
  961. } catch (e) {
  962. /* empty */
  963. }
  964. });
  965. watch(
  966. () => props.visible,
  967. (v) => (visible.value = v)
  968. );
  969. watch(() => props.modelValue, async (val) => {
  970. if (val && val.id) {
  971. try {
  972. const res = await api.GetApplicationDetail(val.id);
  973. console.log("res:::::",res)
  974. if (res.code === 2000 && res.data) {
  975. const data = res.data;
  976. Object.assign(form.value, data);
  977. buildSteps(data);
  978. timeline.value.create = data.create_datetime || '';
  979. timeline.value.approve = data.approve_time || '';
  980. const approverinfo = data.approver_info ?? data.admin_approver_info
  981. timeline.value.approver = approverinfo?.name || '审批人'
  982. // timeline.value.approver = data.admin_approver_info?.name || '审批人';
  983. // console.log("data:::",form.value);
  984. // activeStep.value = 1; // 创建
  985. // if (data.status >= 1) activeStep.value = 2; // 待审批
  986. // if (data.status >= 2) activeStep.value = 3; // 审批通过
  987. // if (data.status >= 3) activeStep.value = 4; // 完成
  988. // 同步 form.items 到 returnDeviceList
  989. if (form.value.items && form.value.items.length > 0) {
  990. returnDeviceList.value = form.value.items
  991. .filter(item => item.device !== null)
  992. .map(item => ({
  993. device_no: item.device,
  994. device_code: item.device_code,
  995. device_type: item.remark,
  996. device_name: item.device_name,
  997. borrow_count: item.quantity,
  998. brand: '',
  999. model: item.device_specification,
  1000. warehouse: item.location,
  1001. problem_records: item.problem_records,
  1002. /* status: '未归还' */
  1003. return_status:item.return_status,
  1004. return_time:item.return_times
  1005. }));
  1006. }
  1007. // 同步 form.records 到 returnAbnormalList
  1008. if (form.value.records && form.value.records.length > 0) {
  1009. // 添加损坏类型记录
  1010. const damageRecord: AbnormalRecord = {
  1011. application_no: form.value.application_no,
  1012. operator_name: form.value.app_user_borrower?.name||form.value.external_borrower_name||'',
  1013. user_code: form.value.borrower_info?.user_code || form.value.app_user_borrower?.user_code||'',
  1014. emergency_phone: form.value.external_borrower_phone || form.value.app_user_borrower?.mobile||'',
  1015. type: '损坏',
  1016. condition: form.value.borrower_damage_count || 0,
  1017. create_time: dayjs().format('YYYY-MM-DD HH:mm:ss')
  1018. };
  1019. // 添加逾期类型记录
  1020. const overdueRecord: AbnormalRecord = {
  1021. application_no: form.value.application_no,
  1022. operator_name: form.value.app_user_borrower?.name||form.value.external_borrower_name||'',
  1023. user_code: form.value.borrower_info?.user_code || form.value.app_user_borrower?.user_code||'',
  1024. emergency_phone: form.value.external_borrower_phone || form.value.app_user_borrower?.mobile||'',
  1025. type: '逾期',
  1026. condition: form.value.borrower_overdue_count || 0, // 逾期数量设为1
  1027. create_time: dayjs().format('YYYY-MM-DD HH:mm:ss')
  1028. };
  1029. returnAbnormalList.value = [damageRecord, overdueRecord];
  1030. abnormalList.value = [damageRecord, overdueRecord];
  1031. }
  1032. // 处理附件回显
  1033. if (data.attachments) {
  1034. if (typeof data.attachments === 'string') {
  1035. // 如果attachments是字符串URL
  1036. attachmentFileList.value = [{
  1037. name: data.attachments.split('/').pop() || '附件',
  1038. url: data.attachments,
  1039. status: 'success'
  1040. }];
  1041. } else if (Array.isArray(data.attachments)) {
  1042. // 如果attachments是数组
  1043. attachmentFileList.value = data.attachments.map((item: any, index: number) => {
  1044. if (typeof item === 'string') {
  1045. return {
  1046. name: item.split('/').pop() || `附件${index + 1}`,
  1047. url: item,
  1048. status: 'success'
  1049. };
  1050. } else if (typeof item === 'object' && item.url) {
  1051. return {
  1052. name: item.name || item.url.split('/').pop() || `附件${index + 1}`,
  1053. url: item.url,
  1054. status: 'success'
  1055. };
  1056. }
  1057. return null;
  1058. }).filter(Boolean);
  1059. }
  1060. }
  1061. }
  1062. } catch (e) {
  1063. console.log("审判失败:::",e)
  1064. ElMessage.error('获取审批信息失败');
  1065. }
  1066. }else if(val&& isEdit.value){
  1067. try {
  1068. const res = await api.GetApplicationDetail(val.id);
  1069. if (res.code === 2000 && res.data) {
  1070. const data = res.data;
  1071. Object.assign(form.value, data);
  1072. // 同步 form.items 到 returnDeviceList
  1073. if (form.value.items && form.value.items.length > 0) {
  1074. returnDeviceList.value = form.value.items
  1075. .filter(item => item.device !== null)
  1076. .map(item => ({
  1077. device_no: item.device,
  1078. device_type: item.remark,
  1079. device_name: item.device_name,
  1080. borrow_count: item.quantity,
  1081. brand: '',
  1082. model: item.device_specification,
  1083. warehouse: item.location,
  1084. problem_records: item.problem_records,
  1085. return_status:item.return_status,
  1086. return_time:item.return_times
  1087. }));
  1088. }
  1089. // 处理附件回显 (编辑模式)
  1090. if (data.attachments) {
  1091. if (typeof data.attachments === 'string') {
  1092. attachmentFileList.value = [{
  1093. name: data.attachments.split('/').pop() || '附件',
  1094. url: data.attachments,
  1095. status: 'success'
  1096. }];
  1097. } else if (Array.isArray(data.attachments)) {
  1098. attachmentFileList.value = data.attachments.map((item: any, index: number) => {
  1099. if (typeof item === 'string') {
  1100. return {
  1101. name: item.split('/').pop() || `附件${index + 1}`,
  1102. url: item,
  1103. status: 'success'
  1104. };
  1105. } else if (typeof item === 'object' && item.url) {
  1106. return {
  1107. name: item.name || item.url.split('/').pop() || `附件${index + 1}`,
  1108. url: item.url,
  1109. status: 'success'
  1110. };
  1111. }
  1112. return null;
  1113. }).filter(Boolean);
  1114. }
  1115. }
  1116. }
  1117. } catch (e) {
  1118. ElMessage.error('获取审批信息失败');
  1119. }
  1120. }
  1121. }, { immediate: true });
  1122. // watch(
  1123. // () => props.modelValue,
  1124. // (v) => {
  1125. // if (v) {
  1126. // // eslint-disable-next-line no-console
  1127. // console.log("val:::::",v);
  1128. // Object.assign(form.value, v);
  1129. // }
  1130. // }, { immediate: true }
  1131. // );
  1132. // 监听借用人选择变化
  1133. watch(() => form.value.app_user_borrower, (newValue) => {
  1134. if (newValue && allUserList.value.length > 0) {
  1135. const selectedUser = allUserList.value.find(user => user.id === newValue);
  1136. if (selectedUser) {
  1137. form.value.user_code = selectedUser.user_code || '';
  1138. form.value.mobile = selectedUser.mobile || '';
  1139. }
  1140. }
  1141. });
  1142. function addItem() {
  1143. showSelectDeviceDialog.value = true;
  1144. }
  1145. function removeItem(index: number) {
  1146. form.value.items.splice(index, 1);
  1147. }
  1148. function onCancel() {
  1149. // 重置标签页到第一个
  1150. activeName.value = 'first';
  1151. // 清理异常记录列表
  1152. returnAbnormalList.value = [];
  1153. abnormalList.value = [];
  1154. // 清理设备列表
  1155. deviceList.value = [];
  1156. returnDeviceList.value = [];
  1157. // 清理搜索框
  1158. searchText.value = '';
  1159. returnSearchText.value = '';
  1160. // 清理备注
  1161. remark.value = '';
  1162. returnRemark.value = '';
  1163. // 重置状态
  1164. returnStatus.value = '未归还';
  1165. // 清理选中的设备
  1166. selectedDevices.value = [];
  1167. // 清理附件文件列表
  1168. attachmentFileList.value = [];
  1169. // 关闭所有对话框
  1170. showSelectDeviceDialog.value = false;
  1171. showSettlementDialog.value = false;
  1172. showAbnormalDialog.value = false;
  1173. showViewAbnormalDialog.value = false;
  1174. // 重置当前异常数据
  1175. currentAbnormalData.value = {
  1176. damage_type: 0,
  1177. condition: '',
  1178. photos: []
  1179. };
  1180. // 发出关闭事件
  1181. emit('update:visible', false);
  1182. }
  1183. // 提交时合并数据
  1184. function onSubmit() {
  1185. if(isCollect.value){
  1186. // 验证是否有设备信息
  1187. const hasDevicesInFirstTab = form.value.items && form.value.items.length > 0;
  1188. const hasDevicesInSecondTab = deviceList.value && deviceList.value.length > 0;
  1189. if (!hasDevicesInFirstTab && !hasDevicesInSecondTab) {
  1190. ElMessage.warning('请添加设备信息后再提交');
  1191. activeName.value = 'second'; // 自动切换到借用列表标签页
  1192. return;
  1193. }
  1194. // 验证设备数量
  1195. const hasInvalidQuantityInFirstTab = form.value.items.some(item => !item.quantity || item.quantity <= 0);
  1196. const hasInvalidQuantityInSecondTab = deviceList.value.some(item => !item.borrow_count || item.borrow_count <= 0);
  1197. if (hasInvalidQuantityInFirstTab || hasInvalidQuantityInSecondTab) {
  1198. ElMessage.warning('请确保所有设备的数量大于0');
  1199. activeName.value = 'second'; // 自动切换到借用列表标签页
  1200. return;
  1201. }
  1202. // 合并两个标签页的设备数据
  1203. const allDevices = [
  1204. ...deviceList.value.map(item => ({
  1205. condition: remark.value,
  1206. quantity: item.borrow_count,
  1207. device_id: Number(item.device_no) || 0
  1208. }))
  1209. ];
  1210. const submitData = {
  1211. id:form.value.id,
  1212. item_id:form.value.id,
  1213. device_assignments: allDevices,
  1214. remark:remark.value,
  1215. accessories:form.value.accessories,
  1216. };
  1217. CollectEquipment(submitData).then((res: ApiResponse) => {
  1218. if(res.code===2000){
  1219. ElMessage.success("领取成功");
  1220. // 准备结算单数据
  1221. settlementData.value = {
  1222. ...form.value,
  1223. items: deviceList.value.map(item => ({
  1224. ...item,
  1225. device_category_name: item.device_type,
  1226. device_name: item.device_name,
  1227. device_specification: item.device_specification,
  1228. device_storage_location: item.warehouse,
  1229. remark: item.device_type,
  1230. quantity: item.borrow_count,
  1231. device_id: item.device_no,
  1232. device_category: item.device_type,
  1233. device_code: item.device_no,
  1234. borrow_type: form.value.borrow_type,
  1235. borrower_dept: form.value.borrower_dept,
  1236. emergency_contact: form.value.emergency_contact,
  1237. emergency_phone: form.value.emergency_phone,
  1238. team_type: form.value.team_type,
  1239. })),
  1240. accessory_info: accessoryInfo.value,
  1241. remark: remark.value,
  1242. expected_start_time: dayjs().format('YYYY-MM-DD HH:mm:ss')
  1243. };
  1244. // 先触发提交事件进行页面刷新
  1245. emit('submit');
  1246. // 关闭当前对话框
  1247. emit('update:visible', false);
  1248. // 使用 nextTick 确保在 DOM 更新后再显示结算单
  1249. nextTick(() => {
  1250. showSettlementDialog.value = true;
  1251. });
  1252. }
  1253. });
  1254. } else {
  1255. // 归还逻辑修改
  1256. // 使用所有设备列表而不是选中的设备
  1257. const allDevices = returnDeviceList.value
  1258. .filter(item => item.is_return === true) // 只选择 return_status 为 2 的设备
  1259. .map(item => ({
  1260. device: item.device_no,
  1261. quantity: item.borrow_count,
  1262. remark: returnRemark.value,
  1263. device_id: Number(item.device_no) || 0,
  1264. // 添加异常相关字段
  1265. condition: item.condition, // 异常说明
  1266. damage_type:item.damage_type,
  1267. photo_urls: item.photos || [], // 异常照片
  1268. }));
  1269. const submitData = {
  1270. id: form.value.id,
  1271. items: allDevices,
  1272. status: returnStatus.value,
  1273. accessories: form.value.accessories,
  1274. };
  1275. ReturnEquipment(submitData).then(async (res: ApiResponse) => {
  1276. if(res.code===2000){
  1277. ElMessage.success("归还成功");
  1278. try {
  1279. if (form.value.id) {
  1280. const detailRes = await api.GetApplicationDetail(form.value.id);
  1281. if (detailRes.code === 2000 && detailRes.data) {
  1282. returnStatus.value = detailRes.data.status_label || returnStatus.value;
  1283. }
  1284. }
  1285. } catch (e) {
  1286. ElMessage.error('获取最新状态失败');
  1287. }
  1288. emit('submit');
  1289. emit('update:visible', false);
  1290. }
  1291. });
  1292. }
  1293. }
  1294. function clickReturn(){
  1295. returnStatus.value = '已归还';
  1296. // 自动选择所有设备
  1297. const tableRef = document.querySelector('.el-table__header-wrapper th.el-table__cell.is-leaf .el-checkbox');
  1298. if (tableRef) {
  1299. (tableRef as HTMLElement).click();
  1300. }
  1301. }
  1302. /* formRef.value.validate((valid: boolean) => {
  1303. if (valid) {
  1304. form.value.items = form.value.items.map(item => ({
  1305. ...item,
  1306. quantity: Number(item.quantity) || 0
  1307. }));
  1308. const firstLocation = form.value.items[0]?.location || '';
  1309. form.value.return_location = firstLocation;
  1310. form.value.expected_start_time = now;
  1311. form.value.expected_end_time = dayjs(form.value.expected_end_time).format('YYYY-MM-DD HH:mm:ss')|| now;
  1312. form.team_members_count=Number(form.team_members_count)||1;
  1313. emit('submit', { ...form.value });
  1314. emit('update:visible', false);
  1315. ElMessage.success("添加成功!");
  1316. }
  1317. }); */
  1318. </script>
  1319. <style>
  1320. .avatar-uploader .el-upload {
  1321. border: 1px dashed #d9d9d9;
  1322. border-radius: 6px;
  1323. cursor: pointer;
  1324. position: relative;
  1325. overflow: hidden;
  1326. }
  1327. .avatar-uploader .el-upload:hover {
  1328. border-color: #409EFF;
  1329. }
  1330. .avatar-uploader-icon {
  1331. font-size: 28px;
  1332. color: #8c939d;
  1333. width: 178px;
  1334. height: 178px;
  1335. line-height: 178px;
  1336. text-align: center;
  1337. }
  1338. .avatar {
  1339. width: 178px;
  1340. height: 178px;
  1341. display: block;
  1342. }
  1343. </style>