feat: 拆分MAAUserEdit

This commit is contained in:
2025-09-15 00:09:01 +08:00
parent d0d5d76f10
commit 660b82da7a
9 changed files with 1601 additions and 1372 deletions

View File

@@ -362,7 +362,7 @@ const handleToggleUserStatus = (user: User) => {
emit('toggleUserStatus', user)
}
const truncateText = (text: string, maxLength: number = 10): string => {
if (!text) return ''
if (!text || text.length === 0) return ''
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,325 @@
<template>
<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="$emit('selectInfrastructureConfig')"
:disabled="loading"
size="large"
>
选择文件
</a-button>
<a-button
type="primary"
@click="$emit('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>
</template>
<script setup lang="ts">
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
defineProps<{
formData: any
loading: boolean
serverOptions: any[]
infrastructureConfigPath: string
infrastructureImporting: boolean
isEdit: boolean
}>()
defineEmits<{
selectInfrastructureConfig: []
importInfrastructureConfig: []
}>()
</script>
<style scoped>
.form-section {
margin-bottom: 32px;
}
.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);
}
.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);
}
</style>

View File

@@ -0,0 +1,119 @@
<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="userMode !== '简洁'"
type="primary"
ghost
size="large"
@click="$emit('handleMAAConfig')"
:loading="maaConfigLoading"
>
<template #icon>
<SettingOutlined />
</template>
MAA配置
</a-button>
<a-button size="large" @click="$emit('handleCancel')" class="cancel-button">
<template #icon>
<ArrowLeftOutlined />
</template>
返回
</a-button>
<a-button
type="primary"
size="large"
@click="$emit('handleSubmit')"
:loading="loading"
class="save-button"
>
<template #icon>
<SaveOutlined />
</template>
{{ isEdit ? '保存修改' : '创建用户' }}
</a-button>
</a-space>
</div>
</template>
<script setup lang="ts">
import { ArrowLeftOutlined, SaveOutlined, SettingOutlined } from '@ant-design/icons-vue'
defineProps<{
scriptId: string
scriptName: string
isEdit: boolean
userMode: string
maaConfigLoading: boolean
loading: boolean
}>()
defineEmits<{
handleMAAConfig: []
handleCancel: []
handleSubmit: []
}>()
</script>
<style scoped>
.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;
}
.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);
}
@media (max-width: 768px) {
.user-edit-header {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
}
</style>

View File

@@ -0,0 +1,160 @@
<template>
<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>
</template>
<script setup lang="ts">
defineProps<{
formData: any
loading: boolean
}>()
</script>
<style scoped>
.form-section {
margin-bottom: 32px;
}
.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;
}
.switch-description {
margin-left: 12px;
font-size: 13px;
color: var(--ant-color-text-secondary);
}
.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);
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<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>
</template>
<script setup lang="ts">
defineProps<{
formData: any
loading: boolean
}>()
</script>
<style scoped>
.form-section {
margin-bottom: 32px;
}
.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;
}
.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;
}
.switch-description {
margin-left: 12px;
font-size: 13px;
color: var(--ant-color-text-secondary);
}
</style>

View File

