zhiqi/src/views/order/intention/Step2.vue

872 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="step2-container">
<div class="main-row">
<div class="step-indicator">
<div :class="['circle', currentStep === 1 ? 'active' : '']">1</div>
<div class="line"></div>
<div :class="['circle', currentStep === 2 ? 'active' : '']">2</div>
</div>
<div class="right-content">
<div v-if="currentStep === 1">
<div class="step-title">第一步:选择车型</div>
<el-form label-width="120px" class="model-form">
<el-form-item label="车型列表" required>
<el-select
v-model="selectedCarTypeProxy"
placeholder="请选择车型"
filterable
@change="handleCarTypeChange"
>
<el-option-group
v-for="group in carTypeOptions"
:key="group.label"
:label="group.label"
>
<el-option
v-for="option in group.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="计划购买数量" required>
<el-input-number v-model="planCount" :min="1" :max="999" />
</el-form-item>
<el-form-item label="计划交付时间" required>
<el-date-picker
v-model="deliveryTime"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="计划交付方式" required>
<el-radio-group v-model="deliveryMethod">
<el-radio label="self">自行提货</el-radio>
<el-radio label="logistics">物流配送</el-radio>
</el-radio-group>
</el-form-item>
<!-- 其他车型的额外表单 -->
<template v-if="selectedCarTypeProxy === '其他'">
<el-form-item label="可参考车型列表">
<el-select
v-model="referenceCarType"
placeholder="请选择参考车型"
filterable
@change="handleReferenceCarTypeChange"
>
<el-option-group
v-for="group in referenceCarTypeOptions"
:key="group.label"
:label="group.label"
>
<el-option
v-for="option in group.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="车型输入">
<el-input v-model="modelInput" placeholder="请输入车型" />
</el-form-item>
<el-form-item label="车型描述">
<el-input
type="textarea"
:rows="5"
v-model="modelDesc"
placeholder="请输入车型描述"
/>
</el-form-item>
</template>
</el-form>
</div>
<div v-else>
<div class="step">
<div class="step-left">
<div class="step-title">
第一步:车型{{
selectedCarTypeProxy === "其他"
? "其他"
: selectedCarTypeProxy
}}
<div
v-if="selectedCarTypeProxy === '其他'"
class="reference-model"
>
参考车型:{{ referenceCarType || "未选择" }}
</div>
</div>
<div
class="step-title"
:style="{
marginTop:
selectedCarTypeProxy === '其他' ? '165px' : '192px',
}"
>
第二步:选择轮对商品
</div>
</div>
<div class="image-box right-align">
<img src="/images/3D.png" alt="3D结构图" class="main-img" />
</div>
</div>
<div class="table-title">商品参数选择</div>
<!-- 一级菜单作为表头 -->
<div
class="header-title"
v-for="item in firstLevelItems"
:key="item.code"
>
<div class="header-code">编号:{{ item.code }}</div>
<div class="header-name">名称:{{ item.label }}</div>
</div>
<div class="tree-table-row">
<!-- 二级菜单作为tab页 -->
<div class="tab-container">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane
v-for="item in secondLevelItems"
:key="item.code"
:name="item.code"
>
<template #label>
<div class="custom-tab-label">
<div class="tab-label-image">
<img
:src="item.image || '/images/part-default.png'"
class="tab-img"
/>
</div>
<div class="tab-label-info">
<div class="tab-label-code">{{ item.code }}</div>
<div class="tab-label-name">{{ item.label }}</div>
</div>
</div>
</template>
<!-- 三级及以下树形结构 -->
<div
v-if="
activeSecondLevelItem &&
activeSecondLevelItem.children &&
activeSecondLevelItem.children.length
"
class="tab-content tab-content-flex"
>
<div class="tab-left">
<el-collapse v-model="collapseActiveNames" accordion>
<el-collapse-item
v-for="thirdLevel in activeSecondLevelItem.children"
:key="thirdLevel.code"
:name="thirdLevel.code"
:disabled="
!thirdLevel.children ||
thirdLevel.children.length === 0
"
>
<template #title>
<span
:class="[
'tree-node',
selectedNode &&
selectedNode.code === thirdLevel.code
? 'active'
: '',
]"
@click.stop="handleNodeClick(thirdLevel)"
>
{{ thirdLevel.label }}
<el-button
v-if="thirdLevel.replaceable"
type="primary"
link
@click.stop="openReplaceDialog(thirdLevel)"
>替换</el-button
>
</span>
</template>
<ul
v-if="
thirdLevel.children &&
thirdLevel.children.length > 0
"
>
<li
v-for="fourthLevel in thirdLevel.children"
:key="fourthLevel.code"
:class="[
'tree-node',
selectedNode &&
selectedNode.code === fourthLevel.code
? 'active'
: '',
]"
@click="handleNodeClick(fourthLevel)"
>
<span>{{ fourthLevel.label }}</span>
<el-button
v-if="fourthLevel.replaceable"
type="primary"
link
@click.stop="openReplaceDialog(fourthLevel)"
>替换</el-button
>
</li>
</ul>
</el-collapse-item>
</el-collapse>
</div>
<div class="tab-right" v-if="selectedNode">
<img
:src="selectedNode.image"
class="detail-img"
v-if="selectedNode.image"
/>
<div class="detail-info">
<div>编号:{{ selectedNode.code }}</div>
<div>名称:{{ selectedNode.label }}</div>
<el-button
v-if="selectedNode.replaceable"
type="primary"
@click="openReplaceDialog(selectedNode)"
>替换</el-button
>
</div>
</div>
<div class="tab-right" v-else>
<span>请选择左侧商品</span>
</div>
</div>
<div v-else class="tab-content no-data">暂无数据</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
<el-dialog
v-model="replaceDialogVisible"
title="特定替换部件"
width="500px"
>
<div class="replace-list">
<div
v-for="item in replaceList"
:key="item.code"
class="replace-item"
:class="{ selected: replaceSelected === item.code }"
@click="replaceSelected = item.code"
>
<img :src="item.image" class="replace-img" v-if="item.image" />
<div>
<div>编号:{{ item.code }}</div>
<div>名称:{{ item.label }}</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="replaceDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleReplace">替换</el-button>
</template>
</el-dialog>
</div>
<div class="btn-row">
<el-button @click="handlePrevStep">上一步</el-button>
<el-button type="primary" @click="handleNextStep">下一步</el-button>
</div>
</div>
</div>
<!-- 车型信息弹窗 -->
<car-model-dialog
v-model="carModelDialogVisible"
:car-model="selectedCarModel"
/>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from "vue";
import {
carTypeOptions,
productTreeData,
replacePartsMap,
} from "@/data/stepMockData";
import CarModelDialog from "@/components/CarModelDialog.vue";
const props = defineProps({
form: Object,
selectedCarType: String,
});
const emit = defineEmits(["update:form", "prev-step", "next-step"]);
const currentStep = ref(1);
const carModelDialogVisible = ref(false);
const selectedCarModel = ref(null);
const referenceCarType = ref("");
const deliveryTime = ref("");
const deliveryMethod = ref("");
// 计算属性:参考车型选项(排除"其他"选项)
const referenceCarTypeOptions = computed(() => {
return carTypeOptions.map((group) => ({
...group,
options: group.options.filter((option) => option.value !== "其他"),
}));
});
// 彻底双向绑定
const selectedCarTypeProxy = computed({
get() {
return props.form?.selectedCarType ?? "其他";
},
set(val) {
if (props.form) {
props.form.selectedCarType = val;
emit("update:form", props.form);
}
},
});
const planCount = computed({
get() {
return props.form?.planCount ?? "";
},
set(val) {
if (props.form) {
props.form.planCount = val;
emit("update:form", props.form);
}
},
});
const modelInput = computed({
get() {
return props.form?.modelInput ?? "";
},
set(val) {
if (props.form) {
props.form.modelInput = val;
emit("update:form", props.form);
}
},
});
const modelDesc = computed({
get() {
return props.form?.modelDesc ?? "";
},
set(val) {
if (props.form) {
props.form.modelDesc = val;
emit("update:form", props.form);
}
},
});
const selectedPartInfo = computed({
get() {
return props.form?.selectedPartInfo ?? null;
},
set(val) {
if (props.form) {
props.form.selectedPartInfo = val;
emit("update:form", props.form);
}
},
});
// 监听车型变化,清空表单
watch(selectedCarTypeProxy, (newVal, oldVal) => {
if (newVal !== oldVal) {
// 清空所有相关字段
if (props.form) {
props.form.planCount = "";
props.form.modelInput = "";
props.form.modelDesc = "";
props.form.selectedPartInfo = null;
emit("update:form", props.form);
}
}
});
// 商品参数选择相关逻辑
const treeData = ref(productTreeData);
const selectedNode = ref(null);
const replaceDialogVisible = ref(false);
const replaceList = ref([]);
const replaceSelected = ref(null);
const activeTab = ref("");
const collapseActiveNames = ref([]);
// 计算一级菜单项
const firstLevelItems = computed(() => treeData.value);
// 计算二级菜单项(只取第一个一级菜单的 children 作为 tab
const secondLevelItems = computed(() => {
return treeData.value.length > 0 ? treeData.value[0].children || [] : [];
});
// 当前激活的二级菜单项
const activeSecondLevelItem = computed(() => {
return secondLevelItems.value.find((item) => item.code === activeTab.value);
});
// 处理tab切换
function handleTabClick(tab) {
activeTab.value = tab.props.name;
selectedNode.value = null;
}
// 处理节点点击
function handleNodeClick(node) {
selectedNode.value = node;
}
// 打开替换对话框
function openReplaceDialog(node) {
replaceList.value = replacePartsMap[node.code] || [];
replaceSelected.value = null;
replaceDialogVisible.value = true;
}
// 处理替换
function handleReplace() {
if (!replaceSelected.value) return;
const newPart = replaceList.value.find(
(item) => item.code === replaceSelected.value
);
if (newPart) {
selectedNode.value.code = newPart.code;
selectedNode.value.label = newPart.label;
selectedNode.value.image = newPart.image;
}
replaceDialogVisible.value = false;
}
const treeProps = {
label: "label",
children: "children",
};
function handleCarTypeChange(value) {
// 只有非"其他"选项才显示车型信息弹窗
if (value !== "其他") {
const selectedOption = carTypeOptions
.flatMap((group) => group.options)
.find((option) => option.value === value);
if (selectedOption) {
selectedCarModel.value = {
name: selectedOption.label,
description: selectedOption.description || "",
specifications: selectedOption.specifications || {},
image: selectedOption.image || "",
};
carModelDialogVisible.value = true;
}
}
}
function handleReferenceCarTypeChange(value) {
// 显示参考车型信息弹窗
const selectedOption = referenceCarTypeOptions.value
.flatMap((group) => group.options)
.find((option) => option.value === value);
if (selectedOption) {
selectedCarModel.value = {
name: selectedOption.label,
description: selectedOption.description || "",
specifications: selectedOption.specifications || {},
image: selectedOption.image || "",
};
carModelDialogVisible.value = true;
}
}
function handlePrevStep() {
if (currentStep.value === 2) {
currentStep.value = 1;
} else {
emit("prev-step");
}
}
function handleNextStep() {
if (currentStep.value === 1) {
currentStep.value = 2;
} else {
emit("next-step");
}
}
function selectFirstNode() {
const thirdLevels = activeSecondLevelItem.value?.children || [];
if (thirdLevels.length > 0) {
// 默认选择第一个节点
selectedNode.value = thirdLevels[0];
// 如果第一个节点有子节点,则展开并选择第一个子节点
if (thirdLevels[0].children && thirdLevels[0].children.length > 0) {
collapseActiveNames.value = [thirdLevels[0].code];
selectedNode.value = thirdLevels[0].children[0];
} else {
collapseActiveNames.value = [];
}
} else {
selectedNode.value = null;
collapseActiveNames.value = [];
}
}
onMounted(() => {
if (secondLevelItems.value.length > 0) {
activeTab.value = secondLevelItems.value[0].code;
}
selectFirstNode();
});
watch(secondLevelItems, (newVal) => {
if (newVal.length > 0) {
activeTab.value = newVal[0].code;
}
selectFirstNode();
});
watch(activeTab, () => {
selectFirstNode();
});
</script>
<style scoped lang="scss">
.step2-container {
background: #fff;
padding: 24px;
border-radius: 8px;
}
.main-row {
display: flex;
align-items: flex-start;
}
.step-indicator {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 24px;
margin-top: 10px;
}
.circle {
width: 32px;
height: 32px;
border-radius: 50%;
background: #ccc;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin-bottom: 8px;
}
.circle.active {
background: #2156f3;
}
.line {
width: 4px;
height: 170px;
background: #ccc;
margin-bottom: 8px;
}
.right-content {
flex: 1;
}
.step {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.step-left {
flex: 1;
margin-right: 10px;
}
.step-title {
font-size: 18px;
margin: 15px 0;
color: #333;
font-weight: 500;
&.active {
color: #2156f3;
}
.reference-model {
font-size: 14px;
color: #666;
margin-top: 8px;
padding-left: 20px;
position: relative;
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
background: #2156f3;
border-radius: 50%;
}
}
}
.image-box.right-align {
display: flex;
justify-content: flex-end;
align-items: flex-start;
margin-bottom: 16px;
}
.table-title {
font-weight: bold;
margin: 16px 0 8px 0;
color: #333;
}
.tree-table-row {
display: flex;
flex-direction: column;
border: 1px solid #bbb;
border-radius: 4px;
padding: 12px;
background: #fff;
}
.header-title {
flex: 1;
padding: 10px;
border-radius: 4px;
}
.header-code,
.header-name {
font-size: 14px;
color: #333;
margin-bottom: 5px;
}
.tab-container {
width: 100%;
}
.tab-header {
background: #f5f7fa;
border-radius: 4px;
padding: 16px;
margin-bottom: 20px;
}
.tab-header-item {
display: flex;
gap: 20px;
align-items: center;
}
.tab-header-image {
width: 200px;
height: 150px;
flex-shrink: 0;
}
.tab-header-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.tab-header-code,
.tab-header-name {
font-size: 14px;
color: #333;
line-height: 1.5;
}
.part-img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 4px;
border: 1px solid #eee;
}
.model-form {
max-width: 900px;
margin-top: 16px;
}
.btn-row {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 20px;
}
.main-img {
width: 580px;
height: 260px;
object-fit: contain;
border-radius: 8px;
border: 1px solid #eee;
}
.custom-tab-label {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 0;
}
.tab-label-image {
width: 80px;
height: 60px;
margin-bottom: 8px;
}
.tab-img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 4px;
border: 1px solid #eee;
}
.tab-label-info {
text-align: center;
}
.tab-label-code {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.tab-label-name {
font-size: 14px;
color: #333;
font-weight: 500;
}
:deep(.el-tabs__item) {
height: auto !important;
padding: 0 20px !important;
}
:deep(.el-tabs__nav) {
display: flex;
gap: 20px;
}
:deep(.el-tabs__item.is-active) {
.tab-label-code,
.tab-label-name {
color: var(--el-color-primary);
}
}
.tree-node {
transition: background 0.2s;
padding: 8px 12px;
cursor: pointer;
border-radius: 4px;
margin-bottom: 4px;
display: flex;
align-items: center;
justify-content: space-between;
}
.tree-node.active {
color: #2156f3;
}
.tree-node:hover {
color: #2156f3;
}
.tab-content-flex {
display: flex;
min-height: 300px;
height: 400px;
max-height: 400px;
overflow: hidden;
}
.tab-left {
width: 260px;
border-right: 1px solid #eee;
padding-right: 16px;
overflow-y: auto;
height: 100%;
}
.tab-right {
flex: 1;
padding-left: 24px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
overflow-y: auto;
height: 100%;
}
.detail-img {
width: 180px;
height: 120px;
object-fit: contain;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 16px;
}
.replace-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.replace-item {
display: flex;
align-items: center;
gap: 16px;
padding: 8px;
border: 1px solid #eee;
border-radius: 4px;
cursor: pointer;
}
.replace-item.selected {
border-color: #2156f3;
}
.replace-img {
width: 80px;
height: 60px;
object-fit: contain;
border-radius: 4px;
border: 1px solid #eee;
}
.no-data {
display: flex;
align-items: center;
justify-content: center;
min-height: 300px;
color: #999;
font-size: 18px;
}
// 新增样式
:deep(.el-collapse-item.is-disabled .el-collapse-item__header) {
cursor: default;
color: #303133;
&:hover {
background-color: transparent;
}
}
:deep(.el-collapse-item.is-disabled .el-collapse-item__arrow) {
display: none;
}
:deep(.el-collapse-item__header) {
padding: 0;
height: auto;
}
:deep(.el-collapse-item__content) {
padding: 0;
padding-left: 16px;
}
:deep(.el-collapse-item__wrap) {
border-bottom: none;
}
</style>