运费模板的实现

 要实现的效果:

        列表:

        弹框:

                当选择自定义运费时才需要填写“计价方式”、“运费数据”’,选择卖家包邮时不展示“计价方式”、“运费数据”’,也不需要传递给后端

 运费配送区域弹框:

        当是添加状态的弹框时,已添加到列表的数据不在展示在“可配送区域”中;当是编辑状态时,将点击编辑的当前行的城市数据默认勾选,并展示在“可配送区域”中;当点击删除后,将删除的数据添加回“可配送区域”中,并展示

cityData地区编码数据格式:省市区三级数据,包含港澳台

 

具体实现代码:

        展示数据列表页面:

<template>
	<el-card>
		<el-button type="primary" @click="add">新增</el-button>
		<!-- 列表 -->
		<el-table v-loading="false" :data="list" border style="width: 100%">
			<el-table-column label="主键">
				<template #default="scope">
					{{ scope.row.id ?? '无数据' }}
				</template>
			</el-table-column>
			<el-table-column label="模板名称" min-width="150px">
				<template #default="scope">
					{{ scope.row.name ?? '' }}
				</template>
			</el-table-column>
			<el-table-column label="发货时间" min-width="150px">
				<template #default="scope">
					{{ logisitcsTempTimeTypeFilter(scope.row.timeType) }}
				</template>
			</el-table-column>
			<el-table-column label="是否包邮" min-width="150px">
				<template #default="scope">
					{{ scope.row.free ? '是' : '否' }}
				</template>
			</el-table-column>
			<el-table-column label="计价方式" min-width="150px">
				<template #default="scope">
					{{ scope.row.priceType === 1 ? '件数' : scope.row.priceType === 2 ? '重量' : '体积' }}
				</template>
			</el-table-column>
			<el-table-column fixed="right" label="操作" width="200px">
				<template #default="scope">
					<span>
						<el-button type="primary" link @click="edit(scope.row)">编辑</el-button>
					</span>
					<span>
						<el-popconfirm title="确认是否删除" confirm-button-text="确认" cancel-button-text="取消" width="auto" @confirm="remove(scope.row.id)">
							<template #reference>
								<el-button type="danger" link>删除</el-button>
							</template>
						</el-popconfirm>
					</span>
				</template>
			</el-table-column>
		</el-table>
		<!-- 分页按钮 -->
		<div class="flex justify-end mt-4">
			<el-pagination
				v-model:page-size="pageSize"
				v-model:current-page="curPage"
				background
				layout="sizes, prev, pager, next"
				:total="total"
				class="mt-4"
				:page-sizes="[10, 30, 50, 80, 100]"
			/>
		</div>

		<!-- 弹窗 -->
		<modal ref="ModalRef" :list="list" @done="done"></modal>
	</el-card>
</template>
<script setup lang="ts">
import useList from '/@/hooks/useList';
import { reactive, ref } from 'vue';
import { errorMessage, message } from '/@/utils/message';
import modal from './childComponents/modal.vue';
import { logisticsTemplateApi } from '/@/network/api/logisticsTemplate/index';
import { DIALOG_MODE } from '/@/hooks/useDialog/types';
import { logisitcsTempTimeTypeFilter } from '/@/utils/filter/logisitcsTemplate';

// 获取组件实例
const ModalRef = ref();

const { loading, curPage, total, list, reset, pageSize, loadData } = useList(logisticsTemplateApi.list);
// 重新请求列表数据
const done = () => {
	loadData();
};

// 编辑
const edit = (data: any) => {
	if (!ModalRef.value) return;
	ModalRef.value.openDialog(DIALOG_MODE.EDIT, data);
};
// 新增
const add = () => {
	if (!ModalRef.value) return;
	ModalRef.value.openDialog(DIALOG_MODE.ADD);
};
// 删除
const remove = async (id: number) => {
	try {
		await logisticsTemplateApi.remove(id);
		message('删除成功');
		loadData();
	} catch (error) {
		errorMessage('删除失败');
	}
};
</script>

<style lang="scss" scoped></style>

        添加模板的弹窗代码( 屎山代码,还没优化(Ω_Ω) ):