@@ -0,0 +1,482 @@
<template>
<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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(medicineNumbTooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示输入框 -->
<a-input-number
v-else
:value="displayMedicineNumb"
@update:value="$emit('update-medicine-numb', $event)"
: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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(seriesNumbTooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示选择框 -->
<a-select
v-else
:value="displaySeriesNumb"
@update:value="$emit('update-series-numb', $event)"
: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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(stageTooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示选择框 -->
<StageSelector
v-else
:value="displayStage"
@update:value="$emit('update-stage', $event)"
:options="stageOptions"
:loading="loading"
placeholder="选择或输入自定义关卡"
@add-custom-stage="handleAddCustomStage"
/>
</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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(stage1Tooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示选择框 -->
<StageSelector
v-else
:value="displayStage1"
@update:value="$emit('update-stage1', $event)"
:options="stageOptions"
:loading="loading"
placeholder="选择或输入自定义关卡"
@add-custom-stage="handleAddCustomStage1"
/>
</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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(stage2Tooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示选择框 -->
<StageSelector
v-else
:value="displayStage2"
@update:value="$emit('update-stage2', $event)"
:options="stageOptions"
:loading="loading"
placeholder="选择或输入自定义关卡"
@add-custom-stage="handleAddCustomStage2"
/>
</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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(stage3Tooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示选择框 -->
<StageSelector
v-else
:value="displayStage3"
@update:value="$emit('update-stage3', $event)"
:options="stageOptions"
:loading="loading"
placeholder="选择或输入自定义关卡"
@add-custom-stage="handleAddCustomStage3"
/>
</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>
<a-tooltip>
<template #title>
<div class="plan-tooltip" v-html="formatTooltip(stageRemainTooltip)"></div>
</template>
<div class="plan-source">来自计划表</div>
</a-tooltip>
</div>
<!-- 固定模式显示选择框 -->
<StageSelector
v-else
:value="displayStageRemain"
@update:value="$emit('update-stage-remain', $event)"
:options="stageRemainOptions"
:loading="loading"
placeholder="选择或输入自定义关卡"
@add-custom-stage="handleAddCustomStageRemain"
/>
</a-form-item>
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
import StageSelector from './StageSelector.vue'
defineProps<{
formData: any
loading: boolean
stageModeOptions: any[]
stageOptions: any[]
stageRemainOptions: any[]
isPlanMode: boolean
displayMedicineNumb: number
displaySeriesNumb: string
displayStage: string
displayStage1: string
displayStage2: string
displayStage3: string
displayStageRemain: string
medicineNumbTooltip: string
seriesNumbTooltip: string
stageTooltip: string
stage1Tooltip: string
stage2Tooltip: string
stage3Tooltip: string
stageRemainTooltip: string
}>()
const emit = defineEmits<{
'update-medicine-numb': [value: number]
'update-series-numb': [value: string]
'update-stage': [value: string]
'update-stage1': [value: string]
'update-stage2': [value: string]
'update-stage3': [value: string]
'update-stage-remain': [value: string]
'handle-add-custom-stage': [stageName: string]
'handle-add-custom-stage1': [stageName: string]
'handle-add-custom-stage2': [stageName: string]
'handle-add-custom-stage3': [stageName: string]
'handle-add-custom-stage-remain': [stageName: string]
}>()
// 事件处理函数
const handleAddCustomStage = (stageName: string) => {
emit('handle-add-custom-stage', stageName)
}
const handleAddCustomStage1 = (stageName: string) => {
emit('handle-add-custom-stage1', stageName)
}
const handleAddCustomStage2 = (stageName: string) => {
emit('handle-add-custom-stage2', stageName)
}
const handleAddCustomStage3 = (stageName: string) => {
emit('handle-add-custom-stage3', stageName)
}
const handleAddCustomStageRemain = (stageName: string) => {
emit('handle-add-custom-stage-remain', stageName)
}
// 格式化 tooltip
const escapeHtml = (text: string) =>
text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
const formatTooltip = (text: string) => {
if (!text) return ''
return escapeHtml(text).replace(/\n/g, '<br/>')
}
</script>
<style scoped>
.form-section {
margin-bottom: 32px;
}
.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);
}
.plan-mode-display {
min-height: 40px;
padding: 8px 12px;
border: 1px solid var(--ant-color-border);
border-radius: 6px;
background: var(--ant-color-bg-container);
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);
}
.plan-tooltip {
white-space: normal;
line-height: 1.5;
max-width: 320px;
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<a-select
:value="value"
@update:value="$emit('update:value', $event)"
:disabled="loading"
size="large"
:placeholder="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="inputRef"
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 options"
: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>
</template>
<script setup lang="ts">
import { ref, defineComponent } from 'vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
// VNodes 组件定义
const VNodes = defineComponent({
props: { vnodes: { type: Object, required: true } },
setup(props) {
return () => props.vnodes as any
},
})
const props = defineProps<{
value: string
options: any[]
loading: boolean
placeholder?: string
}>()
const emit = defineEmits<{
'update:value': [value: string]
'add-custom-stage': [stageName: string]
}>()
const customStageName = ref('')
const inputRef = ref()
// 判断值是否为自定义关卡
const isCustomStage = (value: string) => {
if (!value || value === '' || value === '-') return false
// 检查是否在从API加载的关卡列表中
const predefinedStage = props.options.find(
option => option.value === value && !option.isCustom
)
return !predefinedStage
}
// 验证关卡名称格式
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 addCustomStage = () => {
if (!validateStageName(customStageName.value)) {
message.error('请输入有效的关卡名称')
return
}
const trimmedName = customStageName.value.trim()
// 检查是否已存在
const exists = props.options.find((option: any) => option.value === trimmedName)
if (exists) {
message.warning(`关卡 "${trimmedName}" 已存在`)
return
}
emit('add-custom-stage', trimmedName)
customStageName.value = ''
}
</script>

View File

@@ -0,0 +1,116 @@
<template>
<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>
</template>
<script setup lang="ts">
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
defineProps<{
formData: any
loading: boolean
}>()
</script>
<style scoped>
.form-section {
margin-bottom: 32px;
}
.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;
}
.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);
}
</style>