新增庫存設定相關API串接及介面調整 | 優化即時溫度與冷藏溫度數據獲取邏輯 | 調整儀表板顯示內容

This commit is contained in:
koko 2025-08-18 11:06:47 +08:00
parent f4d26271a4
commit 07a9acb077
13 changed files with 359 additions and 381 deletions

View File

@ -9,7 +9,7 @@ import {
POST_DASHBOARD_PRODUCT_TARGET_SETTING_API, POST_DASHBOARD_PRODUCT_TARGET_SETTING_API,
GET_DASHBOARD_PRODUCT_TARGET_SETTING_API, GET_DASHBOARD_PRODUCT_TARGET_SETTING_API,
GET_DASHBOARD_PRODUCT_HISTORY_API, GET_DASHBOARD_PRODUCT_HISTORY_API,
GET_DASHBOARD_PRODUCT_TASK_API GET_DASHBOARD_PRODUCT_TASK_API,
} from "./api"; } from "./api";
import instance from "@/util/request"; import instance from "@/util/request";
import apihandler from "@/util/apihandler"; import apihandler from "@/util/apihandler";
@ -37,10 +37,9 @@ export const getDashboardDevice = async ({ option }) => {
}; };
export const getDashboardOptionRealTimeData = async ({ option }) => { export const getDashboardOptionRealTimeData = async ({ option }) => {
const res = await instance.post( GET_DASHBOARD_REALTIME_DATA_API, { const res = await instance.get(GET_DASHBOARD_REALTIME_DATA_API, {
option: parseInt(option), params: { option: parseInt(option) },
}); });
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
code: res.code, code: res.code,
@ -91,7 +90,6 @@ export const getDashboardTemp = async ({
timeInterval, timeInterval,
tempOption, tempOption,
}); });
console.log(res);
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
code: res.code, code: res.code,
@ -155,5 +153,4 @@ export const getDashboardProductTask = async () => {
msg: res.msg, msg: res.msg,
code: res.code, code: res.code,
}); });
} };

View File

@ -2,3 +2,6 @@ export const POST_CHANGE_GROUP_VALUE_API = `api/Weight/ChangeGroupValue`;
export const GET_CHECKWEIGHER_API = `/api/HistoryData/GetCheckWeigherNow`; export const GET_CHECKWEIGHER_API = `/api/HistoryData/GetCheckWeigherNow`;
export const POST_SETTING_TYPE_API = `/SituationRoom/SetProduct`; export const POST_SETTING_TYPE_API = `/SituationRoom/SetProduct`;
export const GET_SETTING_INVENTORY_API = `/SituationRoom/GetInventory`;
export const POST_SETTING_INVENTORY_API = `/SituationRoom/SaveInventory`;
export const GET_SETTING_INVENTORY_LOG_API = `/SituationRoom/GetInventoryLog`;

View File

@ -4,10 +4,13 @@ import {
POST_CHANGE_GROUP_VALUE_API, POST_CHANGE_GROUP_VALUE_API,
GET_CHECKWEIGHER_API, GET_CHECKWEIGHER_API,
POST_SETTING_TYPE_API, POST_SETTING_TYPE_API,
GET_SETTING_INVENTORY_API,
POST_SETTING_INVENTORY_API,
GET_SETTING_INVENTORY_LOG_API,
} from "./api"; } from "./api";
export const postChangeGroupValue = async (data) => { export const postChangeGroupValue = async (data) => {
const res = await instance.post(POST_CHANGE_GROUP_VALUE_API,data); const res = await instance.post(POST_CHANGE_GROUP_VALUE_API, data);
return apihandler(res.code, res.data, { return apihandler(res.code, res.data, {
msg: res.msg, msg: res.msg,
@ -35,3 +38,37 @@ export const postProductSettingType = async (data) => {
isSuccess: false, isSuccess: false,
}); });
}; };
export const getSettingInventory = async () => {
const res = await instance.get(GET_SETTING_INVENTORY_API);
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
isSuccess: false,
});
};
export const postSettingInventory = async (data) => {
const res = await instance.post(POST_SETTING_INVENTORY_API, data);
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
isSuccess: false,
});
};
export const getSettingInventoryLog = async ({ start_time, end_time }) => {
const res = await instance.get(GET_SETTING_INVENTORY_LOG_API, {
params: {
start_time,
end_time,
},
});
return apihandler(res.code, res.data, {
msg: res.msg,
code: res.code,
isSuccess: false,
});
};

