luolinsong 2 месяцев назад
Родитель
Сommit
8cfffc6a44
2 измененных файлов с 830 добавлено и 23 удалено
  1. 773 0
      src/components/City/indexX.vue
  2. 57 23
      src/views/hisStore/shippingTemplates/index.vue

+ 773 - 0
src/components/City/indexX.vue

@@ -0,0 +1,773 @@
+<template>
+  <el-dialog
+    :close-on-click-modal="false"
+    :visible.sync="addressView"
+    append-to-body
+    class="modal"
+    title="选择城市"
+    width="1100px">
+
+    <!-- 全选/清空操作栏 -->
+    <el-row :gutter="24" type="flex">
+      <el-col :span="24" class="item">
+        <div class="check-btn">
+          <el-checkbox v-model="iSselect" @change="allCheckbox">全选</el-checkbox>
+          <div class="empty" @click="empty">清空</div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 省市区三级展示 -->
+    <el-row :gutter="24" :loading="loading">
+      <el-col
+        :xl="6" :lg="6" :md="6" :sm="8" :xs="12"
+        class="province-item"
+        v-for="(province, provinceIndex) in cityList"
+        :key="province.cityId">
+
+        <!-- 省级复选框 -->
+        <div
+          class="province-wrapper"
+          @mouseenter="enter(provinceIndex)"
+          @mouseleave="leaveProvince(provinceIndex)"
+          :class="{ 'hovered': hoveredProvince === provinceIndex }">
+
+          <el-checkbox
+            v-model="province.checked"
+            :label="province.cityName"
+            :disabled="province.disabled"
+            @change="checkedProvince(provinceIndex)"
+            :class="{ 'disabled-checkbox': province.disabled }"
+          >
+            {{ province.cityName }}
+            <span v-if="province.count > 0" class="selected-count">(已选{{ province.count }}个)</span>
+          </el-checkbox>
+
+          <!-- 市级悬浮面板 -->
+          <div
+            class="city-panel"
+            v-show="activeCity === provinceIndex"
+            @mouseenter="enterCityPanel(provinceIndex)"
+            @mouseleave="leaveCityPanel(provinceIndex)">
+            <div class="checkBox">
+              <div class="arrow"></div>
+              <div class="city-list">
+                <div
+                  class="city-item"
+                  v-for="(city, cityIndex) in province.children"
+                  :key="city.cityId">
+
+                  <!-- 市级复选框 -->
+                  <el-checkbox
+                    v-model="city.checked"
+                    :label="city.cityName"
+                    :disabled="city.disabled"
+                    @change="checkedCity(provinceIndex, cityIndex)"
+                    :class="{ 'disabled-checkbox': city.disabled }"
+                  >
+                    {{ city.cityName }}
+                    <span v-if="city.count > 0" class="selected-count">(已选{{ city.count }}个)</span>
+                  </el-checkbox>
+
+                  <!-- 县级列表 -->
+                  <div v-if="city.children && city.children.length > 0" class="county-list">
+                    <el-checkbox
+                      v-for="(county, countyIndex) in city.children"
+                      :key="county.cityId"
+                      v-model="county.checked"
+                      :label="county.cityName"
+                      :disabled="county.disabled"
+                      @change="checkedCounty(provinceIndex, cityIndex, countyIndex)"
+                      class="county-item"
+                      :class="{ 'disabled-checkbox': county.disabled }"
+                    >
+                      {{ county.cityName }}
+                    </el-checkbox>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 对话框底部按钮 -->
+    <div slot="footer">
+      <el-button @click="close">取消</el-button>
+      <el-button type="primary" @click="confirm">确定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { getAllList } from "@/api/hisStore/city";
+
+export default {
+  name: 'CityZm',
+  props: {
+    type: {
+      type: Number,
+      default: 0
+    },
+    selectedCities: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      iSselect: false,
+      addressView: false,
+      cityList: [],
+      activeCity: -1,
+      loading: false,
+      // 增加相关状态
+      hoverTimer: null,
+      closeTimer: null,
+      hoveredProvince: -1,
+      hoveredPanel: false,
+      closeDelay: 300 // 延迟300ms关闭
+    };
+  },
+  methods: {
+    // 打开对话框
+    open() {
+      this.addressView = true;
+      this.getCityList();
+    },
+
+    // 鼠标进入省份
+    enter(index) {
+      clearTimeout(this.closeTimer);
+      this.hoveredProvince = index;
+      this.activeCity = index;
+    },
+
+    // 鼠标离开省份
+    leaveProvince(index) {
+      // 延迟关闭,给用户时间移动到面板
+      this.closeTimer = setTimeout(() => {
+        if (!this.hoveredPanel || this.hoveredProvince !== index) {
+          this.activeCity = -1;
+          this.hoveredPanel = false;
+          this.hoveredProvince = -1;
+        }
+      }, this.closeDelay);
+    },
+
+    // 鼠标进入面板
+    enterCityPanel(index) {
+      clearTimeout(this.closeTimer);
+      this.hoveredPanel = true;
+      this.hoveredProvince = index;
+      this.activeCity = index;
+    },
+
+    // 鼠标离开面板
+    leaveCityPanel(index) {
+      this.closeTimer = setTimeout(() => {
+        this.activeCity = -1;
+        this.hoveredPanel = false;
+        this.hoveredProvince = -1;
+      }, this.closeDelay);
+    },
+
+    // 获取省市区数据
+    async getCityList() {
+      this.loading = true;
+      try {
+        const res = await getAllList();
+        const allData = res.data || [];
+
+        // 分离三级数据
+        const provinces = allData.filter(item => item.level === 1);
+        const cities = allData.filter(item => item.level === 2);
+        const counties = allData.filter(item => item.level === 3);
+
+        // 构建省市区树形结构
+        this.cityList = provinces.map(province => {
+          const provinceCities = cities.filter(city => city.parentId === province.cityId);
+
+          const children = provinceCities.map(city => {
+            const cityCounties = counties.filter(county => county.parentId === city.cityId);
+
+            return {
+              ...city,
+              checked: false,
+              disabled: false,
+              count: 0,
+              children: cityCounties.map(county => ({
+                ...county,
+                checked: false,
+                disabled: false
+              }))
+            };
+          });
+
+          return {
+            ...province,
+            checked: false,
+            disabled: false,
+            count: 0,
+            children: children
+          };
+        });
+
+        // 标记已选择的数据
+        this.markSelectedCities();
+
+      } catch (error) {
+        console.error('获取城市数据失败:', error);
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    // 标记已选择的城市
+    markSelectedCities() {
+      if (!this.selectedCities || this.selectedCities.length === 0) return;
+
+      this.selectedCities.forEach(selectedProvince => {
+        const province = this.cityList.find(p => p.cityId === selectedProvince.cityId);
+        if (!province) return;
+
+        // 如果选择了整个省
+        if (!selectedProvince.children || selectedProvince.children.length === 0) {
+          province.disabled = true;
+          province.checked = true;
+          province.children?.forEach(city => {
+            city.disabled = true;
+            city.checked = true;
+            city.children?.forEach(county => {
+              county.disabled = true;
+              county.checked = true;
+            });
+          });
+          return;
+        }
+
+        // 检查是否选择了所有市
+        const allCitiesSelected = selectedProvince.children.length === province.children.length;
+
+        selectedProvince.children.forEach(selectedCity => {
+          const city = province.children.find(c => c.cityId === selectedCity.cityId);
+          if (!city) return;
+
+          // 如果选择了整个市
+          if (!selectedCity.children || selectedCity.children.length === 0) {
+            city.disabled = true;
+            city.checked = true;
+            city.children?.forEach(county => {
+              county.disabled = true;
+              county.checked = true;
+            });
+          } else {
+            // 选择了部分县
+            selectedCity.children.forEach(selectedCounty => {
+              const county = city.children.find(c => c.cityId === selectedCounty.cityId);
+              if (county) {
+                county.disabled = true;
+                county.checked = true;
+              }
+            });
+
+            // 检查是否选择了所有县
+            const selectedCountyCount = selectedCity.children.length;
+            const totalCountyCount = city.children.length;
+            if (selectedCountyCount === totalCountyCount) {
+              city.disabled = true;
+              city.checked = true;
+            }
+          }
+        });
+
+        // 如果选择了所有市,禁用省
+        if (allCitiesSelected) {
+          province.disabled = true;
+          province.checked = true;
+        }
+      });
+
+      // 更新计数
+      this.updateAllCounts();
+    },
+
+    // 全选/全不选
+    allCheckbox() {
+      this.cityList.forEach(province => {
+        if (!province.disabled) {
+          province.checked = this.iSselect;
+          this.updateProvinceCount(province);
+
+          province.children?.forEach(city => {
+            if (!city.disabled) {
+              city.checked = this.iSselect;
+              this.updateCityCount(city);
+
+              city.children?.forEach(county => {
+                if (!county.disabled) {
+                  county.checked = this.iSselect;
+                }
+              });
+            }
+          });
+        }
+      });
+    },
+
+    // 清空选择
+    empty() {
+      // 清除所有定时器
+      clearTimeout(this.hoverTimer);
+      clearTimeout(this.closeTimer);
+
+      this.cityList.forEach(province => {
+        if (!province.disabled) {
+          province.checked = false;
+          province.count = 0;
+
+          province.children?.forEach(city => {
+            if (!city.disabled) {
+              city.checked = false;
+              city.count = 0;
+
+              city.children?.forEach(county => {
+                if (!county.disabled) {
+                  county.checked = false;
+                }
+              });
+            }
+          });
+        }
+      });
+      this.iSselect = false;
+    },
+
+    // 选择省
+    checkedProvince(provinceIndex) {
+      const province = this.cityList[provinceIndex];
+      if (province.disabled) return;
+
+      const isChecked = province.checked;
+
+      province.children?.forEach(city => {
+        if (!city.disabled) {
+          city.checked = isChecked;
+          this.updateCityCount(city);
+
+          city.children?.forEach(county => {
+            if (!county.disabled) {
+              county.checked = isChecked;
+            }
+          });
+        }
+      });
+
+      this.updateProvinceCount(province);
+      this.updateISselect();
+    },
+
+    // 选择市
+    checkedCity(provinceIndex, cityIndex) {
+      const province = this.cityList[provinceIndex];
+      const city = province.children[cityIndex];
+      if (city.disabled) return;
+
+      const isChecked = city.checked;
+
+      // 选择/取消选择所有县
+      city.children?.forEach(county => {
+        if (!county.disabled) {
+          county.checked = isChecked;
+        }
+      });
+
+      this.updateCityCount(city);
+      this.updateProvinceCount(province);
+      this.updateISselect();
+    },
+
+    // 选择县
+    checkedCounty(provinceIndex, cityIndex, countyIndex) {
+      const province = this.cityList[provinceIndex];
+      const city = province.children[cityIndex];
+      const county = city.children[countyIndex];
+
+      if (county.disabled) return;
+
+      // 更新市的选择状态
+      this.updateCityCount(city);
+      // 更新省的选择状态
+      this.updateProvinceCount(province);
+      // 更新全选状态
+      this.updateISselect();
+    },
+
+    // 更新市的选择计数
+    updateCityCount(city) {
+      if (!city.children || city.children.length === 0) return;
+
+      const selectedCounties = city.children.filter(county => county.checked && !county.disabled).length;
+      city.count = selectedCounties;
+
+      // 更新市的选中状态
+      const allCountiesSelected = selectedCounties === city.children.filter(c => !c.disabled).length;
+      city.checked = allCountiesSelected || selectedCounties > 0;
+    },
+
+    // 更新省的选择计数
+    updateProvinceCount(province) {
+      if (!province.children || province.children.length === 0) return;
+
+      const selectedCities = province.children.filter(city => {
+        if (city.disabled) return true; // 已禁用的视为已选
+        return city.checked;
+      }).length;
+
+      province.count = selectedCities;
+
+      // 更新省的选中状态
+      const allCitiesSelected = selectedCities === province.children.filter(c => !c.disabled).length;
+      province.checked = allCitiesSelected || selectedCities > 0;
+    },
+
+    // 更新全选状态
+    updateISselect() {
+      const enabledProvinces = this.cityList.filter(p => !p.disabled);
+      if (enabledProvinces.length === 0) {
+        this.iSselect = false;
+        return;
+      }
+
+      const allProvincesSelected = enabledProvinces.every(province => province.checked);
+      this.iSselect = allProvincesSelected;
+    },
+
+    // 更新所有计数
+    updateAllCounts() {
+      this.cityList.forEach(province => {
+        this.updateProvinceCount(province);
+        province.children?.forEach(city => {
+          this.updateCityCount(city);
+        });
+      });
+      this.updateISselect();
+    },
+
+    // 确定选择
+    confirm() {
+      const selectedData = this.getSelectedData();
+
+      if (selectedData.length === 0) {
+        this.$message.error('至少选择一个省份、城市或县区');
+        return;
+      }
+
+      this.$emit('selectCity', selectedData, this.type);
+      this.close();
+    },
+
+    // 获取选择的数据
+    getSelectedData() {
+      const result = [];
+
+      this.cityList.forEach(province => {
+        // 检查省是否被选中(直接选中或通过选中所有市/县间接选中)
+        const isProvinceChecked = province.checked && !province.disabled;
+        const hasSelectedCities = province.children?.some(city =>
+          (city.checked && !city.disabled) ||
+          city.children?.some(county => county.checked && !county.disabled)
+        );
+
+        if (isProvinceChecked || hasSelectedCities) {
+          const provinceData = {
+            cityId: province.cityId,
+            name: province.cityName,
+            children: []
+          };
+
+          // 处理市级数据
+          province.children?.forEach(city => {
+            // 判断市是否被选中(直接选中或通过选中所有县间接选中)
+            const isCityChecked = city.checked && !city.disabled;
+            const selectedCounties = city.children?.filter(county => county.checked && !county.disabled) || [];
+            const hasSelectedCounties = selectedCounties.length > 0;
+
+            if (isCityChecked || hasSelectedCounties) {
+              const cityData = {
+                cityId: city.cityId,
+                name: city.cityName
+              };
+
+              // 如果有选中的县级,添加到市的 children 中
+              if (hasSelectedCounties && selectedCounties.length > 0) {
+                cityData.children = selectedCounties.map(county => ({
+                  cityId: county.cityId,
+                  name: county.cityName
+                }));
+              }
+
+              provinceData.children.push(cityData);
+            }
+          });
+
+          result.push(provinceData);
+        }
+      });
+
+      return result;
+    },
+
+    // 关闭对话框
+    close() {
+      // 清除所有定时器
+      clearTimeout(this.hoverTimer);
+      clearTimeout(this.closeTimer);
+
+      this.addressView = false;
+      this.cityList = [];
+      this.iSselect = false;
+      this.activeCity = -1;
+      this.hoveredProvince = -1;
+      this.hoveredPanel = false;
+    },
+
+    // 清理定时器
+    beforeDestroy() {
+      clearTimeout(this.hoverTimer);
+      clearTimeout(this.closeTimer);
+    }
+  }
+};
+</script>
+
+<style scoped>
+.modal {
+  max-height: 80vh;
+  overflow-y: auto;
+}
+
+.modal .province-item {
+  position: relative;
+  margin-bottom: 20px;
+  min-height: 50px;
+}
+
+.province-wrapper {
+  position: relative;
+  padding: 8px 12px;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+}
+
+.province-wrapper.hovered {
+  background-color: #f5f7fa;
+}
+
+.modal .city-panel {
+  position: absolute;
+  z-index: 1000;
+  top: 45px; /* 调整为刚好在复选框下方 */
+  left: 0;
+  width: 320px; /* 稍微加宽 */
+  min-height: 200px; /* 设置最小高度 */
+  background: #fff;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+}
+
+.modal .checkBox {
+  padding: 15px;
+  border-radius: 4px;
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.modal .arrow {
+  position: absolute;
+  top: -16px;
+  left: 20px;
+  width: 0;
+  height: 0;
+  border: 8px solid transparent;
+  border-bottom-color: #ebeef5;
+  z-index: 1001;
+}
+
+.modal .arrow:before {
+  position: absolute;
+  top: -7px;
+  left: -7px;
+  content: "";
+  width: 0;
+  height: 0;
+  border: 7px solid transparent;
+  border-bottom-color: #fff;
+  z-index: 1002;
+}
+
+.city-list {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.city-item {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding: 4px;
+  border-radius: 4px;
+  transition: background-color 0.2s ease;
+}
+
+.city-item:hover {
+  background-color: #f8f9fa;
+}
+
+.county-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-left: 20px;
+  margin-top: 5px;
+  padding-left: 15px;
+  border-left: 1px dashed #dcdfe6;
+}
+
+.county-item {
+  width: calc(50% - 4px);
+  font-size: 12px;
+}
+
+.selected-count {
+  font-size: 12px;
+  color: #409eff;
+  margin-left: 4px;
+}
+
+.empty {
+  cursor: pointer;
+  margin-left: 15px;
+  color: #f56c6c;
+  transition: color 0.2s ease;
+}
+
+.empty:hover {
+  color: #f78989;
+}
+
+.check-btn {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  margin-bottom: 15px;
+  padding: 10px 0;
+  border-bottom: 1px solid #ebeef5;
+}
+
+/* 禁用状态样式 */
+.disabled-checkbox {
+  opacity: 0.6;
+  cursor: not-allowed !important;
+}
+
+.disabled-checkbox /deep/ .el-checkbox__input.is-disabled {
+  cursor: not-allowed;
+}
+
+.disabled-checkbox /deep/ .el-checkbox__input.is-disabled .el-checkbox__inner {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+}
+
+.disabled-checkbox /deep/ .el-checkbox__input.is-disabled + .el-checkbox__label {
+  color: #c0c4cc;
+}
+
+/* 鼠标悬停效果 */
+.province-wrapper:hover {
+  background-color: #f5f7fa;
+}
+
+/* 添加悬停提示 */
+.province-wrapper:hover:after {
+  content: "▼ 展开";
+  position: absolute;
+  right: 10px;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 12px;
+  color: #909399;
+  opacity: 0.8;
+}
+
+/* 确保复选框可点击区域足够大 */
+.province-wrapper /deep/ .el-checkbox {
+  width: 100%;
+}
+
+.province-wrapper /deep/ .el-checkbox__label {
+  display: inline-block;
+  width: calc(100% - 20px);
+  padding-right: 60px; /* 为悬停提示留出空间 */
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .modal .city-panel {
+    width: 280px;
+  }
+}
+
+@media (max-width: 992px) {
+  .modal .city-panel {
+    width: 250px;
+    left: -50px; /* 在小屏幕上调整位置 */
+  }
+
+  .modal .arrow {
+    left: 60px; /* 调整箭头位置 */
+  }
+}
+
+@media (max-width: 768px) {
+  .modal {
+    width: 95% !important;
+    max-width: 600px;
+  }
+
+  .modal .city-panel {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 90vw;
+    max-width: 400px;
+    max-height: 80vh;
+    z-index: 2000;
+  }
+
+  .county-item {
+    width: 100%; /* 小屏幕下一行一个 */
+  }
+
+  .modal .arrow {
+    display: none; /* 移动端隐藏箭头 */
+  }
+}
+
+/* 为触摸设备优化 */
+@media (hover: none) and (pointer: coarse) {
+  .province-wrapper {
+    padding: 12px 15px; /* 增大点击区域 */
+  }
+
+  .city-panel {
+    border: 2px solid #409eff; /* 更明显的边框 */
+  }
+}
+</style>

+ 57 - 23
src/views/hisStore/shippingTemplates/index.vue

@@ -203,7 +203,7 @@
 
 <script>
 import { listShippingTemplates, getShippingTemplates, delShippingTemplates, addShippingTemplates, updateShippingTemplates, exportShippingTemplates } from "@/api/hisStore/shippingTemplates";
-import CityZm from '@/components/City/indexZm'
+import CityZm from '@/components/City/indexX'
 export default {
   name: "ShippingTemplates",
   components: {
@@ -274,28 +274,62 @@ export default {
       }
     },
     selectCity: function (data, type) {
-      let cityName = data.map(function (item) {
-        return item.name;
-      }).join(';');
-      switch (type) {
-        case 1:
-          this.templateList.push({
-            region: data,
-            regionName: cityName,
-            first: 1,
-            price: 0,
-            continues: 1,
-            continuePrice: 0
-          });
-          break;
-        case 2:
-          this.appointList.push({
-            place: data,
-            placeName: cityName,
-            number: 0,
-            price: 0
-          });
-          break;
+      console.log('=== selectCity 接收的数据 ===');
+      console.log('原始数据:', data);
+
+      // 更简洁的递归函数获取所有名称
+      const collectAllNames = (items) => {
+        let names = [];
+
+        const traverse = (node) => {
+          // 添加当前节点的名称
+          const nodeName = node.name || node.cityName;
+          if (nodeName) {
+            names.push(nodeName);
+          }
+
+          // 递归处理子节点
+          if (node.children && node.children.length > 0) {
+            node.children.forEach(child => traverse(child));
+          }
+        };
+
+        items.forEach(item => traverse(item));
+        return names;
+      };
+
+      // 获取所有名称并去重
+      const allNames = collectAllNames(data);
+      const uniqueNames = [...new Set(allNames)];
+      const regionName = uniqueNames.join(';');
+
+      console.log('收集到的所有名称:', allNames);
+      console.log('去重后的名称:', uniqueNames);
+      console.log('生成的区域名称:', regionName);
+
+      // 保存数据
+      const newItem = {
+        region: data,
+        regionName: regionName,
+        first: 1,
+        price: 0,
+        continues: 1,
+        continuePrice: 0
+      };
+
+      if (type === 1) {
+        this.templateList.push(newItem);
+        console.log('添加配送区域后的 templateList:', this.templateList);
+      } else if (type === 2) {
+        // 包邮区域使用不同的字段名
+        const appointItem = {
+          place: data,
+          placeName: regionName,
+          number: 0,
+          price: 0
+        };
+        this.appointList.push(appointItem);
+        console.log('添加包邮区域后的 appointList:', this.appointList);
       }
     },
     // 单独添加配送区域