修改:城市选择器升级(热门城市+IP定位)
This commit is contained in:
@@ -1,50 +0,0 @@
|
|||||||
# flux-web Nginx 配置示例
|
|
||||||
#
|
|
||||||
# 使用方法:
|
|
||||||
# 1. 将下面配置复制到 /etc/nginx/sites-available/flux-web
|
|
||||||
# 2. 修改 server_name(你的域名)和 root(项目路径)
|
|
||||||
# 3. 执行:ln -s /etc/nginx/sites-available/flux-web /etc/nginx/sites-enabled/
|
|
||||||
# 4. 测试:nginx -t
|
|
||||||
# 5. 重载:systemctl reload nginx
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name your-domain.com; # ← 改成你的域名
|
|
||||||
|
|
||||||
root /var/www/flux-web; # ← 改成项目路径
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# 静态资源缓存
|
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
|
||||||
expires 30d;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主路由
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# HTTPS 配置(可选,使用 Let's Encrypt 免费证书)
|
|
||||||
# 先执行:apt install certbot python3-certbot-nginx
|
|
||||||
# 然后执行:certbot --nginx -d your-domain.com
|
|
||||||
|
|
||||||
# 完整 HTTPS 配置示例:
|
|
||||||
#
|
|
||||||
# server {
|
|
||||||
# listen 443 ssl;
|
|
||||||
# server_name your-domain.com;
|
|
||||||
# root /var/www/flux-web;
|
|
||||||
# index index.html;
|
|
||||||
#
|
|
||||||
# ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
|
||||||
# ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
|
||||||
#
|
|
||||||
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
|
||||||
# expires 30d;
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# location / {
|
|
||||||
# try_files $uri $uri/ /index.html;
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
@@ -1,10 +1,29 @@
|
|||||||
/**
|
/**
|
||||||
* 城市选择器组件
|
* 城市选择器组件
|
||||||
* 提供省份和城市的联动选择功能
|
* 提供省份和城市的联动选择功能,支持热门城市快速选择和定位
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AreaService } from '../services/area.service.js';
|
import { AreaService } from '../services/area.service.js';
|
||||||
|
|
||||||
|
// 热门城市列表(12个核心城市)
|
||||||
|
const HOT_CITIES = [
|
||||||
|
{ name: '北京市', province: '北京市', code: '1100' },
|
||||||
|
{ name: '上海市', province: '上海市', code: '3100' },
|
||||||
|
{ name: '广州市', province: '广东省', code: '4401' },
|
||||||
|
{ name: '深圳市', province: '广东省', code: '4403' },
|
||||||
|
{ name: '成都市', province: '四川省', code: '5101' },
|
||||||
|
{ name: '杭州市', province: '浙江省', code: '3301' },
|
||||||
|
{ name: '重庆市', province: '重庆市', code: '5000' },
|
||||||
|
{ name: '武汉市', province: '湖北省', code: '4201' },
|
||||||
|
{ name: '西安市', province: '陕西省', code: '6101' },
|
||||||
|
{ name: '南京市', province: '江苏省', code: '3201' },
|
||||||
|
{ name: '苏州市', province: '江苏省', code: '3205' },
|
||||||
|
{ name: '天津市', province: '天津市', code: '1200' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 直辖市列表(省和市同名)
|
||||||
|
const MUNICIPALITIES = ['北京市', '上海市', '天津市', '重庆市'];
|
||||||
|
|
||||||
export class CityPicker {
|
export class CityPicker {
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
@@ -12,6 +31,7 @@ export class CityPicker {
|
|||||||
* @param {string} options.modalId - 模态框元素ID
|
* @param {string} options.modalId - 模态框元素ID
|
||||||
* @param {string} options.provinceColumnId - 省份列元素ID
|
* @param {string} options.provinceColumnId - 省份列元素ID
|
||||||
* @param {string} options.cityColumnId - 城市列元素ID
|
* @param {string} options.cityColumnId - 城市列元素ID
|
||||||
|
* @param {string} options.hotCitiesId - 热门城市区域元素ID
|
||||||
* @param {string} options.cancelBtnId - 取消按钮ID
|
* @param {string} options.cancelBtnId - 取消按钮ID
|
||||||
* @param {string} options.confirmBtnId - 确认按钮ID
|
* @param {string} options.confirmBtnId - 确认按钮ID
|
||||||
* @param {Function} options.onConfirm - 确认回调
|
* @param {Function} options.onConfirm - 确认回调
|
||||||
@@ -20,6 +40,7 @@ export class CityPicker {
|
|||||||
this.modal = document.getElementById(options.modalId);
|
this.modal = document.getElementById(options.modalId);
|
||||||
this.provinceColumn = document.getElementById(options.provinceColumnId);
|
this.provinceColumn = document.getElementById(options.provinceColumnId);
|
||||||
this.cityColumn = document.getElementById(options.cityColumnId);
|
this.cityColumn = document.getElementById(options.cityColumnId);
|
||||||
|
this.hotCitiesArea = document.getElementById(options.hotCitiesId);
|
||||||
this.cancelBtn = document.getElementById(options.cancelBtnId);
|
this.cancelBtn = document.getElementById(options.cancelBtnId);
|
||||||
this.confirmBtn = document.getElementById(options.confirmBtnId);
|
this.confirmBtn = document.getElementById(options.confirmBtnId);
|
||||||
this.onConfirm = options.onConfirm || null;
|
this.onConfirm = options.onConfirm || null;
|
||||||
@@ -37,6 +58,11 @@ export class CityPicker {
|
|||||||
// 数据缓存
|
// 数据缓存
|
||||||
this.provinces = [];
|
this.provinces = [];
|
||||||
this.cities = [];
|
this.cities = [];
|
||||||
|
this.hotCities = HOT_CITIES;
|
||||||
|
|
||||||
|
// 定位相关
|
||||||
|
this.currentLocationCity = null; // 当前定位城市
|
||||||
|
this.isLocating = false; // 是否正在定位
|
||||||
|
|
||||||
if (!this.modal) {
|
if (!this.modal) {
|
||||||
console.error(`[CityPicker] 找不到模态框元素: ${options.modalId}`);
|
console.error(`[CityPicker] 找不到模态框元素: ${options.modalId}`);
|
||||||
@@ -68,6 +94,154 @@ export class CityPicker {
|
|||||||
|
|
||||||
// 预加载省份数据
|
// 预加载省份数据
|
||||||
this.loadProvinces();
|
this.loadProvinces();
|
||||||
|
|
||||||
|
// 获取定位
|
||||||
|
this.getCurrentLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染定位标签
|
||||||
|
*/
|
||||||
|
renderLocationBadge() {
|
||||||
|
const locationSection = document.getElementById('locationSection');
|
||||||
|
if (!locationSection) return;
|
||||||
|
|
||||||
|
locationSection.innerHTML = '';
|
||||||
|
|
||||||
|
if (this.currentLocationCity) {
|
||||||
|
// 已定位,显示当前城市
|
||||||
|
const badge = document.createElement('div');
|
||||||
|
badge.className = 'location-badge';
|
||||||
|
badge.innerHTML = `
|
||||||
|
<span class="location-icon">📍</span>
|
||||||
|
<span class="location-text">${this.currentLocationCity.name}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
badge.addEventListener('click', async () => {
|
||||||
|
await this.selectHotCity(this.currentLocationCity);
|
||||||
|
});
|
||||||
|
|
||||||
|
locationSection.appendChild(badge);
|
||||||
|
} else if (this.isLocating) {
|
||||||
|
// 正在定位
|
||||||
|
const badge = document.createElement('div');
|
||||||
|
badge.className = 'location-badge locating';
|
||||||
|
badge.innerHTML = `
|
||||||
|
<span class="location-icon">⌛</span>
|
||||||
|
<span class="location-text">定位中...</span>
|
||||||
|
`;
|
||||||
|
locationSection.appendChild(badge);
|
||||||
|
} else {
|
||||||
|
// 未定位,显示定位按钮
|
||||||
|
const badge = document.createElement('div');
|
||||||
|
badge.className = 'location-badge';
|
||||||
|
badge.innerHTML = `
|
||||||
|
<span class="location-icon">📍</span>
|
||||||
|
<span class="location-text">获取定位</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
badge.addEventListener('click', () => {
|
||||||
|
this.getCurrentLocation();
|
||||||
|
});
|
||||||
|
|
||||||
|
locationSection.appendChild(badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染热门城市区域
|
||||||
|
*/
|
||||||
|
renderHotCities() {
|
||||||
|
// 渲染定位标签
|
||||||
|
this.renderLocationBadge();
|
||||||
|
|
||||||
|
if (!this.hotCitiesArea) return;
|
||||||
|
|
||||||
|
this.hotCitiesArea.innerHTML = '';
|
||||||
|
|
||||||
|
// 渲染热门城市列表
|
||||||
|
this.hotCities.forEach(city => {
|
||||||
|
const cityTag = document.createElement('div');
|
||||||
|
cityTag.className = 'city-tag';
|
||||||
|
cityTag.textContent = city.name;
|
||||||
|
cityTag.dataset.cityName = city.name;
|
||||||
|
cityTag.dataset.provinceName = city.province;
|
||||||
|
cityTag.dataset.cityCode = city.code;
|
||||||
|
cityTag.dataset.provinceCode = city.code.substring(0, 2) + '00';
|
||||||
|
|
||||||
|
// 添加淡入动画
|
||||||
|
cityTag.style.animationDelay = `${Math.floor(Math.random() * 100)}ms`;
|
||||||
|
|
||||||
|
// 单击选中热门城市
|
||||||
|
cityTag.addEventListener('click', async () => {
|
||||||
|
await this.selectHotCity(city);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 双击直接确认选择
|
||||||
|
cityTag.addEventListener('dblclick', async () => {
|
||||||
|
await this.selectHotCity(city);
|
||||||
|
this.confirmSelection();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hotCitiesArea.appendChild(cityTag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择热门城市
|
||||||
|
* @param {Object} city - 热门城市对象
|
||||||
|
*/
|
||||||
|
async selectHotCity(city) {
|
||||||
|
const isMunicipality = MUNICIPALITIES.includes(city.province);
|
||||||
|
|
||||||
|
this.tempSelectedProvince = city.province;
|
||||||
|
this.tempSelectedCity = city.name;
|
||||||
|
this.tempSelectedProvinceCode = city.provinceCode || city.code.substring(0, 2) + '00';
|
||||||
|
this.tempSelectedCityCode = city.code;
|
||||||
|
|
||||||
|
// 更新热门城市选中状态
|
||||||
|
const tags = this.hotCitiesArea.querySelectorAll('.city-tag');
|
||||||
|
tags.forEach(tag => {
|
||||||
|
tag.classList.toggle('active', tag.dataset.cityCode === city.code);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 同步更新省份和城市的选中状态
|
||||||
|
if (this.provinces.length === 0) {
|
||||||
|
await this.loadProvinces();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中对应的省份
|
||||||
|
const province = this.provinces.find(p => p.name === city.province || p.code === this.tempSelectedProvinceCode);
|
||||||
|
if (province) {
|
||||||
|
this.tempSelectedProvince = province.name;
|
||||||
|
this.tempSelectedProvinceCode = province.code;
|
||||||
|
|
||||||
|
// 更新省份列表选中状态
|
||||||
|
const provinceItems = this.provinceColumn.querySelectorAll('.province-item');
|
||||||
|
provinceItems.forEach(item => {
|
||||||
|
item.classList.toggle('active', item.dataset.provinceCode === province.code);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载并渲染城市列表
|
||||||
|
await this.loadCities(province.code);
|
||||||
|
|
||||||
|
// 选中对应的城市
|
||||||
|
const cityItems = this.cityColumn.querySelectorAll('.city-item');
|
||||||
|
if (isMunicipality) {
|
||||||
|
// 直辖市:自动选中唯一选项(省份名称)
|
||||||
|
if (cityItems.length > 0) {
|
||||||
|
cityItems[0].classList.add('active');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 普通城市:选中对应的城市
|
||||||
|
cityItems.forEach(item => {
|
||||||
|
item.classList.toggle('active', item.dataset.cityCode === city.code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新确认按钮状态
|
||||||
|
this.updateConfirmButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,13 +264,16 @@ export class CityPicker {
|
|||||||
|
|
||||||
this.provinceColumn.innerHTML = '';
|
this.provinceColumn.innerHTML = '';
|
||||||
|
|
||||||
this.provinces.forEach(province => {
|
this.provinces.forEach((province, index) => {
|
||||||
const provinceItem = document.createElement('div');
|
const provinceItem = document.createElement('div');
|
||||||
provinceItem.className = 'city-picker-item';
|
provinceItem.className = 'province-item';
|
||||||
provinceItem.textContent = province.name;
|
provinceItem.textContent = province.name;
|
||||||
provinceItem.dataset.province = province.name;
|
provinceItem.dataset.province = province.name;
|
||||||
provinceItem.dataset.provinceCode = province.code;
|
provinceItem.dataset.provinceCode = province.code;
|
||||||
|
|
||||||
|
// 添加淡入动画
|
||||||
|
provinceItem.style.animation = `fadeIn 0.3s ease ${index * 20}ms backwards`;
|
||||||
|
|
||||||
provinceItem.addEventListener('click', () => {
|
provinceItem.addEventListener('click', () => {
|
||||||
this.selectProvince(province.name, province.code);
|
this.selectProvince(province.name, province.code);
|
||||||
});
|
});
|
||||||
@@ -115,13 +292,20 @@ export class CityPicker {
|
|||||||
this.tempSelectedProvinceCode = provinceCode;
|
this.tempSelectedProvinceCode = provinceCode;
|
||||||
|
|
||||||
// 更新省份选中状态
|
// 更新省份选中状态
|
||||||
const items = this.provinceColumn.querySelectorAll('.city-picker-item');
|
const items = this.provinceColumn.querySelectorAll('.province-item');
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
item.classList.toggle('active', item.dataset.provinceCode === provinceCode);
|
item.classList.toggle('active', item.dataset.provinceCode === provinceCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 清除城市选中状态(切换省份时)
|
||||||
|
this.tempSelectedCity = '';
|
||||||
|
this.tempSelectedCityCode = '';
|
||||||
|
|
||||||
// 加载并渲染城市列表
|
// 加载并渲染城市列表
|
||||||
await this.loadCities(provinceCode);
|
await this.loadCities(provinceCode);
|
||||||
|
|
||||||
|
// 更新确认按钮状态
|
||||||
|
this.updateConfirmButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,20 +330,48 @@ export class CityPicker {
|
|||||||
|
|
||||||
this.cityColumn.innerHTML = '';
|
this.cityColumn.innerHTML = '';
|
||||||
|
|
||||||
// 只显示市(4位),不显示区(6位)
|
// 判断是否为直辖市
|
||||||
const cities = this.cities.filter(area => area.code.length === 4);
|
const isMunicipality = MUNICIPALITIES.includes(this.tempSelectedProvince);
|
||||||
|
|
||||||
cities.forEach(city => {
|
let cities;
|
||||||
|
if (isMunicipality) {
|
||||||
|
// 直辖市:直接显示省份名称作为唯一的城市选项
|
||||||
|
// 这样用户选择后返回的就是"北京市"、"上海市"等
|
||||||
|
cities = [{
|
||||||
|
name: this.tempSelectedProvince,
|
||||||
|
code: this.tempSelectedProvinceCode
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
// 普通省份:显示市级数据(4位代码),排除"市辖区"
|
||||||
|
cities = this.cities.filter(area =>
|
||||||
|
area.code.length === 4 &&
|
||||||
|
area.name !== '市辖区'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cities.forEach((city, index) => {
|
||||||
const cityItem = document.createElement('div');
|
const cityItem = document.createElement('div');
|
||||||
cityItem.className = 'city-picker-item';
|
cityItem.className = 'city-item';
|
||||||
cityItem.textContent = city.name;
|
cityItem.textContent = city.name;
|
||||||
cityItem.dataset.city = city.name;
|
cityItem.dataset.city = city.name;
|
||||||
cityItem.dataset.cityCode = city.code;
|
cityItem.dataset.cityCode = city.code;
|
||||||
|
// 添加 title 属性,鼠标悬停时显示完整城市名称
|
||||||
|
cityItem.title = city.name;
|
||||||
|
|
||||||
|
// 添加淡入动画
|
||||||
|
cityItem.style.animation = `fadeIn 0.3s ease ${index * 15}ms backwards`;
|
||||||
|
|
||||||
|
// 单击选中城市
|
||||||
cityItem.addEventListener('click', () => {
|
cityItem.addEventListener('click', () => {
|
||||||
this.selectCity(city.name, city.code);
|
this.selectCity(city.name, city.code);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 双击直接确认选择
|
||||||
|
cityItem.addEventListener('dblclick', () => {
|
||||||
|
this.selectCity(city.name, city.code);
|
||||||
|
this.confirmSelection();
|
||||||
|
});
|
||||||
|
|
||||||
this.cityColumn.appendChild(cityItem);
|
this.cityColumn.appendChild(cityItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,12 +380,11 @@ export class CityPicker {
|
|||||||
const existingCity = cities.find(c => c.code === this.tempSelectedCityCode);
|
const existingCity = cities.find(c => c.code === this.tempSelectedCityCode);
|
||||||
if (existingCity) {
|
if (existingCity) {
|
||||||
this.selectCity(existingCity.name, existingCity.code);
|
this.selectCity(existingCity.name, existingCity.code);
|
||||||
} else if (cities.length > 0) {
|
|
||||||
// 默认选择第一个
|
|
||||||
this.selectCity(cities[0].name, cities[0].code);
|
|
||||||
}
|
}
|
||||||
} else if (cities.length > 0) {
|
}
|
||||||
// 默认选择第一个
|
|
||||||
|
// 对于直辖市,自动选中唯一的选项
|
||||||
|
if (isMunicipality && cities.length === 1 && !this.tempSelectedCityCode) {
|
||||||
this.selectCity(cities[0].name, cities[0].code);
|
this.selectCity(cities[0].name, cities[0].code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,35 +399,59 @@ export class CityPicker {
|
|||||||
this.tempSelectedCityCode = cityCode;
|
this.tempSelectedCityCode = cityCode;
|
||||||
|
|
||||||
// 更新城市选中状态
|
// 更新城市选中状态
|
||||||
const items = this.cityColumn.querySelectorAll('.city-picker-item');
|
const items = this.cityColumn.querySelectorAll('.city-item');
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
item.classList.toggle('active', item.dataset.cityCode === cityCode);
|
item.classList.toggle('active', item.dataset.cityCode === cityCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 更新热门城市选中状态
|
||||||
|
this.updateHotCitiesSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开选择器
|
* 打开选择器
|
||||||
* @param {string} currentValue - 当前值(格式:"省/市")
|
* @param {string} currentValue - 当前值(格式:"市名" 或 "省/市")
|
||||||
*/
|
*/
|
||||||
async open(currentValue = '') {
|
async open(currentValue = '') {
|
||||||
// 解析当前值,并查找对应的代码
|
// 解析当前值,并查找对应的代码
|
||||||
if (currentValue) {
|
if (currentValue) {
|
||||||
const parts = currentValue.split('/');
|
let cityName = '';
|
||||||
if (parts.length === 2) {
|
let provinceName = '';
|
||||||
const provinceName = parts[0];
|
|
||||||
const cityName = parts[1];
|
|
||||||
|
|
||||||
|
// 支持两种格式:纯市名 "北京市" 或 省市格式 "北京市/北京市"
|
||||||
|
if (currentValue.includes('/')) {
|
||||||
|
const parts = currentValue.split('/');
|
||||||
|
provinceName = parts[0];
|
||||||
|
cityName = parts[1];
|
||||||
|
} else {
|
||||||
|
cityName = currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先在热门城市中查找
|
||||||
|
const hotCity = this.hotCities.find(c => c.name === cityName);
|
||||||
|
if (hotCity) {
|
||||||
|
this.tempSelectedProvince = hotCity.province;
|
||||||
|
this.tempSelectedCity = hotCity.name;
|
||||||
|
this.tempSelectedProvinceCode = hotCity.code.substring(0, 2) + '00';
|
||||||
|
this.tempSelectedCityCode = hotCity.code;
|
||||||
|
} else {
|
||||||
// 查找省份代码
|
// 查找省份代码
|
||||||
if (this.provinces.length === 0) {
|
if (this.provinces.length === 0) {
|
||||||
await this.loadProvinces();
|
await this.loadProvinces();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果有省份名,直接查找
|
||||||
|
if (provinceName) {
|
||||||
const province = this.provinces.find(p => p.name === provinceName);
|
const province = this.provinces.find(p => p.name === provinceName);
|
||||||
if (province) {
|
if (province) {
|
||||||
this.tempSelectedProvince = provinceName;
|
this.tempSelectedProvince = provinceName;
|
||||||
this.tempSelectedProvinceCode = province.code;
|
this.tempSelectedProvinceCode = province.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载城市数据并查找城市代码
|
// 如果有省代码,加载城市数据并查找城市代码
|
||||||
await this.loadCities(province.code);
|
if (this.tempSelectedProvinceCode) {
|
||||||
|
await this.loadCities(this.tempSelectedProvinceCode);
|
||||||
const city = this.cities.find(c => c.name === cityName && c.code.length === 4);
|
const city = this.cities.find(c => c.name === cityName && c.code.length === 4);
|
||||||
if (city) {
|
if (city) {
|
||||||
this.tempSelectedCity = cityName;
|
this.tempSelectedCity = cityName;
|
||||||
@@ -238,16 +473,51 @@ export class CityPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染列表
|
// 渲染列表
|
||||||
|
this.renderLocationBadge(); // 渲染定位标签
|
||||||
|
this.renderHotCities();
|
||||||
this.renderProvinceList();
|
this.renderProvinceList();
|
||||||
|
|
||||||
|
// 更新热门城市选中状态
|
||||||
|
this.updateHotCitiesSelection();
|
||||||
|
|
||||||
if (this.tempSelectedProvinceCode) {
|
if (this.tempSelectedProvinceCode) {
|
||||||
await this.selectProvince(this.tempSelectedProvince, this.tempSelectedProvinceCode);
|
await this.selectProvince(this.tempSelectedProvince, this.tempSelectedProvinceCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新确认按钮状态
|
||||||
|
this.updateConfirmButtonState();
|
||||||
|
|
||||||
// 显示模态框
|
// 显示模态框
|
||||||
document.body.classList.add('modal-open');
|
document.body.classList.add('modal-open');
|
||||||
this.modal.classList.add('show');
|
this.modal.classList.add('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新热门城市选中状态
|
||||||
|
*/
|
||||||
|
updateHotCitiesSelection() {
|
||||||
|
if (!this.hotCitiesArea) return;
|
||||||
|
|
||||||
|
const tags = this.hotCitiesArea.querySelectorAll('.city-tag');
|
||||||
|
tags.forEach(tag => {
|
||||||
|
const isSelected = this.tempSelectedCityCode && tag.dataset.cityCode === this.tempSelectedCityCode;
|
||||||
|
tag.classList.toggle('active', isSelected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新确认按钮状态
|
||||||
|
this.updateConfirmButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新确认按钮状态
|
||||||
|
*/
|
||||||
|
updateConfirmButtonState() {
|
||||||
|
if (this.confirmBtn) {
|
||||||
|
const hasSelection = this.tempSelectedProvince && this.tempSelectedCity;
|
||||||
|
this.confirmBtn.classList.toggle('has-selection', hasSelection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭选择器
|
* 关闭选择器
|
||||||
*/
|
*/
|
||||||
@@ -274,7 +544,8 @@ export class CityPicker {
|
|||||||
this.selectedProvinceCode = this.tempSelectedProvinceCode;
|
this.selectedProvinceCode = this.tempSelectedProvinceCode;
|
||||||
this.selectedCityCode = this.tempSelectedCityCode;
|
this.selectedCityCode = this.tempSelectedCityCode;
|
||||||
|
|
||||||
const cityValue = `${this.tempSelectedProvince}/${this.tempSelectedCity}`;
|
// 只返回市名,如:北京市、武汉市、曲靖市
|
||||||
|
const cityValue = this.tempSelectedCity;
|
||||||
|
|
||||||
if (this.onConfirm) {
|
if (this.onConfirm) {
|
||||||
this.onConfirm({
|
this.onConfirm({
|
||||||
@@ -292,18 +563,38 @@ export class CityPicker {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置选中值
|
* 设置选中值
|
||||||
* @param {string} value - 值(格式:"省/市")
|
* @param {string} value - 值(格式:"市名" 或 "省/市")
|
||||||
*/
|
*/
|
||||||
setValue(value) {
|
setValue(value) {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
|
// 支持两种格式:纯市名或省/市格式
|
||||||
|
if (value.includes('/')) {
|
||||||
const parts = value.split('/');
|
const parts = value.split('/');
|
||||||
if (parts.length === 2) {
|
if (parts.length === 2) {
|
||||||
this.selectedProvince = parts[0];
|
this.selectedProvince = parts[0];
|
||||||
this.selectedCity = parts[1];
|
this.selectedCity = parts[1];
|
||||||
this.tempSelectedProvince = parts[0];
|
this.tempSelectedProvince = parts[0];
|
||||||
this.tempSelectedCity = parts[1];
|
this.tempSelectedCity = parts[1];
|
||||||
// 代码需要通过查找获取,这里暂时留空
|
}
|
||||||
|
} else {
|
||||||
|
// 纯市名,从热门城市中查找省份信息
|
||||||
|
const hotCity = this.hotCities.find(c => c.name === value);
|
||||||
|
if (hotCity) {
|
||||||
|
const provinceCode = hotCity.code.substring(0, 2) + '00';
|
||||||
|
this.selectedProvince = hotCity.province;
|
||||||
|
this.selectedCity = hotCity.name;
|
||||||
|
this.selectedProvinceCode = provinceCode;
|
||||||
|
this.selectedCityCode = hotCity.code;
|
||||||
|
this.tempSelectedProvince = hotCity.province;
|
||||||
|
this.tempSelectedCity = hotCity.name;
|
||||||
|
this.tempSelectedProvinceCode = provinceCode;
|
||||||
|
this.tempSelectedCityCode = hotCity.code;
|
||||||
|
} else {
|
||||||
|
// 如果不在热门城市中,只记录城市名
|
||||||
|
this.selectedCity = value;
|
||||||
|
this.tempSelectedCity = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +608,7 @@ export class CityPicker {
|
|||||||
city: this.selectedCity,
|
city: this.selectedCity,
|
||||||
provinceCode: this.selectedProvinceCode,
|
provinceCode: this.selectedProvinceCode,
|
||||||
cityCode: this.selectedCityCode,
|
cityCode: this.selectedCityCode,
|
||||||
value: `${this.selectedProvince}/${this.selectedCity}`
|
value: this.selectedCity // 只返回市名
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,4 +635,183 @@ export class CityPicker {
|
|||||||
this.provinceColumn = null;
|
this.provinceColumn = null;
|
||||||
this.cityColumn = null;
|
this.cityColumn = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前定位(使用IP定位)
|
||||||
|
*/
|
||||||
|
async getCurrentLocation() {
|
||||||
|
// 如果已经定位过,不再重复定位
|
||||||
|
if (this.currentLocationCity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在定位,避免重复请求
|
||||||
|
if (this.isLocating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLocating = true;
|
||||||
|
|
||||||
|
// 如果定位区域已渲染,更新状态显示"定位中"
|
||||||
|
const locationSection = document.getElementById('locationSection');
|
||||||
|
if (locationSection) {
|
||||||
|
this.renderLocationBadge();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用后端IP定位接口
|
||||||
|
const response = await fetch('/pub/partnerh5/ip_location');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.retcode === 0 && result.result) {
|
||||||
|
const { province, city } = result.result;
|
||||||
|
|
||||||
|
if (province && city) {
|
||||||
|
console.log('[CityPicker] IP定位成功:', province, city);
|
||||||
|
|
||||||
|
// 查找城市代码
|
||||||
|
const cityCode = await this.findCityCode(province, city);
|
||||||
|
|
||||||
|
if (cityCode) {
|
||||||
|
this.currentLocationCity = {
|
||||||
|
name: city,
|
||||||
|
province: province,
|
||||||
|
code: cityCode.cityCode,
|
||||||
|
provinceCode: cityCode.provinceCode
|
||||||
|
};
|
||||||
|
console.log('[CityPicker] 定位成功:', this.currentLocationCity.name);
|
||||||
|
} else {
|
||||||
|
// 如果找不到代码,返回基本信息(代码为空)
|
||||||
|
this.currentLocationCity = {
|
||||||
|
name: city,
|
||||||
|
province: province,
|
||||||
|
code: '',
|
||||||
|
provinceCode: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CityPicker] IP定位失败:', error);
|
||||||
|
// 定位失败时不显示错误,静默处理
|
||||||
|
} finally {
|
||||||
|
this.isLocating = false;
|
||||||
|
// 更新UI显示
|
||||||
|
const locationSection = document.getElementById('locationSection');
|
||||||
|
if (locationSection) {
|
||||||
|
this.renderLocationBadge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逆地理编码,将经纬度转换为城市信息
|
||||||
|
* 使用免费的逆地理编码服务
|
||||||
|
* @param {number} latitude - 纬度
|
||||||
|
* @param {number} longitude - 经度
|
||||||
|
* @returns {Promise<Object|null>} - 城市信息对象
|
||||||
|
*/
|
||||||
|
async reverseGeocode(latitude, longitude) {
|
||||||
|
try {
|
||||||
|
// 使用免费的 Nominatim API(OpenStreetMap)
|
||||||
|
// 注意:该服务有速率限制(每秒最多1次请求)
|
||||||
|
const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=zh-CN`;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'CityPicker/1.0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data && data.address) {
|
||||||
|
const address = data.address;
|
||||||
|
|
||||||
|
// 获取城市信息(优先级:city > town > village > state)
|
||||||
|
let cityName = address.city || address.town || address.village || address.state || '';
|
||||||
|
let provinceName = address.state || '';
|
||||||
|
|
||||||
|
// 处理直辖市的特殊情况
|
||||||
|
const municipalities = ['北京市', '上海市', '天津市', '重庆市'];
|
||||||
|
if (municipalities.includes(provinceName)) {
|
||||||
|
cityName = provinceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果城市名为空,尝试从county字段获取
|
||||||
|
if (!cityName && address.county) {
|
||||||
|
// 移除"市"、"区"、"县"等后缀
|
||||||
|
cityName = address.county.replace(/[市区县]$/, '') + '市';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cityName) {
|
||||||
|
// 查找城市代码
|
||||||
|
const cityCode = await this.findCityCode(provinceName, cityName);
|
||||||
|
|
||||||
|
if (cityCode) {
|
||||||
|
return {
|
||||||
|
name: cityName,
|
||||||
|
province: provinceName,
|
||||||
|
code: cityCode.cityCode,
|
||||||
|
provinceCode: cityCode.provinceCode
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 如果找不到代码,返回基本信息(代码为空)
|
||||||
|
return {
|
||||||
|
name: cityName,
|
||||||
|
province: provinceName,
|
||||||
|
code: '',
|
||||||
|
provinceCode: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CityPicker] 逆地理编码失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找城市代码
|
||||||
|
* @param {string} provinceName - 省份名称
|
||||||
|
* @param {string} cityName - 城市名称
|
||||||
|
* @returns {Promise<Object|null>} - 包含省代码和市代码的对象
|
||||||
|
*/
|
||||||
|
async findCityCode(provinceName, cityName) {
|
||||||
|
try {
|
||||||
|
// 先在省份数据中查找省份代码
|
||||||
|
if (this.provinces.length === 0) {
|
||||||
|
await this.loadProvinces();
|
||||||
|
}
|
||||||
|
|
||||||
|
const province = this.provinces.find(p => p.name === provinceName);
|
||||||
|
if (!province) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载该省的城市数据
|
||||||
|
const cities = await AreaService.getCities(province.code);
|
||||||
|
const city = cities.find(c => c.name === cityName && c.code.length === 4);
|
||||||
|
|
||||||
|
if (city) {
|
||||||
|
return {
|
||||||
|
provinceCode: province.code,
|
||||||
|
cityCode: city.code
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CityPicker] 查找城市代码失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user