<template>
	<div class="">
		<el-dialog v-model="visible" :title="`${modeText}物流模板`" width="80%" :before-close="customCloseDialogFn">
			<el-form ref="ruleFormRef" label-position="left" label-width="120px" :rules="rules" :model="formData">
				<el-form-item label="模板名称:" prop="name">
					<el-input v-model="formData.name" placeholder="请填写模板名称" />
				</el-form-item>
				<el-form-item label="发货时间:" prop="timeType">
					<el-select v-model="formData.timeType" filterable clearable cl class="w-full">
						<el-option label="1天内" :value="0" />
						<el-option label="2天内" :value="1" />
						<el-option label="3天内" :value="2" />
						<el-option label="5天内" :value="3" />
						<el-option label="7天内" :value="4" />
						<el-option label="10天内" :value="5" />
						<el-option label="15天内" :value="6" />
						<el-option label="20天内" :value="7" />
						<el-option label="30天内" :value="8" />
						<el-option label="60天内" :value="9" />
					</el-select>
				</el-form-item>
				<el-form-item label="是否包邮:" prop="free">
					<el-radio-group v-model="formData.free">
						<el-radio :label="false">自定义运费</el-radio>
						<el-radio :label="true">卖家包邮</el-radio>
					</el-radio-group>
				</el-form-item>
				<el-form-item label="计价方式:" prop="priceType" v-show="!formData.free">
					<el-radio-group v-model="formData.priceType">
						<el-radio :label="1">按件数</el-radio>
						<el-radio :label="2">按重量</el-radio>
						<el-radio :label="3">按体积</el-radio>
					</el-radio-group>
				</el-form-item>
				<!-- 运费数据表格 -->
				<el-form-item label="运费数据:" prop="info" v-show="!formData.free">
					<el-table v-loading="false" :data="formData.info" border style="width: 100%">
						<el-table-column label="可配送区域" min-width="200px">
							<template #default="scope"> {{ showCityText(scope.row.area) }} </template>
						</el-table-column>
						<el-table-column label="首件(个)" min-width="120px">
							<template #default="scope">
								{{ scope.row.first ?? '' }}
							</template>
						</el-table-column>
						<el-table-column label="运费(元)" min-width="120px">
							<template #default="scope">
								{{ scope.row.firstPrice ?? '' }}
							</template>
						</el-table-column>
						<el-table-column label="续件(个)" min-width="120px">
							<template #default="scope">
								{{ scope.row.continueNum ?? '' }}
							</template>
						</el-table-column>
						<el-table-column label="续费(元)" min-width="120px">
							<template #default="scope">
								{{ scope.row.continuePrice ?? '' }}
							</template>
						</el-table-column>
						<el-table-column fixed="right" label="操作" width="180px">
							<template #default="scope">
								<span>
									<el-button type="primary" link @click="editCity(scope.row, scope.$index)">编辑</el-button>
								</span>
								<span>
									<el-popconfirm
										title="确认是否删除"
										confirm-button-text="确认"
										cancel-button-text="取消"
										width="auto"
										@confirm="remove(scope.row, scope.$index)"
									>
										<template #reference>
											<el-button type="danger" link>删除</el-button>
										</template>
									</el-popconfirm>
								</span>
							</template>
						</el-table-column>
					</el-table>
					<el-button type="primary" @click="addCityFn">点击添加可配送区域和运费</el-button>
				</el-form-item>
			</el-form>
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="customCloseDialogFn">关闭</el-button>
					<el-button type="primary" @click="submit">{{ modeText }}</el-button>
				</span>
			</template>
		</el-dialog>
		<!-- 运费数据对话框 -->
		<el-dialog v-model="dialogFormVisible" :title="`${cityDialogModel}运费数据`" width="60%" :before-close="beforeClose">
			<el-form :model="form" :rules="infoRules" ref="infoRule">
				<el-form-item label="可配送区域:" label-width="120px" prop="area">
					<!-- 选择地理区域 -->
					<el-tree
						:data="cityDatas"
						ref="tree"
						show-checkbox
						accordion
						node-key="value"
						v-if="dialogFormVisible"
						:props="{ label: 'text', disabled: 'isDisabled' }"
						:default-checked-keys="checkedList"
					/>
				</el-form-item>
				<el-form-item label="首件(个):" label-width="120px" prop="first">
					<el-input v-model="form.first" placeholder="请输入" oninput="value=value.replace(/[^\d]/g,'')" />
				</el-form-item>
				<el-form-item label="运费(元):" label-width="120px" prop="firstPrice">
					<el-input v-model="form.firstPrice" placeholder="请输入" oninput="value=value.replace(/[^\d]/g,'')" />
				</el-form-item>
				<el-form-item label="续件(个):" label-width="120px" prop="continueNum">
					<el-input v-model="form.continueNum" placeholder="请输入" oninput="value=value.replace(/[^\d]/g,'')" />
				</el-form-item>
				<el-form-item label="续费(元):" label-width="120px" prop="continuePrice">
					<el-input v-model="form.continuePrice" placeholder="请输入" oninput="value=value.replace(/[^\d]/g,'')" />
				</el-form-item>
			</el-form>
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="beforeClose">取消</el-button>
					<el-button type="primary" @click="confirm"> 确认 </el-button>
				</span>
			</template>
		</el-dialog>
	</div>
