fix ProductEdit.vue style
This commit is contained in:
@@ -38,6 +38,10 @@ html.theme-dark body {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
.cell-button {
|
.cell-button {
|
||||||
--td-button-border-radius: 1rem;
|
--td-button-border-radius: 1rem;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<t-input v-model.trim="keyword" class="search" borderless clearable placeholder="搜索产品名称或编码" />
|
<t-input v-model.trim="keyword" class="search" borderless clearable placeholder="搜索产品名称或编码" />
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<t-cell-group v-if="filteredProductList.length" theme="card">
|
<t-cell-group v-if="filteredProductList.length" theme="card">
|
||||||
<t-cell v-for="product in filteredProductList" :key="product.id" class="product" :title="product.name" :description="`${product.code || '-'} · ${product.baseUnit || '-'}`">
|
<t-cell v-for="product in filteredProductList" :key="product.id" class="product" :title="product.name" :description="`${product.code || '-'} · ${getProductBaseUnitName(product) || '-'}`">
|
||||||
<template #rightIcon>
|
<template #rightIcon>
|
||||||
<div class="units">
|
<div class="units">
|
||||||
<t-button
|
<t-button
|
||||||
@@ -43,7 +43,7 @@ import ProductAPI from "@/api/ProductAPI";
|
|||||||
import type { Product, ProductUnit } from "@/types/Product";
|
import type { Product, ProductUnit } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { formatProductAmount } from "@/utils/product";
|
import { formatProductAmount, getProductBaseUnitName } from "@/utils/product";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: { productId: string; productUnitId: string } | undefined;
|
modelValue: { productId: string; productUnitId: string } | undefined;
|
||||||
|
|||||||
@@ -371,9 +371,9 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
|
|
||||||
.item {
|
.item {
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
|
border: 1px dashed var(--td-component-border);
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
margin-bottom: .75rem;
|
margin-bottom: .75rem;
|
||||||
border: 1px dashed var(--td-component-border);
|
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-can="permissionCodes.product.read" class="product-detail">
|
<div v-can="permissionCodes.product.read" class="product-detail">
|
||||||
<t-loading v-if="isLoading" size="32px" />
|
<t-loading v-if="isLoading" class="loading" size="32px" />
|
||||||
<template v-if="product">
|
<template v-if="product">
|
||||||
<card-container class="info">
|
<card-container class="info" v-entrance-transition>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<t-image
|
<t-image
|
||||||
@@ -38,10 +38,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</card-container>
|
</card-container>
|
||||||
<t-cell-group title="基本信息" theme="card">
|
<t-cell-group title="基本信息" theme="card" v-entrance-transition="70">
|
||||||
<t-cell title="产品分类" :note="product.category?.name || '未分类'" />
|
<t-cell title="产品分类" :note="product.category?.name || '未分类'" />
|
||||||
<t-cell title="基础单位" :note="product.baseUnit || '-'" />
|
<t-cell title="基础单位" :note="getProductBaseUnitName(product) || '-'" />
|
||||||
<t-cell title="参考成本" :note="formatProductAmount(product.referenceCost)" />
|
|
||||||
<t-cell title="库存管理" :note="product.stockEnabled ? '启用' : '关闭'" />
|
<t-cell title="库存管理" :note="product.stockEnabled ? '启用' : '关闭'" />
|
||||||
<t-cell title="状态" :note="getProductStatusText(product.status)" />
|
<t-cell title="状态" :note="getProductStatusText(product.status)" />
|
||||||
<t-cell title="备注" :note="Text.display(product.remark)"></t-cell>
|
<t-cell title="备注" :note="Text.display(product.remark)"></t-cell>
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
/>
|
/>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<t-cell-group title="业务单位" theme="card" class="units">
|
<t-cell-group title="业务单位" theme="card" class="units" v-entrance-transition="140">
|
||||||
<t-cell
|
<t-cell
|
||||||
v-for="unit in product.unitList || []"
|
v-for="unit in product.unitList || []"
|
||||||
:key="unit.id || unit.name"
|
:key="unit.id || unit.name"
|
||||||
@@ -68,7 +67,7 @@
|
|||||||
v-if="!unit.isBase"
|
v-if="!unit.isBase"
|
||||||
theme="default"
|
theme="default"
|
||||||
size="small"
|
size="small"
|
||||||
:content="`${unit.baseQuantityPerUnit} ${product.baseUnit}`"
|
:content="`${unit.baseQuantityPerUnit} ${getProductBaseUnitName(product)}`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -119,11 +118,12 @@ import PhotoScroller from "@/components/PhotoScroller.vue";
|
|||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { NavBarAction } from "@/types/NavBarAction";
|
import type { NavBarAction } from "@/types/NavBarAction";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import type { Product, ProductUnit } from "@/types/Product";
|
import type { Product } from "@/types/Product";
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import {
|
import {
|
||||||
formatProductAmount,
|
formatProductAmount,
|
||||||
|
getProductBaseUnitName,
|
||||||
getProductPhotoURL,
|
getProductPhotoURL,
|
||||||
getProductStatusText,
|
getProductStatusText,
|
||||||
getProductStatusTheme,
|
getProductStatusTheme,
|
||||||
@@ -164,17 +164,6 @@ const actionList = computed<NavBarAction[]>(() => {
|
|||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getUnitUsageText(unit: ProductUnit): string {
|
|
||||||
const usage: string[] = [];
|
|
||||||
if (unit.allowSale) {
|
|
||||||
usage.push("可销售");
|
|
||||||
}
|
|
||||||
if (unit.allowPurchase) {
|
|
||||||
usage.push("可采购");
|
|
||||||
}
|
|
||||||
return usage.length ? usage.join("、") : "暂无业务用途";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function load(): Promise<void> {
|
async function load(): Promise<void> {
|
||||||
const id = String(route.params.id || "");
|
const id = String(route.params.id || "");
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
|||||||
@@ -1,32 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-can="savePermissionCode" class="product-edit">
|
<div v-can="savePermissionCode" class="product-edit">
|
||||||
<t-form class="form" label-align="left" label-width="6rem">
|
<t-form class="form" label-align="left" label-width="6rem" v-entrance-transition>
|
||||||
<t-form-item label="产品编码" required-mark>
|
<t-form-item label="产品编码" required-mark help="使用英语、拼音或唯一数字">
|
||||||
<t-input v-model.trim="form.code" borderless maxlength="64" placeholder="门店内唯一编码" />
|
<t-input
|
||||||
|
v-model="form.code"
|
||||||
|
borderless
|
||||||
|
maxlength="64"
|
||||||
|
placeholder="门店内唯一编码"
|
||||||
|
/>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="产品名称" required-mark>
|
<t-form-item label="产品名称" required-mark>
|
||||||
<t-input v-model.trim="form.name" borderless maxlength="128" placeholder="请输入产品名称" />
|
<t-input
|
||||||
|
v-model="form.name"
|
||||||
|
borderless
|
||||||
|
maxlength="128"
|
||||||
|
placeholder="请输入产品名称"
|
||||||
|
/>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="产品类型" arrow content-align="right" @click="openTypePicker">
|
<t-form-item
|
||||||
|
label="产品类型"
|
||||||
|
arrow
|
||||||
|
content-align="right"
|
||||||
|
@click="openTypePicker"
|
||||||
|
>
|
||||||
<span class="picker-text" v-text="getProductTypeText(form.productType)" />
|
<span class="picker-text" v-text="getProductTypeText(form.productType)" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="产品分类" arrow content-align="right" @click="openCategoryPicker">
|
<t-form-item label="产品分类" arrow content-align="right" @click="openCategoryPicker">
|
||||||
<span class="picker-text" v-text="selectedCategoryName" />
|
<span class="picker-text" v-text="selectedCategoryName" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="基础单位" required-mark>
|
<t-form-item label="库存管理" content-align="right">
|
||||||
<t-input v-model.trim="form.baseUnit" borderless maxlength="32" placeholder="例如 次、瓶" />
|
<t-switch size="small" v-model="form.stockEnabled" :disabled="form.productType === 'SERVICE'" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="参考成本(元)">
|
<t-form-item label="允许小数" content-align="right">
|
||||||
<t-input v-model.trim="referenceCostText" type="number" align="right" borderless placeholder="非负金额" />
|
<t-switch size="small" v-model="form.allowDecimalQuantity" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="库存管理">
|
<t-form-item v-if="form.stockEnabled" label="最低库存" help="基础单位数量,最多三位小数">
|
||||||
<t-checkbox v-model="form.stockEnabled" :disabled="form.productType === 'SERVICE'">管理库存</t-checkbox>
|
<t-input
|
||||||
</t-form-item>
|
v-model="minStockQuantityText"
|
||||||
<t-form-item label="允许小数">
|
type="number"
|
||||||
<t-checkbox v-model="form.allowDecimalQuantity">业务数量允许小数</t-checkbox>
|
align="right"
|
||||||
</t-form-item>
|
borderless
|
||||||
<t-form-item v-if="form.stockEnabled" label="最低库存">
|
/>
|
||||||
<t-input v-model.trim="minStockQuantityText" type="number" align="right" borderless placeholder="基础单位数量,最多三位小数" />
|
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="状态" arrow content-align="right" @click="openStatusPicker">
|
<t-form-item label="状态" arrow content-align="right" @click="openStatusPicker">
|
||||||
<span class="picker-text" v-text="getProductStatusText(form.status)" />
|
<span class="picker-text" v-text="getProductStatusText(form.status)" />
|
||||||
@@ -34,9 +48,7 @@
|
|||||||
<t-form-item label="备注">
|
<t-form-item label="备注">
|
||||||
<t-textarea v-model.trim="form.remark" borderless maxlength="255" autosize placeholder="可填写产品说明" />
|
<t-textarea v-model.trim="form.remark" borderless maxlength="255" autosize placeholder="可填写产品说明" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
</t-form>
|
<t-form-item label-width="0">
|
||||||
|
|
||||||
<card-container title="产品附件" description="可上传产品图片或其他产品文件" v-entrance-transition="70">
|
|
||||||
<photo-scroller
|
<photo-scroller
|
||||||
v-model="attachmentUrlList"
|
v-model="attachmentUrlList"
|
||||||
v-model:photo-items="attachmentItemList"
|
v-model:photo-items="attachmentItemList"
|
||||||
@@ -48,41 +60,76 @@
|
|||||||
@add-photo="openAttachmentPicker('PHOTO')"
|
@add-photo="openAttachmentPicker('PHOTO')"
|
||||||
@add-other="openAttachmentPicker('OTHER')"
|
@add-other="openAttachmentPicker('OTHER')"
|
||||||
/>
|
/>
|
||||||
<input ref="attachmentInputRef" class="file-input" type="file" accept="image/*" @change="handleAttachmentSelected" />
|
|
||||||
</card-container>
|
|
||||||
|
|
||||||
<card-container title="产品单位" description="必须保留一个基础单位;销售和库存业务只能选择允许的单位" v-entrance-transition="70">
|
|
||||||
<div class="unit-list">
|
|
||||||
<div v-for="(unit, index) in form.unitList" :key="unit.clientKey" class="unit-item">
|
|
||||||
<div class="unit-header">
|
|
||||||
<strong v-text="`单位 ${index + 1}`" />
|
|
||||||
<t-button v-if="1 < form.unitList.length" theme="danger" variant="text" size="small" @click="removeUnit(index)">删除</t-button>
|
|
||||||
</div>
|
|
||||||
<t-form label-width="6rem">
|
|
||||||
<t-form-item label="单位名称" required-mark>
|
|
||||||
<t-input v-model.trim="unit.name" borderless maxlength="32" placeholder="例如 瓶、盒、次" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="基础单位数量" required-mark>
|
|
||||||
<t-input v-model.trim="unit.baseQuantityPerUnit" type="number" align="right" borderless placeholder="每个单位对应的基础单位数量" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="建议售价(元)">
|
|
||||||
<t-input v-model.trim="unit.salePriceText" type="number" align="right" borderless placeholder="非负金额" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="基础单位">
|
|
||||||
<t-checkbox v-model="unit.isBase" @change="setBaseUnit(index)">基础单位</t-checkbox>
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="允许采购">
|
|
||||||
<t-checkbox v-model="unit.allowPurchase">入库可用</t-checkbox>
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="允许销售">
|
|
||||||
<t-checkbox v-model="unit.allowSale">销售和套餐可用</t-checkbox>
|
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
</t-form>
|
</t-form>
|
||||||
</div>
|
<card-container
|
||||||
<t-button class="add-unit" theme="primary" variant="dashed" block @click="addUnit">添加业务单位</t-button>
|
title="产品单位"
|
||||||
|
description="必须有且仅有一个基础单位;销售和库存业务只能选择允许的单位"
|
||||||
|
v-entrance-transition="70"
|
||||||
|
>
|
||||||
|
<div class="units">
|
||||||
|
<t-form
|
||||||
|
class="unit"
|
||||||
|
v-for="(unit, index) in form.unitList"
|
||||||
|
:key="unit.clientKey"
|
||||||
|
>
|
||||||
|
<t-form-item label="单位名称" required-mark>
|
||||||
|
<t-input v-model.trim="unit.name" borderless maxlength="32" placeholder="如 瓶、次" />
|
||||||
|
<!-- <t-button v-if="1 < form.unitList.length" theme="danger" variant="text" size="small" @click="removeUnit(index)">删除</t-button>-->
|
||||||
|
<t-button
|
||||||
|
theme="danger"
|
||||||
|
variant="text"
|
||||||
|
size="extra-small"
|
||||||
|
content="删除"
|
||||||
|
@click="removeUnit(index)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="基础单位" content-align="right">
|
||||||
|
<t-switch
|
||||||
|
size="small"
|
||||||
|
v-model="unit.isBase"
|
||||||
|
:disabled="form.unitList.length === 1"
|
||||||
|
@change="setBaseUnit(index)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item
|
||||||
|
label="基础单位数量"
|
||||||
|
help="每个单位对应的基础单位数量"
|
||||||
|
label-width="7rem"
|
||||||
|
required-mark
|
||||||
|
>
|
||||||
|
<t-input
|
||||||
|
v-model="unit.baseQuantityPerUnit"
|
||||||
|
type="number"
|
||||||
|
align="right"
|
||||||
|
borderless
|
||||||
|
:disabled="form.unitList.length === 1"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="建议售价">
|
||||||
|
<t-input
|
||||||
|
v-model="unit.salePriceText"
|
||||||
|
type="number"
|
||||||
|
align="right"
|
||||||
|
borderless
|
||||||
|
placeholder="金额(元)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="允许采购" content-align="right">
|
||||||
|
<t-switch size="small" v-model="unit.allowPurchase" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="允许销售" content-align="right">
|
||||||
|
<t-switch size="small" v-model="unit.allowSale" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
<t-button
|
||||||
|
class="cell-button"
|
||||||
|
theme="primary"
|
||||||
|
content="添加业务单位"
|
||||||
|
@click="addUnit"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</card-container>
|
</card-container>
|
||||||
|
|
||||||
<t-popup v-model="typePickerVisible" placement="bottom">
|
<t-popup v-model="typePickerVisible" placement="bottom">
|
||||||
<t-picker v-model="typeDraft" :columns="productTypeOptions" title="产品类型" @cancel="typePickerVisible = false" @confirm="confirmType" />
|
<t-picker v-model="typeDraft" :columns="productTypeOptions" title="产品类型" @cancel="typePickerVisible = false" @confirm="confirmType" />
|
||||||
</t-popup>
|
</t-popup>
|
||||||
@@ -92,6 +139,7 @@
|
|||||||
<t-popup v-model="statusPickerVisible" placement="bottom">
|
<t-popup v-model="statusPickerVisible" placement="bottom">
|
||||||
<t-picker v-model="statusDraft" :columns="statusOptions" title="产品状态" @cancel="statusPickerVisible = false" @confirm="confirmStatus" />
|
<t-picker v-model="statusDraft" :columns="statusOptions" title="产品状态" @cancel="statusPickerVisible = false" @confirm="confirmStatus" />
|
||||||
</t-popup>
|
</t-popup>
|
||||||
|
<input ref="attachmentInputRef" class="file-input" type="file" accept="image/*" @change="handleAttachmentSelected" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -131,14 +179,12 @@ const form = reactive<Product & { unitList: ProductUnitForm[] }>({
|
|||||||
code: "",
|
code: "",
|
||||||
name: "",
|
name: "",
|
||||||
productType: "GOODS",
|
productType: "GOODS",
|
||||||
baseUnit: "",
|
|
||||||
stockEnabled: true,
|
stockEnabled: true,
|
||||||
allowDecimalQuantity: false,
|
allowDecimalQuantity: false,
|
||||||
status: "ACTIVE",
|
status: "ACTIVE",
|
||||||
unitList: []
|
unitList: []
|
||||||
});
|
});
|
||||||
const referenceCostText = ref("");
|
const minStockQuantityText = ref("0");
|
||||||
const minStockQuantityText = ref("");
|
|
||||||
const categoryList = ref<ProductCategory[]>([]);
|
const categoryList = ref<ProductCategory[]>([]);
|
||||||
const typePickerVisible = ref(false);
|
const typePickerVisible = ref(false);
|
||||||
const categoryPickerVisible = ref(false);
|
const categoryPickerVisible = ref(false);
|
||||||
@@ -331,19 +377,14 @@ function parseOptionalAmount(value: string): number | null | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validate(): boolean {
|
function validate(): boolean {
|
||||||
if (!form.code.trim() || !form.name.trim() || !form.baseUnit.trim()) {
|
if (!form.code.trim() || !form.name.trim()) {
|
||||||
Message.warning("产品编码、名称和基础单位不能为空");
|
Message.warning("产品编码和名称不能为空");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (form.productType === "SERVICE" && form.stockEnabled) {
|
if (form.productType === "SERVICE" && form.stockEnabled) {
|
||||||
Message.warning("服务产品不能管理库存");
|
Message.warning("服务产品不能管理库存");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const referenceCost = parseOptionalAmount(referenceCostText.value);
|
|
||||||
if (referenceCost === undefined) {
|
|
||||||
Message.warning("价格必须是合法的非负元金额,最多两位小数");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (form.stockEnabled && !normalizeProductQuantity(minStockQuantityText.value || "0")) {
|
if (form.stockEnabled && !normalizeProductQuantity(minStockQuantityText.value || "0")) {
|
||||||
Message.warning("最低库存必须是非负数量,最多三位小数");
|
Message.warning("最低库存必须是非负数量,最多三位小数");
|
||||||
return false;
|
return false;
|
||||||
@@ -377,10 +418,6 @@ function validate(): boolean {
|
|||||||
Message.warning("基础单位数量必须为 1");
|
Message.warning("基础单位数量必须为 1");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (name !== form.baseUnit.trim()) {
|
|
||||||
Message.warning("基础单位名称必须与产品基础单位一致");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (baseCount !== 1) {
|
if (baseCount !== 1) {
|
||||||
@@ -404,11 +441,9 @@ async function save(): Promise<void> {
|
|||||||
code: form.code.trim(),
|
code: form.code.trim(),
|
||||||
name: form.name.trim(),
|
name: form.name.trim(),
|
||||||
productType: form.productType,
|
productType: form.productType,
|
||||||
baseUnit: form.baseUnit.trim(),
|
|
||||||
stockEnabled: form.stockEnabled,
|
stockEnabled: form.stockEnabled,
|
||||||
allowDecimalQuantity: form.allowDecimalQuantity,
|
allowDecimalQuantity: form.allowDecimalQuantity,
|
||||||
status: form.status,
|
status: form.status,
|
||||||
referenceCost: parseOptionalAmount(referenceCostText.value)!,
|
|
||||||
minStockQuantity: form.stockEnabled ? minStockQuantity : null,
|
minStockQuantity: form.stockEnabled ? minStockQuantity : null,
|
||||||
remark: form.remark?.trim() || null,
|
remark: form.remark?.trim() || null,
|
||||||
attachmentList: buildAttachmentList(),
|
attachmentList: buildAttachmentList(),
|
||||||
@@ -456,7 +491,6 @@ async function load(): Promise<void> {
|
|||||||
code: result.code || "",
|
code: result.code || "",
|
||||||
name: result.name || "",
|
name: result.name || "",
|
||||||
productType: result.productType || "GOODS",
|
productType: result.productType || "GOODS",
|
||||||
baseUnit: result.baseUnit || "",
|
|
||||||
stockEnabled: !!result.stockEnabled,
|
stockEnabled: !!result.stockEnabled,
|
||||||
allowDecimalQuantity: !!result.allowDecimalQuantity,
|
allowDecimalQuantity: !!result.allowDecimalQuantity,
|
||||||
status: result.status || "ACTIVE",
|
status: result.status || "ACTIVE",
|
||||||
@@ -471,7 +505,6 @@ async function load(): Promise<void> {
|
|||||||
previewUrl: getAttachmentReadURL(item.id || ""),
|
previewUrl: getAttachmentReadURL(item.id || ""),
|
||||||
url: getAttachmentReadURL(item.id || "", 90)
|
url: getAttachmentReadURL(item.id || "", 90)
|
||||||
}));
|
}));
|
||||||
referenceCostText.value = result.referenceCost === null || result.referenceCost === undefined ? "" : `${Math.floor(result.referenceCost / 100)}.${String(result.referenceCost % 100).padStart(2, "0")}`;
|
|
||||||
minStockQuantityText.value = result.minStockQuantity === null || result.minStockQuantity === undefined ? "" : String(result.minStockQuantity);
|
minStockQuantityText.value = result.minStockQuantity === null || result.minStockQuantity === undefined ? "" : String(result.minStockQuantity);
|
||||||
if (!form.unitList.length) {
|
if (!form.unitList.length) {
|
||||||
addUnit();
|
addUnit();
|
||||||
@@ -508,6 +541,24 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.units {
|
||||||
|
padding: 1rem 0;
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
gap: .5rem;
|
||||||
|
border: 1px dashed var(--td-component-border);
|
||||||
|
padding: .5rem 0;
|
||||||
|
margin-bottom: .75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.unit-list {
|
.unit-list {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
|
|||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { NavBarAction } from "@/types/NavBarAction";
|
import type { NavBarAction } from "@/types/NavBarAction";
|
||||||
import type { Product, ProductCategory, ProductStatus, ProductType } from "@/types/Product";
|
import type { Product, ProductCategory, ProductStatus, ProductType } from "@/types/Product";
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { getProductPhotoURL, getProductStatusText, getProductStatusTheme, getProductTypeText } from "@/utils/product";
|
import { getProductPhotoURL, getProductStatusText, getProductStatusTheme, getProductTypeText } from "@/utils/product";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="hero-meta">
|
<div class="hero-meta">
|
||||||
<span v-text="product.code || '无产品编码'" />
|
<span v-text="product.code || '无产品编码'" />
|
||||||
<strong v-text="`${getProductQuantityText(stock.availableQuantity)} ${product.baseUnit || ''}`" />
|
<strong v-text="`${getProductQuantityText(stock.availableQuantity)} ${getProductBaseUnitName(product)}`" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<card-container title="库存余额">
|
<card-container title="库存余额">
|
||||||
<t-cell-group theme="card" class="summary">
|
<t-cell-group theme="card" class="summary">
|
||||||
<t-cell title="产品" :note="product.name || '未命名产品'" />
|
<t-cell title="产品" :note="product.name || '未命名产品'" />
|
||||||
<t-cell title="基础单位" :note="product.baseUnit || '-'" />
|
<t-cell title="基础单位" :note="getProductBaseUnitName(product) || '-'" />
|
||||||
<t-cell title="实际库存" :note="getProductQuantityText(stock.onHandQuantity)" />
|
<t-cell title="实际库存" :note="getProductQuantityText(stock.onHandQuantity)" />
|
||||||
<t-cell title="预占库存" :note="getProductQuantityText(stock.reservedQuantity)" />
|
<t-cell title="预占库存" :note="getProductQuantityText(stock.reservedQuantity)" />
|
||||||
<t-cell title="可用库存" :note="getProductQuantityText(stock.availableQuantity)" />
|
<t-cell title="可用库存" :note="getProductQuantityText(stock.availableQuantity)" />
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<div class="record-description">
|
<div class="record-description">
|
||||||
<span v-text="`${record.quantity || 0} ${record.unitSnapshot || product.baseUnit || ''} · 基础单位 ${record.baseQuantity || 0}`" />
|
<span v-text="`${record.quantity || 0} ${record.unitSnapshot || getProductBaseUnitName(product)} · 基础单位 ${record.baseQuantity || 0}`" />
|
||||||
<span class="gray" v-text="record.reason || record.remark || '无备注'" />
|
<span class="gray" v-text="record.reason || record.remark || '无备注'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -72,7 +72,7 @@ import ProductStockOperationDialog from "@/pages/product/ProductStockOperationDi
|
|||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { Product, ProductStock, StockOperationType, StockRecord } from "@/types/Product";
|
import type { Product, ProductStock, StockOperationType, StockRecord } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { getProductQuantityText, getStockOperationText } from "@/utils/product";
|
import { getProductBaseUnitName, getProductQuantityText, getStockOperationText } from "@/utils/product";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #note>
|
<template #note>
|
||||||
<span v-text="`${getProductQuantityText(item.availableQuantity)} ${item.product?.baseUnit || ''}`" />
|
<span v-text="`${getProductQuantityText(item.availableQuantity)} ${getProductBaseUnitName(item.product)}`" />
|
||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
@@ -35,7 +35,7 @@ import ProductAPI from "@/api/ProductAPI";
|
|||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { ProductStock } from "@/types/Product";
|
import type { ProductStock } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { getProductQuantityText } from "@/utils/product";
|
import { getProductBaseUnitName, getProductQuantityText } from "@/utils/product";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="operation-dialog">
|
<div class="operation-dialog">
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
<strong v-text="props.product.name || '未命名产品'" />
|
<strong v-text="props.product.name || '未命名产品'" />
|
||||||
<span class="gray" v-text="`当前可用库存:${getProductQuantityText(props.stock?.availableQuantity)} ${props.product.baseUnit || ''}`" />
|
<span class="gray" v-text="`当前可用库存:${getProductQuantityText(props.stock?.availableQuantity)} ${getProductBaseUnitName(props.product)}`" />
|
||||||
</div>
|
</div>
|
||||||
<t-form label-align="left" label-width="5rem">
|
<t-form label-align="left" label-width="5rem">
|
||||||
<t-form-item label="业务单位" required-mark @click="openUnitPicker">
|
<t-form-item label="业务单位" required-mark @click="openUnitPicker">
|
||||||
@@ -39,7 +39,7 @@ import { watch } from "vue";
|
|||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
import type { Product, ProductStock, ProductUnit, ProductStockChangeRequest, StockOperationType } from "@/types/Product";
|
import type { Product, ProductStock, ProductUnit, ProductStockChangeRequest, StockOperationType } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { createProductRequestNo, getProductQuantityText, getStockOperationText } from "@/utils/product";
|
import { createProductRequestNo, getProductBaseUnitName, getProductQuantityText, getStockOperationText } from "@/utils/product";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
|
|
||||||
|
|||||||
@@ -40,11 +40,9 @@ export type Product = {
|
|||||||
code: string;
|
code: string;
|
||||||
name: string;
|
name: string;
|
||||||
productType: ProductType;
|
productType: ProductType;
|
||||||
baseUnit: string;
|
|
||||||
stockEnabled: boolean;
|
stockEnabled: boolean;
|
||||||
allowDecimalQuantity: boolean;
|
allowDecimalQuantity: boolean;
|
||||||
status?: ProductStatus;
|
status?: ProductStatus;
|
||||||
referenceCost?: number | null;
|
|
||||||
minStockQuantity?: number | string | null;
|
minStockQuantity?: number | string | null;
|
||||||
sort?: number;
|
sort?: number;
|
||||||
remark?: string | null;
|
remark?: string | null;
|
||||||
|
|||||||
@@ -89,3 +89,8 @@ export function getProductQuantityText(value?: number | string | null): string {
|
|||||||
}
|
}
|
||||||
return normalizeProductQuantity(value) || "0";
|
return normalizeProductQuantity(value) || "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 从产品单位列表中获取基础单位名称。 */
|
||||||
|
export function getProductBaseUnitName(product?: Product): string {
|
||||||
|
return product?.unitList?.find(unit => unit.isBase)?.name || "";
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user