fix ProductEdit.vue style
This commit is contained in:
@@ -371,9 +371,9 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
|
||||
.item {
|
||||
gap: .5rem;
|
||||
border: 1px dashed var(--td-component-border);
|
||||
padding: .5rem 1rem;
|
||||
margin-bottom: .75rem;
|
||||
border: 1px dashed var(--td-component-border);
|
||||
border-radius: 1rem;
|
||||
|
||||
.header {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<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">
|
||||
<card-container class="info">
|
||||
<card-container class="info" v-entrance-transition>
|
||||
<div class="content">
|
||||
<div class="left">
|
||||
<t-image
|
||||
@@ -38,10 +38,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</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.baseUnit || '-'" />
|
||||
<t-cell title="参考成本" :note="formatProductAmount(product.referenceCost)" />
|
||||
<t-cell title="基础单位" :note="getProductBaseUnitName(product) || '-'" />
|
||||
<t-cell title="库存管理" :note="product.stockEnabled ? '启用' : '关闭'" />
|
||||
<t-cell title="状态" :note="getProductStatusText(product.status)" />
|
||||
<t-cell title="备注" :note="Text.display(product.remark)"></t-cell>
|
||||
@@ -56,7 +55,7 @@
|
||||
/>
|
||||
</t-cell>
|
||||
</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
|
||||
v-for="unit in product.unitList || []"
|
||||
:key="unit.id || unit.name"
|
||||
@@ -68,7 +67,7 @@
|
||||
v-if="!unit.isBase"
|
||||
theme="default"
|
||||
size="small"
|
||||
:content="`${unit.baseQuantityPerUnit} ${product.baseUnit}`"
|
||||
:content="`${unit.baseQuantityPerUnit} ${getProductBaseUnitName(product)}`"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,11 +118,12 @@ import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { NavBarAction } from "@/types/NavBarAction";
|
||||
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 { getErrorMessage } from "@/utils/error";
|
||||
import {
|
||||
formatProductAmount,
|
||||
getProductBaseUnitName,
|
||||
getProductPhotoURL,
|
||||
getProductStatusText,
|
||||
getProductStatusTheme,
|
||||
@@ -164,17 +164,6 @@ const actionList = computed<NavBarAction[]>(() => {
|
||||
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> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) {
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
<template>
|
||||
<div v-can="savePermissionCode" class="product-edit">
|
||||
<t-form class="form" label-align="left" label-width="6rem">
|
||||
<t-form-item label="产品编码" required-mark>
|
||||
<t-input v-model.trim="form.code" borderless maxlength="64" placeholder="门店内唯一编码" />
|
||||
<t-form class="form" label-align="left" label-width="6rem" v-entrance-transition>
|
||||
<t-form-item label="产品编码" required-mark help="使用英语、拼音或唯一数字">
|
||||
<t-input
|
||||
v-model="form.code"
|
||||
borderless
|
||||
maxlength="64"
|
||||
placeholder="门店内唯一编码"
|
||||
/>
|
||||
</t-form-item>
|
||||
<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 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)" />
|
||||
</t-form-item>
|
||||
<t-form-item label="产品分类" arrow content-align="right" @click="openCategoryPicker">
|
||||
<span class="picker-text" v-text="selectedCategoryName" />
|
||||
</t-form-item>
|
||||
<t-form-item label="基础单位" required-mark>
|
||||
<t-input v-model.trim="form.baseUnit" borderless maxlength="32" placeholder="例如 次、瓶" />
|
||||
<t-form-item label="库存管理" content-align="right">
|
||||
<t-switch size="small" v-model="form.stockEnabled" :disabled="form.productType === 'SERVICE'" />
|
||||
</t-form-item>
|
||||
<t-form-item label="参考成本(元)">
|
||||
<t-input v-model.trim="referenceCostText" type="number" align="right" borderless placeholder="非负金额" />
|
||||
<t-form-item label="允许小数" content-align="right">
|
||||
<t-switch size="small" v-model="form.allowDecimalQuantity" />
|
||||
</t-form-item>
|
||||
<t-form-item label="库存管理">
|
||||
<t-checkbox v-model="form.stockEnabled" :disabled="form.productType === 'SERVICE'">管理库存</t-checkbox>
|
||||
</t-form-item>
|
||||
<t-form-item label="允许小数">
|
||||
<t-checkbox v-model="form.allowDecimalQuantity">业务数量允许小数</t-checkbox>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="form.stockEnabled" label="最低库存">
|
||||
<t-input v-model.trim="minStockQuantityText" type="number" align="right" borderless placeholder="基础单位数量,最多三位小数" />
|
||||
<t-form-item v-if="form.stockEnabled" label="最低库存" help="基础单位数量,最多三位小数">
|
||||
<t-input
|
||||
v-model="minStockQuantityText"
|
||||
type="number"
|
||||
align="right"
|
||||
borderless
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item label="状态" arrow content-align="right" @click="openStatusPicker">
|
||||
<span class="picker-text" v-text="getProductStatusText(form.status)" />
|
||||
@@ -34,55 +48,88 @@
|
||||
<t-form-item label="备注">
|
||||
<t-textarea v-model.trim="form.remark" borderless maxlength="255" autosize placeholder="可填写产品说明" />
|
||||
</t-form-item>
|
||||
<t-form-item label-width="0">
|
||||
<photo-scroller
|
||||
v-model="attachmentUrlList"
|
||||
v-model:photo-items="attachmentItemList"
|
||||
title="附件列表"
|
||||
:edit-mode="true"
|
||||
:loading="isAttachmentUploading"
|
||||
:show-viewer-actions="false"
|
||||
:add-actions="attachmentAddActions"
|
||||
@add-photo="openAttachmentPicker('PHOTO')"
|
||||
@add-other="openAttachmentPicker('OTHER')"
|
||||
/>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
|
||||
<card-container title="产品附件" description="可上传产品图片或其他产品文件" v-entrance-transition="70">
|
||||
<photo-scroller
|
||||
v-model="attachmentUrlList"
|
||||
v-model:photo-items="attachmentItemList"
|
||||
title="附件列表"
|
||||
:edit-mode="true"
|
||||
:loading="isAttachmentUploading"
|
||||
:show-viewer-actions="false"
|
||||
:add-actions="attachmentAddActions"
|
||||
@add-photo="openAttachmentPicker('PHOTO')"
|
||||
@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>
|
||||
</div>
|
||||
<t-button class="add-unit" theme="primary" variant="dashed" block @click="addUnit">添加业务单位</t-button>
|
||||
<card-container
|
||||
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>
|
||||
</card-container>
|
||||
|
||||
<t-popup v-model="typePickerVisible" placement="bottom">
|
||||
<t-picker v-model="typeDraft" :columns="productTypeOptions" title="产品类型" @cancel="typePickerVisible = false" @confirm="confirmType" />
|
||||
</t-popup>
|
||||
@@ -92,6 +139,7 @@
|
||||
<t-popup v-model="statusPickerVisible" placement="bottom">
|
||||
<t-picker v-model="statusDraft" :columns="statusOptions" title="产品状态" @cancel="statusPickerVisible = false" @confirm="confirmStatus" />
|
||||
</t-popup>
|
||||
<input ref="attachmentInputRef" class="file-input" type="file" accept="image/*" @change="handleAttachmentSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -131,14 +179,12 @@ const form = reactive<Product & { unitList: ProductUnitForm[] }>({
|
||||
code: "",
|
||||
name: "",
|
||||
productType: "GOODS",
|
||||
baseUnit: "",
|
||||
stockEnabled: true,
|
||||
allowDecimalQuantity: false,
|
||||
status: "ACTIVE",
|
||||
unitList: []
|
||||
});
|
||||
const referenceCostText = ref("");
|
||||
const minStockQuantityText = ref("");
|
||||
const minStockQuantityText = ref("0");
|
||||
const categoryList = ref<ProductCategory[]>([]);
|
||||
const typePickerVisible = ref(false);
|
||||
const categoryPickerVisible = ref(false);
|
||||
@@ -331,19 +377,14 @@ function parseOptionalAmount(value: string): number | null | undefined {
|
||||
}
|
||||
|
||||
function validate(): boolean {
|
||||
if (!form.code.trim() || !form.name.trim() || !form.baseUnit.trim()) {
|
||||
Message.warning("产品编码、名称和基础单位不能为空");
|
||||
if (!form.code.trim() || !form.name.trim()) {
|
||||
Message.warning("产品编码和名称不能为空");
|
||||
return false;
|
||||
}
|
||||
if (form.productType === "SERVICE" && form.stockEnabled) {
|
||||
Message.warning("服务产品不能管理库存");
|
||||
return false;
|
||||
}
|
||||
const referenceCost = parseOptionalAmount(referenceCostText.value);
|
||||
if (referenceCost === undefined) {
|
||||
Message.warning("价格必须是合法的非负元金额,最多两位小数");
|
||||
return false;
|
||||
}
|
||||
if (form.stockEnabled && !normalizeProductQuantity(minStockQuantityText.value || "0")) {
|
||||
Message.warning("最低库存必须是非负数量,最多三位小数");
|
||||
return false;
|
||||
@@ -377,10 +418,6 @@ function validate(): boolean {
|
||||
Message.warning("基础单位数量必须为 1");
|
||||
return false;
|
||||
}
|
||||
if (name !== form.baseUnit.trim()) {
|
||||
Message.warning("基础单位名称必须与产品基础单位一致");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baseCount !== 1) {
|
||||
@@ -404,11 +441,9 @@ async function save(): Promise<void> {
|
||||
code: form.code.trim(),
|
||||
name: form.name.trim(),
|
||||
productType: form.productType,
|
||||
baseUnit: form.baseUnit.trim(),
|
||||
stockEnabled: form.stockEnabled,
|
||||
allowDecimalQuantity: form.allowDecimalQuantity,
|
||||
status: form.status,
|
||||
referenceCost: parseOptionalAmount(referenceCostText.value)!,
|
||||
minStockQuantity: form.stockEnabled ? minStockQuantity : null,
|
||||
remark: form.remark?.trim() || null,
|
||||
attachmentList: buildAttachmentList(),
|
||||
@@ -456,7 +491,6 @@ async function load(): Promise<void> {
|
||||
code: result.code || "",
|
||||
name: result.name || "",
|
||||
productType: result.productType || "GOODS",
|
||||
baseUnit: result.baseUnit || "",
|
||||
stockEnabled: !!result.stockEnabled,
|
||||
allowDecimalQuantity: !!result.allowDecimalQuantity,
|
||||
status: result.status || "ACTIVE",
|
||||
@@ -471,7 +505,6 @@ async function load(): Promise<void> {
|
||||
previewUrl: getAttachmentReadURL(item.id || ""),
|
||||
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);
|
||||
if (!form.unitList.length) {
|
||||
addUnit();
|
||||
@@ -508,6 +541,24 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
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 {
|
||||
padding: 1rem;
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { NavBarAction } from "@/types/NavBarAction";
|
||||
import type { Product, ProductCategory, ProductStatus, ProductType } from "@/types/Product";
|
||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getProductPhotoURL, getProductStatusText, getProductStatusTheme, getProductTypeText } from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span v-text="product.code || '无产品编码'" />
|
||||
<strong v-text="`${getProductQuantityText(stock.availableQuantity)} ${product.baseUnit || ''}`" />
|
||||
<strong v-text="`${getProductQuantityText(stock.availableQuantity)} ${getProductBaseUnitName(product)}`" />
|
||||
</div>
|
||||
</section>
|
||||
<card-container title="库存余额">
|
||||
<t-cell-group theme="card" class="summary">
|
||||
<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.reservedQuantity)" />
|
||||
<t-cell title="可用库存" :note="getProductQuantityText(stock.availableQuantity)" />
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
<template #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 || '无备注'" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -72,7 +72,7 @@ import ProductStockOperationDialog from "@/pages/product/ProductStockOperationDi
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { Product, ProductStock, StockOperationType, StockRecord } from "@/types/Product";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getProductQuantityText, getStockOperationText } from "@/utils/product";
|
||||
import { getProductBaseUnitName, getProductQuantityText, getStockOperationText } from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #note>
|
||||
<span v-text="`${getProductQuantityText(item.availableQuantity)} ${item.product?.baseUnit || ''}`" />
|
||||
<span v-text="`${getProductQuantityText(item.availableQuantity)} ${getProductBaseUnitName(item.product)}`" />
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
@@ -35,7 +35,7 @@ import ProductAPI from "@/api/ProductAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { ProductStock } from "@/types/Product";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getProductQuantityText } from "@/utils/product";
|
||||
import { getProductBaseUnitName, getProductQuantityText } from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="operation-dialog">
|
||||
<div class="summary">
|
||||
<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>
|
||||
<t-form label-align="left" label-width="5rem">
|
||||
<t-form-item label="业务单位" required-mark @click="openUnitPicker">
|
||||
@@ -39,7 +39,7 @@ import { watch } from "vue";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import type { Product, ProductStock, ProductUnit, ProductStockChangeRequest, StockOperationType } from "@/types/Product";
|
||||
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 type { PhotoItem } from "@/types/Photo";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user