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