20260702215426

This commit is contained in:
oneao committed 2026-07-02 21:54:26 +08:00
1 parent 2bbeea19fa
commit d0f7f0d3d8
39 files changed
+8341 -2049

No files matched your search

+1
View File
@@ -4,3 +4,4 @@ export * from "./family";
export * from "./favorite";
export * from "./chat";
export * from "./clothing";
export * from "./meal";
+477
View File
@@ -0,0 +1,477 @@
import { create } from "zustand";
import { loadDataApi, saveDataApi, s3UploadFileApi } from "@/request/api";
import {
MealCategory,
MealDish,
MealDishIngredient,
MealDishStep,
MealDishTag,
MealPlan,
MealPlanItem,
MealPlanLog,
MealSaveChanges,
MealSlot,
MealStatus,
MealTag,
} from "@/types/meal";
const TABLES = {
dish: "b_meal_dish",
category: "b_meal_category",
tag: "b_meal_tag",
dishIngredient: "b_meal_dish_ingredient",
dishStep: "b_meal_dish_step",
dishTag: "b_meal_dish_tag",
slot: "b_meal_slot",
plan: "b_meal_plan",
planItem: "b_meal_plan_item",
planLog: "b_meal_plan_log",
} as const;
interface MealState {
dishes: MealDish[];
categories: MealCategory[];
tags: MealTag[];
dishTags: MealDishTag[];
dishIngredients: MealDishIngredient[];
dishSteps: MealDishStep[];
slots: MealSlot[];
plans: MealPlan[];
planItems: MealPlanItem[];
planLogs: MealPlanLog[];
clear: () => void;
// 查询
fetchAll: (familyId: number) => Promise<void>;
fetchDishes: (familyId: number) => Promise<void>;
fetchCategories: (familyId: number) => Promise<void>;
fetchTags: (familyId: number, parentId?: number) => Promise<void>;
fetchDishTags: (familyId: number, dishId?: number) => Promise<void>;
fetchDishIngredients: (familyId: number, dishId: number) => Promise<void>;
fetchDishSteps: (familyId: number, dishId: number) => Promise<void>;
fetchSlots: (familyId: number) => Promise<void>;
fetchPlansByDate: (familyId: number, planDate: string) => Promise<void>;
fetchPlanItems: (familyId: number, planId?: number) => Promise<void>;
fetchPlanLogs: (familyId: number, planId: number) => Promise<void>;
// 保存
saveDishChanges: (changes: MealSaveChanges<MealDish>) => Promise<boolean>;
saveCategoryChanges: (changes: MealSaveChanges<MealCategory>) => Promise<boolean>;
saveTagChanges: (changes: MealSaveChanges<MealTag>) => Promise<boolean>;
saveDishIngredientChanges: (changes: MealSaveChanges<MealDishIngredient>) => Promise<boolean>;
saveDishStepChanges: (changes: MealSaveChanges<MealDishStep>) => Promise<boolean>;
saveDishTagChanges: (changes: MealSaveChanges<MealDishTag>) => Promise<boolean>;
saveSlotChanges: (changes: MealSaveChanges<MealSlot>) => Promise<boolean>;
savePlanChanges: (changes: MealSaveChanges<MealPlan>) => Promise<boolean>;
savePlanItemChanges: (changes: MealSaveChanges<MealPlanItem>) => Promise<boolean>;
savePlanLogChanges: (changes: MealSaveChanges<MealPlanLog>) => Promise<boolean>;
// 批量保存
saveDishBundle: (payload: {
dishInserts?: Partial<MealDish>[];
dishUpdates?: Partial<MealDish>[];
dishDeletes?: Partial<MealDish>[];
dishTagInserts?: Partial<MealDishTag>[];
dishTagUpdates?: Partial<MealDishTag>[];
dishTagDeletes?: Partial<MealDishTag>[];
dishIngredientInserts?: Partial<MealDishIngredient>[];
dishIngredientUpdates?: Partial<MealDishIngredient>[];
dishIngredientDeletes?: Partial<MealDishIngredient>[];
dishStepInserts?: Partial<MealDishStep>[];
dishStepUpdates?: Partial<MealDishStep>[];
dishStepDeletes?: Partial<MealDishStep>[];
}) => Promise<boolean>;
savePlanBundle: (payload: {
planInserts?: Partial<MealPlan>[];
planUpdates?: Partial<MealPlan>[];
planItemInserts?: Partial<MealPlanItem>[];
planItemUpdates?: Partial<MealPlanItem>[];
planItemDeletes?: Partial<MealPlanItem>[];
planLogInserts?: Partial<MealPlanLog>[];
}) => Promise<boolean>;
// 图片上传
uploadDishImage: (familyId: number, file: any) => Promise<string | null>;
uploadStepImage: (familyId: number, file: any) => Promise<string | null>;
// 家庭动态(占位,待 family feed API 就绪后接入)
recordFeed: (familyId: number, content: string) => void;
}
function normalizeChanges<T>(changes: MealSaveChanges<T>) {
return {
inserts: changes.inserts ?? [],
updates: changes.updates ?? [],
deletes: changes.deletes ?? [],
};
}
export const useMealStore = create<MealState>((set, get) => ({
// --- 状态 ---
dishes: [],
categories: [],
tags: [],
dishTags: [],
dishIngredients: [],
dishSteps: [],
slots: [],
plans: [],
planItems: [],
planLogs: [],
clear: () =>
set({
dishes: [],
categories: [],
tags: [],
dishTags: [],
dishIngredients: [],
dishSteps: [],
slots: [],
plans: [],
planItems: [],
planLogs: [],
}),
// ======== 查询方法 ========
fetchAll: async (familyId: number) => {
if (!familyId) return;
await Promise.all([
get().fetchDishes(familyId),
get().fetchCategories(familyId),
get().fetchTags(familyId),
get().fetchDishTags(familyId),
get().fetchSlots(familyId),
]);
},
fetchDishes: async (familyId: number) => {
if (!familyId) return;
const res = await loadDataApi({
view_name: TABLES.dish,
search_condition: "family_id = ? and status = ?",
search_columns: [],
order_by: "sort_number asc, id asc",
args: [familyId, MealStatus.Enabled],
});
if (res.isSuccess) {
set({ dishes: res.data as MealDish[] });
}
},
fetchCategories: async (familyId: number) => {
if (!familyId) return;
const res = await loadDataApi({
view_name: TABLES.category,
search_condition: "family_id = ? and status = ?",
search_columns: [],
order_by: "sort_number asc, id asc",
args: [familyId, MealStatus.Enabled],
});
if (res.isSuccess) {
set({ categories: res.data as MealCategory[] });
}
},
fetchTags: async (familyId: number, parentId?: number) => {
if (!familyId) return;
const conditions = ["family_id = ?", "status = ?"];
const args: any[] = [familyId, MealStatus.Enabled];
if (parentId !== undefined) {
conditions.push("parent_id = ?");
args.push(parentId);
}
const res = await loadDataApi({
view_name: TABLES.tag,
search_condition: conditions.join(" and "),
search_columns: [],
order_by: "sort_number asc, id asc",
args,
});
if (res.isSuccess) {
set({ tags: res.data as MealTag[] });
}
},
fetchDishTags: async (familyId: number, dishId?: number) => {
if (!familyId) return;
const conditions = ["family_id = ?"];
const args: any[] = [familyId];
if (dishId !== undefined) {
conditions.push("dish_id = ?");
args.push(dishId);
}
const res = await loadDataApi({
view_name: TABLES.dishTag,
search_condition: conditions.join(" and "),
search_columns: [],
order_by: "id asc",
args,
});
if (res.isSuccess) {
set({ dishTags: res.data as MealDishTag[] });
}
},
fetchDishIngredients: async (familyId: number, dishId: number) => {
if (!familyId || !dishId) return;
const res = await loadDataApi({
view_name: TABLES.dishIngredient,
search_condition: "family_id = ? and dish_id = ?",
search_columns: [],
order_by: "sort_number asc, id asc",
args: [familyId, dishId],
});
if (res.isSuccess) {
set({ dishIngredients: res.data as MealDishIngredient[] });
}
},
fetchDishSteps: async (familyId: number, dishId: number) => {
if (!familyId || !dishId) return;
const res = await loadDataApi({
view_name: TABLES.dishStep,
search_condition: "family_id = ? and dish_id = ?",
search_columns: [],
order_by: "step_number asc, sort_number asc, id asc",
args: [familyId, dishId],
});
if (res.isSuccess) {
set({ dishSteps: res.data as MealDishStep[] });
}
},
fetchSlots: async (familyId: number) => {
if (!familyId) return;
const res = await loadDataApi({
view_name: TABLES.slot,
search_condition: "family_id = ? and status = ?",
search_columns: [],
order_by: "sort_number asc, id asc",
args: [familyId, MealStatus.Enabled],
});
if (res.isSuccess) {
set({ slots: res.data as MealSlot[] });
}
},
fetchPlansByDate: async (familyId: number, planDate: string) => {
if (!familyId || !planDate) return;
const res = await loadDataApi({
view_name: TABLES.plan,
search_condition: "family_id = ? and plan_date = ?",
search_columns: [],
order_by: "id asc",
args: [familyId, planDate],
});
if (res.isSuccess) {
set({ plans: res.data as MealPlan[] });
}
},
fetchPlanItems: async (familyId: number, planId?: number) => {
if (!familyId) return;
const conditions = ["family_id = ?"];
const args: any[] = [familyId];
if (planId !== undefined) {
conditions.push("plan_id = ?");
args.push(planId);
}
const res = await loadDataApi({
view_name: TABLES.planItem,
search_condition: conditions.join(" and "),
search_columns: [],
order_by: "sort_number asc, id asc",
args,
});
if (res.isSuccess) {
set({ planItems: res.data as MealPlanItem[] });
}
},
fetchPlanLogs: async (familyId: number, planId: number) => {
if (!familyId || !planId) return;
const res = await loadDataApi({
view_name: TABLES.planLog,
search_condition: "family_id = ? and plan_id = ?",
search_columns: [],
order_by: "create_time asc, id asc",
args: [familyId, planId],
});
if (res.isSuccess) {
set({ planLogs: res.data as MealPlanLog[] });
}
},
// ======== 单表保存方法 ========
saveDishChanges: async (changes: MealSaveChanges<MealDish>) => {
const res = await saveDataApi([
{ table_name: TABLES.dish, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
saveCategoryChanges: async (changes: MealSaveChanges<MealCategory>) => {
const res = await saveDataApi([
{ table_name: TABLES.category, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
saveTagChanges: async (changes: MealSaveChanges<MealTag>) => {
const res = await saveDataApi([
{ table_name: TABLES.tag, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
saveDishIngredientChanges: async (changes: MealSaveChanges<MealDishIngredient>) => {
const res = await saveDataApi([
{ table_name: TABLES.dishIngredient, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
saveDishStepChanges: async (changes: MealSaveChanges<MealDishStep>) => {
const res = await saveDataApi([
{ table_name: TABLES.dishStep, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
saveDishTagChanges: async (changes: MealSaveChanges<MealDishTag>) => {
const res = await saveDataApi([
{ table_name: TABLES.dishTag, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
saveSlotChanges: async (changes: MealSaveChanges<MealSlot>) => {
const res = await saveDataApi([
{ table_name: TABLES.slot, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
savePlanChanges: async (changes: MealSaveChanges<MealPlan>) => {
const res = await saveDataApi([
{ table_name: TABLES.plan, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
savePlanItemChanges: async (changes: MealSaveChanges<MealPlanItem>) => {
const res = await saveDataApi([
{ table_name: TABLES.planItem, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
savePlanLogChanges: async (changes: MealSaveChanges<MealPlanLog>) => {
const res = await saveDataApi([
{ table_name: TABLES.planLog, key_field: "id", ...normalizeChanges(changes) },
]);
return res.isSuccess;
},
// ======== 批量保存方法 ========
saveDishBundle: async (payload) => {
const res = await saveDataApi([
{
table_name: TABLES.dish,
key_field: "id",
inserts: payload.dishInserts ?? [],
updates: payload.dishUpdates ?? [],
deletes: payload.dishDeletes ?? [],
},
{
table_name: TABLES.dishTag,
key_field: "id",
inserts: payload.dishTagInserts ?? [],
updates: payload.dishTagUpdates ?? [],
deletes: payload.dishTagDeletes ?? [],
},
{
table_name: TABLES.dishIngredient,
key_field: "id",
inserts: payload.dishIngredientInserts ?? [],
updates: payload.dishIngredientUpdates ?? [],
deletes: payload.dishIngredientDeletes ?? [],
},
{
table_name: TABLES.dishStep,
key_field: "id",
inserts: payload.dishStepInserts ?? [],
updates: payload.dishStepUpdates ?? [],
deletes: payload.dishStepDeletes ?? [],
},
]);
return res.isSuccess;
},
savePlanBundle: async (payload) => {
const ops: any[] = [
{
table_name: TABLES.plan,
key_field: "id",
inserts: payload.planInserts ?? [],
updates: payload.planUpdates ?? [],
deletes: [],
},
{
table_name: TABLES.planItem,
key_field: "id",
inserts: payload.planItemInserts ?? [],
updates: payload.planItemUpdates ?? [],
deletes: payload.planItemDeletes ?? [],
},
];
if ((payload.planLogInserts ?? []).length > 0) {
ops.push({
table_name: TABLES.planLog,
key_field: "id",
inserts: payload.planLogInserts ?? [],
updates: [],
deletes: [],
});
}
const res = await saveDataApi(ops);
return res.isSuccess;
},
// ======== 图片上传 ========
uploadDishImage: async (familyId: number, file: any) => {
if (!familyId) return null;
const res = await s3UploadFileApi([file], familyId);
if (!res.isSuccess) return null;
return res.data?.[0] ?? null;
},
uploadStepImage: async (familyId: number, file: any) => {
if (!familyId) return null;
const res = await s3UploadFileApi([file], familyId);
if (!res.isSuccess) return null;
return res.data?.[0] ?? null;
},
// ======== 家庭动态(占位) ========
recordFeed: (_familyId: number, _content: string) => {
// TODO: 接入 family feed API 后实现
// 加菜(type=2)和留下备注(type=4)两个动作触发
},
}));