跳到主要内容

204 篇博文 含有标签「iCoding」

个人简介

查看所有标签

压缩图片方案

· 阅读需 1 分钟
Quany
软件工程师

上传图片同一张照片23M,手机系统剪裁后不能上传,提示图片不能超过2M,手机系统不剪裁可以上传,判断有问题

如下步骤处理

1.据图片格式决定质量降低的步长


static int _getQualityStep(String extension) {
// 对于PNG等无损格式,可以加大步长,因为质量参数对其影响机制不同
return (extension == 'png') ? 10 : 5;
}

  1. 循环质量,判断图片大小是否满足要求
flutter: 图片选择成功,开始处理
flutter: 原始图片大小: 2.429793357849121 MB
flutter: 质量 90% 压缩后: 3.1823205947875977 MB
flutter: 质量 85% 压缩后: 2.8928775787353516 MB
flutter: 质量 80% 压缩后: 2.498446464538574 MB
flutter: 质量 75% 压缩后: 2.4492578506469727 MB
flutter: 质量 70% 压缩后: 2.3891944885253906 MB
flutter: 质量 65% 压缩后: 2.2195205688476562 MB
flutter: 质量 60% 压缩后: 2.0848846435546875 MB
flutter: 质量 55% 压缩后: 1.332777976989746 MB
flutter: 通过质量压缩成功达到目标大小

微信公众号

微信公众号

地图标系

· 阅读需 3 分钟
Quany
软件工程师

在地图开发中,坐标系是核心概念。不同地图服务使用不同标准,混用会导致位置偏移。

一、主流坐标系统

1. WGS84(国际标准)

  • 全称:World Geodetic System 1984
  • 特点:GPS 设备获取的原始坐标,国际通用
  • 使用方:Google Maps(境外)、OpenStreetMap、Apple Maps
  • 格式{lat: 39.9042, lng: 116.4074}

2. GCJ-02(火星坐标系)

  • 别名:国测局坐标系
  • 特点:中国官方加密坐标,WGS84 基础上经非线性偏移
  • 使用方:高德地图、腾讯地图、Google Maps(境内)
  • 来源:中国法律规定所有地图必须加密

3. BD-09(百度坐标系)

  • 特点:在 GCJ-02 基础上再次加密
  • 使用方:百度地图
  • 偏移:与 WGS84 偏差可达 500 米

二、坐标系转换公式

JavaScript 实现

// WGS84 → GCJ-02
function wgs84ToGcj02(lng, lat) {
// 复杂非线性转换,实际项目中建议使用成熟库
const pi = 3.141592653589793;
const a = 6378245.0;
const ee = 0.00669342162296594323;
// ... 具体算法实现
}

// GCJ-02 → BD-09
function gcj02ToBd09(lng, lat) {
const z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * Math.PI);
const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * Math.PI);
return {
lng: z * Math.cos(theta) + 0.0065,
lat: z * Math.sin(theta) + 0.006
};
}

推荐库

  • coord-convertnpm install coord-convert
  • gcoordnpm install gcoord(支持多种坐标系)
import { wgs84togcj02, gcj02tobd09 } from 'coord-convert';

const [gcjLng, gcjLat] = wgs84togcj02(116.4074, 39.9042);
const [bdLng, bdLat] = gcj02tobd09(gcjLng, gcjLat);

三、开发实践

1. Google Maps 使用场景

// 直接使用 WGS84 坐标
const position = { lat: 39.9042, lng: 116.4074 }; // 正确

// 如果数据来自高德/腾讯,需先转换
import { gcj02towgs84 } from 'coord-convert';
const wgsPosition = gcj02towgs84(gcjLng, gcjLat);

2. 国内地图服务

// 高德地图 API:使用 GCJ-02
const position = [116.4074, 39.9042]; // 直接传入GCJ-02

// 百度地图 API:使用 BD-09
const point = new BMap.Point(116.404, 39.915); // BD-09坐标

3. 常见错误

// ❌ 错误:将 GCJ-02 坐标用于 Google Maps
// 结果:位置偏移几百米
<Map center={{lat: 39.9042, lng: 116.4074}} /> // 如果这是GCJ-02坐标就会错

// ✅ 正确:确保是 WGS84
const wgs84Coordinate = convertFromGcj02ToWgs84(gcjCoordinate);

四、坐标系检测

如何判断数据坐标系?

function detectCoordinateSystem(lng, lat) {
// 在中国境内
if (lng > 73 && lng < 135 && lat > 18 && lat < 54) {
// 如果与真实位置偏差 < 10m,很可能是 WGS84
// 如果偏差 100-500m,可能是 GCJ-02
// 如果偏差 > 500m,可能是 BD-09
}
}

五、总结

地图服务使用坐标系是否需要转换
Google Maps(全球)WGS84直接适配
Google Maps(中国)GCJ-02需 WGS84→GCJ-02
高德/腾讯地图GCJ-02需 WGS84→GCJ-02
百度地图BD-09需 WGS84→GCJ-02→BD-09
OpenStreetMapWGS84直接适配

核心建议:保存数据时使用 WGS84,使用时根据目标平台动态转换。

微信公众号

微信公众号

定位方案-融合定位

· 阅读需 4 分钟
Quany
软件工程师

Flutter应用在没有GPS的情况下,可以通过Wi-Fi定位IP定位两种方案获取位置信息。


一、Wi-Fi定位(推荐)

原理

