fix ProductStockDetail.vue style
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<bottom-popup
|
||||
v-model="visible"
|
||||
:title="getStockOperationText(operation)"
|
||||
height="min(88vh, 52rem)"
|
||||
left-button-text="取消"
|
||||
:right-button-text="isSaving ? '提交中' : '提交'"
|
||||
@left-button-click="visible = false"
|
||||
@right-button-click="submit"
|
||||
>
|
||||
<div class="operation-popup">
|
||||
<div class="summary">
|
||||
<strong v-text="props.product.name || '未命名产品'" />
|
||||
<span class="gray" v-text="`当前可用库存:${getProductQuantityText(props.stock?.availableQuantity)} ${getProductBaseUnitName(props.product)}`" />
|
||||
</div>
|
||||
<t-form label-align="left" label-width="5rem">
|
||||
<t-form-item label="业务单位" required-mark @click="openUnitPicker">
|
||||
<span class="picker-text" v-text="selectedUnitName" />
|
||||
</t-form-item>
|
||||
<t-form-item label="数量" required-mark>
|
||||
<t-input v-model.trim="quantity" type="number" align="right" borderless placeholder="必须大于 0" />
|
||||
</t-form-item>
|
||||
<t-form-item label="原因">
|
||||
<t-input v-model.trim="reason" borderless maxlength="255" placeholder="原因或备注至少填写一项" />
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-textarea v-model.trim="remark" borderless maxlength="500" autosize placeholder="可补充说明" />
|
||||
</t-form-item>
|
||||
<package-photo-field v-model="photoItems" title="操作照片" :required="true" />
|
||||
</t-form>
|
||||
</div>
|
||||
</bottom-popup>
|
||||
<t-popup v-model="unitPickerVisible" placement="bottom">
|
||||
<t-picker v-model="unitDraft" :columns="unitOptions" title="选择业务单位" @cancel="unitPickerVisible = false" @confirm="confirmUnit" />
|
||||
</t-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch } from "vue";
|
||||
import { BottomPopup } from "timi-tdesign-mobile";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import type { Product, ProductStock, ProductUnit, ProductStockChangeRequest, StockOperationType } from "@/types/Product";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { createProductRequestNo, getProductBaseUnitName, getProductQuantityText, getStockOperationText } from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
|
||||
type ManualStockOperation = Extract<StockOperationType, "MANUAL_IN" | "MANUAL_OUT" | "STOCKTAKE_GAIN" | "STOCKTAKE_LOSS">;
|
||||
type PickerValue = string | number;
|
||||
|
||||
interface Props {
|
||||
product: Product;
|
||||
stock?: ProductStock;
|
||||
operation: ManualStockOperation;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<{
|
||||
success: [];
|
||||
}>();
|
||||
const visible = defineModel<boolean>("visible", { default: false });
|
||||
const isSaving = ref(false);
|
||||
const quantity = ref("");
|
||||
const reason = ref("");
|
||||
const remark = ref("");
|
||||
const requestNo = ref("");
|
||||
const photoItems = ref<PhotoItem[]>([]);
|
||||
const selectedUnitId = ref("");
|
||||
const unitPickerVisible = ref(false);
|
||||
const unitDraft = ref<PickerValue[]>([]);
|
||||
|
||||
const isInbound = computed(() => props.operation === "MANUAL_IN" || props.operation === "STOCKTAKE_GAIN");
|
||||
const selectableUnits = computed<ProductUnit[]>(() => (props.product.unitList || []).filter(unit => isInbound.value ? unit.allowPurchase : unit.allowSale));
|
||||
const unitOptions = computed(() => [selectableUnits.value.map(unit => ({ label: unit.name || "未命名单位", value: unit.id || "" }))]);
|
||||
const selectedUnitName = computed(() => selectableUnits.value.find(unit => unit.id === selectedUnitId.value)?.name || "请选择业务单位");
|
||||
|
||||
function resetForm(): void {
|
||||
quantity.value = "";
|
||||
reason.value = "";
|
||||
remark.value = "";
|
||||
requestNo.value = "";
|
||||
photoItems.value = [];
|
||||
selectedUnitId.value = selectableUnits.value[0]?.id || "";
|
||||
}
|
||||
|
||||
function openUnitPicker(): void {
|
||||
if (selectableUnits.value.length) {
|
||||
unitDraft.value = [selectedUnitId.value];
|
||||
unitPickerVisible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function confirmUnit(value: Array<string | number>): void {
|
||||
selectedUnitId.value = String(value[0] || "");
|
||||
unitPickerVisible.value = false;
|
||||
}
|
||||
|
||||
function validate(): boolean {
|
||||
if (!selectedUnitId.value) {
|
||||
Message.warning("请选择业务单位");
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+(?:\.\d{1,3})?$/.test(quantity.value) || !(0 < Number(quantity.value))) {
|
||||
Message.warning("数量必须是大于 0 的数字,最多三位小数");
|
||||
return false;
|
||||
}
|
||||
if (!props.product.allowDecimalQuantity && !/^\d+$/.test(quantity.value)) {
|
||||
Message.warning("该产品不允许输入小数数量");
|
||||
return false;
|
||||
}
|
||||
if (!reason.value.trim() && !remark.value.trim()) {
|
||||
Message.warning("原因或备注至少填写一项");
|
||||
return false;
|
||||
}
|
||||
if (!photoItems.value.some(item => !!item.tempFileId)) {
|
||||
Message.warning("请上传至少一张操作照片");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function buildRequest(): ProductStockChangeRequest {
|
||||
return {
|
||||
requestNo: requestNo.value || (requestNo.value = createProductRequestNo()),
|
||||
productId: props.product.id || "",
|
||||
productUnitId: selectedUnitId.value,
|
||||
quantity: quantity.value,
|
||||
reason: reason.value.trim() || undefined,
|
||||
remark: remark.value.trim() || null,
|
||||
attachmentList: photoItems.value.flatMap(item => item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: item.attachType || "PHOTO" }] : [])
|
||||
};
|
||||
}
|
||||
|
||||
async function submit(): Promise<void> {
|
||||
if (isSaving.value || !validate()) {
|
||||
return;
|
||||
}
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const request = buildRequest();
|
||||
switch (props.operation) {
|
||||
case "MANUAL_IN":
|
||||
await ProductAPI.manualIn(request);
|
||||
break;
|
||||
case "MANUAL_OUT":
|
||||
await ProductAPI.manualOut(request);
|
||||
break;
|
||||
case "STOCKTAKE_GAIN":
|
||||
await ProductAPI.stocktakeGain(request);
|
||||
break;
|
||||
case "STOCKTAKE_LOSS":
|
||||
await ProductAPI.stocktakeLoss(request);
|
||||
break;
|
||||
}
|
||||
visible.value = false;
|
||||
Message.success("库存操作已提交");
|
||||
emit("success");
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "库存操作失败,请确认请求号后重试"));
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => [visible.value, props.operation] as const, ([isVisible]) => {
|
||||
if (isVisible) {
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.operation-popup {
|
||||
.summary {
|
||||
gap: .25rem;
|
||||
display: grid;
|
||||
padding: .5rem 0 1rem;
|
||||
}
|
||||
|
||||
:deep(.t-form__item) {
|
||||
padding: .25rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user