1. //引入腾旭地图sdk
  2. import QQMapWX from '../../common/qqmap-wx-jssdk.js'


  3. onLoad(){
  4. this.getMapAddress()
  5.  
  6. },
  7.  
  8. methods:{
  9. getMapAddress() {
  10. var that = this
  11. const tMap = new QQMapWX({
  12. key: '1111111-222-5BPWR-TQVW4L-YHAPZ-PABLE' //开发者密钥 //这里要换成自己的key
  13. });
  14. //使用 uni.getLocation获取用户所在经纬度
  15. uni.getLocation({
  16. type: 'wgs84',
  17. geocode: true,
  18. success: (res) => {
  19. uni.setStorageSync('longitude',res.longitude)
  20. uni.setStorageSync('latitude',res.latitude)
  21. console.log("获取经纬度成功");
  22. this.latitude = res.latitude;
  23. this.longitude = res.longitude;
  24. },
  25. fail: () => {
  26. console.log("获取经纬度失败");
  27. },
  28. complete: () => {
  29. // 使用腾讯sdk的reverseGeocoder方法 解析经纬度
  30. tMap.reverseGeocoder({
  31. location: {
  32. latitude: this.latitude,
  33. longitude: this.longitude
  34. },
  35. success: function(res) {
  36. // console.log("解析地址成功", res);
  37. // console.log("当前地址:", res.result.address);
  38. that.address = res.result.address
  39. //保存缓存
  40. uni.setStorage({
  41. key:'local',
  42. data:res.result.address,
  43. success() {
  44. console.log("用户地址信息已缓存")
  45. }
  46. })
  47. },
  48. fail: function(res) {
  49. uni.showToast({
  50. title: '定位失败',
  51. duration: 2000,
  52. icon: "none"
  53. })
  54. console.log(res);
  55. },
  56. })
  57. }
  58. })
  59. },
  60. }

