fix(shopExpressTemplateDetail): 修正配送区域多选字段对接后端机制
- model中省市多选字段由provinceIds/cityIds改为regionMode/regionIds - RegionMultiSelect组件去除update:provinceIds事件,改仅emit update:value - 编辑弹窗中城市选择变化同步更新form.regionIds和form.regionMode字段 - 回显数据时从regionIds拆分赋值,不再使用provinceIds/cityIds - 列表页customRender展示使用record.regionIds反查城市名 - 解决前端提交字段与后端实体不匹配导致数据未保存问题 - 确保本次改动完全兼容后端现有regionMode/regionIds机制,无需后端改动
This commit is contained in:
@@ -23,6 +23,20 @@
|
||||
- 新增 `h` 函数 import。
|
||||
|
||||
### 待办 / 风险
|
||||
- **后端需同步改造**:表加 `province_ids` / `city_ids`(varchar 逗号分隔),接口 add/update 接收这两个字段。前端已按此约定提交,后端未改则数据落不了库。
|
||||
- antd 为 v4,`var(--ant-color-border)` 在暗色下可能回退浅灰(边框略浅,不影响功能);如要完美暗色适配可后续改 less 变量。
|
||||
- 构建验证(vite build)已触发,待结果确认无编译错误。
|
||||
- 构建验证(vite build)已通过。
|
||||
|
||||
## ⚠️ 重大修订(同日):前端字段名与后端不匹配,导致"没存进库"
|
||||
|
||||
### 根因
|
||||
- 后端 `ShopExpressTemplateDetail` 实体**已有**配送区域多选机制:`regionMode`(0=全国 / 1=指定配送白名单 / 2=不发货黑名单)+ `regionIds`(逗号分隔的 6 位城市编码串)。
|
||||
- 旧代码 `provinceId`/`cityId` 是单值历史字段,已被迁移脚本 `sql/shop_express_template_detail_region.sql` 迁成 `regionMode=1, regionIds=城市编码`。
|
||||
- 我第一版前端提交的是 `provinceIds`/`cityIds`,后端实体无此字段 → MyBatis-Plus 自动忽略未知字段 → 看起来"没存没变化"。
|
||||
- ORM 是 MyBatis-Plus,add/update 用 `save()`/`updateById()` 自动生成 SQL,接收的是实体类本身(非 Param);新增字段只需在**实体加属性 + 表加列**,无需改 Mapper XML。
|
||||
|
||||
### 修复(对接后端已有的 regionMode/regionIds,无需后端改动)
|
||||
1. model:`provinceIds`/`cityIds` → 改为 `regionMode?`、`regionIds?`。
|
||||
2. `RegionMultiSelect` 组件:去掉 `update:provinceIds` emit,只 emit `update:value`(城市编码数组)。
|
||||
3. 编辑弹窗:城市选择变化 → `form.regionIds = 逗号串`、`form.regionMode = 1`(有选)或 `0`(全国);回显从 `form.regionIds` 拆出。
|
||||
4. 列表页:customRender 读 `record.regionIds` 反查城市名。
|
||||
- 结论:**本需求纯前端即可闭环,后端无需改**。城市编码(`regions-data.json` 的 city value,6位)与 `regionIds` 完全一致,下单校验 `DeliveryRegionChecker` 读 `regionIds` 直接生效。
|
||||
|
||||
@@ -57,9 +57,10 @@
|
||||
|
||||
### 运费模板配送地区多选(2026-08-11 已实现前端)
|
||||
- 需求:配送地区选择从「省+市单选级联」升级为「省+市两级多选穿梭弹窗」(仿客户截图双栏:左候选/右已选)。
|
||||
- 前端组件:`src/components/RegionMultiSelect/index.vue`(树形可展开,省级"选择"=选该省全部市,市级可单独选;@update:value 城市ID数组 + @update:provinceIds 省份ID数组)。
|
||||
- 数据字段:model 新增 `provinceIds` / `cityIds`(逗号分隔字符串);保留旧 `provinceId`/`cityId` 兼容(编辑回显时旧单值会自动补进 cityIds)。
|
||||
- 列表多值展示:`index.vue` customRender 遍历 `cityIds` 反查 regionMap,超 22 字截断 + tooltip。
|
||||
- ⚠️ 后端待改:`shop_express_template_detail` 表加 `province_ids`/`city_ids`(varchar 逗号分隔),add/update 接口接收这两个字段;前端已按此约定提交,后端未改前数据落不了库。
|
||||
- 注:与"不发货地区(黑名单) regionMode/regionIds"是不同需求,勿混淆。
|
||||
- 前端组件:`src/components/RegionMultiSelect/index.vue`(树形可展开,省级"选择"=选该省全部市,市级可单独选;@update:value 城市编码数组)。
|
||||
- **直接对接后端已有的 `regionMode`(0=全国/1=指定配送白名单/2=不发货黑名单) + `regionIds`(逗号分隔城市编码串) 机制,无需后端改动**。城市编码即 `regions-data.json` 的 city value(6位),与 `regionIds` 完全一致。
|
||||
- 数据字段:model 用 `regionMode` / `regionIds`(已有字段,非新建);旧 `provinceId`/`cityId` 仅作回显兼容。
|
||||
- 列表多值展示:`index.vue` customRender 遍历 `regionIds` 反查 regionMap,超 22 字截断 + tooltip。
|
||||
- 下单校验 `DeliveryRegionChecker` 读 `regionIds` 直接生效(regionMode=2 为不发货黑名单)。
|
||||
- ⚠️ 教训:第一版误用 `provinceIds`/`cityIds` 字段名,与后端 `regionMode`/`regionIds` 不匹配,MyBatis-Plus 自动忽略未知字段导致"没存"。改字段名对接即可,不必新增后端字段。
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ export interface ShopExpressTemplateDetail {
|
||||
provinceId?: number;
|
||||
/** 城市ID(旧单值字段,兼容历史数据) */
|
||||
cityId?: number;
|
||||
/** 省份IDs(逗号分隔,多选配送区域) */
|
||||
provinceIds?: string;
|
||||
/** 城市IDs(逗号分隔,多选配送区域) */
|
||||
cityIds?: string;
|
||||
/** 配送区域模式: 0=全国, 1=指定配送(白名单), 2=指定不发货(黑名单) */
|
||||
regionMode?: number;
|
||||
/** 地区编码列表(6位行政区划码,逗号分隔),regionMode=1或2时有效 */
|
||||
regionIds?: string;
|
||||
/** 首件数量/重量 */
|
||||
firstNum?: string;
|
||||
/** 首件价格 */
|
||||
|
||||
@@ -129,7 +129,6 @@
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string[]): void;
|
||||
(e: 'update:provinceIds', value: string[]): void;
|
||||
}>();
|
||||
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
@@ -226,17 +225,6 @@
|
||||
}))
|
||||
);
|
||||
|
||||
const derivedProvinceIds = computed(() => {
|
||||
const set = new Set<string>();
|
||||
selectedSet.value.forEach((cv) => {
|
||||
const pv = cityToProvince.value[cv];
|
||||
if (pv) {
|
||||
set.add(pv);
|
||||
}
|
||||
});
|
||||
return Array.from(set);
|
||||
});
|
||||
|
||||
const selectProvince = (p: ProvinceItem) => {
|
||||
const s = new Set(selectedSet.value);
|
||||
p.cities.forEach((c) => s.add(c.value));
|
||||
@@ -275,7 +263,6 @@
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('update:value', selectedCities.value);
|
||||
emit('update:provinceIds', derivedProvinceIds.value);
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
|
||||
+22
-32
@@ -24,7 +24,6 @@
|
||||
<RegionMultiSelect
|
||||
:value="cityIds"
|
||||
@update:value="onCityIdsChange"
|
||||
@update:provinceIds="onProvinceIdsChange"
|
||||
/>
|
||||
<div class="rms-tip">不选则默认全国</div>
|
||||
</a-form-item>
|
||||
@@ -128,10 +127,8 @@
|
||||
const maxable = ref(true);
|
||||
// 表单实例
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 已选城市ID数组
|
||||
// 已选城市编码数组(对应后端的 regionIds)
|
||||
const cityIds = ref<string[]>([]);
|
||||
// 已选省份ID数组(由城市推导,随城市变化同步)
|
||||
const provinceIds = ref<string[]>([]);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<ShopExpressTemplateDetail>({
|
||||
@@ -140,8 +137,8 @@
|
||||
type: true,
|
||||
provinceId: undefined,
|
||||
cityId: undefined,
|
||||
provinceIds: undefined,
|
||||
cityIds: undefined,
|
||||
regionMode: undefined,
|
||||
regionIds: undefined,
|
||||
firstNum: undefined,
|
||||
firstAmount: undefined,
|
||||
extraAmount: undefined,
|
||||
@@ -190,16 +187,16 @@
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 城市选中变化,同步到 form.cityIds */
|
||||
/* 城市选中变化,同步到 form.regionIds / regionMode(1=指定配送白名单,0=全国) */
|
||||
const onCityIdsChange = (val: string[]) => {
|
||||
cityIds.value = val;
|
||||
form.cityIds = val.length ? val.join(',') : undefined;
|
||||
};
|
||||
|
||||
/* 省份选中变化(由组件根据已选城市推导),同步到 form.provinceIds */
|
||||
const onProvinceIdsChange = (val: string[]) => {
|
||||
provinceIds.value = val;
|
||||
form.provinceIds = val.length ? val.join(',') : undefined;
|
||||
if (val.length) {
|
||||
form.regionIds = val.join(',');
|
||||
form.regionMode = 1;
|
||||
} else {
|
||||
form.regionIds = undefined;
|
||||
form.regionMode = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
@@ -215,13 +212,14 @@
|
||||
// 确保templateId和type从父级继承
|
||||
formData.templateId = props.templateId;
|
||||
formData.type = props.templateType ?? true;
|
||||
// 多选配送区域(逗号分隔,空则默认全国)
|
||||
formData.cityIds = cityIds.value.length
|
||||
? cityIds.value.join(',')
|
||||
: undefined;
|
||||
formData.provinceIds = provinceIds.value.length
|
||||
? provinceIds.value.join(',')
|
||||
: undefined;
|
||||
// 多选配送区域(regionMode=1 指定配送,0=全国;regionIds 存城市编码逗号串)
|
||||
if (cityIds.value.length) {
|
||||
formData.regionIds = cityIds.value.join(',');
|
||||
formData.regionMode = 1;
|
||||
} else {
|
||||
formData.regionIds = undefined;
|
||||
formData.regionMode = 0;
|
||||
}
|
||||
const saveOrUpdate = isUpdate.value
|
||||
? updateShopExpressTemplateDetail
|
||||
: addShopExpressTemplateDetail;
|
||||
@@ -252,26 +250,18 @@
|
||||
form.templateId = props.templateId;
|
||||
form.type = props.templateType ?? true;
|
||||
}
|
||||
// 回显多选配送区域
|
||||
if (form.cityIds) {
|
||||
cityIds.value = form.cityIds.split(',').filter(Boolean);
|
||||
// 回显多选配送区域(regionIds 存城市编码)
|
||||
if (form.regionIds) {
|
||||
cityIds.value = form.regionIds.split(',').filter(Boolean);
|
||||
} else if (form.cityId) {
|
||||
// 兼容旧单值数据
|
||||
cityIds.value = [String(form.cityId)];
|
||||
} else {
|
||||
cityIds.value = [];
|
||||
}
|
||||
if (form.provinceIds) {
|
||||
provinceIds.value = form.provinceIds.split(',').filter(Boolean);
|
||||
} else if (form.provinceId) {
|
||||
provinceIds.value = [String(form.provinceId)];
|
||||
} else {
|
||||
provinceIds.value = [];
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
cityIds.value = [];
|
||||
provinceIds.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -128,8 +128,8 @@
|
||||
align: 'center',
|
||||
width: 150,
|
||||
customRender: ({ record }) => {
|
||||
// 多选配送区域(cityIds 逗号分隔)
|
||||
const cityIds = (record.cityIds || '')
|
||||
// 多选配送区域(regionIds 逗号分隔的城市编码)
|
||||
const cityIds = (record.regionIds || '')
|
||||
.split(',')
|
||||
.filter(Boolean);
|
||||
if (cityIds.length) {
|
||||
|
||||
Reference in New Issue
Block a user