index.uvue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. <template>
  2. <uni-navbar-lite :showLeft=false title="工单"></uni-navbar-lite>
  3. <view class="list-page">
  4. <!-- 搜索栏 -->
  5. <view class="search-bar">
  6. <view class="search-box">
  7. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  8. <input class="search-input" type="text" placeholder="搜索工单编码、风机编号" v-model="keyword" @confirm="handleSearch" @blur="handleSearch" />
  9. <text v-if="keyword.length > 0" class="clear-icon" @click="clearSearch">✕</text>
  10. </view>
  11. </view>
  12. <view class="status-bar">
  13. <scroll-view class="scroll-view_H" direction="horizontal" show-scrollbar="false">
  14. <view class="scroll-view-item_H uni-bg-red">
  15. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'all'}" @click="switchStatus('all')">全部</text>
  16. </view>
  17. <!-- <view class="scroll-view-item_H uni-bg-green" v-if="checkPermi(['gxt:maintenance:order:edit','gxt:repairOrder:edit']) && !dealLoad">
  18. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'to_issue,accept_return'}" @click="switchStatus('to_issue,accept_return')">待下发</text>
  19. </view> -->
  20. <view class="scroll-view-item_H uni-bg-green" v-if="checkPermi(['gxt:maintenance:order:accept','gxt:repairOrder:accept']) && !dealLoad">
  21. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'assigned'}" @click="switchStatus('assigned')">待接单</text>
  22. </view>
  23. <view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:suspend','gxt:repairOrder:suspend']) && !dealLoad">
  24. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'to_finish'}" @click="switchStatus('to_finish')">待结单</text>
  25. </view>
  26. <view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:approve','gxt:repairOrder:approve']) && !dealLoad">
  27. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'to_approve'}" @click="switchStatus('to_approve')">待审批</text>
  28. </view>
  29. <view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:resume','gxt:repairOrder:resume']) && !dealLoad">
  30. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'suspended'}" @click="switchStatus('suspended')">已挂起</text>
  31. </view>
  32. <view class="scroll-view-item_H" v-if="checkPermi(['gxt:maintenance:order:restart','gxt:repairOrder:restart']) && !dealLoad">
  33. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'completed'}" @click="switchStatus('completed')">已完成</text>
  34. </view>
  35. <view class="scroll-view-item_H" v-if="dealLoad">
  36. <text class="status-txt" :class="{'stauts-sel': currentStatus === 'pending'}" @click="switchStatus('pending')">待处理</text>
  37. </view>
  38. </scroll-view>
  39. </view>
  40. <!-- 列表内容 -->
  41. <common-list
  42. :dataList="dataList"
  43. :loading="loading"
  44. :refreshing="refreshing"
  45. :hasMore="hasMore"
  46. @refresh="handleRefresh"
  47. @loadMore="loadMore"
  48. @itemClick="handleView"
  49. >
  50. <template #default="{ item, index }">
  51. <view class="list-item">
  52. <view class="item-container">
  53. <view class="item-header">
  54. <text class="item-title">{{ getWorkOrderProjectNo(item) }}-{{ getPcsDeviceName(item) }}{{ getOrderType(item) }}</text>
  55. <text class="status-tag" :class="getStatusClass(item)">{{ getWorkOrderStatus(item) }}</text>
  56. </view>
  57. <view class="info-row">
  58. <view class="info-label">
  59. <text class="text-gray">{{ getDisplayTime(item) }}</text>
  60. </view>
  61. </view>
  62. <view class="btn-group">
  63. <view
  64. v-if="getOrderStatus(item) == 'assigned' && canHandleOrder(item,'accept')"
  65. class="btn-primary info-value"
  66. @click.stop="handleItemClick(item,'')"
  67. >
  68. <text class="btn-text">接单</text>
  69. </view>
  70. <view
  71. v-if="getOrderStatus(item) == 'to_approve' && canHandleOrder(item,'')"
  72. class="btn-primary info-value"
  73. @click.stop="handleItemClick(item,'')"
  74. >
  75. <text class="btn-text">审批</text>
  76. </view>
  77. <view
  78. v-if="getOrderStatus(item) == 'suspended' && canHandleOrder(item,'')"
  79. class="btn-primary info-value"
  80. @click.stop="handleItemClick(item,'')"
  81. >
  82. <text class="btn-text">恢复</text>
  83. </view>
  84. <view
  85. v-if="getOrderStatus(item) == 'return' && canHandleOrder(item,'acceptReturn')"
  86. class="btn-primary info-value"
  87. @click.stop="handleItemClick(item,'acceptReturn')"
  88. >
  89. <text class="btn-text">退回</text>
  90. </view>
  91. <view
  92. v-if="getOrderStatus(item) == 'assigned' && canHandleOrder(item,'acceptReturn')"
  93. class="btn-primary info-value"
  94. @click.stop="handleItemClick(item,'acceptReturn')"
  95. >
  96. <text class="btn-text">退回</text>
  97. </view>
  98. <view
  99. v-if="getOrderStatus(item) == 'to_finish' && canHandleOrder(item, 'return')"
  100. class="btn-primary info-value"
  101. @click.stop="handleItemClick(item, 'return')"
  102. >
  103. <text class="btn-text">退回</text>
  104. </view>
  105. <view
  106. v-if="getOrderStatus(item) == 'to_finish' && canHandleOrder(item, 'suspend')"
  107. class="btn-primary info-value"
  108. @click.stop="handleItemClick(item, 'suspend')"
  109. >
  110. <text class="btn-text">挂起</text>
  111. </view>
  112. <view
  113. v-if="getOrderStatus(item) == 'to_finish' && canHandleOrder(item, 'finalize')"
  114. class="btn-primary info-value"
  115. @click.stop="handleItemClick(item, 'finalize')"
  116. >
  117. <text class="btn-text">复启</text>
  118. </view>
  119. </view>
  120. </view>
  121. </view>
  122. </template>
  123. </common-list>
  124. <custom-tabbar :current="1" />
  125. </view>
  126. </template>
  127. <script setup lang="uts">
  128. import { ref, onBeforeUnmount, onMounted } from 'vue'
  129. import type { acceptOrderInfo } from '../../types/order'
  130. import type { SysDictData, DictDataResponse } from '../../types/dict'
  131. import { getOrderList } from '../../api/order/list'
  132. import { getDictDataByType } from '../../api/dict/index'
  133. import {checkPermi, getUserInfo} from '../../utils/storage'
  134. const userId = ref<string>("")
  135. const roles = ref<string>('')
  136. // 列表数据
  137. const dataList = ref<acceptOrderInfo[]>([])
  138. let keyword = ref<string>("")
  139. const page = ref<number>(1)
  140. const pageSize: number = 10
  141. const hasMore = ref<boolean>(true)
  142. const loading = ref<boolean>(false)
  143. const refreshing = ref<boolean>(false)
  144. const total = ref<number>(0)
  145. let currentStatus = ref<string>('') // 添加状态管理
  146. const statusDictList = ref<SysDictData[]>([]) // 工单状态字典列表
  147. // 添加防重复请求的标志位(参考score/pending.uvue的实现)
  148. const isSearching = ref<boolean>(false)
  149. // 添加防重复刷新的标志
  150. const isRefreshing = ref<boolean>(false)
  151. // 添加刷新时间戳,用于防抖
  152. const lastRefreshTime = ref<number>(0)
  153. // 添加字典加载状态
  154. const dictLoaded = ref<boolean>(false)
  155. // 待处理工单加载
  156. const dealLoad = ref<boolean>(false)
  157. type StatusItem = {
  158. key: string,
  159. permis: string[],
  160. condition: () => boolean
  161. }
  162. const statusConfig: StatusItem[] = [
  163. { key: 'all', permis: ['gxt:order:all'], condition: () => !dealLoad.value },
  164. { key: 'assigned', permis: ['gxt:maintenance:order:accept','gxt:repairOrder:accept'], condition: () => !dealLoad.value },
  165. { key: 'to_finish', permis: ['gxt:maintenance:order:suspend','gxt:repairOrder:suspend'], condition: () => !dealLoad.value },
  166. { key: 'to_approve', permis: ['gxt:maintenance:order:approve','gxt:repairOrder:approve'], condition: () => !dealLoad.value },
  167. { key: 'suspended', permis: ['gxt:maintenance:order:resume','gxt:repairOrder:resume'], condition: () => !dealLoad.value },
  168. { key: 'completed', permis: ['gxt:maintenance:order:restart','gxt:repairOrder:restart'], condition: () => !dealLoad.value }
  169. ]
  170. const getOrderStatus = (item: any | null): string => {
  171. if (item == null) return ''
  172. const orderItem = item as acceptOrderInfo
  173. return orderItem.workOrderStatus ?? ''
  174. }
  175. // 获取工单状态字典列表
  176. const loadStatusDictList = async (): Promise<void> => {
  177. try {
  178. const result = await getDictDataByType('gxt_work_order_status')
  179. const resultObj = result as UTSJSONObject
  180. if (resultObj['code'] == 200) {
  181. const data = resultObj['data'] as any[]
  182. const dictData: SysDictData[] = []
  183. if (data.length > 0) {
  184. for (let i = 0; i < data.length; i++) {
  185. const item = data[i] as UTSJSONObject
  186. // 只提取需要的字段
  187. const dictItem: SysDictData = {
  188. dictValue: item['dictValue'] as string | null,
  189. dictLabel: item['dictLabel'] as string | null,
  190. dictCode: null,
  191. dictSort: null,
  192. dictType: null,
  193. cssClass: null,
  194. listClass: null,
  195. isDefault: null,
  196. status: null,
  197. default: null,
  198. createTime: null,
  199. remark: null
  200. }
  201. dictData.push(dictItem)
  202. }
  203. }
  204. statusDictList.value = dictData
  205. dictLoaded.value = true
  206. }
  207. } catch (e: any) {
  208. console.error('获取工单状态字典失败:', e.message)
  209. dictLoaded.value = true
  210. }
  211. }
  212. const getFirstVisibleStatus = (): string => {
  213. for (const item of statusConfig) {
  214. const hasPerm = item.permis.length === 0 || checkPermi(item.permis)
  215. const meetsCondition = item.condition()
  216. if (hasPerm && meetsCondition) {
  217. // if (hasPerm) {
  218. return item.key as string
  219. }
  220. }
  221. return 'all' // fallback,理论上不会走到这里
  222. }
  223. let loadCount = 0
  224. // 加载列表数据
  225. const loadData = async (isRefresh: boolean | null, disablePullDown: boolean | null): Promise<void> => {
  226. const id = ++loadCount
  227. console.log(`【loadData #${id}】开始, isRefresh=${isRefresh}, disablePullDown=${disablePullDown}`)
  228. // 防止重复请求的核心机制(参考score/pending.uvue的实现)
  229. if (loading.value) {
  230. // 确保刷新状态最终被重置,防止卡死
  231. if (isRefresh != true) {
  232. refreshing.value = false;
  233. }
  234. return
  235. }
  236. const shouldRefresh = isRefresh != null ? isRefresh : false
  237. const shouldDisablePullDown = disablePullDown != null ? disablePullDown : false
  238. loading.value = true
  239. if (shouldRefresh && !shouldDisablePullDown) {
  240. page.value = 1
  241. refreshing.value = true
  242. } else if (shouldRefresh && shouldDisablePullDown) {
  243. // 状态切换时,重置页码但不触发动画
  244. page.value = 1
  245. // 即使禁用下拉刷新,也要确保刷新状态最终被重置
  246. refreshing.value = false
  247. } else {
  248. // 对于加载更多操作,不需要显示下拉刷新状态
  249. refreshing.value = false;
  250. }
  251. try {
  252. // 处理默认值
  253. const searchKeyword = keyword.value.length > 0 ? keyword.value : null
  254. const result = await getOrderList(page.value, pageSize, searchKeyword, currentStatus.value)
  255. // 提取响应数据
  256. const resultObj = result as UTSJSONObject
  257. const code = resultObj['code'] as number
  258. const responseData = resultObj['rows'] as any[]
  259. const responseTotal = resultObj['total'] as number
  260. if (code == 200) {
  261. // 将 any[] 转换为 acceptOrderInfo[]
  262. const newData: acceptOrderInfo[] = []
  263. for (let i = 0; i < responseData.length; i++) {
  264. const item = responseData[i] as UTSJSONObject
  265. const orderItem: acceptOrderInfo = {
  266. orderType: item['orderType'] as Number,
  267. id: item['id'] as Number,
  268. teamLeaderId: item['teamLeaderId'] != null ? (item['teamLeaderId'] as Number) : 0,
  269. acceptUserId: item['acceptUserId'] != null ? (item['acceptUserId'] as Number) : 0,
  270. teamLeaderName: item['teamLeaderName'] as string | null,
  271. acceptUserName: item['acceptUserName'] as string | null,
  272. acceptTime: item['acceptTime'] as string | null,
  273. assignTime: item['assignTime'] as string | null,
  274. assignUserName: item['assignUserName'] as string | null,
  275. status: (item['status']==null)?0:item['status'] as Number,
  276. workOrderProjectNo: item['workOrderProjectNo'] as string | null,
  277. workOrderStatus: item['workOrderStatus'] as string | null,
  278. gxtCenterId: item['gxtCenterId'] as Number | 0,
  279. gxtCenter: item['gxtCenter'] as string | null,
  280. pcsStationId: item['pcsStationId'] as Number | 0,
  281. pcsStationName: item['pcsStationName'] as string | null,
  282. pcsDeviceId: item['pcsDeviceId'] as Number | 0,
  283. pcsDeviceName: item['pcsDeviceName'] as string | null,
  284. brand: item['brand'] as string | null,
  285. model: item['model'] as string | null,
  286. createTime: item['createTime'] as string | null,
  287. suspendReason: item['suspendReason'] as string | null,
  288. rejectionReason: item['rejectionReason'] as string | null,
  289. updateTime: item['updateTime'] as string | null, // 新增字段
  290. workEndTime: item['workEndTime'] as string | null // 新增字段
  291. }
  292. newData.push(orderItem)
  293. }
  294. // 不再在前端过滤,直接使用API返回的数据
  295. if (shouldRefresh) {
  296. dataList.value = newData
  297. } else {
  298. dataList.value = [...dataList.value, ...newData]
  299. }
  300. total.value = responseTotal
  301. hasMore.value = dataList.value.length < responseTotal
  302. // 通知首页更新待接单数量
  303. if (currentStatus.value === 'assigned') {
  304. uni.$emit('refreshAssignedCount')
  305. }
  306. } else {
  307. const msg = resultObj['msg'] as string | null
  308. uni.showToast({
  309. title: msg ?? '加载失败',
  310. icon: 'none'
  311. })
  312. }
  313. } catch (e: any) {
  314. uni.showToast({
  315. title: e.message ?? '加载失败',
  316. icon: 'none'
  317. })
  318. } finally {
  319. loading.value = false
  320. // 确保刷新状态能结束(参考score/pending.uvue的实现)
  321. if (shouldRefresh) {
  322. refreshing.value = false;
  323. // 使用setTimeout确保状态彻底重置
  324. setTimeout(() => {
  325. isRefreshing.value = false;
  326. }, 50);
  327. }
  328. }
  329. }
  330. // 辅助函数:从 any 类型提取属性
  331. const getOrderType = (item: any | null): string => {
  332. if (item == null) return ''
  333. const orderInfoItem = item as acceptOrderInfo
  334. return orderInfoItem.orderType == 1?"维修工单":"维保工单";
  335. }
  336. const getWorkOrderProjectNo = (item: any | null): string | null => {
  337. if (item == null) return ''
  338. const orderInfoItem = item as acceptOrderInfo
  339. return orderInfoItem.workOrderProjectNo
  340. }
  341. const getPcsStationName = (item: any | null): string | null=> {
  342. if (item == null) return ''
  343. const orderInfoItem = item as acceptOrderInfo
  344. return orderInfoItem.pcsStationName
  345. }
  346. const getPcsDeviceName = (item: any | null): string | null=> {
  347. if (item == null) return ''
  348. const orderInfoItem = item as acceptOrderInfo
  349. return orderInfoItem.pcsDeviceName
  350. }
  351. const getAssignTime = (item: any | null): string|null => {
  352. if (item == null) return null
  353. const orderInfoItem = item as acceptOrderInfo
  354. return orderInfoItem.assignTime
  355. }
  356. const getAcceptTime = (item: any | null): string|null => {
  357. if (item == null) return null
  358. const orderInfoItem = item as acceptOrderInfo
  359. return orderInfoItem.acceptTime
  360. }
  361. const getCreateTime = (item: any | null): string|null => {
  362. if (item == null) return null
  363. const orderInfoItem = item as acceptOrderInfo
  364. return orderInfoItem.createTime
  365. }
  366. // 根据状态显示不同的时间
  367. const getDisplayTime = (item: any | null): string|null => {
  368. if (item == null) return null
  369. const orderInfoItem = item as acceptOrderInfo
  370. // 如果是"待接单"状态,显示派单时间
  371. if (orderInfoItem.workOrderStatus == 'assigned') {
  372. return '下发时间:' + orderInfoItem.assignTime
  373. } else if(orderInfoItem.workOrderStatus == 'to_finish') {
  374. if(orderInfoItem.workEndTime != null) {
  375. return '结束时间:' + orderInfoItem.workEndTime
  376. }
  377. return '接单时间:' + orderInfoItem.acceptTime
  378. } else if(orderInfoItem.workOrderStatus == 'to_approve') {
  379. return '申请挂起时间:' + orderInfoItem.updateTime
  380. } else if(orderInfoItem.workOrderStatus == 'suspended') {
  381. return '审批通过时间:' + orderInfoItem.updateTime
  382. } else if(orderInfoItem.workOrderStatus == 'return' || orderInfoItem.workOrderStatus == 'accept_return') {
  383. return '退回时间:' + orderInfoItem.updateTime
  384. } else if(orderInfoItem.workOrderStatus == 'completed') {
  385. return '结单时间:' + orderInfoItem.updateTime
  386. } else if(orderInfoItem.workOrderStatus == 'archived') {
  387. return '归档时间:' + orderInfoItem.updateTime
  388. }
  389. // 默认显示创建时间
  390. return '创建时间:' + orderInfoItem.createTime
  391. }
  392. const getWorkOrderStatus = (item: any | null): string | null => {
  393. if (item == null) return ''
  394. const orderInfoItem = item as acceptOrderInfo
  395. const rawStatus = orderInfoItem.workOrderStatus
  396. if (rawStatus==null) return ''
  397. // 如果字典尚未加载,返回原始值
  398. if (!dictLoaded.value) {
  399. return rawStatus
  400. }
  401. // 查找字典中对应的标签
  402. const dictItem = statusDictList.value.find(dict => dict.dictValue == rawStatus)
  403. return dictItem!=null ? dictItem.dictLabel : rawStatus
  404. }
  405. const getStatusClass = (item: any | null): string => {
  406. if (item == null) return ''
  407. const orderInfoItem = item as acceptOrderInfo
  408. const rawStatus = orderInfoItem.workOrderStatus
  409. if (rawStatus==null) return ''
  410. // const status = rawStatus
  411. // 返回对应的状态类名
  412. return `status-${rawStatus}`
  413. }
  414. // 切换状态
  415. const switchStatus = (status: string): void => {
  416. // 添加防重复调用检查(参考score/pending.uvue的实现)
  417. if (loading.value) {
  418. return;
  419. }
  420. // 添加防重复请求检查
  421. if (isSearching.value) {
  422. return
  423. }
  424. isSearching.value = true
  425. currentStatus.value = status
  426. page.value = 1
  427. loadData(true, true) // 第二个参数为true表示禁用下拉刷新动画
  428. // 延迟重置标志位,确保请求发送后才允许下一次搜索
  429. setTimeout(() => {
  430. isSearching.value = false
  431. }, 100)
  432. }
  433. // 下拉刷新
  434. const handleRefresh = async (): Promise<void> => {
  435. console.log("handleRefresh被触发")
  436. console.log("loading.value===",loading.value)
  437. console.log("isRefreshing.value===",isRefreshing.value)
  438. // 防抖处理,避免频繁触发(参考score/pending.uvue的实现)
  439. const now = Date.now();
  440. if (now - lastRefreshTime.value < 1000) {
  441. refreshing.value = false;
  442. return;
  443. }
  444. lastRefreshTime.value = now;
  445. // 添加防重复调用检查
  446. if (loading.value || isRefreshing.value) {
  447. // 如果已经在加载或正在刷新,直接重置刷新状态
  448. refreshing.value = false;
  449. return;
  450. }
  451. console.log("loading.value1===",loading.value)
  452. console.log("isRefreshing.value1===",isRefreshing.value)
  453. // 设置刷新标志
  454. isRefreshing.value = true;
  455. try {
  456. await loadData(true, false); // 使用默认的下拉刷新行为
  457. } catch (error) {
  458. console.error('刷新失败:', error);
  459. refreshing.value = false;
  460. isRefreshing.value = false;
  461. }
  462. // 确保在一定时间后重置刷新标志,防止意外情况
  463. setTimeout(() => {
  464. isRefreshing.value = false
  465. }, 100) // 延迟重置,确保状态完全更新
  466. }
  467. // 加载更多
  468. const loadMore = (): void => {
  469. if (!hasMore.value || loading.value) {
  470. return
  471. }
  472. page.value++
  473. loadData(false, false)
  474. }
  475. // 搜索
  476. const handleSearch = (): void => {
  477. // 添加防重复调用检查(参考score/pending.uvue的实现)
  478. if (loading.value) {
  479. return;
  480. }
  481. // 添加防重复请求检查
  482. if (isSearching.value) {
  483. return
  484. }
  485. isSearching.value = true
  486. page.value = 1
  487. loadData(true, true) // 状态切换时禁用下拉刷新动画
  488. // 延迟重置标志位,确保请求发送后才允许下一次搜索
  489. setTimeout(() => {
  490. isSearching.value = false
  491. }, 100)
  492. }
  493. const handleSearchOnBlur = (): void => {
  494. handleSearch()
  495. }
  496. // 方法:判断当前工单是否显示操作按钮
  497. const canHandleOrder = (item: any | null, buttonType: string | ''): boolean => {
  498. if (item == null) return false
  499. let permit: string[] = []
  500. const orderItem = item as acceptOrderInfo
  501. if(orderItem.workOrderStatus == 'assigned' && buttonType != '' && buttonType == "acceptReturn" && orderItem.orderType == 1) {
  502. // 接单退回
  503. permit = ['gxt:repairOrder:acceptReturn']
  504. } else if(orderItem.workOrderStatus == 'assigned' && buttonType != '' && buttonType == "accept") {
  505. // 接单
  506. permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:accept'] : ['gxt:repairOrder:accept']
  507. } else if(orderItem.workOrderStatus == 'to_finish') {
  508. if(buttonType != '' && buttonType == "suspend" && (orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员"))) {
  509. // 挂起
  510. permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:suspend'] : ['gxt:repairOrder:suspend']
  511. } else if(buttonType != '' && buttonType == "return" && orderItem.orderType == 1 && (orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员"))) {
  512. // 退回
  513. permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:return'] : ['gxt:repairOrder:return']
  514. } else if(buttonType != '' && buttonType == "finalize" && orderItem.orderType == 1 && (orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员"))) {
  515. // 复启
  516. permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:finalize'] : ['gxt:repairOrder:finalize']
  517. }
  518. } else if(orderItem.workOrderStatus == 'to_approve') {
  519. // 审批
  520. permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:approve'] : ['gxt:repairOrder:approve']
  521. } else if(orderItem.workOrderStatus == 'suspended' && (orderItem.teamLeaderId == parseInt(userId.value) || roles.value.includes("管理员"))) {
  522. // 恢复
  523. permit = orderItem.orderType == 2 ? ['gxt:maintenance:order:resume'] : ['gxt:repairOrder:resume']
  524. } else if(orderItem.workOrderStatus == 'return') {
  525. // 接单退回
  526. permit = ['gxt:repairOrder:acceptReturn']
  527. }
  528. else {
  529. return false
  530. }
  531. // const orderType = (item as acceptOrderInfo).orderType
  532. return checkPermi(permit)
  533. }
  534. // 点击列表项
  535. const handleItemClick = (item: any | null, buttonType: string | ''): void => {
  536. if (item == null) return
  537. const orderItem = item as acceptOrderInfo
  538. // if(currentStatus.value === '' || currentStatus.value === 'completed' || currentStatus.value === 'all') {
  539. // // 传递orderType参数以便详情页决定调用哪个API
  540. // uni.navigateTo({
  541. // url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
  542. // })
  543. // } else
  544. if(orderItem.workOrderStatus == 'assigned') {
  545. if(buttonType != '' && buttonType == "acceptReturn") {
  546. // 跳转到退回页面
  547. uni.navigateTo({
  548. url: `/pages/order/detail/returnIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  549. })
  550. } else {
  551. // 跳转到接单页面
  552. uni.navigateTo({
  553. url: `/pages/order/detail/acceptIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  554. })
  555. }
  556. } else if(orderItem.workOrderStatus == 'to_finish') {
  557. if(buttonType != '' && buttonType == "suspend") {
  558. // 跳转到待结单页面
  559. uni.navigateTo({
  560. url: `/pages/order/detail/suspendIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  561. })
  562. } else if(buttonType != '' && buttonType == "return") {
  563. // 跳转到退回页面
  564. uni.navigateTo({
  565. url: `/pages/order/detail/returnIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  566. })
  567. } else if(buttonType != '' && buttonType == "finalize") {
  568. // 跳转到复启页面
  569. uni.navigateTo({
  570. url: `/pages/order/detail/resetIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  571. })
  572. }
  573. } else if(orderItem.workOrderStatus == 'to_approve') {
  574. // 跳转到待审批页面
  575. uni.navigateTo({
  576. url: `/pages/order/detail/approveIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  577. })
  578. } else if(orderItem.workOrderStatus == 'suspended') {
  579. // 跳转到恢复页面
  580. uni.navigateTo({
  581. url: `/pages/order/detail/resumeIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  582. })
  583. } else if(orderItem.workOrderStatus == 'return') {
  584. // 跳转到退回页面
  585. uni.navigateTo({
  586. url: `/pages/order/detail/returnIndex?id=${orderItem.id}&orderType=${orderItem.orderType}`
  587. })
  588. } else {
  589. uni.navigateTo({
  590. url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
  591. })
  592. }
  593. }
  594. // 点击列表项
  595. const handleView = (item: any | null): void => {
  596. if (item == null) return
  597. const orderItem = item as acceptOrderInfo
  598. uni.navigateTo({
  599. url: `/pages/order/detail/index?id=${orderItem.id}&orderType=${orderItem.orderType}`
  600. })
  601. }
  602. // 查看工单详情
  603. const showWorkOrderDetail = (item: any | null): void => {
  604. if (item == null) return
  605. const orderItem = item as acceptOrderInfo
  606. uni.navigateTo({
  607. url: `/pages/workbench/detail/index?id=${orderItem.id}`
  608. })
  609. }
  610. // 清空搜索
  611. const clearSearch = (): void => {
  612. // 添加防重复调用检查(参考score/pending.uvue的实现)
  613. if (loading.value) {
  614. return;
  615. }
  616. // 添加防重复请求检查
  617. if (isSearching.value) {
  618. return
  619. }
  620. isSearching.value = true
  621. keyword.value = ""
  622. page.value = 1
  623. loadData(true, true) // 状态切换时禁用下拉刷新动画
  624. // 延迟重置标志位,确保请求发送后才允许下一次搜索
  625. setTimeout(() => {
  626. isSearching.value = false
  627. }, 100)
  628. }
  629. // 初始化
  630. // onMounted(() => {
  631. onLoad((options: any) => {
  632. const userInfo = getUserInfo()
  633. if (userInfo != null) {
  634. const userIdStr = userInfo['userId'].toString()
  635. userId.value = userIdStr
  636. roles.value = userInfo['roleNames'].toString()
  637. }
  638. const params = options as UTSJSONObject
  639. const isDeal = params['isDeal'] as string | null
  640. if(isDeal != null) {
  641. dealLoad.value = true;
  642. }
  643. loadStatusDictList()
  644. // 自动选中第一个可显示的状态
  645. // currentStatus.value = getFirstVisibleStatus()
  646. currentStatus.value = 'all'
  647. loadData(true, true) // 首次加载时禁用下拉刷新动画
  648. // 监听首页切换状态事件
  649. uni.$on('switchOrderStatus', (status: string) => {
  650. switchStatus(status)
  651. })
  652. // 监听接单成功的事件,刷新列表
  653. uni.$on('refreshOrderList', () => {
  654. page.value = 1
  655. loadData(true, false)
  656. })
  657. })
  658. // 组件卸载前清理事件监听
  659. onBeforeUnmount(() => {
  660. refreshing.value = false
  661. loading.value = false
  662. isRefreshing.value = false
  663. // 移除事件监听
  664. uni.$off('refreshOrderList',{})
  665. uni.$off('switchOrderStatus',{})
  666. })</script>
  667. <style lang="scss">
  668. .list-page {
  669. flex: 1;
  670. background-color: #e8f0f9;
  671. }
  672. .search-bar {
  673. padding: 20rpx 30rpx;
  674. background-color: #d7eafe;
  675. }
  676. .search-box {
  677. flex-direction: row;
  678. align-items: center;
  679. height: 72rpx;
  680. padding: 0 24rpx;
  681. background-color: #f5f5f5;
  682. border-radius: 36rpx;
  683. .search-icon {
  684. width: 32rpx;
  685. height: 32rpx;
  686. margin-right: 12rpx;
  687. }
  688. .search-input {
  689. flex: 1;
  690. font-size: 28rpx;
  691. color: #333333;
  692. }
  693. .clear-icon {
  694. margin-left: 12rpx;
  695. font-size: 28rpx;
  696. color: #999999;
  697. }
  698. }
  699. .status-bar{
  700. padding-bottom: 10px;
  701. padding-left:30rpx;
  702. background-color: #d7eafe;
  703. }
  704. .status-box {
  705. flex-direction: row;
  706. align-items: center;
  707. height: 72rpx;
  708. flex: 1;
  709. .status-txt{
  710. padding: 8px 12px;
  711. text-align: center;
  712. margin-right: 12rpx;
  713. border-radius: 36rpx;
  714. background-color: #fff;
  715. font-size: 28rpx;
  716. // cursor: pointer;
  717. }
  718. .stauts-sel{
  719. background-color: #007AFF;
  720. color: #fff;
  721. }
  722. }
  723. .list-item {
  724. margin: 24rpx 30rpx;
  725. background-color: #ffffff;
  726. border-radius: 16rpx;
  727. }
  728. .item-container {
  729. padding: 30rpx;
  730. }
  731. .item-address {
  732. font-size: 26rpx;
  733. color: #999999;
  734. margin-bottom: 20rpx;
  735. line-height: 40rpx;
  736. }
  737. .btn-primary {
  738. z-index: 999;
  739. border-radius: 10rpx;
  740. font-size: 24rpx;
  741. // white-space: nowrap;
  742. margin-left: 20rpx;
  743. background-color: #165DFF;
  744. line-height: 45rpx;
  745. color: #ffffff;
  746. .btn-text{
  747. color: #ffffff;
  748. font-size: 24rpx;
  749. padding: 5px 15px;
  750. }
  751. }
  752. .item-header {
  753. flex-direction: row;
  754. align-items: flex-start;
  755. margin-bottom: 16rpx;
  756. .item-title {
  757. font-size: 30rpx;
  758. color: #333333;
  759. font-weight: bold;
  760. flex-wrap: wrap;
  761. flex: 0 1 80%;
  762. min-width: 0;
  763. }
  764. .info-value {
  765. font-size: 28rpx;
  766. color: #999999;
  767. margin-left: auto;
  768. flex: 0 0 auto;
  769. white-space: nowrap;
  770. }
  771. }
  772. .info-row {
  773. flex-direction: row;
  774. justify-content: space-between;
  775. align-items: center;
  776. .info-label {
  777. font-size: 26rpx;
  778. color: #666;
  779. }
  780. .info-value {
  781. font-size: 26rpx;
  782. // color: #666;
  783. }
  784. }
  785. .text-gray{
  786. font-size: 26rpx;
  787. color: #666;
  788. }
  789. .status-tag {
  790. padding: 8rpx 20rpx;
  791. border-radius: 20rpx;
  792. font-size: 24rpx;
  793. white-space: nowrap;
  794. margin-left: 50rpx;
  795. border: 1rpx solid;
  796. }
  797. /* 待接单 */
  798. .status-assigned {
  799. background-color: #ebf5ff;
  800. color: #409eff;
  801. border-color: #d8ebff;
  802. }
  803. /* 待结单 */
  804. .status-to_finish {
  805. background-color: #fff7e6;
  806. color: #fa8c16;
  807. border-color: #ffd591;
  808. }
  809. /* 待审批 */
  810. .status-to_approve {
  811. background-color: #fff7e6;
  812. color: #fa8c16;
  813. border-color: #ffd591;
  814. }
  815. /* 已挂起 */
  816. .status-suspended {
  817. background-color: #fff2f0;
  818. color: #ff4d4f;
  819. border-color: #ffccc7;
  820. }
  821. /* 已完成 */
  822. .status-completed {
  823. background-color: #f0f9eb;
  824. color: #5cb87a;
  825. border-color: #c2e7b0;
  826. }
  827. /* 待下发 */
  828. .status-to_issue {
  829. background-color: #f4f4f5;
  830. color: #909399;
  831. border-color: #e9e9eb;
  832. }
  833. /* 已归档 */
  834. .status-archived {
  835. background-color: #f0f9eb;
  836. color: #5cb87a;
  837. border-color: #c2e7b0;
  838. }
  839. /* 退回 */
  840. .status-return {
  841. background-color: #fff2f0;
  842. color: #ff4d4f;
  843. border-color: #ffccc7;
  844. }
  845. /* 退回 */
  846. .status-accept_return {
  847. background-color: #fff2f0;
  848. color: #ff4d4f;
  849. border-color: #ffccc7;
  850. }
  851. /* 作废 */
  852. .status-invalid {
  853. background-color: #fff2f0;
  854. color: #ff4d4f;
  855. border-color: #ffccc7;
  856. }
  857. .scroll-view_H {
  858. width: 100%;
  859. flex-direction: row;
  860. }
  861. .scroll-view-item_H {
  862. justify-content: center;
  863. align-items: center;
  864. .status-txt{
  865. padding: 8px 12px;
  866. text-align: center;
  867. margin-right: 12rpx;
  868. border-radius: 36rpx;
  869. background-color: #fff;
  870. font-size: 28rpx;
  871. }
  872. .stauts-sel{
  873. background-color: #007AFF;
  874. color: #fff;
  875. }
  876. }
  877. .btn-group {
  878. flex-direction: row;
  879. align-items: center;
  880. justify-content: flex-end;
  881. margin-top: 20rpx;
  882. }
  883. </style>