index.uvue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <template>
  2. <uni-navbar-lite @rightClick="handleRight" :show-right="showRight" title="生产汇报"></uni-navbar-lite>
  3. <view class="list-page">
  4. <view class="search-block">
  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" @input="handleSearch" />
  9. <view v-if="keyword.length > 0" class="clear-btn" @tap="clearKeyword">
  10. <text class="clear-btn-text">×</text>
  11. </view>
  12. <view class="search-btn" @tap="handleSearch">
  13. <text class="search-btn-text">搜索</text>
  14. </view>
  15. </view>
  16. </view>
  17. <scroll-view class="status-tabs" scroll-x="true">
  18. <view class="status-tab" :class="{ 'active': currentStatus === '' }" @tap="handleStatusChange('')">
  19. <text class="status-tab-text" :class="{ 'active-text': currentStatus === '' }">全部</text>
  20. </view>
  21. <view class="status-tab" :class="{ 'active': currentStatus === 'PREPARE' }" @tap="handleStatusChange('PREPARE')">
  22. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'PREPARE' }">草稿</text>
  23. </view>
  24. <view class="status-tab" :class="{ 'active': currentStatus === 'CONFIRMED' }" @tap="handleStatusChange('CONFIRMED')">
  25. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'CONFIRMED' }">已确认</text>
  26. </view>
  27. <view class="status-tab" :class="{ 'active': currentStatus === 'FINISHED' }" @tap="handleStatusChange('FINISHED')">
  28. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'FINISHED' }">已完成</text>
  29. </view>
  30. </scroll-view>
  31. </view>
  32. <common-list
  33. :dataList="dataList"
  34. :loading="loading"
  35. :refreshing="refreshing"
  36. :hasMore="hasMore"
  37. @refresh="handleRefresh"
  38. @loadMore="loadMore"
  39. >
  40. <template #default="{ item, index }">
  41. <view class="list-item" @tap="handleItemClick(item, index)">
  42. <view class="item-container">
  43. <view class="item-header">
  44. <text class="item-title">{{ getProductCode(item) }} - {{ getProductName(item) }}</text>
  45. <text class="item-status" :class="'status-' + getStatus(item)">{{ getStatusText(item) }}</text>
  46. </view>
  47. <view class="item-info">
  48. <view class="info-row">
  49. <view class="info-item">
  50. <text class="info-label">汇报数量</text>
  51. <text class="info-value warning">{{ getQuantity(item) }} {{ getUnitName(item) }}</text>
  52. </view>
  53. <view class="info-item">
  54. <text class="info-label">吨数</text>
  55. <text class="info-value primary">{{ getQuantityTon(item) }} {{ getBaseUnitName(item) }}</text>
  56. </view>
  57. </view>
  58. <view class="info-row">
  59. <view class="info-item">
  60. <text class="info-label">报工日期</text>
  61. <text class="info-value">{{ getReportDate(item) }}</text>
  62. </view>
  63. <view class="info-item">
  64. <text class="info-label">汇报人</text>
  65. <text class="info-value">{{ getReporter(item) }}</text>
  66. </view>
  67. </view>
  68. <view class="info-row">
  69. <view class="info-item">
  70. <text class="info-label">已入库</text>
  71. <text class="info-value success">{{ getReceiptedQuantity(item) }} {{ getBaseUnitName(item) }}</text>
  72. </view>
  73. <view class="info-item">
  74. <text class="info-label">时间</text>
  75. <text class="info-value">{{ getTimeRange(item) }}</text>
  76. </view>
  77. </view>
  78. </view>
  79. </view>
  80. </view>
  81. </template>
  82. </common-list>
  83. </view>
  84. </template>
  85. <script setup lang="uts">
  86. import { ref } from 'vue'
  87. import { onLoad, onShow } from '@dcloudio/uni-app';
  88. import { getProReportList } from '../../api/pro/index'
  89. import { getUserInfo } from '../../utils/storage'
  90. import { checkPermission } from '../../utils/permission'
  91. let currentUserId: string = ''
  92. const dataList = ref<any[]>([])
  93. const keyword = ref<string>("")
  94. let searchTimer: number | null = null
  95. const currentStatus = ref<string>("")
  96. const page = ref<number>(1)
  97. const pageSize: number = 10
  98. const hasMore = ref<boolean>(true)
  99. const loading = ref<boolean>(false)
  100. const refreshing = ref<boolean>(false)
  101. const showRight = ref<boolean>(false)
  102. const getProductCode = (item: any | null): string => {
  103. if (item == null) return ''
  104. const jsonItem = item as UTSJSONObject
  105. const val = jsonItem['productCode']
  106. return val != null ? val.toString() : ''
  107. }
  108. const getProductName = (item: any | null): string => {
  109. if (item == null) return ''
  110. const jsonItem = item as UTSJSONObject
  111. const val = jsonItem['productName']
  112. return val != null ? val.toString() : ''
  113. }
  114. const getStatus = (item: any | null): string => {
  115. if (item == null) return ''
  116. const jsonItem = item as UTSJSONObject
  117. const val = jsonItem['status']
  118. return val != null ? val.toString() : ''
  119. }
  120. const getStatusText = (item: any | null): string => {
  121. if (item == null) return ''
  122. const jsonItem = item as UTSJSONObject
  123. const val = jsonItem['status']
  124. const status = val != null ? val.toString() : ''
  125. switch (status) {
  126. case 'PREPARE': return '草稿'
  127. case 'CONFIRMED': return '已确认'
  128. case 'FINISHED': return '已完成'
  129. case 'CANCEL': return '已取消'
  130. default: return status
  131. }
  132. }
  133. const getQuantity = (item: any | null): string => {
  134. if (item == null) return '0'
  135. const jsonItem = item as UTSJSONObject
  136. const val = jsonItem['quantity']
  137. return val != null ? val.toString() : '0'
  138. }
  139. const getUnitName = (item: any | null): string => {
  140. if (item == null) return ''
  141. const jsonItem = item as UTSJSONObject
  142. const val = jsonItem['unitName']
  143. return val != null ? val.toString() : ''
  144. }
  145. const getQuantityTon = (item: any | null): string => {
  146. if (item == null) return '0'
  147. const jsonItem = item as UTSJSONObject
  148. const val = jsonItem['quantityTon']
  149. return val != null ? val.toString() : '0'
  150. }
  151. const getBaseUnitName = (item: any | null): string => {
  152. if (item == null) return ''
  153. const jsonItem = item as UTSJSONObject
  154. const val = jsonItem['baseUnitName']
  155. return val != null ? val.toString() : ''
  156. }
  157. const getReportDate = (item: any | null): string => {
  158. if (item == null) return ''
  159. const jsonItem = item as UTSJSONObject
  160. const val = jsonItem['reportDate']
  161. return val != null ? val.toString() : ''
  162. }
  163. const getReporter = (item: any | null): string => {
  164. if (item == null) return ''
  165. const jsonItem = item as UTSJSONObject
  166. const val = jsonItem['reporter']
  167. return val != null ? val.toString() : ''
  168. }
  169. const getReceiptedQuantity = (item: any | null): string => {
  170. if (item == null) return '0'
  171. const jsonItem = item as UTSJSONObject
  172. const val = jsonItem['receiptedQuantity']
  173. return val != null ? val.toString() : '0'
  174. }
  175. const getSpecification = (item: any | null): string => {
  176. if (item == null) return ''
  177. const jsonItem = item as UTSJSONObject
  178. const val = jsonItem['specification']
  179. return val != null ? val.toString() : ''
  180. }
  181. const getTimeRange = (item: any | null): string => {
  182. if (item == null) return ''
  183. const jsonItem = item as UTSJSONObject
  184. const st = jsonItem['startTime']
  185. const et = jsonItem['endTime']
  186. let startStr = ''
  187. if (st != null) {
  188. const stFull = st.toString()
  189. startStr = stFull.length > 10 ? stFull.substring(11, 16) : stFull
  190. }
  191. let endStr = ''
  192. if (et != null) {
  193. const etFull = et.toString()
  194. endStr = etFull.length > 10 ? etFull.substring(11, 16) : etFull
  195. }
  196. if (startStr.length > 0 && endStr.length > 0) {
  197. return `${startStr}~${endStr}`
  198. } else if (startStr.length > 0) {
  199. return startStr
  200. } else if (endStr.length > 0) {
  201. return endStr
  202. }
  203. return ''
  204. }
  205. const loadData = async (isRefresh: boolean): Promise<void> => {
  206. if (loading.value) return
  207. try {
  208. loading.value = true
  209. if (isRefresh) {
  210. page.value = 1
  211. }
  212. const searchKeyword = keyword.value != null ? keyword.value : ''
  213. const statusParam = currentStatus.value != null ? currentStatus.value : ''
  214. const result = await getProReportList(page.value, pageSize, searchKeyword, statusParam)
  215. const resultObj = result as UTSJSONObject
  216. const rows = resultObj['rows']
  217. const total = resultObj['total'] as number
  218. if (rows != null) {
  219. const newData = rows as any[]
  220. if (isRefresh) {
  221. dataList.value = newData
  222. } else {
  223. dataList.value = [...dataList.value, ...newData]
  224. }
  225. hasMore.value = dataList.value.length < total
  226. } else {
  227. if (isRefresh) {
  228. dataList.value = []
  229. }
  230. hasMore.value = false
  231. }
  232. } catch (e) {
  233. console.error('加载失败:', e)
  234. } finally {
  235. loading.value = false
  236. refreshing.value = false
  237. }
  238. }
  239. const handleRefresh = (): void => {
  240. refreshing.value = true
  241. loadData(true)
  242. }
  243. const loadMore = (): void => {
  244. if (!hasMore.value || loading.value) return
  245. page.value++
  246. loadData(false)
  247. }
  248. const handleSearch = (): void => {
  249. const timer = searchTimer
  250. if (timer != null) {
  251. clearTimeout(timer)
  252. }
  253. searchTimer = setTimeout(() => {
  254. loadData(true)
  255. }, 300)
  256. }
  257. const clearKeyword = (): void => {
  258. keyword.value = ''
  259. loadData(true)
  260. }
  261. const handleStatusChange = (status: string): void => {
  262. currentStatus.value = status
  263. loadData(true)
  264. }
  265. const handleRight = (): void => {
  266. uni.navigateTo({
  267. url: `/pages/pro/create?from=index`
  268. })
  269. }
  270. const handleItemClick = (item: any | null, index: number): void => {
  271. if (item == null) return
  272. const jsonItem = item as UTSJSONObject
  273. const reportId = jsonItem['reportId']
  274. if (reportId == null) return
  275. const status = getStatus(item)
  276. if (status === 'PREPARE') {
  277. uni.navigateTo({
  278. url: `/pages/pro/create?id=${reportId}`
  279. })
  280. } else if (status === 'CONFIRMED' || status === 'FINISHED') {
  281. uni.navigateTo({
  282. url: `/pages/pro/detail?id=${reportId}`
  283. })
  284. }
  285. }
  286. onShow(() => {
  287. if(currentUserId.length > 0){
  288. const needRefresh = uni.getStorageSync('needRefresh')
  289. if (needRefresh === 'true') {
  290. uni.removeStorageSync('needRefresh')
  291. loadData(true)
  292. }
  293. }
  294. })
  295. onMounted(() => {
  296. const userInfo = getUserInfo()
  297. if (userInfo != null) {
  298. const userId = userInfo['userId']
  299. currentUserId = userId != null ? userId.toString() : ''
  300. loadData(true)
  301. }
  302. const pages = getCurrentPages()
  303. const currentPage = pages[pages.length - 1]
  304. const options = currentPage.options
  305. if (options != null && options.from == 'new') {
  306. showRight.value = false
  307. } else {
  308. showRight.value = true
  309. }
  310. })
  311. </script>
  312. <style lang="scss">
  313. .list-page {
  314. flex: 1;
  315. background-color: #e8f0f9;
  316. }
  317. .search-block {
  318. background-color: #ffffff;
  319. }
  320. .search-bar {
  321. padding: 20rpx 30rpx;
  322. }
  323. .search-box {
  324. flex-direction: row;
  325. align-items: center;
  326. height: 72rpx;
  327. padding: 0 24rpx;
  328. background-color: #f5f5f5;
  329. border-radius: 36rpx;
  330. .search-icon {
  331. width: 32rpx;
  332. height: 32rpx;
  333. margin-right: 12rpx;
  334. }
  335. .search-input {
  336. flex: 1;
  337. font-size: 28rpx;
  338. color: #333333;
  339. }
  340. .search-btn {
  341. padding: 10rpx 20rpx;
  342. background-color: #007aff;
  343. border-radius: 8rpx;
  344. margin-left: 10rpx;
  345. }
  346. .search-btn-text {
  347. color: #ffffff;
  348. font-size: 24rpx;
  349. }
  350. .clear-btn {
  351. width: 36rpx;
  352. height: 36rpx;
  353. border-radius: 18rpx;
  354. background-color: #cccccc;
  355. align-items: center;
  356. justify-content: center;
  357. margin-left: 10rpx;
  358. }
  359. .clear-btn-text {
  360. color: #ffffff;
  361. font-size: 24rpx;
  362. font-weight: bold;
  363. }
  364. }
  365. .status-tabs {
  366. display: flex;
  367. flex-direction: row;
  368. padding: 0rpx 30rpx 20rpx;
  369. background-color: #ffffff;
  370. white-space: nowrap;
  371. width: 100%;
  372. }
  373. .status-tab {
  374. display: inline-flex;
  375. flex-direction: row;
  376. align-items: center;
  377. padding: 16rpx 24rpx;
  378. text-align: center;
  379. position: relative;
  380. margin-right: 16rpx;
  381. border-radius: 8rpx;
  382. background-color: #f5f5f5;
  383. justify-content: center;
  384. align-items: center;
  385. &:last-child {
  386. margin-right: 0;
  387. }
  388. &.active {
  389. background-color: #007aff;
  390. }
  391. .status-tab-text {
  392. font-size: 26rpx;
  393. color: #666666;
  394. text-align: center;
  395. &.active-text {
  396. color: #ffffff;
  397. font-weight: bold;
  398. }
  399. }
  400. }
  401. .list-item {
  402. margin: 10rpx 20rpx;
  403. background-color: #ffffff;
  404. border-radius: 16rpx;
  405. }
  406. .item-container {
  407. padding: 30rpx;
  408. }
  409. .item-header {
  410. flex-direction: row;
  411. align-items: center;
  412. margin-bottom: 20rpx;
  413. .item-title {
  414. flex: 1;
  415. font-size: 32rpx;
  416. color: #333333;
  417. font-weight: bold;
  418. }
  419. .item-status {
  420. font-size: 26rpx;
  421. padding: 6rpx 16rpx;
  422. border-radius: 6rpx;
  423. &.status-PREPARE {
  424. background-color: #f0f0f0;
  425. color: #666666;
  426. }
  427. &.status-CONFIRMED {
  428. background-color: #e6f7ff;
  429. color: #1890ff;
  430. }
  431. &.status-FINISHED {
  432. background-color: #f6ffed;
  433. color: #52c41a;
  434. }
  435. &.status-CANCEL {
  436. background-color: #fff1f0;
  437. color: #ff4d4f;
  438. }
  439. }
  440. }
  441. .item-info {
  442. padding: 20rpx;
  443. background-color: #f8f9fa;
  444. border-radius: 8rpx;
  445. .info-row {
  446. flex-direction: row;
  447. justify-content: space-between;
  448. margin-bottom: 16rpx;
  449. &:last-child {
  450. margin-bottom: 0;
  451. }
  452. .info-item {
  453. flex-direction: row;
  454. align-items: center;
  455. flex: 1;
  456. &:last-child {
  457. flex: 1;
  458. justify-content: flex-end;
  459. }
  460. .info-label {
  461. font-size: 26rpx;
  462. color: #666666;
  463. margin-right: 8rpx;
  464. white-space: nowrap;
  465. }
  466. .info-value {
  467. font-size: 26rpx;
  468. color: #333333;
  469. flex: 1;
  470. &.success {
  471. color: #52c41a;
  472. }
  473. &.warning {
  474. color: #faad14;
  475. }
  476. &.primary {
  477. color: #007aff;
  478. }
  479. }
  480. }
  481. }
  482. }
  483. </style>