update at 2026-01-22 18:43:01
This commit is contained in:
@@ -2,87 +2,142 @@
|
||||
* 罗盘业务逻辑组合函数
|
||||
*/
|
||||
|
||||
import { computed, type Ref, type ComputedRef } from 'vue';
|
||||
import type { Example, Sector, TextRadialPosition } from '../types';
|
||||
import {
|
||||
polarToXY,
|
||||
generateSectorData,
|
||||
} from '../utils';
|
||||
import { computed, ref, readonly, watch, type Ref } from 'vue';
|
||||
import type {
|
||||
CenterIconData,
|
||||
DegreeRingData,
|
||||
LayerConfig,
|
||||
LuopanConfig,
|
||||
Sector,
|
||||
SectorLayerConfig,
|
||||
TextRadialPosition,
|
||||
} from '../types';
|
||||
import { polarToXY } from '../utils';
|
||||
import { parseConfig } from '../configParser';
|
||||
import { ColorResolver } from '../colorResolver';
|
||||
import { SectorBuilder } from '../sectorBuilder';
|
||||
import { buildDegreeRing } from '../degreeRing';
|
||||
import { loadCenterIcon } from '../centerIcon';
|
||||
|
||||
const isSectorLayer = (layer: LayerConfig): layer is SectorLayerConfig =>
|
||||
layer.type !== 'centerIcon' && layer.type !== 'degreeRing';
|
||||
|
||||
const findDegreeRingLayer = (layers: LayerConfig[]) =>
|
||||
layers.find((layer) => layer.type === 'degreeRing');
|
||||
|
||||
const findCenterIconLayer = (layers: LayerConfig[]) =>
|
||||
layers.find((layer) => layer.type === 'centerIcon');
|
||||
|
||||
/**
|
||||
* 罗盘逻辑 Hook
|
||||
* @param exampleRef 当前示例的响应式引用
|
||||
* @param textRadialPositionRef 文字径向位置的响应式引用
|
||||
* @param configPathOrObject 配置文件路径或配置对象
|
||||
* @param textRadialPositionRef 文字径向位置的响应式引用(可选)
|
||||
* @returns 罗盘相关的计算属性和方法
|
||||
*/
|
||||
export function useLuopan(
|
||||
exampleRef: Ref<Example>,
|
||||
textRadialPositionRef: Ref<TextRadialPosition>
|
||||
configPathOrObject: string | LuopanConfig,
|
||||
textRadialPositionRef?: Ref<TextRadialPosition>
|
||||
) {
|
||||
/**
|
||||
* 角度分割点列表
|
||||
*/
|
||||
const anglesDeg = computed(() => exampleRef.value.angles);
|
||||
const config = ref<LuopanConfig | null>(null);
|
||||
const sectors = ref<Sector[]>([]);
|
||||
const degreeRing = ref<DegreeRingData | null>(null);
|
||||
const centerIcon = ref<CenterIconData | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref<Error | null>(null);
|
||||
|
||||
/**
|
||||
* 圆环半径列表
|
||||
*/
|
||||
const rings = computed(() => exampleRef.value.radii);
|
||||
const textRadialPosition = computed(
|
||||
() => textRadialPositionRef?.value ?? 'middle'
|
||||
);
|
||||
|
||||
/**
|
||||
* 最外层半径
|
||||
*/
|
||||
const outerMost = computed(() => {
|
||||
const radii = exampleRef.value.radii;
|
||||
return radii[radii.length - 1];
|
||||
});
|
||||
const buildSectors = (configObj: LuopanConfig) => {
|
||||
const resolver = new ColorResolver(configObj.theme, configObj.background);
|
||||
const builder = new SectorBuilder(resolver, {
|
||||
textRadialPosition: textRadialPosition.value,
|
||||
});
|
||||
const sectorLayers = configObj.layers.filter(isSectorLayer);
|
||||
return sectorLayers.flatMap((layer, index) => builder.buildLayer(layer, index));
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成所有扇区数据
|
||||
*/
|
||||
const sectors = computed<Sector[]>(() => {
|
||||
const res: Sector[] = [];
|
||||
const A = exampleRef.value.angles;
|
||||
const R = exampleRef.value.radii;
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const layerCount = R.length;
|
||||
const pieCount = A.length - 1;
|
||||
|
||||
for (let j = 0; j < layerCount; j++) {
|
||||
const rInner = j === 0 ? 0 : R[j - 1];
|
||||
const rOuter = R[j];
|
||||
|
||||
for (let i = 0; i < pieCount; i++) {
|
||||
const aStart = A[i];
|
||||
const aEnd = A[i + 1];
|
||||
|
||||
const sector = generateSectorData({
|
||||
layerIndex: j,
|
||||
pieIndex: i,
|
||||
rInner,
|
||||
rOuter,
|
||||
aStart,
|
||||
aEnd,
|
||||
textRadialPosition: textRadialPositionRef.value,
|
||||
});
|
||||
|
||||
res.push(sector);
|
||||
let configObj: LuopanConfig;
|
||||
if (typeof configPathOrObject === 'string') {
|
||||
const jsonText = await fetch(configPathOrObject).then((res) => res.text());
|
||||
configObj = parseConfig(jsonText);
|
||||
} else {
|
||||
configObj = configPathOrObject;
|
||||
}
|
||||
|
||||
config.value = configObj;
|
||||
sectors.value = buildSectors(configObj);
|
||||
|
||||
const degreeRingLayer = findDegreeRingLayer(configObj.layers);
|
||||
degreeRing.value = degreeRingLayer
|
||||
? buildDegreeRing(degreeRingLayer.degreeRing)
|
||||
: null;
|
||||
|
||||
const centerIconLayer = findCenterIconLayer(configObj.layers);
|
||||
centerIcon.value = centerIconLayer
|
||||
? await loadCenterIcon(centerIconLayer.centerIcon)
|
||||
: null;
|
||||
} catch (err) {
|
||||
error.value = err as Error;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 文字位置切换后仅重建扇区
|
||||
watch(textRadialPosition, () => {
|
||||
if (config.value) {
|
||||
sectors.value = buildSectors(config.value);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
/**
|
||||
* 极坐标转 XY(暴露给模板使用)
|
||||
*/
|
||||
const toXY = polarToXY;
|
||||
loadConfig();
|
||||
|
||||
const sectorLayers = computed(() =>
|
||||
config.value ? config.value.layers.filter(isSectorLayer) : []
|
||||
);
|
||||
|
||||
const rings = computed(() => sectorLayers.value.map((layer) => layer.rOuter));
|
||||
|
||||
const anglesDeg = computed(() => {
|
||||
const firstLayer = sectorLayers.value[0];
|
||||
if (!firstLayer || firstLayer.divisions <= 0) return [];
|
||||
|
||||
const step = 360 / firstLayer.divisions;
|
||||
const start = firstLayer.startAngle ?? 0;
|
||||
return Array.from({ length: firstLayer.divisions + 1 }, (_, i) => start + i * step);
|
||||
});
|
||||
|
||||
const outerMost = computed(() => {
|
||||
if (!config.value) return 0;
|
||||
if (typeof config.value.outerRadius === 'number') return config.value.outerRadius;
|
||||
|
||||
const radii = sectorLayers.value.map((layer) => layer.rOuter);
|
||||
const degreeRingLayer = findDegreeRingLayer(config.value.layers);
|
||||
if (degreeRingLayer) {
|
||||
radii.push(degreeRingLayer.degreeRing.rOuter);
|
||||
}
|
||||
return radii.length > 0 ? Math.max(...radii) : 0;
|
||||
});
|
||||
|
||||
return {
|
||||
config: readonly(config),
|
||||
sectors: readonly(sectors),
|
||||
degreeRing: readonly(degreeRing),
|
||||
centerIcon: readonly(centerIcon),
|
||||
anglesDeg,
|
||||
rings,
|
||||
outerMost,
|
||||
sectors,
|
||||
toXY,
|
||||
toXY: polarToXY,
|
||||
loading: readonly(loading),
|
||||
error: readonly(error),
|
||||
reload: loadConfig,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user