633 lines
14 KiB
JavaScript
633 lines
14 KiB
JavaScript
import { addressOption } from '@/subPackages/sub_clinic_salesperson/utils/address.js';
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/** 跨级搜索结果 */
|
||
|
||
|
||
/** 列表面板统一行 */
|
||
|
||
|
||
const MUNICIPALITY_LABELS = new Set([
|
||
'北京市',
|
||
'天津市',
|
||
'上海市',
|
||
'重庆市',
|
||
]);
|
||
|
||
let provinceIndexCache = null;
|
||
|
||
function isPrefectureCityLabel(label) {
|
||
return label.endsWith('市') && !label.endsWith('自治区');
|
||
}
|
||
|
||
function isDistrictLikeLabel(label) {
|
||
return /(?:区|县|旗)$/.test(label);
|
||
}
|
||
|
||
function detectMunicipality(province, children) {
|
||
if (MUNICIPALITY_LABELS.has(province.label)) {
|
||
return true;
|
||
}
|
||
if (!province.label.endsWith('市') || children.length === 0) {
|
||
return false;
|
||
}
|
||
const districtCount = children.filter((c) => isDistrictLikeLabel(c.label)).length;
|
||
return districtCount / children.length >= 0.8;
|
||
}
|
||
|
||
function buildProvinceIndexList() {
|
||
return (addressOption).map((province) => {
|
||
const children = (province)
|
||
.children ?? [];
|
||
return {
|
||
province: {
|
||
value: province.value,
|
||
label: province.label,
|
||
pvalue: province.pvalue,
|
||
},
|
||
children: children.map((c) => ({
|
||
value: c.value,
|
||
label: c.label,
|
||
pvalue: c.pvalue,
|
||
})),
|
||
isMunicipality: detectMunicipality(province, children),
|
||
};
|
||
});
|
||
}
|
||
|
||
function getProvinceIndexList() {
|
||
if (!provinceIndexCache) {
|
||
provinceIndexCache = buildProvinceIndexList();
|
||
}
|
||
return provinceIndexCache;
|
||
}
|
||
|
||
function findProvinceIndex(provinceId) {
|
||
if (!provinceId) {
|
||
return undefined;
|
||
}
|
||
return getProvinceIndexList().find((p) => p.province.value === provinceId);
|
||
}
|
||
|
||
function findChild(
|
||
provinceIndex,
|
||
childId,
|
||
) {
|
||
if (!childId) {
|
||
return undefined;
|
||
}
|
||
return provinceIndex.children.find((c) => c.value === childId);
|
||
}
|
||
|
||
function getCityNamePrefix(label) {
|
||
return label.replace(/市$/, '');
|
||
}
|
||
|
||
/** 省级列表 */
|
||
export function getProvinces() {
|
||
return getProvinceIndexList().map((p) => p.province);
|
||
}
|
||
|
||
/** 市级列表(直辖市返回空) */
|
||
export function getCities(provinceId) {
|
||
const index = findProvinceIndex(provinceId);
|
||
if (!index || index.isMunicipality) {
|
||
return [];
|
||
}
|
||
return index.children;
|
||
}
|
||
|
||
/** 区级列表 */
|
||
export function getDistricts(
|
||
provinceId,
|
||
cityId,
|
||
) {
|
||
const index = findProvinceIndex(provinceId);
|
||
if (!index) {
|
||
return [];
|
||
}
|
||
if (index.isMunicipality) {
|
||
return index.children;
|
||
}
|
||
if (!cityId) {
|
||
return [];
|
||
}
|
||
const city = findChild(index, cityId);
|
||
if (!city || !isPrefectureCityLabel(city.label)) {
|
||
return [];
|
||
}
|
||
const prefix = getCityNamePrefix(city.label);
|
||
return index.children.filter(
|
||
(c) =>
|
||
c.value !== cityId &&
|
||
!isPrefectureCityLabel(c.label) &&
|
||
c.label.startsWith(prefix),
|
||
);
|
||
}
|
||
|
||
/** 当前层级候选列表 */
|
||
export function getLevelOptions(
|
||
level,
|
||
ctx,
|
||
) {
|
||
if (level === 'province') {
|
||
return getProvinces();
|
||
}
|
||
if (level === 'city') {
|
||
return getCities(ctx.provinceId);
|
||
}
|
||
return getDistricts(ctx.provinceId, ctx.cityId);
|
||
}
|
||
|
||
/** 选择某级后的下一层级(无则 undefined) */
|
||
export function getNextLevel(
|
||
currentLevel,
|
||
ctx,
|
||
) {
|
||
const index = findProvinceIndex(ctx.provinceId);
|
||
if (!index) {
|
||
return undefined;
|
||
}
|
||
if (currentLevel === 'province') {
|
||
return index.isMunicipality ? 'district' : 'city';
|
||
}
|
||
if (currentLevel === 'city') {
|
||
if (!ctx.cityId) {
|
||
return undefined;
|
||
}
|
||
const districts = getDistricts(ctx.provinceId, ctx.cityId);
|
||
return districts.length > 0 ? 'district' : undefined;
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/** 当前层级选中后是否可直接完成(叶节点) */
|
||
export function canCompleteAfterSelect(
|
||
level,
|
||
ctx,
|
||
item,
|
||
) {
|
||
const index = findProvinceIndex(ctx.provinceId);
|
||
if (!index) {
|
||
return false;
|
||
}
|
||
if (level === 'district') {
|
||
return true;
|
||
}
|
||
if (level === 'city') {
|
||
if (index.isMunicipality) {
|
||
return false;
|
||
}
|
||
if (!isPrefectureCityLabel(item.label)) {
|
||
return true;
|
||
}
|
||
return getDistricts(ctx.provinceId, item.value).length === 0;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** 组装对外 address 值 */
|
||
export function buildAddressOutput(
|
||
provinceId,
|
||
cityId,
|
||
districtId,
|
||
) {
|
||
if (!provinceId) {
|
||
return undefined;
|
||
}
|
||
const leafId = districtId ?? cityId;
|
||
if (!leafId) {
|
||
return undefined;
|
||
}
|
||
return [provinceId, leafId];
|
||
}
|
||
|
||
/** 由 address 反查省/市/区 */
|
||
export function resolveAddressValue(
|
||
value,
|
||
) {
|
||
if (!value || value.length < 2) {
|
||
return {};
|
||
}
|
||
const [provinceId, leafId] = value;
|
||
const index = findProvinceIndex(provinceId);
|
||
if (!index) {
|
||
return { provinceId };
|
||
}
|
||
const leaf = findChild(index, leafId);
|
||
if (!leaf) {
|
||
return { provinceId, province: index.province };
|
||
}
|
||
|
||
if (index.isMunicipality) {
|
||
return {
|
||
provinceId,
|
||
districtId: leafId,
|
||
province: index.province,
|
||
district: leaf,
|
||
};
|
||
}
|
||
|
||
const isLeafDistrict =
|
||
!isPrefectureCityLabel(leaf.label) && isDistrictLikeLabel(leaf.label);
|
||
|
||
if (isPrefectureCityLabel(leaf.label)) {
|
||
return {
|
||
provinceId,
|
||
cityId: leafId,
|
||
province: index.province,
|
||
city: leaf,
|
||
};
|
||
}
|
||
|
||
if (isLeafDistrict) {
|
||
for (const city of index.children.filter((c) =>
|
||
isPrefectureCityLabel(c.label),
|
||
)) {
|
||
const prefix = getCityNamePrefix(city.label);
|
||
if (leaf.label.startsWith(prefix)) {
|
||
return {
|
||
provinceId,
|
||
cityId: city.value,
|
||
districtId: leafId,
|
||
province: index.province,
|
||
city,
|
||
district: leaf,
|
||
};
|
||
}
|
||
}
|
||
return {
|
||
provinceId,
|
||
cityId: leafId,
|
||
province: index.province,
|
||
city: leaf,
|
||
};
|
||
}
|
||
|
||
return {
|
||
provinceId,
|
||
cityId: leafId,
|
||
province: index.province,
|
||
city: leaf,
|
||
};
|
||
}
|
||
|
||
/** 面包屑展示项 */
|
||
export function formatBreadcrumbItems(
|
||
resolved,
|
||
) {
|
||
const items = [];
|
||
if (resolved.provinceId && resolved.province) {
|
||
items.push({
|
||
level: 'province',
|
||
id: resolved.provinceId,
|
||
label: resolved.province.label,
|
||
});
|
||
}
|
||
const index = findProvinceIndex(resolved.provinceId);
|
||
if (index?.isMunicipality) {
|
||
if (resolved.districtId && resolved.district) {
|
||
items.push({
|
||
level: 'district',
|
||
id: resolved.districtId,
|
||
label: resolved.district.label,
|
||
});
|
||
}
|
||
return items;
|
||
}
|
||
if (resolved.cityId && resolved.city) {
|
||
items.push({
|
||
level: 'city',
|
||
id: resolved.cityId,
|
||
label: resolved.city.label,
|
||
});
|
||
}
|
||
if (resolved.districtId && resolved.district) {
|
||
items.push({
|
||
level: 'district',
|
||
id: resolved.districtId,
|
||
label: resolved.district.label,
|
||
});
|
||
}
|
||
return items;
|
||
}
|
||
|
||
/** 触发器展示文案 */
|
||
export function formatAddressDisplay(value) {
|
||
const items = formatBreadcrumbItems(resolveAddressValue(value));
|
||
return items.map((i) => i.label).join(' / ');
|
||
}
|
||
|
||
/** 当前层级搜索占位 */
|
||
export function getLevelSearchPlaceholder(level) {
|
||
const map = {
|
||
province: '搜索省',
|
||
city: '搜索市/县',
|
||
district: '搜索区',
|
||
};
|
||
return map[level];
|
||
}
|
||
|
||
/** 模糊筛选 */
|
||
export function filterAddressOptions(
|
||
options,
|
||
keyword,
|
||
) {
|
||
const q = keyword.trim().toLowerCase();
|
||
if (!q) {
|
||
return options;
|
||
}
|
||
return options.filter((o) => o.label.toLowerCase().includes(q));
|
||
}
|
||
|
||
/** 从已选上下文生成面包屑(面板内) */
|
||
export function buildBreadcrumbFromContext(
|
||
ctx,
|
||
) {
|
||
return formatBreadcrumbItems({
|
||
provinceId: ctx.provinceId,
|
||
cityId: ctx.cityId,
|
||
districtId: ctx.districtId,
|
||
province: findProvinceIndex(ctx.provinceId)?.province,
|
||
city: findChild(findProvinceIndex(ctx.provinceId), ctx.cityId),
|
||
district: findChild(findProvinceIndex(ctx.provinceId), ctx.districtId),
|
||
});
|
||
}
|
||
|
||
/** 根据面板内选中生成 ResolvedAddress */
|
||
export function contextToResolved(ctx) {
|
||
const index = findProvinceIndex(ctx.provinceId);
|
||
return {
|
||
provinceId: ctx.provinceId,
|
||
cityId: ctx.cityId,
|
||
districtId: ctx.districtId,
|
||
province: index?.province,
|
||
city: index ? findChild(index, ctx.cityId) : undefined,
|
||
district: index ? findChild(index, ctx.districtId) : undefined,
|
||
};
|
||
}
|
||
|
||
/** 是否直辖市 */
|
||
export function isMunicipalityProvince(provinceId) {
|
||
return !!findProvinceIndex(provinceId)?.isMunicipality;
|
||
}
|
||
|
||
/** 打开面板时的初始层级(有回填则进入待选级,否则省) */
|
||
export function getInitialPickerLevel(
|
||
resolved,
|
||
) {
|
||
const index = findProvinceIndex(resolved.provinceId);
|
||
if (!resolved.provinceId || !index) {
|
||
return 'province';
|
||
}
|
||
if (index.isMunicipality) {
|
||
return 'district';
|
||
}
|
||
if (!resolved.cityId) {
|
||
return 'city';
|
||
}
|
||
const districts = getDistricts(resolved.provinceId, resolved.cityId);
|
||
if (!resolved.districtId && districts.length > 0) {
|
||
return 'district';
|
||
}
|
||
return 'city';
|
||
}
|
||
|
||
/** 当前级候选转列表行 */
|
||
export function levelOptionsToRows(
|
||
level,
|
||
ctx,
|
||
) {
|
||
return getLevelOptions(level, ctx).map((node) => ({
|
||
level,
|
||
node,
|
||
label: node.label,
|
||
complete: canCompleteAfterSelect(
|
||
level,
|
||
level === 'city'
|
||
? { provinceId: ctx.provinceId, cityId: node.value }
|
||
: level === 'district'
|
||
? {
|
||
provinceId: ctx.provinceId,
|
||
cityId: ctx.cityId,
|
||
districtId: node.value,
|
||
}
|
||
: ctx,
|
||
node,
|
||
),
|
||
}));
|
||
}
|
||
|
||
/** 跨级模糊搜索(含第三级区/县) */
|
||
export function searchAddressHits(
|
||
keyword,
|
||
ctx,
|
||
) {
|
||
const q = keyword.trim().toLowerCase();
|
||
if (!q) {
|
||
return [];
|
||
}
|
||
|
||
const hits = [];
|
||
const seen = new Set();
|
||
|
||
const push = (hit) => {
|
||
const key = `${hit.level}-${hit.node.value}`;
|
||
if (!seen.has(key)) {
|
||
seen.add(key);
|
||
hits.push(hit);
|
||
}
|
||
};
|
||
|
||
if (!ctx.provinceId) {
|
||
for (const p of getProvinces()) {
|
||
if (p.label.toLowerCase().includes(q)) {
|
||
push({
|
||
level: 'province',
|
||
node: p,
|
||
pathLabel: p.label,
|
||
complete: false,
|
||
});
|
||
}
|
||
}
|
||
return hits;
|
||
}
|
||
|
||
const index = findProvinceIndex(ctx.provinceId);
|
||
if (!index) {
|
||
return hits;
|
||
}
|
||
|
||
if (index.isMunicipality) {
|
||
for (const d of index.children) {
|
||
if (d.label.toLowerCase().includes(q)) {
|
||
push({
|
||
level: 'district',
|
||
node: d,
|
||
pathLabel: `${index.province.label} / ${d.label}`,
|
||
complete: true,
|
||
});
|
||
}
|
||
}
|
||
return hits;
|
||
}
|
||
|
||
const cityNode = ctx.cityId ? findChild(index, ctx.cityId) : undefined;
|
||
|
||
if (!ctx.cityId) {
|
||
for (const child of index.children) {
|
||
if (!child.label.toLowerCase().includes(q)) {
|
||
continue;
|
||
}
|
||
const rowCtx = { provinceId: ctx.provinceId, cityId: child.value };
|
||
push({
|
||
level: 'city',
|
||
node: child,
|
||
pathLabel: `${index.province.label} / ${child.label}`,
|
||
complete: canCompleteAfterSelect('city', rowCtx, child),
|
||
});
|
||
}
|
||
for (const child of index.children.filter((c) => isPrefectureCityLabel(c.label))) {
|
||
for (const d of getDistricts(ctx.provinceId, child.value)) {
|
||
if (!d.label.toLowerCase().includes(q)) {
|
||
continue;
|
||
}
|
||
push({
|
||
level: 'district',
|
||
node: d,
|
||
pathLabel: `${index.province.label} / ${child.label} / ${d.label}`,
|
||
complete: true,
|
||
});
|
||
}
|
||
}
|
||
return hits;
|
||
}
|
||
|
||
if (cityNode && cityNode.label.toLowerCase().includes(q)) {
|
||
push({
|
||
level: 'city',
|
||
node: cityNode,
|
||
pathLabel: `${index.province.label} / ${cityNode.label}`,
|
||
complete: canCompleteAfterSelect('city', ctx, cityNode),
|
||
});
|
||
}
|
||
|
||
for (const d of getDistricts(ctx.provinceId, ctx.cityId)) {
|
||
if (d.label.toLowerCase().includes(q)) {
|
||
push({
|
||
level: 'district',
|
||
node: d,
|
||
pathLabel: `${index.province.label} / ${cityNode?.label ?? ''} / ${d.label}`,
|
||
complete: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
return hits;
|
||
}
|
||
|
||
/** 搜索命中转列表行 */
|
||
export function searchHitsToRows(hits) {
|
||
return hits.map((hit) => ({
|
||
level: hit.level,
|
||
node: hit.node,
|
||
label: hit.pathLabel,
|
||
complete: hit.complete,
|
||
}));
|
||
}
|
||
|
||
/** 应用跨级搜索选中 */
|
||
export function applySearchHit(
|
||
hit,
|
||
ctx,
|
||
) {
|
||
if (hit.level === 'province') {
|
||
return {
|
||
provinceId: hit.node.value,
|
||
cityId: undefined,
|
||
districtId: undefined,
|
||
};
|
||
}
|
||
if (hit.level === 'city') {
|
||
return {
|
||
provinceId: ctx.provinceId,
|
||
cityId: hit.node.value,
|
||
districtId: undefined,
|
||
};
|
||
}
|
||
if (hit.level === 'district' && findProvinceIndex(ctx.provinceId)?.isMunicipality) {
|
||
return {
|
||
provinceId: ctx.provinceId,
|
||
cityId: undefined,
|
||
districtId: hit.node.value,
|
||
};
|
||
}
|
||
return {
|
||
provinceId: ctx.provinceId,
|
||
cityId: ctx.cityId,
|
||
districtId: hit.node.value,
|
||
};
|
||
}
|
||
|
||
/** 高亮项是否有下一级 */
|
||
export function rowHasNextLevel(
|
||
row,
|
||
ctx,
|
||
) {
|
||
if (row.level === 'province') {
|
||
return !!getNextLevel('province', {
|
||
provinceId: row.node.value,
|
||
cityId: undefined,
|
||
districtId: undefined,
|
||
});
|
||
}
|
||
if (row.level === 'city') {
|
||
return !!getNextLevel('city', {
|
||
provinceId: ctx.provinceId,
|
||
cityId: row.node.value,
|
||
districtId: undefined,
|
||
});
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** 点击面包屑项后截断选中并返回新上下文 */
|
||
export function truncateContextToBreadcrumb(
|
||
ctx,
|
||
item,
|
||
) {
|
||
if (item.level === 'province') {
|
||
return {
|
||
provinceId: item.id,
|
||
cityId: undefined,
|
||
districtId: undefined,
|
||
};
|
||
}
|
||
if (item.level === 'city') {
|
||
return {
|
||
provinceId: ctx.provinceId,
|
||
cityId: item.id,
|
||
districtId: undefined,
|
||
};
|
||
}
|
||
return {
|
||
provinceId: ctx.provinceId,
|
||
cityId: ctx.cityId,
|
||
districtId: item.id,
|
||
};
|
||
}
|