GoodsSelector.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <view>
  3. <u-form-item label="商品名称" :prop="'detailInfos[' + index_experience + '].tradeName'" :required="true">
  4. <u--input :value="currentValue" placeholder="请选择商品名称" readonly :disabled="disabled"
  5. @focus="openPicker"></u--input>
  6. </u-form-item>
  7. <view class="selector-mask" :class="{ show: visible }" @tap="closePicker"></view>
  8. <view class="selector-panel" :class="{ show: visible }">
  9. <view class="selector-header">
  10. <view class="selector-action" @tap="closePicker">取消</view>
  11. <view class="selector-title">选择商品</view>
  12. <view class="selector-action selector-action-placeholder">取消</view>
  13. </view>
  14. <view class="selector-search">
  15. <u-search :show-action="false" v-model="keyword" placeholder="搜索商品名称"></u-search>
  16. </view>
  17. <scroll-view scroll-y class="selector-list">
  18. <view v-if="loading" class="selector-state">加载中...</view>
  19. <view v-else-if="filteredList.length === 0" class="selector-state">暂无可选商品</view>
  20. <view v-else class="selector-list-inner">
  21. <view class="goods-card" v-for="item in filteredList" :key="item.id || item.tradeName"
  22. @tap="selectItem(item)">
  23. <view class="goods-name">{{ item.tradeName }}</view>
  24. <view class="goods-meta" v-if="item.company || item.spec">
  25. <text v-if="item.company">单位:{{ item.company }}</text>
  26. <text v-if="item.spec">规格:{{ item.spec }}</text>
  27. </view>
  28. </view>
  29. </view>
  30. </scroll-view>
  31. </view>
  32. </view>
  33. </template>
  34. <script>
  35. import MaterialManagementService from '@/api/psi/MaterialManagementService'
  36. import WareHouseService from '@/api/psi/WareHouseService'
  37. export default {
  38. name: 'GoodsSelector',
  39. props: {
  40. index_experience: Number,
  41. inputForm: Object,
  42. disabled: Boolean
  43. },
  44. data() {
  45. return {
  46. visible: false,
  47. loading: false,
  48. keyword: '',
  49. goodsList: [],
  50. materialManagementService: null,
  51. wareHouseService: null
  52. }
  53. },
  54. computed: {
  55. currentRow() {
  56. return (this.inputForm.detailInfos || [])[this.index_experience] || {}
  57. },
  58. currentValue() {
  59. return this.currentRow.tradeName || ''
  60. },
  61. filteredList() {
  62. const keyword = (this.keyword || '').trim().toLowerCase()
  63. if (!keyword) {
  64. return this.goodsList
  65. }
  66. return this.goodsList.filter(item => (item.tradeName || '').toLowerCase().includes(keyword))
  67. }
  68. },
  69. created() {
  70. this.materialManagementService = new MaterialManagementService()
  71. this.wareHouseService = new WareHouseService()
  72. },
  73. methods: {
  74. isEmpty(value) {
  75. let result = false
  76. if (value == null || value == undefined) {
  77. result = true
  78. }
  79. if (typeof value === 'string' && (value.replace(/\s+/g, '') === '' || value === '')) {
  80. result = true
  81. }
  82. if (typeof value === 'object' && value instanceof Array && value.length === 0) {
  83. result = true
  84. }
  85. return result
  86. },
  87. async openPicker() {
  88. if (this.disabled) {
  89. return
  90. }
  91. if (this.isEmpty(this.currentRow.procurementTypeId)) {
  92. uni.showToast({
  93. title: '请先选择采购类型',
  94. icon: 'none'
  95. })
  96. return
  97. }
  98. this.visible = true
  99. this.keyword = ''
  100. await this.loadGoodsList()
  101. },
  102. closePicker() {
  103. this.visible = false
  104. },
  105. async loadGoodsList() {
  106. this.loading = true
  107. try {
  108. const data = await this.materialManagementService.findTradeByTypeId(this.currentRow.procurementTypeId)
  109. this.goodsList = (data || []).map(item => ({
  110. ...item,
  111. tradeName: item.tradeName || ''
  112. }))
  113. } catch (e) {
  114. this.goodsList = []
  115. uni.showToast({
  116. title: '商品数据加载失败',
  117. icon: 'none'
  118. })
  119. } finally {
  120. this.loading = false
  121. }
  122. },
  123. async selectItem(item) {
  124. let selected = { ...item }
  125. try {
  126. const data = await this.wareHouseService.wareHouseSummaryList({
  127. current: 1,
  128. size: 20,
  129. tradeName: item.tradeName,
  130. wareHouseType: this.currentRow.procurementTypeId,
  131. orders: []
  132. })
  133. const records = (data && data.records) || []
  134. if (records.length > 0) {
  135. selected = { ...records[0], ...selected }
  136. }
  137. } catch (e) {
  138. }
  139. this.$emit('selected', {
  140. index: this.index_experience,
  141. item: selected
  142. })
  143. this.closePicker()
  144. }
  145. }
  146. }
  147. </script>
  148. <style scoped>
  149. .selector-mask {
  150. position: fixed;
  151. inset: 0;
  152. background: rgba(0, 0, 0, 0.35);
  153. opacity: 0;
  154. visibility: hidden;
  155. transition: all 0.25s ease;
  156. z-index: 1000;
  157. }
  158. .selector-mask.show {
  159. opacity: 1;
  160. visibility: visible;
  161. }
  162. .selector-panel {
  163. position: fixed;
  164. left: 0;
  165. right: 0;
  166. bottom: 0;
  167. height: 68vh;
  168. background: #f7f9fc;
  169. border-radius: 28rpx 28rpx 0 0;
  170. transform: translateY(100%);
  171. transition: transform 0.25s ease;
  172. z-index: 1001;
  173. display: flex;
  174. flex-direction: column;
  175. }
  176. .selector-panel.show {
  177. transform: translateY(0);
  178. }
  179. .selector-header {
  180. display: flex;
  181. align-items: center;
  182. justify-content: space-between;
  183. padding: 28rpx 32rpx 16rpx;
  184. background: #fff;
  185. border-bottom: 1rpx solid #eef2f7;
  186. }
  187. .selector-title {
  188. font-size: 32rpx;
  189. font-weight: 600;
  190. color: #1f2937;
  191. }
  192. .selector-action {
  193. min-width: 80rpx;
  194. font-size: 28rpx;
  195. color: #2979ff;
  196. }
  197. .selector-action-placeholder {
  198. opacity: 0;
  199. }
  200. .selector-search {
  201. padding: 20rpx 24rpx 8rpx;
  202. background: #fff;
  203. }
  204. .selector-list {
  205. flex: 1;
  206. padding: 16rpx 24rpx 32rpx;
  207. }
  208. .selector-list-inner {
  209. display: flex;
  210. flex-direction: column;
  211. gap: 16rpx;
  212. }
  213. .selector-state {
  214. padding-top: 120rpx;
  215. text-align: center;
  216. font-size: 28rpx;
  217. color: #94a3b8;
  218. }
  219. .goods-card {
  220. background: #fff;
  221. border-radius: 20rpx;
  222. padding: 24rpx;
  223. box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06);
  224. }
  225. .goods-name {
  226. font-size: 30rpx;
  227. font-weight: 600;
  228. line-height: 1.45;
  229. color: #0f172a;
  230. }
  231. .goods-meta {
  232. display: flex;
  233. gap: 20rpx;
  234. margin-top: 10rpx;
  235. font-size: 24rpx;
  236. color: #64748b;
  237. flex-wrap: wrap;
  238. }
  239. </style>