incidentList.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <template>
  2. <view class="incidentList">
  3. <view class="head">
  4. <view class="tab" :class="{active: tab.id === dataInfo.tabActiveId}" v-for="tab in dataInfo.tabs" :key="tab.id" @click="clickTab(tab.id)">
  5. {{tab.name}}<text v-if="tab.num !== ''">({{tab.num}})</text>
  6. </view>
  7. <view class="filter" @click="filterClick">
  8. <text class="newicon newicon-shaixuan"></text>
  9. </view>
  10. </view>
  11. <view class="body" v-if="dataInfo.list.length">
  12. <view class="body_item" v-for="data in dataInfo.list" :key="data.id" @click="toIncidentDetail(data)">
  13. <view class="body_item_head ellipsis-multiline">
  14. <text :style="priorityStyle(data.priority)">{{data.priority ? data.priority.name + ' ' : ''}}</text>{{data.description}}
  15. </view>
  16. <view class="body_item_content">
  17. <view class="body_item_content_p" v-if="data.department">
  18. <text class="name ellipsis">报修科室:{{data.department.dept}}</text>
  19. <view class="status" :style="stateStyle(data.state)">{{data.state ? data.state.name : ''}}</view>
  20. </view>
  21. <view class="body_item_content_p" v-if="data.place || data.houseNumber">
  22. <text class="name ellipsis">详细地址:{{data.place ? data.place.building.buildingName : ''}}{{data.place ? data.place.floorName : ''}}{{data.houseNumber}}</text>
  23. </view>
  24. <view class="body_item_content_p" v-if="data.currentLog && data.currentLog.extra1DTO && data.currentLog.extra2 && data.currentLog.startTime">
  25. <text class="name ellipsis">延期处理:{{currentLogOverTime(data.currentLog)}}</text>
  26. </view>
  27. <view class="body_item_content_p">
  28. <text class="name" v-if="data.assigneeName">
  29. 处理人:{{data.assigneeName}}<template v-if="computedSynergetic(data.synergetic)">,{{computedSynergetic(data.synergetic)}}</template>
  30. </text>
  31. <text class="name" v-else-if="data.candidateGroupsName">
  32. 处理组:{{data.candidateGroupsName}}<template v-if="computedSynergetic(data.synergetic)">,{{computedSynergetic(data.synergetic)}}</template>
  33. </text>
  34. <text class="name" v-else>
  35. <template v-if="computedSynergetic(data.synergetic)">,{{computedSynergetic(data.synergetic)}}</template>
  36. </text>
  37. <view class="icon_all" @click.stop="attachmentClick(data)">
  38. <uni-icons type="mic-filled" class="mic-filled" :size="22" color="#949494" v-if="data.callID"></uni-icons>
  39. <uni-icons type="image-filled" class="image-filled" :size="22" color="#949494" v-if="data.reqAttachment"></uni-icons>
  40. </view>
  41. </view>
  42. </view>
  43. <view class="body_item_foot">
  44. <view class="foot_info">
  45. <view class="name" @click.stop="makePhoneCall(data.contactsInformation)">联系电话:{{data.contactsInformation}}<uni-icons type="phone-filled" class="phone-filled" :size="18" :color="primaryColor"></uni-icons></view>
  46. <text class="date">{{formatDate(data.acceptDate, 'MM-dd HH:mm')}}</text>
  47. </view>
  48. <view class="btns">
  49. <button @click.stop="handler('changeUser', data.id)" type="default" class="primaryButton btn" v-if="data.state.value === 'pending' || data.state.value === 'handler' || (data.state.value === 'reassign' && assignFlag)">换人处理</button>
  50. <button @click.stop="handler('handler', data.id)" type="default" class="primaryButton btn" v-if="data.state.value === 'handler' && data.handlingPersonnelUser && data.handlingPersonnelUser.id == loginUserStore.loginUser.user.id">处理</button>
  51. <button @click.stop="receive(data)" type="default" class="primaryButton btn" v-if="computedReceive(data)">接单</button>
  52. </view>
  53. </view>
  54. </view>
  55. </view>
  56. <view class="zanwu" v-else>
  57. <text class="newicon newicon-zanwu"></text>
  58. </view>
  59. <IncidentListFilter v-if="dataInfo.isFilter" @cancelEmit="cancelFilter" @confirmEmit="conformFilter" :evt="dataInfo.evtFilter"></IncidentListFilter>
  60. <IncidentAttachment v-if="dataInfo.isAttachment" @knowEmit="knowAttachment" :incidentData="dataInfo.incidentData"></IncidentAttachment>
  61. </view>
  62. </template>
  63. <script setup>
  64. import IncidentListFilter from '@/components/IncidentListFilter.vue';
  65. import IncidentAttachment from '@/components/IncidentAttachment.vue';
  66. import { startOfDay, endOfDay, format, add } from 'date-fns'
  67. import { ref, reactive, computed } from 'vue'
  68. import { onLoad, onPullDownRefresh, onReachBottom, onTabItemTap } from '@dcloudio/uni-app'
  69. import { api_getDictionary, api_incident, api_incident_count, api_incidentTask } from "@/http/api.js"
  70. import { filterFormatDate } from '@/filters/filterFormatDate.js'
  71. import { computedPriorityStyle } from '@/filters/computedPriorityStyle.js'
  72. import { computedStateStyle } from '@/filters/computedStateStyle.js'
  73. import { computedCurrentLogOverTime } from '@/filters/computedCurrentLogOverTime.js'
  74. import { defaultColor } from '@/static/js/theme.js'
  75. import { useSetTitle } from '@/share/useSetTitle.js'
  76. import { useMakePhoneCall } from '@/share/useMakePhoneCall.js'
  77. import { useSetTabbar } from '@/share/useSetTabbar.js'
  78. import { useLoginUserStore } from '@/stores/loginUser'
  79. import { useIncidentNumStore } from '@/stores/incidentNum'
  80. import { useIncidentListSearchStore } from '@/stores/incidentListSearch'
  81. useSetTitle();
  82. const loginUserStore = useLoginUserStore();
  83. const incidentNumStore = useIncidentNumStore();
  84. const incidentListSearchStore = useIncidentListSearchStore();
  85. const { formatDate } = filterFormatDate();
  86. const { priorityStyle } = computedPriorityStyle();
  87. const { stateStyle } = computedStateStyle();
  88. const { currentLogOverTime } = computedCurrentLogOverTime();
  89. const { makePhoneCall } = useMakePhoneCall();
  90. const { setTabbar } = useSetTabbar();
  91. // 主题颜色
  92. const primaryColor = ref(defaultColor)
  93. const assignFlag = ref(false);//指派权限
  94. const qiangdan = ref(false);//接单权限
  95. // 判断是否显示接单按钮
  96. const computedReceive = computed(() => (data) => {
  97. let inUser = data.currentLog && data.currentLog.workerId == loginUserStore.loginUser.user.id;
  98. let inGroup = false;
  99. loginUserStore.loginUser.user.group.forEach(item => {
  100. if(data.currentLog){
  101. if (item.id == data.currentLog.groupId) {
  102. inGroup = true;
  103. }
  104. }
  105. })
  106. return data.state.value === 'pending' && (inUser || inGroup) && qiangdan.value;
  107. })
  108. // 转换协同人
  109. const computedSynergetic = computed(() => (synergetic) => {
  110. return (synergetic && synergetic.length) ? synergetic.map(v => v.name).join(',') : ''
  111. })
  112. // 数据
  113. const dataInfo = reactive({
  114. tabs: [{id: 0, name: '全部', value: 'all', num: ''}],
  115. tabActiveId: 0,//当前选择的tab
  116. list: [],//工单列表
  117. idx: 0,//页码
  118. hasMore: true,//是否有更多数据
  119. isFilter: false,//筛选框开关
  120. isAttachment: false,//图片和录音开关
  121. incidentId: undefined,
  122. evtFilter: {
  123. hospital: {},
  124. selected: 'todoingAll',
  125. area: {id: 0, area: '全部'},
  126. category: {id: 0, category: '全部'},
  127. acceptDate: [],
  128. },//筛选框数据
  129. })
  130. // 工单详情
  131. function toIncidentDetail(data){
  132. uni.navigateTo({
  133. url: `/pages/incidentDetail/incidentDetail?incidentId=${data.id}`
  134. })
  135. }
  136. // 获取tab选项
  137. function getTabs(){
  138. uni.showLoading({
  139. title: "加载中",
  140. mask: true,
  141. });
  142. api_getDictionary({
  143. "type": "list",
  144. "key": "incident_status"
  145. }).then(res => {
  146. uni.hideLoading();
  147. let incidentStatusList = res || [];
  148. let pending = incidentStatusList.find(v => v.value === 'pending');
  149. let handler = incidentStatusList.find(v => v.value === 'handler');
  150. dataInfo.tabs = [{id: 0, name: '全部', value: 'all', num: ''}];
  151. pending && dataInfo.tabs.push({...pending, ...{num: ''}});
  152. handler && dataInfo.tabs.push({...handler, ...{num: ''}});
  153. getList(0);
  154. })
  155. }
  156. // 点击tab
  157. function clickTab(tabId){
  158. dataInfo.tabActiveId = tabId;
  159. getList(0);
  160. }
  161. // 点击筛选
  162. function filterClick(){
  163. dataInfo.isFilter = true;
  164. }
  165. // 确认筛选
  166. function conformFilter(evtFilter){
  167. dataInfo.evtFilter = evtFilter;
  168. dataInfo.isFilter = false;
  169. getList(0);
  170. }
  171. // 关闭筛选
  172. function cancelFilter(){
  173. dataInfo.isFilter = false;
  174. }
  175. // 点击图片和录音
  176. function attachmentClick(incidentData){
  177. dataInfo.incidentData = incidentData;
  178. dataInfo.isAttachment = true;
  179. }
  180. // 知道了图片和录音
  181. function knowAttachment(){
  182. dataInfo.isAttachment = false;
  183. }
  184. // 处理按钮
  185. function handler(type, incidentId){
  186. uni.navigateTo({
  187. url: `/pages/${type}/${type}?incidentId=${incidentId}`
  188. })
  189. }
  190. // 接单调用方案
  191. function receiveFn(incidentData){
  192. uni.showLoading({
  193. title: "加载中",
  194. mask: true,
  195. });
  196. let postData = {
  197. incident: incidentData,
  198. }
  199. api_incidentTask('receive', postData).then(res => {
  200. uni.hideLoading();
  201. if(res.state == 200){
  202. getList(0);
  203. uni.showToast({
  204. icon: 'none',
  205. title: '接单成功',
  206. });
  207. }else{
  208. uni.showToast({
  209. icon: 'none',
  210. title: res.msg || '请求数据失败!'
  211. });
  212. }
  213. })
  214. }
  215. // 接单按钮
  216. function receive(incidentData){
  217. uni.showModal({
  218. title: '提示',
  219. content: `您确认要接单吗?`,
  220. confirmColor: defaultColor,
  221. confirmText: '确认',
  222. success: function(res) {
  223. if (res.confirm) {
  224. receiveFn(incidentData);
  225. }
  226. }
  227. });
  228. }
  229. // 获取列表信息
  230. function getList(idx){
  231. uni.showLoading({
  232. title: "加载中",
  233. mask: true,
  234. });
  235. dataInfo.idx = idx === undefined ? dataInfo.idx : idx;
  236. if(dataInfo.idx === 0){
  237. dataInfo.list = [];
  238. }
  239. let postData = {
  240. "idx": dataInfo.idx,
  241. "sum": 10,
  242. "incident": {
  243. "queryTask": dataInfo.evtFilter.selected || undefined,
  244. "assignee": loginUserStore.loginUser.user.id,
  245. "statusId": dataInfo.tabActiveId || undefined,
  246. }
  247. }
  248. // 请求参数调整
  249. if(!postData.incident){
  250. postData.incident = {};
  251. }
  252. if(postData.incident.queryTask === 'all' || postData.incident.queryTask === 'callback'){
  253. if(loginUserStore.loginUser.user.duty){
  254. // 当前的所属责任科室
  255. postData.incident.duty = loginUserStore.loginUser.user.duty;
  256. }else if(loginUserStore.loginUser.user.branch){
  257. // 当前的所属院区
  258. postData.incident.branch = loginUserStore.loginUser.user.branch.id;
  259. }
  260. }else{
  261. delete postData.incident.duty;
  262. delete postData.incident.branch;
  263. }
  264. if(postData.incident.queryTask === 'todo' || postData.incident.queryTask === 'owns' || postData.incident.queryTask === 'todoingAll'){
  265. postData.incident.candidateGroups = loginUserStore.loginUser.user.group.map(v => v.id).toString();
  266. }else{
  267. delete postData.incident.candidateGroups;
  268. }
  269. if(dataInfo.evtFilter && dataInfo.evtFilter.category){
  270. postData.incident.levelCategory = { id: dataInfo.evtFilter.category.value };
  271. }
  272. if(dataInfo.evtFilter && Array.isArray(dataInfo.evtFilter.acceptDate) && dataInfo.evtFilter.acceptDate.length){
  273. postData.incident.acceptDate = format(startOfDay(new Date(dataInfo.evtFilter.acceptDate[0])), 'yyyy-MM-dd HH:mm:ss');
  274. postData.incident.acceptDateEnd = format(endOfDay(dataInfo.evtFilter.acceptDate[1]), 'yyyy-MM-dd HH:mm:ss');
  275. }
  276. if(dataInfo.evtFilter && dataInfo.evtFilter.area && dataInfo.evtFilter.area.id){
  277. let id = dataInfo.evtFilter.area.id
  278. // postData.incident.area = dataInfo.evtFilter.area
  279. postData.incident.area = {id:id}
  280. }
  281. incidentListSearchStore.setIncidentListSearchData(dataInfo);
  282. api_incident(postData).then(res => {
  283. uni.hideLoading();
  284. uni.stopPullDownRefresh();
  285. if(res.status == 200){
  286. let list = res.list || [];
  287. if(list.length){
  288. dataInfo.hasMore = true;
  289. dataInfo.list = dataInfo.idx === 0 ? list : dataInfo.list.concat(list);
  290. }else{
  291. dataInfo.hasMore = false;
  292. }
  293. }else{
  294. uni.showToast({
  295. icon: 'none',
  296. title: res.msg || '请求数据失败!'
  297. });
  298. }
  299. })
  300. getCount(postData.incident);
  301. }
  302. // 获取列表数量
  303. function getCount(incident = {}){
  304. let postData = {
  305. wxCount: 'true',
  306. incidentList: [],
  307. }
  308. dataInfo.tabs.forEach(v => {
  309. postData.incidentList.push({...incident, ...{statusId: v.id || undefined}});
  310. })
  311. api_incident_count(postData).then(res => {
  312. if(res.state == 200){
  313. let myData = res.data || [];
  314. dataInfo.tabs.forEach((v, i) => {
  315. v.num = myData[i];
  316. })
  317. }else{
  318. uni.showToast({
  319. icon: 'none',
  320. title: res.msg || '请求数据失败!'
  321. });
  322. }
  323. })
  324. }
  325. // 初始化
  326. function onLoadFn(){
  327. // 我的-数量跳转
  328. if(incidentNumStore.incidentNum.data){
  329. dataInfo.evtFilter.selected = incidentNumStore.incidentNum.data.queryTask;
  330. dataInfo.tabActiveId = incidentNumStore.incidentNum.data.statusId;
  331. incidentNumStore.clearIncidentNumData();
  332. }else if(incidentListSearchStore.incidentListSearch.data){
  333. // 缓存的搜索条件
  334. Object.assign(dataInfo, incidentListSearchStore.incidentListSearch.data);
  335. }
  336. for (let i = 0; i < loginUserStore.loginUser.menu.length; i++) {
  337. if (loginUserStore.loginUser.menu[i].link == "shijianliebiao_assign") {
  338. assignFlag.value = true;
  339. }
  340. if (loginUserStore.loginUser.menu[i].link == "shijianliebiao_qiangdan") {
  341. qiangdan.value = true
  342. }
  343. }
  344. getTabs();
  345. }
  346. onLoad((option) => {
  347. for(let i = 0; i<5; i++){
  348. setTabbar(i)
  349. }
  350. onLoadFn();
  351. })
  352. onTabItemTap(e => {
  353. onLoadFn();
  354. })
  355. onPullDownRefresh(() => {
  356. getList(0)
  357. })
  358. onReachBottom(() => {
  359. dataInfo.idx += 1;
  360. if (dataInfo.hasMore) {
  361. getList(); // 当触底时加载更多数据
  362. }
  363. })
  364. </script>
  365. <style lang="scss" scoped>
  366. page{
  367. height: calc(100vh - var(--window-bottom));
  368. }
  369. .incidentList{
  370. display: flex;
  371. flex-direction: column;
  372. justify-content: space-between;
  373. .head{
  374. height: 88rpx;
  375. display: flex;
  376. position: fixed;
  377. z-index: 99;
  378. width: 100%;
  379. background-color: #fff;
  380. font-size: 30rpx;
  381. .tab{
  382. flex: 1;
  383. display: flex;
  384. justify-content: center;
  385. align-items: center;
  386. border-bottom: 4rpx solid transparent;
  387. &.active{
  388. color: $uni-primary;
  389. border-color: $uni-primary;
  390. }
  391. }
  392. .filter{
  393. width: 84rpx;
  394. display: flex;
  395. justify-content: center;
  396. align-items: center;
  397. .newicon-shaixuan{
  398. font-size: 36rpx;
  399. color: #2C2C2C;
  400. }
  401. }
  402. }
  403. .body{
  404. margin-bottom: var(--window-bottom);
  405. margin-top: 88rpx;
  406. border-top: 6rpx solid #EBEBEB;
  407. .body_item{
  408. border-bottom: 8rpx solid #EBEBEB;
  409. .body_item_head{
  410. word-break: break-all;
  411. text-align: justify;
  412. text-align: left;
  413. margin: 24rpx;
  414. font-size: 30rpx;
  415. }
  416. .body_item_content{
  417. border-top: 1rpx solid #D8D8D8;
  418. padding: 24rpx 24rpx 24rpx 48rpx;
  419. .body_item_content_p{
  420. color: #6A6A6A;
  421. font-size: 26rpx;
  422. display: flex;
  423. justify-content: space-between;
  424. align-items: center;
  425. margin-bottom: 24rpx;
  426. &:last-of-type{
  427. margin-bottom: 0;
  428. }
  429. .name{
  430. flex: 1;
  431. }
  432. .status{
  433. padding: 4rpx 10rpx;
  434. border-radius: 20rpx;
  435. background-color: #DBE8FE;
  436. font-size: 22rpx;
  437. color: #006CF9;
  438. }
  439. .icon_all{
  440. .mic-filled,
  441. .image-filled
  442. {
  443. margin-left: 16rpx;
  444. }
  445. }
  446. }
  447. }
  448. .body_item_foot{
  449. border-top: 1rpx solid #D8D8D8;
  450. font-size: 26rpx;
  451. padding: 24rpx;
  452. .foot_info{
  453. display: flex;
  454. justify-content: space-between;
  455. align-items: center;
  456. .phone-filled{
  457. margin-left: 5rpx;
  458. }
  459. }
  460. }
  461. }
  462. }
  463. .zanwu{
  464. box-sizing: border-box;
  465. margin-bottom: var(--window-bottom);
  466. margin-top: 88rpx;
  467. border-top: 6rpx solid #EBEBEB;
  468. height: calc(100vh - var(--window-bottom) - 88rpx);
  469. display: flex;
  470. justify-content: center;
  471. background-color: #F7F7F7;
  472. .newicon-zanwu{
  473. font-size: 256rpx;
  474. color: #D6D6D6;
  475. margin-top: 140rpx;
  476. }
  477. }
  478. }
  479. </style>