index.uvue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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" />
  9. </view>
  10. </view>
  11. <!-- 列表内容 -->
  12. <common-list
  13. :dataList="dataList"
  14. :loading="loading"
  15. :refreshing="refreshing"
  16. :hasMore="hasMore"
  17. @refresh="handleRefresh"
  18. @loadMore="loadMore"
  19. @itemClick="handleItemClick"
  20. >
  21. <template #default="{ item, index }">
  22. <view class="list-item">
  23. <view class="item-container">
  24. <view class="item-header">
  25. <image class="location-icon" src="/static/images/workbench/list/2.png" mode="aspectFit"></image>
  26. <text class="item-title">{{ getOrderType(item) }}</text>
  27. <text class="detail-link">类型 ›</text>
  28. </view>
  29. <text class="item-address">{{ getWorkOrderProjectNo(item) }}</text>
  30. <view class="item-info">
  31. <view class="info-row">
  32. <view class="info-item">
  33. <text class="info-label">工单状态</text>
  34. <text class="info-value">{{ getWorkOrderStatus(item) }}</text>
  35. </view>
  36. <view class="info-item">
  37. <text class="info-label">场站</text>
  38. <text class="info-value">{{ getPcsStationName(item) }}</text>
  39. </view>
  40. </view>
  41. <view class="info-row">
  42. <view class="info-item">
  43. <text class="info-label">设备名</text>
  44. <text class="info-value">{{ getPcsDeviceName(item) }}</text>
  45. </view>
  46. <view class="info-item">
  47. <text class="info-label">派单时间</text>
  48. <text class="info-value">{{ getAssignTime(item) }}</text>
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. </view>
  54. </template>
  55. </common-list>
  56. <custom-tabbar :current="1" />
  57. </view>
  58. </template>
  59. <script setup lang="uts">
  60. import { ref, computed, onBeforeUnmount } from 'vue'
  61. import type { orderInfo, orderListResponse } from '../../types/order'
  62. import { getOrderList } from '../../api/order/list'
  63. // 列表数据
  64. const dataList = ref<orderInfo[]>([])
  65. const keyword = ref<string>("")
  66. const page = ref<number>(1)
  67. const pageSize: number = 10
  68. const hasMore = ref<boolean>(true)
  69. const loading = ref<boolean>(false)
  70. const refreshing = ref<boolean>(false)
  71. const total = ref<number>(0)
  72. // 辅助函数:从 any 类型提取属性
  73. const getOrderType = (item: any | null): string => {
  74. if (item == null) return ''
  75. const orderInfoItem = item as orderInfo
  76. return orderInfoItem.orderType == 1?"维修工单":"维保工单";
  77. }
  78. const getWorkOrderProjectNo = (item: any | null): string | null => {
  79. if (item == null) return ''
  80. const orderInfoItem = item as orderInfo
  81. return orderInfoItem.workOrderProjectNo
  82. }
  83. const getWorkOrderStatus = (item: any | null): string | null => {
  84. if (item == null) return ''
  85. const orderInfoItem = item as orderInfo
  86. return orderInfoItem.workOrderStatus
  87. }
  88. const getPcsStationName = (item: any | null): string | null=> {
  89. if (item == null) return ''
  90. const orderInfoItem = item as orderInfo
  91. return orderInfoItem.pcsStationName
  92. }
  93. const getPcsDeviceName = (item: any | null): string | null=> {
  94. if (item == null) return ''
  95. const orderInfoItem = item as orderInfo
  96. return orderInfoItem.pcsDeviceName
  97. }
  98. const getAssignTime = (item: any | null): string|null => {
  99. if (item == null) return null
  100. const orderInfoItem = item as orderInfo
  101. return orderInfoItem.assignTime
  102. }
  103. // 加载列表数据
  104. const loadData = async (isRefresh: boolean | null): Promise<void> => {
  105. if (loading.value) {
  106. // 如果正在加载,直接重置刷新状态
  107. refreshing.value = false
  108. return
  109. }
  110. try {
  111. loading.value = true
  112. // 处理默认值
  113. const shouldRefresh = isRefresh != null ? isRefresh : false
  114. if (shouldRefresh) {
  115. page.value = 1
  116. }
  117. // 调用 API
  118. const searchKeyword = keyword.value.length > 0 ? keyword.value : null
  119. const result = await getOrderList(page.value, pageSize, searchKeyword)
  120. // 提取响应数据
  121. const resultObj = result as UTSJSONObject
  122. const code = resultObj['code'] as number
  123. const responseData = resultObj['rows'] as any[]
  124. const responseTotal = resultObj['total'] as number
  125. if (code == 200) {
  126. // 将 any[] 转换为 orderInfo[]
  127. const newData: orderInfo[] = []
  128. for (let i = 0; i < responseData.length; i++) {
  129. const item = responseData[i] as UTSJSONObject
  130. const orderItem: orderInfo = {
  131. orderType: item['orderType'] as Number,
  132. id: item['id'] as Number,
  133. teamLeaderName: item['teamLeaderName'] as string | '',
  134. acceptUserName: item['acceptUserName'] as string | '',
  135. acceptTime: item['acceptTime'] as string | null,
  136. assignTime: item['assignTime'] as string | null,
  137. assignUserName: item['assignUserName'] as string | null,
  138. status: (item['status']==null)?0:item['status'] as Number,
  139. workOrderProjectNo: item['workOrderProjectNo'] as string | null,
  140. workOrderStatus: item['workOrderStatus'] as string | null,
  141. gxtCenterId: item['gxtCenterId'] as Number | 0,
  142. gxtCenter: item['gxtCenter'] as string | null,
  143. pcsStationId: item['pcsStationId'] as Number | 0,
  144. pcsStationName: item['pcsStationName'] as string | null,
  145. pcsDeviceId: item['pcsDeviceId'] as Number | 0,
  146. pcsDeviceName: item['pcsDeviceName'] as string | null,
  147. brand: item['brand'] as string | null,
  148. model: item['model'] as string | null
  149. }
  150. newData.push(orderItem)
  151. }
  152. if (shouldRefresh) {
  153. dataList.value = newData
  154. } else {
  155. dataList.value = [...dataList.value, ...newData]
  156. }
  157. total.value = responseTotal
  158. hasMore.value = dataList.value.length < responseTotal
  159. } else {
  160. const msg = resultObj['msg'] as string | null
  161. uni.showToast({
  162. title: msg ?? '加载失败',
  163. icon: 'none'
  164. })
  165. }
  166. } catch (e: any) {
  167. uni.showToast({
  168. title: e.message ?? '加载失败',
  169. icon: 'none'
  170. })
  171. } finally {
  172. loading.value = false
  173. // #ifdef WEB
  174. // Web 平台立即重置
  175. refreshing.value = false
  176. // #endif
  177. // #ifndef WEB
  178. // App 平台延迟重置刷新状态,确保 UI 更新
  179. setTimeout(() => {
  180. refreshing.value = false
  181. }, 100)
  182. // #endif
  183. }
  184. // #ifdef WEB
  185. // Web 平台额外确保重置
  186. refreshing.value = false
  187. // #endif
  188. }
  189. // 下拉刷新
  190. const handleRefresh = async (): Promise<void> => {
  191. refreshing.value = true
  192. await loadData(true as boolean | null)
  193. }
  194. // 加载更多
  195. const loadMore = (): void => {
  196. if (!hasMore.value || loading.value) {
  197. return
  198. }
  199. page.value++
  200. loadData(false as boolean | null)
  201. }
  202. // 搜索
  203. const handleSearch = (): void => {
  204. page.value = 1
  205. loadData(true as boolean | null)
  206. }
  207. // 点击列表项
  208. const handleItemClick = (item: any | null, index: number): void => {
  209. if (item == null) return
  210. const contractorItem = item as ContractorInfo
  211. uni.navigateTo({
  212. url: `/pages/workbench/detail/index?id=${contractorItem.id}`
  213. })
  214. }
  215. // 组件卸载前清理
  216. onBeforeUnmount(() => {
  217. refreshing.value = false
  218. loading.value = false
  219. })
  220. // 初始化
  221. loadData(true as boolean | null)
  222. </script>
  223. <style lang="scss">
  224. .list-page {
  225. flex: 1;
  226. background-color: #e8f0f9;
  227. }
  228. .search-bar {
  229. padding: 20rpx 30rpx;
  230. background-color: #d7eafe;
  231. }
  232. .search-box {
  233. flex-direction: row;
  234. align-items: center;
  235. height: 72rpx;
  236. padding: 0 24rpx;
  237. background-color: #f5f5f5;
  238. border-radius: 36rpx;
  239. .search-icon {
  240. width: 32rpx;
  241. height: 32rpx;
  242. margin-right: 12rpx;
  243. }
  244. .search-input {
  245. flex: 1;
  246. font-size: 28rpx;
  247. color: #333333;
  248. }
  249. }
  250. .list-item {
  251. margin: 24rpx 30rpx;
  252. background-color: #ffffff;
  253. border-radius: 16rpx;
  254. }
  255. .item-container {
  256. padding: 30rpx;
  257. }
  258. .item-header {
  259. flex-direction: row;
  260. align-items: center;
  261. margin-bottom: 16rpx;
  262. .location-icon {
  263. width: 32rpx;
  264. height: 32rpx;
  265. margin-right: 8rpx;
  266. }
  267. .item-title {
  268. flex: 1;
  269. font-size: 32rpx;
  270. color: #333333;
  271. font-weight: bold;
  272. }
  273. .detail-link {
  274. font-size: 28rpx;
  275. color: #999999;
  276. }
  277. }
  278. .item-address {
  279. font-size: 26rpx;
  280. color: #999999;
  281. margin-bottom: 20rpx;
  282. line-height: 40rpx;
  283. }
  284. .item-info {
  285. padding: 20rpx;
  286. background-color: #f8f9fa;
  287. border-radius: 8rpx;
  288. .info-row {
  289. flex-direction: row;
  290. margin-bottom: 16rpx;
  291. &:last-child {
  292. margin-bottom: 0;
  293. }
  294. .info-item {
  295. flex: 1;
  296. flex-direction: row;
  297. align-items: center;
  298. .info-label {
  299. font-size: 26rpx;
  300. color: #666666;
  301. margin-right: 8rpx;
  302. white-space: nowrap;
  303. }
  304. .info-value {
  305. flex: 1;
  306. font-size: 26rpx;
  307. color: #333333;
  308. }
  309. }
  310. }
  311. }
  312. </style>