</template>
<script setup lang="ts">
import { nextTick, ref, reactive, computed, watch } from 'vue';
import rules from './FormRules';
import infoRules from './infoRules';
import useDialogFn from '/@/hooks/useDialog/useDialogWithForm';
import type { FormInstance } from 'element-plus';
import { deepClone } from '/@/utils/other';
import { DIALOG_MODE } from '/@/hooks/useDialog/types';
import { logisticsTemplateApi } from '/@/network/api/logisticsTemplate/index';
import { errorMessage, message } from '/@/utils/message';
import cityData from './cityData';
// 处理城市数据 只保留省市二级
for (let province of cityData) {
	if (province.children) {
		for (let city of province.children) {
			delete city.children; // 删除市级数据中的 children 属性
		}
	}
}
// 添加parentId 为其父级的value
cityData.forEach((item) => {
	item.parentId = 0;
	item.children?.forEach((citem) => (citem.parentId = item.value));
});
// console.log(cityData);
// 扁平化后的数组 方便后续遍历对比使用
let flatArr: logisticsTemplateApi.cityDataType[] = [];
cityData.forEach((item: any) => {
	flatArr.push({
		value: item.value,
		text: item.text,
		parentId: item.parentId,
	});
	if (item.children) {
		item.children.forEach((childItem: any) => {
			flatArr.push(childItem);
		});
	}
});
// console.log(flatArr);

// 获取表单实例
const ruleFormRef = ref<FormInstance>();
const infoRule = ref<FormInstance>();

const emit = defineEmits<{
	(e: 'done'): void;
}>();
// 提取弹框对应的值和方法
const { visible, mode, closeDialog: customCloseDialog, openDialog: customOpenDialog, modeText } = useDialogFn(ruleFormRef);
/**
 * 物流模板表单数据
 * */
const formData = ref<logisticsTemplateApi.logisticsTemplateInfo>({
	name: '',
	free: false,
	priceType: 1,
	timeType: '',
	info: [],
});
/**
 * 选择配送区域的表单数据
 */
