diff --git a/nginx.conf.example b/nginx.conf.example deleted file mode 100644 index 8f91ba7..0000000 --- a/nginx.conf.example +++ /dev/null @@ -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; -# } -# } diff --git a/src/js/ui/city-picker.js b/src/js/ui/city-picker.js index 8743270..cc8186b 100644 --- a/src/js/ui/city-picker.js +++ b/src/js/ui/city-picker.js @@ -1,10 +1,29 @@ /** * 城市选择器组件 - * 提供省份和城市的联动选择功能 + * 提供省份和城市的联动选择功能,支持热门城市快速选择和定位 */ 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 { /** * 构造函数 @@ -12,6 +31,7 @@ export class CityPicker { * @param {string} options.modalId - 模态框元素ID * @param {string} options.provinceColumnId - 省份列元素ID * @param {string} options.cityColumnId - 城市列元素ID + * @param {string} options.hotCitiesId - 热门城市区域元素ID * @param {string} options.cancelBtnId - 取消按钮ID * @param {string} options.confirmBtnId - 确认按钮ID * @param {Function} options.onConfirm - 确认回调 @@ -20,6 +40,7 @@ export class CityPicker { this.modal = document.getElementById(options.modalId); this.provinceColumn = document.getElementById(options.provinceColumnId); this.cityColumn = document.getElementById(options.cityColumnId); + this.hotCitiesArea = document.getElementById(options.hotCitiesId); this.cancelBtn = document.getElementById(options.cancelBtnId); this.confirmBtn = document.getElementById(options.confirmBtnId); this.onConfirm = options.onConfirm || null; @@ -37,6 +58,11 @@ export class CityPicker { // 数据缓存 this.provinces = []; this.cities = []; + this.hotCities = HOT_CITIES; + + // 定位相关 + this.currentLocationCity = null; // 当前定位城市 + this.isLocating = false; // 是否正在定位 if (!this.modal) { console.error(`[CityPicker] 找不到模态框元素: ${options.modalId}`); @@ -68,6 +94,154 @@ export class CityPicker { // 预加载省份数据 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 = ` + 📍 + ${this.currentLocationCity.name} + `; + + 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 = ` + + 定位中... + `; + locationSection.appendChild(badge); + } else { + // 未定位,显示定位按钮 + const badge = document.createElement('div'); + badge.className = 'location-badge'; + badge.innerHTML = ` + 📍 + 获取定位 + `; + + 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.provinces.forEach(province => { + this.provinces.forEach((province, index) => { const provinceItem = document.createElement('div'); - provinceItem.className = 'city-picker-item'; + provinceItem.className = 'province-item'; provinceItem.textContent = province.name; provinceItem.dataset.province = province.name; provinceItem.dataset.provinceCode = province.code; + // 添加淡入动画 + provinceItem.style.animation = `fadeIn 0.3s ease ${index * 20}ms backwards`; + provinceItem.addEventListener('click', () => { this.selectProvince(province.name, province.code); }); @@ -115,13 +292,20 @@ export class CityPicker { this.tempSelectedProvinceCode = provinceCode; // 更新省份选中状态 - const items = this.provinceColumn.querySelectorAll('.city-picker-item'); + const items = this.provinceColumn.querySelectorAll('.province-item'); items.forEach(item => { item.classList.toggle('active', item.dataset.provinceCode === provinceCode); }); + // 清除城市选中状态(切换省份时) + this.tempSelectedCity = ''; + this.tempSelectedCityCode = ''; + // 加载并渲染城市列表 await this.loadCities(provinceCode); + + // 更新确认按钮状态 + this.updateConfirmButtonState(); } /** @@ -146,20 +330,48 @@ export class CityPicker { 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'); - cityItem.className = 'city-picker-item'; + cityItem.className = 'city-item'; cityItem.textContent = city.name; cityItem.dataset.city = city.name; cityItem.dataset.cityCode = city.code; + // 添加 title 属性,鼠标悬停时显示完整城市名称 + cityItem.title = city.name; + // 添加淡入动画 + cityItem.style.animation = `fadeIn 0.3s ease ${index * 15}ms backwards`; + + // 单击选中城市 cityItem.addEventListener('click', () => { this.selectCity(city.name, city.code); }); + // 双击直接确认选择 + cityItem.addEventListener('dblclick', () => { + this.selectCity(city.name, city.code); + this.confirmSelection(); + }); + this.cityColumn.appendChild(cityItem); }); @@ -168,12 +380,11 @@ export class CityPicker { const existingCity = cities.find(c => c.code === this.tempSelectedCityCode); if (existingCity) { 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); } } @@ -188,35 +399,59 @@ export class CityPicker { this.tempSelectedCityCode = cityCode; // 更新城市选中状态 - const items = this.cityColumn.querySelectorAll('.city-picker-item'); + const items = this.cityColumn.querySelectorAll('.city-item'); items.forEach(item => { item.classList.toggle('active', item.dataset.cityCode === cityCode); }); + + // 更新热门城市选中状态 + this.updateHotCitiesSelection(); } /** * 打开选择器 - * @param {string} currentValue - 当前值(格式:"省/市") + * @param {string} currentValue - 当前值(格式:"市名" 或 "省/市") */ async open(currentValue = '') { // 解析当前值,并查找对应的代码 if (currentValue) { - const parts = currentValue.split('/'); - if (parts.length === 2) { - const provinceName = parts[0]; - const cityName = parts[1]; + let cityName = ''; + let provinceName = ''; + // 支持两种格式:纯市名 "北京市" 或 省市格式 "北京市/北京市" + 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) { await this.loadProvinces(); } - const province = this.provinces.find(p => p.name === provinceName); - if (province) { - this.tempSelectedProvince = provinceName; - this.tempSelectedProvinceCode = province.code; - // 加载城市数据并查找城市代码 - await this.loadCities(province.code); + // 如果有省份名,直接查找 + if (provinceName) { + const province = this.provinces.find(p => p.name === provinceName); + if (province) { + this.tempSelectedProvince = provinceName; + this.tempSelectedProvinceCode = province.code; + } + } + + // 如果有省代码,加载城市数据并查找城市代码 + if (this.tempSelectedProvinceCode) { + await this.loadCities(this.tempSelectedProvinceCode); const city = this.cities.find(c => c.name === cityName && c.code.length === 4); if (city) { this.tempSelectedCity = cityName; @@ -238,16 +473,51 @@ export class CityPicker { } // 渲染列表 + this.renderLocationBadge(); // 渲染定位标签 + this.renderHotCities(); this.renderProvinceList(); + + // 更新热门城市选中状态 + this.updateHotCitiesSelection(); + if (this.tempSelectedProvinceCode) { await this.selectProvince(this.tempSelectedProvince, this.tempSelectedProvinceCode); } + // 更新确认按钮状态 + this.updateConfirmButtonState(); + // 显示模态框 document.body.classList.add('modal-open'); 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.selectedCityCode = this.tempSelectedCityCode; - const cityValue = `${this.tempSelectedProvince}/${this.tempSelectedCity}`; + // 只返回市名,如:北京市、武汉市、曲靖市 + const cityValue = this.tempSelectedCity; if (this.onConfirm) { this.onConfirm({ @@ -292,18 +563,38 @@ export class CityPicker { /** * 设置选中值 - * @param {string} value - 值(格式:"省/市") + * @param {string} value - 值(格式:"市名" 或 "省/市") */ setValue(value) { if (!value) return; - const parts = value.split('/'); - if (parts.length === 2) { - this.selectedProvince = parts[0]; - this.selectedCity = parts[1]; - this.tempSelectedProvince = parts[0]; - this.tempSelectedCity = parts[1]; - // 代码需要通过查找获取,这里暂时留空 + // 支持两种格式:纯市名或省/市格式 + if (value.includes('/')) { + const parts = value.split('/'); + if (parts.length === 2) { + this.selectedProvince = parts[0]; + this.selectedCity = parts[1]; + this.tempSelectedProvince = parts[0]; + 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, provinceCode: this.selectedProvinceCode, cityCode: this.selectedCityCode, - value: `${this.selectedProvince}/${this.selectedCity}` + value: this.selectedCity // 只返回市名 }; } @@ -344,4 +635,183 @@ export class CityPicker { this.provinceColumn = 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} - 城市信息对象 + */ + 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} - 包含省代码和市代码的对象 + */ + 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; + } + } }