PositionSelector.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template>
  2. <van-popup v-model:show="visible" position="bottom" style="height: 60%">
  3. <div style="display: flex; flex-direction: column; height: 100%;">
  4. <div
  5. style="display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; border-bottom: 1px solid #ebedf0;"
  6. >
  7. <div style="font-size: 16px; font-weight: 500;">{{ title }}</div>
  8. <van-icon name="cross" @click="close" />
  9. </div>
  10. <van-search v-model="searchKey" :placeholder="searchPlaceholder" @search="onSearch" />
  11. <div style="flex: 1; overflow-y: auto;">
  12. <van-radio-group v-model="selectedId">
  13. <van-cell-group>
  14. <van-cell v-for="item in columns" :key="item.value" clickable @click="selectItem(item)">
  15. <template #title>
  16. <van-radio :name="item.value">{{ item.text }}</van-radio>
  17. </template>
  18. </van-cell>
  19. </van-cell-group>
  20. </van-radio-group>
  21. <div v-if="loading" style="text-align: center; color: #969799; padding: 10px 0;">加载中...</div>
  22. <van-empty v-if="columns.length === 0 && !loading" description="暂无数据" />
  23. </div>
  24. <div
  25. style="padding: 10px 16px; border-top: 1px solid #ebedf0; display: flex; flex-direction: column; gap: 8px;"
  26. >
  27. <van-button type="default" block :disabled="noMore || loading" @click="loadMore">
  28. {{ noMore ? '没有更多数据' : '加载更多' }}
  29. </van-button>
  30. <van-button type="primary" block @click="confirm">确认</van-button>
  31. </div>
  32. </div>
  33. </van-popup>
  34. </template>
  35. <script setup>
  36. import { showFailToast } from 'vant';
  37. import { ajaxApiGet } from '../common/utils';
  38. import { processException } from '../common/Common.js';
  39. import { ref, watch, computed, onMounted, defineProps, defineEmits, defineExpose } from 'vue';
  40. const props = defineProps({
  41. show: {
  42. type: Boolean,
  43. default: false,
  44. },
  45. positionType: {
  46. type: String,
  47. default: 'transfer', // 'transfer' 或 'idle'
  48. validator: value => ['transfer', 'idle'].includes(value),
  49. },
  50. isUser: {
  51. type: String,
  52. default: 'stockIn',
  53. },
  54. defaultSelectedId: {
  55. type: [String, Number],
  56. default: '',
  57. },
  58. pageSize: {
  59. type: Number,
  60. default: 20,
  61. },
  62. isCanSelect: {
  63. type: Boolean,
  64. default: true,
  65. },
  66. });
  67. const emit = defineEmits([
  68. 'update:show',
  69. 'confirm',
  70. 'close',
  71. ]);
  72. // 内部状态
  73. const visible = ref(props.show);
  74. const searchKey = ref('');
  75. const columns = ref([]);
  76. const currentPage = ref(1);
  77. const loading = ref(false);
  78. const noMore = ref(false);
  79. const selectedId = ref(props.defaultSelectedId);
  80. const selectedItem = ref(null);
  81. // 计算属性
  82. const title = computed(() =>{
  83. const titleMap = {
  84. stockIn: {
  85. transfer: '选择中转区货位',
  86. idle: '选择入库货位',
  87. },
  88. stockOut: {
  89. transfer: '选择中转区货位',
  90. using: '选择出库货位',
  91. },
  92. };
  93. return titleMap[props.isUser]?.[props.positionType] || '选择货位';
  94. });
  95. const searchPlaceholder = computed(() => `请输入${title.value}关键词`);
  96. const apiUrl = computed(() => {
  97. const apiMap = {
  98. stockIn: {
  99. transfer: '/api/positionResource/getTransferPosition',
  100. idle: '/api/positionResource/getIdlePosition',
  101. },
  102. stockOut: {
  103. transfer: '/api/positionResource/getTransferPosition',
  104. using: '/api/positionResource/getOccupiedPosition',
  105. },
  106. };
  107. console.log(props.isUser, props.positionType);
  108. return apiMap[props.isUser]?.[props.positionType] || '';
  109. });
  110. // 监听props变化
  111. watch(() => props.show, newVal => {
  112. visible.value = newVal;
  113. if (newVal && columns.value.length === 0) {
  114. getPositionData();
  115. }
  116. });
  117. // 同步内部visible变化到外部
  118. watch(() => visible.value, newVal => {
  119. emit('update:show', newVal);
  120. });
  121. // 同步默认选中ID
  122. watch(() => props.defaultSelectedId, newVal => {
  123. selectedId.value = newVal;
  124. });
  125. // 搜索
  126. const onSearch = () => {
  127. getPositionData(true);
  128. };
  129. // 加载更多
  130. const loadMore = () => {
  131. if (loading.value || noMore.value) return;
  132. currentPage.value++;
  133. getPositionData(false);
  134. };
  135. // 获取货位数据
  136. const getPositionData = (isReset = true) => {
  137. if (isReset) {
  138. currentPage.value = 1;
  139. columns.value = [];
  140. noMore.value = false;
  141. }
  142. loading.value = true;
  143. const start = (currentPage.value - 1) * props.pageSize;
  144. const length = props.pageSize;
  145. const filter = searchKey.value;
  146. ajaxApiGet(`${apiUrl.value}?start=${start}&length=${length}&filter=${filter}`).then(
  147. success => {
  148. loading.value = false;
  149. const { errorCode, errorMessage, datas, total } = success;
  150. if (errorCode === 0) {
  151. if (datas && datas.length) {
  152. const newColumns = datas.map(item => ({
  153. text: item.name,
  154. value: item.id,
  155. id: item.id,
  156. name: item.name,
  157. no: item.no,
  158. }));
  159. columns.value = [...columns.value, ...newColumns];
  160. noMore.value = columns.value.length >= total;
  161. } else {
  162. noMore.value = true;
  163. }
  164. } else {
  165. noMore.value = true;
  166. showFailToast(errorMessage);
  167. }
  168. },
  169. error => {
  170. loading.value = false;
  171. processException(error);
  172. },
  173. );
  174. };
  175. // 选择项
  176. const selectItem = item => {
  177. selectedId.value = item.value;
  178. selectedItem.value = item;
  179. };
  180. // 确认选择
  181. const confirm = () => {
  182. if (selectedId.value) {
  183. const selected = columns.value.find(item => item.value === selectedId.value);
  184. if (selected) {
  185. emit('confirm', selected);
  186. }
  187. }
  188. close();
  189. };
  190. // 关闭弹窗
  191. const close = () => {
  192. visible.value = false;
  193. emit('close');
  194. };
  195. // 清除选中
  196. const clearSelected = () => {
  197. selectedId.value = '';
  198. selectedItem.value = null;
  199. };
  200. // 组件挂载时,如果show为true则加载数据
  201. onMounted(() => {
  202. if (props.show) {
  203. getPositionData();
  204. }
  205. });
  206. defineExpose({
  207. clearSelected,
  208. });
  209. </script>
  210. <style scoped>
  211. /* 使用深度选择器确保 van-search 样式不受影响 */
  212. :deep(.van-search) {
  213. padding: 8px 12px;
  214. }
  215. :deep(.van-cell) {
  216. align-items: center;
  217. }
  218. :deep(.van-radio) {
  219. margin-right: 0;
  220. }
  221. :deep(.van-button) {
  222. margin-bottom: 8px;
  223. }
  224. </style>