index.vue 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207
  1. <!-- <?xml version="1.0" encoding="UTF-8"?> -->
  2. <template>
  3. <div class="app-container">
  4. <!-- 搜索栏 -->
  5. <el-form
  6. :model="queryParams"
  7. ref="queryForm"
  8. :inline="true"
  9. class="demo-form-inline"
  10. >
  11. <el-form-item>
  12. <el-input
  13. v-model="queryParams.orderNo"
  14. placeholder="服务单号"
  15. clearable
  16. size="small"
  17. style="width: 150px"
  18. />
  19. </el-form-item>
  20. <el-form-item>
  21. <el-input
  22. v-model="queryParams.contractNo"
  23. placeholder="合同单号"
  24. clearable
  25. size="small"
  26. style="width: 150px"
  27. />
  28. </el-form-item>
  29. <el-form-item>
  30. <el-input
  31. v-model="queryParams.deliveryNo"
  32. placeholder="发货单号"
  33. clearable
  34. size="small"
  35. style="width: 150px"
  36. />
  37. </el-form-item>
  38. <el-form-item>
  39. <el-select
  40. v-model="queryParams.serviceStaffIds"
  41. placeholder="服务人员"
  42. clearable
  43. filterable
  44. size="small"
  45. style="width: 120px"
  46. >
  47. <el-option
  48. v-for="item in installerOptions"
  49. :key="item.value"
  50. :label="item.label"
  51. :value="item.value"
  52. />
  53. </el-select>
  54. </el-form-item>
  55. <el-form-item>
  56. <el-input
  57. v-model="queryParams.customerName"
  58. placeholder="客户名称"
  59. clearable
  60. size="small"
  61. style="width: 120px"
  62. />
  63. </el-form-item>
  64. <el-form-item>
  65. <el-select
  66. v-model="queryParams.statusName"
  67. placeholder="处理状态"
  68. clearable
  69. size="small"
  70. style="width: 120px"
  71. >
  72. <el-option
  73. v-for="dict in statusOptions"
  74. :key="dict.value"
  75. :label="dict.label"
  76. :value="dict.value"
  77. />
  78. </el-select>
  79. </el-form-item>
  80. <el-form-item>
  81. <!-- <el-select
  82. v-model="queryParams.projectId"
  83. placeholder="项目名称"
  84. clearable
  85. size="small"
  86. style="width: 120px"
  87. >
  88. <el-option
  89. v-for="dict in projectOptions"
  90. :key="dict.id"
  91. :label="dict.name"
  92. :value="dict.id"
  93. />
  94. </el-select> -->
  95. </el-form-item>
  96. <el-form-item>
  97. <el-date-picker
  98. v-model="queryParams.orderTimeRange"
  99. class="inputDatetime"
  100. type="daterange"
  101. size="small"
  102. range-separator="至"
  103. start-placeholder="下单开始日期"
  104. end-placeholder="下单结束日期"
  105. style="width: 250px"
  106. />
  107. </el-form-item>
  108. <el-form-item>
  109. <el-date-picker
  110. v-model="queryParams.completeTimeRange"
  111. class="inputDatetime"
  112. type="daterange"
  113. size="small"
  114. range-separator="至"
  115. start-placeholder="完成开始日期"
  116. end-placeholder="完成结束日期"
  117. style="width: 250px"
  118. />
  119. </el-form-item>
  120. <div style="width: 100%"></div>
  121. <el-form-item>
  122. <el-button
  123. size="small"
  124. class="successBorder"
  125. icon="el-icon-search"
  126. @click="handleQuery"
  127. >搜索</el-button
  128. >
  129. <el-button
  130. size="small"
  131. class="successBorder"
  132. icon="el-icon-download"
  133. @click="onExportClick"
  134. v-if="
  135. checkButtonPermission('productManagement:installationOrder:export')
  136. "
  137. >导出</el-button
  138. >
  139. <el-button
  140. size="small"
  141. type="primary"
  142. icon="el-icon-plus"
  143. @click="handleAdd"
  144. v-if="
  145. checkButtonPermission('productManagement:installationOrder:add')
  146. "
  147. >新增计划</el-button
  148. >
  149. <el-radio-group
  150. v-model="queryParams.viewType"
  151. size="small"
  152. @change="handleViewTypeChange"
  153. style="margin-left: 8px"
  154. >
  155. <el-radio-button label="">全部</el-radio-button>
  156. <el-radio-button label="未接单"
  157. >未接单<el-badge
  158. v-if="pendingCount > 0"
  159. :value="pendingCount"
  160. class="pending-badge"
  161. /></el-radio-button>
  162. <el-radio-button label="已完成">已完成</el-radio-button>
  163. </el-radio-group>
  164. </el-form-item>
  165. </el-form>
  166. <!-- 表格 -->
  167. <el-table
  168. v-loading="loading"
  169. element-loading-text="数据加载中..."
  170. element-loading-spinner="el-icon-loading"
  171. element-loading-background="rgba(255, 255, 255, 0.8)"
  172. :data="tableData"
  173. border
  174. style="width: 100%; margin-top: 15px"
  175. :empty-text="'暂无数据'"
  176. :header-cell-style="{ background: '#f5f7fa' }"
  177. @row-dblclick="handleView"
  178. height="calc(100vh - 250px)"
  179. :row-class-name="tableRowClassName"
  180. >
  181. <el-table-column label="序号" width="50" align="center">
  182. <template slot-scope="scope">
  183. {{
  184. (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1
  185. }}
  186. </template>
  187. </el-table-column>
  188. <el-table-column prop="orderNo" label="服务单号" align="center">
  189. <template slot-scope="scope">
  190. <el-link
  191. type="primary"
  192. :underline="false"
  193. class="order-no-link"
  194. @click="handleView(scope.row)"
  195. >
  196. {{ scope.row.orderNo }}
  197. </el-link>
  198. </template>
  199. </el-table-column>
  200. <!-- <el-table-column prop="projectName" label="项目名称" align="center" /> -->
  201. <el-table-column
  202. prop="statusName"
  203. label="处理状态"
  204. align="center"
  205. width="100"
  206. >
  207. <template slot-scope="scope">
  208. <el-tag :type="getStatusType(scope.row.statusName)">{{
  209. scope.row.statusName
  210. }}</el-tag>
  211. </template>
  212. </el-table-column>
  213. <el-table-column
  214. prop="customerName"
  215. label="客户名称"
  216. align="center"
  217. width="120"
  218. />
  219. <el-table-column prop="goodsName" label="货品" align="center" width="150">
  220. <template slot-scope="scope">
  221. <div class="product-tags">
  222. <template v-if="scope.row.goodsName">
  223. <el-tag
  224. v-for="(item, index) in scope.row.goodsName.split(',')"
  225. :key="index"
  226. size="mini"
  227. :type="['success', 'warning', 'danger', 'primary'][index % 4]"
  228. effect="light"
  229. :class="{ 'long-text': item.length > 10 }"
  230. >
  231. {{ item }}
  232. </el-tag>
  233. </template>
  234. </div>
  235. </template>
  236. </el-table-column>
  237. <el-table-column
  238. prop="progress"
  239. label="服务进度"
  240. align="center"
  241. width="250"
  242. >
  243. <template slot-scope="scope">
  244. <div
  245. class="progress-wrapper"
  246. @mouseenter="
  247. showProgressTooltip($event, parseInt(scope.row.progress))
  248. "
  249. @mouseleave="hideProgressTooltip"
  250. >
  251. <div class="progress-container">
  252. <div class="progress-bar">
  253. <div class="custom-progress">
  254. <div
  255. class="progress-inner"
  256. :style="{
  257. width:
  258. scope.row.progress > 0
  259. ? parseInt(scope.row.progress) + '%'
  260. : '2px',
  261. backgroundColor: getProgressBarColor(
  262. scope.row.statusName
  263. ),
  264. }"
  265. >
  266. <span class="progress-text" v-if="scope.row.progress > 0">
  267. {{ parseInt(scope.row.progress) }}%
  268. </span>
  269. </div>
  270. </div>
  271. </div>
  272. </div>
  273. </div>
  274. </template>
  275. </el-table-column>
  276. <el-table-column
  277. prop="totalQuantity"
  278. label="计划总量"
  279. align="center"
  280. width="100"
  281. />
  282. <el-table-column
  283. prop="installedQuantity"
  284. label="已完成量"
  285. align="center"
  286. width="100"
  287. />
  288. <el-table-column
  289. prop="uninstalledQuantity"
  290. label="未完成量"
  291. align="center"
  292. width="100"
  293. />
  294. <!-- <el-table-column
  295. prop="dispatcherName"
  296. label="派单人"
  297. align="center"
  298. width="100"
  299. /> -->
  300. <el-table-column
  301. prop="serviceStaffNames"
  302. label="服务人员"
  303. align="center"
  304. width="120"
  305. >
  306. <template slot-scope="scope">
  307. <div class="staff-tags">
  308. <template v-if="scope.row.serviceStaffNames">
  309. <el-tag
  310. v-for="(item, index) in scope.row.serviceStaffNames.split(',')"
  311. :key="index"
  312. size="mini"
  313. :type="['primary', 'success', 'warning', 'danger'][index % 4]"
  314. effect="light"
  315. :class="{ 'long-text': item.length > 10 }"
  316. >
  317. {{ item }}
  318. </el-tag>
  319. </template>
  320. </div>
  321. </template>
  322. </el-table-column>
  323. <el-table-column
  324. prop="createName"
  325. label="下单人"
  326. align="center"
  327. width="100"
  328. />
  329. <el-table-column
  330. prop="orderTime"
  331. label="下单时间"
  332. align="center"
  333. width="140"
  334. />
  335. <el-table-column
  336. prop="acceptTime"
  337. label="接单时间"
  338. align="center"
  339. width="140"
  340. />
  341. <el-table-column
  342. prop="estimatedCompleteTime"
  343. label="预估完成时间"
  344. align="center"
  345. width="140"
  346. />
  347. <el-table-column
  348. prop="remainingTime"
  349. label="距预估完成时间还剩"
  350. align="center"
  351. width="160"
  352. >
  353. <template slot-scope="scope">
  354. <template
  355. v-if="
  356. scope.row.remainingTime !== null &&
  357. scope.row.remainingTime !== undefined
  358. "
  359. >
  360. <span
  361. :class="{
  362. 'normal-time': scope.row.remainingTime > 3,
  363. 'warning-time':
  364. scope.row.remainingTime >= 0 && scope.row.remainingTime <= 3,
  365. 'overdue-time': scope.row.remainingTime < 0,
  366. }"
  367. >
  368. <template v-if="scope.row.remainingTime < 0">
  369. 已逾期 {{ Math.abs(scope.row.remainingTime) }} 天
  370. </template>
  371. <template v-else> {{ scope.row.remainingTime }} 天 </template>
  372. </span>
  373. </template>
  374. </template>
  375. </el-table-column>
  376. <el-table-column
  377. prop="actualCompleteTime"
  378. label="实际完成时间"
  379. align="center"
  380. width="140"
  381. />
  382. <el-table-column
  383. prop="remark"
  384. label="备注"
  385. align="center"
  386. min-width="120"
  387. />
  388. <el-table-column label="操作" align="center" width="220" fixed="right">
  389. <template slot-scope="scope">
  390. <el-button
  391. size="mini"
  392. type="primary"
  393. @click="handleView(scope.row)"
  394. v-if="
  395. getButtonVisible(
  396. {
  397. permission: 'productManagement:installationOrder:view',
  398. type: 'view',
  399. },
  400. scope.row
  401. )
  402. "
  403. >查看</el-button
  404. >
  405. <el-button
  406. size="mini"
  407. type="primary"
  408. @click="handleEdit(scope.row)"
  409. v-if="
  410. getButtonVisible(
  411. {
  412. permission: 'productManagement:installationOrder:edit',
  413. type: 'edit',
  414. },
  415. scope.row
  416. )
  417. "
  418. >编辑</el-button
  419. >
  420. <el-button
  421. size="mini"
  422. type="danger"
  423. @click="handleDelete(scope.row)"
  424. v-if="
  425. getButtonVisible(
  426. {
  427. permission: 'productManagement:installationOrder:delete',
  428. type: 'delete',
  429. },
  430. scope.row
  431. )
  432. "
  433. >删除</el-button
  434. >
  435. <el-button
  436. size="mini"
  437. type="success"
  438. @click="handleDispatch(scope.row)"
  439. v-if="
  440. getButtonVisible(
  441. {
  442. permission: 'productManagement:installationOrder:dispatch',
  443. type: 'dispatch',
  444. },
  445. scope.row
  446. )
  447. "
  448. >派单</el-button
  449. >
  450. <el-button
  451. size="mini"
  452. style="
  453. background-color: #e74c3c;
  454. border-color: #e74c3c;
  455. color: #fff;
  456. "
  457. @click="handleReject(scope.row)"
  458. v-if="
  459. getButtonVisible(
  460. {
  461. permission: 'productManagement:installationOrder:reject',
  462. type: 'reject',
  463. },
  464. scope.row
  465. )
  466. "
  467. >驳回</el-button
  468. >
  469. <el-button
  470. size="mini"
  471. style="
  472. background-color: #f39c12;
  473. border-color: #f39c12;
  474. color: #fff;
  475. "
  476. @click="handleAccept(scope.row)"
  477. v-if="
  478. getButtonVisible(
  479. {
  480. permission: 'productManagement:installationOrder:accept',
  481. type: 'accept',
  482. },
  483. scope.row
  484. )
  485. "
  486. >接单</el-button
  487. >
  488. <el-button
  489. size="mini"
  490. style="
  491. background-color: #409eff;
  492. border-color: #409eff;
  493. color: #fff;
  494. "
  495. @click="handleDailyWrite(scope.row)"
  496. v-if="
  497. getButtonVisible(
  498. {
  499. permission: 'productManagement:installationOrder:dailyWrite',
  500. type: 'write',
  501. },
  502. scope.row
  503. )
  504. "
  505. >填写</el-button
  506. >
  507. <el-button
  508. size="mini"
  509. type="success"
  510. @click="handleComplete(scope.row)"
  511. v-if="
  512. getButtonVisible(
  513. {
  514. permission: 'productManagement:installationOrder:complete',
  515. type: 'complete',
  516. },
  517. scope.row
  518. )
  519. "
  520. >完成</el-button
  521. >
  522. <el-button
  523. size="mini"
  524. type="warning"
  525. @click="handleCheck(scope.row)"
  526. v-if="
  527. getButtonVisible(
  528. {
  529. permission: 'productManagement:installationOrder:check',
  530. type: 'check',
  531. },
  532. scope.row
  533. )
  534. "
  535. >验收</el-button
  536. >
  537. </template>
  538. </el-table-column>
  539. </el-table>
  540. <!-- 分页 -->
  541. <pagination
  542. :total="total"
  543. :page.sync="queryParams.pageNum"
  544. :limit.sync="queryParams.pageSize"
  545. :page-sizes="[10, 20, 30, 50]"
  546. @pagination="getList"
  547. />
  548. <!-- 新增服务计划弹窗
  549. 传递 installerOptions 给下单人和服务人员选择器
  550. 传递 projectOptions 给服务项目选择器
  551. 传递 currentUser 给下单人选择器-->
  552. <add-dialog
  553. :visible.sync="dialogVisible"
  554. :installer-options="installerOptions"
  555. :project-options="projectOptions"
  556. :current-user="currentUser"
  557. @success="getList"
  558. />
  559. <!-- 编辑弹窗 -->
  560. <edit-dialog
  561. :visible.sync="editDialogVisible"
  562. :installer-options="installerOptions"
  563. :project-options="projectOptions"
  564. :current-user="currentUser"
  565. :row-data="currentRow"
  566. @success="handleEditSuccess"
  567. />
  568. <!-- 查看弹窗 -->
  569. <view-dialog
  570. :visible.sync="viewDialogVisible"
  571. :installer-options="installerOptions"
  572. :project-options="projectOptions"
  573. :row-data="currentRow"
  574. :check-button-permission="checkButtonPermission"
  575. :is-current-user-in-service-staff="isCurrentUserInServiceStaff"
  576. @reject="() => (rejectDialogVisible = true)"
  577. @accept="() => (acceptDialogVisible = true)"
  578. />
  579. <!-- 派单弹窗 -->
  580. <dispatch-dialog
  581. :visible.sync="dispatchDialogVisible"
  582. :installer-options="installerOptions"
  583. :current-user="currentUser"
  584. :row-data="currentRow"
  585. @success="getList"
  586. />
  587. <!-- 驳回弹窗 -->
  588. <reject-dialog
  589. :visible.sync="rejectDialogVisible"
  590. :row-data="currentRow"
  591. @confirm="handleRejectConfirm"
  592. />
  593. <!-- 填写弹窗 -->
  594. <daily-write-dialog
  595. :visible.sync="dailyWriteDialogVisible"
  596. :row-data="currentRow"
  597. :current-user="currentUser"
  598. @confirm="handleDailyWriteConfirm"
  599. />
  600. <!-- 接单弹窗 -->
  601. <accept-dialog
  602. :visible.sync="acceptDialogVisible"
  603. :row-data="currentRow"
  604. :current-user="currentUser"
  605. @confirm="handleAcceptConfirm"
  606. />
  607. <!-- 验收弹窗 -->
  608. <check-dialog
  609. :visible.sync="checkDialogVisible"
  610. :row-data="currentRow"
  611. :installer-options="installerOptions"
  612. @confirm="handleCheckConfirm"
  613. :customer-contact-options="customerContactOptions"
  614. :current-user="currentUser"
  615. />
  616. </div>
  617. </template>
  618. <script>
  619. import Pagination from "@/components/Pagination";
  620. import {
  621. GetDataByName,
  622. GetDataByNames,
  623. PostDataByName,
  624. ExecDataByConfig,
  625. } from "@/api/common";
  626. import AddDialog from "./components/AddDialog";
  627. import exportMixin from "@/mixins/exportMixin";
  628. import paramsMixin from "./mixins/paramsMixin";
  629. import { exportInstallationOrder } from "@/api/productManagement/installation";
  630. import Cookies from "js-cookie";
  631. import EditDialog from "./components/EditDialog.vue";
  632. import ViewDialog from "./components/ViewDialog.vue";
  633. import DispatchDialog from "./components/DispatchDialog.vue";
  634. import RejectDialog from "./components/RejectDialog.vue";
  635. import DailyWriteDialog from "./components/DailyWriteDialog.vue";
  636. import AcceptDialog from "./components/AcceptDialog.vue";
  637. import CheckDialog from "./components/CheckDialog.vue";
  638. import * as XLSX from "xlsx";
  639. export default {
  640. name: "InstallationOrder",
  641. components: {
  642. Pagination,
  643. AddDialog,
  644. EditDialog,
  645. ViewDialog,
  646. DispatchDialog,
  647. RejectDialog,
  648. DailyWriteDialog,
  649. AcceptDialog,
  650. CheckDialog,
  651. },
  652. mounted() {
  653. // 打印整个路由对象,看看具体结构
  654. console.log("路由信息:", this.$route);
  655. this.buttonMenu = JSON.parse(sessionStorage.getItem("buttons"));
  656. const menuId = this.$route.query.menuId;
  657. if (menuId) {
  658. this.currentMenuId = parseInt(menuId);
  659. } else {
  660. console.log("没有获取到菜单ID");
  661. }
  662. },
  663. mixins: [exportMixin, paramsMixin],
  664. data() {
  665. return {
  666. // 表格高度
  667. tableHeight: window.innerHeight - 300,
  668. // 遮罩层
  669. loading: false,
  670. // 总条数
  671. total: 0,
  672. // 表格数据
  673. tableData: [],
  674. // 当前菜单ID
  675. currentMenuId: 0,
  676. // 按钮菜单
  677. buttonMenu: [],
  678. // 服务人员选项
  679. installerOptions: [],
  680. // 客户联系人选项
  681. customerContactOptions: [],
  682. // 查询参数
  683. queryParams: {
  684. pageNum: 1,
  685. pageSize: 10,
  686. serviceStaffIds: "",
  687. customerName: "",
  688. statusName: "",
  689. projectName: "",
  690. projectId: "",
  691. orderTimeRange: [],
  692. completeTimeRange: [],
  693. viewType: "",
  694. orderNo: "",
  695. deliveryNo: "",
  696. contractNo: "",
  697. userId: Cookies.get("g_userId") || "",
  698. roleId: Cookies.get("g_roleId") || "",
  699. },
  700. // 状态选项
  701. statusOptions: [
  702. { label: "未接单", value: "未接单" },
  703. { label: "已接单", value: "已接单" },
  704. { label: "处理中", value: "处理中" },
  705. { label: "已完成未验收", value: "已完成未验收" },
  706. { label: "已完成", value: "已完成" },
  707. { label: "接单驳回", value: "接单驳回" },
  708. ],
  709. // 项目选项
  710. projectOptions: [],
  711. // 日期选择器快捷选项
  712. pickerOptions: {
  713. shortcuts: [
  714. {
  715. text: "最近一周",
  716. onClick(picker) {
  717. const end = new Date();
  718. const start = new Date();
  719. start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
  720. picker.$emit("pick", [start, end]);
  721. },
  722. },
  723. {
  724. text: "最近一个月",
  725. onClick(picker) {
  726. const end = new Date();
  727. const start = new Date();
  728. start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
  729. picker.$emit("pick", [start, end]);
  730. },
  731. },
  732. {
  733. text: "最近三个月",
  734. onClick(picker) {
  735. const end = new Date();
  736. const start = new Date();
  737. start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
  738. picker.$emit("pick", [start, end]);
  739. },
  740. },
  741. ],
  742. },
  743. pendingCount: 0,
  744. // 弹窗显示控制
  745. dialogVisible: false,
  746. // 当前用户信息
  747. currentUser: {
  748. id: Cookies.get("g_userId") || "",
  749. name: Cookies.get("g_empname") || "",
  750. roleId: Cookies.get("g_roleId") || "",
  751. },
  752. editDialogVisible: false,
  753. viewDialogVisible: false,
  754. currentRow: {},
  755. progressTooltip: null,
  756. dispatchDialogVisible: false,
  757. rejectDialogVisible: false,
  758. dailyWriteDialogVisible: false,
  759. acceptDialogVisible: false,
  760. checkDialogVisible: false,
  761. };
  762. },
  763. created() {
  764. this.getList();
  765. this.getSelectOptions();
  766. // 添加resize事件监听
  767. this.resizeHandler = this.debounce(() => {
  768. this.tableHeight = window.innerHeight - 300;
  769. }, 100);
  770. window.addEventListener("resize", this.resizeHandler);
  771. },
  772. beforeDestroy() {
  773. // 移除resize事件监听
  774. window.removeEventListener("resize", this.resizeHandler);
  775. this.hideProgressTooltip();
  776. },
  777. methods: {
  778. // 添加防抖函数
  779. debounce(fn, delay) {
  780. let timer = null;
  781. return function () {
  782. const context = this;
  783. const args = arguments;
  784. clearTimeout(timer);
  785. timer = setTimeout(() => {
  786. fn.apply(context, args);
  787. }, delay);
  788. };
  789. },
  790. // 获取列表数据
  791. getList() {
  792. this.loading = true;
  793. // 使用参数处理混入方法
  794. const send_data = [
  795. this.handleParams(this.queryParams, "getInstallationOrderList"),
  796. {
  797. name: "getInstallationOrderToDoNum",
  798. offset: 0,
  799. pagecount: 0,
  800. parammaps: {
  801. userId: this.currentUser.id,
  802. roleId: this.currentUser.roleId,
  803. },
  804. },
  805. ];
  806. GetDataByNames(send_data)
  807. .then((response) => {
  808. this.tableData = response.data.getInstallationOrderList.list || [];
  809. this.total = response.data.getInstallationOrderList.total || 0;
  810. // 获取处理工单数量
  811. this.pendingCount =
  812. response.data.getInstallationOrderToDoNum.list[0].num;
  813. this.loading = false;
  814. })
  815. .catch((error) => {
  816. console.error("获取服务工单列表失败:", error);
  817. this.loading = false;
  818. });
  819. },
  820. // 获取下拉框选项数据
  821. getSelectOptions() {
  822. const send_select_list = [
  823. {
  824. name: "getUsersSelect",
  825. offset: 0,
  826. pagecount: 0,
  827. parammaps: {
  828. enable: "1",
  829. },
  830. },
  831. {
  832. name: "getDictListSelect",
  833. offset: 0,
  834. pagecount: 0,
  835. parammaps: {
  836. pid: "79",
  837. },
  838. },
  839. ];
  840. GetDataByNames(send_select_list)
  841. .then((response) => {
  842. // 处理服务人员数据
  843. const installerList = response.data.getUsersSelect.list || [];
  844. this.installerOptions = installerList.map((item) => ({
  845. value: item.id,
  846. label: item.name,
  847. }));
  848. console.log("服务人员下拉框", this.installerOptions);
  849. // 处理项目数据
  850. this.projectOptions = response.data.getDictListSelect.list || [];
  851. console.log("项目下拉框", this.projectOptions);
  852. })
  853. .catch((error) => {
  854. console.error("获取下拉框数据失败:", error);
  855. });
  856. },
  857. handleQuery() {
  858. this.queryParams.pageNum = 1;
  859. this.getList();
  860. },
  861. // 获取格式化的日期时间
  862. getFormattedDateTime() {
  863. const now = new Date();
  864. const year = now.getFullYear();
  865. const month = String(now.getMonth() + 1).padStart(2, "0");
  866. const day = String(now.getDate()).padStart(2, "0");
  867. const hours = String(now.getHours()).padStart(2, "0");
  868. const minutes = String(now.getMinutes()).padStart(2, "0");
  869. const seconds = String(now.getSeconds()).padStart(2, "0");
  870. return `${year}${month}${day}${hours}${minutes}${seconds}`;
  871. },
  872. // 导出功能
  873. async onExportClick() {
  874. try {
  875. // 使用现有的查询参数,但不分页
  876. const exportParams = {
  877. name: "getInstallationOrderList",
  878. returntype: "Map",
  879. parammaps: {},
  880. };
  881. const response = await GetDataByName(exportParams);
  882. if (response.msg !== "ok") {
  883. this.$message.error("获取导出数据失败");
  884. return;
  885. }
  886. const data = response.data.list || [];
  887. if (data.length === 0) {
  888. this.$message.warning("暂无数据可导出");
  889. return;
  890. }
  891. // 定义表头
  892. const headers = [
  893. "序号",
  894. "服务单号",
  895. "下单人",
  896. "下单时间",
  897. "服务人员",
  898. "接单时间",
  899. "客户",
  900. "发货单号",
  901. "合同",
  902. "货品名称",
  903. "计划总量",
  904. "已完成量",
  905. "未完成量",
  906. "预计完成时间",
  907. "实际完成时间",
  908. "距预估完成时间",
  909. "安装进度",
  910. "派单人",
  911. "客户联系人",
  912. "客户联系人电话",
  913. "备注",
  914. ];
  915. // 格式化数据
  916. const rows = data.map((item, index) => [
  917. index + 1,
  918. item.orderNo || "",
  919. item.createName || "",
  920. item.orderTime || "",
  921. item.serviceStaffNames || "",
  922. item.acceptTime || "",
  923. item.customerName || "",
  924. item.deliveryNo || "",
  925. item.contractNo || "",
  926. item.goodsName || "",
  927. item.totalQuantity || "",
  928. item.installedQuantity || "",
  929. item.uninstalledQuantity || "",
  930. item.estimatedCompleteTime || "",
  931. item.actualCompleteTime || "",
  932. item.remainingTime !== null && item.remainingTime !== undefined
  933. ? item.remainingTime < 0
  934. ? `已逾期 ${Math.abs(item.remainingTime)} 天`
  935. : `${item.remainingTime} 天`
  936. : "",
  937. item.progress ? `${item.progress}%` : "0%",
  938. item.dispatcherName || "",
  939. item.contactName || "",
  940. item.contactPhone || "",
  941. item.remark || "",
  942. ]);
  943. // 创建工作簿
  944. const wb = XLSX.utils.book_new();
  945. // 组合表头和数据
  946. const wsData = [headers, ...rows];
  947. // 创建工作表
  948. const ws = XLSX.utils.aoa_to_sheet(wsData);
  949. // 设置列宽
  950. const colWidths = [
  951. { wch: 8 }, // 序号
  952. { wch: 15 }, // 服务单号
  953. { wch: 12 }, // 下单人
  954. { wch: 20 }, // 下单时间
  955. { wch: 15 }, // 服务项目
  956. { wch: 20 }, // 服务人员
  957. { wch: 20 }, // 接单时间
  958. { wch: 20 }, // 客户
  959. { wch: 15 }, // 发货单号
  960. { wch: 15 }, // 合同
  961. { wch: 30 }, // 货品名称
  962. { wch: 12 }, // 计划总量
  963. { wch: 12 }, // 已完成量
  964. { wch: 12 }, // 未完成量
  965. { wch: 20 }, // 预计完成时间
  966. { wch: 20 }, // 实际完成时间
  967. { wch: 20 }, // 距预估完成时间
  968. { wch: 12 }, // 安装进度
  969. { wch: 12 }, // 派单人
  970. { wch: 15 }, // 客户联系人
  971. { wch: 15 }, // 客户联系人电话
  972. { wch: 30 }, // 备注
  973. ];
  974. ws["!cols"] = colWidths;
  975. // 添加工作表到工作簿
  976. XLSX.utils.book_append_sheet(wb, ws, "服务工单列表");
  977. // 生成文件名
  978. const fileName = `服务工单列表_${this.getFormattedDateTime()}.xlsx`;
  979. // 导出文件
  980. XLSX.writeFile(wb, fileName);
  981. this.$message.success("导出成功");
  982. } catch (error) {
  983. console.error("导出失败:", error);
  984. this.$message.error("导出失败");
  985. }
  986. },
  987. handleAdd() {
  988. this.dialogVisible = true;
  989. },
  990. handleViewTypeChange(value) {
  991. this.queryParams.statusName = value;
  992. this.queryParams.serviceStaffIds = "";
  993. this.queryParams.customerName = "";
  994. this.queryParams.projectName = "";
  995. this.queryParams.projectId = "";
  996. this.queryParams.orderTimeRange = [];
  997. this.queryParams.completeTimeRange = [];
  998. this.queryParams.orderNo = "";
  999. this.queryParams.deliveryNo = "";
  1000. this.queryParams.contractNo = "";
  1001. this.getList();
  1002. },
  1003. getStatusType(statusName) {
  1004. const statusMap = {
  1005. 未接单: "info",
  1006. 已接单: "primary",
  1007. 处理中: "primary",
  1008. 已完成: "success",
  1009. 已完成未验收: "warning",
  1010. 接单驳回: "danger",
  1011. };
  1012. return statusMap[statusName] || "info";
  1013. },
  1014. handleView(row) {
  1015. this.currentRow = row;
  1016. this.viewDialogVisible = true;
  1017. },
  1018. // 处理编辑按钮点击
  1019. handleEdit(row) {
  1020. this.currentRow = row;
  1021. this.editDialogVisible = true;
  1022. },
  1023. // 删除
  1024. async handleDelete(row) {
  1025. await this.$confirm("是否确认删除服务工单?", "警告", {
  1026. type: "warning",
  1027. });
  1028. try {
  1029. const params = {
  1030. common: {
  1031. returnmap: "0",
  1032. },
  1033. data: [
  1034. {
  1035. name: "deleteInstallationOrderById",
  1036. type: "e",
  1037. parammaps: {
  1038. orderId: row.id,
  1039. },
  1040. }, // 在 handleDelete 方法的 params.data 数组中添加:
  1041. {
  1042. name: "insertInstallationOrderProcessLog",
  1043. type: "e",
  1044. parammaps: {
  1045. orderId: row.id,
  1046. operationType: "delete",
  1047. operationUserId: this.currentUser.id,
  1048. operationUserName: this.currentUser.name,
  1049. beforeStatus: row.statusName,
  1050. afterStatus: row.statusName,
  1051. operationContent: "删除服务工单",
  1052. },
  1053. },
  1054. ],
  1055. };
  1056. const response = await ExecDataByConfig(params);
  1057. if (response.msg === "ok") {
  1058. this.$message.success("删除成功");
  1059. this.getList();
  1060. } else {
  1061. this.$message.error(response.msg || "删除失败");
  1062. }
  1063. } catch (error) {
  1064. console.error("删除失败:", error);
  1065. this.$message.error("删除失败");
  1066. }
  1067. },
  1068. // 派单
  1069. async handleDispatch(row) {
  1070. if (!row.estimatedCompleteTime) {
  1071. this.$message.error("当前工单没有预计完成时间,无法派单,请编辑工单");
  1072. return;
  1073. }
  1074. this.currentRow = row;
  1075. this.dispatchDialogVisible = true;
  1076. },
  1077. // 驳回
  1078. async handleReject(row) {
  1079. this.currentRow = row;
  1080. this.rejectDialogVisible = true;
  1081. },
  1082. // 接单
  1083. async handleAccept(row) {
  1084. this.currentRow = row;
  1085. this.acceptDialogVisible = true;
  1086. },
  1087. // 每日填写
  1088. async handleDailyWrite(row) {
  1089. this.currentRow = row;
  1090. this.dailyWriteDialogVisible = true;
  1091. },
  1092. // 处理填写确认
  1093. async handleDailyWriteConfirm(formData) {
  1094. try {
  1095. const params = {
  1096. common: {
  1097. returnmap: "0",
  1098. },
  1099. data: [
  1100. {
  1101. name: "deleteInstallationDailyWriteByDates",
  1102. type: "e",
  1103. parammaps: {
  1104. datas: formData.dates.join(","),
  1105. userId: this.currentUser.id,
  1106. orderId: this.currentRow.id,
  1107. },
  1108. },
  1109. {
  1110. name: "submitInstallationDailyWrite",
  1111. resultmaps: {
  1112. list: formData.records.map((item) => ({
  1113. installDate: this.formatDate(item.date),
  1114. goodsName: item.goodsName,
  1115. goodsId: item.goodsId,
  1116. todayQuantity: item.todayQuantity || 0,
  1117. remark: item.installRemark,
  1118. projectId: item.projectId,
  1119. projectName: item.projectName,
  1120. })),
  1121. },
  1122. children: [
  1123. {
  1124. name: "insertInstallationDailyWrite",
  1125. type: "e",
  1126. parammaps: {
  1127. orderId: this.currentRow.id,
  1128. installUserId: this.currentUser.id,
  1129. installUserName: this.currentUser.name,
  1130. goodsName: "@submitInstallationDailyWrite.goodsName",
  1131. installDate: "@submitInstallationDailyWrite.installDate",
  1132. goodsId: "@submitInstallationDailyWrite.goodsId",
  1133. todayQuantity:
  1134. "@submitInstallationDailyWrite.todayQuantity",
  1135. remark: "@submitInstallationDailyWrite.remark",
  1136. projectId: "@submitInstallationDailyWrite.projectId",
  1137. projectName: "@submitInstallationDailyWrite.projectName",
  1138. },
  1139. },
  1140. ],
  1141. },
  1142. {
  1143. name: "refreshInstallationOrderDetailQuantity",
  1144. type: "e",
  1145. parammaps: {
  1146. orderId: this.currentRow.id,
  1147. },
  1148. },
  1149. {
  1150. name: "refreshInstallationOrderProcessByOrderId",
  1151. type: "e",
  1152. parammaps: {
  1153. orderId: this.currentRow.id,
  1154. },
  1155. },
  1156. {
  1157. name: "updateDailyDetailYesterdayAndBeforeQuantity",
  1158. type: "e",
  1159. parammaps: {
  1160. orderId: this.currentRow.id,
  1161. },
  1162. }, // 在 handleDailyWriteConfirm 方法的 params.data 数组中添加:
  1163. {
  1164. name: "insertInstallationOrderProcessLog",
  1165. type: "e",
  1166. parammaps: {
  1167. orderId: this.currentRow.id,
  1168. operationType: "write",
  1169. operationUserId: this.currentUser.id,
  1170. operationUserName: this.currentUser.name,
  1171. beforeStatus: this.currentRow.statusName,
  1172. afterStatus: this.currentRow.statusName,
  1173. operationContent: `填写日期:${formData.dates.join(
  1174. ","
  1175. )}|产品名称:${formData.records
  1176. .map((item) => item.goodsName)
  1177. .join(",")}|数量:${formData.records.reduce(
  1178. (sum, item) => sum + (parseInt(item.todayQuantity) || 0),
  1179. 0
  1180. )}`,
  1181. },
  1182. },
  1183. ],
  1184. };
  1185. const response = await ExecDataByConfig(params);
  1186. if (response.msg === "ok") {
  1187. this.$message.success("提交成功");
  1188. this.dailyWriteDialogVisible = false;
  1189. this.getList();
  1190. } else {
  1191. this.$message.error(response.msg || "提交失败");
  1192. }
  1193. } catch (error) {
  1194. console.error("提交失败:", error);
  1195. this.$message.error("提交失败");
  1196. }
  1197. },
  1198. // 完成
  1199. async handleComplete(row) {
  1200. await this.$confirm("是否确认完成当前服务工单?", "警告", {
  1201. type: "warning",
  1202. });
  1203. try {
  1204. const params = {
  1205. common: {
  1206. returnmap: "0",
  1207. },
  1208. data: [
  1209. {
  1210. name: "completeInstallationOrder",
  1211. type: "e",
  1212. parammaps: {
  1213. orderId: row.id,
  1214. },
  1215. }, // 在 handleComplete 方法的 params.data 数组中添加:
  1216. {
  1217. name: "insertInstallationOrderProcessLog",
  1218. type: "e",
  1219. parammaps: {
  1220. orderId: row.id,
  1221. operationType: "complete",
  1222. operationUserId: this.currentUser.id,
  1223. operationUserName: this.currentUser.name,
  1224. beforeStatus: row.statusName,
  1225. afterStatus: "已完成未验收",
  1226. operationContent: "完成服务工单",
  1227. },
  1228. },
  1229. ],
  1230. };
  1231. const response = await ExecDataByConfig(params);
  1232. if (response.msg === "ok") {
  1233. this.$message.success("操作成功");
  1234. this.getList();
  1235. } else {
  1236. this.$message.error(response.msg || "操作失败");
  1237. }
  1238. } catch (error) {
  1239. console.error("操作失败:", error);
  1240. this.$message.error("操作失败");
  1241. }
  1242. },
  1243. // 验收-获取客户联系人
  1244. async handleCheck(row) {
  1245. this.currentRow = row;
  1246. this.checkDialogVisible = true;
  1247. const send_data = {
  1248. name: "getContacts",
  1249. returntype: "Map",
  1250. parammaps: {
  1251. customerId: row.customerId,
  1252. },
  1253. };
  1254. GetDataByName(send_data)
  1255. .then((response) => {
  1256. this.customerContactOptions = response.data.list || [];
  1257. console.log("客户联系人选项", this.customerContactOptions);
  1258. })
  1259. .catch((error) => {
  1260. console.error("获取客户联系人选项失败:", error);
  1261. });
  1262. },
  1263. // 处理验收确认
  1264. async handleCheckConfirm(formData) {
  1265. try {
  1266. const params = {
  1267. common: {
  1268. returnmap: "0",
  1269. },
  1270. data: [],
  1271. };
  1272. if (formData.isNewContact) {
  1273. params.data.push({
  1274. name: "insertContacts",
  1275. type: "e",
  1276. parammaps: {
  1277. customerId: this.currentRow.customerId,
  1278. contactName: formData.customContact,
  1279. telephone: formData.telephone,
  1280. },
  1281. });
  1282. }
  1283. // 处理验收图片
  1284. if (formData.checkImage.length > 0) {
  1285. params.data.push({
  1286. name: "insertInstallationOrderImages",
  1287. resultmaps: {
  1288. list: formData.checkImage.map((item, index) => ({
  1289. imageId: item,
  1290. imageType: "check_image",
  1291. })),
  1292. },
  1293. children: [
  1294. {
  1295. name: "insertInstallationOrderImages",
  1296. type: "e",
  1297. parammaps: {
  1298. orderId: this.currentRow.id,
  1299. imageId: "@insertInstallationOrderImages.imageId",
  1300. imageType: "@insertInstallationOrderImages.imageType",
  1301. },
  1302. },
  1303. ],
  1304. });
  1305. }
  1306. params.data.push(
  1307. {
  1308. name: "checkInstallationOrder",
  1309. type: "e",
  1310. parammaps: {
  1311. orderId: formData.orderId,
  1312. checkUserId: formData.serviceStaffId,
  1313. checkUserName:
  1314. this.installerOptions.find(
  1315. (item) => item.id === formData.serviceStaffId
  1316. )?.label || "",
  1317. contactId: formData.isNewContact
  1318. ? "@insertContacts.LastInsertId"
  1319. : formData.customContact,
  1320. checkDate: formData.checkDate,
  1321. },
  1322. },
  1323. {
  1324. name: "insertInstallationOrderProcessLog",
  1325. type: "e",
  1326. parammaps: {
  1327. orderId: this.currentRow.id,
  1328. operationType: "check",
  1329. operationUserId: this.currentUser.id,
  1330. operationUserName: this.currentUser.name,
  1331. beforeStatus: this.currentRow.statusName,
  1332. afterStatus: "已完成",
  1333. operationContent: "验收服务工单",
  1334. },
  1335. }
  1336. );
  1337. const response = await ExecDataByConfig(params);
  1338. if (response.msg === "ok") {
  1339. this.$message.success("验收成功");
  1340. this.checkDialogVisible = false;
  1341. this.getList();
  1342. } else {
  1343. this.$message.error(response.msg || "验收失败");
  1344. }
  1345. } catch (error) {
  1346. console.error("验收失败:", error);
  1347. this.$message.error("验收失败");
  1348. }
  1349. },
  1350. // 检查按钮权限
  1351. checkButtonPermission(buttonPath) {
  1352. return this.buttonMenu?.some((item) => item.path === buttonPath) || false;
  1353. },
  1354. // 格式化日期为yyyy-MM-dd
  1355. formatDate(date) {
  1356. if (!date) return "";
  1357. const d = new Date(date);
  1358. const year = d.getFullYear();
  1359. const month = String(d.getMonth() + 1).padStart(2, "0");
  1360. const day = String(d.getDate()).padStart(2, "0");
  1361. return `${year}-${month}-${day}`;
  1362. },
  1363. // 处理编辑成功
  1364. handleEditSuccess() {
  1365. this.getList(); // 刷新列表数据
  1366. },
  1367. getProgressBarColor(status) {
  1368. // 根据状态返回对应的颜色
  1369. const statusColorMap = {
  1370. 未接单: "#909399", // 灰色
  1371. 已接单: "#409eff", // 蓝色
  1372. 处理中: "#409eff", // 蓝色
  1373. 已完成未验收: "#e6a23c", // 橙色
  1374. 已完成: "#67c23a", // 绿色
  1375. 接单驳回: "#f56c6c", // 红色
  1376. };
  1377. return statusColorMap[status] || "#909399";
  1378. },
  1379. showProgressTooltip(event, percentage) {
  1380. // 先移除可能存在的旧tooltip
  1381. this.hideProgressTooltip();
  1382. this.progressTooltip = document.createElement("div");
  1383. this.progressTooltip.className = "progress-tooltip";
  1384. this.progressTooltip.textContent = (() => {
  1385. const num = parseFloat(percentage);
  1386. if (!Number.isFinite(num)) return "0%";
  1387. if (num < 0) return "0%";
  1388. if (num > 100) return "100%";
  1389. return num.toFixed(0) + "%";
  1390. })();
  1391. document.body.appendChild(this.progressTooltip);
  1392. const rect = event.target.getBoundingClientRect();
  1393. const tooltipRect = this.progressTooltip.getBoundingClientRect();
  1394. this.progressTooltip.style.left =
  1395. rect.left + rect.width / 2 - tooltipRect.width / 2 + "px";
  1396. this.progressTooltip.style.top = rect.top - tooltipRect.height - 8 + "px";
  1397. // 使用 requestAnimationFrame 确保 DOM 更新后再添加显示类
  1398. requestAnimationFrame(() => {
  1399. if (this.progressTooltip) {
  1400. this.progressTooltip.classList.add("show");
  1401. }
  1402. });
  1403. },
  1404. hideProgressTooltip() {
  1405. if (this.progressTooltip) {
  1406. // 移除显示类
  1407. this.progressTooltip.classList.remove("show");
  1408. // 确保移除所有可能存在的旧tooltip
  1409. const oldTooltips = document.querySelectorAll(".progress-tooltip");
  1410. oldTooltips.forEach((tooltip) => {
  1411. if (tooltip.parentNode) {
  1412. tooltip.parentNode.removeChild(tooltip);
  1413. }
  1414. });
  1415. this.progressTooltip = null;
  1416. }
  1417. },
  1418. // 处理驳回
  1419. async handleRejectConfirm(form) {
  1420. try {
  1421. const params = {
  1422. common: {
  1423. returnmap: "0",
  1424. },
  1425. data: [
  1426. {
  1427. name: "rejectInstallationOrder",
  1428. type: "e",
  1429. parammaps: {
  1430. orderId: this.currentRow.id,
  1431. rejectReason: form.rejectReason,
  1432. },
  1433. },
  1434. {
  1435. name: "insertInstallationOrderProcessLog",
  1436. type: "e",
  1437. parammaps: {
  1438. orderId: this.currentRow.id,
  1439. operationType: "reject",
  1440. operationUserId: this.currentUser.id,
  1441. operationUserName: this.currentUser.name,
  1442. beforeStatus: this.currentRow.statusName,
  1443. afterStatus: "接单驳回",
  1444. operationContent: `驳回原因:${form.rejectReason}`,
  1445. },
  1446. },
  1447. ],
  1448. };
  1449. const response = await ExecDataByConfig(params);
  1450. if (response.msg === "ok") {
  1451. this.$message.success("驳回成功");
  1452. this.rejectDialogVisible = false;
  1453. this.viewDialogVisible = false;
  1454. this.getList();
  1455. } else {
  1456. this.$message.error(response.msg || "驳回失败");
  1457. }
  1458. } catch (error) {
  1459. console.error("驳回失败:", error);
  1460. this.$message.error("驳回失败");
  1461. }
  1462. },
  1463. // 处理接单
  1464. async handleAcceptConfirm(form) {
  1465. try {
  1466. const params = {
  1467. common: {
  1468. returnmap: "0",
  1469. },
  1470. data: [
  1471. {
  1472. name: "acceptInstallationOrder",
  1473. type: "e",
  1474. parammaps: {
  1475. orderId: form.orderId,
  1476. acceptId: this.currentUser.id,
  1477. acceptName: this.currentUser.name,
  1478. },
  1479. },
  1480. {
  1481. name: "insertInstallationOrderProcessLog",
  1482. type: "e",
  1483. parammaps: {
  1484. orderId: this.currentRow.id,
  1485. operationType: "accept",
  1486. operationUserId: this.currentUser.id,
  1487. operationUserName: this.currentUser.name,
  1488. beforeStatus: this.currentRow.statusName,
  1489. afterStatus: "处理中",
  1490. operationContent: "接单处理",
  1491. },
  1492. },
  1493. ],
  1494. };
  1495. const response = await ExecDataByConfig(params);
  1496. if (response.msg === "ok") {
  1497. this.$message.success("接单成功");
  1498. this.acceptDialogVisible = false;
  1499. this.viewDialogVisible = false;
  1500. this.getList();
  1501. } else {
  1502. this.$message.error(response.msg || "接单失败");
  1503. }
  1504. } catch (error) {
  1505. console.error("接单失败:", error);
  1506. this.$message.error("接单失败");
  1507. }
  1508. },
  1509. // 设置行的class
  1510. tableRowClassName({ row }) {
  1511. return row.statusName === "已驳回" ? "rejected-row" : "";
  1512. },
  1513. // 判断当前用户是否在服务人员列表中
  1514. isCurrentUserInServiceStaff(serviceStaffIds) {
  1515. console.log("serviceStaffIds", serviceStaffIds);
  1516. if (!serviceStaffIds) return false;
  1517. const currentUserId = parseInt(this.currentUser.id);
  1518. const staffIds = serviceStaffIds
  1519. .split(",")
  1520. .map((id) => parseInt(id.trim()));
  1521. return staffIds.includes(currentUserId);
  1522. },
  1523. // 判断当前用户是否是创建人
  1524. isCurrentUserInCreateId(createId) {
  1525. if (!createId) return false;
  1526. return createId === parseInt(this.currentUser.id);
  1527. },
  1528. /**
  1529. * 按钮显示控制逻辑
  1530. * @param {Object} button - 按钮配置对象
  1531. * @param {Object} row - 行数据
  1532. * @returns {boolean} - 是否显示按钮
  1533. */
  1534. getButtonVisible(button, row) {
  1535. // 权限检查
  1536. if (!this.checkButtonPermission(button.permission)) {
  1537. return false;
  1538. }
  1539. // 状态检查
  1540. const statusMap = {
  1541. view: true, // 查看按钮始终显示
  1542. edit: ["未接单", "已接单", "处理中", "接单驳回"].includes(
  1543. row.statusName
  1544. ),
  1545. delete: ["未接单", "接单驳回"].includes(row.statusName),
  1546. dispatch: ["未接单", "已接单", "处理中"].includes(row.statusName),
  1547. reject: row.statusName === "未接单",
  1548. accept: row.statusName === "未接单",
  1549. write: ["已接单", "处理中"].includes(row.statusName),
  1550. complete: ["已接单", "处理中"].includes(row.statusName),
  1551. check: row.statusName === "已完成未验收",
  1552. };
  1553. if (!statusMap[button.type]) {
  1554. return false;
  1555. }
  1556. // 角色检查
  1557. const roleMap = {
  1558. view: true, // 查看按钮所有人可见
  1559. edit:
  1560. this.isCurrentUserInCreateId(row.createId) ||
  1561. this.isCurrentUserInServiceStaff(row.serviceStaffIds),
  1562. delete: this.isCurrentUserInCreateId(row.createId),
  1563. dispatch: this.isCurrentUserInCreateId(row.createId),
  1564. reject: this.isCurrentUserInServiceStaff(row.serviceStaffIds),
  1565. accept: this.isCurrentUserInServiceStaff(row.serviceStaffIds),
  1566. write: this.isCurrentUserInServiceStaff(row.serviceStaffIds),
  1567. complete: this.isCurrentUserInServiceStaff(row.serviceStaffIds),
  1568. check: true, // 验收按钮所有人可见
  1569. };
  1570. return roleMap[button.type] || false;
  1571. },
  1572. },
  1573. };
  1574. </script>
  1575. <style lang="scss" scoped>
  1576. .app-container {
  1577. padding: 15px;
  1578. background-color: #f5f7fa;
  1579. .el-table {
  1580. // 表格整体样式
  1581. border-radius: 4px;
  1582. box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  1583. // 表头样式
  1584. :deep(.el-table__header-wrapper) {
  1585. position: sticky;
  1586. top: 0;
  1587. z-index: 2;
  1588. .el-table__header {
  1589. thead {
  1590. tr {
  1591. th {
  1592. background-color: #f5f7fa;
  1593. color: #333;
  1594. font-weight: 500;
  1595. height: 44px;
  1596. padding: 8px 0;
  1597. &.is-leaf {
  1598. border-bottom: 1px solid #e8e8e8;
  1599. }
  1600. }
  1601. }
  1602. }
  1603. }
  1604. }
  1605. // 表格内容样式
  1606. :deep(.el-table__body-wrapper) {
  1607. overflow-y: auto;
  1608. &::-webkit-scrollbar {
  1609. width: 6px;
  1610. height: 6px;
  1611. }
  1612. &::-webkit-scrollbar-thumb {
  1613. border-radius: 3px;
  1614. background: #c0c4cc;
  1615. }
  1616. &::-webkit-scrollbar-track {
  1617. border-radius: 3px;
  1618. background: #f5f7fa;
  1619. }
  1620. .el-table__body {
  1621. td {
  1622. padding: 12px 0;
  1623. height: 50px;
  1624. }
  1625. }
  1626. }
  1627. // 重写表格行样式
  1628. ::v-deep(.el-table__body) {
  1629. tr.rejected-row {
  1630. background-color: rgba(245, 108, 108, 0.1) !important;
  1631. }
  1632. tr.rejected-row td {
  1633. background-color: rgba(245, 108, 108, 0.1) !important;
  1634. }
  1635. tr.rejected-row:hover td {
  1636. background-color: rgba(245, 108, 108, 0.15) !important;
  1637. }
  1638. tr.rejected-row.el-table__row--striped td {
  1639. background-color: rgba(245, 108, 108, 0.1) !important;
  1640. }
  1641. tr.rejected-row.current-row td {
  1642. background-color: rgba(245, 108, 108, 0.1) !important;
  1643. }
  1644. }
  1645. // 鼠标悬停效果
  1646. :deep(.el-table__body tr:hover) {
  1647. td {
  1648. background-color: #f0f7ff !important;
  1649. }
  1650. }
  1651. // 移除之前的按钮样式,添加新的美化样式
  1652. :deep(.el-button--mini) {
  1653. margin: 2px 3px;
  1654. padding: 6px 12px;
  1655. height: 28px;
  1656. border-radius: 4px;
  1657. font-size: 12px;
  1658. font-weight: 500;
  1659. transition: all 0.3s;
  1660. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  1661. &:hover {
  1662. transform: translateY(-1px);
  1663. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  1664. }
  1665. &.el-button--primary {
  1666. background-color: #409eff;
  1667. border-color: #409eff;
  1668. &:hover {
  1669. background-color: #66b1ff;
  1670. border-color: #66b1ff;
  1671. }
  1672. }
  1673. &.el-button--success {
  1674. background-color: #67c23a;
  1675. border-color: #67c23a;
  1676. &:hover {
  1677. background-color: #85ce61;
  1678. border-color: #85ce61;
  1679. }
  1680. }
  1681. &.el-button--warning {
  1682. background-color: #e6a23c;
  1683. border-color: #e6a23c;
  1684. &:hover {
  1685. background-color: #ebb563;
  1686. border-color: #ebb563;
  1687. }
  1688. }
  1689. &.el-button--danger {
  1690. background-color: #f56c6c;
  1691. border-color: #f56c6c;
  1692. &:hover {
  1693. background-color: #f78989;
  1694. border-color: #f78989;
  1695. }
  1696. }
  1697. &.el-button--info {
  1698. background-color: #909399;
  1699. border-color: #909399;
  1700. &:hover {
  1701. background-color: #a6a9ad;
  1702. border-color: #a6a9ad;
  1703. }
  1704. }
  1705. // 所有按钮文字颜色设为白色
  1706. color: #fff;
  1707. // 禁用状态样式
  1708. &.is-disabled {
  1709. &:hover {
  1710. transform: none;
  1711. box-shadow: none;
  1712. }
  1713. }
  1714. }
  1715. // 进度条样式
  1716. :deep(.progress-wrapper) {
  1717. padding: 6px 0;
  1718. width: 100%;
  1719. height: 100%;
  1720. cursor: pointer;
  1721. .progress-container {
  1722. display: flex;
  1723. align-items: center;
  1724. justify-content: center;
  1725. .progress-bar {
  1726. width: 100%;
  1727. padding: 0 20px;
  1728. .custom-progress {
  1729. width: 100%;
  1730. height: 12px;
  1731. background-color: #f5f7fa;
  1732. border: 1px solid #e4e7ed;
  1733. border-radius: 100px;
  1734. overflow: hidden;
  1735. position: relative;
  1736. .progress-inner {
  1737. height: 100%;
  1738. transition: all 0.3s ease;
  1739. border-radius: 100px;
  1740. position: relative;
  1741. min-width: 2px;
  1742. .progress-text {
  1743. position: absolute;
  1744. right: 6px;
  1745. top: 50%;
  1746. transform: translateY(-50%);
  1747. color: #fff;
  1748. font-size: 11px;
  1749. line-height: 1;
  1750. text-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
  1751. white-space: nowrap;
  1752. font-weight: 500;
  1753. }
  1754. }
  1755. .progress-text-zero {
  1756. position: absolute;
  1757. left: 10px;
  1758. top: 50%;
  1759. transform: translateY(-50%);
  1760. color: #909399;
  1761. font-size: 11px;
  1762. line-height: 1;
  1763. white-space: nowrap;
  1764. font-weight: 500;
  1765. background-color: #fff;
  1766. padding: 0 4px;
  1767. border-radius: 10px;
  1768. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  1769. }
  1770. }
  1771. }
  1772. }
  1773. }
  1774. // 状态标签样式
  1775. :deep(.el-tag) {
  1776. border-radius: 2px;
  1777. padding: 0 8px;
  1778. height: 24px;
  1779. line-height: 22px;
  1780. // 已完成未验收状态特殊样式
  1781. &.el-tag--warning {
  1782. &:not(.is-hit) {
  1783. background-color: #fdf6ec;
  1784. border-color: #faecd8;
  1785. color: #e6a23c;
  1786. &[type="warning"] {
  1787. background-color: #fdf6ec;
  1788. border-color: #faecd8;
  1789. color: #e6a23c;
  1790. }
  1791. }
  1792. }
  1793. // 已驳回状态样式
  1794. &.el-tag--danger {
  1795. background-color: #fef0f0;
  1796. border-color: #fde2e2;
  1797. color: #f56c6c;
  1798. }
  1799. // 其他状态保持原样
  1800. &.el-tag--success {
  1801. background-color: #f0f9eb;
  1802. border-color: #e1f3d8;
  1803. color: #67c23a;
  1804. }
  1805. &.el-tag--primary {
  1806. background-color: #ecf5ff;
  1807. border-color: #d9ecff;
  1808. color: #409eff;
  1809. }
  1810. }
  1811. // 空数据样式
  1812. :deep(.el-table__empty-block) {
  1813. min-height: 200px;
  1814. .el-table__empty-text {
  1815. color: #909399;
  1816. font-size: 14px;
  1817. line-height: 200px;
  1818. text-align: left;
  1819. }
  1820. }
  1821. // 固定列样式
  1822. :deep(.el-table__fixed-right) {
  1823. height: 100% !important;
  1824. box-shadow: -2px 0 8px rgba(0, 0, 0, 0.05);
  1825. right: 0 !important;
  1826. }
  1827. // 表格边框样式
  1828. :deep(.el-table--border) {
  1829. border: 1px solid #e8e8e8;
  1830. &::after {
  1831. background-color: #e8e8e8;
  1832. }
  1833. th,
  1834. td {
  1835. border-right: 1px solid #e8e8e8;
  1836. }
  1837. }
  1838. // 下拉菜单项样式
  1839. :deep(.el-dropdown-menu__item) {
  1840. padding: 8px 20px;
  1841. font-size: 13px;
  1842. &:hover {
  1843. background-color: #f5f7fa;
  1844. }
  1845. &.dropdown-primary {
  1846. color: #409eff;
  1847. }
  1848. &.dropdown-success {
  1849. color: #67c23a;
  1850. }
  1851. &.dropdown-warning {
  1852. color: #e6a23c;
  1853. }
  1854. &.dropdown-danger {
  1855. color: #f56c6c;
  1856. }
  1857. &.dropdown-info {
  1858. color: #909399;
  1859. }
  1860. }
  1861. // 更多按钮样式
  1862. :deep(.el-dropdown) {
  1863. margin-left: 2px;
  1864. .el-button {
  1865. display: inline-flex;
  1866. align-items: center;
  1867. justify-content: center;
  1868. i {
  1869. margin-left: 3px;
  1870. }
  1871. }
  1872. }
  1873. }
  1874. // 其他已有样式保持不变
  1875. .pagination-container {
  1876. padding: 15px 0;
  1877. text-align: left;
  1878. }
  1879. .demo-form-inline {
  1880. background-color: #fff;
  1881. padding: 15px 15px;
  1882. border-radius: 4px;
  1883. box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  1884. :deep(.el-form-item) {
  1885. margin-bottom: 0;
  1886. margin-top: 3px;
  1887. margin-bottom: 3px;
  1888. margin-right: 5px;
  1889. display: inline-flex;
  1890. align-items: center;
  1891. vertical-align: top;
  1892. }
  1893. :deep(.el-input__inner) {
  1894. height: 32px;
  1895. line-height: 32px;
  1896. }
  1897. :deep(.el-range-editor.el-input__inner) {
  1898. padding: 0 10px;
  1899. margin: 0;
  1900. }
  1901. :deep(.el-button),
  1902. :deep(.el-radio-button__inner) {
  1903. height: 32px;
  1904. padding: 0 15px;
  1905. line-height: 30px;
  1906. vertical-align: top;
  1907. }
  1908. :deep(.el-radio-group) {
  1909. display: inline-flex;
  1910. vertical-align: top;
  1911. }
  1912. :deep(.el-input) {
  1913. margin-left: 0 !important; // 强制覆盖左边距
  1914. }
  1915. :deep(.el-form-item__content) {
  1916. line-height: 32px;
  1917. display: inline-flex;
  1918. align-items: center;
  1919. }
  1920. :deep(.pending-badge) {
  1921. position: absolute;
  1922. top: -8px;
  1923. right: -3px;
  1924. z-index: 2;
  1925. .el-badge__content {
  1926. height: 16px;
  1927. line-height: 16px;
  1928. padding: 0 4px;
  1929. border: none;
  1930. font-size: 12px;
  1931. background-color: #f56c6c;
  1932. }
  1933. }
  1934. :deep(.el-range-separator) {
  1935. color: #606266;
  1936. line-height: 30px;
  1937. font-size: 13px;
  1938. padding: 0 3px;
  1939. }
  1940. }
  1941. :deep(.el-loading-mask) {
  1942. .el-loading-spinner {
  1943. .el-icon-loading {
  1944. font-size: 30px;
  1945. color: #409eff;
  1946. }
  1947. .el-loading-text {
  1948. font-size: 14px;
  1949. margin-top: 10px;
  1950. color: #606266;
  1951. }
  1952. }
  1953. }
  1954. // 货品标签样式
  1955. :deep(.product-tags) {
  1956. display: flex;
  1957. flex-wrap: wrap;
  1958. gap: 4px;
  1959. justify-content: center;
  1960. padding: 2px;
  1961. .el-tag {
  1962. margin: 2px;
  1963. max-width: 150px;
  1964. white-space: normal;
  1965. height: auto;
  1966. padding: 2px 6px;
  1967. line-height: 16px;
  1968. word-break: break-all;
  1969. text-align: center;
  1970. &.long-text {
  1971. font-size: 11px;
  1972. }
  1973. }
  1974. }
  1975. // 服务人员标签样式
  1976. :deep(.staff-tags) {
  1977. display: flex;
  1978. flex-direction: column;
  1979. align-items: center;
  1980. gap: 2px;
  1981. padding: 1px;
  1982. .el-tag {
  1983. margin: 1px;
  1984. min-width: 70px;
  1985. max-width: 100px;
  1986. white-space: nowrap;
  1987. overflow: hidden;
  1988. text-overflow: ellipsis;
  1989. height: 20px;
  1990. padding: 0 6px;
  1991. line-height: 20px;
  1992. font-size: 11px;
  1993. &.long-text {
  1994. font-size: 10px;
  1995. }
  1996. }
  1997. }
  1998. // 时间显示样式
  1999. :deep(.normal-time) {
  2000. color: #606266;
  2001. font-size: 13px;
  2002. }
  2003. :deep(.warning-time) {
  2004. color: #f56c6c;
  2005. font-size: 15px;
  2006. font-weight: bold;
  2007. }
  2008. :deep(.overdue-time) {
  2009. color: #f56c6c;
  2010. font-size: 15px;
  2011. font-weight: bold;
  2012. }
  2013. .order-no-link {
  2014. font-size: 13px;
  2015. font-weight: 500;
  2016. &:hover {
  2017. color: #66b1ff;
  2018. }
  2019. &:active {
  2020. color: #3a8ee6;
  2021. }
  2022. }
  2023. }
  2024. </style>
  2025. <style lang="scss">
  2026. .progress-tooltip {
  2027. position: fixed;
  2028. background: rgba(0, 0, 0, 0.8);
  2029. color: #fff;
  2030. padding: 4px 8px;
  2031. border-radius: 4px;
  2032. font-size: 12px;
  2033. pointer-events: none;
  2034. z-index: 9999;
  2035. opacity: 0;
  2036. transform: translateY(5px);
  2037. transition: all 0.2s ease;
  2038. &::after {
  2039. content: "";
  2040. position: absolute;
  2041. bottom: -4px;
  2042. left: 50%;
  2043. transform: translateX(-50%);
  2044. border-left: 4px solid transparent;
  2045. border-right: 4px solid transparent;
  2046. border-top: 4px solid rgba(0, 0, 0, 0.8);
  2047. }
  2048. &.show {
  2049. opacity: 1;
  2050. transform: translateY(0);
  2051. }
  2052. }
  2053. .el-table {
  2054. tbody {
  2055. tr.rejected-row {
  2056. background-color: rgba(245, 108, 108, 0.1) !important;
  2057. td {
  2058. background-color: rgba(245, 108, 108, 0.1) !important;
  2059. }
  2060. &:hover td {
  2061. background-color: rgba(245, 108, 108, 0.15) !important;
  2062. }
  2063. }
  2064. }
  2065. }
  2066. </style>