计划表改进、添加计划数据协调器和计划名称工具类

引入了usePlanDataCoordinator组件式函数(composable),用于实现计划数据的统一管理与同步。
添加了planNameUtils工具类,用于生成唯一计划名称并进行有效性校验。
重构了plan/index.vue和MaaPlanTable.vue两个页面组件,使其适配上述工具;同时优化了计划切换、名称编辑及自定义阶段管理的功能。
This commit is contained in:
Zrief
2025-10-02 22:44:17 +08:00
parent 993524c4dd
commit f4bcb73fe9
4 changed files with 1022 additions and 733 deletions

View File

@@ -0,0 +1,531 @@
/**
* 计划表数据协调层
*
* 作为前端架构中的"交通指挥中心",负责:
* 1. 统一管理数据流
* 2. 协调视图间的同步
* 3. 处理与后端的通信
* 4. 提供统一的数据访问接口
*/
import { ref, computed } from 'vue'
import type { MaaPlanConfig, MaaPlanConfig_Item } from '@/api'
// 时间维度常量
export const TIME_KEYS = ['ALL', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] as const
export type TimeKey = typeof TIME_KEYS[number]
// 关卡槽位常量
export const STAGE_SLOTS = ['Stage', 'Stage_1', 'Stage_2', 'Stage_3'] as const
export type StageSlot = typeof STAGE_SLOTS[number]
// 统一的数据结构
export interface PlanDataState {
// 基础信息
info: {
name: string
mode: 'ALL' | 'Weekly'
type: string
}
// 时间维度的配置数据
timeConfigs: Record<TimeKey, {
medicineNumb: number
seriesNumb: string
stages: {
primary: string // Stage
backup1: string // Stage_1
backup2: string // Stage_2
backup3: string // Stage_3
remain: string // Stage_Remain
}
}>
// 自定义关卡定义
customStageDefinitions: {
custom_stage_1: string
custom_stage_2: string
custom_stage_3: string
}
}
// 关卡可用性信息
export interface StageAvailability {
value: string
text: string
days: number[]
}
export const STAGE_DAILY_INFO: StageAvailability[] = [
{ value: '-', text: '当前/上次', days: [1, 2, 3, 4, 5, 6, 7] },
{ value: '1-7', text: '1-7', days: [1, 2, 3, 4, 5, 6, 7] },
{ value: 'R8-11', text: 'R8-11', days: [1, 2, 3, 4, 5, 6, 7] },
{ value: '12-17-HARD', text: '12-17-HARD', days: [1, 2, 3, 4, 5, 6, 7] },
{ value: 'LS-6', text: '经验-6/5', days: [1, 2, 3, 4, 5, 6, 7] },
{ value: 'CE-6', text: '龙门币-6/5', days: [2, 4, 6, 7] },
{ value: 'AP-5', text: '红票-5', days: [1, 4, 6, 7] },
{ value: 'CA-5', text: '技能-5', days: [2, 3, 5, 7] },
{ value: 'SK-5', text: '碳-5', days: [1, 3, 5, 6] },
{ value: 'PR-A-1', text: '奶/盾芯片', days: [1, 4, 5, 7] },
{ value: 'PR-A-2', text: '奶/盾芯片组', days: [1, 4, 5, 7] },
{ value: 'PR-B-1', text: '术/狙芯片', days: [1, 2, 5, 6] },
{ value: 'PR-B-2', text: '术/狙芯片组', days: [1, 2, 5, 6] },
{ value: 'PR-C-1', text: '先/辅芯片', days: [3, 4, 6, 7] },
{ value: 'PR-C-2', text: '先/辅芯片组', days: [3, 4, 6, 7] },
{ value: 'PR-D-1', text: '近/特芯片', days: [2, 3, 6, 7] },
{ value: 'PR-D-2', text: '近/特芯片组', days: [2, 3, 6, 7] },
]
/**
* 计划表数据协调器
*/
export function usePlanDataCoordinator() {
// 当前计划表ID
const currentPlanId = ref<string>('default')
// localStorage 相关函数
const CUSTOM_STAGE_KEY_PREFIX = 'maa_custom_stage_definitions_'
const getStorageKey = (): string => {
return `${CUSTOM_STAGE_KEY_PREFIX}${currentPlanId.value}`
}
const getDefaultCustomStageDefinitions = () => ({
custom_stage_1: '',
custom_stage_2: '',
custom_stage_3: '',
})
const loadCustomStageDefinitionsFromStorage = () => {
try {
const storageKey = getStorageKey()
const stored = localStorage.getItem(storageKey)
if (stored) {
const parsed = JSON.parse(stored)
// 确保包含所有必需的键
return {
custom_stage_1: parsed.custom_stage_1 || '',
custom_stage_2: parsed.custom_stage_2 || '',
custom_stage_3: parsed.custom_stage_3 || '',
}
}
} catch (error) {
console.error('[自定义关卡] localStorage 恢复失败:', error)
}
return getDefaultCustomStageDefinitions()
}
const saveCustomStageDefinitionsToStorage = (definitions: Record<string, string>) => {
try {
const storageKey = getStorageKey()
localStorage.setItem(storageKey, JSON.stringify(definitions))
// 只在开发环境输出详细日志
if (process.env.NODE_ENV === 'development') {
console.log(`[自定义关卡] 保存到 localStorage (${currentPlanId.value})`)
}
} catch (error) {
console.error('[自定义关卡] localStorage 保存失败:', error)
}
}
// 单一数据源
const planData = ref<PlanDataState>({
info: {
name: '',
mode: 'ALL',
type: 'MaaPlanConfig'
},
timeConfigs: {} as Record<TimeKey, any>,
customStageDefinitions: loadCustomStageDefinitionsFromStorage()
})
// 初始化时间配置
const initializeTimeConfigs = () => {
TIME_KEYS.forEach(timeKey => {
planData.value.timeConfigs[timeKey] = {
medicineNumb: 0,
seriesNumb: '0',
stages: {
primary: '-',
backup1: '-',
backup2: '-',
backup3: '-',
remain: '-'
}
}
})
}
// 初始化数据
initializeTimeConfigs()
// 从API数据转换为内部数据结构
const fromApiData = (apiData: MaaPlanConfig) => {
// 更新基础信息
if (apiData.Info) {
planData.value.info.name = apiData.Info.Name || ''
planData.value.info.mode = apiData.Info.Mode || 'ALL'
// 如果API数据中包含计划表ID信息更新当前planId
// 注意这里假设planId通过其他方式传入API数据本身可能不包含ID
}
// 更新时间配置
TIME_KEYS.forEach(timeKey => {
const timeData = apiData[timeKey] as MaaPlanConfig_Item
if (timeData) {
planData.value.timeConfigs[timeKey] = {
medicineNumb: timeData.MedicineNumb || 0,
seriesNumb: timeData.SeriesNumb || '0',
stages: {
primary: timeData.Stage || '-',
backup1: timeData.Stage_1 || '-',
backup2: timeData.Stage_2 || '-',
backup3: timeData.Stage_3 || '-',
remain: timeData.Stage_Remain || '-'
}
}
}
})
// 更新自定义关卡定义
const customStages = (apiData.ALL as any)?.customStageDefinitions
if (customStages && typeof customStages === 'object') {
// 只在开发环境输出详细日志
if (process.env.NODE_ENV === 'development') {
console.log(`[自定义关卡] 从后端数据恢复 (${currentPlanId.value})`)
}
const newDefinitions = {
custom_stage_1: customStages.custom_stage_1 || '',
custom_stage_2: customStages.custom_stage_2 || '',
custom_stage_3: customStages.custom_stage_3 || '',
}
// 只有当定义真的不同时才更新和保存
const hasChanged = JSON.stringify(newDefinitions) !== JSON.stringify(planData.value.customStageDefinitions)
if (hasChanged) {
planData.value.customStageDefinitions = newDefinitions
// 同步到 localStorage
saveCustomStageDefinitionsToStorage(planData.value.customStageDefinitions)
}
} else {
// 只在开发环境输出日志
if (process.env.NODE_ENV === 'development') {
console.log(`[自定义关卡] 使用 localStorage 数据 (${currentPlanId.value})`)
}
// 如果后端没有自定义关卡定义,使用 localStorage 中的值
const storedDefinitions = loadCustomStageDefinitionsFromStorage()
planData.value.customStageDefinitions = storedDefinitions
}
}
// 转换为API数据格式
const toApiData = (): MaaPlanConfig => {
const result: MaaPlanConfig = {
Info: {
Name: planData.value.info.name,
Mode: planData.value.info.mode
}
}
TIME_KEYS.forEach(timeKey => {
const config = planData.value.timeConfigs[timeKey]
result[timeKey] = {
MedicineNumb: config.medicineNumb,
SeriesNumb: config.seriesNumb as any,
Stage: config.stages.primary,
Stage_1: config.stages.backup1,
Stage_2: config.stages.backup2,
Stage_3: config.stages.backup3,
Stage_Remain: config.stages.remain
}
})
// 在ALL中包含自定义关卡定义
if (result.ALL) {
(result.ALL as any).customStageDefinitions = planData.value.customStageDefinitions
}
return result
}
// 配置视图数据适配器
const configViewData = computed(() => {
return [
{
key: 'MedicineNumb',
taskName: '吃理智药',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.medicineNumb || 0
])
)
},
{
key: 'SeriesNumb',
taskName: '连战次数',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.seriesNumb || '0'
])
)
},
{
key: 'Stage',
taskName: '关卡选择',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.stages.primary || '-'
])
)
},
{
key: 'Stage_1',
taskName: '备选关卡-1',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.stages.backup1 || '-'
])
)
},
{
key: 'Stage_2',
taskName: '备选关卡-2',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.stages.backup2 || '-'
])
)
},
{
key: 'Stage_3',
taskName: '备选关卡-3',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.stages.backup3 || '-'
])
)
},
{
key: 'Stage_Remain',
taskName: '剩余理智关卡',
...Object.fromEntries(
TIME_KEYS.map(timeKey => [
timeKey,
planData.value.timeConfigs[timeKey]?.stages.remain || '-'
])
)
}
]
})
// 简化视图数据适配器
const simpleViewData = computed(() => {
const result: any[] = []
// 添加自定义关卡
Object.entries(planData.value.customStageDefinitions).forEach(([, stageName]) => {
if (stageName && stageName.trim()) {
const stageStates: Record<string, boolean> = {}
TIME_KEYS.forEach(timeKey => {
const config = planData.value.timeConfigs[timeKey]
stageStates[timeKey] = Object.values(config.stages).includes(stageName)
})
result.push({
key: stageName,
taskName: stageName,
isCustom: true,
stageName: stageName,
...stageStates
})
}
})
// 添加标准关卡
STAGE_DAILY_INFO.filter(stage => stage.value !== '-').forEach(stage => {
const stageStates: Record<string, boolean> = {}
TIME_KEYS.forEach(timeKey => {
const config = planData.value.timeConfigs[timeKey]
stageStates[timeKey] = Object.values(config.stages).includes(stage.value)
})
result.push({
key: stage.value,
taskName: stage.text,
isCustom: false,
stageName: stage.value,
...stageStates
})
})
return result
})
// 更新配置数据
const updateConfig = (timeKey: TimeKey, field: string, value: any) => {
if (field === 'MedicineNumb') {
planData.value.timeConfigs[timeKey].medicineNumb = value
} else if (field === 'SeriesNumb') {
planData.value.timeConfigs[timeKey].seriesNumb = value
} else if (field === 'Stage') {
planData.value.timeConfigs[timeKey].stages.primary = value
} else if (field === 'Stage_1') {
planData.value.timeConfigs[timeKey].stages.backup1 = value
} else if (field === 'Stage_2') {
planData.value.timeConfigs[timeKey].stages.backup2 = value
} else if (field === 'Stage_3') {
planData.value.timeConfigs[timeKey].stages.backup3 = value
} else if (field === 'Stage_Remain') {
planData.value.timeConfigs[timeKey].stages.remain = value
}
}
// 切换关卡状态(简化视图用)
const toggleStage = (stageName: string, timeKey: TimeKey, enabled: boolean) => {
const config = planData.value.timeConfigs[timeKey]
const stageSlots = ['primary', 'backup1', 'backup2', 'backup3'] as const
if (enabled) {
// 找到第一个空槽位
const emptySlot = stageSlots.find(slot =>
!config.stages[slot] || config.stages[slot] === '-'
)
if (emptySlot) {
config.stages[emptySlot] = stageName
}
// 启用后重新按简化视图顺序排列
reassignSlotsBySimpleViewOrder(timeKey)
} else {
// 从所有槽位中移除
stageSlots.forEach(slot => {
if (config.stages[slot] === stageName) {
config.stages[slot] = '-'
}
})
// 移除后重新按简化视图顺序排列
reassignSlotsBySimpleViewOrder(timeKey)
}
}
// 按简化视图顺序重新分配槽位
const reassignSlotsBySimpleViewOrder = (timeKey: TimeKey) => {
const config = planData.value.timeConfigs[timeKey]
const stageSlots = ['primary', 'backup1', 'backup2', 'backup3'] as const
// 收集当前已启用的关卡
const enabledStages = Object.values(config.stages).filter(stage => stage && stage !== '-')
// 清空所有槽位
stageSlots.forEach(slot => {
config.stages[slot] = '-'
})
// 按简化视图的实际显示顺序重新分配
const sortedStages: string[] = []
// 1. 先添加自定义关卡(按 custom_stage_1, custom_stage_2, custom_stage_3 的顺序)
for (let i = 1; i <= 3; i++) {
const key = `custom_stage_${i}` as keyof typeof planData.value.customStageDefinitions
const stageName = planData.value.customStageDefinitions[key]
if (stageName && stageName.trim() && enabledStages.includes(stageName)) {
sortedStages.push(stageName)
}
}
// 2. 再添加标准关卡按STAGE_DAILY_INFO的顺序跳过'-'
STAGE_DAILY_INFO.filter(stage => stage.value !== '-').forEach(stage => {
if (enabledStages.includes(stage.value)) {
sortedStages.push(stage.value)
}
})
// 3. 按顺序分配到槽位第1个→primary第2个→backup1第3个→backup2第4个→backup3
sortedStages.forEach((stageName, index) => {
if (index < stageSlots.length) {
config.stages[stageSlots[index]] = stageName
}
})
// 只在开发环境输出排序日志
if (process.env.NODE_ENV === 'development') {
console.log(`[关卡排序] ${timeKey}:`, sortedStages.join(' → '))
}
}
// 更新自定义关卡定义
const updateCustomStageDefinition = (index: 1 | 2 | 3, name: string) => {
const key = `custom_stage_${index}` as keyof typeof planData.value.customStageDefinitions
const oldName = planData.value.customStageDefinitions[key]
// 只在开发环境输出详细日志
if (process.env.NODE_ENV === 'development') {
console.log(`[自定义关卡] 保存关卡-${index}: "${oldName}" -> "${name}"`)
}
planData.value.customStageDefinitions[key] = name
// 保存到 localStorage
saveCustomStageDefinitionsToStorage(planData.value.customStageDefinitions)
// 如果名称改变了,需要更新所有引用
if (oldName !== name) {
TIME_KEYS.forEach(timeKey => {
const config = planData.value.timeConfigs[timeKey]
Object.keys(config.stages).forEach(stageKey => {
if (config.stages[stageKey as keyof typeof config.stages] === oldName) {
config.stages[stageKey as keyof typeof config.stages] = name || '-'
}
})
})
}
}
// 更新计划表ID
const updatePlanId = (newPlanId: string) => {
if (currentPlanId.value !== newPlanId) {
// 只在开发环境输出日志
if (process.env.NODE_ENV === 'development') {
console.log(`[自定义关卡] 计划表切换: ${currentPlanId.value} -> ${newPlanId}`)
}
currentPlanId.value = newPlanId
// 重新加载自定义关卡定义
const newDefinitions = loadCustomStageDefinitionsFromStorage()
planData.value.customStageDefinitions = newDefinitions
}
}
return {
// 数据
planData: planData.value,
// 视图适配器
configViewData,
simpleViewData,
// 数据转换
fromApiData,
toApiData,
// 数据操作
updateConfig,
toggleStage,
updateCustomStageDefinition,
updatePlanId,
// 工具函数
initializeTimeConfigs
}
}

