add-address.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
  2. // import parseAddress from "../../../../utils/address-parse";
  3. import AddressParse from "address-parse";
  4. import tickleBehavior, {
  5. getTickleContext,
  6. } from "../../../../core/behavior/tickle.behavior";
  7. import { getAddressDetailByPatientIdMethod } from "../../request";
  8. import { areaList } from "@vant/area-data";
  9. import {
  10. addAddressMethod,
  11. deleteAddressMethod,
  12. updateAddressMethod,
  13. } from "../../request";
  14. // module/diet/pages/delivery-address/delivery-address.ts
  15. // import parseAddress from '../../utils/smart-parse-address';
  16. Page({
  17. behaviors: [PageContainerBehavior, tickleBehavior],
  18. properties: {},
  19. data: {
  20. type: "",
  21. areaList,
  22. id: "",
  23. formData: {
  24. id: "",
  25. provinceCode: "",
  26. provinceName: "",
  27. cityCode: "",
  28. cityName: "",
  29. areaCode: "",
  30. areaName: "",
  31. liaison: "",
  32. phone: "",
  33. isDefault: "N",
  34. detailAddress: "",
  35. tagList: [],
  36. },
  37. region: "", // 展示在输入框的省市区字符串
  38. regionValue: [], // picker 选中的值
  39. regionPickerVisible: false,
  40. tagList: ["家", "公司", "学校"],
  41. selectedTag: "",
  42. focusName: false,
  43. focusPhone: false,
  44. focusDetail: false,
  45. smartPasteValue: "",
  46. smartPasteFocus: false,
  47. smartPastePlaceholder: "点击此处粘贴地址信息,可快速识别姓名、电话、地址",
  48. saving: false,
  49. },
  50. onClearDetail() {
  51. this.setData({ detail: "" });
  52. },
  53. onRegionConfirm(e: any) {
  54. this.setData({
  55. region:
  56. e.detail.values[0].name +
  57. ", " +
  58. e.detail.values[1].name +
  59. "," +
  60. e.detail.values[2].name,
  61. regionValue: e.detail.values,
  62. regionPickerVisible: false,
  63. "formData.provinceCode": e.detail.values[0].code,
  64. "formData.provinceName": e.detail.values[0].name,
  65. "formData.cityCode": e.detail.values[1].code,
  66. "formData.cityName": e.detail.values[1].name,
  67. "formData.areaCode": e.detail.values[2].code,
  68. "formData.areaName": e.detail.values[2].name,
  69. });
  70. },
  71. onRegionCancel(e: any) {
  72. this.setData({
  73. regionPickerVisible: false,
  74. });
  75. },
  76. // 输入框双向绑定
  77. onInput(e) {
  78. const { field } = e.currentTarget.dataset;
  79. // 这里 field 必须是 'name'、'phone'、'detail' 之一
  80. this.setData({ [field]: e.detail.value });
  81. },
  82. // 收货人
  83. onFocusInput() {
  84. this.setData({ focusName: true });
  85. },
  86. onBlurInput() {
  87. this.setData({ focusName: false });
  88. },
  89. // 手机号
  90. onFocusPhone() {
  91. this.setData({ focusPhone: true });
  92. },
  93. onBlurPhone() {
  94. this.setData({ focusPhone: false });
  95. },
  96. // 详细地址
  97. onFocusDetail() {
  98. this.setData({ focusDetail: true });
  99. },
  100. onBlurDetail() {
  101. this.setData({ focusDetail: false });
  102. },
  103. // 地区选择
  104. onShowRegionPicker() {
  105. this.setData({ regionPickerVisible: true });
  106. },
  107. // 标签选择
  108. onTagSelect(e) {
  109. const tag = e.currentTarget.dataset.tag;
  110. this.setData({ selectedTag: tag });
  111. },
  112. // 默认地址开关
  113. onDefaultChange(e) {
  114. if (e.detail.value) {
  115. this.setData({ "formData.isDefault": "Y" });
  116. } else {
  117. this.setData({ "formData.isDefault": "N" });
  118. }
  119. },
  120. // 添加地址
  121. async onAddress(params: any) {
  122. const res = await addAddressMethod(params);
  123. wx.showToast({ title: "添加成功", icon: "success" });
  124. },
  125. // 更新地址
  126. async onUpdate(params: any) {
  127. const res = await updateAddressMethod(params);
  128. wx.showToast({ title: "更新成功", icon: "success" });
  129. },
  130. // 保存
  131. async onSave() {
  132. if (this.data.saving) return; // 防重复
  133. try {
  134. const { liaison, phone, detailAddress } = this.data.formData;
  135. if (!liaison) {
  136. wx.showToast({ title: "收货人为空", icon: "none" });
  137. return;
  138. }
  139. if (!phone) {
  140. wx.showToast({ title: "手机号为空", icon: "none" });
  141. return;
  142. }
  143. // 手机号校验
  144. if (!/^1\d{10}$/.test(phone)) {
  145. wx.showToast({ title: "您填写的手机号有误", icon: "none" });
  146. return;
  147. }
  148. if (!this.data.region) {
  149. wx.showToast({ title: "所在地区为空", icon: "none" });
  150. return;
  151. }
  152. if (!detailAddress) {
  153. wx.showToast({ title: "详细地址为空", icon: "none" });
  154. return;
  155. }
  156. this.setData({
  157. "formData.tagList": [this.data.selectedTag],
  158. });
  159. this.setData({ saving: true });
  160. if (this.data.id) {
  161. this.data.formData.id = this.data.id;
  162. await this.onUpdate(this.data.formData);
  163. } else {
  164. await this.onAddress(this.data.formData);
  165. }
  166. this.setData({ saving: false });
  167. // 返回上一页(避免 redirectTo 造成 manage-address 重复入栈)
  168. const pages = getCurrentPages();
  169. if (pages.length > 1) {
  170. wx.navigateBack();
  171. } else {
  172. // 极端情况下没有上一页(比如被 reLaunch 打开),兜底回地址管理
  173. wx.redirectTo({
  174. url: "/module/article/pages/manage-address/manage-address",
  175. });
  176. }
  177. // 保存成功后可自动跳转或提示
  178. } catch (error: any) {
  179. wx.showToast({ title: error.errMsg, icon: "none" });
  180. this.setData({ saving: false }); // 无论成功失败都重置
  181. }
  182. },
  183. // 输入框聚焦
  184. onSmartPasteFocus() {
  185. this.setData({
  186. smartPasteFocus: true,
  187. smartPastePlaceholder:
  188. "粘贴或输入地址信息,如:张三,1801889955,上海市浦东新区浦东大道100号,可自动识别并填充文本",
  189. });
  190. },
  191. // 输入框失焦
  192. onSmartPasteBlur() {
  193. if (this.data.smartPasteValue) {
  194. this.setData({
  195. smartPasteFocus: true,
  196. });
  197. } else {
  198. this.setData({
  199. smartPasteFocus: false,
  200. smartPastePlaceholder:
  201. "点击此处粘贴地址信息,可快速识别姓名、电话、地址",
  202. });
  203. }
  204. },
  205. // 输入内容
  206. onSmartPasteInput(e: any) {
  207. this.setData({ smartPasteValue: e.detail.value });
  208. },
  209. // 清空
  210. onClearSmartPaste() {
  211. this.setData({ smartPasteValue: "" });
  212. },
  213. // 获取省市区编码
  214. getCodeByName(list: any, name: any) {
  215. for (const code in list) {
  216. if (list[code] === name) {
  217. return code;
  218. }
  219. }
  220. return "";
  221. },
  222. // 删除地址
  223. async onDelete() {
  224. const that = this;
  225. wx.showModal({
  226. title: "确定删除地址?",
  227. content: "删除后地址不可恢复",
  228. confirmColor: "#1D6FF6",
  229. cancelColor: "#999",
  230. cancelText: "取消",
  231. confirmText: "删除",
  232. success: async (res) => {
  233. if (res.confirm) {
  234. const res = await deleteAddressMethod(that.data.id);
  235. wx.showToast({ title: "删除成功", icon: "success" });
  236. wx.navigateBack();
  237. }
  238. },
  239. });
  240. },
  241. // 识别
  242. async onRecognizeSmartPaste() {
  243. let value = this.data.smartPasteValue.trim();
  244. if (!value) return;
  245. // 1. 统一分隔符,兼容各种输入
  246. value = value.replace(/[\n,、;;::]/g, ",").replace(/\s+/g, ",");
  247. // 2. 提取手机号
  248. const phoneMatch = value.match(/1\\d{10}/);
  249. let phone = phoneMatch ? phoneMatch[0] : "";
  250. if (phone) value = value.replace(phone, ",");
  251. // 3. 提取姓名(2-4个汉字,通常在最前面)
  252. let name = "";
  253. let nameMatch = value.match(/^[\\u4e00-\\u9fa5]{2,4}/);
  254. if (nameMatch) {
  255. name = nameMatch[0];
  256. value = value.replace(name, ",");
  257. }
  258. // 5. 用智能库进一步解析省市区
  259. let [result] = AddressParse.parse(this.data.smartPasteValue);
  260. result = {
  261. ...result,
  262. name: result.name,
  263. phone: result.phone || result.mobile,
  264. detailAddress: result.details,
  265. county: result.area,
  266. provinceCode: result.provinceCode,
  267. cityCode: result.cityCode,
  268. areaCode: result.areaCode,
  269. };
  270. // 6. 校验
  271. if (!result.name || !result.phone) {
  272. wx.showToast({ title: "请按照提示输入正确的地址信息", icon: "none" });
  273. return;
  274. }
  275. if (!/^1\\d{10}$/.test(result.phone)) {
  276. wx.showToast({ title: "请按照提示输入正确的地址信息", icon: "none" });
  277. }
  278. // 7. 省市区识别
  279. const { province, city, county } = result;
  280. const { province_list, city_list, county_list } = this.data.areaList;
  281. if (province && city) {
  282. const provinceCode = this.getCodeByName(province_list, province);
  283. const cityCode = this.getCodeByName(city_list, city);
  284. const areaCode = this.getCodeByName(county_list, county);
  285. this.setData({
  286. "formData.provinceName": province,
  287. "formData.cityName": city,
  288. "formData.areaName": county || "",
  289. "formData.provinceCode": provinceCode,
  290. "formData.cityCode": cityCode,
  291. "formData.areaCode": areaCode,
  292. "formData.detailAddress": result.details,
  293. region: `${province},${city}${county ? "," + county : ""}`,
  294. });
  295. } else {
  296. wx.showToast({ title: "请按照提示输入正确的地址信息", icon: "none" });
  297. }
  298. // 其他字段(如姓名、手机号、详细地址)可以单独 setData,不受省市影响
  299. this.setData({
  300. "formData.liaison": result.name,
  301. "formData.phone": result.phone || result.mobile,
  302. });
  303. wx.showToast({ title: "已识别", icon: "success" });
  304. },
  305. // 获取地址详情
  306. async getAddressDetail(id: string) {
  307. try {
  308. const res = await getAddressDetailByPatientIdMethod(id);
  309. if (res && res.data) {
  310. this.setData({
  311. "formData.isDefault": res.data.isDefault,
  312. "formData.liaison": res.data.liaison,
  313. "formData.phone": res.data.phone,
  314. "formData.detailAddress": res.data.detailAddress,
  315. "formData.provinceName": res.data.provinceName,
  316. "formData.cityName": res.data.cityName,
  317. "formData.areaName": res.data.areaName,
  318. "formData.provinceCode": res.data.provinceCode,
  319. "formData.cityCode": res.data.cityCode,
  320. "formData.areaCode": res.data.areaCode,
  321. region: `${res.data.provinceName} ${res.data.cityName} ${res.data.areaName}`,
  322. });
  323. // 标签
  324. this.setData({
  325. "formData.tagList": res.data.tagList,
  326. });
  327. this.setData({
  328. selectedTag: res.data.tagList[0] || "",
  329. });
  330. }
  331. } catch (error: any) {
  332. getTickleContext.call(this).showWarnMessage(error.errMsg);
  333. }
  334. },
  335. onLoad(options) {
  336. if (options.id) {
  337. this.setData({ id: options.id });
  338. this.getAddressDetail(options.id);
  339. }
  340. if (options.type) {
  341. this.setData({ type: options.type });
  342. }
  343. if (options.imported) {
  344. const imported = JSON.parse(decodeURIComponent(options.imported));
  345. const { provinceName, cityName, areaName } = imported;
  346. const { province_list, city_list, county_list } = this.data.areaList;
  347. if (provinceName && cityName) {
  348. const provinceCode = this.getCodeByName(province_list, provinceName);
  349. const cityCode = this.getCodeByName(city_list, cityName);
  350. const areaCode = this.getCodeByName(county_list, areaName);
  351. this.setData({
  352. "formData.isDefault": imported.isDefault || "N",
  353. "formData.liaison": imported.liaison,
  354. "formData.phone": imported.phone,
  355. "formData.detailAddress": imported.detailAddress,
  356. "formData.provinceName": imported.provinceName,
  357. "formData.cityName": imported.cityName,
  358. "formData.areaName": imported.areaName,
  359. "formData.provinceCode": provinceCode,
  360. "formData.cityCode": cityCode,
  361. "formData.areaCode": areaCode,
  362. region: imported.region,
  363. });
  364. this.setData({
  365. selectedTag: "",
  366. type: "imported",
  367. });
  368. }
  369. }
  370. },
  371. });