场景省市区组件默认中国。展示下面对应的省选择省之后出现新的tabs页省下面对应的市以此类推。弹框组件。功能1、根据国家查询出对应的省份以此类推。2、接口调用是一级一级查询也可以直接缓存在本地树形组装数据。3、选择城市高亮已勾选数据再次勾选当作清除功能。4、默认国家设置为中国。注1、组件使用的是react写法如需vue写法转换即可。2、UI用的是猪齿鱼不好用项目所用自己替换antd、element比较好用的。3、tabs切换是手写的。4、下拉组件和鼠标悬浮涉及到组件其他纯手写代码。效果图一、代码实现1、父级页面import React, { useState } from react; import { Form, Icon, TextField, useDataSet } from choerodon-ui/pro; import styles from ./main.module.less; import CityModel from /components/CityModel/index; import { LabelLayout } from choerodon-ui/pro/lib/form/enum; import { FieldType } from choerodon-ui/dataset/data-set/enum; const Index (props: any) { const [visible, setVisible] useState(false); /** ds 发起PK申请 */ const [updateDs, setUpdateDs] useState(new Date().getTime()); // 时间戳 用于更新Ds数据状态 const formDataDs: any useDataSet(() { return { autoCreate: true, fields: [ { name: address, type: FieldType.string, label: 省市区, placeholder: 请选择省市区, }, ], events: { update: () { setUpdateDs(new Date().getTime()); }, }, }; }, []); const handleSelect val { // 处理拿到的地址数据 formDataDs.current.set({ address: val.map(item item.name)?.join(/), }); }; return ( div className{styles.cityForm} Form dataSet{formDataDs} columns{4} labelWidth{80} labelLayout{LabelLayout.vertical} TextField nameaddress onClick{() setVisible(true)} onChange{val setVisible(true)} clearButton addonAfter{ Icon typeLOV-o onClick{() { setVisible(true); }} / } / /Form CityModel visible{visible} setVisible{setVisible} title省/市/区 onSelect{val handleSelect(val)} / /div ); }; export default Index;2、组件封装组件包含文件index.tsx 【组件模块】index.less【样式文件】interface.ts【ts类型】store.ts 【js逻辑】api.ts 【接口配置】index.tsx 页面import ImgCountry from xxx/components/selectCity/img/icon.png; import ImgNoData from xxx/components/selectCity/img/no.png; import DataSet from choerodon-ui/dataset/data-set/DataSet; import { Form, Icon, Modal, Select, Tooltip } from choerodon-ui/pro; import { LabelLayout } from choerodon-ui/pro/lib/form/enum; import React, { useEffect, useMemo, useRef, useState } from react; import ./index.less; import { getCountryList } from ./api; import { CityProps, HandleParamsFunction, TabsComponent, TabsListItem, } from ./interface; import { formList, handleFilter, handleHighLight, resetChooseToFalse, } from ./store; const SelectCity: React.FCCityProps ({ /** 控制弹框显示/隐藏 */ visible, /** 设置弹框显示/隐藏的回调函数 */ setVisible, /** 地址选择后的回调函数 */ onSelect, /** 弹框标题默认为地址弹框 */ title 地址弹框, /** 查询当前层级,值固定为country | province | city | district | town */ cityLevel town, }) { const address useRefany(); /** 内容 */ const Box: React.FC{ handleParams: HandleParamsFunction } ({ handleParams, }) { /** ds */ const formDataDs: any useMemo(() new DataSet(formList()), []); /** tabs */ const [tabsList, setTabsList] useStateTabsListItem[]([]); const [tabsComponent, setTabsComponent] useStateTabsComponent[]([]); // tabs 对应组件 const [activeTab, setActiveTab] useState(0); // 默认选中第一个标签 /** 公共获取当前国家对应的省 */ const updateAddress async (val: string) { const res: any await getCountryList(val); const result [ { name: res[0]?.level || , component: res, }, ]; setTabsComponent(result); }; /** 初始化获取当前国家对应的省 */ const getData async () { if (formDataDs.current?.get(country)) { setTabsList([{ name: 请选择, show: true }]); updateAddress(formDataDs.current?.get(country)); // 对应省更新 } }; useEffect(() { getData(); }, []); useEffect(() { const params { tabsList, country: formDataDs.current.get(country), }; handleParams(params); }, [tabsList]); /** 切换国家 change */ const handleCountry async (val: string) { if (val null) { setActiveTab(0); setTabsList([]); setTabsComponent([]); return; } updateAddress(val); setTabsList([{ name: 请选择, show: true }]); setActiveTab(0); }; /** tabs 切换 */ const handleChangeTabs (index: number) { setActiveTab(index); /** 需要高亮的数据 */ const menu tabsList[index]; /** 高亮回显 */ const result handleHighLight(tabsComponent, index, menu.code || ); setTabsComponent(result); }; /** tabs 页签删除 */ const handleTabsDelete (index: number) { if (index activeTab) { /** 当前选中页签及之后的数据都删除 */ const updatedTabsList tabsList.slice(0, index); updatedTabsList[updatedTabsList.length - 1] { name: 请选择, show: true, }; setTabsList(updatedTabsList); const updatedTabsComponent tabsComponent.slice(0, index); setTabsComponent(updatedTabsComponent); } else { /** 删除 tabsList 的第 index 个元素,最后一条数据重置 */ const updatedTabsList [...tabsList]; updatedTabsList.splice(index, 1); updatedTabsList[updatedTabsList.length - 1] { name: 请选择, show: true, }; setTabsList(updatedTabsList); /** 删除 tabsComponent 的第 index 个元素 */ const updatedTabsComponent [...tabsComponent]; updatedTabsComponent.splice(index, 1); setTabsComponent(updatedTabsComponent); } /** 更新 activeTab, 如果删除所有页签清空国家 */ const updateIndex index - 1; if (updateIndex -1) { formDataDs.current.set({ country: , }); } setActiveTab(updateIndex); }; const resetTabs async (select: any, index: number) { /** 删除 tabsList 中从索引 index 开始的所有数据 */ const newTabsList [ ...tabsList.slice(0, index).map((item) ({ ...item, show: false })), { ...select, show: false }, { name: 请选择, show: true }, ]; setTabsList(newTabsList); /** 设置 activeTab */ const newActiveTab newTabsList.findIndex((item) item.show true); setActiveTab(newActiveTab); /** 更新 tabsComponent插入新数据 */ const res: any await getCountryList(select.code); if (res.length 0) { const newData { name: res[0].level, component: res, }; const newTabsComponent [ ...tabsComponent.slice(0, index 1), newData, ]; setTabsComponent(newTabsComponent); } }; const updatedData (select: TabsListItem, index: number) { /** 更新 tabsComponent 中的选择状态 */ const updatedTabsComponent handleHighLight( tabsComponent, index, select.code || , ); setTabsComponent(updatedTabsComponent); /** 更新当前选中的 tabs 页签 */ tabsList[activeTab] { ...select, }; }; const chooseTabs async (index: number, select: TabsListItem) { const chooseData tabsComponent[index].component.filter( (item) item.choose, ); /** 1、勾选的数据之前已经是勾选状态 */ if (chooseData[0]?.code select?.code) { /** 所有的数据的show变成false并且删除 最后一条数据插入一条新数据 {name:请选择 show: true}, */ const updatedTabsList: any tabsList.map((item) ({ ...item, show: false, })); updatedTabsList.pop(); updatedTabsList.push({ name: 请选择, show: true }); setTabsList(updatedTabsList); const updatedTabsComponent resetChooseToFalse(tabsComponent); setTabsComponent(updatedTabsComponent); return; } /** 1、更新 tabsComponent 中的选择状态 */ const updatedTabsComponent handleHighLight( tabsComponent, index, select.code || , ); /** 2、更新当前选中的 tabs 页签 */ tabsList[activeTab] { ...select, }; /** 查询截止到省 */ if (cityLevel province) { setTabsComponent(updatedTabsComponent); return; } /** 查询截止到市 */ if (cityLevel city index 1) { updatedData(select, index); return; } /** 查询截止到区 */ if (cityLevel district index 2) { updatedData(select, index); return; } /** 3、更新 tabsList设置 请选择 页签 */ const res: any await getCountryList(select.code); if (res.length 0) { /** 4、更新 tabsList设置 请选择 页签 */ const updatedTabsList [ ...tabsList.map((item) ({ ...item, show: false })), { name: 请选择, show: true }, ]; setTabsList(updatedTabsList); /** 5、设置 activeTab */ const updateActiveTab updatedTabsList.findIndex( (item) item.show true, ); setActiveTab(updateActiveTab); /** 6、更新 tabsComponent插入新数据 */ const newData { name: res[0].level, component: res, }; updatedTabsComponent.splice(updateActiveTab, 0, newData); } setTabsComponent(updatedTabsComponent); }; /** 选中 */ const handleChooseItem async (select: any, index: number) { /** 1、判断有没有勾选地址 */ const hasChoose tabsComponent[index].component.some( (item) item.choose true, ); /** 2、如果勾选过进行重置 */ if (hasChoose tabsList.length - 1 ! activeTab) { const chooseData tabsComponent[index].component.filter( (item) item.choose, ); /** 勾选的数据和去除数据一样例: 苏州 勾选点击苏州去除勾选 */ if (chooseData[0]?.code select?.code) { if (index 0) { /** 重置当前国家对应的省 */ setActiveTab(0); setTabsList([{ name: 请选择, show: true }]); updateAddress(formDataDs.current?.get(country)); } else { /** 更新 tabsList (删除 tabsList 中从索引 index 开始的所有数据) */ const updatedTabsList [ ...tabsList .slice(0, index) .map((item) ({ ...item, show: false })), { name: 请选择, show: true }, ]; setTabsList(updatedTabsList); /** 更新 tabsComponent */ const newTabsComponent [...tabsComponent.slice(0, index 1)]; const updatedTabsComponent resetChooseToFalse(newTabsComponent); setTabsComponent(updatedTabsComponent); } return; } /** 勾选数据与修改数据不一致例:苏州 变为 扬州 */ await resetTabs(select, index); return; } /** 3、添加地址 */ await chooseTabs(index, select); }; /** 重置 */ const handleReset () { /** 默认数据为中国 */ formDataDs.current?.set({ country: CN, }); setActiveTab(0); setTabsList([{ name: 请选择, show: true }]); updateAddress(CN); }; return ( div classNameltcCity {/* 国家 */} div classNamesearch img src{ImgCountry} alt / Form classNameleft dataSet{formDataDs} columns{1} labelWidth{40} labelLayout{LabelLayout.horizontal} Select namecountry searchable onChange{handleCountry} / /Form div classNamereset onClick{handleReset} 重置 /div /div {cityLevel ! country ( {/* 当前选中 */} {handleFilter(tabsList) ( div classNamename 当前选中 span classNamenameText{handleFilter(tabsList)}/span /div )} {/* tabs */} div classNametabsContainer {/* 菜单 */} {tabsList.length 0 ( div classNametabs {tabsList.map((item, index) ( div key{index} className{tab ${activeTab index ? active : }} onClick{() handleChangeTabs(index)} Tooltip title{item.name} themelight div classNametitle{item.name}/div /Tooltip Icon typeclose classNameclose onClick{(e) { e.stopPropagation(); handleTabsDelete(index); }} / /div ))} /div )} {/* 内容 */} {tabsComponent.length 0 ( div classNametabContent {tabsComponent.map((item: any, index: number) ( div key{index} className{content ${ activeTab index ? show : hide }} {item.component.length 0 ? ( div classNamebox {item.component.map( (cityItem: any, cityIndex: number) { return ( div key{cityIndex} className{tags ${ cityItem.choose ? tagsActive : }} onClick{() handleChooseItem(cityItem, index) } {cityItem.name} /div ); }, )} /div / ) : ( div classNamenoData img classNamepic src{ImgNoData} alt / div classNametxt无检索结果/div /div / )} /div ))} /div )} /div / )} /div ); }; /** 选择的地址 */ const onParams: HandleParamsFunction (params) { address.current params; }; /** 提交 */ const handleSubmit async () { const { country, tabsList } address.current; /** 获取国家信息 */ const res: any await getCountryList(); const countryData res.filter( (item: TabsListItem) item.code country, ); /** 详细地址组装 */ const newData [...countryData, ...tabsList]; const result newData.filter((item) item.name ! 请选择); onSelect(result); }; /** 弹框打开 */ const openModal () { Modal.open({ title: title, style: { width: 600px }, closable: true, maskClosable: true, onClose: () { setVisible(false); }, children: Box handleParams{onParams} /, okText: 确定, onOk: async () { handleSubmit(); }, }); }; useEffect(() { if (visible) { openModal(); } }, [visible]); return /; }; export default SelectCity;store.tsimport DataSet, { DataSetProps } from choerodon-ui/dataset/data-set/DataSet; import { FieldType } from choerodon-ui/dataset/data-set/enum; import { getCountryListApi } from ./api; import { TabsComponent } from ./interface; const handleFilter (list: any) { if (list.length 0) { return list .map((item: any) item.name) ?.filter((val: any) val ! 请选择) ?.join(/); } return 请选择; }; /** 获取国家下拉列表 */ const countryOptions: DataSet new DataSet({ autoQuery: true, paging: false, // 不分页 transport: { read: (): any { return { ...getCountryListApi(), }; }, }, }); /** ds */ const formList (): DataSetProps { return { autoCreate: true, fields: [ { name: country, type: FieldType.string, label: 国家, placeholder: 请选择, valueField: code, textField: name, options: countryOptions, defaultValue: CN, }, ], }; }; /** * 高亮显示找到list的第index数组下 code和选中code相同的那条数据设置choose为true其他数据为false * param tabsComponent list * param index 索引 * param selectedCode 需要高亮的值的code * returns */ const handleHighLight ( tabsComponent: TabsComponent[], index: number, selectedCode: string, ) { const result tabsComponent.map((tab: any, tabIndex: number) { if (tabIndex index) { const updatedComponent tab.component.map((cityItem: any) { if (cityItem.code selectedCode) { return { ...cityItem, choose: true }; } else { return { ...cityItem, choose: false }; } }); return { ...tab, component: updatedComponent }; } else { return { ...tab, component: tab.component.map((cityItem: any) ({ ...cityItem, choose: false, })), }; } }); return result; }; /** * 重置数组中choose都为false * param data * returns */ const resetChooseToFalse (data: TabsComponent[]) { return data.map((item) ({ ...item, component: item.component.map((subItem) ({ ...subItem, choose: false, })), })); }; export { formList, handleFilter, handleHighLight, resetChooseToFalse };interface.tsexport interface CityProps { visible: boolean; setVisible: (val: boolean) void; onSelect: (val: any) void; title?: string; cityLevel?: country | province | city | district | town; } export interface TabsListItem { name: string; tenantId?: number; id?: number; code?: string; codePath?: string; namePath?: string; quickIndex?: string; level?: number; show?: boolean; choose?: boolean; } export interface TabsComponent { name: string; component: TabsListItem[]; } export type HandleParamsFunction (params: { tabsList: TabsListItem[]; country: string; }) void;index.less 页面.ltcCity { width: 100%; height: 100%; /** 查询 */ .search { width: 100%; display: flex; flex-direction: row; align-items: center; img { width: 14px; height: 14px; } .left { flex: 1; } .reset { width: 40px; font-size: 12px; color: #0099f2; text-align: right; } } /** 当前选中 */ .name { padding: 8px 0; .nameText { color: #0099f2; } } /** tabs */ .tabsContainer { .tabs { height: 28px; line-height: 28px; display: flex; align-items: center; margin-top: 8px; .tab { font-size: 12px; font-weight: 400; padding: 0 8px; cursor: pointer; border: 1px solid #c6cfd8; border-right: 0; display: flex; align-items: center; justify-content: space-between; .title { width: 50px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .close { font-size: 8px; font-weight: 700; color: #0099f2; cursor: pointer; margin-left: 10px; :hover { color: white; background: #c0c4cc; border-radius: 50%; } } } .tab:first-child { border-top-left-radius: 4px; } .tab:last-child { border-top-right-radius: 4px; border-right: 1px solid #c6cfd8; } } .tab.active { color: #0099f2; font-weight: 700px; border-bottom: 1px solid white; } .tabContent { padding: 10px 0; .box { display: flex; flex-wrap: wrap; gap: 10px; // height: 310px; overflow-y: auto; .tags { width: calc((100% - 30px) / 4); flex-basis: calc((100% - 30px) / 4); border-radius: 4px; padding: 2px 3px; border: 1px solid #c6cfd8; cursor: pointer; display: flex; align-items: center; justify-content: center; // display: inline-block; // text-align: center; /* 超出显示省略号 */ // white-space: nowrap; // overflow: hidden; // text-overflow: ellipsis; :hover { background: rgba(0, 153, 242, 8%); color: #0099f2; } } .tagsActive { border: 1px solid #0099f2; color: #0099f2; } } /** 无数据 */ .noData { width: 100%; // height: 310px; display: flex; align-items: center; flex-direction: column; justify-content: center; background: #f5f7f8; .pic { width: 50px; height: 50px; } .txt { color: #a7b1bc; font-size: 12px; line-height: 16px; } } /* 默认隐藏所有内容 */ .content { display: none; } /* 显示当前选中的内容 */ .content.show { display: block; } /* 隐藏未选中的内容 */ .content.hide { display: none; } } } }