这里是common文件夹下的 qqmap-wx-jssdk.js 文件

  1. var ERROR_CONF = {
  2. KEY_ERR: 311,
  3. KEY_ERR_MSG: 'key格式错误',
  4. PARAM_ERR: 310,
  5. PARAM_ERR_MSG: '请求参数信息有误',
  6. SYSTEM_ERR: 600,
  7. SYSTEM_ERR_MSG: '系统错误',
  8. WX_ERR_CODE: 1000,
  9. WX_OK_CODE: 200
  10. };
  11. var BASE_URL = 'https://apis.map.qq.com/ws/';
  12. var URL_SEARCH = BASE_URL + 'place/v1/search';
  13. var URL_SUGGESTION = BASE_URL + 'place/v1/suggestion';
  14. var URL_GET_GEOCODER = BASE_URL + 'geocoder/v1/';
  15. var URL_CITY_LIST = BASE_URL + 'district/v1/list';
  16. var URL_AREA_LIST = BASE_URL + 'district/v1/getchildren';
  17. var URL_DISTANCE = BASE_URL + 'distance/v1/';
  18. var URL_DIRECTION = BASE_URL + 'direction/v1/';
  19. var MODE = {
  20. driving: 'driving',
  21. transit: 'transit'
  22. };
  23. var EARTH_RADIUS = 6378136.49;
  24. var Utils = {
  25. /**
  26. * md5加密方法
  27. * 版权所有2011 Sebastian Tschan,https://blueimp.net
  28. */
  29. safeAdd(x, y) {
  30. var lsw = (x & 0xffff) + (y & 0xffff);
  31. var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  32. return (msw << 16) | (lsw & 0xffff);
  33. },
  34. bitRotateLeft(num, cnt) {
  35. return (num << cnt) | (num >>> (32 - cnt));
  36. },
  37. md5cmn(q, a, b, x, s, t) {
  38. return this.safeAdd(this.bitRotateLeft(this.safeAdd(this.safeAdd(a, q), this.safeAdd(x, t)), s), b);
  39. },
  40. md5ff(a, b, c, d, x, s, t) {
  41. return this.md5cmn((b & c) | (~b & d), a, b, x, s, t);
  42. },
  43. md5gg(a, b, c, d, x, s, t) {
  44. return this.md5cmn((b & d) | (c & ~d), a, b, x, s, t);
  45. },
  46. md5hh(a, b, c, d, x, s, t) {
  47. return this.md5cmn(b ^ c ^ d, a, b, x, s, t);
  48. },
  49. md5ii(a, b, c, d, x, s, t) {
  50. return this.md5cmn(c ^ (b | ~d), a, b, x, s, t);
  51. },
  52. binlMD5(x, len) {
  53. /* append padding */
  54. x[len >> 5] |= 0x80 << (len % 32);
  55. x[((len + 64) >>> 9 << 4) + 14] = len;
  56.  
  57. var i;
  58. var olda;
  59. var oldb;
  60. var oldc;
  61. var oldd;
  62. var a = 1732584193;
  63. var b = -271733879;
  64. var c = -1732584194;
  65. var d = 271733878;
  66.  
  67. for (i = 0; i < x.length; i += 16) {
  68. olda = a;
  69. oldb = b;
  70. oldc = c;
  71. oldd = d;
  72.  
  73. a = this.md5ff(a, b, c, d, x[i], 7, -680876936);
  74. d = this.md5ff(d, a, b, c, x[i + 1], 12, -389564586);
  75. c = this.md5ff(c, d, a, b, x[i + 2], 17, 606105819);
  76. b = this.md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
  77. a = this.md5ff(a, b, c, d, x[i + 4], 7, -176418897);
  78. d = this.md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
  79. c = this.md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
  80. b = this.md5ff(b, c, d, a, x[i + 7], 22, -45705983);
  81. a = this.md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
  82. d = this.md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
  83. c = this.md5ff(c, d, a, b, x[i + 10], 17, -42063);
  84. b = this.md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
  85. a = this.md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
  86. d = this.md5ff(d, a, b, c, x[i + 13], 12, -40341101);
  87. c = this.md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
  88. b = this.md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
  89.  
  90. a = this.md5gg(a, b, c, d, x[i + 1], 5, -165796510);
  91. d = this.md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
  92. c = this.md5gg(c, d, a, b, x[i + 11], 14, 643717713);
  93. b = this.md5gg(b, c, d, a, x[i], 20, -373897302);
  94. a = this.md5gg(a, b, c, d, x[i + 5], 5, -701558691);
  95. d = this.md5gg(d, a, b, c, x[i + 10], 9, 38016083);
  96. c = this.md5gg(c, d, a, b, x[i + 15], 14, -660478335);
  97. b = this.md5gg(b, c, d, a, x[i + 4], 20, -405537848);
  98. a = this.md5gg(a, b, c, d, x[i + 9], 5, 568446438);
  99. d = this.md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
  100. c = this.md5gg(c, d, a, b, x[i + 3], 14, -187363961);
  101. b = this.md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
  102. a = this.md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
  103. d = this.md5gg(d, a, b, c, x[i + 2], 9, -51403784);
  104. c = this.md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
  105. b = this.md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
  106.  
  107. a = this.md5hh(a, b, c, d, x[i + 5], 4, -378558);
  108. d = this.md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
  109. c = this.md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
  110. b = this.md5hh(b, c, d, a, x[i + 14], 23, -35309556);
  111. a = this.md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
  112. d = this.md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
  113. c = this.md5hh(c, d, a, b, x[i + 7], 16, -155497632);
  114. b = this.md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
  115. a = this.md5hh(a, b, c, d, x[i + 13], 4, 681279174);
  116. d = this.md5hh(d, a, b, c, x[i], 11, -358537222);
  117. c = this.md5hh(c, d, a, b, x[i + 3], 16, -722521979);
  118. b = this.md5hh(b, c, d, a, x[i + 6], 23, 76029189);
  119. a = this.md5hh(a, b, c, d, x[i + 9], 4, -640364487);
  120. d = this.md5hh(d, a, b, c, x[i + 12], 11, -421815835);
  121. c = this.md5hh(c, d, a, b, x[i + 15], 16, 530742520);
  122. b = this.md5hh(b, c, d, a, x[i + 2], 23, -995338651);
  123.  
  124. a = this.md5ii(a, b, c, d, x[i], 6, -198630844);
  125. d = this.md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
  126. c = this.md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
  127. b = this.md5ii(b, c, d, a, x[i + 5], 21, -57434055);
  128. a = this.md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
  129. d = this.md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
  130. c = this.md5ii(c, d, a, b, x[i + 10], 15, -1051523);
  131. b = this.md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
  132. a = this.md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
  133. d = this.md5ii(d, a, b, c, x[i + 15], 10, -30611744);
  134. c = this.md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
  135. b = this.md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
  136. a = this.md5ii(a, b, c, d, x[i + 4], 6, -145523070);
  137. d = this.md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
  138. c = this.md5ii(c, d, a, b, x[i + 2], 15, 718787259);
  139. b = this.md5ii(b, c, d, a, x[i + 9], 21, -343485551);
  140.  
  141. a = this.safeAdd(a, olda);
  142. b = this.safeAdd(b, oldb);
  143. c = this.safeAdd(c, oldc);
  144. d = this.safeAdd(d, oldd);
  145. }
  146. return [a, b, c, d];
  147. },
  148. binl2rstr(input) {
  149. var i;
  150. var output = '';
  151. var length32 = input.length * 32;
  152. for (i = 0; i < length32; i += 8) {
  153. output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff);
  154. }
  155. return output;
  156. },
  157. rstr2binl(input) {
  158. var i;
  159. var output = [];
  160. output[(input.length >> 2) - 1] = undefined;
  161. for (i = 0; i < output.length; i += 1) {
  162. output[i] = 0;
  163. }
  164. var length8 = input.length * 8;
  165. for (i = 0; i < length8; i += 8) {
  166. output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32);
  167. }
  168. return output;
  169. },
  170. rstrMD5(s) {
  171. return this.binl2rstr(this.binlMD5(this.rstr2binl(s), s.length * 8));
  172. },
  173. rstrHMACMD5(key, data) {
  174. var i;
  175. var bkey = this.rstr2binl(key);
  176. var ipad = [];
  177. var opad = [];
  178. var hash;
  179. ipad[15] = opad[15] = undefined;
  180. if (bkey.length > 16) {
  181. bkey = this.binlMD5(bkey, key.length * 8);
  182. }
  183. for (i = 0; i < 16; i += 1) {
  184. ipad[i] = bkey[i] ^ 0x36363636;
  185. opad[i] = bkey[i] ^ 0x5c5c5c5c;
  186. }
  187. hash = this.binlMD5(ipad.concat(this.rstr2binl(data)), 512 + data.length * 8);
  188. return this.binl2rstr(this.binlMD5(opad.concat(hash), 512 + 128));
  189. },
  190. rstr2hex(input) {
  191. var hexTab = '0123456789abcdef';
  192. var output = '';
  193. var x;
  194. var i;
  195. for (i = 0; i < input.length; i += 1) {
  196. x = input.charCodeAt(i);
  197. output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
  198. }
  199. return output;
  200. },
  201. str2rstrUTF8(input) {
  202. return unescape(encodeURIComponent(input));
  203. },
  204. rawMD5(s) {
  205. return this.rstrMD5(this.str2rstrUTF8(s));
  206. },
  207. hexMD5(s) {
  208. return this.rstr2hex(this.rawMD5(s));
  209. },
  210. rawHMACMD5(k, d) {
  211. return this.rstrHMACMD5(this.str2rstrUTF8(k), str2rstrUTF8(d));
  212. },
  213. hexHMACMD5(k, d) {
  214. return this.rstr2hex(this.rawHMACMD5(k, d));
  215. },
  216.  
  217. md5(string, key, raw) {
  218. if (!key) {
  219. if (!raw) {
  220. return this.hexMD5(string);
  221. }
  222. return this.rawMD5(string);
  223. }
  224. if (!raw) {
  225. return this.hexHMACMD5(key, string);
  226. }
  227. return this.rawHMACMD5(key, string);
  228. },
  229. /**
  230. * 得到md5加密后的sig参数
  231. * @param {Object} requestParam 接口参数
  232. * @param {String} sk签名字符串
  233. * @param {String} featrue 方法名
  234. * @return 返回加密后的sig参数
  235. */
  236. getSig(requestParam, sk, feature, mode) {
  237. var sig = null;
  238. var requestArr = [];
  239. Object.keys(requestParam).sort().forEach(function(key){
  240. requestArr.push(key + '=' + requestParam[key]);
  241. });
  242. if (feature == 'search') {
  243. sig = '/ws/place/v1/search?' + requestArr.join('&') + sk;
  244. }
  245. if (feature == 'suggest') {
  246. sig = '/ws/place/v1/suggestion?' + requestArr.join('&') + sk;
  247. }
  248. if (feature == 'reverseGeocoder') {
  249. sig = '/ws/geocoder/v1/?' + requestArr.join('&') + sk;
  250. }
  251. if (feature == 'geocoder') {
  252. sig = '/ws/geocoder/v1/?' + requestArr.join('&') + sk;
  253. }
  254. if (feature == 'getCityList') {
  255. sig = '/ws/district/v1/list?' + requestArr.join('&') + sk;
  256. }
  257. if (feature == 'getDistrictByCityId') {
  258. sig = '/ws/district/v1/getchildren?' + requestArr.join('&') + sk;
  259. }
  260. if (feature == 'calculateDistance') {
  261. sig = '/ws/distance/v1/?' + requestArr.join('&') + sk;
  262. }
  263. if (feature == 'direction') {
  264. sig = '/ws/direction/v1/' + mode + '?' + requestArr.join('&') + sk;
  265. }
  266. sig = this.md5(sig);
  267. return sig;
  268. },
  269. /**
  270. * 得到终点query字符串
  271. * @param {Array|String} 检索数据
  272. */
  273. location2query(data) {
  274. if (typeof data == 'string') {
  275. return data;
  276. }
  277. var query = '';
  278. for (var i = 0; i < data.length; i++) {
  279. var d = data[i];
  280. if (!!query) {
  281. query += ';';
  282. }
  283. if (d.location) {
  284. query = query + d.location.lat + ',' + d.location.lng;
  285. }
  286. if (d.latitude && d.longitude) {
  287. query = query + d.latitude + ',' + d.longitude;
  288. }
  289. }
  290. return query;
  291. },
  292.  
  293. /**
  294. * 计算角度
  295. */
  296. rad(d) {
  297. return d * Math.PI / 180.0;
  298. },
  299. /**
  300. * 处理终点location数组
  301. * @return 返回终点数组
  302. */
  303. getEndLocation(location){
  304. var to = location.split(';');
  305. var endLocation = [];
  306. for (var i = 0; i < to.length; i++) {
  307. endLocation.push({
  308. lat: parseFloat(to[i].split(',')[0]),
  309. lng: parseFloat(to[i].split(',')[1])
  310. })
  311. }
  312. return endLocation;
  313. },
  314.  
  315. /**
  316. * 计算两点间直线距离
  317. * @param a 表示纬度差
  318. * @param b 表示经度差
  319. * @return 返回的是距离,单位m
  320. */
  321. getDistance(latFrom, lngFrom, latTo, lngTo) {
  322. var radLatFrom = this.rad(latFrom);
  323. var radLatTo = this.rad(latTo);
  324. var a = radLatFrom - radLatTo;
  325. var b = this.rad(lngFrom) - this.rad(lngTo);
  326. var distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLatFrom) * Math.cos(radLatTo) * Math.pow(Math.sin(b / 2), 2)));
  327. distance = distance * EARTH_RADIUS;
  328. distance = Math.round(distance * 10000) / 10000;
  329. return parseFloat(distance.toFixed(0));
  330. },
  331. /**
  332. * 使用微信接口进行定位
  333. */
  334. getWXLocation(success, fail, complete) {
  335. wx.getLocation({
  336. type: 'gcj02',
  337. success: success,
  338. fail: fail,
  339. complete: complete
  340. });
  341. },
  342.  
  343. /**
  344. * 获取location参数
  345. */
  346. getLocationParam(location) {
  347. if (typeof location == 'string') {
  348. var locationArr = location.split(',');
  349. if (locationArr.length === 2) {
  350. location = {
  351. latitude: location.split(',')[0],
  352. longitude: location.split(',')[1]
  353. };
  354. } else {
  355. location = {};
  356. }
  357. }
  358. return location;
  359. },
  360.  
  361. /**
  362. * 回调函数默认处理
  363. */
  364. polyfillParam(param) {
  365. param.success = param.success || function () { };
  366. param.fail = param.fail || function () { };
  367. param.complete = param.complete || function () { };
  368. },
  369.  
  370. /**
  371. * 验证param对应的key值是否为空
  372. *
  373. * @param {Object} param 接口参数
  374. * @param {String} key 对应参数的key
  375. */
  376. checkParamKeyEmpty(param, key) {
  377. if (!param[key]) {
  378. var errconf = this.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + key +'参数格式有误');
  379. param.fail(errconf);
  380. param.complete(errconf);
  381. return true;
  382. }
  383. return false;
  384. },
  385.  
  386. /**
  387. * 验证参数中是否存在检索词keyword
  388. *
  389. * @param {Object} param 接口参数
  390. */
  391. checkKeyword(param){
  392. return !this.checkParamKeyEmpty(param, 'keyword');
  393. },
  394.  
  395. /**
  396. * 验证location值
  397. *
  398. * @param {Object} param 接口参数
  399. */
  400. checkLocation(param) {
  401. var location = this.getLocationParam(param.location);
  402. if (!location || !location.latitude || !location.longitude) {
  403. var errconf = this.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' location参数格式有误');
  404. param.fail(errconf);
  405. param.complete(errconf);
  406. return false;
  407. }
  408. return true;
  409. },
  410.  
  411. /**
  412. * 构造错误数据结构
  413. * @param {Number} errCode 错误码
  414. * @param {Number} errMsg 错误描述
  415. */
  416. buildErrorConfig(errCode, errMsg) {
  417. return {
  418. status: errCode,
  419. message: errMsg
  420. };
  421. },
  422.  
  423. /**
  424. *
  425. * 数据处理函数
  426. * 根据传入参数不同处理不同数据
  427. * @param {String} feature 功能名称
  428. * search 地点搜索
  429. * suggest关键词提示
  430. * reverseGeocoder逆地址解析
  431. * geocoder地址解析
  432. * getCityList获取城市列表:父集
  433. * getDistrictByCityId获取区县列表:子集
  434. * calculateDistance距离计算
  435. * @param {Object} param 接口参数
  436. * @param {Object} data 数据
  437. */
  438. handleData(param,data,feature){
  439. if (feature == 'search') {
  440. var searchResult = data.data;
  441. var searchSimplify = [];
  442. for (var i = 0; i < searchResult.length; i++) {
  443. searchSimplify.push({
  444. id: searchResult[i].id || null,
  445. title: searchResult[i].title || null,
  446. latitude: searchResult[i].location && searchResult[i].location.lat || null,
  447. longitude: searchResult[i].location && searchResult[i].location.lng || null,
  448. address: searchResult[i].address || null,
  449. category: searchResult[i].category || null,
  450. tel: searchResult[i].tel || null,
  451. adcode: searchResult[i].ad_info && searchResult[i].ad_info.adcode || null,
  452. city: searchResult[i].ad_info && searchResult[i].ad_info.city || null,
  453. district: searchResult[i].ad_info && searchResult[i].ad_info.district || null,
  454. province: searchResult[i].ad_info && searchResult[i].ad_info.province || null
  455. })
  456. }
  457. param.success(data, {
  458. searchResult: searchResult,
  459. searchSimplify: searchSimplify
  460. })
  461. } else if (feature == 'suggest') {
  462. var suggestResult = data.data;
  463. var suggestSimplify = [];
  464. for (var i = 0; i < suggestResult.length; i++) {
  465. suggestSimplify.push({
  466. adcode: suggestResult[i].adcode || null,
  467. address: suggestResult[i].address || null,
  468. category: suggestResult[i].category || null,
  469. city: suggestResult[i].city || null,
  470. district: suggestResult[i].district || null,
  471. id: suggestResult[i].id || null,
  472. latitude: suggestResult[i].location && suggestResult[i].location.lat || null,
  473. longitude: suggestResult[i].location && suggestResult[i].location.lng || null,
  474. province: suggestResult[i].province || null,
  475. title: suggestResult[i].title || null,
  476. type: suggestResult[i].type || null
  477. })
  478. }
  479. param.success(data, {
  480. suggestResult: suggestResult,
  481. suggestSimplify: suggestSimplify
  482. })
  483. } else if (feature == 'reverseGeocoder') {
  484. var reverseGeocoderResult = data.result;
  485. var reverseGeocoderSimplify = {
  486. address: reverseGeocoderResult.address || null,
  487. latitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lat || null,
  488. longitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lng || null,
  489. adcode: reverseGeocoderResult.ad_info && reverseGeocoderResult.ad_info.adcode || null,
  490. city: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.city || null,
  491. district: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.district || null,
  492. nation: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.nation || null,
  493. province: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.province || null,
  494. street: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street || null,
  495. street_number: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street_number || null,
  496. recommend: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.recommend || null,
  497. rough: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.rough || null
  498. };
  499. if (reverseGeocoderResult.pois) {//判断是否返回周边poi
  500. var pois = reverseGeocoderResult.pois;
  501. var poisSimplify = [];
  502. for (var i = 0;i < pois.length;i++) {
  503. poisSimplify.push({
  504. id: pois[i].id || null,
  505. title: pois[i].title || null,
  506. latitude: pois[i].location && pois[i].location.lat || null,
  507. longitude: pois[i].location && pois[i].location.lng || null,
  508. address: pois[i].address || null,
  509. category: pois[i].category || null,
  510. adcode: pois[i].ad_info && pois[i].ad_info.adcode || null,
  511. city: pois[i].ad_info && pois[i].ad_info.city || null,
  512. district: pois[i].ad_info && pois[i].ad_info.district || null,
  513. province: pois[i].ad_info && pois[i].ad_info.province || null
  514. })
  515. }
  516. param.success(data,{
  517. reverseGeocoderResult: reverseGeocoderResult,
  518. reverseGeocoderSimplify: reverseGeocoderSimplify,
  519. pois: pois,
  520. poisSimplify: poisSimplify
  521. })
  522. } else {
  523. param.success(data, {
  524. reverseGeocoderResult: reverseGeocoderResult,
  525. reverseGeocoderSimplify: reverseGeocoderSimplify
  526. })
  527. }
  528. } else if (feature == 'geocoder') {
  529. var geocoderResult = data.result;
  530. var geocoderSimplify = {
  531. title: geocoderResult.title || null,
  532. latitude: geocoderResult.location && geocoderResult.location.lat || null,
  533. longitude: geocoderResult.location && geocoderResult.location.lng || null,
  534. adcode: geocoderResult.ad_info && geocoderResult.ad_info.adcode || null,
  535. province: geocoderResult.address_components && geocoderResult.address_components.province || null,
  536. city: geocoderResult.address_components && geocoderResult.address_components.city || null,
  537. district: geocoderResult.address_components && geocoderResult.address_components.district || null,
  538. street: geocoderResult.address_components && geocoderResult.address_components.street || null,
  539. street_number: geocoderResult.address_components && geocoderResult.address_components.street_number || null,
  540. level: geocoderResult.level || null
  541. };
  542. param.success(data,{
  543. geocoderResult: geocoderResult,
  544. geocoderSimplify: geocoderSimplify
  545. });
  546. } else if (feature == 'getCityList') {
  547. var provinceResult = data.result[0];
  548. var cityResult = data.result[1];
  549. var districtResult = data.result[2];
  550. param.success(data,{
  551. provinceResult: provinceResult,
  552. cityResult: cityResult,
  553. districtResult: districtResult
  554. });
  555. } else if (feature == 'getDistrictByCityId') {
  556. var districtByCity = data.result[0];
  557. param.success(data, districtByCity);
  558. } else if (feature == 'calculateDistance') {
  559. var calculateDistanceResult = data.result.elements;
  560. var distance = [];
  561. for (var i = 0; i < calculateDistanceResult.length; i++){
  562. distance.push(calculateDistanceResult[i].distance);
  563. }
  564. param.success(data, {
  565. calculateDistanceResult: calculateDistanceResult,
  566. distance: distance
  567. });
  568. } else if (feature == 'direction') {
  569. var direction = data.result.routes;
  570. param.success(data,direction);
  571. } else {
  572. param.success(data);
  573. }
  574. },
  575.  
  576. /**
  577. * 构造微信请求参数,公共属性处理
  578. *
  579. * @param {Object} param 接口参数
  580. * @param {Object} param 配置项
  581. * @param {String} feature 方法名
  582. */
  583. buildWxRequestConfig(param, options, feature) {
  584. var that = this;
  585. options.header = { "content-type": "application/json" };
  586. options.method = 'GET';
  587. options.success = function (res) {
  588. var data = res.data;
  589. if (data.status === 0) {
  590. that.handleData(param, data, feature);
  591. } else {
  592. param.fail(data);
  593. }
  594. };
  595. options.fail = function (res) {
  596. res.statusCode = ERROR_CONF.WX_ERR_CODE;
  597. param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
  598. };
  599. options.complete = function (res) {
  600. var statusCode = +res.statusCode;
  601. switch(statusCode) {
  602. case ERROR_CONF.WX_ERR_CODE: {
  603. param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
  604. break;
  605. }
  606. case ERROR_CONF.WX_OK_CODE: {
  607. var data = res.data;
  608. if (data.status === 0) {
  609. param.complete(data);
  610. } else {
  611. param.complete(that.buildErrorConfig(data.status, data.message));
  612. }
  613. break;
  614. }
  615. default:{
  616. param.complete(that.buildErrorConfig(ERROR_CONF.SYSTEM_ERR, ERROR_CONF.SYSTEM_ERR_MSG));
  617. }
  618.  
  619. }
  620. };
  621. return options;
  622. },
  623.  
  624. /**
  625. * 处理用户参数是否传入坐标进行不同的处理
  626. */
  627. locationProcess(param, locationsuccess, locationfail, locationcomplete) {
  628. var that = this;
  629. locationfail = locationfail || function (res) {
  630. res.statusCode = ERROR_CONF.WX_ERR_CODE;
  631. param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
  632. };
  633. locationcomplete = locationcomplete || function (res) {
  634. if (res.statusCode == ERROR_CONF.WX_ERR_CODE) {
  635. param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
  636. }
  637. };
  638. if (!param.location) {
  639. that.getWXLocation(locationsuccess, locationfail, locationcomplete);
  640. } else if (that.checkLocation(param)) {
  641. var location = Utils.getLocationParam(param.location);
  642. locationsuccess(location);
  643. }
  644. }
  645. };
  646.  
  647. class QQMapWX {
  648.  
  649. /**
  650. * 构造函数
  651. *
  652. * @param {Object} options 接口参数,key 为必选参数
  653. */
  654. constructor(options) {
  655. if (!options.key) {
  656. throw Error('key值不能为空');
  657. }
  658. this.key = options.key;
  659. };
  660.  
  661. /**
  662. * POI周边检索
  663. *
  664. * @param {Object} options 接口参数对象
  665. *
  666. * 参数对象结构可以参考
  667. * @see http://lbs.qq.com/webservice_v1/guide-search.html
  668. */
  669. search(options) {
  670. var that = this;
  671. options = options || {};
  672.  
  673. Utils.polyfillParam(options);
  674.  
  675. if (!Utils.checkKeyword(options)) {
  676. return;
  677. }
  678.  
  679. var requestParam = {
  680. keyword: options.keyword,
  681. orderby: options.orderby || '_distance',
  682. page_size: options.page_size || 10,
  683. page_index: options.page_index || 1,
  684. output: 'json',
  685. key: that.key
  686. };
  687.  
  688. if (options.address_format) {
  689. requestParam.address_format = options.address_format;
  690. }
  691.  
  692. if (options.filter) {
  693. requestParam.filter = options.filter;
  694. }
  695.  
  696. var distance = options.distance || "1000";
  697. var auto_extend = options.auto_extend || 1;
  698. var region = null;
  699. var rectangle = null;
  700.  
  701. //判断城市限定参数
  702. if (options.region) {
  703. region = options.region;
  704. }
  705.  
  706. //矩形限定坐标(暂时只支持字符串格式)
  707. if (options.rectangle) {
  708. rectangle = options.rectangle;
  709. }
  710.  
  711. var locationsuccess = function (result) {
  712. if (region && !rectangle) {
  713. //城市限定参数拼接
  714. requestParam.boundary = "region(" + region + "," + auto_extend + "," + result.latitude + "," + result.longitude + ")";
  715. if (options.sig) {
  716. requestParam.sig = Utils.getSig(requestParam, options.sig, 'search');
  717. }
  718. } else if (rectangle && !region) {
  719. //矩形搜索
  720. requestParam.boundary = "rectangle(" + rectangle + ")";
  721. if (options.sig) {
  722. requestParam.sig = Utils.getSig(requestParam, options.sig, 'search');
  723. }
  724. } else {
  725. requestParam.boundary = "nearby(" + result.latitude + "," + result.longitude + "," + distance + "," + auto_extend + ")";
  726. if (options.sig) {
  727. requestParam.sig = Utils.getSig(requestParam, options.sig, 'search');
  728. }
  729. }
  730. wx.request(Utils.buildWxRequestConfig(options, {
  731. url: URL_SEARCH,
  732. data: requestParam
  733. }, 'search'));
  734. };
  735. Utils.locationProcess(options, locationsuccess);
  736. };
  737.  
  738. /**
  739. * sug模糊检索
  740. *
  741. * @param {Object} options 接口参数对象
  742. *
  743. * 参数对象结构可以参考
  744. * http://lbs.qq.com/webservice_v1/guide-suggestion.html
  745. */
  746. getSuggestion(options) {
  747. var that = this;
  748. options = options || {};
  749. Utils.polyfillParam(options);
  750.  
  751. if (!Utils.checkKeyword(options)) {
  752. return;
  753. }
  754.  
  755. var requestParam = {
  756. keyword: options.keyword,
  757. region: options.region || '全国',
  758. region_fix: options.region_fix || 0,
  759. policy: options.policy || 0,
  760. page_size: options.page_size || 10,//控制显示条数
  761. page_index: options.page_index || 1,//控制页数
  762. get_subpois : options.get_subpois || 0,//返回子地点
  763. output: 'json',
  764. key: that.key
  765. };
  766. //长地址
  767. if (options.address_format) {
  768. requestParam.address_format = options.address_format;
  769. }
  770. //过滤
  771. if (options.filter) {
  772. requestParam.filter = options.filter;
  773. }
  774. //排序
  775. if (options.location) {
  776. var locationsuccess = function (result) {
  777. requestParam.location = result.latitude + ',' + result.longitude;
  778. if (options.sig) {
  779. requestParam.sig = Utils.getSig(requestParam, options.sig, 'suggest');
  780. }
  781. wx.request(Utils.buildWxRequestConfig(options, {
  782. url: URL_SUGGESTION,
  783. data: requestParam
  784. }, "suggest"));
  785. };
  786. Utils.locationProcess(options, locationsuccess);
  787. } else {
  788. if (options.sig) {
  789. requestParam.sig = Utils.getSig(requestParam, options.sig, 'suggest');
  790. }
  791. wx.request(Utils.buildWxRequestConfig(options, {
  792. url: URL_SUGGESTION,
  793. data: requestParam
  794. }, "suggest"));
  795. }
  796. };
  797.  
  798. /**
  799. * 逆地址解析
  800. *
  801. * @param {Object} options 接口参数对象
  802. *
  803. * 请求参数结构可以参考
  804. * http://lbs.qq.com/webservice_v1/guide-gcoder.html
  805. */
  806. reverseGeocoder(options) {
  807. var that = this;
  808. options = options || {};
  809. Utils.polyfillParam(options);
  810. var requestParam = {
  811. coord_type: options.coord_type || 5,
  812. get_poi: options.get_poi || 0,
  813. output: 'json',
  814. key: that.key
  815. };
  816. if (options.poi_options) {
  817. requestParam.poi_options = options.poi_options
  818. }
  819.  
  820. var locationsuccess = function (result) {
  821. requestParam.location = result.latitude + ',' + result.longitude;
  822. if (options.sig) {
  823. requestParam.sig = Utils.getSig(requestParam, options.sig, 'reverseGeocoder');
  824. }
  825. wx.request(Utils.buildWxRequestConfig(options, {
  826. url: URL_GET_GEOCODER,
  827. data: requestParam
  828. }, 'reverseGeocoder'));
  829. };
  830. Utils.locationProcess(options, locationsuccess);
  831. };
  832.  
  833. /**
  834. * 地址解析
  835. *
  836. * @param {Object} options 接口参数对象
  837. *
  838. * 请求参数结构可以参考
  839. * http://lbs.qq.com/webservice_v1/guide-geocoder.html
  840. */
  841. geocoder(options) {
  842. var that = this;
  843. options = options || {};
  844. Utils.polyfillParam(options);
  845.  
  846. if (Utils.checkParamKeyEmpty(options, 'address')) {
  847. return;
  848. }
  849.  
  850. var requestParam = {
  851. address: options.address,
  852. output: 'json',
  853. key: that.key
  854. };
  855.  
  856. //城市限定
  857. if (options.region) {
  858. requestParam.region = options.region;
  859. }
  860.  
  861. if (options.sig) {
  862. requestParam.sig = Utils.getSig(requestParam, options.sig, 'geocoder');
  863. }
  864.  
  865. wx.request(Utils.buildWxRequestConfig(options, {
  866. url: URL_GET_GEOCODER,
  867. data: requestParam
  868. },'geocoder'));
  869. };
  870.  
  871. /**
  872. * 获取城市列表
  873. *
  874. * @param {Object} options 接口参数对象
  875. *
  876. * 请求参数结构可以参考
  877. * http://lbs.qq.com/webservice_v1/guide-region.html
  878. */
  879. getCityList(options) {
  880. var that = this;
  881. options = options || {};
  882. Utils.polyfillParam(options);
  883. var requestParam = {
  884. output: 'json',
  885. key: that.key
  886. };
  887.  
  888. if (options.sig) {
  889. requestParam.sig = Utils.getSig(requestParam, options.sig, 'getCityList');
  890. }
  891.  
  892. wx.request(Utils.buildWxRequestConfig(options, {
  893. url: URL_CITY_LIST,
  894. data: requestParam
  895. },'getCityList'));
  896. };
  897.  
  898. /**
  899. * 获取对应城市ID的区县列表
  900. *
  901. * @param {Object} options 接口参数对象
  902. *
  903. * 请求参数结构可以参考
  904. * http://lbs.qq.com/webservice_v1/guide-region.html
  905. */
  906. getDistrictByCityId(options) {
  907. var that = this;
  908. options = options || {};
  909. Utils.polyfillParam(options);
  910.  
  911. if (Utils.checkParamKeyEmpty(options, 'id')) {
  912. return;
  913. }
  914.  
  915. var requestParam = {
  916. id: options.id || '',
  917. output: 'json',
  918. key: that.key
  919. };
  920.  
  921. if (options.sig) {
  922. requestParam.sig = Utils.getSig(requestParam, options.sig, 'getDistrictByCityId');
  923. }
  924.  
  925. wx.request(Utils.buildWxRequestConfig(options, {
  926. url: URL_AREA_LIST,
  927. data: requestParam
  928. },'getDistrictByCityId'));
  929. };
  930.  
  931. /**
  932. * 用于单起点到多终点的路线距离(非直线距离)计算:
  933. * 支持两种距离计算方式:步行和驾车。
  934. * 起点到终点最大限制直线距离10公里。
  935. *
  936. * 新增直线距离计算。
  937. *
  938. * @param {Object} options 接口参数对象
  939. *
  940. * 请求参数结构可以参考
  941. * http://lbs.qq.com/webservice_v1/guide-distance.html
  942. */
  943. calculateDistance(options) {
  944. var that = this;
  945. options = options || {};
  946. Utils.polyfillParam(options);
  947.  
  948. if (Utils.checkParamKeyEmpty(options, 'to')) {
  949. return;
  950. }
  951.  
  952. var requestParam = {
  953. mode: options.mode || 'walking',
  954. to: Utils.location2query(options.to),
  955. output: 'json',
  956. key: that.key
  957. };
  958.  
  959. if (options.from) {
  960. options.location = options.from;
  961. }
  962.  
  963. //计算直线距离
  964. if(requestParam.mode == 'straight'){
  965. var locationsuccess = function (result) {
  966. var locationTo = Utils.getEndLocation(requestParam.to);//处理终点坐标
  967. var data = {
  968. message:"query ok",
  969. result:{
  970. elements:[]
  971. },
  972. status:0
  973. };
  974. for (var i = 0; i < locationTo.length; i++) {
  975. data.result.elements.push({//将坐标存入
  976. distance: Utils.getDistance(result.latitude, result.longitude, locationTo[i].lat, locationTo[i].lng),
  977. duration:0,
  978. from:{
  979. lat: result.latitude,
  980. lng:result.longitude
  981. },
  982. to:{
  983. lat: locationTo[i].lat,
  984. lng: locationTo[i].lng
  985. }
  986. });
  987. }
  988. var calculateResult = data.result.elements;
  989. var distanceResult = [];
  990. for (var i = 0; i < calculateResult.length; i++) {
  991. distanceResult.push(calculateResult[i].distance);
  992. }
  993. return options.success(data,{
  994. calculateResult: calculateResult,
  995. distanceResult: distanceResult
  996. });
  997. };
  998.  
  999. Utils.locationProcess(options, locationsuccess);
  1000. } else {
  1001. var locationsuccess = function (result) {
  1002. requestParam.from = result.latitude + ',' + result.longitude;
  1003. if (options.sig) {
  1004. requestParam.sig = Utils.getSig(requestParam, options.sig, 'calculateDistance');
  1005. }
  1006. wx.request(Utils.buildWxRequestConfig(options, {
  1007. url: URL_DISTANCE,
  1008. data: requestParam
  1009. },'calculateDistance'));
  1010. };
  1011.  
  1012. Utils.locationProcess(options, locationsuccess);
  1013. }
  1014. };
  1015.  
  1016. /**
  1017. * 路线规划:
  1018. *
  1019. * @param {Object} options 接口参数对象
  1020. *
  1021. * 请求参数结构可以参考
  1022. * https://lbs.qq.com/webservice_v1/guide-road.html
  1023. */
  1024. direction(options) {
  1025. var that = this;
  1026. options = options || {};
  1027. Utils.polyfillParam(options);
  1028.  
  1029. if (Utils.checkParamKeyEmpty(options, 'to')) {
  1030. return;
  1031. }
  1032.  
  1033. var requestParam = {
  1034. output: 'json',
  1035. key: that.key
  1036. };
  1037.  
  1038. //to格式处理
  1039. if (typeof options.to == 'string') {
  1040. requestParam.to = options.to;
  1041. } else {
  1042. requestParam.to = options.to.latitude + ',' + options.to.longitude;
  1043. }
  1044. //初始化局部请求域名
  1045. var SET_URL_DIRECTION = null;
  1046. //设置默认mode属性
  1047. options.mode = options.mode || MODE.driving;
  1048.  
  1049. //设置请求域名
  1050. SET_URL_DIRECTION = URL_DIRECTION + options.mode;
  1051.  
  1052. if (options.from) {
  1053. options.location = options.from;
  1054. }
  1055.  
  1056. if (options.mode == MODE.driving) {
  1057. if (options.from_poi) {
  1058. requestParam.from_poi = options.from_poi;
  1059. }
  1060. if (options.heading) {
  1061. requestParam.heading = options.heading;
  1062. }
  1063. if (options.speed) {
  1064. requestParam.speed = options.speed;
  1065. }
  1066. if (options.accuracy) {
  1067. requestParam.accuracy = options.accuracy;
  1068. }
  1069. if (options.road_type) {
  1070. requestParam.road_type = options.road_type;
  1071. }
  1072. if (options.to_poi) {
  1073. requestParam.to_poi = options.to_poi;
  1074. }
  1075. if (options.from_track) {
  1076. requestParam.from_track = options.from_track;
  1077. }
  1078. if (options.waypoints) {
  1079. requestParam.waypoints = options.waypoints;
  1080. }
  1081. if (options.policy) {
  1082. requestParam.policy = options.policy;
  1083. }
  1084. if (options.plate_number) {
  1085. requestParam.plate_number = options.plate_number;
  1086. }
  1087. }
  1088.  
  1089. if (options.mode == MODE.transit) {
  1090. if (options.departure_time) {
  1091. requestParam.departure_time = options.departure_time;
  1092. }
  1093. if (options.policy) {
  1094. requestParam.policy = options.policy;
  1095. }
  1096. }
  1097.  
  1098. var locationsuccess = function (result) {
  1099. requestParam.from = result.latitude + ',' + result.longitude;
  1100. if (options.sig) {
  1101. requestParam.sig = Utils.getSig(requestParam, options.sig, 'direction',options.mode);
  1102. }
  1103. wx.request(Utils.buildWxRequestConfig(options, {
  1104. url: SET_URL_DIRECTION,
  1105. data: requestParam
  1106. }, 'direction'));
  1107. };
  1108.  
  1109. Utils.locationProcess(options, locationsuccess);
  1110. }
  1111. };
  1112.  
  1113. module.exports = QQMapWX;