View File

@ -105,6 +105,18 @@ watch(
{ immediate: true } { immediate: true }
); );
// title
watch(
() => props.title,
(newTitle) => {
if (chart.value) {
chartOption.value.title.text = newTitle;
chart.value.setOption(chartOption.value);
}
},
{ immediate: true }
);
defineExpose({ defineExpose({
chart, chart,
}); });

View File

@ -73,7 +73,7 @@ const curWidth = computed(() => {
:disabled="disabled" :disabled="disabled"
:readonly="readonly" :readonly="readonly"
:required="required" :required="required"
class="text-lg text-white bg-transparent w-full input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:bg-base-300 read-only:text-zinc-500 read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0" class="text-lg text-white bg-transparent w-full input input-bordered rounded-md px-3 border-info focus-within:border-info read-only:bg-base-300 read-only:text-zinc-300 read-only:border-0 read-only:focus-within:outline-0 read-only:focus:outline-0"
/> />
<input <input
v-else v-else

View File

@ -5,7 +5,7 @@ export default function useFormErrorMessage(scheme) {
onMounted(() => { onMounted(() => {
// formErrorMsg.value = scheme // formErrorMsg.value = scheme
if (scheme) { if (scheme && scheme.fields) {
formErrorMsg.value = Object.fromEntries( formErrorMsg.value = Object.fromEntries(
Object.keys(scheme.fields).map((f) => [f, ""]) Object.keys(scheme.fields).map((f) => [f, ""])
); );
@ -41,9 +41,11 @@ export default function useFormErrorMessage(scheme) {
}; };
const updateScheme = (scheme) => { const updateScheme = (scheme) => {
formErrorMsg.value = Object.fromEntries( if (scheme && scheme.fields) {
Object.keys(scheme.fields).map((f) => [f, ""]) formErrorMsg.value = Object.fromEntries(
); Object.keys(scheme.fields).map((f) => [f, ""])
);
}
}; };
return { formErrorMsg, handleSubmit, handleErrorReset, updateScheme }; return { formErrorMsg, handleSubmit, handleErrorReset, updateScheme };

View File

@ -1,30 +1,26 @@
<template> <template>
<div class="card bg-neutral text-neutral-content shadow-sm shadow-gray-400"> <div class="card rounded-md bg-neutral text-neutral-content shadow-sm shadow-gray-400">
<div class="card-body text-xs px-3 py-4"> <div class="card-body text-xs px-1 py-2">
<p>安全庫存量: {{ inventory }} </p> <p>安全存量: {{ safety_stock }} </p>
<p>目標庫存量: {{ targetInventory }} </p> <p>目前存量: {{ current_stock }} </p>
<p> {{ updateTime }}</p> <p>{{ updateTime }}</p>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
defineProps({ defineProps({
inventory: { safety_stock: {
type: Number, type: Number,
required: true, required: true,
}, },
targetInventory: { current_stock: {
type: Number, type: Number,
required: true, required: true,
}, },
lastIncrease: { updateTime: {
type: Number,
required: true,
},
updateTime: {
type: String, type: String,
required: true, required: true,
}, },
}); });
</script> </script>

View File

@ -3,7 +3,7 @@ import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant"; import { SECOND_CHART_COLOR } from "@/constant";
import { getDashboardEnergy } from "@/apis/dashboard"; import { getDashboardEnergy } from "@/apis/dashboard";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { ref, computed, onMounted, watch } from "vue"; import { ref, computed, onMounted, watch, onUnmounted } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
const weeks = ["週日", "週一", "週二", "週三", "週四", "週五", "週六"]; const weeks = ["週日", "週一", "週二", "週三", "週四", "週五", "週六"];
@ -11,32 +11,30 @@ const weeks = ["週日", "週一", "週二", "週三", "週四", "週五", "週
const data = ref([]); const data = ref([]);
const getEnergyData = async () => { const getEnergyData = async () => {
const res = await getDashboardEnergy(); const res = await getDashboardEnergy();
console.log(res.data); if (res.isSuccess) {
data.value = res.data; data.value = res.data?.electric || [];
} else {
console.error("獲取用電量數據失敗:", res.message);
}
}; };
// let timer = null;
function generateFakeData() {
const names = ["上週", "本週"];
const now = dayjs();
return names.map((full_name, idx) => ({
full_name,
data: Array.from({ length: 7 }).map((_, i) => ({
time: now.subtract(6 - i, "day").toISOString(),
value: Math.round(Math.random() * 100 + 200 + idx * 50), // 200~350
})),
}));
}
onMounted(() => { onMounted(() => {
// getEnergyData(); getEnergyData();
timer = setInterval(() => {
getEnergyData();
}, 600000); // 10
});
// // 10 onUnmounted(() => {
// setInterval(() => { if (timer) {
// getEnergyData(); clearInterval(timer);
// }, 600000); timer = null;
}
data.value = generateFakeData(); if (electricity_chart.value && electricity_chart.value.chart) {
electricity_chart.value.chart.dispose();
}
}); });
const electricity_chart = ref(null); const electricity_chart = ref(null);

View File

@ -2,7 +2,7 @@
import LineChart from "@/components/chart/LineChart.vue"; import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant"; import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref, inject, onMounted, watch } from "vue"; import { ref, onMounted, watch, onUnmounted } from "vue";
import { getDashboardTemp } from "@/apis/dashboard"; import { getDashboardTemp } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam"; import useSearchParams from "@/hooks/useSearchParam";
@ -55,86 +55,55 @@ const defaultChartOption = ref({
const frozen_temp_chart = ref(null); const frozen_temp_chart = ref(null);
//
function generateFakeFrozenData() {
const names = ["冷藏槽A", "冷藏槽B"];
const now = dayjs();
return names.map((full_name, idx) => ({
full_name,
data: Array.from({ length: 12 }).map((_, i) => ({
time: now.subtract(55 - i * 5, "minute").toISOString(),
value: Math.round(Math.random() * 3 + 2 + idx * 2), // 2~7
})),
}));
}
const data = ref([]); const data = ref([]);
const getData = async (timeInterval) => { const getData = async () => {
// showChartLoading(frozen_temp_chart.value.chart);
const res = await getDashboardTemp({ const res = await getDashboardTemp({
timeInterval, // =>1.4.8 timeInterval: 1, // =>1.4.8
tempOption: 2, // 1:;2: tempOption: 2, // 1:;2:
}); });
console.log(res);
if (res.isSuccess) { if (res.isSuccess) {
data.value = res.data?.freezer; data.value = res.data || [];
} }
}; };
watch( watch(data, (newValue) => {
searchParams, if (newValue?.length > 0) {
(newValue, oldValue) => { frozen_temp_chart.value.chart.setOption({
if ( legend: {
newValue[intervalType] && data: newValue.map(({ full_name }) => full_name),
newValue[intervalType] !== oldValue[intervalType] },
) { xAxis: {
window.clearInterval(timeoutTimer.value); data: newValue[0]?.data.map(({ time }) => dayjs(time).format("HH:mm")),
getData(parseInt(newValue[intervalType])); },
timeoutTimer.value = setInterval(() => { series: newValue.map(({ full_name, data: seriesData }, index) => ({
getData(parseInt(newValue[intervalType])); name: full_name,
}, 60000); type: "line",
} data: seriesData.map(({ value }) => value),
}, showSymbol: false,
{ itemStyle: {
deep: true, color: SECOND_CHART_COLOR[index],
},
})),
});
} }
); });
let timer = null;
watch(
data,
(newValue) => {
// clearChart(frozen_temp_chart.value.chart);
newValue.length > 0 &&
frozen_temp_chart.value.chart.setOption({
legend: {
data: newValue.map(({ full_name }) => full_name),
},
xAxis: {
data: newValue[0]?.data.map(({ time }) =>
dayjs(time).format("HH:mm")
),
},
series: newValue.map(({ full_name, data }, index) => ({
name: full_name,
type: "line",
data: data.map(({ value }) => value),
showSymbol: false,
itemStyle: {
color: SECOND_CHART_COLOR[index],
},
})),
});
// frozen_temp_chart.value.chart.hideLoading();
},
{ deep: true }
);
const timeoutTimer = ref("");
onMounted(() => { onMounted(() => {
// getData(1); getData();
// timeoutTimer.value = setInterval(() => { timer = setInterval(() => {
// getData(1); getData();
// }, 60000); }, 60 * 1000);
data.value = generateFakeFrozenData(); });
onUnmounted(() => {
if (timer) {
clearInterval(timer);
timer = null;
}
}); });
</script> </script>

View File

@ -2,28 +2,14 @@
import LineChart from "@/components/chart/LineChart.vue"; import LineChart from "@/components/chart/LineChart.vue";
import { SECOND_CHART_COLOR } from "@/constant"; import { SECOND_CHART_COLOR } from "@/constant";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref, watch, onMounted } from "vue"; import { ref, watch, onMounted, onUnmounted } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn"; import useActiveBtn from "@/hooks/useActiveBtn";
import { getDashboardTemp } from "@/apis/dashboard"; import { getDashboardTemp } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam"; import useSearchParams from "@/hooks/useSearchParam";
import useDashboardOption from "@/hooks/useDashboardOption";
const { searchParams } = useSearchParams(); const { searchParams } = useSearchParams();
const intervalType = "immediateTemp"; const intervalType = "immediateTemp";
//
function generateFakeData() {
const names = ["二重釜-溫度", "調理鍋-溫度", "調理鍋-糖度"];
const now = dayjs();
return names.map((full_name, idx) => ({
full_name,
data: Array.from({ length: 12 }).map((_, i) => ({
time: now.subtract(55 - i * 5, "minute").toISOString(),
value: Math.round(Math.random() * 10 + 20 + idx * 5), // 20~35
})),
}));
}
const other_real_temp_chart = ref(null); const other_real_temp_chart = ref(null);
const defaultChartOption = ref({ const defaultChartOption = ref({
tooltip: { tooltip: {
@ -39,10 +25,10 @@ const defaultChartOption = ref({
bottom: "0%", bottom: "0%",
}, },
grid: { grid: {
top: "10%", top: "5%",
left: "0%", left: "0%",
right: "0%", right: "0%",
bottom: "10%", bottom: "20%",
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
@ -72,7 +58,6 @@ const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const data = ref([]); const data = ref([]);
onMounted(() => { onMounted(() => {
data.value = generateFakeData();
setItems([ setItems([
{ {
id: 1, id: 1,
@ -94,58 +79,33 @@ onMounted(() => {
}, },
]); ]);
}); });
// const getData = async (timeInterval, typeOption) => {
// // showChartLoading(other_real_temp_chart.value.chart);
// const res = await getDashboardTemp({
// timeInterval, // =>1.4.8
// tempOption: 1, // 1:;2:
// typeOption, // 1:;2:;3:
// });
// if (res.isSuccess) {
// data.value = res.data[selectedBtn.value.typeOption];
// }
// };
// const timeoutTimer = ref(""); const getData = async (typeOption) => {
const res = await getDashboardTemp({
timeInterval: 1, // =>1.4.8
tempOption: 1,
typeOption, // 1:-;2:調-;3:-
});
if (res.isSuccess) {
data.value = res.data || [];
}
};
// watch( let timer = null;
// selectedBtn,
// (newValue) => {
// window.clearInterval(timeoutTimer.value);
// getData(
// parseInt(searchParams.value[intervalType] || 1),
// newValue.typeOption
// );
// timeoutTimer.value = setInterval(() => {
// getData(
// parseInt(searchParams.value[intervalType] || 1),
// newValue.typeOption
// );
// }, 60000);
// },
// {
// deep: true,
// }
// );
// watch( watch(
// searchParams, selectedBtn,
// (newValue, oldValue) => { (newValue) => {
// if ( window.clearInterval(timer);
// newValue[intervalType] && getData(newValue.key);
// newValue[intervalType] !== oldValue[intervalType] timer = setInterval(() => {
// ) { getData(newValue.key);
// window.clearInterval(timeoutTimer.value); }, 60 * 1000);
// getData(parseInt(newValue[intervalType]), selectedBtn.value.typeOption); },
// timeoutTimer.value = setInterval(() => { {
// getData(parseInt(newValue[intervalType]), selectedBtn.value.typeOption); deep: true,
// }, 60000); }
// } );
// },
// {
// deep: true,
// }
// );
watch(data, (newValue) => { watch(data, (newValue) => {
// clearChart(other_real_temp_chart.value.chart); // clearChart(other_real_temp_chart.value.chart);
@ -157,10 +117,10 @@ watch(data, (newValue) => {
xAxis: { xAxis: {
data: newValue[0]?.data.map(({ time }) => dayjs(time).format("HH:mm")), data: newValue[0]?.data.map(({ time }) => dayjs(time).format("HH:mm")),
}, },
series: newValue.map(({ full_name, data }, index) => ({ series: newValue.map(({ full_name, data: seriesData }, index) => ({
name: full_name, name: full_name,
type: "line", type: "line",
data: data.map(({ value }) => value), data: seriesData.map(({ value }) => value),
showSymbol: false, showSymbol: false,
itemStyle: { itemStyle: {
color: SECOND_CHART_COLOR[index], color: SECOND_CHART_COLOR[index],
@ -170,6 +130,13 @@ watch(data, (newValue) => {
} }
// other_real_temp_chart.value.chart.hideLoading(); // other_real_temp_chart.value.chart.hideLoading();
}); });
onUnmounted(() => {
if (timer) {
clearInterval(timer);
timer = null;
}
});
</script> </script>
<template> <template>

View File

@ -1,51 +1,29 @@
<script setup> <script setup>
import LiquidfillChart from "@/components/chart/LiquidfillChart.vue"; import LiquidfillChart from "@/components/chart/LiquidfillChart.vue";
import { ref, onMounted, provide, watch, inject } from "vue"; import { ref, onMounted, provide, watch, inject } from "vue";
import { getDashboardProductCompletion } from "@/apis/dashboard"; import { getSettingInventory } from "@/apis/productSetting";
import DashboardDescriptionCard from "./DashboardDescriptionCard.vue"; import DashboardDescriptionCard from "./DashboardDescriptionCard.vue";
import dayjs from "dayjs";
// const descriptionCards = ref([]);
const production_data = ref([]);
const progress_data = ref([]); const progress_data = ref([]);
const getCompletion = async () => {
// API 使
// const res = await getDashboardProductCompletion();
for (let i = 0; i < 3; i++) {
production_data.value.push(Math.floor(Math.random() * 100));
}
};
const descriptionCards = [ const getCompletion = async () => {
{ const res = await getSettingInventory();
title: "發酵槽", descriptionCards.value = res.data.map((item) => ({
inventory: 38, title: item.name,
targetInventory: 12, percentage: item.percentage,
lastIncrease: 12, current_stock: item.current_stock,
updateTime: "2025-05-16 15:30", safety_stock: item.safety_stock,
}, updateTime: dayjs(item.updated_at).format("YYYY-MM-DD HH:mm:ss"),
{ }));
title: "醋池", };
inventory: 45,
targetInventory: 15,
lastIncrease: 8,
updateTime: "2025-05-16 16:00",
},
{
title: "澄清醋",
inventory: 22,
targetInventory: 10,
lastIncrease: 5,
updateTime: "2025-05-16 16:30",
},
];
provide("dashboard_product_complete", { getCompletion, progress_data }); provide("dashboard_product_complete", { getCompletion, progress_data });
onMounted(() => { onMounted(() => {
getCompletion(); getCompletion();
}); });
</script> </script>
<template> <template>
@ -53,30 +31,13 @@ onMounted(() => {
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h3 class="text-info font-bold text-xl text-center">原醋即時庫存量</h3> <h3 class="text-info font-bold text-xl text-center">原醋即時庫存量</h3>
</div> </div>
<div class="w-full grid grid-cols-3"> <div class="w-full grid grid-cols-3">
<div> <div v-for="(card, idx) in descriptionCards.slice(0, 3)" :key="idx">
<LiquidfillChart <LiquidfillChart
id="dashboard_mesona_production" :id="`dashboard_product_${idx}`"
title="發酵槽" :title="card.title || ''"
:value="production_data[0]" :value="card.percentage || 0"
color="#facd91" :color="idx === 0 ? '#facd91' : idx === 1 ? '#7dd7df' : '#8cce30'"
/>
</div>
<div>
<LiquidfillChart
id="dashboard_aiyu_production"
title="醋池"
:value="production_data[1]"
color="#7dd7df"
/>
</div>
<div>
<LiquidfillChart
id="dashboard_blackTea_production"
title="澄清醋"
:value="production_data[2]"
color="#8cce30"
/> />
</div> </div>
</div> </div>
@ -86,9 +47,8 @@ onMounted(() => {
v-for="(card, index) in descriptionCards" v-for="(card, index) in descriptionCards"
:key="index" :key="index"
:title="card.title" :title="card.title"
:inventory="card.inventory" :safety_stock="card.safety_stock"
:targetInventory="card.targetInventory" :current_stock="card.current_stock"
:lastIncrease="card.lastIncrease"
:updateTime="card.updateTime" :updateTime="card.updateTime"
/> />
</div> </div>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, defineProps, inject, watch } from "vue"; import { ref, computed, defineProps, inject, watch } from "vue";
import * as yup from "yup"; import * as yup from "yup";
import "yup-phone-lite"; import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage"; import useFormErrorMessage from "@/hooks/useFormErrorMessage";
@ -11,35 +11,35 @@ const props = defineProps({
formState: Object, formState: Object,
itemData: Array, itemData: Array,
}); });
const emit = defineEmits(["pushData"]); const emit = defineEmits(["pushData", "resetModalForm"]);
const dateItem = ref([ const settingScheme = yup.object({
{ total_capacity: yup.number().required("必填").min(0, "不能小於0"),
key: "start_time",
name: "start_time",
value: dayjs(),
dateFormat: "yyyy-MM-dd",
placeholder: "請輸入日期",
},
]);
const itemScheme = yup.object({
start_time: yup.date().required("必填"),
itemName: yup.string().required("必填"),
safety_stock: yup.number().required("必填").min(0, "不能小於0"), safety_stock: yup.number().required("必填").min(0, "不能小於0"),
target_stock: yup.number().required("必填").min(0, "不能小於0"), current_stock: yup.number().required("必填").min(0, "不能小於0"),
});
const recordScheme = yup.object({
current_stock: yup.number().required("必填").min(0, "不能小於0"),
});
const itemScheme = computed(() => {
return props.formState && props.formState.type === 1
? settingScheme
: recordScheme;
}); });
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } = const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
useFormErrorMessage(itemScheme); useFormErrorMessage(itemScheme.value);
const onCancel = () => { const onCancel = () => {
handleErrorReset(); handleErrorReset();
emit("resetModalForm");
inventory_setting_modal.close(); inventory_setting_modal.close();
}; };
const onOk = async () => { const onOk = async () => {
const value = await handleSubmit(itemScheme, props.formState); const value = await handleSubmit(itemScheme.value, props.formState);
emit("pushData", { ...value }); emit("pushData", { ...value });
onCancel(); onCancel();
}; };
@ -48,55 +48,46 @@ const onOk = async () => {
<template> <template>
<Modal <Modal
id="inventory_setting_modal" id="inventory_setting_modal"
:title=" :title="props.formState?.type == 1 ? '設定' : '入庫'"
props.formState?.start_time ? '修改原醋庫存列表' : '新增原醋庫存列表'
"
:onCancel="onCancel" :onCancel="onCancel"
:width="710" :width="710"
> >
<template #modalContent> <template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between"> <form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<DateGroup <Input :value="formState" class="my-2" name="name" readonly>
class="my-2" <template #topLeft>項目</template>
:items="dateItem" </Input>
inputClass="w-full shadow-none" <Input
:required="true"
>
<template #topLeft>日期</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.start_time }}
</span></template
>
</DateGroup>
<Select
:value="formState" :value="formState"
class="my-2" class="my-2"
selectClass="border-info focus-within:border-info" name="total_capacity"
name="itemName" v-if="formState?.type == 1"
Attribute="full_name"
:options="props.itemData"
> >
<template #topLeft>品名</template> <template #topLeft>總庫存量</template>
<template #bottomLeft> <template #bottomLeft>
<span class="text-error text-base">{{ <span class="text-error text-base">{{
formErrorMsg.itemName formErrorMsg.total_capacity
}}</span> }}</span>
</template> </template>
</Select> </Input>
<Input :value="formState" class="my-2" name="safety_stock"> <Input
<template #topLeft>安全庫存量</template> :value="formState"
class="my-2"
name="safety_stock"
v-if="formState?.type == 1"
>
<template #topLeft>安全存量</template>
<template #bottomLeft> <template #bottomLeft>
<span class="text-error text-base">{{ <span class="text-error text-base">{{
formErrorMsg.safety_stock formErrorMsg.safety_stock
}}</span> }}</span>
</template> </template>
</Input> </Input>
<Input :value="formState" class="my-2" name="target_stock"> <Input :value="formState" class="my-2" name="current_stock">
<template #topLeft>標庫存量</template> <template #topLeft>存量</template>
<template #bottomLeft> <template #bottomLeft>
<span class="text-error text-base">{{ <span class="text-error text-base">{{
formErrorMsg.target_stock formErrorMsg.current_stock
}}</span> }}</span>
</template> </template>
</Input> </Input>

View File

@ -1,33 +1,35 @@
<script setup> <script setup>
import Table from "@/components/customUI/Table.vue"; import Table from "@/components/customUI/Table.vue";
import InventorySettingAddModal from "./InventorySettingAddModal.vue"; import InventorySettingAddModal from "./InventorySettingAddModal.vue";
import { postChangeGroupValue, getCheckWeigher } from "@/apis/productSetting"; import {
import { ref, onMounted, inject } from "vue"; getSettingInventory,
postSettingInventory,
getSettingInventoryLog,
} from "@/apis/productSetting";
import { ref, onMounted, inject, watch } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
import dayjs from "dayjs"; import dayjs from "dayjs";
const { openToast } = inject("app_toast"); const { openToast } = inject("app_toast");
const columns = [ const settingColumns = [
{ { title: "項目", key: "name" },
title: "日期", { title: "總庫存量", key: "total_capacity" },
key: "date", { title: "安全存量", key: "safety_stock" },
}, { title: "目前存量", key: "current_stock" },
{ { title: "更新時間", key: "updated_at" },
title: "品名", { title: "功能", key: "operation" },
key: "full_name", ];
},
{ const recordColumns = [
title: "安全庫存量", { title: "項目", key: "item_name", filter: true },
key: "safety", { title: "總庫存量", key: "total_capacity" },
}, { title: "安全存量", key: "safety_stock" },
{ { title: "目前存量", key: "current_stock" },
title: "目標庫存量", { title: "操作類型", key: "type" },
key: "target", { title: "最後修改者", key: "user_name", filter: true },
}, { title: "日期", key: "time", sort: true },
{
title: "操作",
key: "operation",
},
]; ];
const dataSource = ref([]); const dataSource = ref([]);
@ -53,35 +55,38 @@ const dateRange = ref([
]); ]);
const searchState = ref({ const searchState = ref({
itemName: "all",
start_time: dayjs().subtract(30, "day").format("YYYY-MM-DD"), start_time: dayjs().subtract(30, "day").format("YYYY-MM-DD"),
end_time: dayjs().format("YYYY-MM-DD"), end_time: dayjs().format("YYYY-MM-DD"),
}); });
const formState = ref({ const formState = ref({});
start_time: "",
itemName: 1,
safety_stock: 0,
target_stock: 0,
});
const loading = ref(false); const loading = ref(false);
const getDataSource = async () => { const getDataSource = async () => {
loading.value = true; loading.value = true;
// const res = await getCheckWeigher(); const res = await getSettingInventory();
// dataSource.value = res.data; if (!res.isSuccess) {
dataSource.value = [];
} else {
dataSource.value = res.data.map((item) => ({
...item,
updated_at: dayjs(item.updated_at).format("YYYY-MM-DD HH:mm:ss"),
}));
}
loading.value = false; loading.value = false;
}; };
const pushDataSource = async (data) => { const pushDataSource = async (value) => {
loading.value = true; const res = await postSettingInventory(value);
// if (res.isSuccess) {
getDataSource();
loading.value = false; } else {
openToast("error", res.msg, "#account_user_modal");
}
}; };
const onSearch = () => { const onSearch = async () => {
// //
searchState.value.start_time = dayjs(dateRange.value[0].value).format( searchState.value.start_time = dayjs(dateRange.value[0].value).format(
"YYYY-MM-DD" "YYYY-MM-DD"
@ -90,81 +95,122 @@ const onSearch = () => {
"YYYY-MM-DD" "YYYY-MM-DD"
); );
console.log("搜尋條件:", searchState.value); const res = await getSettingInventoryLog(searchState.value);
if (!res.isSuccess) {
dataSource.value = [];
} else {
dataSource.value = res.data.map((item) => ({
...item,
time: dayjs(item.time).format("YYYY-MM-DD HH:mm:ss"),
}));
}
}; };
const openModal = (record) => { const openModal = (type, item_id, record) => {
if (record) { if (type === 1) {
formState.value = { ...record }; formState.value = {
type: 1,
item_id: item_id,
name: record.name,
total_capacity: record.total_capacity,
safety_stock: record.safety_stock,
current_stock: record.current_stock,
};
} else { } else {
resetModalForm(); formState.value = {
type: 2,
item_id: item_id,
name: record.name,
current_stock: record.current_stock,
};
} }
inventory_setting_modal.showModal(); inventory_setting_modal.showModal();
}; };
const resetModalForm = () => { const resetModalForm = () => {
formState.value = { formState.value = {};
start_time: "",
itemName: 1,
safety_stock: 0,
target_stock: 0,
};
}; };
//
const removeAccount = async () => {};
onMounted(() => { onMounted(() => {
getDataSource(); setItems([
{
title: "設定",
key: "setting",
active: true,
},
{
title: "紀錄",
key: "record",
active: false,
},
]);
}); });
watch(
() => selectedBtn.value?.key,
(key) => {
dataSource.value = [];
if (key === "setting") {
getDataSource();
} else if (key === "record") {
onSearch();
}
},
{
immediate: true,
}
);
</script> </script>
<template> <template>
<div class="flex justify-start items-center mb-3"> <InventorySettingAddModal
<h3 class="text-xl mr-5">原醋庫存列表</h3> @pushData="pushDataSource"
<InventorySettingAddModal :formState="formState"
@pushData="pushDataSource" :itemData="itemData"
:formState="formState" @resetModalForm="resetModalForm"
:itemData="itemData" />
/> <div class="w-full custom-border p-4 mb-4">
<button <div class="flex flex-wrap items-center justify-start">
class="btn btn-sm btn-success mr-3" <ButtonGroup
@click.stop.prevent="openModal(null)" :items="items"
> :withLine="true"
<font-awesome-icon :icon="['fas', 'plus']" />新增 class="mr-5"
</button> :onclick="(e, item) => changeActiveBtn(item)"
</div> />
<Table :columns="columns" :dataSource="dataSource" :loading="loading"> <template v-if="selectedBtn?.key === 'record'">
<template #beforeTable> <DateGroup
<div class="flex items-center gap-5 mb-8"> :items="dateRange"
<Select :withLine="true"
:value="searchState" :isTopLabelExist="false"
class="" :isBottomLabelExist="false"
selectClass="border-info focus-within:border-info" class="mr-5"
name="itemName" />
Attribute="full_name" <button class="btn btn-success" @click.stop.prevent="onSearch">
:options="[{ full_name: '全品項', key: 'all' }, ...itemData]"
>
</Select>
<DateGroup :items="dateRange" :withLine="true" />
<button class="btn btn-outline-success" @click.stop.prevent="onSearch">
搜尋 搜尋
</button> </button>
</div> </template>
</template> </div>
</div>
<Table
:columns="selectedBtn?.key === 'setting' ? settingColumns : recordColumns"
:dataSource="dataSource"
:loading="loading"
>
<template #bodyCell="{ record, column, index }"> <template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'operation'"> <template v-if="column.key === 'operation'">
<button <button
class="btn btn-sm btn-success text-white mr-2" class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)" @click.stop.prevent="() => openModal(1, record.item_id, record)"
> >
修改 設定
</button> </button>
<button <button
class="btn btn-sm btn-error text-white" class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => removeAccount(record.id)" @click.stop.prevent="() => openModal(2, record.item_id, record)"
:disabled="record?.item_id == 2"
> >
刪除 入庫
</button> </button>
</template> </template>
<template v-else> <template v-else>