Merge remote-tracking branch 'gitee/0.8.X' into 0.8.X

This commit is contained in:
2025-12-22 16:55:21 +08:00
5 changed files with 3744 additions and 32 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,256 @@
<template>
<div class="area-select-container">
<!-- 区域选择器 -->
<el-cascader
v-model="areaValue"
:options="areaTree"
:props="cascaderProps"
filterable
clearable
placeholder="请选择中国区域"
collapse-tags
@change="handleAreaChange"
v-if="isAreaTreeReady"
style="width: 100%; margin-bottom: 8px;"
/>
<!-- 详细地址输入框 -->
<el-input
v-model="detailAddress"
placeholder="请输入详细地址XX街道XX小区XX号楼"
@input="handleDetailChange"
style="width: 100%;"
/>
</div>
</template>
<script>
import areaData from './data.js'
export default {
name: 'ChinaAreaSelectWithDetail',
props: {
// 外部传入的组合值:格式为「[标准地址](自定义地址)」
value: {
type: String,
default: ''
},
// 配置绑定值类型code行政区划代码/ name区域名称
keyType: {
type: String,
default: 'code',
validator: val => ['code', 'name'].includes(val)
},
// 是否只选择到市级(省/市),默认选到区级(省/市/区)
onlyCityLevel: {
type: Boolean,
default: false
}
},
data() {
return {
areaTree: [],
// 特殊区域代码(直辖市+港澳特别行政区)
specialAreaCodes: ['110000', '120000', '310000', '500000', '810000', '820000'],
isAreaTreeReady: false,
// 区域选择器的内部值数组格式存储code
areaValue: [],
// 详细地址输入值
detailAddress: '',
cascaderProps: {
value: 'code',
label: 'name',
children: 'children',
checkStrictly: false,
expandTrigger: 'click',
multiple: false,
leaf: node => !node.children
}
}
},
watch: {
// 监听外部传入的value自动拆分并回显
value: {
immediate: true,
handler(val) {
this.parseCombineValue(val)
}
}
},
computed: {
// 组合值:对外输出「[标准地址](自定义地址)」格式
combineValue() {
// 1. 转换区域值为标准字符串code/name格式
let standardAddress = ''
if (this.areaValue.length) {
standardAddress = this.keyType === 'name'
? this.convertCodeToName(this.areaValue).join('/')
: this.areaValue.join('/')
}
// 2. 组合最终格式:[标准地址](自定义地址)
if (!standardAddress && !this.detailAddress) return ''
if (standardAddress && !this.detailAddress) return `[${standardAddress}]()`
if (!standardAddress && this.detailAddress) return `[()](${this.detailAddress})`
return `[${standardAddress}](${this.detailAddress})`
}
},
created() {
this.$nextTick(() => {
this.buildAreaTree()
})
},
methods: {
/**
* 正则解析组合值:提取[标准地址]和(自定义地址)部分
* @param {String} val - 外部传入的「[标准地址](自定义地址)」格式值
*/
parseCombineValue(val) {
if (!val) {
this.areaValue = []
this.detailAddress = ''
return
}
// 核心正则:匹配「[xxx](yyy)」格式分组提取xxx和yyy无匹配则为空
const reg = /\[([^\]]*)\]\(([^)]*)\)/
const matchResult = val.match(reg)
// 提取标准地址(第一个分组)和自定义地址(第二个分组)
const standardAddress = matchResult?.[1] || ''
const customAddress = matchResult?.[2] || ''
// 1. 处理自定义地址:直接赋值
this.detailAddress = customAddress.trim()
// 2. 处理标准地址转换为区域选择器的code数组
if (standardAddress) {
const standardArr = standardAddress.split('/').filter(Boolean)
this.areaValue = this.keyType === 'name'
? this.convertNameToCode(standardArr) // name转code
: standardArr.filter(code => !!areaData[code]) // code直接过滤无效值
} else {
this.areaValue = []
}
},
/**
* 构建树形结构:兼容直辖市+港澳特别行政区的两级结构
*/
buildAreaTree() {
const tree = {};
// 第一步初始化所有省级节点非叶子节点带children
Object.keys(areaData).forEach(code => {
if (code.endsWith('0000') && !tree[code]) {
tree[code] = {
code: code,
name: areaData[code],
children: [] // 省级节点是父节点保留children
};
}
});
// 第二步:遍历所有非省级节点,构建层级
Object.entries(areaData).forEach(([code, name]) => {
if (code.length !== 6) return;
const provinceCode = code.substring(0, 2) + '0000';
// 跳过省级节点(已初始化)
if (code === provinceCode) return;
// 判断是否为特殊区域(直辖市/港澳)
const isSpecialArea = this.specialAreaCodes.includes(provinceCode);
// 1. 特殊区域处理区级直接挂载到省级且区级是叶子节点无children
if (isSpecialArea) {
if (tree[provinceCode]) {
tree[provinceCode].children.push({ code, name });
}
}
// 2. 普通省份处理:省→市→区三级
else {
const cityCode = code.substring(0, 4) + '00';
const provinceNode = tree[provinceCode];
if (!provinceNode) return;
// 2.1 初始化市级节点非叶子节点带children
if (!provinceNode.children.some(item => item.code === cityCode)) {
provinceNode.children.push({
code: cityCode,
name: areaData[cityCode] || '',
children: [] // 市级节点是父节点保留children
});
}
// 2.2 挂载区级节点叶子节点无children
if (code !== cityCode) {
const cityNode = provinceNode.children.find(item => item.code === cityCode);
if (cityNode) {
cityNode.children.push({ code, name });
}
}
}
});
// 第三步:格式化树形结构,过滤空节点
this.areaTree = Object.values(tree)
.filter(province => province.name && province.children.length > 0)
.map(province => {
province.children = province.children.filter(city => city.name);
return province;
});
// 标记数据就绪,渲染组件
this.isAreaTreeReady = true;
},
/**
* code转name容错处理
*/
convertCodeToName(codeArr) {
if (!Array.isArray(codeArr) || !codeArr.length) return [];
return codeArr.map(code => areaData[code] || '').filter(Boolean);
},
/**
* name转code容错处理
*/
convertNameToCode(nameArr) {
if (!Array.isArray(nameArr) || !nameArr.length) return [];
const nameToCodeMap = Object.fromEntries(
Object.entries(areaData).map(([code, name]) => [name, code])
);
return nameArr.map(name => nameToCodeMap[name] || '').filter(Boolean);
},
/**
* 区域选择器变化回调
*/
handleAreaChange() {
this.$emit('input', this.combineValue)
this.$emit('change', this.combineValue, {
standard: this.keyType === 'name' ? this.convertCodeToName(this.areaValue).join('/') : this.areaValue.join('/'),
custom: this.detailAddress
})
},
/**
* 详细地址变化回调
*/
handleDetailChange() {
this.$emit('input', this.combineValue)
this.$emit('change', this.combineValue, {
standard: this.keyType === 'name' ? this.convertCodeToName(this.areaValue).join('/') : this.areaValue.join('/'),
custom: this.detailAddress
})
}
}
}
</script>
<style scoped>
.area-select-container {
width: 100%;
}
</style>

