deliverDialog.vue 13 KB

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