add product
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="product-selector">
|
||||
<t-form-item :label="props.label" :required-mark="props.requiredMark" arrow @click="open">
|
||||
<span class="picker-text" v-text="selectedText || props.placeholder" />
|
||||
</t-form-item>
|
||||
<t-popup v-model="visible" placement="bottom" :style="{height: '80vh'}">
|
||||
<div class="popup">
|
||||
<div class="header">
|
||||
<t-button variant="text" size="small" @click="visible = false">取消</t-button>
|
||||
<strong>选择产品单位</strong>
|
||||
<t-button theme="primary" variant="text" size="small" @click="confirm">确定</t-button>
|
||||
</div>
|
||||
<t-input v-model.trim="keyword" class="search" borderless clearable placeholder="搜索产品名称或编码" />
|
||||
<div class="list">
|
||||
<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 || '-'}`">
|
||||
<template #rightIcon>
|
||||
<div class="units">
|
||||
<t-button
|
||||
v-for="unit in getSaleableUnits(product)"
|
||||
:key="unit.id"
|
||||
:theme="isSelected(product, unit) ? 'primary' : 'default'"
|
||||
:variant="isSelected(product, unit) ? 'base' : 'outline'"
|
||||
size="small"
|
||||
@click.stop="select(product, unit)"
|
||||
>
|
||||
<span class="unit-name" v-text="unit.name" />
|
||||
<span v-if="unit.salePrice !== undefined && unit.salePrice !== null" class="unit-price" v-text="formatProductAmount(unit.salePrice)" />
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<div v-else class="empty" v-text="loading ? '正在加载可售产品' : '暂无可售产品'" />
|
||||
</div>
|
||||
</div>
|
||||
</t-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import type { Product, ProductUnit } from "@/types/Product";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { formatProductAmount } from "@/utils/product";
|
||||
|
||||
interface Props {
|
||||
modelValue: { productId: string; productUnitId: string } | undefined;
|
||||
label?: string;
|
||||
requiredMark?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { label: "产品", requiredMark: false, placeholder: "请选择产品单位" });
|
||||
const emit = defineEmits<{ "update:modelValue": [value: Props["modelValue"]] }>();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const keyword = ref("");
|
||||
const productList = ref<Product[]>([]);
|
||||
const draft = ref<Props["modelValue"]>();
|
||||
const selectedText = computed(() => {
|
||||
if (!props.modelValue) {
|
||||
return "";
|
||||
}
|
||||
const product = productList.value.find(item => item.id === props.modelValue?.productId);
|
||||
const unit = product ? product.unitList?.find(item => item.id === props.modelValue?.productUnitId) : undefined;
|
||||
return product && unit ? `${product.name} · ${unit.name}` : "已选择产品单位";
|
||||
});
|
||||
const filteredProductList = computed(() => {
|
||||
const text = keyword.value.toLocaleLowerCase();
|
||||
return productList.value.filter(item => !text || `${item.name}${item.code}`.toLocaleLowerCase().includes(text));
|
||||
});
|
||||
|
||||
async function open(): Promise<void> {
|
||||
visible.value = true;
|
||||
draft.value = props.modelValue ? { ...props.modelValue } : undefined;
|
||||
if (productList.value.length) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
productList.value = await ProductAPI.sellableList({ index: 0, size: 1000 });
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "可售产品加载失败"));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getSaleableUnits(product: Product): ProductUnit[] {
|
||||
return (product.unitList || []).filter(item => item.allowSale !== false && item.id);
|
||||
}
|
||||
function isSelected(product: Product, unit: ProductUnit): boolean {
|
||||
return draft.value?.productId === product.id && draft.value?.productUnitId === unit.id;
|
||||
}
|
||||
function select(product: Product, unit: ProductUnit): void {
|
||||
if (product.id && unit.id) {
|
||||
draft.value = { productId: product.id, productUnitId: unit.id };
|
||||
}
|
||||
}
|
||||
function confirm(): void {
|
||||
if (draft.value) {
|
||||
emit("update:modelValue", draft.value);
|
||||
visible.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.product-selector {
|
||||
.picker-text {
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.popup {
|
||||
height: 100%;
|
||||
padding: 1rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 0 1rem .75rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 1rem .75rem;
|
||||
border: 1px solid var(--td-component-border);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.product {
|
||||
.units {
|
||||
gap: .35rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
.t-button {
|
||||
gap: .125rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.unit-price {
|
||||
font-size: .6875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--td-text-color-secondary);
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user