View File

@@ -0,0 +1,91 @@
/**
* 计划表名称管理工具函数
*/
export interface PlanNameValidationResult {
isValid: boolean
message?: string
}
/**
* 生成唯一的计划表名称(使用数字后缀)
* @param planType 计划表类型
* @param existingNames 已存在的名称列表
* @returns 唯一的计划表名称
*/
export function generateUniquePlanName(planType: string, existingNames: string[]): string {
const baseNames = {
MaaPlanConfig: '新 MAA 计划表',
GeneralPlan: '新通用计划表',
CustomPlan: '新自定义计划表',
} as Record<string, string>
const baseName = baseNames[planType] || '新计划表'
// 如果基础名称没有被使用,直接返回
if (!existingNames.includes(baseName)) {
return baseName
}
// 查找可用的编号
let counter = 2
let candidateName = `${baseName} ${counter}`
while (existingNames.includes(candidateName)) {
counter++
candidateName = `${baseName} ${counter}`
}
return candidateName
}
/**
* 验证计划表名称是否可用
* @param newName 新名称
* @param existingNames 已存在的名称列表
* @param currentName 当前名称(编辑时排除自己)
* @returns 验证结果
*/
export function validatePlanName(
newName: string,
existingNames: string[],
currentName?: string
): PlanNameValidationResult {
// 检查名称是否为空
if (!newName || !newName.trim()) {
return { isValid: false, message: '计划表名称不能为空' }
}
const trimmedName = newName.trim()
// 检查名称长度
if (trimmedName.length > 50) {
return { isValid: false, message: '计划表名称不能超过50个字符' }
}
// 检查是否与其他计划表重名(排除当前名称)
const isDuplicate = existingNames.some(name =>
name === trimmedName && name !== currentName
)
if (isDuplicate) {
return { isValid: false, message: '计划表名称已存在,请使用其他名称' }
}
return { isValid: true }
}
/**
* 获取计划表类型的显示标签
* @param planType 计划表类型
* @returns 显示标签
*/
export function getPlanTypeLabel(planType: string): string {
const labels = {
MaaPlanConfig: 'MAA计划表',
GeneralPlan: '通用计划表',
CustomPlan: '自定义计划表',
} as Record<string, string>
return labels[planType] || '计划表'
}