Wi-Fi定位通过扫描周围Wi-Fi热点的MAC地址信号强度,与服务器数据库匹配来计算位置,精度可达20-50米

实现方式

1. 使用高德/百度地图SDK(推荐)

国内地图SDK内置Wi-Fi定位功能,自动融合GPS、Wi-Fi、基站数据。

// 使用 flutter_amap_location 插件
import 'package:flutter_amap_location/flutter_amap_location.dart';

// 初始化定位
await AMapLocationClient.startup(new AMapLocationOption(
desiredAccuracy: CLLocationAccuracy.kCLLocationAccuracyHundredMeters,
locationMode: AMapLocationMode.Hight_Accuracy, // 高精度模式会启用Wi-Fi定位
));

// 获取定位
var location = await AMapLocationClient.getLocation(true);
print('纬度: ${location.latitude}, 经度: ${location.longitude}');

2. 使用 geolocator 插件

import 'package:geolocator/geolocator.dart';

// 检查定位服务是否启用
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();

// 获取位置(自动使用最佳可用方案:GPS > Wi-Fi > 基站)
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high, // high会自动启用Wi-Fi定位
);
print('位置: ${position.latitude}, ${position.longitude}');

必需权限(Android)

android/app/src/main/AndroidManifest.xml 中声明:

<!-- 网络定位(无GPS情况下的定位)必选 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<!-- Wi-Fi定位必需 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<!-- 设备和运营商信息(基站定位辅助) -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

关键点ACCESS_COARSE_LOCATION 权限允许使用Wi-Fi和基站进行粗略定位。


二、IP定位(备用方案)

原理

通过设备的公网IP地址查询地理位置,精度较低(通常只能定位到城市级别),但无需任何特殊权限

实现方式

1. 使用 ip_geolocation_io 插件

import 'package:ip_geolocation_io/ip_geolocation_io.dart';

// 获取IP定位信息
final ipGeo = IpGeolocationIo();
final geoData = await ipGeo.getGeoData();

print('国家: ${geoData.country}');
print('省份: ${geoData.state}');
print('城市: ${geoData.city}');
print('纬度: ${geoData.latitude}'); // 城市中心坐标
print('经度: ${geoData.longitude}');