const form = ref({
	area: [],
	continueNum: '',
	continuePrice: '',
	first: '',
	firstPrice: '',
});
const dialogFormVisible = ref(false);
const cityDialogModel = ref('添加');
// 关闭弹框并重新请求列表数据
const closeDialog = () => {
	// 关闭并清空表单中数据
	customCloseDialogFn();
	emit('done');
};
const openDialog = (mode: DIALOG_MODE, data?: any) => {
	customOpenDialog(mode);
	nextTick(() => {
		if (mode === DIALOG_MODE.EDIT && data) {
			const val = deepClone(data);
			formData.value = val as any;
		}
	});
};
// 树形控件实例
const tree = ref();
let cityDatas = ref(JSON.parse(JSON.stringify(cityData)));
// 展示在表格中的区域字符串
let cityStr: string;
// 获取传入数组项重复出现了多少次的函数
function countOccurrences(arr: any) {
	const counts: any = {};
	cityStr = '';
	// 获取城市的parentId
	let codeArr = arr.map((item: any) => item.parentId);
	// console.log(codeArr);

	// 遍历数组,同一省份的城市出现了多少次
	codeArr.forEach(function (num: any) {
		// 对于每个数字,累计其出现次数
		if (counts[num]) {
			counts[num]++;
		} else {
			counts[num] = 1;
		}
	});
	// 遍历计数对象,打印数字和出现次数
	for (let num in counts) {
		// console.log(`${num}出现了${counts[num]}次。`);
		// 根据地区编码前两位获取到是属于哪一个省级
		if (num != '0') {
			let a: any = cityData.find((item: any) => {
				return item.value == num;
			});
			// console.log('a', a);
			// 判读重复出现的次数是不是大于(因为全选会多出一个省级的对象,所以是判断大于长度)省级中市级数组的长度 是就证明是全选
			if (counts[num] == a?.children?.length) {
				// 全选就直接拼接省份名
				cityStr = cityStr + ' ' + a?.text;
			} else {
				// 将属于当前省份的数据项提取出来成一个新数组
				let newarr = arr.filter((item: any) => item.parentId == a.value);
				// console.log('newarr', newarr);
				cityStr = cityStr + ' ' + a?.text + '(';
				// 遍历新数组将对应的城市名拼接起来
				newarr.forEach((item: any, index: number) => {
					let b = a?.children?.find((citem: any) => citem.value == item.value);
					cityStr = cityStr + b?.text + '、';
				});
				cityStr = cityStr.substring(0, cityStr.length - 1) + ')';
			}
		}
	}
}
const customCloseDialogFn = () => {
	customCloseDialog();
	cityDatas.value = JSON.parse(JSON.stringify(cityData));
};
//处理展示配送区域文字的格式
const showCityText = (arr: { code: string; name: string }[]) => {
	let newArr: logisticsTemplateApi.cityDataType[] = [];
	flatArr.forEach((item) => {
		arr.forEach((citem) => {
			if (item.value == citem.code) {
				newArr.push(item);
			}
		});
	});
	countOccurrences(newArr);
	return cityStr;
};
// 清空表单数据
const resetForm = () => {
	form.value.continueNum = '';
	form.value.continuePrice = '';
	form.value.first = '';
	form.value.firstPrice = '';
};
// 添加配送区域等信息到表格中
const confirm = async () => {
	// 选中的数据
	const checkedArr = tree.value.getCheckedNodes();
	if (!checkedArr[0]) return errorMessage('请选择可配送区域');
	// 转成上传需要的格式
	form.value.area = checkedArr.map((item: any) => ({ code: item.value, name: item.text }));
	if (!infoRule.value) return;
	// 验证
	const valid = await infoRule.value.validate();
	if (!valid) return;
	if (cityDialogModel.value === '添加') {
		formData.value.info.push({ ...form.value });
	} else {
		formData.value.info[editIndex] = { ...form.value };
	}
	// 删除掉已选中的数据
	checkedArr.forEach((item: any) => {
		// 删除省级
		cityDatas.value.forEach((citem: any, cindex: number) => {
			if (item.value == citem.value) {
				cityDatas.value.splice(cindex, 1);
				return;
			}
			// 删除市级
			citem.children.forEach((gitem: any, gindex: number) => {
				if (item.value == gitem.value) {
					citem.children.splice(gindex, 1);
				}
			});
		});
	});
	// 清空表单数据
	resetForm();
	dialogFormVisible.value = false;
};
// 添加配送区域
const addCityFn = () => {
	cityDialogModel.value = '添加';
	checkedList.value = [];
	dialogFormVisible.value = true;
};
// 默认选中的数组
const checkedList = ref([]);
// 保存编辑的索引
let editIndex = 0;
// 编辑前的城市数据
let beforeEditCityDatas = ref([]);
// 编辑配送区域数据
const editCity = (data: any, index: number) => {
	cityDialogModel.value = '编辑';
	editIndex = index;
	// 将编辑前的城市数据保存起来,若没编辑就保持原来的数据
	beforeEditCityDatas.value = JSON.parse(JSON.stringify(cityDatas.value));
	checkedList.value = data.area.map((item: any) => item.code);
	dialogFormVisible.value = true;
	form.value = { ...data };
	editPush(data.area);
};
//将编辑中的数据加回城市数据中
const editPush = (data: any) => {
	const addCityDatas: any = [];
	// 取到对应的带parentId的数据
	data.forEach((item: any) => {
		flatArr.forEach((citem) => {
			if (item.code == citem.value) {
				addCityDatas.push(citem);
			}
		});
	});
	addCityDatas.forEach((item: any) => {
		if (item.parentId == 0) {
			cityDatas.value.push({ ...item, children: [] });
		} else {
			// 判断城市数据里有没有这个父级
			const hasParentId = cityDatas.value.find((hitem: any) => hitem.value === item.parentId);
			if (hasParentId) {
				cityDatas.value.forEach((citem: any, cindex: number) => {
					if (item.parentId == citem.value) {
						citem.children.push(item);
						// 排序
						sortCity(citem.children);
					}
				});
			} else {
				const findParent = flatArr.find((fitem: any) => fitem.value === item.parentId);
				cityDatas.value.push(findParent);
				cityDatas.value[cityDatas.value.length - 1].children = [];
				cityDatas.value[cityDatas.value.length - 1].children.push(item);
			}
		}
	});
	sortCity(cityDatas.value);
};
// 如果编辑没有确认更改数据 恢复更新前数据
const beforeClose = () => {
	dialogFormVisible.value = false;
	if (cityDialogModel.value === '编辑') {
		cityDatas.value = beforeEditCityDatas.value;
	}
	resetForm();
};
// 如果是编辑模式就将已选中的城市从展示数据中删除
watch(
	() => visible.value,
	(newVal) => {
		if (newVal && modeText.value === '编辑') {
			setTimeout(() => {
				const flatInfo = formData.value.info.flat();
				// console.log(flatInfo);
				// 删除掉已选中的数据
				flatInfo.forEach((item: any) => {
					item.area.forEach((jitem: any) => {
						// 删除省级
						cityDatas.value.forEach((citem: any, cindex: number) => {
							if (jitem.code == citem.value) {
								cityDatas.value.splice(cindex, 1);
								return;
							}
							// 删除市级
							citem.children.forEach((gitem: any, gindex: number) => {
								if (jitem.code == gitem.value) {
									citem.children.splice(gindex, 1);
								}
							});
						});
					});
				});
			}, 100);
		}
	}
);
// 删除
const remove = (data: any, index: number) => {
	formData.value.info.splice(index, 1);
	editPush(data.area);
};
//排序方法 添加回来后的城市用的是push 数据回添加在最后
const sortCity = (data: any) => {
	data.sort((a: any, b: any) => {
		return a.value - b.value;
	});
};
// 提交方法
const submit = async () => {
	if (!ruleFormRef.value) return;
	// 验证
	const valid = await ruleFormRef.value.validate();
	if (!valid) return;
	let sendData: any = { info: [] };
	// 判断是否包邮
	if (formData.value.free) {
		sendData = formData.value;
		sendData.info = [];
	} else if (!formData.value.free && formData.value.info.length === 0) {
		return errorMessage('选择自定义运费时,需要填写运费数据信息');
	} else {
		sendData = formData.value;
	}
	if (mode.value === DIALOG_MODE.EDIT) {
		// 提交
		try {
			await logisticsTemplateApi.update(sendData);
			message('修改成功');
			closeDialog();
		} catch (error) {
			errorMessage('修改失败');
		}
	}
	if (mode.value === DIALOG_MODE.ADD) {
		// 提交
		try {
			await logisticsTemplateApi.add(sendData);
			message('添加成功');
			closeDialog();
		} catch (error) {
			errorMessage('添加失败');
		}
	}
};

defineExpose({ visible, openDialog, closeDialog });
</script>

<style lang="scss" scoped>
:deep(.el-tree) {
	display: flex;
	flex-wrap: wrap;
}
:deep(.el-tree .el-tree-node__children) {
	display: flex;
	flex-wrap: wrap;
}
</style>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值