View File

@@ -57,6 +57,7 @@
:current-mode="currentMode"
:view-mode="viewMode"
:options-loaded="!loading"
:plan-id="activePlanId"
@update-table-data="handleTableDataUpdate"
/>
</PlanConfig>
@@ -70,6 +71,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { message } from 'ant-design-vue'
import { usePlanApi } from '@/composables/usePlanApi'
import { generateUniquePlanName, validatePlanName, getPlanTypeLabel } from '@/utils/planNameUtils'
import PlanHeader from './components/PlanHeader.vue'
import PlanSelector from './components/PlanSelector.vue'
import PlanConfig from './components/PlanConfig.vue'
@@ -100,6 +102,7 @@ const viewMode = ref<'config' | 'simple'>('config')
const isEditingPlanName = ref<boolean>(false)
const loading = ref(true)
const switching = ref(false) // 添加切换状态
// Use a record to match child component expectations
const tableData = ref<Record<string, any>>({})
@@ -128,13 +131,18 @@ const debounce = <T extends (...args: any[]) => any>(func: T, wait: number): T =
const handleAddPlan = async (planType: string = 'MaaPlanConfig') => {
try {
const response = await createPlan(planType)
const defaultName = getDefaultPlanName(planType)
const newPlan = { id: response.planId, name: defaultName, type: planType }
const uniqueName = getDefaultPlanName(planType)
const newPlan = { id: response.planId, name: uniqueName, type: planType }
planList.value.push(newPlan)
activePlanId.value = newPlan.id
currentPlanName.value = defaultName
currentPlanName.value = uniqueName
await loadPlanData(newPlan.id)
message.info(`已创建新的${getPlanTypeLabel(planType)},建议您修改为更有意义的名称`, 3)
// 如果生成的名称包含数字,说明有重名,提示用户
if (uniqueName.match(/\s\d+$/)) {
message.info(`已创建新的${getPlanTypeLabel(planType)}"${uniqueName}",建议您修改为更有意义的名称`, 4)
} else {
message.success(`已创建新的${getPlanTypeLabel(planType)}"${uniqueName}"`)
}
} catch (error) {
console.error('添加计划失败:', error)
}
@@ -186,6 +194,7 @@ const saveInBackground = async (planId: string) => {
const planData: Record<string, any> = { ...(tableData.value || {}) }
planData.Info = { Mode: currentMode.value, Name: currentPlanName.value, Type: planType }
console.log(`[计划表] 保存数据 (${planId}):`, planData)
await updatePlan(planId, planData)
} catch (error) {
console.error('后台保存计划数据失败:', error)
@@ -214,20 +223,27 @@ const handleSave = async () => {
await debouncedSave()
}
// 优化计划切换逻辑
// 优化计划切换逻辑 - 异步保存,立即切换
const onPlanChange = async (planId: string) => {
if (planId === activePlanId.value) return
// 触发当前计划的异步保存,但不等待完成
if (activePlanId.value) {
saveInBackground(activePlanId.value).catch(error => {
console.warn('切换时保存当前计划失败:', error)
})
}
switching.value = true
try {
// 异步保存当前计划,不等待完成
if (activePlanId.value) {
saveInBackground(activePlanId.value).catch(error => {
console.error('切换时保存当前计划失败:', error)
message.warning('保存当前计划时出现问题,请检查数据是否完整')
})
}
// 立即切换到新计划
activePlanId.value = planId
await loadPlanData(planId)
// 立即切换到新计划,提升响应速度
console.log(`[计划表] 切换到新计划: ${planId}`)
activePlanId.value = planId
await loadPlanData(planId)
} finally {
switching.value = false
}
}
const startEditPlanName = () => {
@@ -242,13 +258,29 @@ const startEditPlanName = () => {
}
const finishEditPlanName = () => {
isEditingPlanName.value = false
if (activePlanId.value) {
const currentPlan = planList.value.find(plan => plan.id === activePlanId.value)
if (currentPlan) {
currentPlan.name = currentPlanName.value || getDefaultPlanName(currentPlan.type)
const newName = currentPlanName.value?.trim() || ''
const existingNames = planList.value.map(plan => plan.name)
// 验证新名称
const validation = validatePlanName(newName, existingNames, currentPlan.name)
if (!validation.isValid) {
// 如果验证失败,显示错误消息并恢复原名称
message.error(validation.message || '计划表名称无效')
currentPlanName.value = currentPlan.name
} else {
// 如果验证成功,更新名称
currentPlan.name = newName
currentPlanName.value = newName
// 触发保存操作,确保名称被保存到后端
handleSave()
}
}
}
isEditingPlanName.value = false
}
const onModeChange = () => {
@@ -263,20 +295,41 @@ const handleTableDataUpdate = async (newData: Record<string, any>) => {
const loadPlanData = async (planId: string) => {
try {
const response = await getPlans(planId)
currentPlanData.value = response.data
if (response.data && response.data[planId]) {
const planData = response.data[planId] as PlanData
// 优化先检查缓存数据避免不必要的API调用
let planData: PlanData | null = null
if (currentPlanData.value && currentPlanData.value[planId]) {
planData = currentPlanData.value[planId] as PlanData
console.log(`[计划表] 使用缓存数据 (${planId})`)
} else {
const response = await getPlans(planId)
currentPlanData.value = response.data
planData = response.data[planId] as PlanData
console.log(`[计划表] 加载新数据 (${planId})`)
}
if (planData) {
if (planData.Info) {
const apiName = planData.Info.Name || ''
if (!apiName && !currentPlanName.value) {
const currentPlan = planList.value.find(plan => plan.id === planId)
if (currentPlan) currentPlanName.value = currentPlan.name
const currentPlan = planList.value.find(plan => plan.id === planId)
// 优先使用planList中的名称
if (currentPlan && currentPlan.name) {
currentPlanName.value = currentPlan.name
if (apiName !== currentPlan.name) {
console.log(`[计划表] 同步名称: API="${apiName}" -> planList="${currentPlan.name}"`)
}
} else if (apiName) {
currentPlanName.value = apiName
if (currentPlan) {
currentPlan.name = apiName
}
}
currentMode.value = planData.Info.Mode || 'ALL'
}
tableData.value = planData
}
} catch (error) {
@@ -288,17 +341,47 @@ const initPlans = async () => {
try {
const response = await getPlans()
if (response.index && response.index.length > 0) {
// 优化预先收集所有名称避免O(n²)复杂度
const allPlanNames: string[] = []
planList.value = response.index.map((item: any) => {
const planId = item.uid
const planData = response.data[planId]
const planType = item.type
const planName = planData?.Info?.Name || getDefaultPlanName(planType)
let planName = planData?.Info?.Name || ''
// 如果API中没有名称或者名称是默认的模板名称则生成唯一名称
if (!planName || planName === '新 MAA 计划表' || planName === '新通用计划表' || planName === '新自定义计划表') {
planName = generateUniquePlanName(planType, allPlanNames)
}
allPlanNames.push(planName)
return { id: planId, name: planName, type: planType }
})
const queryPlanId = (route.query.planId as string) || ''
const target = queryPlanId ? planList.value.find(p => p.id === queryPlanId) : null
activePlanId.value = target ? target.id : planList.value[0].id
await loadPlanData(activePlanId.value)
const selectedPlanId = target ? target.id : planList.value[0].id
// 优化直接使用已获取的数据避免重复API调用
activePlanId.value = selectedPlanId
const planData = response.data[selectedPlanId]
if (planData) {
currentPlanData.value = response.data
// 直接设置数据避免loadPlanData的重复调用
const selectedPlan = planList.value.find(plan => plan.id === selectedPlanId)
if (selectedPlan) {
currentPlanName.value = selectedPlan.name
}
if (planData.Info) {
currentMode.value = planData.Info.Mode || 'ALL'
}
console.log(`[计划表] 初始加载数据 (${selectedPlanId}):`, planData)
tableData.value = planData
}
} else {
currentPlanData.value = null
}
@@ -310,22 +393,12 @@ const initPlans = async () => {
}
}
const getDefaultPlanName = (planType: string) =>
(
({
MaaPlanConfig: '新 MAA 计划表',
GeneralPlan: '新通用计划表',
CustomPlan: '新自定义计划表',
}) as Record<string, string>
)[planType] || '新计划表'
const getPlanTypeLabel = (planType: string) =>
(
({
MaaPlanConfig: 'MAA计划表',
GeneralPlan: '通用计划表',
CustomPlan: '自定义计划表',
}) as Record<string, string>
)[planType] || '计划表'
const getDefaultPlanName = (planType: string) => {
// 保持原来的逻辑,但添加重名检测
const existingNames = planList.value.map(plan => plan.name)
return generateUniquePlanName(planType, existingNames)
}
// getPlanTypeLabel 现在从 @/utils/planNameUtils 导入,删除本地定义
watch(
() => [currentPlanName.value, currentMode.value],

File diff suppressed because it is too large Load Diff