feat(customer): 新增客户相关配卷和财务信息查询接口 fix(base.vue): 修复发货单时间条件显示问题 refactor(CustomerEdit): 替换地址选择组件为普通输入框 feat(CoilSelector): 增加入场卷号查询条件并调整对话框宽度 style(OrderEdit): 调整客户名称和销售员选择框宽度 refactor(ChinaAreaSelect): 优化地址解析逻辑并支持空对象处理 feat(FileUpload/FileList): 新增文件预览功能组件 refactor(KLPService/CustomerSelect): 优化客户选择组件并支持自定义字段绑定 fix(AbnormalForm): 修复异常位置校验逻辑并保留当前卷号 feat(ContractTabs): 新增合同附件展示功能 refactor(warehouse/record): 重构操作记录统计展示方式 feat(contract): 集成客户选择组件并优化合同信息填充 refactor(order): 调整订单表单布局并集成合同信息 feat(FilePreview): 新增文件预览组件 feat(customer): 新增财务状态和发货配卷展示 refactor(CustomerOrder): 移除冗余代码并优化布局 feat(PlanDetailForm): 新增合同附件查看功能 feat(dict): 新增字典管理页面
234 lines
7.2 KiB
Vue
234 lines
7.2 KiB
Vue
<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'
|
||
import { formatAreaTextEnhanced } from './index.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) {
|
||
const formattedAddress = formatAreaTextEnhanced(val)
|
||
this.areaValue = formattedAddress.standard.split('/').filter(Boolean)
|
||
this.detailAddress = formattedAddress.custom.trim()
|
||
},
|
||
|
||
/**
|
||
* 构建树形结构:兼容直辖市+港澳特别行政区的两级结构
|
||
*/
|
||
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> |