|
@@ -0,0 +1,1655 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="shipping-template-component">
|
|
|
|
|
+ <el-card class="box-card">
|
|
|
|
|
+ <!-- 头部 -->
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <el-icon><Van /></el-icon>
|
|
|
|
|
+ <span>{{ isEditMode ? '编辑运费模板' : '新建运费模板' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form label-width="120px">
|
|
|
|
|
+ <!-- 模板信息 -->
|
|
|
|
|
+ <el-card shadow="never" class="mb-4">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-sub-header">
|
|
|
|
|
+ <span>模板信息</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item :rules="[{ required: true, message: '请输入模板名称', trigger: 'blur' }]" label="模板名称"
|
|
|
|
|
+ prop="templateInfo.name">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="templateInfo.name"
|
|
|
|
|
+ placeholder="例如:全国配送模板"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-item-tip">给模板起一个容易识别的名称</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item :rules="[{ required: true, message: '请选择计费方式', trigger: 'change' }]" label="计费方式"
|
|
|
|
|
+ prop="templateInfo.calcMethod">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="templateInfo.calcMethod"
|
|
|
|
|
+ @change="handleCalcMethodChange"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ placeholder="请选择计费方式"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option label="按件数计算(标准化小商品如服饰)" value="piece" />
|
|
|
|
|
+ <el-option label="按重量计算(重货如五金件)" value="weight" />
|
|
|
|
|
+ <el-option label="按体积计算(轻抛货如泡沫箱、家具)" value="volume" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <div class="form-item-tip">根据商品特性选择合适的计费方式</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 单位选择 -->
|
|
|
|
|
+ <el-row :gutter="20" v-if="templateInfo.calcMethod !== 'piece'">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item :rules="[{ required: true, message: '请选择计量单位', trigger: 'change' }]" label="计量单位"
|
|
|
|
|
+ prop="templateInfo.unit">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="templateInfo.unit"
|
|
|
|
|
+ @change="handleUnitChange"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ placeholder="请选择计量单位"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="unit in getAvailableUnits()"
|
|
|
|
|
+ :key="unit.value"
|
|
|
|
|
+ :label="unit.label"
|
|
|
|
|
+ :value="unit.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <div class="form-item-tip">选择合适的计量单位便于计算运费</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 默认区设置 -->
|
|
|
|
|
+ <el-card shadow="never" class="mb-4">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
|
|
+ <span>默认计费规则</span>
|
|
|
|
|
+ <el-tag type="info">适用于未指定地区</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ class="mb-4"
|
|
|
|
|
+ description="这是适用于全国大部分地区的运费计算规则,针对特定区域可以在下方设置特殊区域规则"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ title="默认计费规则说明"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item :label="getFirstLabel()" :rules="[{ required: true, type: 'number', min: 0.001, message: getFirstLabel() + '必须大于0', trigger: 'blur' }]"
|
|
|
|
|
+ prop="defaultRule.first">
|
|
|
|
|
+ <div class="input-with-unit">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="defaultRule.first"
|
|
|
|
|
+ :min="getMinFirstValue()"
|
|
|
|
|
+ :step="getFirstStep()"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="unit-text">
|
|
|
|
|
+ <span v-if="getCurrentDisplayUnit() === 'cm3'">
|
|
|
|
|
+ cm<sup>3</sup>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else-if="getCurrentDisplayUnit() === 'm3'">
|
|
|
|
|
+ m<sup>3</sup>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else>{{ getCurrentDisplayUnit() }}</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item :label="getFirstPriceLabel()" :rules="[{ required: true, type: 'number', min: 0, message: getFirstPriceLabel() + '不能为负数', trigger: 'blur' }]"
|
|
|
|
|
+ prop="defaultRule.firstPrice">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="defaultRule.firstPrice"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :step="0.01"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-item-tip">首{{ getCurrentDisplayUnit() }}的费用</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item :label="getNextPriceLabel()" :rules="[{ required: true, type: 'number', min: 0, message: getNextPriceLabel() + '不能为负数', trigger: 'blur' }]"
|
|
|
|
|
+ prop="defaultRule.nextPrice">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="defaultRule.nextPrice"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :step="0.01"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-item-tip">超出首{{ getCurrentDisplayUnit() }}后每增加一个单位的费用</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 特殊区设置 -->
|
|
|
|
|
+ <el-card shadow="never" class="mb-4">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
|
|
+ <span>特殊区域设置</span>
|
|
|
|
|
+ <el-button type="primary" @click="addSpecialArea" plain>
|
|
|
|
|
+ <el-icon><Plus /></el-icon>添加特殊区
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ class="mb-4"
|
|
|
|
|
+ description="可以为某些偏远或特殊地区设置独立的运费规则,这些规则会覆盖默认规则"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ title="特殊区域设置说明"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 特殊区列表 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(area, index) in specialAreas"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="special-area mb-4 p-4 border rounded"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ @click="removeSpecialArea(index)"
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ :icon="Close"
|
|
|
|
|
+ circle
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ class="float-right"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="20" class="mb-4">
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item :prop="'specialAreas.' + index + '.regions'" :rules="{ type: 'array', required: true, message: '请选择至少一个地区', trigger: 'change' }"
|
|
|
|
|
+ label="选择地区">
|
|
|
|
|
+ <div class="region-tags mb-2">
|
|
|
|
|
+ <el-tag
|
|
|
|
|
+ v-for="(region, regionIndex) in area.regions"
|
|
|
|
|
+ :key="regionIndex"
|
|
|
|
|
+ closable
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ class="mr-2 mb-2"
|
|
|
|
|
+ @close="removeRegion(index, regionIndex)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ formatRegionName(region) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-button @click="openRegionModal(index)" type="primary" link>
|
|
|
|
|
+ <el-icon><Plus /></el-icon>添加地区
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <div class="form-item-tip">点击添加地区,可选择省份、城市或区县</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item :prop="'specialAreas.' + index + '.area_name'"
|
|
|
|
|
+ :rules="{ required: true, message: '请输入区域名称', trigger: 'blur' }"
|
|
|
|
|
+ label="区域名称">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="area.area_name"
|
|
|
|
|
+ placeholder="例如:偏远地区"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-item-tip">给这个特殊区域起一个名字,便于识别</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 特殊区域价格设置 -->
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item :label="getFirstLabel()" :prop="'specialAreas.' + index + '.first'"
|
|
|
|
|
+ :rules="{ required: true, type: 'number', min: 0.001, message: getFirstLabel() + '必须大于0', trigger: 'blur' }">
|
|
|
|
|
+ <div class="input-with-unit">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="area.first"
|
|
|
|
|
+ :min="getMinFirstValue()"
|
|
|
|
|
+ :step="getFirstStep()"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="unit-text">
|
|
|
|
|
+ <span v-if="getCurrentDisplayUnit() === 'cm3'">
|
|
|
|
|
+ cm<sup>3</sup>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else-if="getCurrentDisplayUnit() === 'm3'">
|
|
|
|
|
+ m<sup>3</sup>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else>{{ getCurrentDisplayUnit() }}</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item :label="getFirstPriceLabel()" :prop="'specialAreas.' + index + '.firstPrice'"
|
|
|
|
|
+ :rules="{ required: true, type: 'number', min: 0, message: getFirstPriceLabel() + '不能为负数', trigger: 'blur' }">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="area.firstPrice"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :step="0.01"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-item-tip">首{{ getCurrentDisplayUnit() }}的费用</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="8">
|
|
|
|
|
+ <el-form-item :label="getNextPriceLabel()" :prop="'specialAreas.' + index + '.nextPrice'"
|
|
|
|
|
+ :rules="{ required: true, type: 'number', min: 0, message: getNextPriceLabel() + '不能为负数', trigger: 'blur' }">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="area.nextPrice"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :step="0.01"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="form-item-tip">超出首{{ getCurrentDisplayUnit() }}后每增加一个单位的费用</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-empty v-if="specialAreas.length === 0" description="暂无特殊区域,请点击上方按钮添加"/>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 操作按钮 -->
|
|
|
|
|
+ <div class="flex justify-end">
|
|
|
|
|
+ <el-button @click="cancel">取消</el-button>
|
|
|
|
|
+ <el-button :loading="props.loading" type="primary" @click="saveTemplate">
|
|
|
|
|
+ <el-icon><DocumentAdd /></el-icon>{{ isEditMode ? '保存模板' : '创建模板' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 地区选择对话框 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="showRegionModal"
|
|
|
|
|
+ :before-close="closeRegionModal"
|
|
|
|
|
+ title="选择地区"
|
|
|
|
|
+ width="800px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ class="mb-4"
|
|
|
|
|
+ description="可以选择省份、城市或区县级别,系统会自动处理层级关系"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ title="地区选择说明"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <div class="region-selector-container">
|
|
|
|
|
+ <div class="selector-header">
|
|
|
|
|
+ <span>省份</span>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :disabled="!provinceList.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="addAllProvinces"
|
|
|
|
|
+ >
|
|
|
|
|
+ 全部
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :disabled="!selectedProvinces.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ @click="addCurrentProvincesToSelected"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-scrollbar height="300px">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="province in provinceList"
|
|
|
|
|
+ :key="province.code"
|
|
|
|
|
+ :class="['region-item', { active: isProvinceSelected(province.code) || selectedProvinceCode === province.code }]"
|
|
|
|
|
+ @click="selectProvince(province)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-checkbox
|
|
|
|
|
+ :model-value="isProvinceSelected(province.code)"
|
|
|
|
|
+ @change="toggleProvinceSelection(province)"
|
|
|
|
|
+ @click.stop
|
|
|
|
|
+ />
|
|
|
|
|
+ {{ province.name }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-scrollbar>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <div class="region-selector-container">
|
|
|
|
|
+ <div class="selector-header">
|
|
|
|
|
+ <span>城市</span>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="selectedProvince"
|
|
|
|
|
+ :disabled="!cityList.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="addAllCities"
|
|
|
|
|
+ >
|
|
|
|
|
+ 全部
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :disabled="!selectedCities.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ @click="addCurrentCitiesToSelected"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-scrollbar height="300px">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="city in cityList"
|
|
|
|
|
+ :key="city.code"
|
|
|
|
|
+ :class="['region-item', { active: isCitySelected(city.code) || selectedCityCode === city.code }]"
|
|
|
|
|
+ @click="selectCity(city)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-checkbox
|
|
|
|
|
+ :disabled="isProvinceOfCitySelected(city)"
|
|
|
|
|
+ :model-value="isCitySelected(city.code)"
|
|
|
|
|
+ @change="toggleCitySelection(city)"
|
|
|
|
|
+ @click.stop
|
|
|
|
|
+ />
|
|
|
|
|
+ {{ city.name }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-scrollbar>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <div class="region-selector-container">
|
|
|
|
|
+ <div class="selector-header">
|
|
|
|
|
+ <span>区县</span>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="selectedCity"
|
|
|
|
|
+ :disabled="!areaList.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="addAllAreas"
|
|
|
|
|
+ >
|
|
|
|
|
+ 全部
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :disabled="!selectedAreas.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ @click="addCurrentAreasToSelected"
|
|
|
|
|
+ >
|
|
|
|
|
+ 添加
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-scrollbar height="300px">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="area in areaList"
|
|
|
|
|
+ :key="area.code"
|
|
|
|
|
+ :class="['region-item', { active: isAreaSelected(area.code) }]"
|
|
|
|
|
+ @click="toggleAreaSelection(area)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-checkbox
|
|
|
|
|
+ :disabled="isHigherLevelRegionSelected(area)"
|
|
|
|
|
+ :model-value="isAreaSelected(area.code)"
|
|
|
|
|
+ @change="toggleAreaSelection(area)"
|
|
|
|
|
+ @click.stop
|
|
|
|
|
+ />
|
|
|
|
|
+ {{ area.name }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-scrollbar>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <div class="region-selector-container">
|
|
|
|
|
+ <div class="selector-header">
|
|
|
|
|
+ <span>已选择</span>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :disabled="!selectedRegions.length"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ @click="clearSelectedRegions"
|
|
|
|
|
+ >
|
|
|
|
|
+ 清空
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-scrollbar height="300px">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(region, index) in selectedRegions"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="selected-region-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span>{{ formatRegionName(region) }}</span>
|
|
|
|
|
+ <el-icon @click.stop="removeSelectedRegion(index)">
|
|
|
|
|
+ <Close/>
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="!selectedRegions.length" class="no-data">
|
|
|
|
|
+ 暂无选择
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-scrollbar>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button @click="closeRegionModal">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="confirmRegions">确认选择</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue'
|
|
|
|
|
+import {ElMessage, ElMessageBox} from 'element-plus'
|
|
|
|
|
+import {Close, DocumentAdd, Plus, Van} from '@element-plus/icons-vue'
|
|
|
|
|
+import {getAreas, getCities, getProvinces} from '../api/index.js'
|
|
|
|
|
+
|
|
|
|
|
+// 定义组件props
|
|
|
|
|
+const props = defineProps({
|
|
|
|
|
+ // 模板数据,用于编辑模式
|
|
|
|
|
+ templateData: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ default: null
|
|
|
|
|
+ },
|
|
|
|
|
+ // 加载状态
|
|
|
|
|
+ loading: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ default: false
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 定义组件事件
|
|
|
|
|
+const emit = defineEmits(['save', 'cancel'])
|
|
|
|
|
+
|
|
|
|
|
+// 判断是否为编辑模式
|
|
|
|
|
+const isEditMode = computed(() => {
|
|
|
|
|
+ return props.templateData && props.templateData.templateInfo && props.templateData.templateInfo.id
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 省份数据
|
|
|
|
|
+const provinceData = ref<any[]>([])
|
|
|
|
|
+
|
|
|
|
|
+// 模板信息
|
|
|
|
|
+const templateInfo = reactive({
|
|
|
|
|
+ id: null,
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ calcMethod: 'piece', // piece, weight, volume
|
|
|
|
|
+ unit: 'kg' // 默认单位为 kg
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 默认规则
|
|
|
|
|
+const defaultRule = reactive({
|
|
|
|
|
+ first: 1,
|
|
|
|
|
+ firstPrice: 10,
|
|
|
|
|
+ nextPrice: 5
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 特殊区域列表
|
|
|
|
|
+const specialAreas = ref<any[]>([])
|
|
|
|
|
+
|
|
|
|
|
+// 地区选择模态框相关
|
|
|
|
|
+const showRegionModal = ref(false)
|
|
|
|
|
+const currentAreaIndex = ref(-1)
|
|
|
|
|
+
|
|
|
|
|
+// 按省市区选择相关
|
|
|
|
|
+const provinceList = ref<any[]>([]) // 省份列表
|
|
|
|
|
+const cityList = ref<any[]>([]) // 城市列表
|
|
|
|
|
+const areaList = ref<any[]>([]) // 区县列表
|
|
|
|
|
+const selectedProvince = ref<any>(null) // 已选择的省份
|
|
|
|
|
+const selectedProvinceCode = ref<string>('') // 已选择的省份编码
|
|
|
|
|
+const selectedCity = ref<any>(null) // 已选择的城市
|
|
|
|
|
+const selectedCityCode = ref<string>('') // 已选择的城市编码
|
|
|
|
|
+const selectedProvinces = ref<any[]>([]) // 已选择的省份列表
|
|
|
|
|
+const selectedCities = ref<any[]>([]) // 已选择的城市列表
|
|
|
|
|
+const selectedAreas = ref<any[]>([]) // 已选择的区县列表
|
|
|
|
|
+const selectedRegions = ref<any[]>([]) // 已选择的地区列表
|
|
|
|
|
+
|
|
|
|
|
+// 在组件挂载时加载省份数据
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取省份数据
|
|
|
|
|
+ const res = await getProvinces()
|
|
|
|
|
+ provinceData.value = res.data || []
|
|
|
|
|
+ provinceList.value = res.data || []
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载省份数据失败:', error)
|
|
|
|
|
+ // 不再使用硬编码的默认省份列表
|
|
|
|
|
+ provinceData.value = []
|
|
|
|
|
+ provinceList.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 选择省份
|
|
|
|
|
+const selectProvince = async (province: any) => {
|
|
|
|
|
+ selectedProvince.value = province
|
|
|
|
|
+ selectedProvinceCode.value = province.code
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 调用API获取城市列表
|
|
|
|
|
+ const res = await getCities(province.code)
|
|
|
|
|
+ cityList.value = res.data || []
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedCity.value = null
|
|
|
|
|
+ selectedCityCode.value = ''
|
|
|
|
|
+ selectedCities.value = []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取城市列表失败:', error)
|
|
|
|
|
+ cityList.value = []
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedCity.value = null
|
|
|
|
|
+ selectedCityCode.value = ''
|
|
|
|
|
+ selectedCities.value = []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 选择城市
|
|
|
|
|
+const selectCity = async (city: any) => {
|
|
|
|
|
+ selectedCity.value = city
|
|
|
|
|
+ selectedCityCode.value = city.code
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 调用API获取区县列表
|
|
|
|
|
+ const res = await getAreas(city.code)
|
|
|
|
|
+ areaList.value = res.data || []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取区县列表失败:', error)
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 切换城市选择
|
|
|
|
|
+const toggleCitySelection = (city: any) => {
|
|
|
|
|
+ const index = selectedCities.value.findIndex((c: any) => c.city_id === city.code)
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ // 取消选择
|
|
|
|
|
+ selectedCities.value.splice(index, 1)
|
|
|
|
|
+ // 同时取消该城市下所有区县的选择
|
|
|
|
|
+ selectedAreas.value = selectedAreas.value.filter((area: any) => area.city_id !== city.city_id)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 添加选择之前检查是否已选择了所属省份
|
|
|
|
|
+ const isProvinceAlreadySelected = selectedProvinces.value.some(
|
|
|
|
|
+ (province: any) => province.province_id === city.province_id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (isProvinceAlreadySelected) {
|
|
|
|
|
+ // 获取省份名称用于提示
|
|
|
|
|
+ const province = selectedProvinces.value.find(
|
|
|
|
|
+ (p: any) => p.province_id === city.province_id
|
|
|
|
|
+ )
|
|
|
|
|
+ const provinceName = province ? province.province_name : '该省份'
|
|
|
|
|
+ ElMessage.warning(`您已选择了${provinceName},无需再单独选择其下属城市`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选择,直接存储完整的6个字段
|
|
|
|
|
+ const province = provinceList.value.find((p: any) => p.code === city.province + '0000')
|
|
|
|
|
+ const provinceName = province ? province.name : '未知省份'
|
|
|
|
|
+
|
|
|
|
|
+ selectedCities.value.push({
|
|
|
|
|
+ area_id: null,
|
|
|
|
|
+ city_id: city.code,
|
|
|
|
|
+ area_name: null,
|
|
|
|
|
+ city_name: city.name,
|
|
|
|
|
+ province_id: city.province + '0000',
|
|
|
|
|
+ province_name: provinceName
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 切换区县选择
|
|
|
|
|
+const toggleAreaSelection = (area: any) => {
|
|
|
|
|
+ const index = selectedAreas.value.findIndex((a: any) => a.area_id === area.code)
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ // 取消选择
|
|
|
|
|
+ selectedAreas.value.splice(index, 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 添加选择之前检查是否已选择了所属城市或省份
|
|
|
|
|
+ const isCityAlreadySelected = selectedCities.value.some(
|
|
|
|
|
+ (city: any) => city.city_id === area.city_id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ const isProvinceAlreadySelected = selectedProvinces.value.some(
|
|
|
|
|
+ (province: any) => province.province_id === area.province_id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (isCityAlreadySelected) {
|
|
|
|
|
+ // 获取城市名称用于提示
|
|
|
|
|
+ const city = selectedCities.value.find(
|
|
|
|
|
+ (c: any) => c.city_id === area.city_id
|
|
|
|
|
+ )
|
|
|
|
|
+ const cityName = city ? city.city_name : '该城市'
|
|
|
|
|
+ ElMessage.warning(`您已选择了${cityName},无需再单独选择其下属区县`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isProvinceAlreadySelected) {
|
|
|
|
|
+ // 获取省份名称用于提示
|
|
|
|
|
+ const province = selectedProvinces.value.find(
|
|
|
|
|
+ (p: any) => p.province_id === area.province + '0000'
|
|
|
|
|
+ )
|
|
|
|
|
+ const provinceName = province ? province.province_name : '该省份'
|
|
|
|
|
+ ElMessage.warning(`您已选择了${provinceName},无需再单独选择其下属区县`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选择,直接存储完整的6个字段
|
|
|
|
|
+ const province = provinceList.value.find((p: any) => p.code === area.province + '0000')
|
|
|
|
|
+ const provinceName = province ? province.name : '未知省份'
|
|
|
|
|
+
|
|
|
|
|
+ const city = cityList.value.find((c: any) => c.code === area.province + area.city + '00')
|
|
|
|
|
+ const cityName = city ? city.name : '未知城市'
|
|
|
|
|
+
|
|
|
|
|
+ selectedAreas.value.push({
|
|
|
|
|
+ area_id: area.code,
|
|
|
|
|
+ city_id: area.province + area.city + '00',
|
|
|
|
|
+ area_name: area.name,
|
|
|
|
|
+ city_name: cityName,
|
|
|
|
|
+ province_id: area.province + '0000',
|
|
|
|
|
+ province_name: provinceName
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 切换省份选择
|
|
|
|
|
+const toggleProvinceSelection = (province: any) => {
|
|
|
|
|
+ const index = selectedProvinces.value.findIndex((p: any) => p.province_id === province.code)
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ // 取消选择
|
|
|
|
|
+ selectedProvinces.value.splice(index, 1)
|
|
|
|
|
+ // 同时取消该省份下所有城市和区县的选择
|
|
|
|
|
+ selectedCities.value = selectedCities.value.filter((city: any) => city.province_id !== province.province_id)
|
|
|
|
|
+ selectedAreas.value = selectedAreas.value.filter((area: any) => area.province_id !== province.province_id)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 添加选择,直接存储完整的6个字段
|
|
|
|
|
+ selectedProvinces.value.push({
|
|
|
|
|
+ area_id: null,
|
|
|
|
|
+ city_id: null,
|
|
|
|
|
+ area_name: null,
|
|
|
|
|
+ city_name: null,
|
|
|
|
|
+ province_id: province.code,
|
|
|
|
|
+ province_name: province.name
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断城市是否被选中
|
|
|
|
|
+const isCitySelected = (cityCode: string) => {
|
|
|
|
|
+ return selectedCities.value.some((city: any) => city.city_id === cityCode)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断区县是否被选中
|
|
|
|
|
+const isAreaSelected = (areaCode: string) => {
|
|
|
|
|
+ return selectedAreas.value.some((area: any) => area.area_id === areaCode)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断省份是否被选中
|
|
|
|
|
+const isProvinceSelected = (provinceCode: string) => {
|
|
|
|
|
+ return selectedProvinces.value.some((province: any) => province.province_id === provinceCode)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断城市的省份是否已经被选中
|
|
|
|
|
+const isProvinceOfCitySelected = (city: any) => {
|
|
|
|
|
+ return selectedProvinces.value.some((province: any) => province.province_id === city.province_id)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 判断区县的上级(城市或省份)是否已经被选中
|
|
|
|
|
+const isHigherLevelRegionSelected = (area: any) => {
|
|
|
|
|
+ // 检查所属城市是否被选中
|
|
|
|
|
+ const isCitySelected = selectedCities.value.some((city: any) => city.city_id === area.city_id)
|
|
|
|
|
+ // 检查所属省份是否被选中
|
|
|
|
|
+ const isProvinceSelected = selectedProvinces.value.some((province: any) => province.province_id === area.province_id)
|
|
|
|
|
+ return isCitySelected || isProvinceSelected
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加所有城市
|
|
|
|
|
+const addAllCities = () => {
|
|
|
|
|
+ // 过滤掉那些所属省份已经被选中的城市
|
|
|
|
|
+ const citiesToAdd = cityList.value.filter((city: any) => !isProvinceOfCitySelected(city))
|
|
|
|
|
+ selectedCities.value = citiesToAdd.map(city => {
|
|
|
|
|
+ const province = provinceList.value.find((p: any) => p.code === city.province + '0000')
|
|
|
|
|
+ const provinceName = province ? province.name : '未知省份'
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ area_id: null,
|
|
|
|
|
+ city_id: city.code,
|
|
|
|
|
+ area_name: null,
|
|
|
|
|
+ city_name: city.name,
|
|
|
|
|
+ province_id: city.province + '0000',
|
|
|
|
|
+ province_name: provinceName
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加所有区县
|
|
|
|
|
+const addAllAreas = () => {
|
|
|
|
|
+ // 过滤掉那些所属城市或省份已经被选中的区县
|
|
|
|
|
+ const areasToAdd = areaList.value.filter((area: any) => !isHigherLevelRegionSelected(area))
|
|
|
|
|
+ selectedAreas.value = areasToAdd.map(area => {
|
|
|
|
|
+ const province = provinceList.value.find((p: any) => p.code === area.province + '0000')
|
|
|
|
|
+ const provinceName = province ? province.name : '未知省份'
|
|
|
|
|
+
|
|
|
|
|
+ const city = cityList.value.find((c: any) => c.code === area.province + area.city + '00')
|
|
|
|
|
+ const cityName = city ? city.name : '未知城市'
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ area_id: area.code,
|
|
|
|
|
+ city_id: area.province + area.city + '00',
|
|
|
|
|
+ area_name: area.name,
|
|
|
|
|
+ city_name: cityName,
|
|
|
|
|
+ province_id: area.province + '0000',
|
|
|
|
|
+ province_name: provinceName
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加所有省份
|
|
|
|
|
+const addAllProvinces = async () => {
|
|
|
|
|
+ // 检查是否已选择了省份的子级(城市或区县)
|
|
|
|
|
+ const hasCitySelected = selectedCities.value.some((city: any) => {
|
|
|
|
|
+ return selectedProvinces.value.some((province: any) => province.province_id === city.province_id)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const hasAreaSelected = selectedAreas.value.some((area: any) => {
|
|
|
|
|
+ return selectedProvinces.value.some((province: any) => province.province_id === area.province_id)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (hasCitySelected || hasAreaSelected) {
|
|
|
|
|
+ ElMessage.warning('您已选择了省份的子级区域,无法再添加全部省份')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ selectedProvinces.value = provinceList.value.map(province => ({
|
|
|
|
|
+ area_id: null,
|
|
|
|
|
+ city_id: null,
|
|
|
|
|
+ area_name: null,
|
|
|
|
|
+ city_name: null,
|
|
|
|
|
+ province_id: province.code,
|
|
|
|
|
+ province_name: province.name
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
|
|
+ // 如果省份列表不为空,加载第一个省份的城市数据
|
|
|
|
|
+ if (provinceList.value.length > 0) {
|
|
|
|
|
+ const firstProvince = provinceList.value[0]
|
|
|
|
|
+ selectedProvince.value = firstProvince
|
|
|
|
|
+ selectedProvinceCode.value = firstProvince.code
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 调用API获取城市列表
|
|
|
|
|
+ const res = await getCities(firstProvince.code)
|
|
|
|
|
+ cityList.value = res.data || []
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedCity.value = null
|
|
|
|
|
+ selectedCityCode.value = ''
|
|
|
|
|
+ selectedCities.value = []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取城市列表失败:', error)
|
|
|
|
|
+ cityList.value = []
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedCity.value = null
|
|
|
|
|
+ selectedCityCode.value = ''
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 清空已选择的地区
|
|
|
|
|
+const clearSelectedRegions = () => {
|
|
|
|
|
+ selectedRegions.value = []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 移除已选择的地区
|
|
|
|
|
+const removeSelectedRegion = (index: number) => {
|
|
|
|
|
+ selectedRegions.value.splice(index, 1)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 格式化地区名称显示
|
|
|
|
|
+const formatRegionName = (region: any) => {
|
|
|
|
|
+ if (!region.city_id && !region.area_id) {
|
|
|
|
|
+ // 省级区域,只显示省份名称
|
|
|
|
|
+ return region.province_name || ''
|
|
|
|
|
+ } else if (region.city_id && !region.area_id) {
|
|
|
|
|
+ // 市级区域,显示"河北省/石家庄市"格式
|
|
|
|
|
+ return `${region.province_name || ''}/${region.city_name || ''}`
|
|
|
|
|
+ } else if (region.city_id && region.area_id) {
|
|
|
|
|
+ // 区县级区域,显示"河北省/石家庄市/长安区"格式
|
|
|
|
|
+ return `${region.province_name || ''}/${region.city_name || ''}/${region.area_name || ''}`
|
|
|
|
|
+ }
|
|
|
|
|
+ return region.province_name || ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 打开地区选择模态框
|
|
|
|
|
+const openRegionModal = (areaIndex: number) => {
|
|
|
|
|
+ currentAreaIndex.value = areaIndex
|
|
|
|
|
+ showRegionModal.value = true
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化省市选择模式数据
|
|
|
|
|
+ selectedProvince.value = null
|
|
|
|
|
+ selectedProvinceCode.value = ''
|
|
|
|
|
+ selectedCity.value = null
|
|
|
|
|
+ selectedCityCode.value = ''
|
|
|
|
|
+ cityList.value = []
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedProvinces.value = []
|
|
|
|
|
+ selectedCities.value = []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+
|
|
|
|
|
+ // 从现有数据中恢复已选择的地区
|
|
|
|
|
+ const regions = [...specialAreas.value[areaIndex].regions]
|
|
|
|
|
+ selectedRegions.value = regions
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 关闭地区选择模态框
|
|
|
|
|
+const closeRegionModal = () => {
|
|
|
|
|
+ showRegionModal.value = false
|
|
|
|
|
+ currentAreaIndex.value = -1
|
|
|
|
|
+
|
|
|
|
|
+ // 重置省市选择模式数据
|
|
|
|
|
+ selectedProvince.value = null
|
|
|
|
|
+ selectedProvinceCode.value = ''
|
|
|
|
|
+ selectedCity.value = null
|
|
|
|
|
+ selectedCityCode.value = ''
|
|
|
|
|
+ cityList.value = []
|
|
|
|
|
+ areaList.value = []
|
|
|
|
|
+ selectedCities.value = []
|
|
|
|
|
+ selectedAreas.value = []
|
|
|
|
|
+ selectedRegions.value = []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 确认选择地区
|
|
|
|
|
+const confirmRegions = () => {
|
|
|
|
|
+ if (currentAreaIndex.value >= 0) {
|
|
|
|
|
+ // 将当前所有选中的省份、城市、区县添加到已选择列表
|
|
|
|
|
+ const currentRegions = []
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选中的省份
|
|
|
|
|
+ for (const province of selectedProvinces.value) {
|
|
|
|
|
+ currentRegions.push({
|
|
|
|
|
+ area_id: province.area_id,
|
|
|
|
|
+ city_id: province.city_id,
|
|
|
|
|
+ area_name: province.area_name,
|
|
|
|
|
+ city_name: province.city_name,
|
|
|
|
|
+ province_id: province.province_id,
|
|
|
|
|
+ province_name: province.province_name
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选中的城市(过滤掉所属省份已经被选中的城市)
|
|
|
|
|
+ for (const city of selectedCities.value) {
|
|
|
|
|
+ const isProvinceSelected = selectedProvinces.value.some(
|
|
|
|
|
+ (province: any) => province.province_id === city.province_id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!isProvinceSelected) {
|
|
|
|
|
+ currentRegions.push({
|
|
|
|
|
+ area_id: city.area_id,
|
|
|
|
|
+ city_id: city.city_id,
|
|
|
|
|
+ area_name: city.area_name,
|
|
|
|
|
+ city_name: city.city_name,
|
|
|
|
|
+ province_id: city.province_id,
|
|
|
|
|
+ province_name: city.province_name
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选中的区县(过滤掉所属城市或省份已经被选中的区县)
|
|
|
|
|
+ for (const area of selectedAreas.value) {
|
|
|
|
|
+ const isCitySelected = selectedCities.value.some(
|
|
|
|
|
+ (city: any) => city.city_id === area.city_id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ const isProvinceSelected = selectedProvinces.value.some(
|
|
|
|
|
+ (province: any) => province.province_id === area.province_id
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!isCitySelected && !isProvinceSelected) {
|
|
|
|
|
+ currentRegions.push({
|
|
|
|
|
+ area_id: area.area_id,
|
|
|
|
|
+ city_id: area.city_id,
|
|
|
|
|
+ area_name: area.area_name,
|
|
|
|
|
+ city_name: area.city_name,
|
|
|
|
|
+ province_id: area.province_id,
|
|
|
|
|
+ province_name: area.province_name
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并已存在的地区和新选择的地区
|
|
|
|
|
+ const allRegions = [...selectedRegions.value, ...currentRegions]
|
|
|
|
|
+
|
|
|
|
|
+ // 去重处理
|
|
|
|
|
+ const uniqueRegions = allRegions.filter((region, index, self) =>
|
|
|
|
|
+ index === self.findIndex(r =>
|
|
|
|
|
+ r.province_id === region.province_id &&
|
|
|
|
|
+ r.city_id === region.city_id &&
|
|
|
|
|
+ r.area_id === region.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 最后进行层级关系检查,确保不会同时存在父级和子级
|
|
|
|
|
+ const filteredRegions = []
|
|
|
|
|
+ for (const region of uniqueRegions) {
|
|
|
|
|
+ let shouldAdd = true
|
|
|
|
|
+
|
|
|
|
|
+ if (!region.city_id && !region.area_id) {
|
|
|
|
|
+ // 省级区域,检查是否已存在其子级
|
|
|
|
|
+ const hasChild = filteredRegions.some(r =>
|
|
|
|
|
+ (r.province_id === region.province_id && (r.city_id || r.area_id))
|
|
|
|
|
+ )
|
|
|
|
|
+ shouldAdd = !hasChild
|
|
|
|
|
+ } else if (region.city_id && !region.area_id) {
|
|
|
|
|
+ // 市级区域,检查是否已存在父级或子级
|
|
|
|
|
+ const hasParent = filteredRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && !r.city_id && !r.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ const hasChild = filteredRegions.some(r =>
|
|
|
|
|
+ r.city_id === region.city_id && r.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ shouldAdd = !hasParent && !hasChild
|
|
|
|
|
+ } else if (region.city_id && region.area_id) {
|
|
|
|
|
+ // 区县级区域,检查是否已存在父级
|
|
|
|
|
+ const hasParentProvince = filteredRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && !r.city_id && !r.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ const hasParentCity = filteredRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && r.city_id === region.city_id && !r.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ shouldAdd = !hasParentProvince && !hasParentCity
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (shouldAdd) {
|
|
|
|
|
+ filteredRegions.push(region)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ specialAreas.value[currentAreaIndex.value].regions = filteredRegions
|
|
|
|
|
+ }
|
|
|
|
|
+ closeRegionModal()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 监听传入的模板数据变化
|
|
|
|
|
+watch(() => props.templateData, (newVal) => {
|
|
|
|
|
+ if (newVal) {
|
|
|
|
|
+ // 填充表单数据用于编辑
|
|
|
|
|
+ templateInfo.id = newVal.templateInfo?.id || null
|
|
|
|
|
+ templateInfo.name = newVal.templateInfo?.name || ''
|
|
|
|
|
+ templateInfo.calcMethod = newVal.templateInfo?.calcMethod || 'piece'
|
|
|
|
|
+ templateInfo.unit = newVal.templateInfo?.unit || 'kg'
|
|
|
|
|
+
|
|
|
|
|
+ if (newVal.defaultRule) {
|
|
|
|
|
+ defaultRule.first = Number(newVal.defaultRule.first) || 1
|
|
|
|
|
+ defaultRule.firstPrice = Number(newVal.defaultRule.firstPrice) || 0
|
|
|
|
|
+ defaultRule.nextPrice = Number(newVal.defaultRule.nextPrice) || 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Array.isArray(newVal.specialAreas)) {
|
|
|
|
|
+ specialAreas.value = newVal.specialAreas.map((area: any) => {
|
|
|
|
|
+ // 确保每个区域的字段都正确处理,特别是处理可能为 null 的字段
|
|
|
|
|
+ const processedRegions = Array.isArray(area.regions)
|
|
|
|
|
+ ? area.regions.map((region: any) => ({
|
|
|
|
|
+ province_id: region.province_id || null,
|
|
|
|
|
+ province_name: region.province_name || '',
|
|
|
|
|
+ city_id: region.city_id || null,
|
|
|
|
|
+ city_name: region.city_name || null,
|
|
|
|
|
+ area_id: region.area_id || null,
|
|
|
|
|
+ area_name: region.area_name || null
|
|
|
|
|
+ })).filter((region: any) => region.province_id) // 过滤掉没有province_id的无效区域
|
|
|
|
|
+ : [];
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ area_name: area.area_name || area.name || '',
|
|
|
|
|
+ regions: processedRegions,
|
|
|
|
|
+ first: Number(area.first) || 1,
|
|
|
|
|
+ firstPrice: Number(area.first_price || area.firstPrice) || 0,
|
|
|
|
|
+ nextPrice: Number(area.next_price || area.nextPrice) || 0
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ specialAreas.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有传入数据,重置表单
|
|
|
|
|
+ // 为了避免引用错误,这里手动重置表单而不是调用resetForm函数
|
|
|
|
|
+ templateInfo.id = null
|
|
|
|
|
+ templateInfo.name = ''
|
|
|
|
|
+ templateInfo.calcMethod = 'piece'
|
|
|
|
|
+ templateInfo.unit = 'kg'
|
|
|
|
|
+ defaultRule.first = 1
|
|
|
|
|
+ defaultRule.firstPrice = 10
|
|
|
|
|
+ defaultRule.nextPrice = 5
|
|
|
|
|
+ specialAreas.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}, { immediate: true })
|
|
|
|
|
+
|
|
|
|
|
+// 可用单位选项
|
|
|
|
|
+const unitOptions = {
|
|
|
|
|
+ weight: [
|
|
|
|
|
+ { label: '克(g)', value: 'g' },
|
|
|
|
|
+ { label: '千克(kg)', value: 'kg' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ volume: [
|
|
|
|
|
+ { label: '立方厘米(cm³)', value: 'cm3' },
|
|
|
|
|
+ { label: '立方米(m³)', value: 'm3' }
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取可用单位选项
|
|
|
|
|
+const getAvailableUnits = () => {
|
|
|
|
|
+ if (templateInfo.calcMethod === 'weight') {
|
|
|
|
|
+ return unitOptions.weight
|
|
|
|
|
+ } else if (templateInfo.calcMethod === 'volume') {
|
|
|
|
|
+ return unitOptions.volume
|
|
|
|
|
+ }
|
|
|
|
|
+ return []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取当前显示的单位
|
|
|
|
|
+const getCurrentDisplayUnit = () => {
|
|
|
|
|
+ // 如果是按件数计费,直接返回件
|
|
|
|
|
+ if (templateInfo.calcMethod === 'piece') {
|
|
|
|
|
+ return '件'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 返回用户选择的具体单位
|
|
|
|
|
+ return templateInfo.unit
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取首值标签
|
|
|
|
|
+const getFirstLabel = () => {
|
|
|
|
|
+ switch (templateInfo.calcMethod) {
|
|
|
|
|
+ case 'piece': return '首件'
|
|
|
|
|
+ case 'weight': return '首重'
|
|
|
|
|
+ case 'volume': return '首体积'
|
|
|
|
|
+ default: return '首件'
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取首价标签
|
|
|
|
|
+const getFirstPriceLabel = () => {
|
|
|
|
|
+ switch (templateInfo.calcMethod) {
|
|
|
|
|
+ case 'piece': return '首件费用(元)'
|
|
|
|
|
+ case 'weight': return '首重费用(元)'
|
|
|
|
|
+ case 'volume': return '首体积费用(元)'
|
|
|
|
|
+ default: return '首件费用(元)'
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取续价标签
|
|
|
|
|
+const getNextPriceLabel = () => {
|
|
|
|
|
+ switch (templateInfo.calcMethod) {
|
|
|
|
|
+ case 'piece': return '续件单价(元)'
|
|
|
|
|
+ case 'weight': return '续重单价(元)'
|
|
|
|
|
+ case 'volume': return '续体积单价(元)'
|
|
|
|
|
+ default: return '续件单价(元)'
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取首值的最小值
|
|
|
|
|
+const getMinFirstValue = () => {
|
|
|
|
|
+ switch (templateInfo.calcMethod) {
|
|
|
|
|
+ case 'piece': return 1
|
|
|
|
|
+ case 'weight': return templateInfo.unit === 'g' ? 100 : 0.1
|
|
|
|
|
+ case 'volume': return templateInfo.unit === 'cm3' ? 1000 : 0.001
|
|
|
|
|
+ default: return 1
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取首值的步长
|
|
|
|
|
+const getFirstStep = () => {
|
|
|
|
|
+ switch (templateInfo.calcMethod) {
|
|
|
|
|
+ case 'piece': return 1
|
|
|
|
|
+ case 'weight': return templateInfo.unit === 'g' ? 100 : 0.1
|
|
|
|
|
+ case 'volume': return templateInfo.unit === 'cm3' ? 1000 : 0.001
|
|
|
|
|
+ default: return 1
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 处理计费方式变更
|
|
|
|
|
+const handleCalcMethodChange = async () => {
|
|
|
|
|
+ // 当计费方式改变时,更新所有规则的首值、步长和单位
|
|
|
|
|
+ defaultRule.first = getMinFirstValue()
|
|
|
|
|
+
|
|
|
|
|
+ // 更新所有特殊区域的首值
|
|
|
|
|
+ specialAreas.value.forEach(area => {
|
|
|
|
|
+ area.first = getMinFirstValue()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 强制更新DOM以确保单位正确显示
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ console.log('计费方式已更改为:', templateInfo.calcMethod)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 处理单位变化
|
|
|
|
|
+const handleUnitChange = () => {
|
|
|
|
|
+ // 更新默认规则的首值、步长
|
|
|
|
|
+ defaultRule.first = getMinFirstValue()
|
|
|
|
|
+
|
|
|
|
|
+ // 更新所有特殊区域的首值
|
|
|
|
|
+ specialAreas.value.forEach(area => {
|
|
|
|
|
+ area.first = getMinFirstValue()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ console.log('单位已更改为:', templateInfo.unit)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加特殊区域
|
|
|
|
|
+const addSpecialArea = () => {
|
|
|
|
|
+ specialAreas.value.push({
|
|
|
|
|
+ area_name: '',
|
|
|
|
|
+ regions: [],
|
|
|
|
|
+ first: getMinFirstValue(),
|
|
|
|
|
+ firstPrice: 15,
|
|
|
|
|
+ nextPrice: 8
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 删除特殊区域
|
|
|
|
|
+const removeSpecialArea = (index: number) => {
|
|
|
|
|
+ ElMessageBox.confirm('确定要删除这个特殊区域吗?', '提示', {
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ specialAreas.value.splice(index, 1)
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ // 取消删除
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 删除地区
|
|
|
|
|
+const removeRegion = (areaIndex: number, regionIndex: number) => {
|
|
|
|
|
+ specialAreas.value[areaIndex].regions.splice(regionIndex, 1)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 保存模板
|
|
|
|
|
+const saveTemplate = () => {
|
|
|
|
|
+ // 数据验证
|
|
|
|
|
+ if (!templateInfo.name) {
|
|
|
|
|
+ ElMessage.error('请输入模板名称')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 验证默认规则
|
|
|
|
|
+ if (defaultRule.first <= 0) {
|
|
|
|
|
+ ElMessage.error('默认规则' + getFirstLabel() + '必须大于0')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (defaultRule.firstPrice < 0) {
|
|
|
|
|
+ ElMessage.error('默认规则' + getFirstPriceLabel() + '不能为负数')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (defaultRule.nextPrice < 0) {
|
|
|
|
|
+ ElMessage.error('默认规则' + getNextPriceLabel() + '不能为负数')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 验证特殊区域
|
|
|
|
|
+ for (let i = 0; i < specialAreas.value.length; i++) {
|
|
|
|
|
+ const area = specialAreas.value[i]
|
|
|
|
|
+ if (!area.area_name) {
|
|
|
|
|
+ ElMessage.error(`第${i + 1}个特殊区域请输入区域名称`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (area.regions.length === 0) {
|
|
|
|
|
+ ElMessage.error(`第${i + 1}个特殊区域请选择地区`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (area.first <= 0) {
|
|
|
|
|
+ ElMessage.error(`第${i + 1}个特殊区域` + getFirstLabel() + '必须大于0')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (area.firstPrice < 0) {
|
|
|
|
|
+ ElMessage.error(`第${i + 1}个特殊区域` + getFirstPriceLabel() + '不能为负数')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (area.nextPrice < 0) {
|
|
|
|
|
+ ElMessage.error(`第${i + 1}个特殊区域` + getNextPriceLabel() + '不能为负数')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理特殊区域数据,确保只包含有效的6字段结构
|
|
|
|
|
+ const processedSpecialAreas = specialAreas.value.map(area => {
|
|
|
|
|
+ // 过滤掉无效的区域数据并确保每个区域都有province_id
|
|
|
|
|
+ const validRegions = area.regions
|
|
|
|
|
+ .filter(region => region && region.province_id)
|
|
|
|
|
+ .map(region => ({
|
|
|
|
|
+ province_id: region.province_id,
|
|
|
|
|
+ province_name: region.province_name || '',
|
|
|
|
|
+ city_id: region.city_id || null,
|
|
|
|
|
+ city_name: region.city_name || null,
|
|
|
|
|
+ area_id: region.area_id || null,
|
|
|
|
|
+ area_name: region.area_name || null
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ area_name: area.area_name,
|
|
|
|
|
+ first: area.first,
|
|
|
|
|
+ firstPrice: area.firstPrice,
|
|
|
|
|
+ nextPrice: area.nextPrice,
|
|
|
|
|
+ regions: validRegions
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const templateData = {
|
|
|
|
|
+ templateInfo: { ...templateInfo },
|
|
|
|
|
+ defaultRule: { ...defaultRule },
|
|
|
|
|
+ specialAreas: processedSpecialAreas
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ emit('save', templateData)
|
|
|
|
|
+ const message = isEditMode.value ? '模板保存成功' : '模板创建成功'
|
|
|
|
|
+ ElMessage.success(message)
|
|
|
|
|
+ console.log('保存模板数据:', templateData)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 取消操作
|
|
|
|
|
+const cancel = () => {
|
|
|
|
|
+ ElMessageBox.confirm('确定要取消吗?未保存的数据将会丢失。', '提示', {
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ emit('cancel')
|
|
|
|
|
+ console.log('取消操作')
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ // 取消操作
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置表单
|
|
|
|
|
+const resetForm = () => {
|
|
|
|
|
+ templateInfo.id = null
|
|
|
|
|
+ templateInfo.name = ''
|
|
|
|
|
+ templateInfo.calcMethod = 'piece'
|
|
|
|
|
+ templateInfo.unit = 'kg'
|
|
|
|
|
+ defaultRule.first = 1
|
|
|
|
|
+ defaultRule.firstPrice = 10
|
|
|
|
|
+ defaultRule.nextPrice = 5
|
|
|
|
|
+ specialAreas.value = []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加当前选中的省份到已选择列表
|
|
|
|
|
+const addCurrentProvincesToSelected = () => {
|
|
|
|
|
+ const newRegions = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选中的省份
|
|
|
|
|
+ for (const province of selectedProvinces.value) {
|
|
|
|
|
+ newRegions.push({
|
|
|
|
|
+ area_id: province.area_id,
|
|
|
|
|
+ city_id: province.city_id,
|
|
|
|
|
+ area_name: province.area_name,
|
|
|
|
|
+ city_name: province.city_name,
|
|
|
|
|
+ province_id: province.province_id,
|
|
|
|
|
+ province_name: province.province_name
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并到已选择区域并去重
|
|
|
|
|
+ const allRegions = [...selectedRegions.value, ...newRegions];
|
|
|
|
|
+ const uniqueRegions = allRegions.filter((region, index, self) =>
|
|
|
|
|
+ index === self.findIndex(r =>
|
|
|
|
|
+ r.province_id === region.province_id &&
|
|
|
|
|
+ r.city_id === region.city_id &&
|
|
|
|
|
+ r.area_id === region.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 检查添加省份时是否存在下级区域(已选择的城市或区县)
|
|
|
|
|
+ const finalRegions = [];
|
|
|
|
|
+ for (const region of uniqueRegions) {
|
|
|
|
|
+ let shouldAdd = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (!region.city_id && !region.area_id) {
|
|
|
|
|
+ // 对于省份,检查是否存在下级区域
|
|
|
|
|
+ const hasChild = uniqueRegions.some(r =>
|
|
|
|
|
+ (r.province_id === region.province_id && (r.city_id || r.area_id))
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasChild) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.province_name}的下级区域,无法再添加该省份`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (region.city_id && !region.area_id) {
|
|
|
|
|
+ // 对于城市,检查是否存在上级省份或下级区县
|
|
|
|
|
+ const hasParent = uniqueRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && !r.city_id && !r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+ const hasChild = uniqueRegions.some(r =>
|
|
|
|
|
+ r.city_id === region.city_id && r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParent) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.province_name}的上级省份,无法再添加该城市`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (hasChild) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.city_name}的下级区县,无法再添加该城市`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (region.city_id && region.area_id) {
|
|
|
|
|
+ // 对于区县,检查是否存在上级省份或城市
|
|
|
|
|
+ const hasParentProvince = uniqueRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && !r.city_id && !r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+ const hasParentCity = uniqueRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && r.city_id === region.city_id && !r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParentProvince) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.province_name}的上级省份,无法再添加该区县`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParentCity) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.city_name}的上级城市,无法再添加该区县`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (shouldAdd) {
|
|
|
|
|
+ finalRegions.push(region);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ selectedRegions.value = finalRegions;
|
|
|
|
|
+
|
|
|
|
|
+ // 清空当前选中状态
|
|
|
|
|
+ selectedProvinces.value = [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加当前选中的城市到已选择列表
|
|
|
|
|
+const addCurrentCitiesToSelected = () => {
|
|
|
|
|
+ const newRegions = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选中的城市
|
|
|
|
|
+ for (const city of selectedCities.value) {
|
|
|
|
|
+ newRegions.push({
|
|
|
|
|
+ province_id: city.province_id,
|
|
|
|
|
+ province_name: city.province_name,
|
|
|
|
|
+ city_id: city.city_id,
|
|
|
|
|
+ city_name: city.city_name,
|
|
|
|
|
+ area_id: city.area_id,
|
|
|
|
|
+ area_name: city.area_name
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并到已选择区域并去重
|
|
|
|
|
+ const allRegions = [...selectedRegions.value, ...newRegions];
|
|
|
|
|
+ const uniqueRegions = allRegions.filter((region, index, self) =>
|
|
|
|
|
+ index === self.findIndex(r =>
|
|
|
|
|
+ r.province_id === region.province_id &&
|
|
|
|
|
+ r.city_id === region.city_id &&
|
|
|
|
|
+ r.area_id === region.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 检查添加城市时是否存在上级或下级区域
|
|
|
|
|
+ const finalRegions = [];
|
|
|
|
|
+ for (const region of uniqueRegions) {
|
|
|
|
|
+ let shouldAdd = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (!region.city_id && !region.area_id) {
|
|
|
|
|
+ // 对于省份,检查是否存在下级区域
|
|
|
|
|
+ const hasChild = uniqueRegions.some(r =>
|
|
|
|
|
+ (r.province_id === region.province_id && (r.city_id || r.area_id))
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasChild) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.province_name}的下级区域,无法再添加该省份`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (region.city_id && !region.area_id) {
|
|
|
|
|
+ // 对于城市,检查是否存在上级省份或下级区县
|
|
|
|
|
+ const hasParent = uniqueRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && !r.city_id && !r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+ const hasChild = uniqueRegions.some(r =>
|
|
|
|
|
+ r.city_id === region.city_id && r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParent) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.province_name}的上级省份,无法再添加该城市`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (hasChild) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.city_name}的下级区县,无法再添加该城市`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (region.city_id && region.area_id) {
|
|
|
|
|
+ // 对于区县,检查是否存在上级省份或城市
|
|
|
|
|
+ const hasParentProvince = uniqueRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && !r.city_id && !r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+ const hasParentCity = uniqueRegions.some(r =>
|
|
|
|
|
+ r.province_id === region.province_id && r.city_id === region.city_id && !r.area_id
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParentProvince) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.province_name}的上级省份,无法再添加该区县`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParentCity) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${region.city_name}的上级城市,无法再添加该区县`);
|
|
|
|
|
+ shouldAdd = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (shouldAdd) {
|
|
|
|
|
+ finalRegions.push(region);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ selectedRegions.value = finalRegions;
|
|
|
|
|
+
|
|
|
|
|
+ // 清空当前选中状态
|
|
|
|
|
+ selectedCities.value = [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 添加当前选中的区县到已选择列表
|
|
|
|
|
+const addCurrentAreasToSelected = () => {
|
|
|
|
|
+ const newRegions = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 添加选中的区县
|
|
|
|
|
+ for (const area of selectedAreas.value) {
|
|
|
|
|
+ // 检查要添加的区县是否存在上级(已选择的省份或城市)
|
|
|
|
|
+ const hasParentProvince = selectedProvinces.value.some(province => province.province_id === area.province_id);
|
|
|
|
|
+ const hasParentCity = selectedCities.value.some(city => city.city_id === area.city_id);
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParentProvince) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${area.name}的上级省份,无法再添加该区县`);
|
|
|
|
|
+ continue; // 跳过这个区县,不添加
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (hasParentCity) {
|
|
|
|
|
+ ElMessage.warning(`您已选择了${area.name}的上级城市,无法再添加该区县`);
|
|
|
|
|
+ continue; // 跳过这个区县,不添加
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ newRegions.push({
|
|
|
|
|
+ area_id: area.area_id,
|
|
|
|
|
+ city_id: area.city_id,
|
|
|
|
|
+ area_name: area.area_name,
|
|
|
|
|
+ city_name: area.city_name,
|
|
|
|
|
+ province_id: area.province_id,
|
|
|
|
|
+ province_name: area.province_name
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并到已选择区域并去重
|
|
|
|
|
+ const allRegions = [...selectedRegions.value, ...newRegions];
|
|
|
|
|
+ const uniqueRegions = allRegions.filter((region, index, self) =>
|
|
|
|
|
+ index === self.findIndex(r =>
|
|
|
|
|
+ r.province_id === region.province_id &&
|
|
|
|
|
+ r.city_id === region.city_id &&
|
|
|
|
|
+ r.area_id === region.area_id
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ selectedRegions.value = uniqueRegions;
|
|
|
|
|
+
|
|
|
|
|
+ // 清空当前选中状态
|
|
|
|
|
+ selectedAreas.value = [];
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.shipping-template-component {
|
|
|
|
|
+ .card-header {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .card-sub-header {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .special-area {
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .region-tags {
|
|
|
|
|
+ min-height: 32px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .province-checkbox-group {
|
|
|
|
|
+ max-height: 300px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .mb-4 {
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-card__header) {
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
|
|
+ font-weight: normal;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .input-with-unit {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ .unit-text {
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .form-item-tip {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .region-selector-container {
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ height: 350px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+
|
|
|
|
|
+ .selector-header {
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ border-bottom: 1px solid #dcdfe6;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .region-item {
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ background-color: #ecf5ff;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .selected-region-item {
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .no-data {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-footer {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|