feat: migrate app routes and business modules
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<RegionPickerSheet
|
||||
:visible="visible"
|
||||
:columns="columns"
|
||||
:indexes="indexes"
|
||||
:indicator-style="indicatorStyle"
|
||||
:close-on-mask="closeOnMask"
|
||||
@change="changeSelection"
|
||||
@cancel="close"
|
||||
@confirm="confirmSelection"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onUnmounted, ref } from "vue";
|
||||
import RegionPickerSheet from "@/components/genealogy/RegionPickerSheet.vue";
|
||||
import {
|
||||
createRequestController,
|
||||
isRequestCancelled
|
||||
} from "@/services/api/request-controller.js";
|
||||
import { regionApi } from "@/services/api/region-service.js";
|
||||
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
|
||||
|
||||
const props = defineProps({
|
||||
closeOnMask: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxLevels: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
validator: (levelCount) => Number.isInteger(levelCount) && levelCount > 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"error-change",
|
||||
"loading-change",
|
||||
"select",
|
||||
"transient-change",
|
||||
]);
|
||||
|
||||
const indicatorStyle =
|
||||
"height: 104rpx; border-top: 1px solid rgba(159, 23, 15, .46); border-bottom: 1px solid rgba(159, 23, 15, .46); background: rgba(159, 23, 15, .08);";
|
||||
const visible = ref(false);
|
||||
const columns = ref([]);
|
||||
const indexes = ref([0]);
|
||||
const selectedTrail = ref([]);
|
||||
const preparedRegionCode = ref("");
|
||||
const regionRequestController = createRequestController();
|
||||
let componentActive = true;
|
||||
let loading = false;
|
||||
|
||||
const setLoading = (nextLoading) => {
|
||||
loading = nextLoading;
|
||||
emit("loading-change", nextLoading);
|
||||
};
|
||||
const setError = (message = "") => {
|
||||
emit("error-change", message);
|
||||
};
|
||||
const fetchRegionChildren = (parentCode) =>
|
||||
regionApi.getRegionChildren(parentCode, {
|
||||
requestController: regionRequestController,
|
||||
});
|
||||
|
||||
const loadColumns = async (requestedIndexes = []) => {
|
||||
const rootOptions = columns.value[0] || (await fetchRegionChildren("0"));
|
||||
if (!rootOptions.length) return false;
|
||||
const nextColumns = [rootOptions];
|
||||
const nextIndexes = [];
|
||||
let options = rootOptions;
|
||||
for (let level = 0; level < props.maxLevels; level += 1) {
|
||||
const requestedIndex = Number(requestedIndexes[level]);
|
||||
const selectedIndex = Number.isInteger(requestedIndex)
|
||||
? Math.min(Math.max(requestedIndex, 0), options.length - 1)
|
||||
: 0;
|
||||
const selectedOption = options[selectedIndex];
|
||||
if (!selectedOption) break;
|
||||
nextIndexes.push(selectedIndex);
|
||||
if (level === props.maxLevels - 1) break;
|
||||
const childOptions = await fetchRegionChildren(selectedOption.regionCode);
|
||||
if (!childOptions.length) break;
|
||||
nextColumns.push(childOptions);
|
||||
options = childOptions;
|
||||
}
|
||||
if (!componentActive) return false;
|
||||
columns.value = nextColumns;
|
||||
indexes.value = nextIndexes;
|
||||
selectedTrail.value = nextColumns
|
||||
.map((column, level) => column[nextIndexes[level]])
|
||||
.filter(Boolean);
|
||||
return Boolean(selectedTrail.value.length);
|
||||
};
|
||||
|
||||
const loadRegionPath = async (regionCode) => {
|
||||
const regionPath = (await regionApi.getRegionPath(regionCode, {
|
||||
requestController: regionRequestController,
|
||||
})).filter((regionNode) => regionNode?.regionCode);
|
||||
if (
|
||||
!regionPath.length ||
|
||||
regionPath[regionPath.length - 1].regionCode !== regionCode
|
||||
) {
|
||||
throw new Error("REGION_PATH_MISMATCH");
|
||||
}
|
||||
const nextColumns = [];
|
||||
const nextIndexes = [];
|
||||
const nextTrail = [];
|
||||
let parentCode = "0";
|
||||
for (const regionPathNode of regionPath.slice(0, props.maxLevels)) {
|
||||
const options = await fetchRegionChildren(parentCode);
|
||||
const selectedIndex = options.findIndex(
|
||||
(option) => option.regionCode === regionPathNode.regionCode,
|
||||
);
|
||||
if (selectedIndex < 0) throw new Error("REGION_PATH_MISMATCH");
|
||||
nextColumns.push(options);
|
||||
nextIndexes.push(selectedIndex);
|
||||
nextTrail.push(options[selectedIndex]);
|
||||
parentCode = regionPathNode.regionCode;
|
||||
}
|
||||
if (!componentActive) return false;
|
||||
columns.value = nextColumns;
|
||||
indexes.value = nextIndexes;
|
||||
selectedTrail.value = nextTrail;
|
||||
return Boolean(nextTrail.length);
|
||||
};
|
||||
|
||||
const prepare = async (initialRegionCode = "") => {
|
||||
const normalizedRegionCode = String(initialRegionCode || "");
|
||||
if (loading) return false;
|
||||
if (
|
||||
columns.value.length &&
|
||||
preparedRegionCode.value === normalizedRegionCode
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
regionRequestController.abort();
|
||||
setLoading(true);
|
||||
setError();
|
||||
try {
|
||||
const ready = normalizedRegionCode
|
||||
? await loadRegionPath(normalizedRegionCode)
|
||||
: await loadColumns([0]);
|
||||
if (!componentActive || !ready) return false;
|
||||
preparedRegionCode.value = normalizedRegionCode;
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (!componentActive || isRequestCancelled(error)) return false;
|
||||
const fallback = normalizedRegionCode
|
||||
? "暂时无法定位当前地区,请稍后重试。"
|
||||
: "地区列表加载失败,请重试";
|
||||
setError(getRequestErrorMessage(error, fallback));
|
||||
return false;
|
||||
} finally {
|
||||
if (componentActive) setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const open = async (initialRegionCode = "") => {
|
||||
if (!(await prepare(initialRegionCode))) return false;
|
||||
visible.value = true;
|
||||
emit("transient-change", true);
|
||||
return true;
|
||||
};
|
||||
const close = () => {
|
||||
visible.value = false;
|
||||
emit("transient-change", false);
|
||||
};
|
||||
const changeSelection = async (event) => {
|
||||
if (loading) return;
|
||||
const requestedIndexes = (event?.detail?.value || []).map(
|
||||
(index) => Number(index) || 0,
|
||||
);
|
||||
const changedLevel = requestedIndexes.findIndex(
|
||||
(index, level) => index !== (indexes.value[level] || 0),
|
||||
);
|
||||
if (changedLevel < 0) return;
|
||||
setLoading(true);
|
||||
setError();
|
||||
try {
|
||||
const loaded = await loadColumns(requestedIndexes.slice(0, changedLevel + 1));
|
||||
if (!componentActive || !loaded) return;
|
||||
preparedRegionCode.value = "";
|
||||
} catch (error) {
|
||||
if (!componentActive || isRequestCancelled(error)) return;
|
||||
setError(getRequestErrorMessage(error, "地区列表加载失败,请重试"));
|
||||
} finally {
|
||||
if (componentActive) setLoading(false);
|
||||
}
|
||||
};
|
||||
const confirmSelection = () => {
|
||||
const trail = columns.value
|
||||
.map((column, level) => column[Number(indexes.value[level])])
|
||||
.filter(Boolean);
|
||||
const region = trail[trail.length - 1];
|
||||
if (!region) return;
|
||||
selectedTrail.value = trail;
|
||||
preparedRegionCode.value = region.regionCode;
|
||||
emit("select", { region, trail });
|
||||
close();
|
||||
};
|
||||
|
||||
defineExpose({ close, open, prepare });
|
||||
|
||||
onUnmounted(() => {
|
||||
componentActive = false;
|
||||
regionRequestController.abort();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user