createByApply.uvue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. <template>
  2. <uni-navbar-lite :showRight=false title="创建采购单"></uni-navbar-lite>
  3. <view class="page-container">
  4. <scroll-view class="page-content" scroll-y="true">
  5. <view v-if="applyList.length === 0" class="empty-tip">
  6. <text class="empty-tip-text">暂无申请单数据</text>
  7. </view>
  8. <view v-else>
  9. <view class="purchase-info-section">
  10. <view class="purchase-info-row">
  11. <text class="purchase-info-label">采购单名称</text>
  12. <view class="purchase-info-value-wrap">
  13. <input
  14. class="purchase-info-input"
  15. v-model="purchaseName"
  16. placeholder="请输入采购单名称"
  17. />
  18. </view>
  19. </view>
  20. <view class="purchase-remark-section">
  21. <text class="purchase-remark-label">采购单备注</text>
  22. <textarea
  23. class="purchase-remark-input"
  24. placeholder="请输入采购单备注"
  25. v-model="purchaseRemark"
  26. :maxlength="500"
  27. ></textarea>
  28. </view>
  29. </view>
  30. <view v-for="(apply, applyIndex) in applyList" :key="applyIndex" class="section">
  31. <view class="section-header">
  32. <view class="section-indicator"></view>
  33. <text class="section-title">申请单 {{ apply.applyCode }}</text>
  34. </view>
  35. <view class="info-card">
  36. <view class="info-row">
  37. <text class="info-label">申请人</text>
  38. <text class="info-value">{{ apply.nickName }}</text>
  39. </view>
  40. <view class="info-row">
  41. <text class="info-label">申请时间</text>
  42. <text class="info-value">{{ apply.createTime }}</text>
  43. </view>
  44. <view class="info-row">
  45. <text class="info-label">用途</text>
  46. <text class="info-value">{{ apply.remark }}</text>
  47. </view>
  48. </view>
  49. <view class="material-section">
  50. <view class="material-section-header">
  51. <text class="material-section-title">物料明细</text>
  52. </view>
  53. <view class="material-list">
  54. <view
  55. v-for="(item, itemIndex) in apply.lineList"
  56. :key="itemIndex"
  57. class="material-item"
  58. >
  59. <view class="material-info">
  60. <text class="material-name">{{ getItemName(item) }}</text>
  61. <text class="material-spec" v-if="getSpecification(item)">规格:{{ getSpecification(item) }}</text>
  62. </view>
  63. <view class="material-detail">
  64. <view class="detail-row">
  65. <text class="detail-label">申请数量</text>
  66. <text class="detail-value">{{ getQuantity(item) }} {{ getMeasureName(item) }}</text>
  67. </view>
  68. <view class="detail-row purchase-row">
  69. <text class="detail-label">采购数量</text>
  70. <view class="purchase-input-wrap">
  71. <input
  72. class="purchase-input"
  73. type="number"
  74. v-model="item.purchaseQty"
  75. @input="handlePurchaseQtyChange"
  76. />
  77. <text class="purchase-unit">{{ getMeasureName(item) }}</text>
  78. </view>
  79. </view>
  80. </view>
  81. </view>
  82. </view>
  83. </view>
  84. </view>
  85. </view>
  86. <view class="bottom-space"></view>
  87. </scroll-view>
  88. <view class="bottom-buttons">
  89. <button class="submit-btn" @click="handleSubmit">生成采购单</button>
  90. </view>
  91. </view>
  92. </template>
  93. <script setup lang="uts">
  94. import { ref } from 'vue'
  95. import { getPurchaseApplyById } from '../../api/apply/index'
  96. import { createMergePurchase } from '../../api/purchase/index'
  97. import { getUserInfo } from '../../utils/storage'
  98. const applyIds = ref<string>("")
  99. const idList = ref<string[]>([])
  100. const applyList = ref<UTSJSONObject[]>([])
  101. const loading = ref<boolean>(false)
  102. const purchaseName = ref<string>("")
  103. const purchaseRemark = ref<string>("")
  104. const currentUserId = ref<string>("")
  105. const getItemName = (item: UTSJSONObject): string => {
  106. if (item == null) return ''
  107. const val = item['itemName']
  108. return val != null ? val.toString() : ''
  109. }
  110. const getSpecification = (item: UTSJSONObject): string => {
  111. if (item == null) return ''
  112. const val = item['specification']
  113. return val != null ? val.toString() : ''
  114. }
  115. const getQuantity = (item: UTSJSONObject): string => {
  116. if (item == null) return '0'
  117. const val = item['quantityApply']
  118. return val != null ? val.toString() : '0'
  119. }
  120. const getMeasureName = (item: UTSJSONObject): string => {
  121. if (item == null) return ''
  122. const val = item['measureName']
  123. if (val != null) return val.toString()
  124. const code = item['unitOfMeasure']
  125. return code != null ? code.toString() : ''
  126. }
  127. const handlePurchaseQtyChange = (): void => {
  128. }
  129. const loadApplyDetails = (): void => {
  130. if (idList.value.length === 0) return
  131. loading.value = true
  132. const promises = idList.value.map((id) => {
  133. return getPurchaseApplyById(id)
  134. })
  135. Promise.all(promises).then((responses: any[]) => {
  136. const resultList: UTSJSONObject[] = []
  137. responses.forEach((response: any) => {
  138. const res = response as UTSJSONObject
  139. const data = res["data"] as UTSJSONObject
  140. const apply = new UTSJSONObject()
  141. apply['applyId'] = data['applyId'] != null ? parseInt(data['applyId'].toString()) : 0
  142. apply['applyCode'] = data['applyCode'] != null ? data['applyCode'].toString() : ''
  143. apply['nickName'] = data['nickName'] != null ? data['nickName'].toString() : ''
  144. apply['createTime'] = data['createTime'] != null ? data['createTime'].toString() : ''
  145. apply['remark'] = data['remark'] != null ? data['remark'].toString() : ''
  146. apply['applyUser'] = data['applyUser'] != null ? data['applyUser'].toString() : ''
  147. apply['applyUserId'] = data['applyUserId'] != null ? parseInt(data['applyUserId'].toString()) : 0
  148. apply['applyDeptName'] = data['applyDeptName'] != null ? data['applyDeptName'].toString() : ''
  149. const lines = data['wmPurchaseApplyLineList']
  150. const lineList: UTSJSONObject[] = []
  151. if (lines != null) {
  152. (lines as UTSJSONObject[]).forEach((line: UTSJSONObject) => {
  153. const lineItem = new UTSJSONObject()
  154. lineItem['lineId'] = line['id'] != null ? line['id'].toString() : ''
  155. lineItem['itemId'] = line['itemId'] != null ? line['itemId'].toString() : ''
  156. lineItem['itemCode'] = line['itemCode'] != null ? line['itemCode'].toString() : ''
  157. lineItem['itemName'] = line['itemName'] != null ? line['itemName'].toString() : ''
  158. lineItem['specification'] = line['specification'] != null ? line['specification'].toString() : ''
  159. lineItem['unitOfMeasure'] = line['unitOfMeasure'] != null ? line['unitOfMeasure'].toString() : ''
  160. lineItem['measureName'] = line['measureName'] != null ? line['measureName'].toString() : ''
  161. lineItem['quantityApply'] = line['quantityApply'] != null ? line['quantityApply'] : 0
  162. lineItem['purchaseQty'] = line['quantityApply'] != null ? line['quantityApply'].toString() : '0'
  163. lineList.push(lineItem)
  164. })
  165. }
  166. apply['lineList'] = lineList
  167. resultList.push(apply)
  168. })
  169. applyList.value = resultList
  170. loading.value = false
  171. generatePurchaseName()
  172. }).catch((e) => {
  173. console.error('加载申请单详情失败:', e)
  174. loading.value = false
  175. uni.showToast({ title: '加载失败', icon: 'none' })
  176. })
  177. }
  178. const generatePurchaseName = (): void => {
  179. if (applyList.value.length === 0) {
  180. purchaseName.value = ''
  181. return
  182. }
  183. const now = new Date()
  184. const year = now.getFullYear()
  185. const month = String(now.getMonth() + 1).padStart(2, '0')
  186. const day = String(now.getDate()).padStart(2, '0')
  187. const hours = String(now.getHours()).padStart(2, '0')
  188. const minutes = String(now.getMinutes()).padStart(2, '0')
  189. const seconds = String(now.getSeconds()).padStart(2, '0')
  190. const dateStr = `${year}${month}${day}${hours}${minutes}${seconds}`
  191. const applicants = new Set<string>()
  192. applyList.value.forEach((apply) => {
  193. const name = apply['nickName']
  194. if (name != null && name.toString().length > 0) {
  195. applicants.add(name.toString())
  196. }
  197. })
  198. const applicantStr = Array.from(applicants).join(',')
  199. purchaseName.value = `${dateStr}-${applicantStr}`
  200. }
  201. const handleSubmit = (): void => {
  202. const purchaseData = new UTSJSONObject()
  203. const mergedItems: UTSJSONObject[] = []
  204. let totalQuantity = 0
  205. applyList.value.forEach((apply) => {
  206. const applyId = apply['applyId']
  207. const applyRemark = apply['remark'] != null ? apply['remark'].toString() : ''
  208. const applyUser = apply['applyUser'] != null ? apply['applyUser'].toString() : ''
  209. const applyUserId = apply['applyUserId']
  210. const applyDeptName = apply['applyDeptName'] != null ? apply['applyDeptName'].toString() : ''
  211. const lineList = apply['lineList'] as UTSJSONObject[]
  212. lineList.forEach((item) => {
  213. const qty = parseInt(item['purchaseQty'])
  214. if (qty > 0 && qty <= parseInt(getQuantity(item))) {
  215. const lineItem = new UTSJSONObject()
  216. lineItem['applyId'] = applyId
  217. lineItem['applyLineId'] = parseInt(item['lineId'])
  218. lineItem['itemId'] = parseInt(item['itemId'])
  219. lineItem['itemCode'] = item['itemCode'] != null ? item['itemCode'].toString() : ''
  220. lineItem['itemName'] = item['itemName'] != null ? item['itemName'].toString() : ''
  221. lineItem['specification'] = item['specification'] != null ? item['specification'].toString() : ''
  222. lineItem['unitOfMeasure'] = item['unitOfMeasure'] != null ? item['unitOfMeasure'].toString() : ''
  223. lineItem['measureName'] = item['measureName'] != null ? item['measureName'].toString() : ''
  224. lineItem['quantityApply'] = parseInt(getQuantity(item))
  225. lineItem['quantityPurchase'] = qty
  226. lineItem['applyRemark'] = applyRemark
  227. lineItem['applyUser'] = applyUser
  228. lineItem['applyUserId'] = applyUserId
  229. lineItem['applyDeptName'] = applyDeptName
  230. mergedItems.push(lineItem)
  231. totalQuantity += qty
  232. }
  233. })
  234. })
  235. if (mergedItems.length === 0) {
  236. uni.showToast({ title: '请填写有效的采购数量', icon: 'none' })
  237. return
  238. }
  239. const mergePurchase = new UTSJSONObject()
  240. mergePurchase['mergeName'] = purchaseName.value
  241. mergePurchase['remark'] = purchaseRemark.value
  242. mergePurchase['totalQuantity'] = totalQuantity
  243. purchaseData['mergePurchase'] = mergePurchase
  244. purchaseData['applyUserId'] = currentUserId.value.length > 0 ? parseInt(currentUserId.value) : 0
  245. purchaseData['applyIds'] = idList.value.map(id => parseInt(id))
  246. purchaseData['mergedItems'] = mergedItems
  247. purchaseData['platform'] = 'mobile'
  248. uni.showModal({
  249. title: '提示',
  250. content: '确定生成采购单吗?',
  251. success: (res) => {
  252. if (res.confirm) {
  253. uni.showLoading({ title: '生成中...' })
  254. createMergePurchase(purchaseData).then((response: any) => {
  255. uni.hideLoading()
  256. const result = response as UTSJSONObject
  257. if (result['code'] === 200) {
  258. uni.showToast({ title: '生成成功', icon: 'success' })
  259. setTimeout(() => {
  260. uni.redirectTo({
  261. url: '/pages/apply/index?refresh=1'
  262. })
  263. }, 1500)
  264. } else {
  265. const msg = result['msg'] != null ? result['msg'].toString() : '生成失败'
  266. uni.showToast({ title: msg, icon: 'none' })
  267. }
  268. }).catch((e) => {
  269. uni.hideLoading()
  270. const error = e as UTSError
  271. const errMsg = error?.message
  272. uni.showToast({ title: errMsg != null ? errMsg.toString() : '生成失败', icon: 'none' })
  273. })
  274. }
  275. }
  276. })
  277. }
  278. onLoad((options: any) => {
  279. const userInfo = getUserInfo()
  280. if (userInfo != null) {
  281. const userId = userInfo['userId']
  282. currentUserId.value = userId != null ? userId.toString() : ''
  283. }
  284. const params = options as UTSJSONObject
  285. if (params != null && params['applyIds'] != null) {
  286. applyIds.value = params['applyIds'].toString()
  287. idList.value = applyIds.value.split(',').filter((id: string) => id.trim().length > 0)
  288. loadApplyDetails()
  289. }
  290. })
  291. </script>
  292. <style lang="scss">
  293. .page-container {
  294. flex: 1;
  295. background-color: #e8f0f9;
  296. }
  297. .page-content {
  298. flex: 1;
  299. padding: 20rpx;
  300. padding-bottom: 140rpx;
  301. }
  302. .purchase-info-section {
  303. margin-bottom: 20rpx;
  304. background: #ffffff;
  305. border-radius: 16rpx;
  306. padding: 20rpx;
  307. }
  308. .purchase-info-row {
  309. flex-direction: row;
  310. justify-content: space-between;
  311. align-items: center;
  312. margin-bottom: 20rpx;
  313. }
  314. .purchase-info-label {
  315. font-size: 28rpx;
  316. color: #666666;
  317. }
  318. .purchase-info-value-wrap {
  319. flex: 1;
  320. margin-left: 20rpx;
  321. background-color: #f8f9fa;
  322. border-radius: 8rpx;
  323. padding: 12rpx 16rpx;
  324. }
  325. .purchase-info-value {
  326. font-size: 28rpx;
  327. color: #333333;
  328. font-weight: bold;
  329. }
  330. .purchase-info-input {
  331. font-size: 28rpx;
  332. color: #333333;
  333. width: 100%;
  334. background-color: transparent;
  335. }
  336. .purchase-remark-section {
  337. margin-top: 16rpx;
  338. }
  339. .purchase-remark-label {
  340. font-size: 28rpx;
  341. color: #666666;
  342. margin-bottom: 12rpx;
  343. display: block;
  344. }
  345. .purchase-remark-input {
  346. width: 100%;
  347. min-height: 160rpx;
  348. background-color: #f8f9fa;
  349. border-radius: 8rpx;
  350. padding: 16rpx;
  351. font-size: 28rpx;
  352. color: #333333;
  353. box-sizing: border-box;
  354. }
  355. .section {
  356. margin-bottom: 20rpx;
  357. background: #ffffff;
  358. border-radius: 16rpx;
  359. padding: 20rpx;
  360. }
  361. .section-header {
  362. flex-direction: row;
  363. align-items: center;
  364. margin-bottom: 20rpx;
  365. }
  366. .section-indicator {
  367. width: 6rpx;
  368. height: 32rpx;
  369. background-color: #007aff;
  370. border-radius: 3rpx;
  371. margin-right: 12rpx;
  372. }
  373. .section-title {
  374. font-size: 32rpx;
  375. color: #333333;
  376. font-weight: bold;
  377. }
  378. .info-card {
  379. background-color: #f8f9fa;
  380. border-radius: 8rpx;
  381. padding: 20rpx;
  382. margin-bottom: 20rpx;
  383. }
  384. .info-row {
  385. flex-direction: row;
  386. justify-content: space-between;
  387. margin-bottom: 16rpx;
  388. &:last-child {
  389. margin-bottom: 0;
  390. }
  391. }
  392. .info-label {
  393. font-size: 28rpx;
  394. color: #666666;
  395. }
  396. .info-value {
  397. font-size: 28rpx;
  398. color: #333333;
  399. }
  400. .material-section {
  401. background-color: #f8f9fa;
  402. border-radius: 8rpx;
  403. padding: 20rpx;
  404. }
  405. .material-section-header {
  406. margin-bottom: 16rpx;
  407. }
  408. .material-section-title {
  409. font-size: 28rpx;
  410. color: #333333;
  411. font-weight: bold;
  412. }
  413. .material-list {
  414. background-color: #ffffff;
  415. border-radius: 8rpx;
  416. padding: 16rpx;
  417. }
  418. .material-item {
  419. padding: 16rpx 0;
  420. border-bottom: 1rpx solid #f0f0f0;
  421. &:last-child {
  422. border-bottom: none;
  423. }
  424. }
  425. .material-info {
  426. margin-bottom: 12rpx;
  427. }
  428. .material-name {
  429. font-size: 28rpx;
  430. color: #333333;
  431. font-weight: bold;
  432. display: block;
  433. }
  434. .material-spec {
  435. font-size: 24rpx;
  436. color: #999999;
  437. margin-top: 4rpx;
  438. display: block;
  439. }
  440. .material-detail {
  441. flex-direction: row;
  442. flex-wrap: wrap;
  443. }
  444. .detail-row {
  445. flex-direction: row;
  446. width: 50%;
  447. align-items: center;
  448. margin-bottom: 8rpx;
  449. }
  450. .detail-label {
  451. font-size: 26rpx;
  452. color: #666666;
  453. margin-right: 8rpx;
  454. }
  455. .detail-value {
  456. font-size: 26rpx;
  457. color: #ff0000;
  458. }
  459. .purchase-row {
  460. justify-content: flex-end;
  461. }
  462. .purchase-input-wrap {
  463. flex-direction: row;
  464. align-items: center;
  465. background-color: #f5f5f5;
  466. border-radius: 8rpx;
  467. padding: 8rpx 12rpx;
  468. }
  469. .purchase-input {
  470. width: 120rpx;
  471. height: 48rpx;
  472. font-size: 26rpx;
  473. color: #ff0000;
  474. text-align: right;
  475. background: transparent;
  476. }
  477. .purchase-unit {
  478. font-size: 24rpx;
  479. color: #666666;
  480. margin-left: 8rpx;
  481. }
  482. .empty-tip {
  483. align-items: center;
  484. padding: 100rpx 20rpx;
  485. }
  486. .empty-tip-text {
  487. color: #999999;
  488. font-size: 28rpx;
  489. }
  490. .bottom-space {
  491. height: 40rpx;
  492. }
  493. .bottom-buttons {
  494. position: fixed;
  495. bottom: 0;
  496. left: 0;
  497. right: 0;
  498. padding: 20rpx 30rpx;
  499. background-color: #ffffff;
  500. border-top: 1rpx solid #e5e5e5;
  501. }
  502. .submit-btn {
  503. width: 100%;
  504. height: 88rpx;
  505. background-color: #007aff;
  506. color: #ffffff;
  507. font-size: 32rpx;
  508. font-weight: 600;
  509. border-radius: 22rpx;
  510. border: none;
  511. }
  512. </style>