2. 使用 HTTP 请求

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> getLocationByIP() async {
final response = await http.get(Uri.parse('http://ip-api.com/json/'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
print('城市: ${data['city']}');
print('坐标: ${data['lat']}, ${data['lon']}');
}
}

三、两种方案对比

特性Wi-Fi定位IP定位
精度高(20-50米)低(城市级,误差几公里)
依赖需开启Wi-Fi,无需连接仅需网络连接
权限需要定位权限无需权限
速度快(1-3秒)极快(毫秒级)
耗电中等极低
室内支持✅ 优秀❌ 不支持

四、最佳实践建议

1. 自动降级策略

Future<Position?> getLocation() async {
try {
// 优先使用高精度定位(GPS+Wi-Fi)
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
} catch (e) {
// GPS/Wi-Fi都失败,降级到IP定位
final ipData = await getLocationByIP();
return Position(
latitude: ipData.latitude,
longitude: ipData.longitude,
timestamp: DateTime.now(),
accuracy: 5000, // 标记低精度
);
}
}

2. Android 10+ 注意事项

  • 需要动态申请 ACCESS_BACKGROUND_LOCATION 权限才能在后台定位
  • 建议引导用户开启Wi-Fi扫描功能以提升定位精度

3. iOS 配置

ios/Runner/Info.plist 中添加:

<key>NSLocationWhenInUseUsageDescription</key>
<string>App需要您的同意才能访问位置</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>App需要您的同意才能在后台访问位置</string>

五、总结

推荐使用 Wi-Fi定位 作为GPS的替代方案,精度足以满足大多数场景需求。IP定位仅作为最后备用手段。主流地图SDK已内置完善的多源融合定位能力,开发者无需关心底层实现细节。

实现路径:配置权限 → 选择插件 → 调用统一API → 自动处理GPS/Wi-Fi/基站切换。

微信公众号

微信公众号

figma MCP

· 阅读需 5 分钟
Quany
软件工程师

Figma MCP(Model Context Protocol)是一项创新技术,它作为一个“翻译官”和“桥梁”,将Figma设计工具与AI编程助手(如Cursor、Claude Code等)无缝连接起来。这使得AI能够直接“理解”Figma设计稿中的元素、样式和结构,并自动生成高质量的前端代码,从而显著提升从设计到开发的转换效率 。

🧩 核心功能与应用场景

通过Figma MCP,AI助手可以为你完成以下一系列自动化任务,覆盖了从设计资源提取到代码生成的全流程 :

  • 生成前端代码:只需在Figma中选中一个画板(Frame)或组件,AI即可根据设计生成对应框架(如React、Vue、Tailwind CSS)的代码,非常适合快速构建新功能或页面 。
  • 提取设计上下文:直接获取设计文件中使用的变量(Variables)、组件和布局数据,并将其注入到集成开发环境(IDE)中。这对于维护设计系统和使用组件库的工作流尤为有用 。
  • 导出设计资源:根据需要,批量或单独导出设计稿中的图片、SVG等资源,并支持指定格式和缩放比例 。
  • 分析设计系统:自动分析颜色方案、字体使用、组件结构等,生成分析报告或CSS变量文件,帮助保持设计一致性 。
  • 获取设计稿截图:获取设计节点的可视化截图,为AI提供精确的视觉参考,确保代码还原度 。

⚙️ 如何配置与连接

Figma官方提供了两种使用MCP服务器的方式,你可以根据自身情况选择一种进行配置 。

服务器类型适用场景激活方式
本地服务器(Local)适合大多数个人开发者和小团队,通过Figma桌面应用运行,数据在本地处理 。1. 打开最新版的Figma桌面应用。
2. 进入一个设计文件。
3. 切换至Dev Mode(开发模式)。
4. 在检查(Inspect)面板的MCP server区域,点击 Enable desktop MCP server。服务器通常会运行在 http://127.0.0.1:3845/mcp
远程服务器(Remote)适合大型团队或希望简化配置的用户,直接连接至Figma的托管端点,无需运行桌面应用 。无需本地激活,服务器地址为:https://mcp.figma.com/mcp。但需要注意,某些高级功能可能需要特定的Figma订阅席位(如Dev或Full seat)。

连接到AI工具(以Cursor为例) 配置好服务器后,需要在你的AI编程工具中进行连接。下面以流行的Cursor为例 :

  1. 打开Cursor,进入 Settings(设置)。
  2. 找到 MCP 选项页签。
  3. 点击 Add new global MCP server(添加新的全局MCP服务器)。
  4. 根据你选择的服务器类型,输入对应的配置信息 :
    • 本地服务器
      {
      "mcpServers": {
      "Figma": {
      "url": "http://127.0.0.1:3845/mcp"
      }
      }
      }
    • 远程服务器
      {
      "mcpServers": {
      "Figma": {
      "url": "https://mcp.figma.com/mcp"
      }
      }
      }
  5. 保存配置。通常,连接状态指示灯变为绿色即表示成功 。

🚀 最佳实践与使用技巧

为了获得更精准、高效的代码生成结果,可以参考以下建议 :

  • 提供高质量的设计稿:在Figma中,尽量使用组件自动布局(Auto Layout)来构建界面,并使用变量(Variables)来定义颜色、间距等样式。为图层赋予语义化的名称(如CardContainer而非Group 5),这能极大地帮助AI理解你的设计意图 。
  • 使用明确的提示词:在与AI交互时,清晰的指令至关重要。你可以指定技术栈、输出路径等,例如:
    • “为当前选中的Figma框架生成Vue 3组合式API的代码,并使用我们项目中src/components/ui下的现有组件。”
    • “获取这个设计中使用到的所有颜色和间距变量名及其数值。”
  • 利用Code Connect提升一致性:如果项目使用了Figma的Code Connect功能,务必启用它。这能将Figma组件直接映射到代码库中的具体组件实现,让AI生成的代码能最大程度地复用现有组件,保证一致性 。
  • 分批处理大型设计:如果设计稿非常复杂,一次性生成大量代码可能会超出上下文限制。可以尝试先使用get_metadata工具获取高层级节点图,然后有选择地针对特定节点生成代码 。

⚠️ 注意事项

  • 权限与订阅:使用Figma MCP服务器需要相应的Figma账户权限。部分高级功能或更高的调用频率限制可能仅限于ProfessionalOrganizationEnterprise订阅计划中的DevFull席位用户 。
  • 项目适配性:AI生成的代码是一个强大的起点,但可能无法100%完美适配所有项目特定的架构和逻辑。生成的代码需要开发者进行审查和必要的调整 。
  • 备选方案:除了官方方案,社区也存在一些开源的Figma MCP服务器实现(如figma-mcp-full-server),它们可能提供额外的功能,如更强大的批量导出和重试机制 。另外,国内也有类似Pixso这样的设计工具提供了类似的MCP功能,可能更适合部分用户的使用习惯 。

希望这份详细的指南能帮助你更好地理解和运用Figma MCP,解锁AI驱动的设计到代码自动化工作流。如果你在配置或使用过程中遇到更具体的问题,欢迎随时提出。

微信公众号

微信公众号

tlbs-map-react 通过 ref 设置中心点

· 阅读需 1 分钟
Quany
软件工程师

问题

鑫联盟需求要 16 以下的 node版本,但react-native-update-cli需要 18 以上的版本,故而左右为难;

脚本: react-native-update-cli/lib/bundle.js

    const reactNativeBundleProcess = (0, _child_process.spawn)('/Volumes/Samsung/Home/.nvm/versions/node/v16.20.2/bin/node', reactNativeBundleArgs);
console.log(`Running bundle command: node ${reactNativeBundleArgs.join(' ')}`);

微信公众号

微信公众号

react useContext 的使用方法

· 阅读需 4 分钟
Quany
软件工程师

useContext 是 React 中一个非常实用的 Hook,它让你能够轻松地在组件树中传递数据,无需手动层层传递 props。下面是一个清晰的使用指南。

💁 基本使用步骤

使用 useContext 主要分为三个步骤:创建 Context、提供 Context 值、在组件中订阅 Context。

步骤操作代码示例
1. 创建 Context使用 React.createContext 创建一个 Context 对象。你可以为其提供一个默认值,当组件不在 Provider 包裹下时会使用该默认值。const MyContext = React.createContext(defaultValue);
2. 提供 Context 值在组件树上层使用 <MyContext.Provider> 组件包裹需要接收数据的子组件,并通过 value 属性传递数据。<MyContext.Provider value={someValue}> <ChildComponent /> </MyContext.Provider>
3. 订阅 Context 值在子组件(任何层级)中,使用 useContext Hook 来获取 Context 的当前值。const value = useContext(MyContext);

🔢 修改 Context 的值

让 Context 数据变得可修改,通常是通过在 value 中传递一个状态和更新该状态的函数来实现的。

// 在提供 Context 的父组件中(如 App.js)
import React, { useState } from 'react';
import MyContext from './MyContext';
import ChildComponent from './ChildComponent';

function App() {
const [user, setUser] = useState({ name: 'John', age: 30 });

// 将状态和更新函数一起传递
const contextValue = {
user,
updateUser: setUser
};

return (
<MyContext.Provider value={contextValue}>
<ChildComponent />
</MyContext.Provider>
);
}

在子组件中,你就可以获取并调用这个函数来更新状态:

// 在子组件中
import React, { useContext } from 'react';
import MyContext from './MyContext';

function ChildComponent() {
const { user, updateUser } = useContext(MyContext);

const handleClick = () => {
updateUser({ ...user, name: 'Jane' });
};

return (
<div>
<p>Name: {user.name}</p>
<button onClick={handleClick}>Change Name</button>
</div>
);
}

这种方式使得深层子组件可以直接更新全局状态。

🎯 适用场景与最佳实践

  • 典型应用场景useContext 非常适合于那些需要被许多不同层级的组件访问的"全局"数据,例如:

    • 用户认证信息(如用户登录状态、个人资料)。
    • 界面主题(如浅色/深色模式)。
    • 多语言国际化(i18n)信息。
  • 性能优化提醒:需要特别注意,每当 Providervalue 属性发生变化时,所有订阅了该 Context 的子组件都会重新渲染,即使它们只使用了 value 中未变化的部分。为了优化性能,你可以考虑:

    • 拆分 Context:将不常变化的数据和频繁变化的数据放到不同的 Context 中。
    • 使用 React.memo:结合 React.memo 来防止不必要的子组件重渲染。
    • 使用 useMemo:对 value 进行记忆化处理。
  • 创建自定义 Hook:这是一个推荐的最佳实践。你可以创建一个自定义 Hook 来使用 useContext,这有助于提高代码的复用性,并且在多个组件使用相同 Context 时非常方便。

    // 创建自定义 Hook
    import { useContext } from 'react';
    import { MyContext } from './MyContext';

    export function useMyContext() {
    const context = useContext(MyContext);
    if (context === undefined) {
    // 可选:检查 Context 是否在 Provider 内
    throw new Error('useMyContext must be used within a MyContext.Provider');
    }
    return context;
    }

    // 在组件中使用自定义 Hook
    function MyComponent() {
    const { user } = useMyContext(); // 使用更简洁
    // ...
    }

⚠️ 注意事项

  • 默认值:只有在组件所处的组件树中没有匹配到 Provider 时,useContext 才会返回创建 Context 时传入的默认值(defaultValue)。
  • 寻找 ProvideruseContext 会寻找组件树中离它最近的同一个 Context 的 Provider
  • 类组件中的使用:在类组件中,你不能使用 useContext Hook。替代方法是使用 <MyContext.Consumer> 渲染函数,或者通过静态属性 static contextType = MyContext 来连接,然后通过 this.context 来访问。

💎 核心价值

简单来说,useContext 的核心价值在于解决了组件树中"深层级"组件之间的数据传递难题。它让你无需再通过中间组件一层层地传递 props(即"prop drilling"),使得状态共享更加清晰和高效。

希望这份指南能帮助你更好地理解和使用 useContext!如果你对特定场景下的应用还有疑问,欢迎继续提出。

微信公众号

微信公众号

tlbs-map-react 通过 ref 设置中心点

· 阅读需 4 分钟
Quany
软件工程师

tlbs-map-react 中,通过 ref 设置地图中心点是一种非常灵活和强大的方式。下面我为您详细介绍几种实现方法:

🔧 方法一:使用 useRef 获取地图实例(推荐)

这是最常用和推荐的方法,通过 React 的 useRef Hook 获取地图实例,然后调用地图的原生方法。

import React, { useRef, useState } from 'react';
import { TMap } from 'tlbs-map-react';

const MapComponent = () => {
// 创建 ref 引用
const mapRef = useRef(null);
const [newCenter, setNewCenter] = useState({ lat: 39.916, lng: 116.397 });

// 地图加载完成后的回调
const handleMapLoad = (mapInstance) => {
mapRef.current = mapInstance;
console.log('地图实例已获取:', mapInstance);
};

// 通过 ref 设置中心点的函数
const setMapCenter = (lat, lng) => {
if (mapRef.current) {
// 方法1: 使用 setCenter 方法
mapRef.current.setCenter(new TMap.LatLng(lat, lng));

// 方法2: 或者使用 panTo 方法(平滑移动)
// mapRef.current.panTo(new TMap.LatLng(lat, lng));

console.log(`地图中心已设置为: ${lat}, ${lng}`);
} else {
console.warn('地图实例尚未就绪');
}
};

// 处理输入框变化
const handleInputChange = (e) => {
const { name, value } = e.target;
setNewCenter(prev => ({
...prev,
[name]: parseFloat(value)
}));
};

return (
<div style={{ width: '100%', height: '500px' }}>
{/* 控制面板 */}
<div style={{ marginBottom: '10px', padding: '10px', background: '#f5f5f5' }}>
<label>纬度: </label>
<input
type="number"
name="lat"
value={newCenter.lat}
onChange={handleInputChange}
step="0.0001"
/>
<label style={{ marginLeft: '10px' }}>经度: </label>
<input
type="number"
name="lng"
value={newCenter.lng}
onChange={handleInputChange}
step="0.0001"
/>
<button
onClick={() => setMapCenter(newCenter.lat, newCenter.lng)}
style={{ marginLeft: '10px' }}
>
设置中心点
</button>
</div>

{/* 地图组件 */}
<TMap
ref={mapRef}
apiKey="您的腾讯地图密钥"
options={{
center: newCenter, // 初始中心点
zoom: 12
}}
onLoad={handleMapLoad}
/>
</div>
);
};

export default MapComponent;

🎯 方法二:创建自定义 Hook

为了更好的代码复用,可以创建一个自定义 Hook:

import { useRef, useCallback } from 'react';

// 自定义 Hook:用于地图操作
export const useMapControl = () => {
const mapRef = useRef(null);

const setCenter = useCallback((lat, lng) => {
if (mapRef.current) {
mapRef.current.setCenter(new TMap.LatLng(lat, lng));
return true;
}
return false;
}, []);

const getCenter = useCallback(() => {
if (mapRef.current) {
return mapRef.current.getCenter();
}
return null;
}, []);

const setZoom = useCallback((zoomLevel) => {
if (mapRef.current) {
mapRef.current.setZoom(zoomLevel);
}
}, []);

return {
mapRef,
setCenter,
getCenter,
setZoom
};
};

// 在组件中使用
const MyMap = () => {
const { mapRef, setCenter, getCenter } = useMapControl();

const handleSetToBeijing = () => {
setCenter(39.9042, 116.4074);
};

const handleLogCenter = () => {
const center = getCenter();
if (center) {
console.log('当前中心点:', center.lat, center.lng);
}
};

return (
<div>
<div>
<button onClick={handleSetToBeijing}>定位到北京</button>
<button onClick={handleLogCenter}>打印中心点</button>
</div>

<TMap
ref={mapRef}
apiKey="您的密钥"
options={{
center: { lat: 39.916, lng: 116.397 },
zoom: 10
}}
/>
</div>
);
};

📍 方法三:结合地理编码设置中心点

在实际应用中,经常需要根据地址名称来设置中心点:

import React, { useRef, useState } from 'react';
import { TMap } from 'tlbs-map-react';

const GeocodingMap = () => {
const mapRef = useRef(null);
const [address, setAddress] = useState('');

// 地理编码函数
const geocodeAddress = async (address) => {
try {
// 使用腾讯地图地理编码服务
const geocoder = new TMap.service.Geocoder();

return new Promise((resolve, reject) => {
geocoder.getLocation({ address: address }, (result) => {
if (result && result.result && result.result.location) {
resolve(result.result.location);
} else {
reject('地址解析失败');
}
});
});
} catch (error) {
console.error('地理编码错误:', error);
throw error;
}
};

const handleSearch = async () => {
if (!address.trim()) return;

try {
const location = await geocodeAddress(address);
if (mapRef.current && location) {
mapRef.current.setCenter(new TMap.LatLng(location.lat, location.lng));
mapRef.current.setZoom(15); // 放大到更详细的级别
}
} catch (error) {
alert('无法找到该地址,请重试');
}
};

return (
<div>
<div style={{ marginBottom: '10px' }}>
<input
type="text"
value={address}
onChange={(e) => setAddress(e.target.value)}
placeholder="输入地址..."
style={{ width: '300px', padding: '8px' }}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
/>
<button onClick={handleSearch} style={{ padding: '8px 16px', marginLeft: '10px' }}>
搜索
</button>
</div>

<TMap
ref={mapRef}
apiKey="您的密钥"
options={{
center: { lat: 39.916, lng: 116.397 },
zoom: 10
}}
style={{ width: '100%', height: '400px' }}
/>
</div>
);
};

⚠️ 重要注意事项

  1. 确保地图已加载:在调用 mapRef.current 的方法之前,务必检查 mapRef.current 不为 null

  2. 错误处理

const safeSetCenter = (lat, lng) => {
if (!mapRef.current) {
console.error('地图实例未就绪');
return;
}

try {
mapRef.current.setCenter(new TMap.LatLng(lat, lng));
} catch (error) {
console.error('设置中心点失败:', error);
}
};
  1. 性能优化:频繁设置中心点可能会影响性能,可以考虑使用防抖:
import { debounce } from 'lodash';

const debouncedSetCenter = debounce((lat, lng) => {
if (mapRef.current) {
mapRef.current.setCenter(new TMap.LatLng(lat, lng));
}
}, 300);

通过 ref 设置中心点为您提供了最大的灵活性,特别是在需要动态响应各种用户交互或数据变化的场景中。

微信公众号

微信公众号

flutter app_settings 使用教程

· 阅读需 4 分钟
Quany
软件工程师

app_settings 插件让 Flutter 应用能够便捷地打开系统和应用自身的设置页面,这在引导用户开启权限或调整系统设置时非常实用。下面是一个详细的使用指南。

📲 安装与配置

首先,将 app_settings 插件添加到你的项目中。

  1. 添加依赖:在 pubspec.yaml 文件的 dependencies 部分添加最新版本的插件。

    dependencies:
    flutter:
    sdk: flutter
    app_settings: ^6.1.1

    然后运行 flutter pub get 命令来安装依赖。

  2. 平台特定配置(iOS)

    • 如果项目使用 Objective-C:需要在 ios/Podfile 中启用 use_frameworks!
      target 'Runner' do
      use_frameworks!
      # ... 其他配置
      end
    • 如果使用 Swift Package Manager (SPM):需要在终端执行 flutter config --enable-swift-package-manager 来启用 Swift 支持。

🎯 核心使用方法

完成安装后,你可以在代码中导入并使用插件。

1. 打开通用应用设置

最基本的功能是打开当前应用的系统设置页面,用户可以在这里管理应用权限。

import 'package:flutter/material.dart';
import 'package:app_settings/app_settings.dart';

ElevatedButton(
onPressed: () {
// 这将跳转到系统中本应用的设置界面
AppSettings.openAppSettings();
},
child: const Text('打开应用设置'),
)

2. 打开特定系统设置页面

插件支持直接跳转到特定的系统设置页面,如位置、Wi-Fi、蓝牙、通知等。当用户需要开启某项权限时,这能提供更直接的引导。

ElevatedButton(
onPressed: () {
// 直接打开系统的位置服务设置页面
AppSettings.openAppSettings(type: AppSettingsType.location);
},
child: const Text('打开位置设置'),
)

下面的表格汇总了插件支持打开的主要设置类型:

设置类型 (AppSettingsType)说明
settings当前应用的详细设置页面(默认)
location系统定位服务设置
wifiWi-Fi 网络设置
bluetooth蓝牙设置
notification通知设置
sound声音和音量设置
display显示设置
developer开发者选项(Android)

3. Android Q及以上版本的设置面板

对于 Android Q (API 29) 及更高版本,插件还支持打开快速设置面板(Settings Panel),它会在悬浮窗中显示部分常用设置,用户无需离开当前应用即可进行快速调整。

ElevatedButton(
onPressed: () {
// 在Android Q+设备上会弹出音量控制面板
AppSettings.openAppSettingsPanel(AppSettingsPanelType.volume);
},
child: const Text('快速调节音量'),
)

除了音量 (volume),支持的设置面板类型还包括互联网连接 (internetConnectivity)、NFC (nfc) 和电池状态 (battery) 等。

4. Android平台高级选项

在 Android 上,你可以在打开设置页面时使用 asAnotherTask 参数。将其设置为 true 会使设置页面在一个新的 Activity 中打开,这有助于维护应用的导航栈状态。

AppSettings.openAppSettings(
type: AppSettingsType.notification,
asAnotherTask: true, // 在新Activity中打开
);

💡 最佳实践与注意事项

为了确保最佳的用户体验和功能稳定性,这里有一些建议:

实践要点说明与建议
检查平台兼容性某些设置类型可能在某些平台或系统版本上不受支持。插件在遇到不支持的类型时会自动回退到打开通用应用设置页面。
清晰的用户引导在跳转设置页面前,最好通过对话框或文字向用户说明为何需要他们进行此操作(例如:“请在系统设置中开启位置权限,以便为您提供周边服务。”)。
处理回退场景从设置页面返回应用后,建议在 Widget 的生命周期方法(如 onResume)中检查用户是否已授权,并更新应用的UI状态。
iOS 私有 API 注意在 iOS 上,直接打开特定设置子页面(如 AppSettingsType.wifi)使用的是以 App-Prefs:root= 开头的私有 URL Scheme。请注意,在提交 App Store 审核时,使用私有 API 可能存在理论上的风险,尽管在实践中通常被接受。最稳妥的方式是仅打开通用应用设置(AppSettingsType.settings)。

希望这份详细的教程能帮助你顺利地在项目中集成 app_settings 插件。如果你在特定场景下遇到更具体的问题,欢迎随时提出。

微信公众号

微信公众号

热更新解决方案

· 阅读需 7 分钟
Quany
软件工程师

根据项目中的服务文件,我将API接口按功能模块进行了分类整理。以下是各模块的接口详情:

1. 公共接口 (common-service.ts)

获取枚举字段

function getFieldEnum(params: { code: string }): Promise<any>
  • URL: /operation-manager/object/field/enum/metadata/listFieldEnumValue
  • 参数:
    • code: string - 枚举字段代码
  • 返回: 枚举值列表

单图上传

function postSingleImgUpload(file: File)
  • URL: /resource/management/file/upload
  • 参数:
    • file: File - 图片文件对象
  • 返回: 上传结果,包含图片URL等信息

分类树形结构

function postCategoryList(params: Record<string, any>)
  • URL: /boMenuApi/gql/category/list
  • 参数: 分类查询参数
  • 返回: 分类树形结构数据

获取菜单分类分组

function getMenuCategory(params = { isArchived: false }): Promise<any>
  • URL: /boMenuApi/api/menu/tree
  • 参数:
    • isArchived: boolean - 是否包含归档项,默认false
  • 返回: 菜单分类树

查询业务配置

function queryBizConfig(params?: any, config?: any): Promise<any>
  • URL: /vulcan/special/queryConfig
  • 参数: 配置查询参数
  • 返回: 业务配置信息

发布菜单

function publishMenu(params: any, config?: any): Promise<any>
  • URL: /boMenuApi/bo-menu/api/publish-tool/addPos
  • 参数: 发布相关参数
  • 返回: 发布结果

新增商品规格类型

function postItemSizeAdd(params: Record<string, any> = {})
  • URL: /boMenuApi/api/item/size/add
  • 参数: 规格类型信息
  • 返回: 新增结果

AI识别图片上传

function postFilesUpload(params: any, config?: any)
  • URL: /boMenuApi/api/item/proxy/starship-upload
  • 参数: 上传相关参数
  • 返回: 上传结果

获取商户信息

function getCorporationInfo(params: any = {}): Promise<any>
  • URL: /boShopApi/api/corporation/corporationInfo
  • 参数: 查询参数
  • 返回: 商户信息

2. 门店管理接口 (store/service.ts)

查询门店

function queryStore(params: { dataId: string }): Promise<Response<StoreInfo | null>>
  • URL: /boShopApi/api/shop/query
  • 参数:
    • dataId: string - 门店ID(必填)
  • 返回: 门店信息对象

更新门店

function updateStore(params: UpdateStoreInfoParams): Promise<Response<any>>
  • URL: /boShopApi/api/shop/update
  • 参数: 门店信息更新对象
  • 返回: 更新结果

批量保存或更新语言

function batchSaveOrUpdateLanguage(params: BatchSaveOrUpdateLanguageParams): Promise<Response<null>>
  • URL: /operation-manager/content/language/batchSaveOrUpdate
  • 参数: 语言批量更新参数
  • 返回: 更新结果

获取语言内容

function getContentLanguage(params: { domain: string; resource: string; key: string }): Promise<Response<null>>
  • URL: /operation-manager/content/language/getContentLanguage
  • 参数:
    • domain: string - 域名
    • resource: string - 资源名
    • key: string - 键名
  • 返回: 语言内容

自动生成门店编码

function generateStoreCode(params: { corporationId: string; orgType: string }): Promise<Response<string>>
  • URL: /organize/generateOrgCode
  • 参数:
    • corporationId: string - 公司ID
    • orgType: string - 组织类型
  • 返回: 生成的门店编码

查询员工列表

function queryEmployeeList(params: QueryEmployeeListParams): Promise<Response<Employee[]>>
  • URL: /vulcan/employee/queryEmployeeByCodes
  • 参数: 员工查询参数
  • 返回: 员工列表

其他查询接口

接口名URL参数返回
查询语言列表/operation-manager/dictionary/listLanguage分页参数语言列表
查询品牌列表/organize/queryBrandListcorporationId: string品牌列表
查询货币列表/operation-manager/dictionary/listCurrency分页参数货币列表
获取日期格式列表/operation-manager/format/listDataFormatAll分页参数日期格式列表
获取时间格式列表/operation-manager/format/listTimeFormatAll分页参数时间格式列表
获取数字格式列表/operation-manager/format/listNumberFormat分页参数数字格式列表
获取电话格式列表/operation-manager/format/listPhoneFormatAllcountryCode: string电话格式列表
获取时区列表/operation-manager/dictionary/listTimeZone国家代码列表和归档状态时区列表
查询国家列表/operation-manager/dictionary/listCountrylanguageCode: string国家列表
查询区域/operation-manager/dictionary/listArea国家代码、语言代码等区域列表
查询地址格式/operation-manager/format/listAddressFormatAllcountryCode: string地址格式列表

地图相关接口

// 搜索地点
function searchLocation(params: { address: string }): Promise<Response<any>>
// 经纬度取位置信息
function getLocationInfo(params: { latitude: string; longitude: string }): Promise<Response<any>>

3. 桌台管理接口 (table/service.ts)

桌台相关接口

接口名URL参数返回
查询桌台/boShopApi/api/table/listTable区域ID、分页参数等桌台列表
新增桌台/boShopApi/api/table/createTable桌台信息对象添加结果
修改桌台/boShopApi/api/table/updateTable桌台信息对象更新结果
删除桌台/boShopApi/api/table/deleteTabletableId: string删除结果
查询单桌台/boShopApi/api/table/queryTabletableId: string桌台详情
排序桌台/boShopApi/api/table/tableSort桌台排序数组排序结果
批量新增桌台/boShopApi/api/table/batchInsertTable批量桌台参数批量添加结果
批量删除桌台/boShopApi/api/table/batchDeleteTable桌台ID数组批量删除结果
批量修改桌台/boShopApi/api/table/batchUpdateTable批量桌台参数批量更新结果

区域相关接口

接口名URL参数返回
查询区域/boShopApi/api/area/queryAreaareaId: string区域详情
新增区域/boShopApi/api/area/createArea区域信息对象添加结果
修改区域/boShopApi/api/area/updateArea区域信息对象更新结果
获取区域列表/boShopApi/api/area/listArea归档状态区域列表
排序区域/boShopApi/api/area/areaSort区域排序数组排序结果
删除区域/boShopApi/api/area/deleteAreaareaId: string删除结果

其他桌台管理接口

接口名URL参数返回
查询(处理服务费)/boMenuApi/surcharge/queryPage归档状态、服务费类型、分页参数服务费列表
发布桌台/boShopApi/api/area/publish发布结果

4. 菜单管理接口 (menus/service.ts)

菜单相关接口

接口名URL参数返回
查询菜单列表/boMenuApi/api/menu/tree归档状态、菜单渠道菜单树结构
添加菜单/boMenuApi/api/menu/add菜单信息对象添加结果
修改菜单/boMenuApi/api/menu/update菜单信息对象更新结果
查询菜单/boMenuApi/api/menu/querydataId: string菜单详情
菜单排序/boMenuApi/api/menu/set/sequence中心菜单UID、排序信息排序结果
获取价格带/boMenuApi/api/pricing/all查询参数价格带列表

菜单分组相关接口

接口名URL参数返回
创建菜单分组/boMenuApi/api/menu/group/add菜单分组信息添加结果
更新菜单分组/boMenuApi/api/menu/group/update菜单分组信息更新结果
获取菜单分组详情/boMenuApi/api/menu/group/query查询参数菜单分组详情

5. 商品分类接口 (categories/service.tsx)

分类相关接口

接口名URL参数返回
获取商品分类列表/boMenuApi/gql/category/list分类级别类型、归档状态分类列表
获取商品分类详情/boMenuApi/gql/category/v2/getOnedataId: string分类详情
新增分类/boMenuApi/gql/category/add分类信息添加结果
编辑分类/boMenuApi/gql/category/update分类信息更新结果

其他相关接口

接口名URL参数返回
查询备餐站列表/boShopApi/api/prep/station/shop/listPage分页参数、归档状态备餐站列表
查询税率列表/boMenuApi/api/tax/queryPage分页参数、归档状态税率列表
查询科目名称/boShopApi/api/financial/account/list查询参数科目列表
查询课程列表/boMenuApi/api/course/list归档状态课程列表

6. 通用功能接口 (commons/service.ts)

图片处理接口

接口名URL参数返回
批量裁剪图片/resource/management/image/space/shearImageBatch图片裁剪数据裁剪结果
上传图片/resource/management/file/upload图片文件上传结果

7. 接口返回数据结构

大部分接口的返回数据遵循以下通用结构:

// 基本响应结构
type Response<T> = {
code: string; // 响应码,000表示成功
message: string; // 响应消息
data: T; // 响应数据,具体类型由接口定义
}

// 带分页的响应结构
type ResponseWithPager<T> = {
code: string;
message: string;
data: {
list: T[]; // 数据列表
page: PageInfo; // 分页信息
};
}

// 分页信息
type PageInfo = {
total: number; // 总记录数
pageNo: number; // 当前页码
pageSize: number; // 每页大小
pageCount: number; // 总页数
}

以上是项目中所有主要API接口的汇总信息,包含了接口的分类、功能、请求参数和返回数据结构。

微信公众号

微信公众号

前端开发相关讨论

· 阅读需 4 分钟
Quany
软件工程师

讨论内容

会议讨论了前端开发的流程、规范、投产注意事项以及新工具MCP的使用等内容,具体如下:

  • 前端开发文档与流程
    • 入职手册内容:包含前端开发入职手册,涉及VPN、Git、Satan、OM系统等账号申请及使用说明,如VPN账号用于联网及登录其他系统,申请后用该账号登录Git并分配代码权限。
    • 投产流程规范:强调投产流程规范化,包括与产品沟通、检查单填写、后端先投前端再投、代码合并、项目构建与部署等步骤,新菜单需求分支合并有特殊要求。
    • 发版时间安排:遵循两周一次大迭代的节奏,不同地区发版时间不同,如周一晚10点发北京、第三天10点发东南亚、周四10点半发欧洲、下午发北美,周二周五不上线。
  • 代码规范与管理
    • 命名规范:商户APP分支命名有规范,如release、测试分支、需求分支等有特定命名方式,可避免混淆和代码管理问题。
    • 版本管理:不同APP版本管理方式不同,商户APP和预定APP需自行管理总分值和tag,养成规范打tag习惯有助于了解项目迭代情况。
    • 临时分支使用:临时分支可用于剔除特定需求,避免回滚代码,操作简单高效。
  • APP上架与更新
    • 苹果商店上架:需填写字符描述关键词、更新语等信息,选择上传test fly后的构建版本号,提交审核。
    • Google play上架:新增APP时需填资料,加版本号后上传AAB文件送审,审核进度可通过绑定邮箱查看。
  • 前端技术方案规范
    • 方案目的:是衔接产品、测试、前后端开发的核心文档,消除信息差,明确实现路径。
    • 内容要求:包括需求背景(结合产品文档加见解)、目的、核心功能、分工、待确认项等,可参考消息APP设置消息推送模板。
  • 投产配置与注意事项
    • 平台与仓库概念:常用管理平台业务环境OM和静态资源部署平台CSD,项目开发涉及自己的仓库和package仓库(类似后端阿波罗配置中心)。
    • 代码合并与部署:发生产时将对应区域生产分支合到本地,解决冲突后推上去合并,部署按开发、测试、UAT验证、生产环境流程进行。
    • 配置注意事项:业务对象、菜单和角色、翻译工作台等配置有要求和注意点,如业务对象配置多语言时英文借助工具精简,菜单发布需提前制定计划并与北京相关人员沟通。
  • MCP工具介绍与使用
    • 工具功能:是前端知识库,可查询文档规范、投产流程、代码使用方法等,提高工作效率,减少询问同事时间。
    • 数据存储与更新:数据存储在周佐他们的数据库,源头文档更新需手动同步。
    • 使用方法:通过特定指令查询,平台搭建未完成,后续会有全局config,大家可收集问题反馈。
  • 任务
    • 仓库统计添加:在文档中添加统计,记录只用 release 分支发版的仓库数量,同时维护项目分支情况,自行填写负责或设计的项目信息
    • UAT操作补充:补充发 UAT 版本时菜单开关的注意事项及操作流程,说明如何避免与他人操作相互覆盖,有问题可咨询说话人 5 或周云
    • 操作例子发送:将生产的 package 项目发 UAT 时,在 release 分支修改对应版本号的操作原理和快捷方式的例子发送到群里
    • 全局 config 整理:整理一个全局的 config,以便开发过程中遇到问题时更方便调用,无需再单独调用;收集日常开发问题,可找彭强反馈。

微信公众号

微信公众号