20260702215426
This commit is contained in:
1 parent
2bbeea19fa
commit
d0f7f0d3d8
39 files changed
+8341
-2049
No files matched your search
@@ -4,3 +4,4 @@ export * from "./family";
|
||||
export * from "./favorite";
|
||||
export * from "./chat";
|
||||
export * from "./clothing";
|
||||
export * from "./meal";
|
||||
@@ -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)两个动作触发
|
||||
},
|
||||
}));
|
||||
Reference in new issue
Block a user