GoodsSelector.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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.brand || item.specification || item.company">
  25. <text v-if="item.brand">品牌:{{ item.brand }}</text>
  26. <text v-if="item.specification">规格:{{ item.specification }}</text>
  27. <text v-if="item.company">单位:{{ item.company }}</text>
  28. </view>
  29. </view>
  30. </view>
  31. </scroll-view>
  32. </view>
  33. </view>
  34. </template>
  35. <script>
  36. import MaterialTypeService from '@/api/psi/MaterialTypeService'
  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. materialTypeService: null
  51. }
  52. },
  53. computed: {
  54. currentRow() {
  55. return (this.inputForm.detailInfos || [])[this.index_experience] || {}
  56. },
  57. currentValue() {
  58. return this.currentRow.tradeName || ''
  59. },
  60. filteredList() {
  61. const keyword = (this.keyword || '').trim().toLowerCase()
  62. if (!keyword) {
  63. return this.goodsList
  64. }
  65. return this.goodsList.filter(item => (item.tradeName || '').toLowerCase().includes(keyword))
  66. }
  67. },
  68. created() {
  69. this.materialTypeService = new MaterialTypeService()
  70. },
  71. methods: {
  72. isEmpty(value) {
  73. let result = false
  74. if (value == null || value == undefined) {
  75. result = true
  76. }
  77. if (typeof value === 'string' && (value.replace(/\s+/g, '') === '' || value === '')) {
  78. result = true
  79. }
  80. if (typeof value === 'object' && value instanceof Array && value.length === 0) {
  81. result = true
  82. }
  83. return result
  84. },
  85. async openPicker() {
  86. if (this.disabled) {
  87. return
  88. }
  89. if (this.isEmpty(this.currentRow.procurementTypeId)) {
  90. uni.showToast({
  91. title: '请先选择采购类型',
  92. icon: 'none'
  93. })
  94. return
  95. }
  96. this.visible = true
  97. this.keyword = ''
  98. await this.loadGoodsList()
  99. },
  100. closePicker() {
  101. this.visible = false
  102. },
  103. async loadGoodsList() {
  104. this.loading = true
  105. try {
  106. const data = await this.materialTypeService.cgList()
  107. this.goodsList = (data || []).filter(item => {
  108. const parentId = item.parentId || item.parent_id || ''
  109. return String(item.infoType || item.info_type || '') === '1' &&
  110. (!this.currentRow.procurementTypeId || parentId === this.currentRow.procurementTypeId)
  111. }).map(item => ({
  112. ...item,
  113. tradeName: item.tradeName || item.name || ''
  114. }))
  115. } catch (e) {
  116. this.goodsList = []
  117. uni.showToast({
  118. title: '商品数据加载失败',
  119. icon: 'none'
  120. })
  121. } finally {
  122. this.loading = false
  123. }
  124. },
  125. async selectItem(item) {
  126. this.$emit('selected', {
  127. index: this.index_experience,
  128. item
  129. })
  130. this.closePicker()
  131. }
  132. }
  133. }
  134. </script>
  135. <style scoped>
  136. .selector-mask {
  137. position: fixed;
  138. inset: 0;
  139. background: rgba(0, 0, 0, 0.35);
  140. opacity: 0;
  141. visibility: hidden;
  142. transition: all 0.25s ease;
  143. z-index: 1000;
  144. }
  145. .selector-mask.show {
  146. opacity: 1;
  147. visibility: visible;
  148. }
  149. .selector-panel {
  150. position: fixed;
  151. left: 0;
  152. right: 0;
  153. bottom: 0;
  154. height: 68vh;
  155. background: #f7f9fc;
  156. border-radius: 28rpx 28rpx 0 0;
  157. transform: translateY(100%);
  158. transition: transform 0.25s ease;
  159. z-index: 1001;
  160. display: flex;
  161. flex-direction: column;
  162. }
  163. .selector-panel.show {
  164. transform: translateY(0);
  165. }
  166. .selector-header {
  167. display: flex;
  168. align-items: center;
  169. justify-content: space-between;
  170. padding: 28rpx 32rpx 16rpx;
  171. background: #fff;
  172. border-bottom: 1rpx solid #eef2f7;
  173. }
  174. .selector-title {
  175. font-size: 32rpx;
  176. font-weight: 600;
  177. color: #1f2937;
  178. }
  179. .selector-action {
  180. min-width: 80rpx;
  181. font-size: 28rpx;
  182. color: #2979ff;
  183. }
  184. .selector-action-placeholder {
  185. opacity: 0;
  186. }
  187. .selector-search {
  188. padding: 20rpx 24rpx 8rpx;
  189. background: #fff;
  190. }
  191. .selector-list {
  192. flex: 1;
  193. padding: 16rpx 24rpx 32rpx;
  194. }
  195. .selector-list-inner {
  196. display: flex;
  197. flex-direction: column;
  198. gap: 16rpx;
  199. }
  200. .selector-state {
  201. padding-top: 120rpx;
  202. text-align: center;
  203. font-size: 28rpx;
  204. color: #94a3b8;
  205. }
  206. .goods-card {
  207. background: #fff;
  208. border-radius: 20rpx;
  209. padding: 24rpx;
  210. box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06);
  211. }
  212. .goods-name {
  213. font-size: 30rpx;
  214. font-weight: 600;
  215. line-height: 1.45;
  216. color: #0f172a;
  217. }
  218. .goods-meta {
  219. display: flex;
  220. gap: 20rpx;
  221. margin-top: 10rpx;
  222. font-size: 24rpx;
  223. color: #64748b;
  224. flex-wrap: wrap;
  225. }
  226. </style>