forked from admin/deShanXiao
354 lines
8.2 KiB
Vue
354 lines
8.2 KiB
Vue
<template>
|
||
<div class="service-selection">
|
||
<!-- 标题 -->
|
||
<h2 class="title">服务项目选择</h2>
|
||
<!-- 搜索框 -->
|
||
|
||
<el-form>
|
||
<el-row :gutter="15" style="margin-top: 15px">
|
||
<el-col :span="8">
|
||
<el-form-item label="商品名称">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="请输入商品名称"
|
||
clearable />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="16">
|
||
<el-form-item label="价格">
|
||
<el-input-number
|
||
v-model="price"
|
||
:min="0"
|
||
clearable
|
||
placeholder="请输入价格"
|
||
type="number"></el-input-number>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<el-divider style="margin: 8px 0; margin-bottom: 30px" />
|
||
<!-- 两列布局 -->
|
||
<div
|
||
class="content"
|
||
v-loading="lodaing"
|
||
element-loading-text="数据加载中....">
|
||
<!-- 左侧树结构 -->
|
||
<div class="left">
|
||
<el-tree
|
||
ref="tree"
|
||
:data="categoryTree"
|
||
:props="treeProps"
|
||
show-checkbox
|
||
check-strictly
|
||
@check="getCheckedKeys" />
|
||
</div>
|
||
|
||
<!-- 右侧商品列表 -->
|
||
<div class="right">
|
||
<div
|
||
v-for="(item, index) in categoryServiceList"
|
||
:key="index"
|
||
class="item">
|
||
<!-- 商品信息 -->
|
||
<div class="item-info">
|
||
<div class="item-name">商品名称:{{ item.name }}</div>
|
||
<div class="item-group">
|
||
所属分类:{{
|
||
categoryTreeAll.find((fitem) => fitem.id === item.parentId)
|
||
?.name || "无"
|
||
}}
|
||
</div>
|
||
<div class="item-price">
|
||
{{ item.price }} 元 / {{ item.unit || "(暂无单位)" }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加/删除按钮 -->
|
||
<div class="item-actions">
|
||
<!-- <el-button
|
||
v-if="!item.quantity || item.quantity === 0"
|
||
type="primary"
|
||
@click="addItem(item)">
|
||
<span style="color: #fff">+ 添加</span>
|
||
</el-button>
|
||
<div v-else class="quantity-control">
|
||
<el-button type="danger" @click="removeItem(item)">-</el-button>
|
||
<span class="quantity">{{ item.quantity }}</span>
|
||
<el-button type="primary" @click="addItem(item)">+ </el-button>
|
||
</div> -->
|
||
|
||
<el-input-number
|
||
style="margin-top: 5px"
|
||
v-model="item.quantity"
|
||
:min="0"
|
||
@change="methods.quantityChange(item)" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 底部悬浮框 -->
|
||
<div class="footer">
|
||
<div class="footer-item">
|
||
<span>总金额:</span>
|
||
<span
|
||
><span class="red-font">{{ totalAmount }} </span>元</span
|
||
>
|
||
</div>
|
||
<!-- <div class="footer-item">
|
||
<span>减免金额:</span>
|
||
<span
|
||
><span class="green-font">
|
||
{{ discountAmount }}
|
||
</span>
|
||
元</span
|
||
>
|
||
</div>
|
||
<div class="footer-item">
|
||
<span>实收金额:</span>
|
||
<span
|
||
><span class="red-font">
|
||
{{ actualAmount }}
|
||
</span>
|
||
元</span
|
||
>
|
||
</div> -->
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, watch } from "vue";
|
||
import api from "@/lib/request";
|
||
|
||
const emits = defineEmits(["selectedProduct"]);
|
||
const products = defineModel<Product[]>();
|
||
|
||
// 搜索关键词
|
||
const searchKeyword = ref("");
|
||
const price = ref(0);
|
||
|
||
const lodaing = ref(false);
|
||
|
||
// 分类树数据
|
||
const categoryTree = ref([]);
|
||
const categoryTreeAll = ref([]);
|
||
const categoryServiceAll = ref<Product[]>([]);
|
||
const selectedCategory = ref([]);
|
||
const tree = ref();
|
||
|
||
const categoryServiceList = computed(() => {
|
||
let resData: Product[] = [];
|
||
selectedCategory.value.forEach((item) => {
|
||
let findData = categoryServiceAll.value.filter(
|
||
(fitem) => fitem.parentId === item.id
|
||
);
|
||
if (findData) {
|
||
resData = [...resData, ...findData];
|
||
}
|
||
});
|
||
let res = selectedCategory.value.length ? resData : categoryServiceAll.value;
|
||
|
||
if (searchKeyword.value) {
|
||
res = res.filter((item) => item.name.includes(searchKeyword.value));
|
||
let includeCategory = categoryTreeAll.value.filter((category) =>
|
||
category.name.includes(searchKeyword.value)
|
||
);
|
||
categoryServiceAll.value.forEach((item) => {
|
||
includeCategory.forEach((iitem) => {
|
||
if (
|
||
item.parentId === iitem.id &&
|
||
!res.find((fitem) => fitem.name === item.name)
|
||
) {
|
||
res.unshift(item);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
if (price.value && price.value !== 0) {
|
||
res = res.filter((fitem) => Number(fitem.price) === price.value);
|
||
}
|
||
return res;
|
||
});
|
||
|
||
// 树结构配置
|
||
const treeProps = {
|
||
children: "children",
|
||
label: "name",
|
||
};
|
||
|
||
const syncProductsWithCategoryServiceAll = () => {
|
||
if (!products.value) return; // 防止 products 为空时报错
|
||
|
||
categoryServiceAll.value.forEach((item) => {
|
||
const findItem = products.value?.find(
|
||
(newItem) => newItem.name === item.name
|
||
);
|
||
if (findItem) {
|
||
item.quantity = findItem.quantity;
|
||
} else {
|
||
item.quantity = 0;
|
||
}
|
||
});
|
||
};
|
||
|
||
watch(
|
||
() => products.value,
|
||
(newVal) => {
|
||
syncProductsWithCategoryServiceAll();
|
||
},
|
||
{
|
||
deep: true,
|
||
immediate: true,
|
||
}
|
||
);
|
||
|
||
// 总金额
|
||
const totalAmount = computed(() => {
|
||
return categoryServiceAll.value.reduce(
|
||
(sum, item) => sum + item.price * (item.quantity || 0),
|
||
0
|
||
);
|
||
});
|
||
|
||
const methods = {
|
||
quantityChange(item: any) {
|
||
let findData = products.value?.find(
|
||
(findItem) => findItem.name === item.name
|
||
);
|
||
if (!findData) {
|
||
products.value?.push(item);
|
||
} else {
|
||
findData.quantity = item.quantity;
|
||
}
|
||
},
|
||
};
|
||
|
||
const getCheckedKeys = (data: any) => {
|
||
selectedCategory.value = tree.value.getCheckedNodes();
|
||
};
|
||
|
||
onMounted(async () => {
|
||
lodaing.value = true;
|
||
let treeList = await api().get("/service-category/list");
|
||
let allTreeList = await api().get("/service-category/list", {
|
||
params: { all: true },
|
||
});
|
||
let allService = await api().get("/service-item/list", {
|
||
params: { all: true },
|
||
});
|
||
|
||
categoryTree.value = treeList.data.list;
|
||
categoryServiceAll.value = allService.data.list;
|
||
categoryTreeAll.value = allTreeList.data.list;
|
||
|
||
syncProductsWithCategoryServiceAll();
|
||
|
||
lodaing.value = false;
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.service-selection {
|
||
position: relative;
|
||
overflow: hidden;
|
||
.title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
.search-box {
|
||
margin-bottom: 20px;
|
||
width: 50%;
|
||
}
|
||
.left {
|
||
width: 200px;
|
||
margin-right: 20px;
|
||
}
|
||
|
||
.right {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding-bottom: 30px;
|
||
max-height: calc(100vh - 150px);
|
||
flex-wrap: wrap;
|
||
display: flex;
|
||
width: 100%;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
.content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
.item {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
width: calc(50% - 10px);
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
padding: 10px;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
flex-direction: column;
|
||
// .item-image {
|
||
// width: 100px;
|
||
// height: 100px;
|
||
// margin-right: 10px;
|
||
// }
|
||
.item-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.item-name {
|
||
font-weight: bold;
|
||
}
|
||
|
||
.item-category,
|
||
.item-group,
|
||
.item-price {
|
||
font-size: 12px;
|
||
color: #666;
|
||
}
|
||
.item-actions {
|
||
margin-left: 10px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.quantity-control {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.quantity {
|
||
margin: 0 10px;
|
||
}
|
||
|
||
.footer {
|
||
position: fixed;
|
||
bottom: 0;
|
||
right: 0;
|
||
width: 50%;
|
||
z-index: 10;
|
||
background: #fff;
|
||
padding: 10px 20px;
|
||
border-top: 1px solid #eee;
|
||
display: flex;
|
||
justify-content: space-around;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||
.footer-item {
|
||
font-size: 14px;
|
||
}
|
||
.red-font,
|
||
.green-font {
|
||
font-size: 18px;
|
||
color: #f04b22;
|
||
font-weight: bold;
|
||
padding-right: 8px;
|
||
}
|
||
.green-font {
|
||
color: #67c23a;
|
||
}
|
||
}
|
||
</style>
|