|
@@ -0,0 +1,388 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <el-dialog :model-value="modelValue" title="发货" width="1200px" @update:model-value="handleDialogChange" @open="handleOpen" @close="handleClose">
|
|
|
|
|
+ <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="订单编号" prop="orderCode">
|
|
|
|
|
+ <el-input v-model="form.orderCode" disabled />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :span="12" v-if="form.deliverMethod === '0'">
|
|
|
|
|
+ <el-form-item label="送货人" prop="deliverMan">
|
|
|
|
|
+ <el-input v-model="form.deliverMan" placeholder="请输入送货人姓名" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12" v-if="form.deliverMethod === '1'">
|
|
|
|
|
+ <el-form-item label="物流公司名称" prop="logisticsCompany">
|
|
|
|
|
+ <el-select v-model="form.logisticsCompany" placeholder="请选择" style="width: 100%" filterable>
|
|
|
|
|
+ <el-option v-for="company in logisticsCompanyList" :key="company.id" :label="company.logisticsName" :value="company.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item v-if="form.deliverMethod === '1'" label="物流单号" prop="logisticNo">
|
|
|
|
|
+ <el-input v-model="form.logisticNo" placeholder="请输入物流单号" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="发货方式" prop="deliverMethod">
|
|
|
|
|
+ <el-radio-group v-model="form.deliverMethod">
|
|
|
|
|
+ <el-radio v-for="dict in deliver_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12" v-if="form.deliverMethod === '1'">
|
|
|
|
|
+ <el-form-item label="收货人手机" prop="consigneePhone">
|
|
|
|
|
+ <el-input v-model="form.consigneePhone" placeholder="请输入收货人手机" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12" v-if="form.deliverMethod === '0'">
|
|
|
|
|
+ <el-form-item label="手机号码" prop="phone">
|
|
|
|
|
+ <el-input v-model="form.phone" placeholder="请输入手机号码" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="24">
|
|
|
|
|
+ <el-form-item label="发货备注" prop="deliverRemark">
|
|
|
|
|
+ <el-input v-model="form.deliverRemark" type="textarea" :rows="3" placeholder="请输入内容" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 商品列表 -->
|
|
|
|
|
+ <el-table :data="productList" border style="width: 100%" max-height="600px">
|
|
|
|
|
+ <el-table-column prop="productNo" label="产品编号" width="100" />
|
|
|
|
|
+ <el-table-column label="产品图片" width="100">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-image v-if="scope.row.productImage" :src="scope.row.productImage" style="width: 60px; height: 60px" fit="cover" />
|
|
|
|
|
+ <span v-else>暂无图片</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="productName" label="产品名称" min-width="200" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="categoryName" label="类别" width="120" />
|
|
|
|
|
+ <el-table-column prop="productUnit" label="单位" width="80" />
|
|
|
|
|
+ <el-table-column prop="orderQuantity" label="商品总数" width="100" />
|
|
|
|
|
+ <el-table-column prop="quantitySent" label="已发货数量" width="100" />
|
|
|
|
|
+ <el-table-column prop="unsentQuantity" label="未发货数量" width="100" />
|
|
|
|
|
+ <el-table-column label="发货数量">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="scope.row.deliverNum"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :max="scope.row.unsentQuantity"
|
|
|
|
|
+ :precision="0"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ @change="handleDeliveryQuantityChange(scope.$index)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="100" fixed="right" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button link type="danger" size="small" @click="handleDeleteProduct(scope.$index)">删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="queryParams.pageNum"
|
|
|
|
|
+ v-model:page-size="queryParams.pageSize"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ :page-sizes="[20, 50, 100]"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
|
|
+ class="mt-4"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button @click="handleCancel">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确认</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, reactive, computed, onMounted } from 'vue';
|
|
|
|
|
+import { getOrderMain } from '@/api/order/orderMain';
|
|
|
|
|
+import { OrderMainVO } from '@/api/order/orderMain/types';
|
|
|
|
|
+import { listOrderProduct } from '@/api/order/orderProduct';
|
|
|
|
|
+import { OrderProductVO } from '@/api/order/orderProduct/types';
|
|
|
|
|
+import { listLogisticsCompany } from '@/api/company/logisticsCompany';
|
|
|
|
|
+import { LogisticsCompanyVO } from '@/api/company/logisticsCompany/types';
|
|
|
|
|
+import { addOrderDeliver } from '@/api/order/orderDeliver';
|
|
|
|
|
+import { OrderDeliverVO, OrderDeliverForm } from '@/api/order/orderDeliver/types';
|
|
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
|
|
+
|
|
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
+const { deliver_method } = toRefs<any>(proxy?.useDict('deliver_method'));
|
|
|
|
|
+
|
|
|
|
|
+interface Props {
|
|
|
|
|
+ modelValue: boolean;
|
|
|
|
|
+ orderId?: string | number;
|
|
|
|
|
+ orderNo?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface Emits {
|
|
|
|
|
+ (e: 'update:modelValue', value: boolean): void;
|
|
|
|
|
+ (e: 'success'): void;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const props = defineProps<Props>();
|
|
|
|
|
+const emit = defineEmits<Emits>();
|
|
|
|
|
+
|
|
|
|
|
+const formRef = ref();
|
|
|
|
|
+const submitLoading = ref(false);
|
|
|
|
|
+const productList = ref<any[]>([]);
|
|
|
|
|
+const total = ref(0);
|
|
|
|
|
+
|
|
|
|
|
+// 物流公司列表
|
|
|
|
|
+const logisticsCompanyList = ref<LogisticsCompanyVO[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+// 查询参数
|
|
|
|
|
+const queryParams = ref({
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 20,
|
|
|
|
|
+ orderId: undefined as string | number | undefined
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 表单数据
|
|
|
|
|
+const form = reactive<OrderDeliverForm>({
|
|
|
|
|
+ orderId: undefined,
|
|
|
|
|
+ orderCode: '',
|
|
|
|
|
+ logisticPackNo: '',
|
|
|
|
|
+ deliverMethod: '1',
|
|
|
|
|
+ deliverMan: '',
|
|
|
|
|
+ phone: '',
|
|
|
|
|
+ logisticsStatus: '',
|
|
|
|
|
+ deliverRemark: '',
|
|
|
|
|
+ checklistRemark: '',
|
|
|
|
|
+ logisticsCompany: undefined,
|
|
|
|
|
+ logisticNo: '',
|
|
|
|
|
+ logisticPackStatus: '',
|
|
|
|
|
+ consigneePhone: '',
|
|
|
|
|
+ remark: '',
|
|
|
|
|
+ orderDeliverProducts: []
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 动态校验规则
|
|
|
|
|
+const rules = computed(() => {
|
|
|
|
|
+ const baseRules: any = {
|
|
|
|
|
+ deliverMethod: [{ required: true, message: '请选择发货方式', trigger: 'change' }]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 第三方物流(deliverMethod === '1')
|
|
|
|
|
+ if (form.deliverMethod === '1') {
|
|
|
|
|
+ baseRules.logisticsCompany = [{ required: true, message: '请选择物流公司', trigger: 'change' }];
|
|
|
|
|
+ baseRules.logisticNo = [{ required: true, message: '请输入物流单号', trigger: 'blur' }];
|
|
|
|
|
+ baseRules.consigneePhone = [
|
|
|
|
|
+ { required: true, message: '请输入收货人手机', trigger: 'blur' },
|
|
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 自有物流(deliverMethod === '0')
|
|
|
|
|
+ if (form.deliverMethod === '0') {
|
|
|
|
|
+ baseRules.deliverMan = [{ required: true, message: '请输入送货人姓名', trigger: 'blur' }];
|
|
|
|
|
+ baseRules.phone = [
|
|
|
|
|
+ { required: true, message: '请输入手机号码', trigger: 'blur' },
|
|
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return baseRules;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 对话框状态变化
|
|
|
|
|
+const handleDialogChange = (val: boolean) => {
|
|
|
|
|
+ emit('update:modelValue', val);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 对话框打开时触发
|
|
|
|
|
+const handleOpen = () => {
|
|
|
|
|
+ resetForm();
|
|
|
|
|
+ if (props.orderId) {
|
|
|
|
|
+ form.orderId = props.orderId;
|
|
|
|
|
+ form.orderCode = props.orderNo || '';
|
|
|
|
|
+ queryParams.value.orderId = props.orderId;
|
|
|
|
|
+ loadProductList();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 对话框关闭时触发
|
|
|
|
|
+const handleClose = () => {
|
|
|
|
|
+ resetForm();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 重置表单
|
|
|
|
|
+const resetForm = () => {
|
|
|
|
|
+ form.orderId = undefined;
|
|
|
|
|
+ form.orderCode = '';
|
|
|
|
|
+ form.logisticsCompany = undefined;
|
|
|
|
|
+ form.logisticNo = '';
|
|
|
|
|
+ form.deliverMethod = '1';
|
|
|
|
|
+ form.deliverMan = '';
|
|
|
|
|
+ form.phone = '';
|
|
|
|
|
+ form.consigneePhone = '';
|
|
|
|
|
+ form.deliverRemark = '';
|
|
|
|
|
+ productList.value = [];
|
|
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
|
|
+ formRef.value?.clearValidate();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 加载商品列表
|
|
|
|
|
+const loadProductList = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getOrderMain(props.orderId);
|
|
|
|
|
+ console.log(res.data.orderProductList);
|
|
|
|
|
+
|
|
|
|
|
+ // 为每个商品添加发货数量字段,默认为未发货数量
|
|
|
|
|
+ productList.value = (res.data.orderProductList || []).map((item: OrderProductVO) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ deliverNum: 0,
|
|
|
|
|
+ productNo: item.productNo,
|
|
|
|
|
+ productId: item.productId,
|
|
|
|
|
+ productUnitId: item.productUnit
|
|
|
|
|
+ }));
|
|
|
|
|
+ total.value = res.data.orderProductList.length || 0;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载商品列表失败:', error);
|
|
|
|
|
+ ElMessage.error('加载商品列表失败');
|
|
|
|
|
+ productList.value = [];
|
|
|
|
|
+ total.value = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 加载物流公司列表
|
|
|
|
|
+const loadLogisticsCompanyList = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await listLogisticsCompany({
|
|
|
|
|
+ isShow: '0',
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 1000
|
|
|
|
|
+ });
|
|
|
|
|
+ logisticsCompanyList.value = res.rows || [];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载物流公司列表失败:', error);
|
|
|
|
|
+ logisticsCompanyList.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 发货数量变化
|
|
|
|
|
+const handleDeliveryQuantityChange = (index: number) => {
|
|
|
|
|
+ const product = productList.value[index];
|
|
|
|
|
+ if (product) {
|
|
|
|
|
+ // 确保发货数量不超过未发货数量
|
|
|
|
|
+ if (product.deliverNum > product.unsentQuantity) {
|
|
|
|
|
+ product.deliverNum = product.unsentQuantity;
|
|
|
|
|
+ ElMessage.warning('发货数量不能大于未发货数量');
|
|
|
|
|
+ }
|
|
|
|
|
+ // 确保发货数量不小于0
|
|
|
|
|
+ if (product.deliverNum < 0) {
|
|
|
|
|
+ product.deliverNum = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 删除商品
|
|
|
|
|
+const handleDeleteProduct = (index: number) => {
|
|
|
|
|
+ productList.value.splice(index, 1);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 分页大小变化
|
|
|
|
|
+const handleSizeChange = () => {
|
|
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
|
|
+ loadProductList();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 页码变化
|
|
|
|
|
+const handleCurrentChange = () => {
|
|
|
|
|
+ loadProductList();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 取消
|
|
|
|
|
+const handleCancel = () => {
|
|
|
|
|
+ emit('update:modelValue', false);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 提交
|
|
|
|
|
+const handleSubmit = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 验证表单
|
|
|
|
|
+ await formRef.value?.validate();
|
|
|
|
|
+
|
|
|
|
|
+ // 验证是否有发货商品
|
|
|
|
|
+ const deliveryProducts = productList.value.filter((item) => item.deliverNum > 0);
|
|
|
|
|
+ if (deliveryProducts.length === 0) {
|
|
|
|
|
+ ElMessage.warning('请至少选择一个商品进行发货');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ submitLoading.value = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 组装发货数据
|
|
|
|
|
+ const deliveryData: OrderDeliverForm = {
|
|
|
|
|
+ orderId: form.orderId,
|
|
|
|
|
+ orderCode: form.orderCode,
|
|
|
|
|
+ logisticsCompany: form.logisticsCompany,
|
|
|
|
|
+ logisticNo: form.logisticNo,
|
|
|
|
|
+ deliverMethod: form.deliverMethod,
|
|
|
|
|
+ deliverMan: form.deliverMan,
|
|
|
|
|
+ phone: form.phone,
|
|
|
|
|
+ consigneePhone: form.consigneePhone,
|
|
|
|
|
+ deliverRemark: form.deliverRemark,
|
|
|
|
|
+ orderDeliverProducts: deliveryProducts.map((item) => ({
|
|
|
|
|
+ productId: item.productId,
|
|
|
|
|
+ productNo: item.productNo,
|
|
|
|
|
+ productName: item.productName,
|
|
|
|
|
+ productUnitId: item.productUnitId,
|
|
|
|
|
+ productUnit: item.productUnit,
|
|
|
|
|
+ deliverNum: item.deliverNum
|
|
|
|
|
+ }))
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 调用发货API
|
|
|
|
|
+ await addOrderDeliver(deliveryData);
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success('发货成功');
|
|
|
|
|
+ emit('success');
|
|
|
|
|
+ emit('update:modelValue', false);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('发货失败:', error);
|
|
|
|
|
+ if (error !== false) {
|
|
|
|
|
+ // 不是表单验证失败
|
|
|
|
|
+ ElMessage.error('发货失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 组件挂载时加载物流公司列表
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadLogisticsCompanyList();
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.mt-4 {
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-footer {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|