Files
AUTO-MAS-test/frontend/src/views/MAAUserEdit.vue

2105 lines
66 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="user-edit-header">
<div class="header-nav">
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item>
<router-link to="/scripts">脚本管理</router-link>
</a-breadcrumb-item>
<a-breadcrumb-item>
<router-link :to="`/scripts/${scriptId}/edit`" class="breadcrumb-link">
{{ scriptName }}
</router-link>
</a-breadcrumb-item>
<a-breadcrumb-item>
{{ isEdit ? '编辑用户' : '添加用户' }}
</a-breadcrumb-item>
</a-breadcrumb>
</div>
<a-space size="middle">
<a-button
v-if="formData.Info.Mode !== '简洁'"
type="primary"
ghost
size="large"
@click="handleMAAConfig"
:loading="maaConfigLoading"
>
<template #icon>
<SettingOutlined />
</template>
MAA配置
</a-button>
<a-button size="large" @click="handleCancel" class="cancel-button">
<template #icon>
<ArrowLeftOutlined />
</template>
返回
</a-button>
<a-button
type="primary"
size="large"
@click="handleSubmit"
:loading="loading"
class="save-button"
>
<template #icon>
<SaveOutlined />
</template>
{{ isEdit ? '保存修改' : '创建用户' }}
</a-button>
</a-space>
</div>
<div class="user-edit-content">
<a-card class="config-card">
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical" class="config-form">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-header">
<h3>基本信息</h3>
</div>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="userName" required>
<template #label>
<a-tooltip title="用于区分用户的名称,相同名称的用户将被视为同一用户进行统计">
<span class="form-label">
用户名
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input
v-model:value="formData.userName"
placeholder="请输入用户名"
:disabled="loading"
size="large"
class="modern-input"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="userId">
<template #label>
<a-tooltip title="用于切换账号官服输入手机号B服输入B站ID无需切换则留空">
<span class="form-label">
账号ID
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input
v-model:value="formData.userId"
placeholder="请输入账号ID"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="status">
<template #label>
<a-tooltip title="是否启用该用户">
<span class="form-label">
启用状态
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select v-model:value="formData.Info.Status" size="large">
<a-select-option :value="true"></a-select-option>
<a-select-option :value="false"></a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item :name="['Info', 'Password']">
<template #label>
<a-tooltip title="用户密码,仅用于存储以防遗忘,此外无任何作用">
<span class="form-label">
密码
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input-password
v-model:value="formData.Info.Password"
placeholder="密码仅用于储存以防遗忘,此外无任何作用"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="server">
<template #label>
<a-tooltip title="选择用户所在的游戏服务器">
<span class="form-label">
服务器
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select
v-model:value="formData.Info.Server"
placeholder="请选择服务器"
:disabled="loading"
:options="serverOptions"
size="large"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="remainedDay">
<template #label>
<a-tooltip title="账号剩余的有效天数,「-1」表示无限">
<span class="form-label">
剩余天数
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input-number
v-model:value="formData.Info.RemainedDay"
:min="-1"
:max="9999"
placeholder="0"
:disabled="loading"
size="large"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="mode">
<template #label>
<a-tooltip title="简洁模式下配置沿用脚本全局配置,详细模式下沿用用户自定义配置">
<span class="form-label">
用户配置模式
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select
v-model:value="formData.Info.Mode"
:options="[
{ label: '简洁', value: '简洁' },
{ label: '详细', value: '详细' },
]"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="mode">
<template #label>
<a-tooltip title="选择基建模式,自定义基建模式需要自行选择自定义基建配置文件">
<span class="form-label">
基建模式
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select
v-model:value="formData.Info.InfrastMode"
:options="[
{ label: '常规模式', value: 'Normal' },
{ label: '一键轮休', value: 'Rotation' },
{ label: '自定义基建', value: 'Custom' },
]"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 自定义基建配置文件选择 -->
<a-row :gutter="24" v-if="formData.Info.InfrastMode === 'Custom'">
<a-col :span="24">
<a-form-item name="infrastructureConfigFile">
<template #label>
<a-tooltip title="选择自定义基建配置JSON文件">
<span class="form-label">
基建配置文件
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<div style="display: flex; gap: 12px; align-items: center">
<a-input
v-model:value="formData.Info.InfrastPath"
placeholder="请选择基建配置JSON文件"
readonly
size="large"
style="flex: 1"
/>
<a-button
type="primary"
ghost
@click="selectInfrastructureConfig"
:disabled="loading"
size="large"
>
选择文件
</a-button>
<a-button
type="primary"
@click="importInfrastructureConfig"
:disabled="loading || !infrastructureConfigPath || !isEdit"
:loading="infrastructureImporting"
size="large"
>
导入配置
</a-button>
</div>
<div style="color: #999; font-size: 12px; margin-top: 4px">
请选择有效的基建配置JSON文件点击导入配置按钮将其应用到当前用户如果已经导入可以忽略此选择框
</div>
</a-form-item>
</a-col>
</a-row>
<a-form-item name="notes">
<template #label>
<a-tooltip title="为用户添加备注信息">
<span class="form-label">
备注
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-textarea
v-model:value="formData.Info.Notes"
placeholder="请输入备注信息"
:rows="4"
:disabled="loading"
class="modern-input"
/>
</a-form-item>
</div>
<!-- 关卡配置 -->
<div class="form-section">
<div class="section-header">
<h3>关卡配置</h3>
</div>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="mode">
<template #label>
<a-tooltip title="剿灭代理关卡选择">
<span class="form-label">
剿灭代理
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select
v-model:value="formData.Info.Annihilation"
:options="[
{ label: '关闭', value: 'Close' },
{ label: '当期剿灭', value: 'Annihilation' },
{ label: '切尔诺伯格', value: 'Chernobog@Annihilation' },
{ label: '龙门外环', value: 'LungmenOutskirts@Annihilation' },
{ label: '龙门市区', value: 'LungmenDowntown@Annihilation' },
]"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="mode">
<template #label>
<a-tooltip title="可选择「固定」或「计划表」">
<span class="form-label">
关卡配置模式
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select
v-model:value="formData.Info.StageMode"
:options="stageModeOptions"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="6">
<a-form-item name="medicineNumb">
<template #label>
<a-tooltip title="吃理智药数量">
<span class="form-label">
吃理智药数量
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">{{ displayMedicineNumb }}</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示输入框 -->
<a-input-number
v-else
v-model:value="displayMedicineNumb"
:min="0"
:max="9999"
placeholder="0"
:disabled="loading"
size="large"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="mode">
<template #label>
<a-tooltip
title="AUTO自动识别关卡最大代理倍率保持最大代理倍率且使用理智药后理智不溢出数值1~6按设定倍率执行代理不切换不调整游戏内代理倍率设定"
>
<span class="form-label">
连战次数
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">
{{
displaySeriesNumb === '0'
? 'AUTO'
: displaySeriesNumb === '-1'
? '不切换'
: displaySeriesNumb
}}
</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
v-model:value="displaySeriesNumb"
:options="[
{ label: 'AUTO', value: '0' },
{ label: '1', value: '1' },
{ label: '2', value: '2' },
{ label: '3', value: '3' },
{ label: '4', value: '4' },
{ label: '5', value: '5' },
{ label: '6', value: '6' },
{ label: '不切换', value: '-1' },
]"
:disabled="loading"
size="large"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="mode">
<template #label>
<a-tooltip title="关卡选择">
<span class="form-label">
关卡选择
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">
{{ displayStage === '-' ? '当前/上次' : displayStage || '不选择' }}
</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
v-model:value="displayStage"
:disabled="loading"
size="large"
placeholder="选择或输入自定义关卡"
>
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vnodes="menu" />
<a-divider style="margin: 4px 0" />
<a-space style="padding: 4px 8px" size="small">
<a-input
ref="stageInputRef"
v-model:value="customStageName"
placeholder="输入自定义关卡,如: 11-8"
style="flex: 1"
size="small"
@keyup.enter="addCustomStage"
/>
<a-button type="text" size="small" @click="addCustomStage">
<template #icon>
<PlusOutlined />
</template>
添加关卡
</a-button>
</a-space>
</template>
<a-select-option
v-for="option in stageOptions"
:key="option.value"
:value="option.value"
>
<template v-if="option.label.includes('|')">
<span>{{ option.label.split('|')[0] }}</span>
<a-tag color="green" size="small" style="margin-left: 8px">
{{ option.label.split('|')[1] }}
</a-tag>
</template>
<template v-else>
{{ option.label }}
<a-tag
v-if="isCustomStage(option.value)"
color="blue"
size="small"
style="margin-left: 8px"
>
自定义
</a-tag>
</template>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="6">
<a-form-item name="mode">
<template #label>
<a-tooltip
title="备选关卡-1所有备选关卡均选择「当前/上次」时视为不使用备选关卡"
>
<span class="form-label">
备选关卡-1
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">
{{ displayStage1 === '-' ? '当前/上次' : displayStage1 || '不选择' }}
</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
v-model:value="displayStage1"
:disabled="loading"
size="large"
placeholder="选择或输入自定义关卡"
>
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vnodes="menu" />
<a-divider style="margin: 4px 0" />
<a-space style="padding: 4px 8px" size="small">
<a-input
ref="stage1InputRef"
v-model:value="customStage1Name"
placeholder="输入自定义关卡,如: 11-8"
style="flex: 1"
size="small"
@keyup.enter="addCustomStage1"
/>
<a-button type="text" size="small" @click="addCustomStage1">
<template #icon>
<PlusOutlined />
</template>
添加关卡
</a-button>
</a-space>
</template>
<a-select-option
v-for="option in stageOptions"
:key="option.value"
:value="option.value"
>
<template v-if="option.label.includes('|')">
<span>{{ option.label.split('|')[0] }}</span>
<a-tag color="green" size="small" style="margin-left: 8px">
{{ option.label.split('|')[1] }}
</a-tag>
</template>
<template v-else>
{{ option.label }}
<a-tag
v-if="isCustomStage(option.value)"
color="blue"
size="small"
style="margin-left: 8px"
>
自定义
</a-tag>
</template>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="mode">
<template #label>
<a-tooltip
title="备选关卡-2所有备选关卡均选择「当前/上次」时视为不使用备选关卡"
>
<span class="form-label">
备选关卡-2
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">
{{ displayStage2 === '-' ? '当前/上次' : displayStage2 || '不选择' }}
</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
v-model:value="displayStage2"
:disabled="loading"
size="large"
placeholder="选择或输入自定义关卡"
>
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vnodes="menu" />
<a-divider style="margin: 4px 0" />
<a-space style="padding: 4px 8px" size="small">
<a-input
ref="stage2InputRef"
v-model:value="customStage2Name"
placeholder="输入自定义关卡,如: 11-8"
style="flex: 1"
size="small"
@keyup.enter="addCustomStage2"
/>
<a-button type="text" size="small" @click="addCustomStage2">
<template #icon>
<PlusOutlined />
</template>
添加关卡
</a-button>
</a-space>
</template>
<a-select-option
v-for="option in stageOptions"
:key="option.value"
:value="option.value"
>
<template v-if="option.label.includes('|')">
<span>{{ option.label.split('|')[0] }}</span>
<a-tag color="green" size="small" style="margin-left: 8px">
{{ option.label.split('|')[1] }}
</a-tag>
</template>
<template v-else>
{{ option.label }}
<a-tag
v-if="isCustomStage(option.value)"
color="blue"
size="small"
style="margin-left: 8px"
>
自定义
</a-tag>
</template>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="mode">
<template #label>
<a-tooltip
title="备选关卡-3所有备选关卡均选择「当前/上次」时视为不使用备选关卡"
>
<span class="form-label">
备选关卡-3
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">
{{ displayStage3 === '-' ? '当前/上次' : displayStage3 || '不选择' }}
</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
v-model:value="displayStage3"
:disabled="loading"
size="large"
placeholder="选择或输入自定义关卡"
>
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vnodes="menu" />
<a-divider style="margin: 4px 0" />
<a-space style="padding: 4px 8px" size="small">
<a-input
ref="stage3InputRef"
v-model:value="customStage3Name"
placeholder="输入自定义关卡,如: 11-8"
style="flex: 1"
size="small"
@keyup.enter="addCustomStage3"
/>
<a-button type="text" size="small" @click="addCustomStage3">
<template #icon>
<PlusOutlined />
</template>
添加关卡
</a-button>
</a-space>
</template>
<a-select-option
v-for="option in stageOptions"
:key="option.value"
:value="option.value"
>
<template v-if="option.label.includes('|')">
<span>{{ option.label.split('|')[0] }}</span>
<a-tag color="green" size="small" style="margin-left: 8px">
{{ option.label.split('|')[1] }}
</a-tag>
</template>
<template v-else>
{{ option.label }}
<a-tag
v-if="isCustomStage(option.value)"
color="blue"
size="small"
style="margin-left: 8px"
>
自定义
</a-tag>
</template>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="mode">
<template #label>
<a-tooltip title="剩余理智关卡,选择「不选择」时视为不使用剩余理智关卡">
<span class="form-label">
剩余理智关卡
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<!-- 计划模式显示只读文本 -->
<div v-if="isPlanMode" class="plan-mode-display">
<div class="plan-value">
{{ displayStageRemain === '-' ? '不选择' : displayStageRemain || '不选择' }}
</div>
<div class="plan-source">来自计划表</div>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
v-model:value="displayStageRemain"
:disabled="loading"
size="large"
placeholder="选择或输入自定义关卡"
>
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vnodes="menu" />
<a-divider style="margin: 4px 0" />
<a-space style="padding: 4px 8px" size="small">
<a-input
ref="stageRemainInputRef"
v-model:value="customStageRemainName"
placeholder="输入自定义关卡,如: 11-8"
style="flex: 1"
size="small"
@keyup.enter="addCustomStageRemain"
/>
<a-button type="text" size="small" @click="addCustomStageRemain">
<template #icon>
<PlusOutlined />
</template>
添加关卡
</a-button>
</a-space>
</template>
<a-select-option
v-for="option in stageRemainOptions"
:key="option.value"
:value="option.value"
>
<template v-if="option.label.includes('|')">
<span>{{ option.label.split('|')[0] }}</span>
<a-tag color="green" size="small" style="margin-left: 8px">
{{ option.label.split('|')[1] }}
</a-tag>
</template>
<template v-else>
{{ option.label }}
<a-tag
v-if="isCustomStage(option.value)"
color="blue"
size="small"
style="margin-left: 8px"
>
自定义
</a-tag>
</template>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</div>
<!-- 任务配置 -->
<div class="form-section">
<div class="section-header">
<h3>任务配置</h3>
</div>
<a-row :gutter="24">
<a-col :span="6">
<a-form-item name="ifWakeUp" label="开始唤醒">
<a-switch v-model:checked="formData.Task.IfWakeUp" :disabled="loading" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="ifRecruiting" label="自动公招">
<a-switch v-model:checked="formData.Task.IfRecruiting" :disabled="loading" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="ifBase" label="基建换班">
<a-switch v-model:checked="formData.Task.IfBase" :disabled="loading" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="ifCombat" label="刷理智">
<a-switch v-model:checked="formData.Task.IfCombat" :disabled="loading" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="6">
<a-form-item name="ifMall" label="获取信用及购物">
<a-switch v-model:checked="formData.Task.IfMall" :disabled="loading" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="ifMission" label="领取奖励">
<a-switch v-model:checked="formData.Task.IfMission" :disabled="loading" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="ifAutoRoguelike">
<template #label>
<a-tooltip title="未完全适配,请谨慎使用">
<span>自动肉鸽 </span>
<QuestionCircleOutlined class="help-icon" />
</a-tooltip>
</template>
<a-switch v-model:checked="formData.Task.IfAutoRoguelike" :disabled="true" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="ifReclamation">
<template #label>
<a-tooltip title="暂不支持,等待适配中~">
<span>生息演算 </span>
<QuestionCircleOutlined class="help-icon" />
</a-tooltip>
</template>
<a-switch v-model:checked="formData.Task.IfReclamation" :disabled="true" />
</a-form-item>
</a-col>
</a-row>
</div>
<!-- 森空岛配置 -->
<div class="form-section">
<div class="section-header">
<h3>森空岛配置</h3>
<a
href="https://doc.auto-mas.top/docs/advanced-features.html#%E8%8E%B7%E5%8F%96%E9%B9%B0%E8%A7%92%E7%BD%91%E7%BB%9C%E9%80%9A%E8%A1%8C%E8%AF%81%E7%99%BB%E5%BD%95%E5%87%AD%E8%AF%81"
target="_blank"
class="section-doc-link"
title="查看森空岛签到配置文档"
>
文档
</a>
</div>
<a-row :gutter="24" align="middle">
<a-col :span="6">
<span style="font-weight: 500">森空岛签到</span>
</a-col>
<a-col :span="18">
<a-switch v-model:checked="formData.Info.IfSkland" :disabled="loading" />
<span class="switch-description">开启后将启用森空岛签到功能</span>
</a-col>
</a-row>
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="24">
<span style="font-weight: 500">森空岛Token</span>
<a-input-password
v-model:value="formData.Info.SklandToken"
:disabled="loading || !formData.Info.IfSkland"
placeholder="请输入森空岛Token"
size="large"
style="margin-top: 8px; width: 100%"
allow-clear
/>
</a-col>
</a-row>
</div>
<!-- 通知配置 -->
<div class="form-section">
<div class="section-header">
<h3>通知配置</h3>
</div>
<a-row :gutter="24" align="middle">
<a-col :span="6">
<span style="font-weight: 500">启用通知</span>
</a-col>
<a-col :span="18">
<a-switch v-model:checked="formData.Notify.Enabled" :disabled="loading" />
<span class="switch-description">启用后将发送此用户的任务通知到选中的渠道</span>
</a-col>
</a-row>
<!-- 发送统计/六星等可选通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<span style="font-weight: 500">通知内容</span>
</a-col>
<a-col :span="18" style="display: flex; gap: 32px">
<a-checkbox
v-model:checked="formData.Notify.IfSendStatistic"
:disabled="loading || !formData.Notify.Enabled"
>统计信息
</a-checkbox>
<a-checkbox
v-model:checked="formData.Notify.IfSendSixStar"
:disabled="loading || !formData.Notify.Enabled"
>公开招募高资喜报
</a-checkbox>
</a-col>
</a-row>
<!-- 邮件通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfSendMail"
:disabled="loading || !formData.Notify.Enabled"
>邮件通知
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.ToAddress"
placeholder="请输入收件人邮箱地址"
:disabled="loading || !formData.Notify.Enabled || !formData.Notify.IfSendMail"
size="large"
style="width: 100%"
/>
</a-col>
</a-row>
<!-- Server酱通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfServerChan"
:disabled="loading || !formData.Notify.Enabled"
>Server酱
</a-checkbox>
</a-col>
<a-col :span="18" style="display: flex; gap: 8px">
<a-input
v-model:value="formData.Notify.ServerChanKey"
placeholder="请输入SENDKEY"
:disabled="loading || !formData.Notify.Enabled || !formData.Notify.IfServerChan"
size="large"
style="flex: 2"
/>
</a-col>
</a-row>
<!-- 企业微信群机器人通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfCompanyWebHookBot"
:disabled="loading || !formData.Notify.Enabled"
>企业微信群机器人
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.CompanyWebHookBotUrl"
placeholder="请输入机器人Webhook地址"
:disabled="
loading || !formData.Notify.Enabled || !formData.Notify.IfCompanyWebHookBot
"
size="large"
style="width: 100%"
class="modern-input"
/>
</a-col>
</a-row>
</div>
</a-form>
</a-card>
</div>
<a-float-button
type="primary"
@click="handleSubmit"
class="float-button"
:style="{
right: '24px',
}"
>
<template #icon>
<SaveOutlined />
</template>
</a-float-button>
</template>
<script setup lang="ts">
import { computed, defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import {
ArrowLeftOutlined,
PlusOutlined,
QuestionCircleOutlined,
SaveOutlined,
SettingOutlined,
} from '@ant-design/icons-vue'
import type { FormInstance, Rule } from 'ant-design-vue/es/form'
import { useUserApi } from '@/composables/useUserApi'
import { useScriptApi } from '@/composables/useScriptApi'
import { usePlanApi } from '@/composables/usePlanApi'
import { useWebSocket } from '@/composables/useWebSocket'
import { Service } from '@/api'
import { GetStageIn } from '@/api/models/GetStageIn'
const router = useRouter()
const route = useRoute()
const { addUser, updateUser, getUsers, loading: userLoading } = useUserApi()
const { getScript } = useScriptApi()
const { getPlans } = usePlanApi()
const { subscribe, unsubscribe } = useWebSocket()
const formRef = ref<FormInstance>()
const loading = computed(() => userLoading.value)
// 路由参数
const scriptId = route.params.scriptId as string
const userId = route.params.userId as string
const isEdit = computed(() => !!userId)
// 脚本信息
const scriptName = ref('')
// MAA配置相关
const maaConfigLoading = ref(false)
const maaWebsocketId = ref<string | null>(null)
// 基建配置文件相关
const infrastructureConfigPath = ref('')
const infrastructureImporting = ref(false)
// 服务器选项
const serverOptions = [
{ label: '官服', value: 'Official' },
{ label: 'B服', value: 'Bilibili' },
{ label: '国际服YoStarEN', value: 'YoStarEN' },
{ label: '日服YoStarJP', value: 'YoStarJP' },
{ label: '韩服YoStarKR', value: 'YoStarKR' },
{ label: '繁中服txwy', value: 'txwy' },
]
// 关卡选项
const stageOptions = ref<any[]>([{ label: '不选择', value: '' }])
// 剩余理智关卡专用选项(将"当前/上次"改为"不选择"
const stageRemainOptions = computed(() => {
return stageOptions.value.map(option => {
if (option.value === '-') {
return { ...option, label: option.label.replace('当前/上次', '不选择') }
}
return option
})
})
// 判断值是否为自定义关卡
const isCustomStage = (value: string) => {
if (!value || value === '' || value === '-') return false
// 检查是否在从API加载的关卡列表中
const predefinedStage = stageOptions.value.find(
option => option.value === value && !option.isCustom
)
return !predefinedStage
}
// 关卡配置模式选项
const stageModeOptions = ref<any[]>([{ label: '固定', value: 'Fixed' }])
// 计划模式状态
const isPlanMode = computed(() => {
return formData.Info.StageMode !== 'Fixed'
})
const planModeConfig = ref<any>(null)
// 计算属性用于显示正确的值(来自计划表或用户配置)
const displayMedicineNumb = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.MedicineNumb !== undefined) {
return planModeConfig.value.MedicineNumb
}
return formData.Info.MedicineNumb
},
set: value => {
if (!isPlanMode.value) {
formData.Info.MedicineNumb = value
}
},
})
const displaySeriesNumb = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.SeriesNumb !== undefined) {
return planModeConfig.value.SeriesNumb
}
return formData.Info.SeriesNumb
},
set: value => {
if (!isPlanMode.value) {
formData.Info.SeriesNumb = value
}
},
})
const displayStage = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.Stage !== undefined) {
return planModeConfig.value.Stage
}
return formData.Info.Stage
},
set: value => {
if (!isPlanMode.value) {
formData.Info.Stage = value
}
},
})
const displayStage1 = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.Stage_1 !== undefined) {
return planModeConfig.value.Stage_1
}
return formData.Info.Stage_1
},
set: value => {
if (!isPlanMode.value) {
formData.Info.Stage_1 = value
}
},
})
const displayStage2 = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.Stage_2 !== undefined) {
return planModeConfig.value.Stage_2
}
return formData.Info.Stage_2
},
set: value => {
if (!isPlanMode.value) {
formData.Info.Stage_2 = value
}
},
})
const displayStage3 = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.Stage_3 !== undefined) {
return planModeConfig.value.Stage_3
}
return formData.Info.Stage_3
},
set: value => {
if (!isPlanMode.value) {
formData.Info.Stage_3 = value
}
},
})
const displayStageRemain = computed({
get: () => {
if (isPlanMode.value && planModeConfig.value?.Stage_Remain !== undefined) {
return planModeConfig.value.Stage_Remain
}
return formData.Info.Stage_Remain
},
set: value => {
if (!isPlanMode.value) {
formData.Info.Stage_Remain = value
}
},
})
// 获取计划当前配置
const getPlanCurrentConfig = (planData: any) => {
if (!planData) return null
const mode = planData.Info?.Mode || 'ALL'
if (mode === 'ALL') {
return planData.ALL || null
} else if (mode === 'Weekly') {
// 获取+12时区的当前时间
const now = new Date()
const utc12Time = new Date(now.getTime() + 12 * 60 * 60 * 1000)
// 如果是凌晨4点前算作前一天
if (utc12Time.getHours() < 4) {
utc12Time.setDate(utc12Time.getDate() - 1)
}
const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
const today = weekdays[utc12Time.getDay()]
console.log('计划表周模式调试:', {
原始时间: now.toISOString(),
UTC12时间: utc12Time.toISOString(),
星期: today,
计划数据: planData,
})
// 优先使用今天的配置如果没有或为空则使用ALL配置
const todayConfig = planData[today]
if (todayConfig && Object.keys(todayConfig).length > 0) {
return todayConfig
}
return planData.ALL || null
}
return null
}
// MAA脚本默认用户数据
const getDefaultMAAUserData = () => ({
Info: {
Name: '',
Id: '',
Password: '',
Server: 'Official',
MedicineNumb: 0,
RemainedDay: -1,
SeriesNumb: '0',
Notes: '',
Status: true,
Mode: '简洁',
InfrastMode: 'Normal',
InfrastPath: '',
Routine: true,
Annihilation: 'Annihilation',
Stage: '1-7',
StageMode: 'Fixed',
Stage_1: '',
Stage_2: '',
Stage_3: '',
Stage_Remain: '',
IfSkland: false,
SklandToken: '',
},
Task: {
IfWakeUp: true,
IfBase: true,
IfCombat: true,
IfMall: true,
IfMission: true,
IfRecruiting: true,
IfReclamation: false,
IfAutoRoguelike: false,
},
Notify: {
Enabled: false,
ToAddress: '',
IfSendMail: false,
IfSendSixStar: false,
IfSendStatistic: false,
IfServerChan: false,
IfCompanyWebHookBot: false,
ServerChanKey: '',
ServerChanChannel: '',
ServerChanTag: '',
CompanyWebHookBotUrl: '',
},
Data: {
CustomInfrastPlanIndex: '',
IfPassCheck: false,
LastAnnihilationDate: '',
LastProxyDate: '',
LastSklandDate: '',
ProxyTimes: 0,
},
})
// 创建扁平化的表单数据,用于表单验证
const formData = reactive({
// 扁平化的验证字段
userName: '',
userId: '',
// 嵌套的实际数据
...getDefaultMAAUserData(),
})
// 表单验证规则
const rules = computed(() => {
const baseRules: Record<string, Rule[]> = {
userName: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 1, max: 50, message: '用户名长度应在1-50个字符之间', trigger: 'blur' },
],
}
return baseRules
})
// 同步扁平化字段与嵌套数据
watch(
() => formData.Info.Name,
newVal => {
if (formData.userName !== newVal) {
formData.userName = newVal || ''
}
},
{ immediate: true }
)
watch(
() => formData.Info.Id,
newVal => {
if (formData.userId !== newVal) {
formData.userId = newVal || ''
}
},
{ immediate: true }
)
watch(
() => formData.userName,
newVal => {
if (formData.Info.Name !== newVal) {
formData.Info.Name = newVal || ''
}
}
)
watch(
() => formData.userId,
newVal => {
if (formData.Info.Id !== newVal) {
formData.Info.Id = newVal || ''
}
}
)
// 加载脚本信息
const loadScriptInfo = async () => {
try {
const script = await getScript(scriptId)
if (script) {
scriptName.value = script.name
// 如果是编辑模式,加载用户数据
if (isEdit.value) {
await loadUserData()
}
} else {
message.error('脚本不存在')
handleCancel()
}
} catch (error) {
console.error('加载脚本信息失败:', error)
message.error('加载脚本信息失败')
}
}
// 加载用户数据
const loadUserData = async () => {
try {
const userResponse = await getUsers(scriptId, userId)
if (userResponse && userResponse.code === 200) {
// 查找指定的用户数据
const userIndex = userResponse.index.find(index => index.uid === userId)
if (userIndex && userResponse.data[userId]) {
const userData = userResponse.data[userId] as any
// 填充MAA用户数据
if (userIndex.type === 'MaaUserConfig') {
Object.assign(formData, {
Info: { ...getDefaultMAAUserData().Info, ...userData.Info },
Task: { ...getDefaultMAAUserData().Task, ...userData.Task },
Notify: { ...getDefaultMAAUserData().Notify, ...userData.Notify },
Data: { ...getDefaultMAAUserData().Data, ...userData.Data },
})
}
// 同步扁平化字段 - 使用nextTick确保数据更新完成后再同步
await nextTick()
formData.userName = formData.Info.Name || ''
formData.userId = formData.Info.Id || ''
// 检查并添加自定义关卡到选项列表
const stageFields = ['Stage', 'Stage_1', 'Stage_2', 'Stage_3', 'Stage_Remain']
stageFields.forEach(field => {
const stageValue = (formData.Info as any)[field]
if (stageValue && isCustomStage(stageValue)) {
// 检查是否已存在
const exists = stageOptions.value.find((option: any) => option.value === stageValue)
if (!exists) {
stageOptions.value.push({
label: stageValue,
value: stageValue,
isCustom: true,
})
}
}
})
console.log('用户数据加载成功:', {
userName: formData.userName,
userId: formData.userId,
InfoName: formData.Info.Name,
InfoId: formData.Info.Id,
fullData: formData,
})
} else {
message.error('用户不存在')
handleCancel()
}
} else {
message.error('获取用户数据失败')
handleCancel()
}
} catch (error) {
console.error('加载用户数据失败:', error)
message.error('加载用户数据失败')
}
}
const loadStageOptions = async () => {
try {
const response = await Service.getStageComboxApiInfoComboxStagePost({
type: GetStageIn.type.TODAY,
})
if (response && response.code === 200 && response.data) {
stageOptions.value = [...response.data].map(option => ({
...option,
isCustom: false, // 明确标记从API加载的关卡为非自定义
}))
}
} catch (error) {
console.error('加载关卡选项失败:', error)
}
}
const loadStageModeOptions = async () => {
try {
const response = await Service.getPlanComboxApiInfoComboxPlanPost()
if (response && response.code === 200 && response.data) {
stageModeOptions.value = response.data
}
} catch (error) {
console.error('加载关卡配置模式选项失败:', error)
// 保持默认的固定选项
}
}
// 替换 VNodes 组件定义
const VNodes = defineComponent({
props: { vnodes: { type: Object, required: true } },
setup(props) {
return () => props.vnodes as any
},
})
// 选择基建配置文件
const selectInfrastructureConfig = async () => {
try {
const path = await (window as any).electronAPI?.selectFile([
{ name: 'JSON 文件', extensions: ['json'] },
{ name: '所有文件', extensions: ['*'] },
])
if (path && path.length > 0) {
infrastructureConfigPath.value = path
formData.Info.InfrastPath = path[0]
message.success('文件选择成功')
}
} catch (error) {
console.error('文件选择失败:', error)
message.error('文件选择失败')
}
}
// 导入基建配置
const importInfrastructureConfig = async () => {
if (!infrastructureConfigPath.value) {
message.warning('请先选择配置文件')
return
}
if (!isEdit.value) {
message.warning('请先保存用户后再导入配置')
return
}
try {
infrastructureImporting.value = true
const result = await Service.importInfrastructureApiScriptsUserInfrastructurePost({
scriptId: scriptId,
userId: userId,
jsonFile: infrastructureConfigPath.value[0],
})
if (result && result.code === 200) {
message.success('基建配置导入成功')
infrastructureConfigPath.value = ''
} else {
message.error('基建配置导入失败')
}
} catch (error) {
console.error('基建配置导入失败:', error)
message.error('基建配置导入失败')
} finally {
infrastructureImporting.value = false
}
}
const handleSubmit = async () => {
try {
await formRef.value?.validate()
// 确保扁平化字段同步到嵌套数据
formData.Info.Name = formData.userName
formData.Info.Id = formData.userId
console.log('提交前的表单数据:', {
userName: formData.userName,
userId: formData.userId,
InfoName: formData.Info.Name,
InfoId: formData.Info.Id,
isEdit: isEdit.value,
})
// 排除 InfrastPath 字段
const { InfrastPath, ...infoWithoutInfrastPath } = formData.Info
// 构建提交数据
const userData = {
Info: { ...infoWithoutInfrastPath },
Task: { ...formData.Task },
Notify: { ...formData.Notify },
Data: { ...formData.Data },
}
if (isEdit.value) {
// 编辑模式
const result = await updateUser(scriptId, userId, userData)
if (result) {
message.success('用户更新成功')
handleCancel()
}
} else {
// 添加模式
const result = await addUser(scriptId)
if (result) {
// 创建成功后立即更新用户数据
try {
const updateResult = await updateUser(scriptId, result.userId, userData)
console.log('用户数据更新结果:', updateResult)
if (updateResult) {
message.success('用户创建成功')
handleCancel()
} else {
message.error('用户创建成功,但数据更新失败,请手动编辑用户信息')
// 不跳转,让用户可以重新保存
}
} catch (updateError) {
console.error('更新用户数据时发生错误:', updateError)
message.error('用户创建成功,但数据更新失败,请手动编辑用户信息')
}
}
}
} catch (error) {
console.error('表单验证失败:', error)
}
}
const handleMAAConfig = async () => {
if (!isEdit.value) {
message.warning('请先保存用户后再进行MAA配置')
return
}
try {
maaConfigLoading.value = true
// 如果已有连接,先断开
if (maaWebsocketId.value) {
unsubscribe(maaWebsocketId.value)
maaWebsocketId.value = null
}
// 直接订阅(旧 connect 参数移除)
const subId = userId
subscribe(subId, {
onError: error => {
console.error(`用户 ${formData.userName} MAA配置错误:`, error)
message.error(`MAA配置连接失败: ${error}`)
maaWebsocketId.value = null
},
})
maaWebsocketId.value = subId
message.success(`已开始配置用户 ${formData.userName} 的MAA设置`)
} catch (error) {
console.error('MAA配置失败:', error)
message.error('MAA配置失败')
} finally {
maaConfigLoading.value = false
}
}
// 自定义关卡相关
const customStageName = ref('')
const customStage1Name = ref('')
const customStage2Name = ref('')
const customStage3Name = ref('')
const customStageRemainName = ref('')
// 输入框引用
const stageInputRef = ref()
const stage1InputRef = ref()
const stage2InputRef = ref()
const stage3InputRef = ref()
const stageRemainInputRef = ref()
// 验证关卡名称格式
const validateStageName = (stageName: string): boolean => {
if (!stageName || !stageName.trim()) {
return false
}
// 简单的关卡名称验证,可以根据实际需要调整
const stagePattern = /^[a-zA-Z0-9\-_\u4e00-\u9fa5]+$/
return stagePattern.test(stageName.trim())
}
// 添加自定义关卡到选项列表
const addStageToOptions = (stageName: string) => {
if (!stageName || !stageName.trim()) {
return false
}
const trimmedName = stageName.trim()
// 检查是否已存在
const exists = stageOptions.value.find((option: any) => option.value === trimmedName)
if (exists) {
message.warning(`关卡 "${trimmedName}" 已存在`)
return false
}
// 添加到选项列表
stageOptions.value.push({
label: trimmedName,
value: trimmedName,
isCustom: true,
})
message.success(`自定义关卡 "${trimmedName}" 添加成功`)
return true
}
// 添加主关卡
const addCustomStage = () => {
if (!validateStageName(customStageName.value)) {
message.error('请输入有效的关卡名称')
return
}
if (addStageToOptions(customStageName.value)) {
if (!isPlanMode.value) {
formData.Info.Stage = customStageName.value.trim()
}
customStageName.value = ''
nextTick(() => {
stageInputRef.value?.focus()
})
}
}
// 添加备选关卡-1
const addCustomStage1 = () => {
if (!validateStageName(customStage1Name.value)) {
message.error('请输入有效的关卡名称')
return
}
if (addStageToOptions(customStage1Name.value)) {
if (!isPlanMode.value) {
formData.Info.Stage_1 = customStage1Name.value.trim()
}
customStage1Name.value = ''
nextTick(() => {
stage1InputRef.value?.focus()
})
}
}
// 添加备选关卡-2
const addCustomStage2 = () => {
if (!validateStageName(customStage2Name.value)) {
message.error('请输入有效的关卡名称')
return
}
if (addStageToOptions(customStage2Name.value)) {
if (!isPlanMode.value) {
formData.Info.Stage_2 = customStage2Name.value.trim()
}
customStage2Name.value = ''
nextTick(() => {
stage2InputRef.value?.focus()
})
}
}
// 添加备选关卡-3
const addCustomStage3 = () => {
if (!validateStageName(customStage3Name.value)) {
message.error('请输入有效的关卡名称')
return
}
if (addStageToOptions(customStage3Name.value)) {
if (!isPlanMode.value) {
formData.Info.Stage_3 = customStage3Name.value.trim()
}
customStage3Name.value = ''
nextTick(() => {
stage3InputRef.value?.focus()
})
}
}
// 添加剩余理智关卡
const addCustomStageRemain = () => {
if (!validateStageName(customStageRemainName.value)) {
message.error('请输入有效的关卡名称')
return
}
if (addStageToOptions(customStageRemainName.value)) {
if (!isPlanMode.value) {
formData.Info.Stage_Remain = customStageRemainName.value.trim()
}
customStageRemainName.value = ''
nextTick(() => {
stageRemainInputRef.value?.focus()
})
}
}
const handleCancel = () => {
if (maaWebsocketId.value) {
unsubscribe(maaWebsocketId.value)
maaWebsocketId.value = null
}
router.push('/scripts')
}
// 初始化加载
onMounted(() => {
if (!scriptId) {
message.error('缺少脚本ID参数')
handleCancel()
return
}
loadScriptInfo()
loadStageModeOptions()
loadStageOptions()
// 设置StageMode变化监听器
watch(
() => formData.Info.StageMode,
async newStageMode => {
if (newStageMode === 'Fixed') {
// 切换到固定模式,清除计划配置
planModeConfig.value = null
} else if (newStageMode && newStageMode !== '') {
// 切换到计划模式,加载计划配置
try {
const response = await getPlans(newStageMode)
if (response && response.code === 200 && response.data[newStageMode]) {
const planData = response.data[newStageMode]
const currentConfig = getPlanCurrentConfig(planData)
planModeConfig.value = currentConfig
console.log('计划配置加载成功:', {
planId: newStageMode,
currentConfig,
planModeConfigValue: planModeConfig.value,
})
// 从stageModeOptions中查找对应的计划名称
const planOption = stageModeOptions.value.find(option => option.value === newStageMode)
const planName = planOption ? planOption.label : newStageMode
message.success(`已切换到计划模式:${planName}`)
} else {
message.warning('计划配置加载失败,请检查计划是否存在')
planModeConfig.value = null
}
} catch (error) {
console.error('加载计划配置失败:', error)
message.error('加载计划配置时发生错误')
planModeConfig.value = null
}
}
},
{ immediate: false }
)
})
</script>
<style scoped>
.user-edit-container {
padding: 32px;
min-height: 100vh;
background: var(--ant-color-bg-layout);
}
.user-edit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
padding: 0 8px;
}
.header-nav {
flex: 1;
}
.breadcrumb {
margin: 0;
}
.header-title h1 {
margin: 0;
font-size: 32px;
font-weight: 700;
color: var(--ant-color-text);
background: linear-gradient(135deg, var(--ant-color-primary), var(--ant-color-primary-hover));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
margin: 4px 0 0 0;
font-size: 16px;
color: var(--ant-color-text-secondary);
}
.user-edit-content {
max-width: 1200px;
margin: 0 auto;
}
.config-card {
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.config-card :deep(.ant-card-body) {
padding: 32px;
}
.config-form {
max-width: none;
}
/* form-section 样式 - 来自 ScriptEdit.vue */
.form-section {
margin-bottom: 32px;
}
.form-section:last-child {
margin-bottom: 0;
}
.section-header {
margin-bottom: 20px;
padding-bottom: 8px;
border-bottom: 2px solid var(--ant-color-border-secondary);
display: flex;
justify-content: space-between;
align-items: center;
}
.section-header h3 {
margin: 0;
font-size: 20px;
font-weight: 700;
color: var(--ant-color-text);
display: flex;
align-items: center;
gap: 12px;
}
.section-header h3::before {
content: '';
width: 4px;
height: 24px;
background: linear-gradient(135deg, var(--ant-color-primary), var(--ant-color-primary-hover));
border-radius: 2px;
}
/* 表单标签 */
.form-label {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--ant-color-text);
font-size: 14px;
}
.help-icon {
color: var(--ant-color-text-tertiary);
font-size: 14px;
cursor: help;
transition: color 0.3s ease;
}
.help-icon:hover {
color: var(--ant-color-primary);
}
.modern-input {
border-radius: 8px;
border: 2px solid var(--ant-color-border);
background: var(--ant-color-bg-container);
transition: all 0.3s ease;
}
.modern-input:hover {
border-color: var(--ant-color-primary-hover);
}
.modern-input:focus,
.modern-input.ant-input-focused {
border-color: var(--ant-color-primary);
box-shadow: 0 0 0 4px rgba(24, 144, 255, 0.1);
}
/* section标题右侧文档链接 */
.section-doc-link {
color: var(--ant-color-primary) !important;
text-decoration: none;
font-size: 14px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
border: 1px solid var(--ant-color-primary);
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 4px;
}
.section-doc-link:hover {
color: var(--ant-color-primary-hover) !important;
background-color: var(--ant-color-primary-bg);
border-color: var(--ant-color-primary-hover);
text-decoration: none;
}
.form-card {
margin-bottom: 24px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.form-card :deep(.ant-card-head) {
border-bottom: 2px solid var(--ant-color-border-secondary);
}
.form-card :deep(.ant-card-head-title) {
font-size: 18px;
font-weight: 600;
color: var(--ant-color-text);
}
.user-form :deep(.ant-form-item-label > label) {
font-weight: 500;
color: var(--ant-color-text);
}
.switch-description,
.task-description {
margin-left: 12px;
font-size: 13px;
color: var(--ant-color-text-secondary);
}
.task-description {
display: block;
margin-top: 4px;
margin-left: 0;
}
.cancel-button {
border: 1px solid var(--ant-color-border);
background: var(--ant-color-bg-container);
color: var(--ant-color-text);
}
.cancel-button:hover {
border-color: var(--ant-color-primary);
color: var(--ant-color-primary);
}
.save-button {
background: var(--ant-color-primary);
border-color: var(--ant-color-primary);
}
.save-button:hover {
background: var(--ant-color-primary-hover);
border-color: var(--ant-color-primary-hover);
}
/* 表单标签样式 */
.form-label {
display: flex;
align-items: center;
gap: 6px;
font-weight: 500;
color: var(--ant-color-text);
}
.help-icon {
font-size: 14px;
color: var(--ant-color-text-tertiary);
cursor: help;
transition: color 0.3s ease;
}
.help-icon:hover {
color: var(--ant-color-primary);
}
/* 响应式设计 */
@media (max-width: 768px) {
.user-edit-container {
padding: 16px;
}
.user-edit-header {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.header-title h1 {
font-size: 24px;
}
.user-edit-content {
max-width: 100%;
}
}
.float-button {
width: 60px;
height: 60px;
}
/* 计划模式提示样式 */
.plan-mode-hint {
margin-top: 4px;
font-size: 12px;
color: var(--ant-color-primary);
font-weight: 500;
}
/* 计划模式显示样式 */
.plan-mode-display {
min-height: 40px;
padding: 8px 12px;
border: 1px solid var(--ant-color-border);
border-radius: 6px;
background: var(--ant-color-fill-alter);
display: flex;
align-items: center;
justify-content: space-between;
}
.plan-value {
font-size: 14px;
color: var(--ant-color-text);
font-weight: 500;
flex: 1;
}
.plan-source {
font-size: 12px;
color: var(--ant-color-primary);
font-weight: 500;
padding: 2px 8px;
background: var(--ant-color-primary-bg);
border-radius: 12px;
border: 1px solid var(--ant-color-primary-border);
}
</style>