detail.uvue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. <template>
  2. <uni-navbar-lite :showRight=false :title="title"></uni-navbar-lite>
  3. <view class="page-container">
  4. <scroll-view class="page-content" :scroll-y="true">
  5. <!-- 订单基本信息 -->
  6. <view class="section">
  7. <view class="section-header">
  8. <view class="section-indicator"></view>
  9. <text class="section-title">订单信息</text>
  10. </view>
  11. <view class="info-card">
  12. <view class="info-row">
  13. <text class="info-label">采购单编号</text>
  14. <text class="info-value primary">{{ purchaseCode }}</text>
  15. </view>
  16. <view class="info-row">
  17. <text class="info-label">供应商</text>
  18. <text class="info-value">{{ vendorName || '-' }}</text>
  19. </view>
  20. <view class="info-row">
  21. <text class="info-label">合同号</text>
  22. <text class="info-value">{{ contractNumber || '-' }}</text>
  23. </view>
  24. <view class="info-row">
  25. <text class="info-label">总金额</text>
  26. <text class="info-value warning">{{ totalAmount }}</text>
  27. </view>
  28. <view class="info-row">
  29. <text class="info-label">状态</text>
  30. <view class="status-badge" :class="'status-' + status">
  31. {{ statusText }}
  32. </view>
  33. </view>
  34. </view>
  35. </view>
  36. <!-- 订单行列表 -->
  37. <view class="section">
  38. <view class="section-header">
  39. <view class="section-indicator"></view>
  40. <text class="section-title">采购明细</text>
  41. <text class="section-count">共 {{ lineList.length }} 项</text>
  42. </view>
  43. <view class="line-list">
  44. <view v-for="(item, index) in lineList" :key="getLineId(item, index)" class="line-card">
  45. <view class="line-top">
  46. <text class="line-index">{{ index + 1 }}</text>
  47. <view class="line-right">
  48. <text class="item-name">{{ getItemName(item) }}</text>
  49. <text class="item-code-tag">{{ getItemCode(item) }}</text>
  50. </view>
  51. </view>
  52. <view class="line-body">
  53. <view class="line-row">
  54. <view class="line-cell">
  55. <text class="line-label">规格</text>
  56. <text class="line-value">{{ item.specification || '-' }}</text>
  57. </view>
  58. <view class="line-cell">
  59. <text class="line-label">单位</text>
  60. <text class="line-value">{{ item.measureName || '-' }}</text>
  61. </view>
  62. </view>
  63. <view class="line-row">
  64. <view class="line-cell">
  65. <text class="line-label">数量</text>
  66. <text class="line-value qty">{{ item.quantity || '0' }}</text>
  67. </view>
  68. <view class="line-cell">
  69. <text class="line-label">单价</text>
  70. <text class="line-value">{{ getPrice(item) }}</text>
  71. </view>
  72. </view>
  73. <view class="line-row">
  74. <view class="line-cell">
  75. <text class="line-label">金额</text>
  76. <text class="line-value amount">{{ getTotalPrice(item) }}</text>
  77. </view>
  78. </view>
  79. </view>
  80. </view>
  81. </view>
  82. </view>
  83. <!-- 备注 -->
  84. <view class="section" v-if="remark && remark.length > 0">
  85. <view class="section-header">
  86. <view class="section-indicator"></view>
  87. <text class="section-title">备注</text>
  88. </view>
  89. <view class="info-card">
  90. <text class="remark-text">{{ remark }}</text>
  91. </view>
  92. </view>
  93. </scroll-view>
  94. <view class="bottom-bar" v-if="status === 'PREPARE' && checkPermission('wm:purchase:confirm')">
  95. <button class="confirm-btn" @click="handleConfirm">确认采购单</button>
  96. </view>
  97. </view>
  98. </template>
  99. <script setup lang="uts">
  100. import { ref, computed, onMounted } from 'vue'
  101. import { getPurchase, confirmPurchase } from '../../api/purchase/index'
  102. import { checkPermission } from '../../utils/permission'
  103. const purchaseId = ref<string>('')
  104. const purchaseCode = ref<string>('')
  105. const vendorName = ref<string>('')
  106. const contractNumber = ref<string>('')
  107. const totalAmount = ref<string>('')
  108. const status = ref<string>('')
  109. const remark = ref<string>('')
  110. const lineList = ref<any[]>([])
  111. const title = computed(() => '采购订单详情')
  112. const statusText = computed(() => {
  113. switch (status.value) {
  114. case 'PREPARE': return '草稿'
  115. case 'CONFIRMED': return '已确认'
  116. case 'FINISHED': return '已完成'
  117. case 'CANCEL': return '已取消'
  118. default: return status.value
  119. }
  120. })
  121. const formatAmount = (val: any): string => {
  122. if (val == null) return '0.00'
  123. const num = Number(val)
  124. if (isNaN(num)) return '0.00'
  125. return num.toFixed(2)
  126. }
  127. const getPrice = (item: any | null): string => {
  128. if (item == null) return '0.00'
  129. const jsonItem = item as UTSJSONObject
  130. return formatAmount(jsonItem['price'])
  131. }
  132. const getTotalPrice = (item: any | null): string => {
  133. if (item == null) return '0.00'
  134. const jsonItem = item as UTSJSONObject
  135. return formatAmount(jsonItem['totalPrice'])
  136. }
  137. const getItemCode = (item: any | null): string => {
  138. if (item == null) return ''
  139. const jsonItem = item as UTSJSONObject
  140. const val = jsonItem['itemCode']
  141. return val != null ? val.toString() : ''
  142. }
  143. const getItemName = (item: any | null): string => {
  144. if (item == null) return ''
  145. const jsonItem = item as UTSJSONObject
  146. const val = jsonItem['itemName']
  147. let name = val != null ? val.toString() : ''
  148. const idx = name.lastIndexOf('(')
  149. if (idx > 0) {
  150. name = name.substring(0, idx)
  151. }
  152. return name
  153. }
  154. const getLineId = (item: any, index: number): string => {
  155. return `line-${index}`
  156. }
  157. const loadDetail = (): void => {
  158. if (purchaseId.value.length === 0) return
  159. getPurchase(purchaseId.value).then((response: any) => {
  160. const res = response as UTSJSONObject
  161. const data = res['data'] as UTSJSONObject
  162. purchaseCode.value = data['purchaseCode'] != null ? data['purchaseCode'].toString() : ''
  163. vendorName.value = data['vendorName'] != null ? data['vendorName'].toString() : ''
  164. contractNumber.value = data['contractNumber'] != null ? data['contractNumber'].toString() : ''
  165. totalAmount.value = formatAmount(data['totalAmount'])
  166. status.value = data['status'] != null ? data['status'].toString() : ''
  167. remark.value = data['remark'] != null ? data['remark'].toString() : ''
  168. const lines = data['wmItemPurchaseLineList'] as any[]
  169. if (lines && lines.length > 0) {
  170. lineList.value = lines
  171. }
  172. }).catch((e) => {
  173. const error = e as UTSError
  174. uni.showToast({ title: error?.message.toString() || '加载失败', icon: 'none' })
  175. })
  176. }
  177. const handleConfirm = (): void => {
  178. uni.showLoading({ title: '检查物料...' })
  179. getPurchase(purchaseId.value).then((response: any) => {
  180. uni.hideLoading()
  181. const res = response as UTSJSONObject
  182. const data = res['data'] as UTSJSONObject
  183. const lines = data['wmItemPurchaseLineList'] as any[]
  184. const pendingLines: string[] = []
  185. if (lines != null && lines.length > 0) {
  186. for (let i = 0; i < lines.length; i++) {
  187. const line = lines[i] as UTSJSONObject
  188. if (line['auditStatus'] != null && line['auditStatus'].toString() === '0') {
  189. const itemName = line['itemName']?.toString() ?? ''
  190. const itemCode = line['itemCode']?.toString() ?? ''
  191. pendingLines.push(`${itemName}(${itemCode})`)
  192. }
  193. }
  194. }
  195. if (pendingLines.length > 0) {
  196. const names = pendingLines.join('、')
  197. uni.showModal({
  198. title: '提示',
  199. content: `以下物料未审核,无法确认采购单:\n${names}`,
  200. showCancel: true,
  201. confirmText: '知道了',
  202. cancelText: '取消'
  203. })
  204. return
  205. }
  206. uni.showModal({
  207. title: '提示',
  208. content: `确认完成【${purchaseCode.value}】采购单吗?确认后不能修改。`,
  209. success: (res) => {
  210. if (res.confirm) {
  211. uni.showLoading({ title: '确认中...' })
  212. confirmPurchase(purchaseId.value).then(() => {
  213. uni.hideLoading()
  214. uni.showToast({ title: '确认成功', icon: 'success' })
  215. setTimeout(() => { uni.navigateBack() }, 500)
  216. }).catch((e) => {
  217. uni.hideLoading()
  218. const error = e as UTSError
  219. uni.showToast({ title: error?.message ?? '确认失败', icon: 'none' })
  220. })
  221. }
  222. }
  223. })
  224. }).catch((e) => {
  225. uni.hideLoading()
  226. const error = e as UTSError
  227. uni.showToast({ title: error?.message?.toString() ?? '获取采购单详情失败', icon: 'none' })
  228. })
  229. }
  230. onMounted(() => {
  231. const pages = getCurrentPages()
  232. const currentPage = pages[pages.length - 1]
  233. const options = currentPage.options
  234. if (options != null && options.id != null) {
  235. purchaseId.value = options.id.toString()
  236. loadDetail()
  237. }
  238. })
  239. </script>
  240. <style lang="scss">
  241. .page-container {
  242. flex: 1;
  243. background-color: #e8f0f9;
  244. }
  245. .page-content {
  246. flex: 1;
  247. padding: 20rpx;
  248. padding-bottom: 20rpx;
  249. }
  250. .section {
  251. margin-bottom: 20rpx;
  252. background: #ffffff;
  253. border-radius: 16rpx;
  254. padding: 20rpx;
  255. }
  256. .section-header {
  257. display: flex;
  258. flex-direction: row;
  259. align-items: center;
  260. margin-bottom: 20rpx;
  261. }
  262. .section-indicator {
  263. width: 6rpx;
  264. height: 32rpx;
  265. background-color: #007aff;
  266. border-radius: 3rpx;
  267. margin-right: 12rpx;
  268. }
  269. .section-title {
  270. font-size: 32rpx;
  271. font-weight: bold;
  272. color: #333333;
  273. }
  274. .section-count {
  275. margin-left: auto;
  276. font-size: 24rpx;
  277. color: #999999;
  278. }
  279. .info-card {
  280. background-color: #f8f9fa;
  281. border-radius: 12rpx;
  282. padding: 20rpx;
  283. }
  284. .info-row {
  285. display: flex;
  286. flex-direction: row;
  287. align-items: center;
  288. padding: 16rpx 0;
  289. border-bottom: 1rpx solid #e9ecef;
  290. &:last-child {
  291. border-bottom: none;
  292. }
  293. }
  294. .info-label {
  295. font-size: 28rpx;
  296. color: #666666;
  297. width: 180rpx;
  298. flex-shrink: 0;
  299. }
  300. .info-value {
  301. font-size: 28rpx;
  302. color: #333333;
  303. flex: 1;
  304. &.warning {
  305. color: #faad14;
  306. }
  307. &.primary {
  308. color: #007aff;
  309. font-weight: bold;
  310. }
  311. }
  312. .status-badge {
  313. font-size: 24rpx;
  314. padding: 6rpx 16rpx;
  315. border-radius: 6rpx;
  316. &.status-PREPARE {
  317. background-color: #f0f0f0;
  318. color: #666666;
  319. }
  320. &.status-CONFIRMED {
  321. background-color: #e6f7ff;
  322. color: #1890ff;
  323. }
  324. &.status-FINISHED {
  325. background-color: #f6ffed;
  326. color: #52c41a;
  327. }
  328. &.status-CANCEL {
  329. background-color: #fff1f0;
  330. color: #ff4d4f;
  331. }
  332. }
  333. .line-list {
  334. display: flex;
  335. flex-direction: column;
  336. }
  337. .line-card {
  338. background-color: #ffffff;
  339. border-radius: 12rpx;
  340. padding: 16rpx;
  341. margin-bottom: 12rpx;
  342. }
  343. .line-top {
  344. flex-direction: row;
  345. align-items: center;
  346. margin-bottom: 10rpx;
  347. }
  348. .line-index {
  349. font-size: 40rpx;
  350. font-weight: 700;
  351. color: #007aff;
  352. margin-right: 20rpx;
  353. width: 60rpx;
  354. text-align: center;
  355. }
  356. .line-right {
  357. flex-direction: column;
  358. flex: 1;
  359. }
  360. .item-name {
  361. font-size: 30rpx;
  362. font-weight: 600;
  363. color: #1a1a1a;
  364. }
  365. .item-code-tag {
  366. font-size: 20rpx;
  367. color: #007aff;
  368. margin-top: 4rpx;
  369. }
  370. .line-body {
  371. display: flex;
  372. flex-direction: column;
  373. gap: 8rpx;
  374. }
  375. .line-row {
  376. display: flex;
  377. flex-direction: row;
  378. gap: 12rpx;
  379. }
  380. .line-cell {
  381. display: flex;
  382. flex-direction: row;
  383. align-items: center;
  384. flex: 1;
  385. padding: 8rpx 12rpx;
  386. background-color: #f8f9fa;
  387. border-radius: 8rpx;
  388. }
  389. .line-label {
  390. font-size: 22rpx;
  391. color: #999999;
  392. margin-right: 4rpx;
  393. }
  394. .line-value {
  395. font-size: 24rpx;
  396. color: #333333;
  397. &.qty {
  398. color: #007aff;
  399. }
  400. &.amount {
  401. color: #faad14;
  402. }
  403. }
  404. .remark-text {
  405. font-size: 28rpx;
  406. color: #333333;
  407. line-height: 1.6;
  408. }
  409. .bottom-bar {
  410. padding: 20rpx;
  411. background-color: #ffffff;
  412. box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.06);
  413. }
  414. .confirm-btn {
  415. width: 100%;
  416. padding: 24rpx 0;
  417. background-color: #007aff;
  418. color: #ffffff;
  419. font-size: 32rpx;
  420. border-radius: 12rpx;
  421. border: none;
  422. text-align: center;
  423. font-weight: bold;
  424. }
  425. </style>