uniapp 微信小程序 根据经纬度解析地址(腾讯地图)的更多相关文章

  1. 微信小程序-获取经纬度

    微信小程序-获取经纬度 最近公司新功能 要求在外的市场人员 发送位置信息回来. 用的还是微信小程序开发.... 微信小程序 提供一个接口 getLocation 这个接口反回来的位置 相对实际位置 相 ...

  2. 微信小程序 使用wxParse解析html

    微信小程序 加载 HTML 标签:https://blog.csdn.net/zclengendary/article/details/54312030 微信小程序 使用wxParse解析html:h ...

  3. uniapp 微信小程序 配置分享朋友和朋友圈

    uniapp 微信小程序 配置分享朋友和朋友圈 首先在小程序中配置微信分享,和微信朋友圈, onShareAppMessage, onShareTimeline 这两个API 和 onLoad 同级目 ...

  4. uni-app微信小程序开发之引入腾讯视频小程序播放插件

    登录微信小程序管理后台添加腾讯视频播放插件: 正式开始使用腾讯视频小程序插件之前需先在微信公众平台 -> 第三方设置 -> 插件管理处添加插件,如下图所示: 在uni-app中引入插件代码 ...

  5. 【重点突破】—— UniApp微信小程序开发教程学习Three

    一.实战 HBuilderX:在微信小程序中运行页面,需要设置->安全 开启微信小程序服务端口,HBuilder工具->设置->配置程序路径 网络请求.模板语法.打开页面.页面传参 ...

  6. 【重点突破】—— UniApp 微信小程序开发官网学习Two

    一.使用Vue.js注意事项 Vue.js在uni-app中使用的差异: 新增:uni-app除了支持Vue实例的生命周期,还支持应用启动.页面显示等生命周期 受限:发布到H5时支持所有vue的语法, ...

  7. 【重点突破】—— UniApp 微信小程序开发官网学习One

    一.初步认识 uni-app官网:https://uniapp.dcloud.io/component/README HBuilderX官方IDE下载地址: http://www.dcloud.io/ ...

  8. uni-app 微信小程序授权登录

    1.微信小程序 获取用户信息 与获取手机号 详细信息看官方公告:https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce8 ...

  9. 微信小程序获取经纬度所在城市

    小程序的wx.getLocation()获得是经纬度并不包含地名,所以要通过经纬度用相应的地图转换出地名(本文使用的是百度地图) // 获取坐标 onLoad: function (options)  ...

