feat(cultivate-flower): 新增等待时长字段并更新阶段规则校验逻辑

This commit is contained in:
Austin
2026-10-08 12:47:17 +08:00
parent 5a7d8407bc
commit 55ae8b2acd
5 changed files with 34 additions and 14 deletions
@@ -292,6 +292,7 @@ Query 参数:
"required_points": 10,
"water_points": 2,
"fertilize_points": 3,
"wait_hours": 36,
"allowed_actions": ["water", "fertilize"],
"image_file_id": "file_xxx",
"animation_file_id": "file_xxx",
@@ -329,7 +330,7 @@ Query 参数:
- 状态枚举:`online`、`offline`、`activity_only`、`maintenance`。
- 阶段枚举:`seed`、`sprout`、`bud`、`bloom`。
- 阶段链必须从 `seed` 连续配置并以 `bloom` 结束,不能重复或成环。
- 非 `bloom` 阶段 `required_points > 0`,且至少允许一个动作。
- 非 `bloom` 阶段 `required_points` 与 `wait_hours` 不能同时为 0(2026-10-08 起:升级条件 = 积分就绪 AND 等待就绪,允许纯积分或纯等待阶段),且至少允许一个动作。
- 动作枚举:`water`、`fertilize`。
- 文件 ID 必须通过 IM 内部文件校验,风险或待审核素材不能保存。
- 花种和阶段规则必须在一个数据库事务内保存。
@@ -51,6 +51,7 @@ type cultivateStageRuleDTO struct {
RequiredPoints uint `json:"requiredPoints"`
WaterPoints uint `json:"waterPoints"`
FertilizePoints uint `json:"fertilizePoints"`
WaitHours uint `json:"waitHours"`
AllowedActions []string `json:"allowedActions"`
ImageFileID string `json:"imageFileId"`
AnimationFileID string `json:"animationFileId"`
@@ -182,7 +183,7 @@ func flowerPayload(in *sysin.CultivateFlowerEditInp) map[string]any {
rules = append(rules, map[string]any{
"id": strconv.FormatUint(rule.Id, 10), "stage": rule.Stage, "next_stage": rule.NextStage,
"required_points": rule.RequiredPoints, "water_points": rule.WaterPoints, "fertilize_points": rule.FertilizePoints,
"allowed_actions": rule.AllowedActions, "image_file_id": rule.ImageFileId, "animation_file_id": rule.AnimationFileId,
"wait_hours": rule.WaitHours, "allowed_actions": rule.AllowedActions, "image_file_id": rule.ImageFileId, "animation_file_id": rule.AnimationFileId,
"progress_text_template": rule.ProgressTextTemplate, "completion_text": rule.CompletionText, "sort_no": rule.SortNo,
})
}
@@ -227,7 +228,7 @@ func flowerViewModel(item *cultivateFlowerDTO) (*sysin.CultivateFlowerViewModel,
}
result.StageRules = append(result.StageRules, &sysin.CultivateStageRuleEditInp{
Id: id, Stage: rule.Stage, NextStage: rule.NextStage, RequiredPoints: rule.RequiredPoints,
WaterPoints: rule.WaterPoints, FertilizePoints: rule.FertilizePoints, AllowedActions: rule.AllowedActions,
WaterPoints: rule.WaterPoints, FertilizePoints: rule.FertilizePoints, WaitHours: rule.WaitHours, AllowedActions: rule.AllowedActions,
ImageFileId: rule.ImageFileID, AnimationFileId: rule.AnimationFileID,
ProgressTextTemplate: rule.ProgressTextTemplate, CompletionText: rule.CompletionText, SortNo: rule.SortNo,
})
@@ -18,6 +18,7 @@ type CultivateStageRuleEditInp struct {
RequiredPoints uint `json:"requiredPoints"`
WaterPoints uint `json:"waterPoints"`
FertilizePoints uint `json:"fertilizePoints"`
WaitHours uint `json:"waitHours"`
AllowedActions []string `json:"allowedActions"`
ImageFileId string `json:"imageFileId"`
AnimationFileId string `json:"animationFileId"`
@@ -64,8 +65,8 @@ func (in *CultivateFlowerEditInp) Filter(ctx context.Context) error {
return gerror.New("成长阶段不能重复")
}
seen[rule.Stage] = true
if rule.Stage != "bloom" && rule.RequiredPoints == 0 {
return gerror.New("非开花阶段升级阈值必须大于0")
if rule.Stage != "bloom" && rule.RequiredPoints == 0 && rule.WaitHours == 0 {
return gerror.New("非开花阶段必须配置升级阈值或等待时长")
}
if rule.NextStage != "" && !validStage[rule.NextStage] {
return gerror.New("下一阶段不正确")
+3 -2
View File
@@ -17,6 +17,7 @@
<n-grid cols="1 s:1 m:3" responsive="screen" :x-gap="12">
<n-gi><n-form-item label="下一阶段"><n-input v-model:value="rule.nextStage" disabled /></n-form-item></n-gi>
<n-gi><n-form-item label="升级成长值"><n-input-number v-model:value="rule.requiredPoints" :min="0" /></n-form-item></n-gi>
<n-gi><n-form-item label="等待时长(小时)" :show-feedback="false"><n-input-number v-model:value="rule.waitHours" :min="0" :disabled="rule.stage === 'bloom'" placeholder="0 表示不等待" /></n-form-item></n-gi>
<n-gi><n-form-item label="允许动作"><n-select v-model:value="rule.allowedActions" multiple :options="actionOptions" /></n-form-item></n-gi>
<n-gi><n-form-item label="浇水成长值"><n-input-number v-model:value="rule.waterPoints" :min="0" /></n-form-item></n-gi>
<n-gi><n-form-item label="施肥成长值"><n-input-number v-model:value="rule.fertilizePoints" :min="0" /></n-form-item></n-gi>
@@ -35,10 +36,10 @@
import { ref } from 'vue';
import { useMessage } from 'naive-ui';
import { Edit, View } from '@/api/cultivate/flowers';
import { actionOptions, newState, rules, State, statusOptions } from './model';
import { actionOptions, newState, rules, State, statusOptions, validateStageRules } from './model';
const emit=defineEmits(['reloadTable']);const message=useMessage();const showModal=ref(false);const loading=ref(false);const submitting=ref(false);const formRef=ref();const formValue=ref<State>(newState(null));
function openModal(state:State|null){showModal.value=true;formValue.value=newState(null);if(!state?.id)return;loading.value=true;View({id:state.id}).then((res)=>formValue.value=newState(res)).finally(()=>loading.value=false)}
function submit(){formRef.value?.validate((errors)=>{if(errors){message.error('请检查配置内容');return}submitting.value=true;Edit(formValue.value).then(()=>{message.success('配置已保存');showModal.value=false;emit('reloadTable')}).finally(()=>submitting.value=false)})}
function submit(){formRef.value?.validate((errors)=>{if(errors){message.error('请检查配置内容');return}const stageError=validateStageRules(formValue.value.stageRules);if(stageError){message.error(stageError);return}submitting.value=true;Edit(formValue.value).then(()=>{message.success('配置已保存');showModal.value=false;emit('reloadTable')}).finally(()=>submitting.value=false)})}
defineExpose({openModal});
</script>
+23 -7
View File
@@ -13,6 +13,7 @@ export interface StageRule {
requiredPoints: number;
waterPoints: number;
fertilizePoints: number;
waitHours: number;
allowedActions: string[];
imageFileId: string;
animationFileId: string;
@@ -41,16 +42,31 @@ export class State {
export function defaultStageRules(): StageRule[] {
return [
stageRule('seed', 'sprout', 12, 1, 0, ['water'], 10),
stageRule('sprout', 'bud', 60, 1, 0, ['water'], 20),
stageRule('bud', 'bloom', 90, 1, 5, ['water', 'fertilize'], 30),
stageRule('bloom', '', 0, 0, 0, [], 40),
stageRule('seed', 'sprout', 12, 1, 0, 36, ['water'], 10),
stageRule('sprout', 'bud', 18, 1, 0, 60, ['water'], 20),
stageRule('bud', 'bloom', 24, 1, 6, 72, ['water', 'fertilize'], 30),
stageRule('bloom', '', 0, 0, 0, 0, [], 40),
];
}
function stageRule(stage: string, nextStage: string, requiredPoints: number, waterPoints: number, fertilizePoints: number, allowedActions: string[], sortNo: number): StageRule {
return { stage, nextStage, requiredPoints, waterPoints, fertilizePoints, allowedActions, imageFileId: '', animationFileId: '', progressTextTemplate: '', completionText: '', sortNo };
function stageRule(stage: string, nextStage: string, requiredPoints: number, waterPoints: number, fertilizePoints: number, waitHours: number, allowedActions: string[], sortNo: number): StageRule {
return { stage, nextStage, requiredPoints, waterPoints, fertilizePoints, waitHours, allowedActions, imageFileId: '', animationFileId: '', progressTextTemplate: '', completionText: '', sortNo };
}
// 非 bloom 阶段升级阈值与等待时长不能同时为 0(与 cultivate_server 校验一致)
export function validateStageRules(stageRules: StageRule[]): string | null {
for (const rule of stageRules) {
if (rule.stage !== 'bloom' && rule.requiredPoints === 0 && rule.waitHours === 0) {
return `阶段 ${rule.stage} 的升级成长值与等待时长不能同时为 0`;
}
}
return null;
}
export function newState(state: State | Record<string, any> | null): State {
const result = state ? new State(cloneDeep(state)) : new State();
// 兼容旧版后端未返回 waitHours 的数据
for (const rule of result.stageRules) { rule.waitHours = Number(rule.waitHours ?? 0) || 0; }
return result;
}
export function newState(state: State | Record<string, any> | null): State { return state ? new State(cloneDeep(state)) : new State(); }
export const rules = {
flowerCode: { required: true, trigger: ['blur', 'input'], message: '请输入花种编码' },