View File

@@ -86,11 +86,16 @@
</el-col>
<el-col :span="24" v-hasPermi="['crm:customer:address']">
<el-form-item label="客户地址" prop="address">
<el-input
<!-- <el-input
type="textarea"
v-model="customer.address"
placeholder="请输入客户地址"
@input="handleInputChange"
/> -->
<ChinaAreaSelect
v-model="customer.address"
placeholder="请选择客户地址"
@change="handleInputChange"
/>
</el-form-item>
</el-col>
@@ -110,11 +115,13 @@
<script>
import JSONTableInput from './JSONTableInput.vue'
import ChinaAreaSelect from '@/components/ChinaAreaSelect/index.vue'
export default {
name: 'CustomerEdit',
components: {
JSONTableInput
JSONTableInput,
ChinaAreaSelect
},
props: {
// 客户信息对象(双向绑定)

View File

@@ -135,7 +135,10 @@
</el-select>
</el-form-item>
<el-form-item label="客户地址" prop="address">
<el-input v-model="form.address" placeholder="请输入客户地址" />
<ChinaAreaSelect
v-model="form.address"
placeholder="请选择客户地址"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
@@ -161,6 +164,7 @@ import JSONTableInput from '../components/JSONTableInput.vue'
import CustomerDetail from '../components/CustomerInfo.vue'
import CustomerEdit from '../components/CustomerEdit.vue'
import CustomerOrder from '../components/CustomerOrder.vue'
import ChinaAreaSelect from '@/components/ChinaAreaSelect/index.vue'
import { listCustomer, addCustomer, updateCustomer, delCustomer } from '@/api/crm/customer'
@@ -171,7 +175,8 @@ export default {
JSONTableInput,
CustomerDetail,
CustomerEdit,
CustomerOrder
CustomerOrder,
ChinaAreaSelect
},
dicts: ['customer_industry', 'customer_level'],
data() {

View File

@@ -455,25 +455,21 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
throw new ServiceException("未找到列标识为 " + columnFlag + " 的库位");
}
// 1. 筛选出待拆分的父库位(只校验需要拆分的库位,非拆分库位不限制)
List<WmsActualWarehouse> toSplitParents = columnLocations.stream()
.filter(loc -> splitIds.contains(loc.getActualWarehouseId()))
.collect(Collectors.toList());
// 2. 批量校验占用状态
// 检查整个列中是否有任何库位被占用
List<String> occupiedCodes = new ArrayList<>();
for (WmsActualWarehouse parent : toSplitParents) {
for (WmsActualWarehouse location : columnLocations) {
// 锁定状态=0 → 被占用
boolean isOccupied = parent.getIsEnabled() != null && parent.getIsEnabled() == 0;
boolean isOccupied = location.getIsEnabled() != null && location.getIsEnabled() == 0;
if (isOccupied) {
occupiedCodes.add(parent.getActualWarehouseCode());
occupiedCodes.add(location.getActualWarehouseCode());
}
}
// 3. 存在被占用的父库位,抛出异常提示
// 如果有任何库位被占用,抛出异常提示
if (!occupiedCodes.isEmpty()) {
throw new ServiceException("以下库位被占用,无法拆分:" + String.join(",", occupiedCodes));
throw new ServiceException("以下库位被占用,无法拆分:" + String.join(",", occupiedCodes));
}
// 批量查询所有子库位(一次性查询,避免循环查询)
List<Long> parentIds = columnLocations.stream()
.map(WmsActualWarehouse::getActualWarehouseId)
@@ -709,7 +705,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
}
// 批量查询所有子库位
List<Long> parentIds = locationsToMerge.stream()
List<Long> parentIds = columnLocations.stream()
.map(WmsActualWarehouse::getActualWarehouseId)
.collect(Collectors.toList());
@@ -717,20 +713,13 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
.in(WmsActualWarehouse::getParentId, parentIds)
.eq(WmsActualWarehouse::getActualWarehouseType, 4)); // 四级子库位
// 按父ID分组子库位
Map<Long, List<WmsActualWarehouse>> childrenByParent = allChildren.stream()
.collect(Collectors.groupingBy(WmsActualWarehouse::getParentId));
// 检查是否有子库位被占用
for (WmsActualWarehouse location : locationsToMerge) {
List<WmsActualWarehouse> children = childrenByParent.get(location.getActualWarehouseId());
if (CollUtil.isNotEmpty(children)) {
boolean anyOccupied = children.stream()
.filter(c -> c.getDelFlag() == 0)
.anyMatch(c -> !Objects.equals(c.getIsEnabled(), 1));
if (anyOccupied) {
throw new ServiceException("子库位被占用,不能合并:" + location.getActualWarehouseCode());
}
// 检查整列中是否有任何子库位被占用
if (CollUtil.isNotEmpty(allChildren)) {
boolean anyOccupied = allChildren.stream()
.filter(c -> c.getDelFlag() == 0)
.anyMatch(c -> !Objects.equals(c.getIsEnabled(), 1));
if (anyOccupied) {
throw new ServiceException("该列存在被占用的子库位,不能合并");
}
}
@@ -739,7 +728,9 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
List<WmsActualWarehouse> parentsToUpdate = new ArrayList<>();
for (WmsActualWarehouse location : locationsToMerge) {
List<WmsActualWarehouse> children = childrenByParent.get(location.getActualWarehouseId());
List<WmsActualWarehouse> children = allChildren.stream()
.filter(c -> Objects.equals(c.getParentId(), location.getActualWarehouseId()))
.collect(Collectors.toList());
// 隐藏所有子库位
if (CollUtil.isNotEmpty(children)) {
@@ -747,7 +738,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
if (child.getDelFlag() == 0) {
WmsActualWarehouse childUpdate = new WmsActualWarehouse();
childUpdate.setActualWarehouseId(child.getActualWarehouseId());
childUpdate.setDelFlag(2);
childUpdate.setDelFlag(1);
childrenToUpdate.add(childUpdate);
}
}