新增庫存設定相關API串接及介面調整 | 優化即時溫度與冷藏溫度數據獲取邏輯 | 調整儀表板顯示內容
This commit is contained in:
parent
f4d26271a4
commit
07a9acb077
@ -9,7 +9,7 @@ import {
|
||||
POST_DASHBOARD_PRODUCT_TARGET_SETTING_API,
|
||||
GET_DASHBOARD_PRODUCT_TARGET_SETTING_API,
|
||||
GET_DASHBOARD_PRODUCT_HISTORY_API,
|
||||
GET_DASHBOARD_PRODUCT_TASK_API
|
||||
GET_DASHBOARD_PRODUCT_TASK_API,
|
||||
} from "./api";
|
||||
import instance from "@/util/request";
|
||||
import apihandler from "@/util/apihandler";
|
||||
@ -37,10 +37,9 @@ export const getDashboardDevice = async ({ option }) => {
|
||||
};
|
||||
|
||||
export const getDashboardOptionRealTimeData = async ({ option }) => {
|
||||
const res = await instance.post( GET_DASHBOARD_REALTIME_DATA_API, {
|
||||
option: parseInt(option),
|
||||
const res = await instance.get(GET_DASHBOARD_REALTIME_DATA_API, {
|
||||
params: { option: parseInt(option) },
|
||||
});
|
||||
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
@ -91,7 +90,6 @@ export const getDashboardTemp = async ({
|
||||
timeInterval,
|
||||
tempOption,
|
||||
});
|
||||
console.log(res);
|
||||
return apihandler(res.code, res.data, {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
@ -155,5 +153,4 @@ export const getDashboardProductTask = async () => {
|
||||
msg: res.msg,
|
||||
code: res.code,
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -2,3 +2,6 @@ export const POST_CHANGE_GROUP_VALUE_API = `api/Weight/ChangeGroupValue`;
|
||||
|
||||
export const GET_CHECKWEIGHER_API = `/api/HistoryData/GetCheckWeigherNow`;
|
||||
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`;
|
||||
|
@ -4,10 +4,13 @@ import {
|
||||
POST_CHANGE_GROUP_VALUE_API,
|
||||
GET_CHECKWEIGHER_API,
|
||||
POST_SETTING_TYPE_API,
|
||||
GET_SETTING_INVENTORY_API,
|
||||
POST_SETTING_INVENTORY_API,
|
||||
GET_SETTING_INVENTORY_LOG_API,
|
||||
} from "./api";
|
||||
|
||||
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, {
|
||||
msg: res.msg,
|
||||
@ -35,3 +38,37 @@ export const postProductSettingType = async (data) => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
@ -105,6 +105,18 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 新增監聽 title 變化
|
||||
watch(
|
||||
() => props.title,
|
||||
(newTitle) => {
|
||||
if (chart.value) {
|
||||
chartOption.value.title.text = newTitle;
|
||||
chart.value.setOption(chartOption.value);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
chart,
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ const curWidth = computed(() => {
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
: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
|
||||
v-else
|
||||
|
@ -5,7 +5,7 @@ export default function useFormErrorMessage(scheme) {
|
||||
|
||||
onMounted(() => {
|
||||
// formErrorMsg.value = scheme
|
||||
if (scheme) {
|
||||
if (scheme && scheme.fields) {
|
||||
formErrorMsg.value = Object.fromEntries(
|
||||
Object.keys(scheme.fields).map((f) => [f, ""])
|
||||
);
|
||||
@ -41,9 +41,11 @@ export default function useFormErrorMessage(scheme) {
|
||||
};
|
||||
|
||||
const updateScheme = (scheme) => {
|
||||
formErrorMsg.value = Object.fromEntries(
|
||||
Object.keys(scheme.fields).map((f) => [f, ""])
|
||||
);
|
||||
if (scheme && scheme.fields) {
|
||||
formErrorMsg.value = Object.fromEntries(
|
||||
Object.keys(scheme.fields).map((f) => [f, ""])
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return { formErrorMsg, handleSubmit, handleErrorReset, updateScheme };
|
||||
|
@ -1,28 +1,24 @@
|
||||
<template>
|
||||
<div class="card bg-neutral text-neutral-content shadow-sm shadow-gray-400">
|
||||
<div class="card-body text-xs px-3 py-4">
|
||||
<p>安全庫存量: {{ inventory }} 頓</p>
|
||||
<p>目標庫存量: {{ targetInventory }} 頓</p>
|
||||
<p> {{ updateTime }}</p>
|
||||
<div class="card rounded-md bg-neutral text-neutral-content shadow-sm shadow-gray-400">
|
||||
<div class="card-body text-xs px-1 py-2">
|
||||
<p>安全存量: {{ safety_stock }} 頓</p>
|
||||
<p>目前存量: {{ current_stock }} 頓</p>
|
||||
<p>{{ updateTime }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
inventory: {
|
||||
safety_stock: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
targetInventory: {
|
||||
current_stock: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
lastIncrease: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
updateTime: {
|
||||
updateTime: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ import LineChart from "@/components/chart/LineChart.vue";
|
||||
import { SECOND_CHART_COLOR } from "@/constant";
|
||||
import { getDashboardEnergy } from "@/apis/dashboard";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import { ref, computed, onMounted, watch, onUnmounted } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const weeks = ["週日", "週一", "週二", "週三", "週四", "週五", "週六"];
|
||||
@ -11,32 +11,30 @@ const weeks = ["週日", "週一", "週二", "週三", "週四", "週五", "週
|
||||
const data = ref([]);
|
||||
const getEnergyData = async () => {
|
||||
const res = await getDashboardEnergy();
|
||||
console.log(res.data);
|
||||
data.value = res.data;
|
||||
if (res.isSuccess) {
|
||||
data.value = res.data?.electric || [];
|
||||
} else {
|
||||
console.error("獲取用電量數據失敗:", res.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 假資料產生器
|
||||
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
|
||||
})),
|
||||
}));
|
||||
}
|
||||
let timer = null;
|
||||
|
||||
onMounted(() => {
|
||||
// getEnergyData();
|
||||
getEnergyData();
|
||||
timer = setInterval(() => {
|
||||
getEnergyData();
|
||||
}, 600000); // 每10分鐘
|
||||
});
|
||||
|
||||
// // 每10分鐘
|
||||
// setInterval(() => {
|
||||
// getEnergyData();
|
||||
// }, 600000);
|
||||
|
||||
data.value = generateFakeData();
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
if (electricity_chart.value && electricity_chart.value.chart) {
|
||||
electricity_chart.value.chart.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const electricity_chart = ref(null);
|
||||
|
@ -2,7 +2,7 @@
|
||||
import LineChart from "@/components/chart/LineChart.vue";
|
||||
import { SECOND_CHART_COLOR } from "@/constant";
|
||||
import dayjs from "dayjs";
|
||||
import { ref, inject, onMounted, watch } from "vue";
|
||||
import { ref, onMounted, watch, onUnmounted } from "vue";
|
||||
import { getDashboardTemp } from "@/apis/dashboard";
|
||||
import useSearchParams from "@/hooks/useSearchParam";
|
||||
|
||||
@ -55,86 +55,55 @@ const defaultChartOption = ref({
|
||||
|
||||
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 getData = async (timeInterval) => {
|
||||
// showChartLoading(frozen_temp_chart.value.chart);
|
||||
const getData = async () => {
|
||||
const res = await getDashboardTemp({
|
||||
timeInterval, // 時間間隔=>1.4.8
|
||||
timeInterval: 1, // 時間間隔=>1.4.8
|
||||
tempOption: 2, // 1:即時溫度;2:冷藏溫度
|
||||
});
|
||||
console.log(res);
|
||||
if (res.isSuccess) {
|
||||
data.value = res.data?.freezer;
|
||||
data.value = res.data || [];
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
searchParams,
|
||||
(newValue, oldValue) => {
|
||||
if (
|
||||
newValue[intervalType] &&
|
||||
newValue[intervalType] !== oldValue[intervalType]
|
||||
) {
|
||||
window.clearInterval(timeoutTimer.value);
|
||||
getData(parseInt(newValue[intervalType]));
|
||||
timeoutTimer.value = setInterval(() => {
|
||||
getData(parseInt(newValue[intervalType]));
|
||||
}, 60000);
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
watch(data, (newValue) => {
|
||||
if (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: seriesData }, index) => ({
|
||||
name: full_name,
|
||||
type: "line",
|
||||
data: seriesData.map(({ value }) => value),
|
||||
showSymbol: false,
|
||||
itemStyle: {
|
||||
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(() => {
|
||||
// getData(1);
|
||||
// timeoutTimer.value = setInterval(() => {
|
||||
// getData(1);
|
||||
// }, 60000);
|
||||
data.value = generateFakeFrozenData();
|
||||
getData();
|
||||
timer = setInterval(() => {
|
||||
getData();
|
||||
}, 60 * 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -2,28 +2,14 @@
|
||||
import LineChart from "@/components/chart/LineChart.vue";
|
||||
import { SECOND_CHART_COLOR } from "@/constant";
|
||||
import dayjs from "dayjs";
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import { ref, watch, onMounted, onUnmounted } from "vue";
|
||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||
import { getDashboardTemp } from "@/apis/dashboard";
|
||||
import useSearchParams from "@/hooks/useSearchParam";
|
||||
import useDashboardOption from "@/hooks/useDashboardOption";
|
||||
|
||||
const { searchParams } = useSearchParams();
|
||||
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 defaultChartOption = ref({
|
||||
tooltip: {
|
||||
@ -39,10 +25,10 @@ const defaultChartOption = ref({
|
||||
bottom: "0%",
|
||||
},
|
||||
grid: {
|
||||
top: "10%",
|
||||
top: "5%",
|
||||
left: "0%",
|
||||
right: "0%",
|
||||
bottom: "10%",
|
||||
bottom: "20%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
@ -72,7 +58,6 @@ const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
|
||||
const data = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
data.value = generateFakeData();
|
||||
setItems([
|
||||
{
|
||||
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(
|
||||
// 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,
|
||||
// }
|
||||
// );
|
||||
let timer = null;
|
||||
|
||||
// watch(
|
||||
// searchParams,
|
||||
// (newValue, oldValue) => {
|
||||
// if (
|
||||
// newValue[intervalType] &&
|
||||
// newValue[intervalType] !== oldValue[intervalType]
|
||||
// ) {
|
||||
// window.clearInterval(timeoutTimer.value);
|
||||
// getData(parseInt(newValue[intervalType]), selectedBtn.value.typeOption);
|
||||
// timeoutTimer.value = setInterval(() => {
|
||||
// getData(parseInt(newValue[intervalType]), selectedBtn.value.typeOption);
|
||||
// }, 60000);
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// deep: true,
|
||||
// }
|
||||
// );
|
||||
watch(
|
||||
selectedBtn,
|
||||
(newValue) => {
|
||||
window.clearInterval(timer);
|
||||
getData(newValue.key);
|
||||
timer = setInterval(() => {
|
||||
getData(newValue.key);
|
||||
}, 60 * 1000);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(data, (newValue) => {
|
||||
// clearChart(other_real_temp_chart.value.chart);
|
||||
@ -157,10 +117,10 @@ watch(data, (newValue) => {
|
||||
xAxis: {
|
||||
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,
|
||||
type: "line",
|
||||
data: data.map(({ value }) => value),
|
||||
data: seriesData.map(({ value }) => value),
|
||||
showSymbol: false,
|
||||
itemStyle: {
|
||||
color: SECOND_CHART_COLOR[index],
|
||||
@ -170,6 +130,13 @@ watch(data, (newValue) => {
|
||||
}
|
||||
// other_real_temp_chart.value.chart.hideLoading();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,51 +1,29 @@
|
||||
<script setup>
|
||||
import LiquidfillChart from "@/components/chart/LiquidfillChart.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 dayjs from "dayjs";
|
||||
|
||||
// 假資料
|
||||
const production_data = ref([]);
|
||||
|
||||
const descriptionCards = 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 = [
|
||||
{
|
||||
title: "發酵槽",
|
||||
inventory: 38,
|
||||
targetInventory: 12,
|
||||
lastIncrease: 12,
|
||||
updateTime: "2025-05-16 15:30",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
];
|
||||
const getCompletion = async () => {
|
||||
const res = await getSettingInventory();
|
||||
descriptionCards.value = res.data.map((item) => ({
|
||||
title: item.name,
|
||||
percentage: item.percentage,
|
||||
current_stock: item.current_stock,
|
||||
safety_stock: item.safety_stock,
|
||||
updateTime: dayjs(item.updated_at).format("YYYY-MM-DD HH:mm:ss"),
|
||||
}));
|
||||
};
|
||||
|
||||
provide("dashboard_product_complete", { getCompletion, progress_data });
|
||||
|
||||
onMounted(() => {
|
||||
getCompletion();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -53,30 +31,13 @@ onMounted(() => {
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-info font-bold text-xl text-center">原醋即時庫存量</h3>
|
||||
</div>
|
||||
|
||||
<div class="w-full grid grid-cols-3">
|
||||
<div>
|
||||
<div v-for="(card, idx) in descriptionCards.slice(0, 3)" :key="idx">
|
||||
<LiquidfillChart
|
||||
id="dashboard_mesona_production"
|
||||
title="發酵槽"
|
||||
:value="production_data[0]"
|
||||
color="#facd91"
|
||||
/>
|
||||
</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"
|
||||
:id="`dashboard_product_${idx}`"
|
||||
:title="card.title || ''"
|
||||
:value="card.percentage || 0"
|
||||
:color="idx === 0 ? '#facd91' : idx === 1 ? '#7dd7df' : '#8cce30'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -86,9 +47,8 @@ onMounted(() => {
|
||||
v-for="(card, index) in descriptionCards"
|
||||
:key="index"
|
||||
:title="card.title"
|
||||
:inventory="card.inventory"
|
||||
:targetInventory="card.targetInventory"
|
||||
:lastIncrease="card.lastIncrease"
|
||||
:safety_stock="card.safety_stock"
|
||||
:current_stock="card.current_stock"
|
||||
:updateTime="card.updateTime"
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, defineProps, inject, watch } from "vue";
|
||||
import { ref, computed, defineProps, inject, watch } from "vue";
|
||||
import * as yup from "yup";
|
||||
import "yup-phone-lite";
|
||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||
@ -11,35 +11,35 @@ const props = defineProps({
|
||||
formState: Object,
|
||||
itemData: Array,
|
||||
});
|
||||
const emit = defineEmits(["pushData"]);
|
||||
const emit = defineEmits(["pushData", "resetModalForm"]);
|
||||
|
||||
const dateItem = ref([
|
||||
{
|
||||
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("必填"),
|
||||
const settingScheme = yup.object({
|
||||
total_capacity: 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 } =
|
||||
useFormErrorMessage(itemScheme);
|
||||
useFormErrorMessage(itemScheme.value);
|
||||
|
||||
const onCancel = () => {
|
||||
handleErrorReset();
|
||||
emit("resetModalForm");
|
||||
inventory_setting_modal.close();
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
const value = await handleSubmit(itemScheme, props.formState);
|
||||
const value = await handleSubmit(itemScheme.value, props.formState);
|
||||
emit("pushData", { ...value });
|
||||
onCancel();
|
||||
};
|
||||
@ -48,55 +48,46 @@ const onOk = async () => {
|
||||
<template>
|
||||
<Modal
|
||||
id="inventory_setting_modal"
|
||||
:title="
|
||||
props.formState?.start_time ? '修改原醋庫存列表' : '新增原醋庫存列表'
|
||||
"
|
||||
:title="props.formState?.type == 1 ? '設定' : '入庫'"
|
||||
:onCancel="onCancel"
|
||||
:width="710"
|
||||
>
|
||||
<template #modalContent>
|
||||
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
|
||||
<DateGroup
|
||||
class="my-2"
|
||||
:items="dateItem"
|
||||
inputClass="w-full shadow-none"
|
||||
:required="true"
|
||||
>
|
||||
<template #topLeft>日期</template>
|
||||
<template #bottomLeft
|
||||
><span class="text-error text-base">
|
||||
{{ formErrorMsg.start_time }}
|
||||
</span></template
|
||||
>
|
||||
</DateGroup>
|
||||
<Select
|
||||
<Input :value="formState" class="my-2" name="name" readonly>
|
||||
<template #topLeft>項目</template>
|
||||
</Input>
|
||||
<Input
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="itemName"
|
||||
Attribute="full_name"
|
||||
:options="props.itemData"
|
||||
name="total_capacity"
|
||||
v-if="formState?.type == 1"
|
||||
>
|
||||
<template #topLeft>品名</template>
|
||||
<template #topLeft>總庫存量</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">{{
|
||||
formErrorMsg.itemName
|
||||
formErrorMsg.total_capacity
|
||||
}}</span>
|
||||
</template>
|
||||
</Select>
|
||||
<Input :value="formState" class="my-2" name="safety_stock">
|
||||
<template #topLeft>安全庫存量</template>
|
||||
</Input>
|
||||
<Input
|
||||
:value="formState"
|
||||
class="my-2"
|
||||
name="safety_stock"
|
||||
v-if="formState?.type == 1"
|
||||
>
|
||||
<template #topLeft>安全存量</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">{{
|
||||
formErrorMsg.safety_stock
|
||||
}}</span>
|
||||
</template>
|
||||
</Input>
|
||||
<Input :value="formState" class="my-2" name="target_stock">
|
||||
<template #topLeft>目標庫存量</template>
|
||||
<Input :value="formState" class="my-2" name="current_stock">
|
||||
<template #topLeft>目前存量</template>
|
||||
<template #bottomLeft>
|
||||
<span class="text-error text-base">{{
|
||||
formErrorMsg.target_stock
|
||||
formErrorMsg.current_stock
|
||||
}}</span>
|
||||
</template>
|
||||
</Input>
|
||||
|
@ -1,33 +1,35 @@
|
||||
<script setup>
|
||||
import Table from "@/components/customUI/Table.vue";
|
||||
import InventorySettingAddModal from "./InventorySettingAddModal.vue";
|
||||
import { postChangeGroupValue, getCheckWeigher } from "@/apis/productSetting";
|
||||
import { ref, onMounted, inject } from "vue";
|
||||
import {
|
||||
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";
|
||||
|
||||
const { openToast } = inject("app_toast");
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "日期",
|
||||
key: "date",
|
||||
},
|
||||
{
|
||||
title: "品名",
|
||||
key: "full_name",
|
||||
},
|
||||
{
|
||||
title: "安全庫存量",
|
||||
key: "safety",
|
||||
},
|
||||
{
|
||||
title: "目標庫存量",
|
||||
key: "target",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
},
|
||||
const settingColumns = [
|
||||
{ title: "項目", key: "name" },
|
||||
{ title: "總庫存量", key: "total_capacity" },
|
||||
{ title: "安全存量", key: "safety_stock" },
|
||||
{ title: "目前存量", key: "current_stock" },
|
||||
{ title: "更新時間", key: "updated_at" },
|
||||
{ title: "功能", key: "operation" },
|
||||
];
|
||||
|
||||
const recordColumns = [
|
||||
{ title: "項目", key: "item_name", filter: true },
|
||||
{ title: "總庫存量", key: "total_capacity" },
|
||||
{ title: "安全存量", key: "safety_stock" },
|
||||
{ title: "目前存量", key: "current_stock" },
|
||||
{ title: "操作類型", key: "type" },
|
||||
{ title: "最後修改者", key: "user_name", filter: true },
|
||||
{ title: "日期", key: "time", sort: true },
|
||||
];
|
||||
|
||||
const dataSource = ref([]);
|
||||
@ -53,35 +55,38 @@ const dateRange = ref([
|
||||
]);
|
||||
|
||||
const searchState = ref({
|
||||
itemName: "all",
|
||||
start_time: dayjs().subtract(30, "day").format("YYYY-MM-DD"),
|
||||
end_time: dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
|
||||
const formState = ref({
|
||||
start_time: "",
|
||||
itemName: 1,
|
||||
safety_stock: 0,
|
||||
target_stock: 0,
|
||||
});
|
||||
const formState = ref({});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const getDataSource = async () => {
|
||||
loading.value = true;
|
||||
// const res = await getCheckWeigher();
|
||||
// dataSource.value = res.data;
|
||||
const res = await getSettingInventory();
|
||||
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;
|
||||
};
|
||||
|
||||
const pushDataSource = async (data) => {
|
||||
loading.value = true;
|
||||
// 檢查是新增還是修改
|
||||
|
||||
loading.value = false;
|
||||
const pushDataSource = async (value) => {
|
||||
const res = await postSettingInventory(value);
|
||||
if (res.isSuccess) {
|
||||
getDataSource();
|
||||
} else {
|
||||
openToast("error", res.msg, "#account_user_modal");
|
||||
}
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
const onSearch = async () => {
|
||||
// 搜尋邏輯
|
||||
searchState.value.start_time = dayjs(dateRange.value[0].value).format(
|
||||
"YYYY-MM-DD"
|
||||
@ -90,81 +95,122 @@ const onSearch = () => {
|
||||
"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) => {
|
||||
if (record) {
|
||||
formState.value = { ...record };
|
||||
const openModal = (type, item_id, record) => {
|
||||
if (type === 1) {
|
||||
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 {
|
||||
resetModalForm();
|
||||
formState.value = {
|
||||
type: 2,
|
||||
item_id: item_id,
|
||||
name: record.name,
|
||||
current_stock: record.current_stock,
|
||||
};
|
||||
}
|
||||
inventory_setting_modal.showModal();
|
||||
};
|
||||
|
||||
const resetModalForm = () => {
|
||||
formState.value = {
|
||||
start_time: "",
|
||||
itemName: 1,
|
||||
safety_stock: 0,
|
||||
target_stock: 0,
|
||||
};
|
||||
formState.value = {};
|
||||
};
|
||||
|
||||
// 刪除功能
|
||||
const removeAccount = async () => {};
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-start items-center mb-3">
|
||||
<h3 class="text-xl mr-5">原醋庫存列表</h3>
|
||||
<InventorySettingAddModal
|
||||
@pushData="pushDataSource"
|
||||
:formState="formState"
|
||||
:itemData="itemData"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-success mr-3"
|
||||
@click.stop.prevent="openModal(null)"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'plus']" />新增
|
||||
</button>
|
||||
</div>
|
||||
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
|
||||
<template #beforeTable>
|
||||
<div class="flex items-center gap-5 mb-8">
|
||||
<Select
|
||||
:value="searchState"
|
||||
class=""
|
||||
selectClass="border-info focus-within:border-info"
|
||||
name="itemName"
|
||||
Attribute="full_name"
|
||||
:options="[{ full_name: '全品項', key: 'all' }, ...itemData]"
|
||||
>
|
||||
</Select>
|
||||
<DateGroup :items="dateRange" :withLine="true" />
|
||||
<button class="btn btn-outline-success" @click.stop.prevent="onSearch">
|
||||
<InventorySettingAddModal
|
||||
@pushData="pushDataSource"
|
||||
:formState="formState"
|
||||
:itemData="itemData"
|
||||
@resetModalForm="resetModalForm"
|
||||
/>
|
||||
<div class="w-full custom-border p-4 mb-4">
|
||||
<div class="flex flex-wrap items-center justify-start">
|
||||
<ButtonGroup
|
||||
:items="items"
|
||||
:withLine="true"
|
||||
class="mr-5"
|
||||
:onclick="(e, item) => changeActiveBtn(item)"
|
||||
/>
|
||||
<template v-if="selectedBtn?.key === 'record'">
|
||||
<DateGroup
|
||||
:items="dateRange"
|
||||
:withLine="true"
|
||||
:isTopLabelExist="false"
|
||||
:isBottomLabelExist="false"
|
||||
class="mr-5"
|
||||
/>
|
||||
<button class="btn btn-success" @click.stop.prevent="onSearch">
|
||||
搜尋
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
:columns="selectedBtn?.key === 'setting' ? settingColumns : recordColumns"
|
||||
:dataSource="dataSource"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #bodyCell="{ record, column, index }">
|
||||
<template v-if="column.key === 'operation'">
|
||||
<button
|
||||
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
|
||||
class="btn btn-sm btn-error text-white"
|
||||
@click.stop.prevent="() => removeAccount(record.id)"
|
||||
class="btn btn-sm btn-success text-white mr-2"
|
||||
@click.stop.prevent="() => openModal(2, record.item_id, record)"
|
||||
:disabled="record?.item_id == 2"
|
||||
>
|
||||
刪除
|
||||
入庫
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
Loading…
Reference in New Issue
Block a user