deliverDialog.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <template>
  2. <el-dialog :model-value="modelValue" title="发货" width="1200px" @update:model-value="handleDialogChange" @open="handleOpen" @close="handleClose">
  3. <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
  4. <el-row :gutter="20">
  5. <el-col :span="12">
  6. <el-form-item label="订单编号" prop="orderCode">
  7. <el-input v-model="form.orderCode" disabled />
  8. </el-form-item>
  9. </el-col>
  10. <el-col :span="12" v-if="form.deliverMethod === '0'">
  11. <el-form-item label="送货人" prop="deliverMan">
  12. <el-input v-model="form.deliverMan" placeholder="请输入送货人姓名" />
  13. </el-form-item>
  14. </el-col>
  15. <el-col :span="12" v-if="form.deliverMethod === '1'">
  16. <el-form-item label="物流公司名称" prop="logisticsCompany">
  17. <el-select v-model="form.logisticsCompany" placeholder="请选择" style="width: 100%" filterable>
  18. <el-option v-for="company in logisticsCompanyList" :key="company.id" :label="company.logisticsName" :value="company.id" />
  19. </el-select>
  20. </el-form-item>
  21. </el-col>
  22. </el-row>
  23. <el-row :gutter="20">
  24. <el-col :span="12">
  25. <el-form-item v-if="form.deliverMethod === '1'" label="物流单号" prop="logisticNo">
  26. <el-input v-model="form.logisticNo" placeholder="请输入物流单号" />
  27. </el-form-item>
  28. </el-col>
  29. <el-col :span="12">
  30. <el-form-item label="发货方式" prop="deliverMethod">
  31. <el-radio-group v-model="form.deliverMethod">
  32. <el-radio v-for="dict in deliver_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
  33. </el-radio-group>
  34. </el-form-item>
  35. </el-col>
  36. </el-row>
  37. <el-row :gutter="20">
  38. <el-col :span="12" v-if="form.deliverMethod === '1'">
  39. <el-form-item label="收货人手机" prop="consigneePhone">
  40. <el-input v-model="form.consigneePhone" placeholder="请输入收货人手机" />
  41. </el-form-item>
  42. </el-col>
  43. <el-col :span="12" v-if="form.deliverMethod === '0'">
  44. <el-form-item label="手机号码" prop="phone">
  45. <el-input v-model="form.phone" placeholder="请输入手机号码" />
  46. </el-form-item>
  47. </el-col>
  48. </el-row>
  49. <el-row :gutter="20">
  50. <el-col :span="24">
  51. <el-form-item label="发货备注" prop="deliverRemark">
  52. <el-input v-model="form.deliverRemark" type="textarea" :rows="3" placeholder="请输入内容" />
  53. </el-form-item>
  54. </el-col>
  55. </el-row>
  56. </el-form>
  57. <!-- 商品列表 -->
  58. <el-table :data="productList" border style="width: 100%" max-height="600px">
  59. <el-table-column prop="productNo" label="产品编号" width="100" />
  60. <el-table-column label="产品图片" width="100">
  61. <template #default="scope">
  62. <el-image v-if="scope.row.productImage" :src="scope.row.productImage" style="width: 60px; height: 60px" fit="cover" />
  63. <span v-else>暂无图片</span>
  64. </template>
  65. </el-table-column>
  66. <el-table-column prop="productName" label="产品名称" min-width="200" show-overflow-tooltip />
  67. <el-table-column prop="categoryName" label="类别" width="120" />
  68. <el-table-column prop="productUnit" label="单位" width="80" />
  69. <el-table-column prop="orderQuantity" label="商品总数" width="100" />
  70. <el-table-column prop="quantitySent" label="已发货数量" width="100" />
  71. <el-table-column prop="unsentQuantity" label="未发货数量" width="100" />
  72. <el-table-column label="发货数量">
  73. <template #default="scope">
  74. <el-input-number
  75. v-model="scope.row.deliverNum"
  76. :min="0"
  77. :max="scope.row.unsentQuantity"
  78. :precision="0"
  79. size="small"
  80. :controls="false"
  81. style="width: 100%"
  82. @change="handleDeliveryQuantityChange(scope.$index)"
  83. />
  84. </template>
  85. </el-table-column>
  86. <el-table-column label="操作" width="100" fixed="right" align="center">
  87. <template #default="scope">
  88. <el-button link type="danger" size="small" @click="handleDeleteProduct(scope.$index)">删除</el-button>
  89. </template>
  90. </el-table-column>
  91. </el-table>
  92. <!-- 分页 -->
  93. <el-pagination
  94. v-model:current-page="queryParams.pageNum"
  95. v-model:page-size="queryParams.pageSize"
  96. :total="total"
  97. :page-sizes="[20, 50, 100]"
  98. layout="total, sizes, prev, pager, next, jumper"
  99. @size-change="handleSizeChange"
  100. @current-change="handleCurrentChange"
  101. class="mt-4"
  102. />
  103. <template #footer>
  104. <div class="dialog-footer">
  105. <el-button @click="handleCancel">取消</el-button>
  106. <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确认</el-button>
  107. </div>
  108. </template>
  109. </el-dialog>
  110. </template>
  111. <script setup lang="ts">
  112. import { ref, reactive, computed, onMounted } from 'vue';
  113. import { getOrderMain } from '@/api/order/orderMain';
  114. import { OrderMainVO } from '@/api/order/orderMain/types';
  115. import { listOrderProduct } from '@/api/order/orderProduct';
  116. import { OrderProductVO } from '@/api/order/orderProduct/types';
  117. import { listLogisticsCompany } from '@/api/company/logisticsCompany';
  118. import { LogisticsCompanyVO } from '@/api/company/logisticsCompany/types';
  119. import { addOrderDeliver } from '@/api/order/orderDeliver';
  120. import { OrderDeliverVO, OrderDeliverForm } from '@/api/order/orderDeliver/types';
  121. import { ElMessage } from 'element-plus';
  122. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  123. const { deliver_method } = toRefs<any>(proxy?.useDict('deliver_method'));
  124. interface Props {
  125. modelValue: boolean;
  126. orderId?: string | number;
  127. orderNo?: string;
  128. }
  129. interface Emits {
  130. (e: 'update:modelValue', value: boolean): void;
  131. (e: 'success'): void;
  132. }
  133. const props = defineProps<Props>();
  134. const emit = defineEmits<Emits>();
  135. const formRef = ref();
  136. const submitLoading = ref(false);
  137. const productList = ref<any[]>([]);
  138. const total = ref(0);
  139. // 物流公司列表
  140. const logisticsCompanyList = ref<LogisticsCompanyVO[]>([]);
  141. // 查询参数
  142. const queryParams = ref({
  143. pageNum: 1,
  144. pageSize: 20,
  145. orderId: undefined as string | number | undefined
  146. });
  147. // 表单数据
  148. const form = reactive<OrderDeliverForm>({
  149. orderId: undefined,
  150. orderCode: '',
  151. logisticPackNo: '',
  152. deliverMethod: '1',
  153. deliverMan: '',
  154. phone: '',
  155. logisticsStatus: '',
  156. deliverRemark: '',
  157. checklistRemark: '',
  158. logisticsCompany: undefined,
  159. logisticNo: '',
  160. logisticPackStatus: '',
  161. consigneePhone: '',
  162. remark: '',
  163. orderDeliverProducts: []
  164. });
  165. // 动态校验规则
  166. const rules = computed(() => {
  167. const baseRules: any = {
  168. deliverMethod: [{ required: true, message: '请选择发货方式', trigger: 'change' }]
  169. };
  170. // 第三方物流(deliverMethod === '1')
  171. if (form.deliverMethod === '1') {
  172. baseRules.logisticsCompany = [{ required: true, message: '请选择物流公司', trigger: 'change' }];
  173. baseRules.logisticNo = [{ required: true, message: '请输入物流单号', trigger: 'blur' }];
  174. baseRules.consigneePhone = [
  175. { required: true, message: '请输入收货人手机', trigger: 'blur' },
  176. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
  177. ];
  178. }
  179. // 自有物流(deliverMethod === '0')
  180. if (form.deliverMethod === '0') {
  181. baseRules.deliverMan = [{ required: true, message: '请输入送货人姓名', trigger: 'blur' }];
  182. baseRules.phone = [
  183. { required: true, message: '请输入手机号码', trigger: 'blur' },
  184. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
  185. ];
  186. }
  187. return baseRules;
  188. });
  189. // 对话框状态变化
  190. const handleDialogChange = (val: boolean) => {
  191. emit('update:modelValue', val);
  192. };
  193. // 对话框打开时触发
  194. const handleOpen = () => {
  195. resetForm();
  196. if (props.orderId) {
  197. form.orderId = props.orderId;
  198. form.orderCode = props.orderNo || '';
  199. queryParams.value.orderId = props.orderId;
  200. loadProductList();
  201. }
  202. };
  203. // 对话框关闭时触发
  204. const handleClose = () => {
  205. resetForm();
  206. };
  207. // 重置表单
  208. const resetForm = () => {
  209. form.orderId = undefined;
  210. form.orderCode = '';
  211. form.logisticsCompany = undefined;
  212. form.logisticNo = '';
  213. form.deliverMethod = '1';
  214. form.deliverMan = '';
  215. form.phone = '';
  216. form.consigneePhone = '';
  217. form.deliverRemark = '';
  218. productList.value = [];
  219. queryParams.value.pageNum = 1;
  220. formRef.value?.clearValidate();
  221. };
  222. // 加载商品列表
  223. const loadProductList = async () => {
  224. try {
  225. const res = await getOrderMain(props.orderId);
  226. console.log(res.data.orderProductList);
  227. // 为每个商品添加发货数量字段,默认为未发货数量
  228. productList.value = (res.data.orderProductList || []).map((item: OrderProductVO) => ({
  229. ...item,
  230. deliverNum: 0,
  231. productNo: item.productNo,
  232. productId: item.productId,
  233. productUnitId: item.productUnit
  234. }));
  235. total.value = res.data.orderProductList.length || 0;
  236. } catch (error) {
  237. console.error('加载商品列表失败:', error);
  238. ElMessage.error('加载商品列表失败');
  239. productList.value = [];
  240. total.value = 0;
  241. }
  242. };
  243. // 加载物流公司列表
  244. const loadLogisticsCompanyList = async () => {
  245. try {
  246. const res = await listLogisticsCompany({
  247. isShow: '0',
  248. pageNum: 1,
  249. pageSize: 1000
  250. });
  251. logisticsCompanyList.value = res.rows || [];
  252. } catch (error) {
  253. console.error('加载物流公司列表失败:', error);
  254. logisticsCompanyList.value = [];
  255. }
  256. };
  257. // 发货数量变化
  258. const handleDeliveryQuantityChange = (index: number) => {
  259. const product = productList.value[index];
  260. if (product) {
  261. // 确保发货数量不超过未发货数量
  262. if (product.deliverNum > product.unsentQuantity) {
  263. product.deliverNum = product.unsentQuantity;
  264. ElMessage.warning('发货数量不能大于未发货数量');
  265. }
  266. // 确保发货数量不小于0
  267. if (product.deliverNum < 0) {
  268. product.deliverNum = 0;
  269. }
  270. }
  271. };
  272. // 删除商品
  273. const handleDeleteProduct = (index: number) => {
  274. productList.value.splice(index, 1);
  275. };
  276. // 分页大小变化
  277. const handleSizeChange = () => {
  278. queryParams.value.pageNum = 1;
  279. loadProductList();
  280. };
  281. // 页码变化
  282. const handleCurrentChange = () => {
  283. loadProductList();
  284. };
  285. // 取消
  286. const handleCancel = () => {
  287. emit('update:modelValue', false);
  288. };
  289. // 提交
  290. const handleSubmit = async () => {
  291. try {
  292. // 验证表单
  293. await formRef.value?.validate();
  294. // 验证是否有发货商品
  295. const deliveryProducts = productList.value.filter((item) => item.deliverNum > 0);
  296. if (deliveryProducts.length === 0) {
  297. ElMessage.warning('请至少选择一个商品进行发货');
  298. return;
  299. }
  300. submitLoading.value = true;
  301. // 组装发货数据
  302. const deliveryData: OrderDeliverForm = {
  303. orderId: form.orderId,
  304. orderCode: form.orderCode,
  305. logisticsCompany: form.logisticsCompany,
  306. logisticNo: form.logisticNo,
  307. deliverMethod: form.deliverMethod,
  308. deliverMan: form.deliverMan,
  309. phone: form.phone,
  310. consigneePhone: form.consigneePhone,
  311. deliverRemark: form.deliverRemark,
  312. orderDeliverProducts: deliveryProducts.map((item) => ({
  313. productId: item.productId,
  314. productNo: item.productNo,
  315. productName: item.productName,
  316. productUnitId: item.productUnitId,
  317. productUnit: item.productUnit,
  318. deliverNum: item.deliverNum
  319. }))
  320. };
  321. // 调用发货API
  322. await addOrderDeliver(deliveryData);
  323. ElMessage.success('发货成功');
  324. emit('success');
  325. emit('update:modelValue', false);
  326. } catch (error) {
  327. console.error('发货失败:', error);
  328. if (error !== false) {
  329. // 不是表单验证失败
  330. ElMessage.error('发货失败,请重试');
  331. }
  332. } finally {
  333. submitLoading.value = false;
  334. }
  335. };
  336. // 组件挂载时加载物流公司列表
  337. onMounted(() => {
  338. loadLogisticsCompanyList();
  339. });
  340. </script>
  341. <style scoped lang="scss">
  342. .mt-4 {
  343. margin-top: 16px;
  344. }
  345. .dialog-footer {
  346. display: flex;
  347. justify-content: flex-end;
  348. gap: 10px;
  349. }
  350. </style>