detail.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. <template>
  2. <view v-if="time">
  3. <view style="background-color: #fff;padding: 20rpx 0;">
  4. <up-steps :current="current" dot activeColor="#0279FE">
  5. <up-steps-item v-for="item in steps" :key="item.title" :title="item.title" />
  6. </up-steps>
  7. </view>
  8. <view class="main">
  9. <view class="billno" style="margin-bottom: -24rpx;">
  10. 工单编号:{{ detail.billno }}
  11. </view>
  12. <view class="row">
  13. <view class="label col-center">服务类型</view>
  14. <view class="servicetype" :style="{
  15. 'background': { '安装': '#E8F8D5', '维修': '#FFE2E5', '清洁': '#E2EBFF', '清洗': '#E2EBFF' }[detail.servicetype] || '#F0F0F0',
  16. 'color': { '安装': '#4B8E00', '维修': '#B00016', '清洁': '#2A5AD9', '清洗': '#2A5AD9' }[detail.servicetype] || '#333333'
  17. }">
  18. {{ detail.servicetype || '--' }}</view>
  19. </view>
  20. <view class="row">
  21. <view class="label">产品品类</view>
  22. <view class="value">{{ detail.class1 || '--' }}</view>
  23. </view>
  24. <view class="row" v-if="detail.servicetype == '维修'">
  25. <view class="label">故障类型</view>
  26. <view class="value">{{ detail.class2 || '--' }}</view>
  27. </view>
  28. <view class="row">
  29. <view class="label">服务地址</view>
  30. <view class="value">{{ detail.province + detail.city + detail.county + detail.address || '--' }}</view>
  31. </view>
  32. <view class="row" style="align-items: center;">
  33. <view class="label justify">联系人</view>
  34. <view class="value phonenumber">{{ detail.scenecontact || '--' }}
  35. <block v-if="detail.scenecontactphonenumber">
  36. <text style="margin: 0 20rpx 0 10rpx;">
  37. {{ detail.scenecontactphonenumber }}
  38. </text>
  39. <My-button :customStyle="{
  40. width: '142rpx',
  41. height: '48rpx',
  42. 'background-color': '#FFFFFF',
  43. 'color': '#3874F6',
  44. borderRadius: '10rpx'
  45. }" :frontIconStyle="{
  46. marginRight: '6rpx',
  47. }" frontIcon="icon-bodadianhua1" text="电话" :phonenumber="detail.scenecontactphonenumber" />
  48. </block>
  49. </view>
  50. </view>
  51. <view class="row">
  52. <view class="label">派单日期</view>
  53. <view class="value">{{ detail.createdate || '--' }}</view>
  54. </view>
  55. <view class="row">
  56. <view class="label">服务需求</view>
  57. <view class="value">{{ detail.remarks || '--' }}</view>
  58. </view>
  59. <view class="row" style="align-items: center;">
  60. <view class="label">工单备注</view>
  61. <view class="value phonenumber">{{ detail.remarks_workorder || '--' }}<My-button v-if="isonlinesales" :customStyle="{
  62. width: '142rpx',
  63. height: '48rpx',
  64. 'background-color': '#FFFFFF',
  65. 'color': '#3874F6',
  66. borderRadius: '10rpx',
  67. 'margin-left': '20rpx'
  68. }" text="修改备注" @onClick="editRemarks" />
  69. </view>
  70. </view>
  71. <up-transition :show="transition" v-if="detail.sku || detail.itemname">
  72. <view class="transition">
  73. <up-divider />
  74. <view class="row" v-if="detail.sku">
  75. <view class="label justify">序列号</view>
  76. <view class="value">{{ detail.sku || '--' }}</view>
  77. </view>
  78. <view class="row" v-if="detail.itemname">
  79. <view class="label">产品品名</view>
  80. <view class="value">{{ detail.itemname || '--' }}</view>
  81. </view>
  82. <view class="row" v-if="detail.model">
  83. <view class="label">产品型号</view>
  84. <view class="value">{{ detail.model || '--' }}</view>
  85. </view>
  86. <view class="row" v-if="detail.cardno">
  87. <view class="label">保修信息</view>
  88. <view class="value">
  89. <text :style="{ color: detail.inqualityguaranteeperiod ? '#70B603' : '#D9001B' }"
  90. style="margin-right: 0rpx;">
  91. {{ detail.inqualityguaranteeperiod ? '在保' : '已过保' }}
  92. </text>
  93. {{ detail.cardno || '' }}
  94. <view v-if="detail.cardno && detail.cardbegdate" style="margin-top: 20rpx;">
  95. {{ detail.cardbegdate || '' }} - {{ detail.cardenddate || '' }}
  96. </view>
  97. </view>
  98. </view>
  99. </view>
  100. </up-transition>
  101. <block v-if="detail.status == '进行中'">
  102. <view class="changeTransition" hover-class="navigator-hover" @click="transition = !transition">
  103. 详细信息
  104. <view class="iconfont icon-dianjizhankai" :class="transition ? 'shrink' : ''" />
  105. </view>
  106. </block>
  107. <!-- 底部按钮 -->
  108. <view v-if="detail.status == '待接单'" class="but-box">
  109. <view class="but-box-item" v-if="detail.customerphonenumber">
  110. <My-button :customStyle="{
  111. 'background-color': '#FFFFFF',
  112. 'color': '#3874F6',
  113. }" frontIcon="icon-bodadianhua1" text="电话" :phonenumber="detail.customerphonenumber" />
  114. </view>
  115. <view class="but-box-item" v-if="detail.team.length" @click="takeOrderShow = true">
  116. <My-button frontIcon="icon-jiedan" text="接单" />
  117. <up-modal negativeTop="100" :show="takeOrderShow" title="是否确认接单?" showCancelButton
  118. @confirm="takeOrders" @cancel="takeOrderShow = false" ref="uModal"
  119. :asyncClose="true"></up-modal>
  120. </view>
  121. <view class="but-box-item" v-else @click="takeOrderShow = true">
  122. <My-button frontIcon="icon-jiedan" text="抢单" />
  123. <up-modal negativeTop="100" :show="takeOrderShow" title="是否确认抢单?" showCancelButton
  124. @confirm="snatchingOrders" @cancel="takeOrderShow = false" ref="uModal"
  125. :asyncClose="true"></up-modal>
  126. </view>
  127. </view>
  128. <!-- 底部按钮 -->
  129. <view v-else-if="detail.status != '已完工'" class="but-box" :style="{
  130. paddingBottom: detail.status == '进行中' ? '40rpx' : '0rpx',
  131. }">
  132. <view class="but-box-item" @click="toChangeMsg">
  133. <My-button :customStyle="{
  134. 'background-color': '#FFFFFF',
  135. 'color': '#3874F6',
  136. }" text="信息修改" />
  137. </view>
  138. <view class="but-box-item" v-if="detail.status == '待开始'"
  139. @click="detail.servicetype == '安装' ? toCMTips() : confirmStartShow = true">
  140. <My-button text="确认开始" />
  141. <up-modal negativeTop="100" title="提示" cancelText="信息修改" confirmText="确认开始" :show="confirmStartShow"
  142. showCancelButton @confirm="confirmStart" @cancel="confirmCancel" ref="uModal"
  143. :asyncClose="true">
  144. <view class="slot-content">
  145. <view>
  146. 请仔细<text style="color: red;">核对服务信息</text>以及<text style="color: red;">填写单程距离</text>
  147. </view>
  148. <view style="margin-top: 20rpx;">
  149. 开始工单后<text style="color: red;">信息不可修改!</text>
  150. </view>
  151. </view>
  152. </up-modal>
  153. </view>
  154. </view>
  155. </view>
  156. <view class="main" v-if="current >= 2 && detail.nodes.length" style="padding: 30rpx;">
  157. <nodes ref="Nodes" :status="detail.status" :nodes="detail.nodes" />
  158. <view class="but-box" v-if="current != 4">
  159. <view class="but-box-item" v-if="detail.status == '提交'">
  160. <My-button @onClick="completion" :disabled="detail.status != '提交'" text="确认完工" />
  161. </view>
  162. <view class="but-box-item" v-else>
  163. <My-button :disabled="detail.status == '提交'" @onClick="submitAndCompletion" text="确认完工" />
  164. </view>
  165. </view>
  166. </view>
  167. </view>
  168. <up-modal :show="shoeEditRemarks" title="工单备注" showCancelButton @confirm="confirmRemarks
  169. " ref="uModal1" :asyncClose="true" @cancel="shoeEditRemarks = false">
  170. <view style="width: 99%;">
  171. <up-textarea v-model="refuseremarks" placeholder="工单备注" count></up-textarea>
  172. </view>
  173. </up-modal>
  174. <view style="height: 50px;" />
  175. </template>
  176. <script setup>
  177. import { ref, reactive, getCurrentInstance } from 'vue';
  178. const { $Http } = getCurrentInstance().proxy;
  179. import { onLoad, onUnload } from '@dcloudio/uni-app';
  180. import nodes from './modules/nodes.vue';
  181. const transition = ref(true);
  182. const Nodes = ref(null);
  183. const current = ref(-1);
  184. const steps = reactive([
  185. { title: '接单', value: '待接单' },
  186. { title: '开始', value: '待开始' },
  187. { title: '进行中', value: '进行中' },
  188. { title: '提交', value: '提交' },
  189. { title: '完工', value: '已完工' }
  190. ]);
  191. let sa_workorderid = 0,
  192. isonlinesales = ref(false);
  193. onLoad((options) => {
  194. sa_workorderid = options.id;
  195. getDetail()
  196. $Http.getDetail = function () {
  197. uni.showToast({ title: "保存成功", icon: 'none' });
  198. getDetail()
  199. }
  200. $Http.basic({
  201. "classname": "common.usercenter.usercenter",
  202. "method": "queryUserMsg",
  203. "content": {}
  204. }).then(res => {
  205. console.log("用户信息", res);
  206. if (res.code === 1) {
  207. if (res.data.agent.type == '网销' || res.data.usertype == 1) {
  208. isonlinesales.value = true;
  209. }
  210. }
  211. })
  212. });
  213. onUnload(() => {
  214. delete $Http.content1
  215. });
  216. const time = ref(new Date().getTime());
  217. let detail = reactive({
  218. servicetype: ""
  219. });
  220. function getDetail() {
  221. $Http.basic({ "id": "20230208140103", "content": { "nocache": true, sa_workorderid } }).then(res => {
  222. console.log("工单详情", res)
  223. $Http.content1 = {
  224. itemid: res.data.itemid,
  225. sku: res.data.sku,
  226. sys_enterpriseid: res.data.sys_enterpriseid,
  227. sa_workorderid: res.data.sa_workorderid,
  228. }
  229. if (res.code !== 1) return uni.showToast({ title: res.msg, icon: 'none' });
  230. try {
  231. res.data.inqualityguaranteeperiod = new Date() >= new Date(res.data.cardbegdate) && new Date() <= new Date(res.data.cardenddate);
  232. } catch (error) {
  233. }
  234. if (res.data.status == '进行中' && detail.servicetype == '') transition.value = false;
  235. time.value = new Date().getTime()
  236. detail = reactive(res.data);
  237. $Http.workDetail = detail;
  238. current.value = steps.findIndex(item => item.value === res.data.status);
  239. })
  240. }
  241. // 工序是否完成
  242. function isAllNodesCompleted() {
  243. // 收集所有必填末级节点
  244. const requiredLeafNodes = [];
  245. // 递归查找末级节点
  246. function findLeafNodes(nodeList) {
  247. for (const node of nodeList) {
  248. // 检查是否为末级节点(没有子节点)
  249. if (!node.child || node.child.length === 0) {
  250. // 检查是否是必填节点(required == 11)
  251. if (node.workpresetjson?.required == 11 && node.status !== "1") {
  252. requiredLeafNodes.push(node);
  253. }
  254. } else {
  255. // 递归检查子节点
  256. findLeafNodes(node.child);
  257. }
  258. }
  259. }
  260. // 开始查找
  261. findLeafNodes(detail.nodes);
  262. // 检查所有必填末级节点是否已完成
  263. const allCompleted = requiredLeafNodes.every(node => node.status === "1");
  264. if (!allCompleted) uni.showModal({
  265. confirmText: '关闭',
  266. content: '还有工序未完成,不能提交!',
  267. showCancel: false
  268. })
  269. return allCompleted;
  270. }
  271. // 提交工单
  272. function submit() {
  273. if (isAllNodesCompleted()) uni.showModal({
  274. confirmText: '继续提交',
  275. content: '请确认工单内容,提交后不可修改!',
  276. success: ({ confirm }) => {
  277. if (confirm) {
  278. $Http.basic({
  279. id: 2025072315401603,
  280. "content": {
  281. "sa_workorderid": detail.sa_workorderid
  282. }
  283. }).then(res => {
  284. console.log("提交结果", res)
  285. uni.showToast({
  286. title: res.code == 1 ? '提交成功' : res.msg,
  287. icon: 'none'
  288. });
  289. let workorderids = uni.getStorageSync('workorderids') || [];
  290. // 把当前工单id从缓存中移除
  291. workorderids = workorderids.filter(item => item != detail.sa_workorderid);
  292. uni.setStorageSync('workorderids', workorderids);
  293. if (res.code == 1) {
  294. getDetail();
  295. transition.value = true;
  296. }
  297. })
  298. }
  299. },
  300. })
  301. }
  302. // 完成工单
  303. function completion() {
  304. if (isAllNodesCompleted()) uni.showModal({
  305. title: '提示',
  306. confirmText: '确认完工',
  307. content: '请确认工单内容,完工后不可修改!',
  308. success: ({ confirm }) => {
  309. if (confirm) {
  310. $Http.basic({
  311. id: 20230209144903,
  312. "content": {
  313. "sa_workorderid": detail.sa_workorderid
  314. }
  315. }).then(res => {
  316. console.log("完工结果", res)
  317. uni.showToast({
  318. title: res.code == 1 ? '完工成功' : res.msg,
  319. icon: 'none'
  320. });
  321. if (res.code == 1) getDetail();
  322. })
  323. }
  324. },
  325. })
  326. }
  327. // 提交直接完工
  328. function submitAndCompletion() {
  329. if (isAllNodesCompleted()) uni.showModal({
  330. title: '提示',
  331. confirmText: '确认完工',
  332. content: '请确认工单内容,完工后不可修改!',
  333. success: ({ confirm }) => {
  334. if (confirm) {
  335. $Http.basic({
  336. id: 2025072315401603,
  337. "content": {
  338. "sa_workorderid": detail.sa_workorderid
  339. }
  340. }).then(res1 => {
  341. console.log("提交", res1)
  342. if (res1.code != 1) return uni.showToast({
  343. title: res1.msg,
  344. icon: 'none'
  345. });
  346. // 把当前工单id从缓存中移除
  347. let workorderids = uni.getStorageSync('workorderids') || [];
  348. workorderids = workorderids.filter(item => item != detail.sa_workorderid);
  349. uni.setStorageSync('workorderids', workorderids);
  350. transition.value = true;
  351. $Http.basic({
  352. id: 20230209144903,
  353. "content": {
  354. "sa_workorderid": detail.sa_workorderid
  355. }
  356. }).then(res => {
  357. console.log("完工结果", res)
  358. uni.showToast({
  359. title: res.code == 1 ? '完工成功' : res.msg,
  360. icon: 'none'
  361. });
  362. getDetail();
  363. })
  364. })
  365. }
  366. },
  367. })
  368. }
  369. // 接单
  370. const takeOrderShow = ref(false);
  371. function takeOrders() {
  372. $Http.basic({
  373. id: 20230210101103,
  374. "content": {
  375. "sa_workorderid": detail.sa_workorderid
  376. }
  377. }).then(res => {
  378. console.log("接单结果", res)
  379. if (res.code == 1) {
  380. takeOrderShow.value = false;
  381. getDetail()
  382. uni.navigateTo({
  383. url: `/pages/workOrder/changeMsg?id=` + detail.sa_workorderid,
  384. success: () => {
  385. uni.showToast({
  386. title: '接单成功,请确认服务信息',
  387. icon: 'none'
  388. });
  389. }
  390. });
  391. } else {
  392. takeOrderShow.value = false;
  393. if (res.msg) uni.showModal({
  394. title: '提示',
  395. content: res.msg,
  396. showCancel: false
  397. });
  398. }
  399. })
  400. }
  401. function snatchingOrders() {
  402. $Http.basic({
  403. "id": "2025101710510903",
  404. "content": {
  405. "sa_workorderid": detail.sa_workorderid,
  406. "projectlearders": [uni.getStorageSync('userMsg').userid],
  407. ismanage: 0 // 用于避免多次抢单
  408. },
  409. }).then(res => {
  410. if (res.code == 1) {
  411. console.log("抢单结果", res)
  412. takeOrders();
  413. } else {
  414. if (res.msg) uni.showToast({
  415. title: res.msg,
  416. icon: 'none'
  417. });
  418. }
  419. })
  420. }
  421. function toCMTips() {
  422. let id = detail.sa_workorderid,
  423. workorderids = uni.getStorageSync('workorderids') || [];
  424. let hasId = workorderids.findIndex(item => item == id);
  425. if (hasId == -1) {
  426. uni.showModal({
  427. title: '提示',
  428. confirmText: '信息确认',
  429. content: '安装工单请务必确认服务信息和客户信息无误,且需正确录入产品序列号!',
  430. success: ({ confirm }) => {
  431. if (confirm) toChangeMsg();
  432. },
  433. })
  434. }
  435. else {
  436. confirmStartShow.value = true
  437. }
  438. }
  439. // 修改信息
  440. function toChangeMsg() {
  441. uni.navigateTo({
  442. url: `/pages/workOrder/changeMsg?id=` + sa_workorderid
  443. });
  444. };
  445. // 确认开始
  446. const confirmStartShow = ref(false);
  447. function confirmStart() {
  448. $Http.basic({
  449. id: 20230209144503,
  450. "content": {
  451. "sa_workorderid": detail.sa_workorderid
  452. }
  453. }).then(res => {
  454. console.log("确认开始", res)
  455. if (res.code == 1) {
  456. confirmStartShow.value = false;
  457. getDetail();
  458. uni.showToast({
  459. title: '确认开始成功',
  460. icon: 'none'
  461. });
  462. let workorderids = uni.getStorageSync('workorderids') || [];
  463. // 把当前工单id从缓存中移除
  464. workorderids = workorderids.filter(item => item != detail.sa_workorderid);
  465. uni.setStorageSync('workorderids', workorderids);
  466. } else {
  467. if (res.msg) uni.showToast({
  468. title: res.msg,
  469. icon: 'none'
  470. });
  471. }
  472. })
  473. }
  474. function confirmCancel() {
  475. confirmStartShow.value = false;
  476. toChangeMsg();
  477. }
  478. // 编辑工单备注
  479. let shoeEditRemarks = ref(false),
  480. refuseremarks = ref("");
  481. let uModal1 = ref(null);
  482. function editRemarks() {
  483. refuseremarks.value = detail.remarks_workorder || "";
  484. shoeEditRemarks.value = true;
  485. }
  486. function confirmRemarks() {
  487. $Http.basic({
  488. "id": 2026020509571602,
  489. "content": {
  490. sa_workorderid: detail.sa_workorderid,
  491. remarks_workorder: refuseremarks.value
  492. }
  493. }).then(res => {
  494. console.log("工单备注", res)
  495. uni.showToast({ title: res.code !== 1 ? res.msg : '编辑成功', icon: 'none' });
  496. if (res.code !== 1) return uModal1.value.loading = false;
  497. shoeEditRemarks.value = false;
  498. detail.remarks_workorder = refuseremarks.value;
  499. })
  500. }
  501. defineExpose({ detail })
  502. </script>
  503. <style lang="scss" scoped>
  504. .main {
  505. position: relative;
  506. width: 690rpx;
  507. background: #FFFFFF;
  508. box-shadow: 0rpx 4rpx 16rpx 2rpx rgba(150, 157, 165, 0.16);
  509. border-radius: 10rpx;
  510. box-sizing: border-box;
  511. padding: 40rpx;
  512. margin: 40rpx auto 0;
  513. overflow: hidden;
  514. .changeTransition {
  515. display: flex;
  516. align-items: center;
  517. justify-content: center;
  518. color: #3874F6;
  519. font-size: 28rpx;
  520. position: absolute;
  521. bottom: 0;
  522. left: 0;
  523. width: 100%;
  524. height: 60rpx;
  525. background: #EDF4FF;
  526. .icon-dianjizhankai {
  527. font-size: 20rpx;
  528. margin-left: 16rpx;
  529. transform: rotate(-90deg);
  530. transition: transform 0.3s ease;
  531. }
  532. .shrink {
  533. transform: rotate(90deg);
  534. }
  535. }
  536. .row {
  537. display: flex;
  538. font-size: 28rpx;
  539. font-family: Microsoft YaHei, Microsoft YaHei;
  540. line-height: 32rpx;
  541. margin-top: 20rpx;
  542. .label {
  543. position: relative;
  544. width: 108rpx;
  545. color: #999999;
  546. box-sizing: border-box;
  547. flex-shrink: 0;
  548. margin-right: 30rpx;
  549. white-space: nowrap;
  550. }
  551. .label::after {
  552. position: absolute;
  553. top: 0;
  554. right: -12rpx;
  555. content: ':';
  556. }
  557. .value {
  558. color: #333333;
  559. }
  560. .phonenumber {
  561. width: 100%;
  562. display: flex;
  563. align-items: center;
  564. }
  565. .servicetype {
  566. border-radius: 8rpx;
  567. padding: 8rpx 16rpx;
  568. font-weight: bold;
  569. }
  570. }
  571. .col-center {
  572. display: flex;
  573. align-items: center;
  574. }
  575. .col-center::after {
  576. top: 50% !important;
  577. transform: translateY(-50%);
  578. }
  579. .justify {
  580. text-align: justify;
  581. text-align-last: justify;
  582. }
  583. .billno {
  584. position: relative;
  585. width: 400rpx;
  586. top: -40rpx;
  587. left: -40rpx;
  588. background: #3874F6;
  589. border-radius: 0 0rpx 24rpx 0rpx;
  590. padding: 10rpx 52rpx 10rpx 20rpx;
  591. font-family: Microsoft YaHei, Microsoft YaHei;
  592. font-size: 26rpx;
  593. color: #FFFFFF;
  594. }
  595. }
  596. .but-box {
  597. display: flex;
  598. align-items: center;
  599. justify-content: space-around;
  600. margin-top: 40rpx;
  601. .but-box-item {
  602. width: 45%;
  603. }
  604. }
  605. </style>