From 73ebc8b1e79056d0e06b898773f21b7d870c469c Mon Sep 17 00:00:00 2001
From: jhd <1684074631@qq.com>
Date: Tue, 2 Jun 2026 15:44:00 +0800
Subject: [PATCH] =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E8=8E=B7=E5=8F=96=E7=9C=81?=
=?UTF-8?q?=E4=BB=BD=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
scripts/backfill-province.cjs | 418 +++++++++++++++++++
src/api/sales.js | 32 ++
src/layout/components/Sidebar/index.vue | 4 +-
src/router/index.js | 6 +
src/views/screens/sales-heatmap/index.vue | 465 ++++++++++++++++++++++
5 files changed, 924 insertions(+), 1 deletion(-)
create mode 100644 scripts/backfill-province.cjs
create mode 100644 src/views/screens/sales-heatmap/index.vue
diff --git a/scripts/backfill-province.cjs b/scripts/backfill-province.cjs
new file mode 100644
index 0000000..6ea1d53
--- /dev/null
+++ b/scripts/backfill-province.cjs
@@ -0,0 +1,418 @@
+/**
+ * 客户省份回填脚本
+ *
+ * 从 crm_customer.address 字段中提取省份信息,回填到 province 字段。
+ * 匹配优先级:省份前缀 > 地级市名 > 县级市/区名
+ *
+ * 用法: node scripts/backfill-province.js
+ */
+
+const mysql = require('mysql2')
+
+// ===== 省份名称(与 china.json 完全一致) =====
+const PROVINCES = [
+ '北京市', '天津市', '上海市', '重庆市',
+ '河北省', '山西省', '辽宁省', '吉林省', '黑龙江省',
+ '江苏省', '浙江省', '安徽省', '福建省', '江西省', '山东省',
+ '河南省', '湖北省', '湖南省', '广东省', '海南省',
+ '四川省', '贵州省', '云南省', '陕西省', '甘肃省', '青海省',
+ '台湾省',
+ '内蒙古自治区', '广西壮族自治区', '西藏自治区', '宁夏回族自治区', '新疆维吾尔自治区',
+ '香港特别行政区', '澳门特别行政区'
+]
+
+// ===== 省份简称 → 全称映射 =====
+const SHORT_PROVINCE = {
+ '北京': '北京市', '天津': '天津市', '上海': '上海市', '重庆': '重庆市',
+ '河北': '河北省', '山西': '山西省', '辽宁': '辽宁省', '吉林': '吉林省',
+ '黑龙江': '黑龙江省', '江苏': '江苏省', '浙江': '浙江省', '安徽': '安徽省',
+ '福建': '福建省', '江西': '江西省', '山东': '山东省', '河南': '河南省',
+ '湖北': '湖北省', '湖南': '湖南省', '广东': '广东省', '海南': '海南省',
+ '四川': '四川省', '贵州': '贵州省', '云南': '云南省', '陕西': '陕西省',
+ '甘肃': '甘肃省', '青海': '青海省', '台湾': '台湾省',
+ '内蒙古': '内蒙古自治区', '广西': '广西壮族自治区',
+ '西藏': '西藏自治区', '宁夏': '宁夏回族自治区', '新疆': '新疆维吾尔自治区',
+ '香港': '香港特别行政区', '澳门': '澳门特别行政区'
+}
+
+// ===== 城市 → 省份映射(覆盖全部地级行政区和常见县级市) =====
+const CITY_PROVINCE = {
+
+ // ----- 北京市 -----
+ '北京': '北京市',
+
+ // ----- 天津市 -----
+ '天津': '天津市',
+
+ // ----- 上海市 -----
+ '上海': '上海市',
+
+ // ----- 重庆市 -----
+ '重庆': '重庆市',
+
+ // ===== 河北省 =====
+ '石家庄': '河北省', '唐山': '河北省', '秦皇岛': '河北省', '邯郸': '河北省',
+ '邢台': '河北省', '保定': '河北省', '张家口': '河北省', '承德': '河北省',
+ '沧州': '河北省', '廊坊': '河北省', '衡水': '河北省',
+
+ // ===== 山西省 =====
+ '太原': '山西省', '大同': '山西省', '阳泉': '山西省', '长治': '山西省',
+ '晋城': '山西省', '朔州': '山西省', '晋中': '山西省', '运城': '山西省',
+ '忻州': '山西省', '临汾': '山西省', '吕梁': '山西省',
+
+ // ===== 辽宁省 =====
+ '沈阳': '辽宁省', '大连': '辽宁省', '鞍山': '辽宁省', '抚顺': '辽宁省',
+ '本溪': '辽宁省', '丹东': '辽宁省', '锦州': '辽宁省', '营口': '辽宁省',
+ '阜新': '辽宁省', '辽阳': '辽宁省', '盘锦': '辽宁省', '铁岭': '辽宁省',
+ '朝阳': '辽宁省', '葫芦岛': '辽宁省',
+
+ // ===== 吉林省 =====
+ '长春': '吉林省', '吉林': '吉林省', '四平': '吉林省', '辽源': '吉林省',
+ '通化': '吉林省', '白山': '吉林省', '松原': '吉林省', '白城': '吉林省',
+ '延边': '吉林省',
+
+ // ===== 黑龙江省 =====
+ '哈尔滨': '黑龙江省', '齐齐哈尔': '黑龙江省', '鸡西': '黑龙江省',
+ '鹤岗': '黑龙江省', '双鸭山': '黑龙江省', '大庆': '黑龙江省',
+ '伊春': '黑龙江省', '佳木斯': '黑龙江省', '七台河': '黑龙江省',
+ '牡丹江': '黑龙江省', '黑河': '黑龙江省', '绥化': '黑龙江省',
+ '大兴安岭': '黑龙江省',
+
+ // ===== 江苏省 =====
+ '南京': '江苏省', '无锡': '江苏省', '徐州': '江苏省', '常州': '江苏省',
+ '苏州': '江苏省', '南通': '江苏省', '连云港': '江苏省', '淮安': '江苏省',
+ '盐城': '江苏省', '扬州': '江苏省', '镇江': '江苏省', '泰州': '江苏省',
+ '宿迁': '江苏省',
+ // 江苏县级市/县
+ '常熟': '江苏省', '张家港': '江苏省', '昆山': '江苏省', '太仓': '江苏省',
+ '江阴': '江苏省', '宜兴': '江苏省', '丹阳': '江苏省', '扬中': '江苏省',
+ '句容': '江苏省', '靖江': '江苏省', '泰兴': '江苏省', '兴化': '江苏省',
+ '沭阳': '江苏省', '泗阳': '江苏省', '泗洪': '江苏省', '沛县': '江苏省',
+ '丰县': '江苏省', '睢宁': '江苏省', '东海': '江苏省', '灌云': '江苏省',
+ '灌南': '江苏省', '涟水': '江苏省', '洪泽': '江苏省', '盱眙': '江苏省',
+ '金湖': '江苏省', '滨海': '江苏省', '阜宁': '江苏省', '射阳': '江苏省',
+ '建湖': '江苏省', '东台': '江苏省', '宝应': '江苏省', '仪征': '江苏省',
+ '高邮': '江苏省', '溧阳': '江苏省', '如东': '江苏省', '启东': '江苏省',
+ '如皋': '江苏省', '海门': '江苏省', '海安': '江苏省',
+
+ // ===== 浙江省 =====
+ '杭州': '浙江省', '宁波': '浙江省', '温州': '浙江省', '嘉兴': '浙江省',
+ '湖州': '浙江省', '绍兴': '浙江省', '金华': '浙江省', '衢州': '浙江省',
+ '舟山': '浙江省', '台州': '浙江省', '丽水': '浙江省',
+ // 浙江县级市/县
+ '海宁': '浙江省', '嘉善': '浙江省', '平湖': '浙江省', '桐乡': '浙江省',
+ '德清': '浙江省', '长兴': '浙江省', '安吉': '浙江省', '诸暨': '浙江省',
+ '上虞': '浙江省', '嵊州': '浙江省', '新昌': '浙江省', '兰溪': '浙江省',
+ '义乌': '浙江省', '东阳': '浙江省', '永康': '浙江省', '武义': '浙江省',
+ '浦江': '浙江省', '江山': '浙江省', '常山': '浙江省', '开化': '浙江省',
+ '龙游': '浙江省', '临海': '浙江省', '温岭': '浙江省', '玉环': '浙江省',
+ '三门': '浙江省', '天台': '浙江省', '仙居': '浙江省', '青田': '浙江省',
+ '缙云': '浙江省', '遂昌': '浙江省', '松阳': '浙江省', '云和': '浙江省',
+ '庆元': '浙江省', '景宁': '浙江省', '龙泉': '浙江省',
+
+ // ===== 安徽省 =====
+ '合肥': '安徽省', '芜湖': '安徽省', '蚌埠': '安徽省', '淮南': '安徽省',
+ '马鞍山': '安徽省', '淮北': '安徽省', '铜陵': '安徽省', '安庆': '安徽省',
+ '黄山': '安徽省', '滁州': '安徽省', '阜阳': '安徽省', '宿州': '安徽省',
+ '六安': '安徽省', '亳州': '安徽省', '池州': '安徽省', '宣城': '安徽省',
+ // 安徽县级市/县
+ '界首': '安徽省',
+
+ // ===== 福建省 =====
+ '福州': '福建省', '厦门': '福建省', '莆田': '福建省', '三明': '福建省',
+ '泉州': '福建省', '漳州': '福建省', '南平': '福建省', '龙岩': '福建省',
+ '宁德': '福建省',
+
+ // ===== 江西省 =====
+ '南昌': '江西省', '景德镇': '江西省', '萍乡': '江西省', '九江': '江西省',
+ '新余': '江西省', '鹰潭': '江西省', '赣州': '江西省', '吉安': '江西省',
+ '宜春': '江西省', '抚州': '江西省', '上饶': '江西省',
+
+ // ===== 山东省 =====
+ '济南': '山东省', '青岛': '山东省', '淄博': '山东省', '枣庄': '山东省',
+ '东营': '山东省', '烟台': '山东省', '潍坊': '山东省', '济宁': '山东省',
+ '泰安': '山东省', '威海': '山东省', '日照': '山东省', '临沂': '山东省',
+ '德州': '山东省', '聊城': '山东省', '滨州': '山东省', '菏泽': '山东省',
+ // 山东县级市/县
+ '莱芜': '山东省', '博兴': '山东省', '冠县': '山东省', '梁山': '山东省',
+ '曹县': '山东省', '滕州': '山东省', '肥城': '山东省', '新泰': '山东省',
+ '宁阳': '山东省', '东平': '山东省', '沂水': '山东省', '沂南': '山东省',
+ '莒南': '山东省', '临沭': '山东省', '郯城': '山东省', '费县': '山东省',
+ '平邑': '山东省', '蒙阴': '山东省', '沂源': '山东省', '临朐': '山东省',
+ '昌乐': '山东省', '青州': '山东省', '诸城': '山东省', '寿光': '山东省',
+ '安丘': '山东省', '高密': '山东省', '昌邑': '山东省', '曲阜': '山东省',
+ '兖州': '山东省', '邹城': '山东省', '微山': '山东省', '鱼台': '山东省',
+ '金乡': '山东省', '嘉祥': '山东省', '汶上': '山东省', '泗水': '山东省',
+ '梁山县': '山东省',
+
+ // ===== 河南省 =====
+ '郑州': '河南省', '开封': '河南省', '洛阳': '河南省', '平顶山': '河南省',
+ '安阳': '河南省', '鹤壁': '河南省', '新乡': '河南省', '焦作': '河南省',
+ '濮阳': '河南省', '许昌': '河南省', '漯河': '河南省', '三门峡': '河南省',
+ '南阳': '河南省', '商丘': '河南省', '信阳': '河南省', '周口': '河南省',
+ '驻马店': '河南省',
+ // 河南县级市/县
+ '济源': '河南省', '巩义': '河南省', '新郑': '河南省', '新密': '河南省',
+ '登封': '河南省', '荥阳': '河南省', '中牟': '河南省', '偃师': '河南省',
+ '孟津': '河南省', '新安': '河南省', '栾川': '河南省', '嵩县': '河南省',
+ '汝阳': '河南省', '宜阳': '河南省', '洛宁': '河南省', '伊川': '河南省',
+ '林州': '河南省', '滑县': '河南省', '内黄': '河南省', '汤阴': '河南省',
+ '浚县': '河南省', '淇县': '河南省', '原阳': '河南省', '延津': '河南省',
+ '封丘': '河南省', '长垣': '河南省', '卫辉': '河南省', '辉县': '河南省',
+ '修武': '河南省', '博爱': '河南省', '武陟': '河南省', '温县': '河南省',
+ '沁阳': '河南省', '孟州': '河南省', '南乐': '河南省', '范县': '河南省',
+ '台前': '河南省', '禹州': '河南省', '长葛': '河南省', '鄢陵': '河南省',
+ '襄城': '河南省', '舞阳': '河南省', '临颍': '河南省', '义马': '河南省',
+ '灵宝': '河南省', '卢氏': '河南省', '邓州': '河南省', '镇平': '河南省',
+ '内乡': '河南省', '淅川': '河南省', '西峡': '河南省', '唐河': '河南省',
+ '新野': '河南省', '方城': '河南省', '社旗': '河南省', '桐柏': '河南省',
+ '永城': '河南省', '虞城': '河南省', '民权': '河南省', '宁陵': '河南省',
+ '睢县': '河南省', '夏邑': '河南省', '柘城': '河南省', '罗山': '河南省',
+ '光山': '河南省', '新县': '河南省', '商城': '河南省', '固始': '河南省',
+ '潢川': '河南省', '淮滨': '河南省', '息县': '河南省', '扶沟': '河南省',
+ '西华': '河南省', '商水': '河南省', '沈丘': '河南省', '郸城': '河南省',
+ '淮阳': '河南省', '太康': '河南省', '鹿邑': '河南省', '项城': '河南省',
+ '西平': '河南省', '上蔡': '河南省', '平舆': '河南省', '正阳': '河南省',
+ '确山': '河南省', '泌阳': '河南省', '汝南': '河南省', '遂平': '河南省',
+ '新蔡': '河南省',
+
+ // ===== 湖北省 =====
+ '武汉': '湖北省', '黄石': '湖北省', '十堰': '湖北省', '宜昌': '湖北省',
+ '襄阳': '湖北省', '鄂州': '湖北省', '荆门': '湖北省', '孝感': '湖北省',
+ '荆州': '湖北省', '黄冈': '湖北省', '咸宁': '湖北省', '随州': '湖北省',
+ '恩施': '湖北省',
+
+ // ===== 湖南省 =====
+ '长沙': '湖南省', '株洲': '湖南省', '湘潭': '湖南省', '衡阳': '湖南省',
+ '邵阳': '湖南省', '岳阳': '湖南省', '常德': '湖南省', '张家界': '湖南省',
+ '益阳': '湖南省', '郴州': '湖南省', '永州': '湖南省', '怀化': '湖南省',
+ '娄底': '湖南省', '湘西': '湖南省',
+
+ // ===== 广东省 =====
+ '广州': '广东省', '韶关': '广东省', '深圳': '广东省', '珠海': '广东省',
+ '汕头': '广东省', '佛山': '广东省', '江门': '广东省', '湛江': '广东省',
+ '茂名': '广东省', '肇庆': '广东省', '惠州': '广东省', '梅州': '广东省',
+ '汕尾': '广东省', '河源': '广东省', '阳江': '广东省', '清远': '广东省',
+ '东莞': '广东省', '中山': '广东省', '潮州': '广东省', '揭阳': '广东省',
+ '云浮': '广东省',
+
+ // ===== 海南省 =====
+ '海口': '海南省', '三亚': '海南省', '三沙': '海南省', '儋州': '海南省',
+
+ // ===== 四川省 =====
+ '成都': '四川省', '自贡': '四川省', '攀枝花': '四川省', '泸州': '四川省',
+ '德阳': '四川省', '绵阳': '四川省', '广元': '四川省', '遂宁': '四川省',
+ '内江': '四川省', '乐山': '四川省', '南充': '四川省', '眉山': '四川省',
+ '宜宾': '四川省', '广安': '四川省', '达州': '四川省', '雅安': '四川省',
+ '巴中': '四川省', '资阳': '四川省', '阿坝': '四川省', '甘孜': '四川省',
+ '凉山': '四川省',
+
+ // ===== 贵州省 =====
+ '贵阳': '贵州省', '六盘水': '贵州省', '遵义': '贵州省', '安顺': '贵州省',
+ '毕节': '贵州省', '铜仁': '贵州省', '黔西南': '贵州省', '黔东南': '贵州省',
+ '黔南': '贵州省',
+
+ // ===== 云南省 =====
+ '昆明': '云南省', '曲靖': '云南省', '玉溪': '云南省', '保山': '云南省',
+ '昭通': '云南省', '丽江': '云南省', '普洱': '云南省', '临沧': '云南省',
+ '楚雄': '云南省', '红河': '云南省', '文山': '云南省', '西双版纳': '云南省',
+ '大理': '云南省', '德宏': '云南省', '怒江': '云南省', '迪庆': '云南省',
+
+ // ===== 陕西省 =====
+ '西安': '陕西省', '铜川': '陕西省', '宝鸡': '陕西省', '咸阳': '陕西省',
+ '渭南': '陕西省', '延安': '陕西省', '汉中': '陕西省', '榆林': '陕西省',
+ '安康': '陕西省', '商洛': '陕西省',
+
+ // ===== 甘肃省 =====
+ '兰州': '甘肃省', '嘉峪关': '甘肃省', '金昌': '甘肃省', '白银': '甘肃省',
+ '天水': '甘肃省', '武威': '甘肃省', '张掖': '甘肃省', '平凉': '甘肃省',
+ '酒泉': '甘肃省', '庆阳': '甘肃省', '定西': '甘肃省', '陇南': '甘肃省',
+ '临夏': '甘肃省', '甘南': '甘肃省',
+
+ // ===== 青海省 =====
+ '西宁': '青海省', '海东': '青海省', '海北': '青海省', '黄南': '青海省',
+ '海南州': '青海省', '果洛': '青海省', '玉树': '青海省', '海西': '青海省',
+
+ // ===== 台湾省 =====
+ '台北': '台湾省', '高雄': '台湾省', '台中': '台湾省',
+
+ // ===== 内蒙古自治区 =====
+ '呼和浩特': '内蒙古自治区', '包头': '内蒙古自治区', '乌海': '内蒙古自治区',
+ '赤峰': '内蒙古自治区', '通辽': '内蒙古自治区', '鄂尔多斯': '内蒙古自治区',
+ '呼伦贝尔': '内蒙古自治区', '巴彦淖尔': '内蒙古自治区', '乌兰察布': '内蒙古自治区',
+ '兴安': '内蒙古自治区', '锡林郭勒': '内蒙古自治区', '阿拉善': '内蒙古自治区',
+
+ // ===== 广西壮族自治区 =====
+ '南宁': '广西壮族自治区', '柳州': '广西壮族自治区', '桂林': '广西壮族自治区',
+ '梧州': '广西壮族自治区', '北海': '广西壮族自治区', '防城港': '广西壮族自治区',
+ '钦州': '广西壮族自治区', '贵港': '广西壮族自治区', '玉林': '广西壮族自治区',
+ '百色': '广西壮族自治区', '贺州': '广西壮族自治区', '河池': '广西壮族自治区',
+ '来宾': '广西壮族自治区', '崇左': '广西壮族自治区',
+
+ // ===== 西藏自治区 =====
+ '拉萨': '西藏自治区', '日喀则': '西藏自治区', '昌都': '西藏自治区',
+ '林芝': '西藏自治区', '山南': '西藏自治区', '那曲': '西藏自治区',
+ '阿里': '西藏自治区',
+
+ // ===== 宁夏回族自治区 =====
+ '银川': '宁夏回族自治区', '石嘴山': '宁夏回族自治区', '吴忠': '宁夏回族自治区',
+ '固原': '宁夏回族自治区', '中卫': '宁夏回族自治区',
+
+ // ===== 新疆维吾尔自治区 =====
+ '乌鲁木齐': '新疆维吾尔自治区', '克拉玛依': '新疆维吾尔自治区',
+ '吐鲁番': '新疆维吾尔自治区', '哈密': '新疆维吾尔自治区',
+ '昌吉': '新疆维吾尔自治区', '博尔塔拉': '新疆维吾尔自治区',
+ '巴音郭楞': '新疆维吾尔自治区', '阿克苏': '新疆维吾尔自治区',
+ '克孜勒苏': '新疆维吾尔自治区', '喀什': '新疆维吾尔自治区',
+ '和田': '新疆维吾尔自治区', '伊犁': '新疆维吾尔自治区',
+ '塔城': '新疆维吾尔自治区', '阿勒泰': '新疆维吾尔自治区',
+ '石河子': '新疆维吾尔自治区', '阿拉尔': '新疆维吾尔自治区',
+ '图木舒克': '新疆维吾尔自治区', '五家渠': '新疆维吾尔自治区',
+ '北屯': '新疆维吾尔自治区', '铁门关': '新疆维吾尔自治区',
+ '双河': '新疆维吾尔自治区', '可克达拉': '新疆维吾尔自治区',
+ '昆玉': '新疆维吾尔自治区', '胡杨河': '新疆维吾尔自治区',
+
+ // ===== 香港特别行政区 =====
+ '香港': '香港特别行政区',
+
+ // ===== 澳门特别行政区 =====
+ '澳门': '澳门特别行政区',
+}
+
+/**
+ * 从地址中提取省份名称
+ */
+function extractProvince(address) {
+ if (!address) return ''
+
+ // 清理地址前缀
+ let addr = address.trim()
+ // 去掉 "地址:" 等前缀
+ addr = addr.replace(/^地址[::]?\s*/, '')
+ // 去掉首尾空格
+ addr = addr.trim()
+
+ // ---- 特殊模式 1: "中国(山东)自由贸易试验区..." ----
+ const chinaMatch = addr.match(/^中国\((\S+?)\)/)
+ if (chinaMatch) {
+ const shortName = chinaMatch[1]
+ if (SHORT_PROVINCE[shortName]) return SHORT_PROVINCE[shortName]
+ }
+
+ // ---- 模式 2: 地址以完整省份名开头 ----
+ // 按长度降序排序(避免"宁夏回族自治区"被"宁夏"提前匹配)
+ const sortedProvinces = [...PROVINCES].sort((a, b) => b.length - a.length)
+ for (const p of sortedProvinces) {
+ if (addr.startsWith(p)) return p
+ }
+
+ // ---- 模式 3: 地址以省份简称开头(如"山东济南市"、"河南洛阳") ----
+ const sortedShorts = Object.keys(SHORT_PROVINCE).sort((a, b) => b.length - a.length)
+ for (const s of sortedShorts) {
+ if (addr.startsWith(s)) return SHORT_PROVINCE[s]
+ }
+
+ // ---- 模式 4: 地址以城市名开头 ----
+ // 按长度降序排序,避免"张家港"被"张家"错误匹配
+ const sortedCities = Object.keys(CITY_PROVINCE).sort((a, b) => b.length - a.length)
+ for (const city of sortedCities) {
+ if (addr.startsWith(city)) return CITY_PROVINCE[city]
+ }
+
+ // ---- 模式 5: 地址以区号/代码开头(如 [370000/370800/370829]) ----
+ // 行政区划代码前两位 = 省代码
+ const codeMatch = addr.match(/^\[?(\d{2})/)
+ if (codeMatch) {
+ const codeMap = {
+ '11': '北京市', '12': '天津市', '13': '河北省', '14': '山西省',
+ '15': '内蒙古自治区', '21': '辽宁省', '22': '吉林省', '23': '黑龙江省',
+ '31': '上海市', '32': '江苏省', '33': '浙江省', '34': '安徽省',
+ '35': '福建省', '36': '江西省', '37': '山东省', '41': '河南省',
+ '42': '湖北省', '43': '湖南省', '44': '广东省', '45': '广西壮族自治区',
+ '46': '海南省', '50': '重庆市', '51': '四川省', '52': '贵州省',
+ '53': '云南省', '54': '西藏自治区', '61': '陕西省', '62': '甘肃省',
+ '63': '青海省', '64': '宁夏回族自治区', '65': '新疆维吾尔自治区',
+ '71': '台湾省', '81': '香港特别行政区', '82': '澳门特别行政区',
+ }
+ const code = codeMatch[1]
+ if (codeMap[code]) return codeMap[code]
+ }
+
+ return ''
+}
+
+// ---- 主流程 ----
+async function main() {
+ const conn = mysql.createConnection({
+ host: '140.143.206.120', port: 13306,
+ user: 'klp', password: 'KeLunPu@123',
+ database: 'klp-oa-test', connectTimeout: 10000,
+ supportBigNumbers: true,
+ bigNumberStrings: true
+ })
+
+ const connPromise = conn.promise()
+
+ // 查询需要回填的客户
+ const [rows] = await connPromise.query(
+ "SELECT customer_id, company_name, address FROM crm_customer WHERE del_flag = 0 AND (province IS NULL OR province = '') AND address IS NOT NULL AND address != '' ORDER BY customer_id"
+ )
+
+ console.log(`共 ${rows.length} 条客户记录需要回填省份\n`)
+
+ let matched = 0
+ let unmatched = 0
+ const unmatchedList = []
+ const updates = []
+
+ for (const row of rows) {
+ const province = extractProvince(row.address)
+ if (province) {
+ updates.push({ customer_id: row.customer_id, province, company_name: row.company_name, address: row.address })
+ matched++
+ } else {
+ unmatched++
+ unmatchedList.push({ company_name: row.company_name, address: row.address })
+ }
+ }
+
+ console.log(`匹配成功: ${matched}`)
+ console.log(`无法匹配: ${unmatched}\n`)
+
+ if (unmatchedList.length > 0) {
+ console.log('=== 无法匹配的地址 ===')
+ unmatchedList.forEach(u => console.log(` ${u.company_name} | ${u.address}`))
+ console.log('')
+ }
+
+ // 批量更新
+ if (updates.length > 0) {
+ console.log('正在回填数据库...')
+ const sql = 'UPDATE crm_customer SET province = ? WHERE customer_id = ?'
+ let success = 0
+ for (const u of updates) {
+ try {
+ await connPromise.query(sql, [u.province, u.customer_id])
+ success++
+ } catch (err) {
+ console.log(` 更新失败 ID=${u.customer_id}: ${err.message}`)
+ }
+ }
+ console.log(`回填完成: ${success}/${updates.length} 条已更新`)
+ }
+
+ // 统计各省份分布
+ const [stats] = await connPromise.query(
+ "SELECT province, COUNT(*) as cnt FROM crm_customer WHERE del_flag = 0 AND province != '' GROUP BY province ORDER BY cnt DESC"
+ )
+ console.log('\n=== 省份分布 ===')
+ stats.forEach(s => console.log(` ${s.province}: ${s.cnt}`))
+
+ await connPromise.end()
+ console.log('\n完成!')
+}
+
+main().catch(err => {
+ console.error('脚本执行失败:', err.message)
+ process.exit(1)
+})
diff --git a/src/api/sales.js b/src/api/sales.js
index 1c325b7..a7ef0ff 100644
--- a/src/api/sales.js
+++ b/src/api/sales.js
@@ -115,3 +115,35 @@ export function getFullSalesReport(params) {
params
})
}
+
+// ===== 省份热力地图数据 =====
+const mockProvinceStats = [
+ { name: '山东省', customerCount: 124, orderCount: 2800, salesAmount: 3100 },
+ { name: '河南省', customerCount: 103, orderCount: 2300, salesAmount: 2550 },
+ { name: '江苏省', customerCount: 35, orderCount: 850, salesAmount: 950 },
+ { name: '天津市', customerCount: 17, orderCount: 420, salesAmount: 480 },
+ { name: '河北省', customerCount: 12, orderCount: 310, salesAmount: 360 },
+ { name: '上海市', customerCount: 10, orderCount: 260, salesAmount: 320 },
+ { name: '湖北省', customerCount: 7, orderCount: 180, salesAmount: 210 },
+ { name: '辽宁省', customerCount: 6, orderCount: 150, salesAmount: 170 },
+ { name: '浙江省', customerCount: 5, orderCount: 130, salesAmount: 150 },
+ { name: '安徽省', customerCount: 4, orderCount: 100, salesAmount: 115 },
+ { name: '四川省', customerCount: 4, orderCount: 90, salesAmount: 100 },
+ { name: '湖南省', customerCount: 3, orderCount: 75, salesAmount: 85 },
+ { name: '福建省', customerCount: 3, orderCount: 70, salesAmount: 80 },
+ { name: '山西省', customerCount: 2, orderCount: 50, salesAmount: 55 },
+ { name: '陕西省', customerCount: 2, orderCount: 45, salesAmount: 50 },
+ { name: '江西省', customerCount: 2, orderCount: 40, salesAmount: 45 },
+ { name: '北京市', customerCount: 1, orderCount: 25, salesAmount: 30 },
+ { name: '广东省', customerCount: 1, orderCount: 20, salesAmount: 25 },
+ { name: '海南省', customerCount: 1, orderCount: 15, salesAmount: 18 },
+ { name: '广西壮族自治区', customerCount: 1, orderCount: 10, salesAmount: 12 }
+]
+
+/**
+ * 省份热力统计数据
+ */
+export const getProvinceStats = withMock(
+ (params) => request({ url: '/crm/salesReport/provinceStats', method: 'get', params }),
+ mockProvinceStats
+)
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
index 6535f18..0b8e684 100644
--- a/src/layout/components/Sidebar/index.vue
+++ b/src/layout/components/Sidebar/index.vue
@@ -45,7 +45,8 @@ const iconMap = {
'oee': PieChart,
'output': PieChart,
'stop': Bell,
- 'sales': Coin
+ 'sales': Coin,
+ 'heatmap': Monitor
}
const getIcon = (iconName) => {
@@ -54,6 +55,7 @@ const getIcon = (iconName) => {
const menuItems = [
{ path: '/sales', meta: { title: '销售信息大屏', icon: 'sales' } },
+ { path: '/sales-heatmap', meta: { title: '销售热力地图', icon: 'heatmap' } },
{ path: '/dashboard/order', meta: { title: '订单大屏', icon: 'order' } },
{ path: '/dashboard/cost', meta: { title: '成本大屏', icon: 'cost' } },
{ path: '/dashboard/energy', meta: { title: '能源大屏', icon: 'energy' } },
diff --git a/src/router/index.js b/src/router/index.js
index df48683..3812f50 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -73,6 +73,12 @@ export const constantRoutes = [
name: 'WipOverview',
component: () => import('@/views/screens/wip/index.vue'),
meta: { title: '在产大屏' }
+ },
+ {
+ path: '/sales-heatmap',
+ name: 'SalesHeatmap',
+ component: () => import('@/views/screens/sales-heatmap/index.vue'),
+ meta: { title: '销售热力地图大屏' }
}
]
diff --git a/src/views/screens/sales-heatmap/index.vue b/src/views/screens/sales-heatmap/index.vue
new file mode 100644
index 0000000..87f0585
--- /dev/null
+++ b/src/views/screens/sales-heatmap/index.vue
@@ -0,0 +1,465 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ k.label }}
+
{{ k.value }}
+
{{ k.unit }}
+
+
+
+
+
+
+
+
+
+
+