index.uvue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <template>
  2. <uni-navbar-lite @rightClick="handleRight" title="申请单"></uni-navbar-lite>
  3. <view class="list-page">
  4. <!-- 搜索栏和状态标签 -->
  5. <view class="search-block">
  6. <view class="search-bar">
  7. <view class="search-box">
  8. <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
  9. <input class="search-input" type="text" placeholder="请输入申请单号查询" v-model="keyword" @confirm="handleSearch" />
  10. <view v-if="keyword.length > 0" class="clear-btn" @tap="clearKeyword">
  11. <text class="clear-btn-text">×</text>
  12. </view>
  13. <view class="search-btn" @tap="handleSearch">
  14. <text class="search-btn-text">搜索</text>
  15. </view>
  16. </view>
  17. </view>
  18. <!-- 状态标签 -->
  19. <view class="status-tabs">
  20. <view
  21. class="status-tab"
  22. :class="{ 'active': currentStatus === '' }"
  23. @tap="handleStatusChange('')"
  24. >
  25. <text class="status-tab-text" :class="{ 'active-text': currentStatus === '' }">全部</text>
  26. </view>
  27. <view
  28. class="status-tab"
  29. :class="{ 'active': currentStatus === 'PREPARE' }"
  30. @tap="handleStatusChange('PREPARE')"
  31. >
  32. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'PREPARE' }">待确认</text>
  33. </view>
  34. <view
  35. class="status-tab"
  36. :class="{ 'active': currentStatus === 'CONFIRMED' }"
  37. @tap="handleStatusChange('CONFIRMED')"
  38. >
  39. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'CONFIRMED' }">已确认</text>
  40. </view>
  41. <view
  42. class="status-tab"
  43. :class="{ 'active': currentStatus === 'APPROVING' }"
  44. @tap="handleStatusChange('APPROVING')"
  45. >
  46. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'APPROVING' }">审批中</text>
  47. </view>
  48. <view
  49. class="status-tab"
  50. :class="{ 'active': currentStatus === 'APPROVED' }"
  51. @tap="handleStatusChange('APPROVED')"
  52. >
  53. <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'APPROVED' }">已审批</text>
  54. </view>
  55. </view>
  56. </view>
  57. <!-- 列表内容 -->
  58. <common-list
  59. :dataList="dataList"
  60. :loading="loading"
  61. :refreshing="refreshing"
  62. :hasMore="hasMore"
  63. @refresh="handleRefresh"
  64. @loadMore="loadMore"
  65. @itemClick="handleItemClick"
  66. >
  67. <template #default="{ item, index }">
  68. <view class="list-item">
  69. <view class="item-container">
  70. <view class="item-header">
  71. <text class="item-title">{{ getApplyCode(item) }}</text>
  72. <text class="item-status" :class="'status-' + getStatus(item)">{{ getStatusText(item) }}</text>
  73. </view>
  74. <view class="item-info">
  75. <view class="info-row">
  76. <view class="info-item">
  77. <text class="info-label">申请时间</text>
  78. <text class="info-value">{{ getCreateTime(item) }}</text>
  79. </view>
  80. <view class="info-item">
  81. <text class="info-label">申请人</text>
  82. <text class="info-value">{{ getNickName(item) }}</text>
  83. </view>
  84. </view>
  85. <view class="info-row">
  86. <view class="info-item">
  87. <text class="info-label">已完成</text>
  88. <text class="info-value success">{{ getFinishedCount(item) }}</text>
  89. </view>
  90. <view class="info-item">
  91. <text class="info-label">未完成</text>
  92. <text class="info-value warning">{{ getUnfinishedCount(item) }}</text>
  93. </view>
  94. </view>
  95. </view>
  96. </view>
  97. </view>
  98. </template>
  99. </common-list>
  100. </view>
  101. </template>
  102. <script setup lang="uts">
  103. import { ref, onShow } from 'vue'
  104. import { getPurchaseApplyList } from '../../api/apply/index'
  105. // 列表数据
  106. const dataList = ref<any[]>([])
  107. const keyword = ref<string>("")
  108. const currentStatus = ref<string>("")
  109. const page = ref<number>(1)
  110. const pageSize: number = 10
  111. const hasMore = ref<boolean>(true)
  112. const loading = ref<boolean>(false)
  113. const refreshing = ref<boolean>(false)
  114. // 获取申请单号
  115. const getApplyCode = (item: any | null): string => {
  116. if (item == null) return ''
  117. const jsonItem = item as UTSJSONObject
  118. const val = jsonItem['applyCode']
  119. return val != null ? val.toString() : ''
  120. }
  121. // 获取状态
  122. const getStatus = (item: any | null): string => {
  123. if (item == null) return ''
  124. const jsonItem = item as UTSJSONObject
  125. const val = jsonItem['status']
  126. return val != null ? val.toString() : ''
  127. }
  128. // 获取状态文本
  129. const getStatusText = (item: any | null): string => {
  130. if (item == null) return ''
  131. const jsonItem = item as UTSJSONObject
  132. const val = jsonItem['status']
  133. const status = val != null ? val.toString() : ''
  134. switch (status) {
  135. case 'PREPARE': return '草稿'
  136. case 'CONFIRMED': return '已确认'
  137. case 'FINISHED': return '已完成'
  138. case 'CANCEL': return '已取消'
  139. case 'APPROVED': return '已审批'
  140. case 'APPROVING': return '审批中'
  141. default: return status
  142. }
  143. }
  144. // 获取创建时间
  145. const getCreateTime = (item: any | null): string => {
  146. if (item == null) return ''
  147. const jsonItem = item as UTSJSONObject
  148. const val = jsonItem['createTime']
  149. return val != null ? val.toString() : ''
  150. }
  151. // 获取申请人
  152. const getNickName = (item: any | null): string => {
  153. if (item == null) return ''
  154. const jsonItem = item as UTSJSONObject
  155. const val = jsonItem['nickName']
  156. return val != null ? val.toString() : ''
  157. }
  158. // 获取已完成数量
  159. const getFinishedCount = (item: any | null): string => {
  160. if (item == null) return '0'
  161. const jsonItem = item as UTSJSONObject
  162. const val = jsonItem['finishedCount']
  163. return val != null ? val.toString() : '0'
  164. }
  165. // 获取未完成数量
  166. const getUnfinishedCount = (item: any | null): string => {
  167. if (item == null) return '0'
  168. const jsonItem = item as UTSJSONObject
  169. const val = jsonItem['unfinishedCount']
  170. return val != null ? val.toString() : '0'
  171. }
  172. // 加载列表数据
  173. const loadData = async (isRefresh: boolean): Promise<void> => {
  174. if (loading.value) return
  175. try {
  176. loading.value = true
  177. if (isRefresh) {
  178. page.value = 1
  179. }
  180. const searchKeyword = keyword.value != null ? keyword.value : ''
  181. const statusParam = currentStatus.value != null ? currentStatus.value : ''
  182. const result = await getPurchaseApplyList(page.value, pageSize, searchKeyword, statusParam)
  183. const resultObj = result as UTSJSONObject
  184. const rows = resultObj['rows']
  185. const total = resultObj['total'] as number
  186. if (rows != null) {
  187. const newData = rows as any[]
  188. if (isRefresh) {
  189. dataList.value = newData
  190. } else {
  191. dataList.value = [...dataList.value, ...newData]
  192. }
  193. hasMore.value = dataList.value.length < total
  194. } else {
  195. if (isRefresh) {
  196. dataList.value = []
  197. }
  198. hasMore.value = false
  199. }
  200. } catch (e) {
  201. console.error('加载失败:', e)
  202. } finally {
  203. loading.value = false
  204. refreshing.value = false
  205. }
  206. }
  207. // 下拉刷新
  208. const handleRefresh = (): void => {
  209. refreshing.value = true
  210. loadData(true)
  211. }
  212. // 加载更多
  213. const loadMore = (): void => {
  214. if (!hasMore.value || loading.value) return
  215. page.value++
  216. loadData(false)
  217. }
  218. // 搜索
  219. const handleSearch = (): void => {
  220. const searchKeyword = keyword.value != null ? keyword.value : ''
  221. loadData(true)
  222. }
  223. // 清空搜索关键字
  224. const clearKeyword = (): void => {
  225. keyword.value = ''
  226. loadData(true)
  227. }
  228. // 切换状态
  229. const handleStatusChange = (status: string): void => {
  230. currentStatus.value = status
  231. loadData(true)
  232. }
  233. const handleRight = ():void=>{
  234. uni.navigateTo({
  235. url: `/pages/apply/applyNew?from=index`
  236. })
  237. }
  238. // 点击列表项
  239. const handleItemClick = (item: any | null, index: number): void => {
  240. if (item == null) return
  241. const jsonItem = item as UTSJSONObject
  242. const applyId = jsonItem['applyId']
  243. uni.navigateTo({
  244. url: `/pages/apply/applyInfo?id=${applyId}`
  245. })
  246. }
  247. // 初始化
  248. loadData(true)
  249. // 页面显示时刷新列表
  250. onShow(() => {
  251. loadData(true)
  252. })
  253. </script>
  254. <style lang="scss">
  255. .list-page {
  256. flex: 1;
  257. background-color: #e8f0f9;
  258. }
  259. .search-block {
  260. background-color: #ffffff;
  261. }
  262. .search-bar {
  263. padding: 20rpx 30rpx;
  264. }
  265. .status-tabs {
  266. display: flex;
  267. flex-direction: row;
  268. padding: 0rpx 30rpx 20rpx;
  269. background-color: #ffffff;
  270. }
  271. .status-tab {
  272. flex: 1;
  273. padding: 16rpx 0;
  274. text-align: center;
  275. margin-right: 16rpx;
  276. border-radius: 8rpx;
  277. background-color: #f5f5f5;
  278. justify-content: center;
  279. align-items: center;
  280. &:last-child {
  281. margin-right: 0;
  282. }
  283. &.active {
  284. background-color: #007aff;
  285. }
  286. .status-tab-text {
  287. font-size: 26rpx;
  288. color: #666666;
  289. text-align: center;
  290. &.active-text {
  291. color: #ffffff;
  292. font-weight: bold;
  293. }
  294. }
  295. }
  296. .search-box {
  297. flex-direction: row;
  298. align-items: center;
  299. height: 72rpx;
  300. padding: 0 24rpx;
  301. background-color: #f5f5f5;
  302. border-radius: 36rpx;
  303. .search-icon {
  304. width: 32rpx;
  305. height: 32rpx;
  306. margin-right: 12rpx;
  307. }
  308. .search-input {
  309. flex: 1;
  310. font-size: 28rpx;
  311. color: #333333;
  312. }
  313. .search-btn {
  314. padding: 10rpx 20rpx;
  315. background-color: #007aff;
  316. border-radius: 8rpx;
  317. margin-left: 10rpx;
  318. }
  319. .search-btn-text {
  320. color: #ffffff;
  321. font-size: 24rpx;
  322. }
  323. .clear-btn {
  324. width: 36rpx;
  325. height: 36rpx;
  326. border-radius: 18rpx;
  327. background-color: #cccccc;
  328. align-items: center;
  329. justify-content: center;
  330. margin-left: 10rpx;
  331. }
  332. .clear-btn-text {
  333. color: #ffffff;
  334. font-size: 24rpx;
  335. font-weight: bold;
  336. }
  337. }
  338. .list-item {
  339. margin: 24rpx 30rpx;
  340. background-color: #ffffff;
  341. border-radius: 16rpx;
  342. }
  343. .item-container {
  344. padding: 30rpx;
  345. }
  346. .item-header {
  347. flex-direction: row;
  348. align-items: center;
  349. margin-bottom: 20rpx;
  350. .item-title {
  351. flex: 1;
  352. font-size: 32rpx;
  353. color: #333333;
  354. font-weight: bold;
  355. }
  356. .item-status {
  357. font-size: 26rpx;
  358. padding: 6rpx 16rpx;
  359. border-radius: 6rpx;
  360. &.status-PREPARE {
  361. background-color: #f0f0f0;
  362. color: #666666;
  363. }
  364. &.status-CONFIRMED {
  365. background-color: #e6f7ff;
  366. color: #1890ff;
  367. }
  368. &.status-FINISHED {
  369. background-color: #f6ffed;
  370. color: #52c41a;
  371. }
  372. &.status-CANCEL {
  373. background-color: #fff1f0;
  374. color: #ff4d4f;
  375. }
  376. &.status-APPROVING {
  377. background-color: #d1f5f8;
  378. color: #ff007f;
  379. }
  380. &.status-APPROVED {
  381. background-color: #ebfffd;
  382. color: #55ff00;
  383. }
  384. }
  385. }
  386. .item-info {
  387. padding: 20rpx;
  388. background-color: #f8f9fa;
  389. border-radius: 8rpx;
  390. .info-row {
  391. flex-direction: row;
  392. justify-content: space-between;
  393. margin-bottom: 16rpx;
  394. &:last-child {
  395. margin-bottom: 0;
  396. }
  397. .info-item {
  398. flex-direction: row;
  399. align-items: center;
  400. flex: 1;
  401. &:last-child {
  402. flex: 1;
  403. justify-content: flex-end;
  404. }
  405. .info-label {
  406. font-size: 26rpx;
  407. color: #666666;
  408. margin-right: 8rpx;
  409. white-space: nowrap;
  410. }
  411. .info-value {
  412. font-size: 26rpx;
  413. color: #333333;
  414. flex: 1;
  415. &.success {
  416. color: #52c41a;
  417. }
  418. &.warning {
  419. color: #faad14;
  420. }
  421. }
  422. }
  423. }
  424. }
  425. </style>