feat(GiftSelect): 新增礼品选择组件

- 实现礼品选择弹窗功能,支持单选和多选模式
- 集成搜索功能,包含礼品编码、名称、规格型号筛选
- 添加价格区间滑块选择器,支持可视化价格范围设置
- 实现库存状态标签显示,区分无库存、低库存和正常库存
- 集成分页表格展示礼品列表,支持复选框批量选择
- 添加库存验证逻辑,防止选择无库存礼品
- 实现已选择礼品标签显示和删除功能
- 集成确认回调机制,支持选择结果回传
dev
zangch@mesnac.com 6 days ago
parent 517b213f1e
commit f4e72ac152

@ -0,0 +1,320 @@
<template>
<div>
<el-dialog v-model="dialog.visible" :title="'礼品选择'" width="80%" top="5vh" append-to-body destroy-on-close>
<el-row :gutter="20">
<el-col :span="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="礼品编码" prop="giftCode">
<el-input v-model="queryParams.giftCode" placeholder="请输入礼品编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="礼品名称" prop="giftName">
<el-input v-model="queryParams.giftName" placeholder="请输入礼品名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="规格型号" prop="specification">
<el-input v-model="queryParams.specification" placeholder="请输入规格型号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="价格区间" prop="priceRange">
<div class="flex items-center" style="width: 260px">
<el-slider v-model="sliderRange" range :min="priceMin" :max="priceMax" :step="priceStep" :marks="priceMarks" style="flex: 1" />
<div class="ml-3 text-sm text-gray-600">{{ sliderRange[0] }} ~ {{ sliderRange[1] }}</div>
</div>
</el-form-item>
<el-form-item label="仅显示有库存" prop="hasStock">
<el-switch v-model="queryParams.hasStock" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="() => resetQuery()">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template v-if="prop.multiple" #header>
<el-tag v-for="gift in selectGiftList" :key="gift.giftId" closable style="margin: 2px" @close="handleCloseTag(gift)">
{{ gift.giftName }}
</el-tag>
</template>
<vxe-table
ref="tableRef"
height="420px"
border
show-overflow
:data="giftList"
:loading="loading"
:row-config="{ keyField: 'giftId', isHover: true }"
:checkbox-config="{ reserve: true, trigger: 'row', highlight: true, showHeader: prop.multiple }"
@checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange"
>
<vxe-column type="checkbox" width="50" align="center" />
<vxe-column key="giftCode" title="礼品编码" align="center" field="giftCode" width="140" />
<vxe-column key="giftName" title="礼品名称" align="center" field="giftName" width="180" />
<vxe-column key="specification" title="规格型号" align="center" field="specification" width="160" />
<vxe-column key="unitPrice" title="采购单价(元)" align="center" field="unitPrice" width="120">
<template #default="scope">{{ scope.row.unitPrice ? '¥' + scope.row.unitPrice : '-' }}</template>
</vxe-column>
<vxe-column key="inventoryQuantity" title="库存数量" align="center" field="inventoryQuantity" width="140">
<template #default="scope">
<el-tag :type="inventoryTagType(scope.row.inventoryQuantity)" disable-transitions>{{ scope.row.inventoryQuantity ?? '-' }}</el-tag>
</template>
</vxe-column>
<vxe-column key="remark" title="备注" align="center" field="remark" min-width="160" show-overflow />
</vxe-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="pageList"
/>
</el-card>
</el-col>
</el-row>
<template #footer>
<el-button @click="close"></el-button>
<el-button type="primary" @click="confirm"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { listCrmGiftInfo } from '@/api/oa/crm/crmGiftInfo';
import { CrmGiftInfoQuery, CrmGiftInfoVO } from '@/api/oa/crm/crmGiftInfo/types';
import { VxeTableInstance } from 'vxe-table';
import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref, watch } from 'vue';
import type { FormInstance } from 'element-plus';
interface PropType {
modelValue?: CrmGiftInfoVO[] | CrmGiftInfoVO | undefined;
multiple?: boolean;
data?: string | number | (string | number)[] | undefined;
maxQuantity?: 'stock' | number;
}
const prop = withDefaults(defineProps<PropType>(), {
multiple: true,
modelValue: undefined,
data: undefined,
maxQuantity: 'stock'
});
const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
// proxy$modalanimate
const proxy = (getCurrentInstance()?.proxy || {}) as any;
const giftList = ref<CrmGiftInfoVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const selectGiftList = ref<CrmGiftInfoVO[]>([]);
const priceMin = 0;
const priceMax = 10000;
const priceStep = 10;
const priceMarks = {
0: '0',
2000: '2000',
5000: '5000',
8000: '8000',
10000: '10000'
};
const sliderRange = ref<[number, number]>([priceMin, priceMax]);
const queryFormRef = ref<FormInstance>();
const tableRef = ref<VxeTableInstance<CrmGiftInfoVO>>();
const dialog = reactive({
visible: false
});
// 使reactive便
const queryParams = reactive<
CrmGiftInfoQuery & {
hasStock?: boolean;
unitPriceMin?: number;
unitPriceMax?: number;
}
>({
pageNum: 1,
pageSize: 10,
giftCode: undefined,
giftName: undefined,
specification: undefined,
unitPriceMin: undefined,
unitPriceMax: undefined,
hasStock: undefined,
params: {}
});
const defaultIds = computed(() => computedIds(prop.data));
const computedIds = (data: any) => {
if (data === '' || data === null || data === undefined) {
return [] as string[];
}
if (data instanceof Array) {
return data.map((item) => String(item));
} else if (typeof data === 'string') {
return data.split(',');
} else if (typeof data === 'number') {
return [String(data)];
} else {
console.warn('<GiftSelect> data type should be array or string or number');
return [];
}
};
/** 查询礼品列表 */
const getList = async () => {
loading.value = true;
const res = await listCrmGiftInfo(queryParams);
giftList.value = res.rows || [];
total.value = res.total || 0;
loading.value = false;
};
const pageList = async () => {
await getList();
const rows = giftList.value.filter((item) => selectGiftList.value.some((g) => String(g.giftId) === String(item.giftId)));
await nextTick(() => tableRef.value?.setCheckboxRow(rows, true));
};
/** 搜索按钮 */
const handleQuery = () => {
queryParams.unitPriceMin = sliderRange.value[0];
queryParams.unitPriceMax = sliderRange.value[1];
queryParams.pageNum = 1;
getList();
};
/** 重置按钮 */
const resetQuery = (refresh = true) => {
queryFormRef.value?.resetFields();
sliderRange.value = [priceMin, priceMax];
queryParams.unitPriceMin = undefined;
queryParams.unitPriceMax = undefined;
queryParams.pageNum = 1;
refresh && handleQuery();
};
const handleCheckboxChange = (checked: any) => {
const row = checked.row as CrmGiftInfoVO;
if (checked.checked) {
//
if (prop.maxQuantity === 'stock' && (!row.inventoryQuantity || row.inventoryQuantity <= 0)) {
proxy?.$modal.msgWarning('库存为0的礼品不可选择');
tableRef.value?.setCheckboxRow(row, false);
return;
}
if (!prop.multiple) {
tableRef.value?.setCheckboxRow(selectGiftList.value, false);
selectGiftList.value = [];
}
selectGiftList.value.push(row);
} else {
selectGiftList.value = selectGiftList.value.filter((item) => String(item.giftId) !== String(row.giftId));
}
};
const handleCheckboxAll = (checked: any) => {
const rows = giftList.value;
if (checked.checked) {
rows.forEach((row) => {
if (prop.maxQuantity === 'stock' && (!row.inventoryQuantity || row.inventoryQuantity <= 0)) {
return;
}
if (!selectGiftList.value.some((item) => String(item.giftId) === String(row.giftId))) {
selectGiftList.value.push(row);
}
});
} else {
selectGiftList.value = selectGiftList.value.filter((item) => !rows.some((row) => String(row.giftId) === String(item.giftId)));
}
};
const handleCloseTag = (gift: CrmGiftInfoVO) => {
const rowKey = String(gift.giftId);
const rows = selectGiftList.value.filter((item) => String(item.giftId) === rowKey);
tableRef.value?.setCheckboxRow(rows, false);
selectGiftList.value = selectGiftList.value.filter((item) => String(item.giftId) !== rowKey);
};
const initSelectGift = async () => {
if (defaultIds.value.length > 0) {
const rows = giftList.value.filter((item) => defaultIds.value.includes(String(item.giftId)));
selectGiftList.value = rows;
await nextTick(() => tableRef.value?.setCheckboxRow(rows, true));
} else if (prop.modelValue) {
const mv = prop.modelValue instanceof Array ? prop.modelValue : [prop.modelValue];
selectGiftList.value = mv;
await nextTick(() => tableRef.value?.setCheckboxRow(mv, true));
}
};
const inventoryTagType = (qty?: number) => {
if (qty === undefined || qty === null) return 'info';
if (qty <= 0) return 'danger';
if (qty <= 10) return 'warning';
return 'success';
};
const confirm = () => {
//
if (prop.maxQuantity === 'stock') {
const invalid = selectGiftList.value.find((item) => !item.inventoryQuantity || item.inventoryQuantity <= 0);
if (invalid) {
proxy?.$modal.msgWarning('存在库存为0的礼品无法确认');
return;
}
}
emit('update:modelValue', selectGiftList.value);
emit('confirmCallBack', selectGiftList.value);
dialog.visible = false;
};
const open = async () => {
dialog.visible = true;
};
const close = () => {
dialog.visible = false;
};
watch(
() => dialog.visible,
async (val) => {
if (val) {
await getList();
await initSelectGift();
} else {
tableRef.value?.clearCheckboxReserve();
tableRef.value?.clearCheckboxRow();
resetQuery(false);
selectGiftList.value = [];
}
}
);
const expose = {
open,
close
};
onMounted(async () => {
//
await getList();
});
defineExpose(expose);
</script>
<style scoped></style>
Loading…
Cancel
Save