<script setup lang="ts">
|
import { computed, reactive, ref } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
import {
|
createMaterial,
|
getMaterial,
|
getMaterialCategoryList,
|
getMeasureUnitPage,
|
updateMaterial,
|
type MaterialCategory,
|
type MaterialItem,
|
type MeasureUnit,
|
} from "../../../api/basic-data";
|
import { useAuth } from "../../../stores/auth";
|
|
const { state } = useAuth();
|
const id = ref<number>();
|
const saving = ref(false);
|
const categories = ref<MaterialCategory[]>([]);
|
const units = ref<MeasureUnit[]>([]);
|
const form = reactive<MaterialItem>({
|
code: "",
|
name: "",
|
status: 0,
|
isBatchManaged: false,
|
syncMes: true,
|
});
|
const categoryOptions = computed(() => flatten(categories.value));
|
function flatten(
|
list: MaterialCategory[],
|
depth = 0
|
): { id: number; name: string }[] {
|
return list.flatMap(item =>
|
item.id
|
? [
|
{
|
id: item.id,
|
name: `${" ".repeat(depth)}${item.name || "未命名分类"}`,
|
},
|
...flatten(item.children || [], depth + 1),
|
]
|
: []
|
);
|
}
|
function numberValue(value: string | number | undefined) {
|
return value === "" || value === undefined ? undefined : Number(value);
|
}
|
async function loadOptions() {
|
if (!state.accessToken) return;
|
const [categoryData, unitData] = await Promise.all([
|
getMaterialCategoryList(state.accessToken),
|
getMeasureUnitPage(state.accessToken, {
|
pageNo: 1,
|
pageSize: 100,
|
status: 0,
|
}),
|
]);
|
categories.value = categoryData;
|
units.value = unitData.list;
|
}
|
async function save() {
|
if (!form.code.trim() || !form.name.trim())
|
return uni.showToast({ title: "请填写物料编码和名称", icon: "none" });
|
if (!form.categoryId || !form.unitMeasureId)
|
return uni.showToast({ title: "请选择物料分类和计量单位", icon: "none" });
|
if (!state.accessToken || saving.value) return;
|
saving.value = true;
|
try {
|
const data: MaterialItem = {
|
...form,
|
code: form.code.trim(),
|
name: form.name.trim(),
|
categoryId: numberValue(form.categoryId),
|
unitMeasureId: numberValue(form.unitMeasureId),
|
purchasePrice: numberValue(form.purchasePrice),
|
salesPrice: numberValue(form.salesPrice),
|
costPrice: numberValue(form.costPrice),
|
safetyStock: numberValue(form.safetyStock),
|
minStock: numberValue(form.minStock),
|
maxStock: numberValue(form.maxStock),
|
expiryDay: form.isBatchManaged ? numberValue(form.expiryDay) : undefined,
|
};
|
if (id.value)
|
await updateMaterial(state.accessToken, { ...data, id: id.value });
|
else await createMaterial(state.accessToken, data);
|
uni.showToast({ title: "保存成功", icon: "success" });
|
setTimeout(() => uni.navigateBack(), 500);
|
} catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : "保存失败",
|
icon: "none",
|
});
|
} finally {
|
saving.value = false;
|
}
|
}
|
onLoad(async query => {
|
id.value = query.id ? Number(query.id) : undefined;
|
try {
|
await loadOptions();
|
if (id.value && state.accessToken)
|
Object.assign(form, await getMaterial(state.accessToken, id.value));
|
} catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : "数据加载失败",
|
icon: "none",
|
});
|
}
|
});
|
</script>
|
<template>
|
<view class="page">
|
<view class="form-card">
|
<view class="field"><text>物料编码 <text class="required">*</text></text><input v-model="form.code"
|
placeholder="请输入物料编码" /></view>
|
<view class="field"><text>物料名称 <text class="required">*</text></text><input v-model="form.name"
|
placeholder="请输入物料名称" /></view>
|
<view class="field"><text>物料分类 <text class="required">*</text></text>
|
<picker :range="categoryOptions"
|
range-key="name"
|
@change="form.categoryId = categoryOptions[$event.detail.value].id">
|
<view class="picker"><text>{{ categoryOptions.find(x => x.id === form.categoryId)?.name || '请选择分类' }}</text><text class="picker-arrow">›</text></view>
|
</picker>
|
</view>
|
<view class="field"><text>计量单位 <text class="required">*</text></text>
|
<picker :range="units"
|
range-key="name"
|
@change="form.unitMeasureId = units[$event.detail.value].id">
|
<view class="picker"><text>{{ units.find(x => x.id === form.unitMeasureId)?.name || '请选择单位' }}</text><text class="picker-arrow">›</text></view>
|
</picker>
|
</view>
|
<view class="field"><text>规格</text><input v-model="form.specification"
|
placeholder="请输入规格" /></view>
|
<view class="field"><text>采购价</text><input v-model="form.purchasePrice"
|
type="digit"
|
placeholder="0" /></view>
|
<view class="field"><text>销售价</text><input v-model="form.salesPrice"
|
type="digit"
|
placeholder="0" /></view>
|
<view class="field"><text>成本价</text><input v-model="form.costPrice"
|
type="digit"
|
placeholder="0" /></view>
|
<view class="field"><text>安全库存</text><input v-model="form.safetyStock"
|
type="digit"
|
placeholder="0" /></view>
|
<view class="field"><text>最小库存</text><input v-model="form.minStock"
|
type="digit"
|
placeholder="0" /></view>
|
<view class="field"><text>最大库存</text><input v-model="form.maxStock"
|
type="digit"
|
placeholder="0" /></view>
|
<view class="field switch"><text>批次管理</text>
|
<switch :checked="form.isBatchManaged"
|
@change="form.isBatchManaged = $event.detail.value" />
|
</view>
|
<view v-if="form.isBatchManaged"
|
class="field"><text>有效期(天)</text><input v-model="form.expiryDay"
|
type="number"
|
placeholder="请输入有效期" /></view>
|
<view class="field switch"><text>同步 MES</text>
|
<switch :checked="form.syncMes"
|
@change="form.syncMes = $event.detail.value" />
|
</view>
|
<view class="field"><text>状态</text><radio-group @change="form.status = Number($event.detail.value)"><label>
|
<radio :checked="form.status === 0"
|
value="0" />启用
|
</label><label>
|
<radio :checked="form.status === 1"
|
value="1" />停用
|
</label></radio-group></view>
|
<view class="field column"><text>备注</text><textarea v-model="form.remark"
|
placeholder="请输入备注" ></textarea></view>
|
</view><button class="save"
|
:loading="saving"
|
:disabled="saving"
|
@click="save">保存</button>
|
</view>
|
</template>
|
<style lang="scss" scoped>
|
.page {
|
min-height: 100vh;
|
padding: 24rpx 30rpx 70rpx;
|
background: #f6f7f9;
|
}
|
.form-card {
|
background: #fff;
|
border-radius: 16rpx;
|
overflow: hidden;
|
}
|
.field {
|
display: flex;
|
align-items: center;
|
min-height: 96rpx;
|
padding: 0 24rpx;
|
border-bottom: 1rpx solid #eef1f5;
|
gap: 20rpx;
|
}
|
.field > text {
|
width: 180rpx;
|
color: #33455d;
|
font-size: 27rpx;
|
}
|
.field input,
|
.field picker,
|
.picker {
|
flex: 1;
|
color: #1e3049;
|
font-size: 27rpx;
|
}
|
.picker {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
color: #65758a;
|
}
|
.picker > text:first-child {
|
min-width: 0;
|
flex: 1;
|
}
|
.picker-arrow {
|
margin-left: 16rpx;
|
color: #8092a7;
|
font-size: 36rpx;
|
line-height: 1;
|
}
|
.switch {
|
justify-content: space-between;
|
}
|
.switch > text {
|
width: auto;
|
}
|
.field radio-group {
|
flex: 1;
|
display: flex;
|
gap: 30rpx;
|
font-size: 26rpx;
|
}
|
.column {
|
align-items: flex-start;
|
flex-direction: column;
|
padding: 20rpx 24rpx;
|
}
|
.column > text {
|
width: auto;
|
}
|
.column textarea {
|
width: 100%;
|
height: 130rpx;
|
font-size: 27rpx;
|
}
|
.save {
|
margin-top: 32rpx;
|
color: #fff;
|
background: var(--color-primary, #1769d8);
|
border-radius: 12rpx;
|
font-size: 30rpx;
|
}
|
.save::after {
|
border: 0;
|
}
|
</style>
|