随机推荐

  1. PAT (Basic Level) Practice 1013 数素数 分数 20

    令 Pi​ 表示第 i 个素数.现任给两个正整数 M≤N≤104,请输出 PM​ 到 PN​ 的所有素数. 输入格式: 输入在一行中给出 M 和 N,其间以空格分隔. 输出格式: 输出从 PM​ 到  ...

  2. pgsql 的问题

    pgsql 怎么插入inet类型的数据?insert into table (remote_addr) values ( ?::INET); pgsql如何截取时间的精度 select  create ...

  3. 2022-08-25-cdn套中套

    layout: post cid: 19 title: cdn套中套 slug: 19 date: 2022/08/25 20:32:00 updated: 2022/08/26 11:20:20 s ...

  4. Vue3 SFC 和 TSX 方式调用子组件中的函数

    在开发中会遇到这样的需求:获取子组件的引用,并调用子组件中定义的方法.如封装了一个表单组件,在父组件中需要调用这个表单组件的引用,并调用这个表单组件的校验表单函数或重置表单函数.要实现这个功能,首先要 ...

  5. JVM、JDK、JRE你分的清吗

    JVM.JDK.JRE你分的清吗 前言 在我们学习Java的时候,就经常听到"需要安装JDK"."运行需要JRE"."JVM调优"等等,这里 ...

  6. JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环

    文章目录 1.流程控制语句 1.1 代码 1.2 测试结果 2.弹窗提示输入内容 2.1 代码 2.2 测试结果 3.条件分支语句 3.1 代码 3.2 测试结果 4.while和 do...whil ...

  7. 学习ASP.NET Core Blazor编程系列八——数据校验

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  8. 51单片机-独立按键控制led矩阵的左移和右移

    51单片机学习 独立按键 控制led灯光矩阵的左移和右移 开发板采用的是普中的A2学习开发板,具体的代码如下: typedef unsigned int u16; void delay(u16 tim ...

  9. 倍福Ads协议通信测试

    测试环境:vs2015 + TC31-Full-Setup.3.1.4022.30.exe 首先需要安装TC31-Full-Setup.3.1.4022.30.exe 本例子是用本机作测试,如果使用远 ...

  10. Perl printf 函数

    转载 Perl printf 函数