新增庫存設定相關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,
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,
});
}
};

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 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,
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,
});
};

View File

@ -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,
});

View File

@ -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

View File

@ -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 };

View File

@ -1,30 +1,26 @@
<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,
},
});
</script>
</script>

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>