secondShareGoods.html 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
  6. <meta name="format-detection" content="telephone=no">
  7. <title>商品分享</title>
  8. <style>
  9. :root {
  10. --orange: #F58220;
  11. --price-red: #E62E2E;
  12. --tag-bg: #fff5e9;
  13. --tag-text: #b05b28;
  14. --text: #1a1a1a;
  15. --text-secondary: #999999;
  16. --safe-bottom: env(safe-area-inset-bottom, 0px);
  17. }
  18. * {
  19. margin: 0;
  20. padding: 0;
  21. box-sizing: border-box;
  22. }
  23. html {
  24. font-size: 16px;
  25. -webkit-tap-highlight-color: transparent;
  26. overflow-x: hidden;
  27. }
  28. body {
  29. font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", sans-serif;
  30. background: #fff;
  31. color: var(--text);
  32. min-height: 100vh;
  33. padding-bottom: calc(100px + var(--safe-bottom));
  34. }
  35. .fab-wrap {
  36. position: fixed;
  37. left: 0;
  38. right: 0;
  39. bottom: 0;
  40. z-index: 200;
  41. padding: 12px 24px calc(12px + var(--safe-bottom));
  42. background: linear-gradient(to top, rgba(255, 255, 255, 0.98) 70%, transparent);
  43. pointer-events: none;
  44. }
  45. .fab-dock__slot {
  46. width: 100%;
  47. max-width: 198px;
  48. height: 48px;
  49. margin: 0 auto;
  50. pointer-events: auto;
  51. }
  52. #openApp img {
  53. display: block;
  54. width: 100%;
  55. height: 48px;
  56. object-fit: contain;
  57. pointer-events: none;
  58. }
  59. #launch-btn {
  60. display: none;
  61. width: 100%;
  62. height: 48px;
  63. min-height: 48px;
  64. border-radius: 24px;
  65. overflow: hidden;
  66. opacity: 1;
  67. }
  68. /* 仅分享卡片等场景展示开放标签;复制链接进入见 .wx-copy-link-entry */
  69. body.is-wechat.wx-jssdk-ready.wx-open-tag-scene #launch-btn {
  70. display: block;
  71. }
  72. body.is-wechat.wx-jssdk-ready.wx-open-tag-scene #openApp {
  73. display: none !important;
  74. }
  75. .fab-wrap .fab {
  76. pointer-events: auto;
  77. }
  78. #openAppToast {
  79. display: none;
  80. position: fixed;
  81. left: 16px;
  82. right: 16px;
  83. bottom: calc(72px + var(--safe-bottom));
  84. z-index: 10001;
  85. padding: 10px 14px;
  86. font-size: 13px;
  87. line-height: 1.45;
  88. color: #fff;
  89. text-align: center;
  90. background: rgba(0, 0, 0, 0.78);
  91. border-radius: 8px;
  92. pointer-events: none;
  93. word-break: break-all;
  94. }
  95. #openApp {
  96. touch-action: manipulation;
  97. }
  98. .fab {
  99. display: flex;
  100. align-items: center;
  101. justify-content: center;
  102. gap: 10px;
  103. width: 100%;
  104. max-width: 198px;
  105. margin: 0 auto;
  106. height: 48px;
  107. border: none;
  108. border-radius: 24px;
  109. background: #F47D1F;
  110. color: #fff;
  111. font-size: 16px;
  112. font-weight: 600;
  113. box-shadow: 0 4px 16px rgba(245, 130, 32, 0.45);
  114. cursor: pointer;
  115. }
  116. .fab__logo {
  117. width: 28px;
  118. height: 28px;
  119. flex-shrink: 0;
  120. }
  121. .home-indicator {
  122. height: 5px;
  123. background: #000;
  124. border-radius: 3px;
  125. width: 134px;
  126. margin: 8px auto 4px;
  127. opacity: 0.2;
  128. }
  129. /* 顶部大图 + 叠字 */
  130. .goods-hero {
  131. position: relative;
  132. width: 100%;
  133. min-height: 58vh;
  134. max-height: 520px;
  135. background: #e8e8e8 center / cover no-repeat;
  136. overflow: hidden;
  137. }
  138. .goods-hero__img {
  139. position: absolute;
  140. inset: 0;
  141. width: 100%;
  142. height: 100%;
  143. object-fit: cover;
  144. display: block;
  145. }
  146. .goods-hero__shade {
  147. position: absolute;
  148. inset: 0;
  149. background: linear-gradient(
  150. 180deg,
  151. rgba(0, 0, 0, 0.35) 0%,
  152. rgba(0, 0, 0, 0.1) 38%,
  153. rgba(0, 0, 0, 0.45) 100%
  154. );
  155. pointer-events: none;
  156. }
  157. .goods-hero__inner {
  158. position: relative;
  159. z-index: 1;
  160. min-height: 58vh;
  161. max-height: 520px;
  162. padding: calc(12px + env(safe-area-inset-top, 0px)) 16px 28px;
  163. display: flex;
  164. flex-direction: column;
  165. justify-content: space-between;
  166. align-items: flex-start;
  167. }
  168. .goods-hero__user {
  169. display: flex;
  170. align-items: center;
  171. gap: 10px;
  172. }
  173. .goods-hero__avatar {
  174. width: 40px;
  175. height: 40px;
  176. border-radius: 50%;
  177. object-fit: cover;
  178. flex-shrink: 0;
  179. background: rgba(255, 255, 255, 0.3);
  180. }
  181. .goods-hero__user-meta {
  182. display: flex;
  183. flex-direction: column;
  184. gap: 2px;
  185. }
  186. .goods-hero__name {
  187. font-size: 15px;
  188. font-weight: 600;
  189. color: #fff;
  190. text-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
  191. }
  192. .goods-hero__time {
  193. font-size: 12px;
  194. color: rgba(255, 255, 255, 0.82);
  195. text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  196. }
  197. .goods-hero__slogan {
  198. margin-top: auto;
  199. padding-bottom: 4px;
  200. }
  201. .goods-hero__slogan-line {
  202. font-size: 26px;
  203. font-weight: 800;
  204. line-height: 1.25;
  205. color: #fff;
  206. letter-spacing: 0.02em;
  207. text-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
  208. }
  209. /* 白色内容区 */
  210. .goods-panel {
  211. background: #fff;
  212. border-radius: 16px 16px 0 0;
  213. margin-top: -16px;
  214. position: relative;
  215. z-index: 2;
  216. padding: 20px 16px 24px;
  217. box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.06);
  218. }
  219. .goods-panel__row-price {
  220. display: flex;
  221. align-items: flex-end;
  222. justify-content: space-between;
  223. gap: 12px;
  224. margin-bottom: 14px;
  225. }
  226. .goods-panel__price {
  227. display: flex;
  228. align-items: baseline;
  229. gap: 2px;
  230. color: var(--price-red);
  231. font-weight: 800;
  232. line-height: 1;
  233. }
  234. .goods-panel__price-yen {
  235. font-size: 18px;
  236. }
  237. .goods-panel__price-num {
  238. font-size: 28px;
  239. letter-spacing: -0.02em;
  240. }
  241. .goods-panel__dist {
  242. display: inline-flex;
  243. align-items: center;
  244. gap: 4px;
  245. font-size: 12px;
  246. color: var(--text-secondary);
  247. flex-shrink: 0;
  248. padding-bottom: 2px;
  249. }
  250. .goods-panel__dist svg,
  251. .goods-panel__dist img {
  252. width: 14px;
  253. height: 14px;
  254. opacity: 0.85;
  255. display: block;
  256. flex-shrink: 0;
  257. object-fit: contain;
  258. }
  259. .goods-panel__title {
  260. font-size: 17px;
  261. font-weight: 700;
  262. line-height: 1.45;
  263. color: var(--text);
  264. margin-bottom: 16px;
  265. }
  266. .goods-panel__row-stats {
  267. display: flex;
  268. align-items: center;
  269. justify-content: space-between;
  270. gap: 12px;
  271. margin-bottom: 16px;
  272. }
  273. .goods-panel__stats {
  274. display: flex;
  275. align-items: center;
  276. gap: 18px;
  277. }
  278. .goods-panel__stat {
  279. display: inline-flex;
  280. align-items: center;
  281. gap: 5px;
  282. font-size: 14px;
  283. color: var(--text-secondary);
  284. }
  285. .goods-panel__stat svg {
  286. width: 16px;
  287. height: 16px;
  288. flex-shrink: 0;
  289. }
  290. .goods-panel__stat img {
  291. width: 16px;
  292. height: 16px;
  293. flex-shrink: 0;
  294. display: block;
  295. object-fit: contain;
  296. }
  297. .goods-panel__share {
  298. display: inline-flex;
  299. align-items: center;
  300. gap: 4px;
  301. padding: 0;
  302. border: none;
  303. background: none;
  304. font-size: 14px;
  305. color: var(--text-secondary);
  306. cursor: pointer;
  307. -webkit-tap-highlight-color: transparent;
  308. }
  309. .goods-panel__share svg {
  310. width: 18px;
  311. height: 18px;
  312. }
  313. .goods-panel__tags {
  314. display: flex;
  315. flex-wrap: wrap;
  316. align-items: center;
  317. gap: 8px;
  318. }
  319. .goods-tag {
  320. display: inline-block;
  321. padding: 2px 8px;
  322. border-radius: 4px;
  323. font-size: 12px;
  324. font-weight: 400;
  325. line-height: 1.35;
  326. background: #fff5e9;
  327. }
  328. .goods-tag--topic {
  329. color: #b05b28;
  330. }
  331. .goods-tag--label {
  332. color: #333333;
  333. }
  334. /* 留言 */
  335. .goods-comments {
  336. margin-top: 20px;
  337. padding-top: 20px;
  338. border-top: 1px solid #eee;
  339. }
  340. .goods-comments__head {
  341. display: flex;
  342. align-items: center;
  343. justify-content: space-between;
  344. margin-bottom: 18px;
  345. }
  346. .goods-comments__title {
  347. font-size: 17px;
  348. font-weight: 700;
  349. color: var(--text);
  350. }
  351. .goods-comments__total {
  352. display: inline-flex;
  353. align-items: center;
  354. gap: 4px;
  355. font-size: 14px;
  356. color: #151515;
  357. }
  358. .goods-comments__total svg {
  359. width: 18px;
  360. height: 18px;
  361. opacity: 0.85;
  362. }
  363. .goods-comments__list {
  364. list-style: none;
  365. }
  366. .goods-comments__empty {
  367. list-style: none;
  368. padding: 24px 12px;
  369. text-align: center;
  370. font-size: 14px;
  371. color: var(--text-secondary);
  372. }
  373. .goods-cmt {
  374. padding-bottom: 18px;
  375. margin-bottom: 18px;
  376. border-bottom: 1px solid #eee;
  377. }
  378. .goods-cmt:last-child {
  379. margin-bottom: 0;
  380. padding-bottom: 0;
  381. border-bottom: none;
  382. }
  383. .goods-cmt__row {
  384. display: flex;
  385. align-items: flex-start;
  386. gap: 12px;
  387. }
  388. .goods-cmt__avatar {
  389. width: 40px;
  390. height: 40px;
  391. border-radius: 50%;
  392. object-fit: cover;
  393. background: #e8e8e8;
  394. flex-shrink: 0;
  395. }
  396. .goods-cmt--nested .goods-cmt__avatar {
  397. width: 32px;
  398. height: 32px;
  399. }
  400. .goods-cmt__body {
  401. flex: 1;
  402. min-width: 0;
  403. }
  404. .goods-cmt__name {
  405. font-size: 15px;
  406. font-weight: 700;
  407. color: var(--text);
  408. line-height: 1.3;
  409. }
  410. .goods-cmt__time {
  411. display: block;
  412. margin-top: 2px;
  413. font-size: 12px;
  414. color: var(--text-secondary);
  415. line-height: 1.35;
  416. }
  417. .goods-cmt__text {
  418. margin-top: 10px;
  419. font-size: 15px;
  420. line-height: 1.5;
  421. color: var(--text);
  422. word-break: break-word;
  423. }
  424. .goods-cmt__actions {
  425. display: flex;
  426. align-items: center;
  427. gap: 20px;
  428. margin-top: 12px;
  429. }
  430. .goods-cmt__action {
  431. display: inline-flex;
  432. align-items: center;
  433. gap: 4px;
  434. padding: 0;
  435. border: none;
  436. background: none;
  437. font-size: 13px;
  438. color: var(--text-secondary);
  439. cursor: pointer;
  440. -webkit-tap-highlight-color: transparent;
  441. }
  442. .goods-cmt__action svg {
  443. width: 16px;
  444. height: 16px;
  445. flex-shrink: 0;
  446. opacity: 0.88;
  447. }
  448. .goods-cmt__thread {
  449. margin-top: 14px;
  450. padding-left: 52px;
  451. }
  452. .goods-cmt__replies-inner.is-collapsed {
  453. display: none;
  454. }
  455. .goods-cmt__thread-bar {
  456. display: flex;
  457. align-items: center;
  458. justify-content: space-between;
  459. width: 100%;
  460. margin-top: 10px;
  461. padding: 10px 12px;
  462. border: none;
  463. border-radius: 8px;
  464. background: #f5f5f5;
  465. font-size: 13px;
  466. color: #888;
  467. cursor: pointer;
  468. -webkit-tap-highlight-color: transparent;
  469. }
  470. .goods-cmt__thread-bar-right {
  471. display: inline-flex;
  472. align-items: center;
  473. gap: 4px;
  474. color: var(--text-secondary);
  475. }
  476. .goods-cmt__thread-chevron {
  477. width: 14px;
  478. height: 14px;
  479. transition: transform 0.2s ease;
  480. flex-shrink: 0;
  481. }
  482. .goods-cmt__thread-bar.is-collapsed .goods-cmt__thread-chevron {
  483. transform: rotate(180deg);
  484. }
  485. .goods-cmt--nested {
  486. padding-bottom: 0;
  487. margin-bottom: 0;
  488. border-bottom: none;
  489. }
  490. .goods-cmt--nested .goods-cmt__text {
  491. margin-top: 8px;
  492. }
  493. </style>
  494. </head>
  495. <body>
  496. <section class="goods-hero" id="goodsHero" aria-label="商品配图">
  497. <img class="goods-hero__img" id="goodsHeroImg" src="" alt="">
  498. <div class="goods-hero__shade" aria-hidden="true"></div>
  499. <div class="goods-hero__inner">
  500. <div class="goods-hero__user">
  501. <img class="goods-hero__avatar" id="goodsUserAvatar" src="images/demouser.png" alt="">
  502. <div class="goods-hero__user-meta">
  503. <span class="goods-hero__name" id="goodsUserName">维尼</span>
  504. <span class="goods-hero__time" id="goodsTimeAgo">11小时前</span>
  505. </div>
  506. </div>
  507. <div class="goods-hero__slogan" id="goodsHeroSloganWrap" style="display:none;">
  508. <div class="goods-hero__slogan-line" id="goodsSlogan1"></div>
  509. <div class="goods-hero__slogan-line" id="goodsSlogan2"></div>
  510. </div>
  511. </div>
  512. </section>
  513. <div class="goods-panel">
  514. <div class="goods-panel__row-price">
  515. <div class="goods-panel__price">
  516. <span class="goods-panel__price-yen">¥</span>
  517. <span class="goods-panel__price-num" id="goodsPrice">690.00</span>
  518. </div>
  519. <div class="goods-panel__dist">
  520. <img src="images/juli.svg" alt="" width="14" height="14" decoding="async" aria-hidden="true">
  521. <span id="goodsDistance">距离 2.5km</span>
  522. </div>
  523. </div>
  524. <h1 class="goods-panel__title" id="goodsTitle">HCK哈士奇BC-330RDE烤漆复古冰箱家用大容量单门冰柜高颜值网红</h1>
  525. <div class="goods-panel__row-stats">
  526. <div class="goods-panel__stats">
  527. <span class="goods-panel__stat goods-panel__stat--hot" id="goodsHotStat">
  528. <img src="images/huo.svg" alt="" width="16" height="16" decoding="async" aria-hidden="true">
  529. <span id="goodsHotCount" style="color:#151515;">510</span>
  530. </span>
  531. <span class="goods-panel__stat">
  532. <img src="images/xing.svg" alt="" width="16" height="16" decoding="async" aria-hidden="true">
  533. <span id="goodsFavCount" style="color:#151515;">260</span>
  534. </span>
  535. </div>
  536. </div>
  537. <div class="goods-panel__tags" id="goodsTags"></div>
  538. <section class="goods-comments" id="goodsComments" aria-label="留言">
  539. <div class="goods-comments__head">
  540. <h2 class="goods-comments__title">留言</h2>
  541. <div class="goods-comments__total">
  542. <img src="images/liuyan.svg" alt="" width="16" height="16" decoding="async" aria-hidden="true">
  543. <span id="goodsCommentTotal">24</span>
  544. </div>
  545. </div>
  546. <ul class="goods-comments__list" id="goodsCommentList"></ul>
  547. </section>
  548. </div>
  549. <div id="openAppToast" role="status" aria-live="polite"></div>
  550. <div class="fab-wrap">
  551. <div class="fab-dock__slot">
  552. <button type="button" class="fab" id="openApp">
  553. <img src="images/uBtn.png" alt="APP内打开" decoding="async">
  554. </button>
  555. <wx-open-launch-app id="launch-btn" appid="wxf5f1efe3a9f5012e" extinfo="">
  556. <script type="text/wxtag-template">
  557. <style>
  558. .wx-open-app-btn {
  559. display: block;
  560. width: 100%;
  561. height: 48px;
  562. margin: 0;
  563. padding: 0;
  564. border: none;
  565. border-radius: 24px;
  566. background: #F47D1F;
  567. box-shadow: 0 4px 16px rgba(245, 130, 32, 0.45);
  568. cursor: pointer;
  569. overflow: hidden;
  570. -webkit-tap-highlight-color: transparent;
  571. }
  572. .wx-open-app-btn img {
  573. display: block;
  574. width: 100%;
  575. height: 48px;
  576. object-fit: contain;
  577. opacity: 1;
  578. pointer-events: none;
  579. -webkit-user-drag: none;
  580. }
  581. </style>
  582. <button class="wx-open-app-btn" aria-label="APP内打开">
  583. <img src="https://test.ailien.shop/h5/HBuilderProjects/images/uBtn.png" alt="APP内打开" width="198" height="48" />
  584. </button>
  585. </script>
  586. </wx-open-launch-app>
  587. </div>
  588. <div class="home-indicator" aria-hidden="true"></div>
  589. </div>
  590. <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
  591. <script>
  592. (function () {
  593. 'use strict';
  594. /**
  595. * 进入页仅用 querySecondGoodsDetailWithOutJWT 判断商品是否存在(不用其 data 渲染页面):
  596. * POST {API_BASE_SECOND}/recommend/querySecondGoodsDetailWithOutJWT
  597. * body:goodsId、longitude、latitude、phoneId
  598. * 不存在/已下架/删除或 msg=暂无承载数据 → shareUndefined;存在 → applyFromUrl + 留言
  599. */
  600. /**
  601. * 唤起 App 落地:二手商品详情(与 pages.json 路径一致)
  602. */
  603. var APP_ANDROID_PACKAGE = 'com.alien.Udianzaizhe';
  604. var APP_IOS_URL_SCHEME = 'shopro://';
  605. var APP_UNI_STORE_PATH = 'pages/secondHandTransactions/pages/detail/index';
  606. var API_BASE = 'https://test.ailien.shop/alienStore';
  607. /**
  608. * 微信 JSSDK — 与 shareIndex.html 一致
  609. * POST {API_BASE}/wx/getWxConfig,body 传 url(当前页完整地址,不含 #)
  610. */
  611. var WECHAT_MP_APP_ID = 'wx412792c77f47babd';
  612. var WECHAT_OPEN_APP_ID = 'wxf5f1efe3a9f5012e';
  613. var WECHAT_GET_WX_CONFIG_PATH = '/wx/getWxConfig';
  614. var H5_PAGE_BASE_FALLBACK = 'https://test.ailien.shop/h5/HBuilderProjects/';
  615. var WX_GET_CONFIG_SIGN_URL = H5_PAGE_BASE_FALLBACK + 'secondShareGoods.html';
  616. var WECHAT_JS_SAFE_HOSTS = ['uat.ailien.shop', 'test.ailien.shop'];
  617. var weChatJssdkConfigured = false;
  618. var wxConfigSignRetriedBaseUrl = false;
  619. var wxInitLastError = '';
  620. var wxJssdkInitPromise = null;
  621. function showDownloadTip() {
  622. var msg = '请到应用商店下载';
  623. if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
  624. uni.showToast({ title: msg, icon: 'none', duration: 2500 });
  625. } else {
  626. window.alert(msg);
  627. }
  628. }
  629. function mergeSearchAndHashParams() {
  630. var params = new URLSearchParams();
  631. function ingestAppend(querySlice) {
  632. if (!querySlice) return;
  633. var p = new URLSearchParams(querySlice);
  634. p.forEach(function (val, key) {
  635. params.append(key, val);
  636. });
  637. }
  638. function ingestSet(querySlice) {
  639. if (!querySlice) return;
  640. var p = new URLSearchParams(querySlice);
  641. p.forEach(function (val, key) {
  642. params.set(key, val);
  643. });
  644. }
  645. if (location.search && location.search.length > 1) {
  646. ingestAppend(location.search.slice(1));
  647. }
  648. var hash = location.hash || '';
  649. var qi = hash.indexOf('?');
  650. if (qi >= 0) {
  651. ingestSet(hash.slice(qi + 1));
  652. }
  653. return params;
  654. }
  655. function q(name) {
  656. var v = mergeSearchAndHashParams().get(name);
  657. return v == null ? '' : String(v);
  658. }
  659. function buildAppOpenQueryStringMerged() {
  660. var params = mergeSearchAndHashParams();
  661. var gid = params.get('goodsId') || params.get('id') || '';
  662. if (gid && !params.has('goodsId')) {
  663. params.set('goodsId', gid);
  664. }
  665. var sid = params.get('storeId') || params.get('id') || '';
  666. if (sid && !params.has('storeId')) {
  667. params.set('storeId', sid);
  668. }
  669. var qsOut = params.toString();
  670. return qsOut ? ('?' + qsOut) : '';
  671. }
  672. function getSecondGoodsAppUniPath() {
  673. return String(APP_UNI_STORE_PATH || 'pages/secondHandTransactions/pages/detail/index').replace(
  674. /^\//,
  675. ''
  676. );
  677. }
  678. function buildAppDeepLink() {
  679. var path = getSecondGoodsAppUniPath();
  680. var s = buildAppOpenQueryStringMerged();
  681. var root = APP_IOS_URL_SCHEME.replace(/\/$/, '');
  682. if (!s) {
  683. return root + '/' + path;
  684. }
  685. return root + '/' + path + s;
  686. }
  687. /** Uni 路由串(无 shopro://),供 wx-open-launch-app extinfo */
  688. function buildAppUniPageLaunchUrl() {
  689. var path = getSecondGoodsAppUniPath();
  690. var qs = buildAppOpenQueryStringMerged().replace(/^\?/, '');
  691. return qs ? path + '?' + qs : path;
  692. }
  693. /**
  694. * wx-open-launch-app extinfo:pages/secondHandTransactions/pages/detail/index?goodsId=…
  695. */
  696. function buildWeChatLaunchExtinfo() {
  697. var uniPage = buildAppUniPageLaunchUrl();
  698. if (uniPage.length <= 1024) return uniPage;
  699. var params = mergeSearchAndHashParams();
  700. var mini = new URLSearchParams();
  701. var goodsId = params.get('goodsId') || params.get('id') || '';
  702. if (goodsId) mini.set('goodsId', goodsId);
  703. var path = getSecondGoodsAppUniPath();
  704. var shortPage = path + '?' + mini.toString();
  705. if (shortPage.length <= 1024) return shortPage;
  706. return shortPage.slice(0, 1024);
  707. }
  708. function isWeChatInAppBrowser() {
  709. return /MicroMessenger/i.test(navigator.userAgent || '');
  710. }
  711. function getWxShareEntryFrom() {
  712. var from = q('from');
  713. return from ? String(from).trim().toLowerCase() : '';
  714. }
  715. /**
  716. * wx-open-launch-app 仅在分享卡片等场景可用;复制链接打开会 launch:fail。
  717. * 勿在 getWxConfig 前改地址栏(iOS 微信按进入时 url 验签,replaceState 会导致签名无效)。
  718. */
  719. function isWxOpenLaunchAppSceneSupported() {
  720. if (!isWeChatInAppBrowser()) return false;
  721. if (readQueryParam('wxOpenTag') === '1' || readQueryParam('wxForceOpenTag') === '1') {
  722. return true;
  723. }
  724. var from = getWxShareEntryFrom();
  725. return (
  726. from === 'singlemessage' ||
  727. from === 'groupmessage' ||
  728. from === 'timeline'
  729. );
  730. }
  731. function applyWxOpenLaunchSceneBodyClass() {
  732. if (!isWeChatInAppBrowser()) return;
  733. var ok = isWxOpenLaunchAppSceneSupported();
  734. document.body.classList.toggle('wx-open-tag-scene', ok);
  735. document.body.classList.toggle('wx-copy-link-entry', !ok);
  736. }
  737. function logWxEntryDiagnostics() {
  738. if (!isWeChatInAppBrowser()) return;
  739. console.log('[wx-entry]', {
  740. entryFrom: getWxShareEntryFrom() || '(无,多为复制链接进入)',
  741. openLaunchTagScene: isWxOpenLaunchAppSceneSupported(),
  742. signUrl: getWxConfigSignUrl()
  743. });
  744. }
  745. function showWxCopyLinkEntryTip() {
  746. showFabToast(
  747. '当前为复制链接进入,微信不支持直接打开 App。请使用 App「分享到微信」发送卡片后,点卡片进入。'
  748. );
  749. }
  750. function isIOSWeChatBrowser() {
  751. var ua = navigator.userAgent || '';
  752. return /MicroMessenger/i.test(ua) && /iPhone|iPad|iPod/i.test(ua);
  753. }
  754. function readQueryParam(name) {
  755. return q(name);
  756. }
  757. function isWxDebugOn() {
  758. return readQueryParam('wxDebug') === '1';
  759. }
  760. function isWxForceDebug() {
  761. return readQueryParam('wxForce') === '1';
  762. }
  763. function isWxConfigOnClickDebug() {
  764. return readQueryParam('wxConfigOnClick') === '1';
  765. }
  766. function isWxPcAutoDebugHost() {
  767. var h = (location.hostname || '').toLowerCase();
  768. if (h === 'localhost' || h === '127.0.0.1') return true;
  769. for (var i = 0; i < WECHAT_JS_SAFE_HOSTS.length; i++) {
  770. if (WECHAT_JS_SAFE_HOSTS[i] === h) return true;
  771. }
  772. return false;
  773. }
  774. function isWxPcBrowser() {
  775. return !isWeChatInAppBrowser();
  776. }
  777. function shouldInitWeChatJssdkOnLoad() {
  778. if (isWeChatInAppBrowser()) return true;
  779. return isWxForceDebug() || isWxPcAutoDebugHost();
  780. }
  781. function shouldFetchWxConfig(fromUserClick) {
  782. if (isWeChatInAppBrowser()) return true;
  783. if (fromUserClick) return true;
  784. return isWxForceDebug() || isWxPcAutoDebugHost();
  785. }
  786. function getWxHtmlUrl() {
  787. var forced = String(q('wxSignUrl') || '').trim();
  788. if (forced) return forced.split('#')[0];
  789. return String(location.href || '').split('#')[0];
  790. }
  791. function getWxSignPageUrlForApi() {
  792. var htmlUrl = getWxHtmlUrl();
  793. if (htmlUrl && /^https?:\/\//i.test(htmlUrl)) return htmlUrl;
  794. return WX_GET_CONFIG_SIGN_URL;
  795. }
  796. function getWxHtmlUrlBase() {
  797. var htmlUrl = getWxSignPageUrlForApi();
  798. try {
  799. var u = new URL(htmlUrl);
  800. return u.origin + u.pathname;
  801. } catch (eU) {
  802. return WX_GET_CONFIG_SIGN_URL;
  803. }
  804. }
  805. function getWxConfigSignUrl() {
  806. if (String(q('wxSignBaseOnly') || '') === '1') return getWxHtmlUrlBase();
  807. return getWxSignPageUrlForApi();
  808. }
  809. function getWxGetConfigApiUrl() {
  810. return API_BASE.replace(/\/$/, '') + WECHAT_GET_WX_CONFIG_PATH;
  811. }
  812. function buildWxGetConfigRequestBody(htmlUrl) {
  813. return {
  814. url: String(htmlUrl || '').split('#')[0].trim()
  815. };
  816. }
  817. function resolveWxConfigAppIdFromSignData(d) {
  818. if (!d || typeof d !== 'object') return '';
  819. var mp =
  820. d.mpAppId ||
  821. d.mpAppid ||
  822. d.officialAppId ||
  823. d.gzhAppId ||
  824. d.serviceAppId;
  825. if (mp != null && String(mp).trim() !== '') return String(mp).trim();
  826. var fromQuery = String(q('wxMpAppId') || WECHAT_MP_APP_ID || '').trim();
  827. if (fromQuery) return fromQuery;
  828. var raw = d.appId || d.appid || d.wxAppId;
  829. return raw != null && String(raw).trim() !== '' ? String(raw).trim() : '';
  830. }
  831. function normalizeWxJssdkSignPayload(res, signUrlUsed) {
  832. if (!res || typeof res !== 'object') return null;
  833. var d = res.data != null && typeof res.data === 'object' ? res.data : res;
  834. if (!d || typeof d !== 'object') return null;
  835. var appId = resolveWxConfigAppIdFromSignData(d);
  836. var timestamp = d.timestamp != null ? d.timestamp : d.timeStamp;
  837. var nonceStr =
  838. d.nonceStr != null && String(d.nonceStr) !== ''
  839. ? d.nonceStr
  840. : d.noncestr != null && String(d.noncestr) !== ''
  841. ? d.noncestr
  842. : d.nonce;
  843. var signature = d.signature || d.sign;
  844. if (!appId || timestamp == null || nonceStr == null || nonceStr === '' || !signature) {
  845. return null;
  846. }
  847. return {
  848. appId: String(appId),
  849. timestamp: Number(timestamp),
  850. nonceStr: String(nonceStr),
  851. signature: String(signature),
  852. signUrl: String(signUrlUsed || '')
  853. .split('#')[0]
  854. .trim()
  855. };
  856. }
  857. function getWxErrMsg(err) {
  858. if (!err) return '';
  859. if (err.errMsg) return String(err.errMsg);
  860. if (typeof err === 'string') return err;
  861. try {
  862. return JSON.stringify(err);
  863. } catch (e) {
  864. return String(err);
  865. }
  866. }
  867. function formatWxConfigErrorTip(err, signPageUrl) {
  868. var errMsg = getWxErrMsg(err);
  869. var tip = '微信 JSSDK 配置失败';
  870. if (/invalid signature/i.test(errMsg)) {
  871. return (
  872. tip +
  873. ':签名无效。url=' +
  874. (signPageUrl || getWxHtmlUrl()) +
  875. ';appId=' +
  876. WECHAT_MP_APP_ID
  877. );
  878. }
  879. if (/require\s*subscribe/i.test(errMsg)) {
  880. return tip + ':' + errMsg + '(服务号需用户已关注)';
  881. }
  882. return errMsg ? tip + ':' + errMsg : tip;
  883. }
  884. function setWxInitError(msg) {
  885. wxInitLastError = String(msg || '').trim();
  886. if (wxInitLastError) console.warn('[wx]', wxInitLastError);
  887. }
  888. function requestWeChatJssdkSignAndConfig(htmlUrlOptional) {
  889. var htmlUrl = String(
  890. htmlUrlOptional != null ? htmlUrlOptional : getWxConfigSignUrl()
  891. )
  892. .split('#')[0]
  893. .trim();
  894. if (!htmlUrl || !/^https?:\/\//i.test(htmlUrl)) {
  895. htmlUrl = WX_GET_CONFIG_SIGN_URL;
  896. }
  897. if (isWxDebugOn()) {
  898. try {
  899. window.alert('htmlUrl(签名用):\n' + htmlUrl);
  900. } catch (eDbg) {}
  901. }
  902. var requestUrl = getWxGetConfigApiUrl();
  903. var requestBody = buildWxGetConfigRequestBody(htmlUrl);
  904. console.log('[wx] htmlUrl=', htmlUrl);
  905. console.log('[wx] POST getWxConfig →', requestUrl, requestBody);
  906. return fetch(requestUrl, {
  907. method: 'POST',
  908. mode: 'cors',
  909. credentials: 'omit',
  910. headers: {
  911. Accept: 'application/json',
  912. 'Content-Type': 'application/json;charset=UTF-8'
  913. },
  914. body: JSON.stringify(requestBody)
  915. })
  916. .then(function (r) {
  917. if (r.ok) return r.json();
  918. return r
  919. .text()
  920. .catch(function () {
  921. return '';
  922. })
  923. .then(function (text) {
  924. var hint = '';
  925. try {
  926. var j = JSON.parse(text);
  927. hint = j.msg || j.message || '';
  928. } catch (eP) {
  929. if (text) hint = text.slice(0, 120);
  930. }
  931. throw new Error('getWxConfig HTTP ' + r.status + (hint ? ':' + hint : ''));
  932. });
  933. })
  934. .then(function (res) {
  935. if (res && res.code != null) {
  936. var c = Number(res.code);
  937. if (c !== 200 && c !== 0 && res.success !== true) {
  938. throw new Error(res.msg || res.message || 'getWxConfig code ' + c);
  939. }
  940. }
  941. var sign = normalizeWxJssdkSignPayload(res, htmlUrl);
  942. if (!sign) {
  943. console.warn('[wx] getWxConfig 响应字段不全', res, 'htmlUrl=', htmlUrl);
  944. throw new Error(
  945. 'getWxConfig 缺少 appId/timestamp/nonceStr/signature(见控制台)'
  946. );
  947. }
  948. if (sign.appId !== WECHAT_MP_APP_ID) {
  949. console.warn(
  950. '[wx] 后端 appId=' + sign.appId + ',期望服务号 ' + WECHAT_MP_APP_ID
  951. );
  952. }
  953. if (typeof wx === 'undefined') {
  954. if (isWxPcBrowser()) {
  955. console.log('[wx] PC getWxConfig 成功(无 jweixin)', sign);
  956. return true;
  957. }
  958. setWxInitError('jweixin.js 未加载');
  959. return false;
  960. }
  961. return applyWxConfigFromSign(sign, htmlUrl);
  962. });
  963. }
  964. function applyWxConfigFromSign(sign, htmlUrl) {
  965. var wxConfigParams = {
  966. debug: isWxDebugOn(),
  967. appId: String(sign.appId),
  968. timestamp: sign.timestamp,
  969. nonceStr: String(sign.nonceStr),
  970. signature: String(sign.signature),
  971. jsApiList: [],
  972. openTagList: ['wx-open-launch-app']
  973. };
  974. return new Promise(function (resolve) {
  975. wx.config(wxConfigParams);
  976. wx.ready(function () {
  977. weChatJssdkConfigured = true;
  978. document.body.classList.add('wx-jssdk-ready');
  979. applyWxOpenLaunchSceneBodyClass();
  980. if (isWxOpenLaunchAppSceneSupported()) {
  981. refreshWxLaunchTagAttrs();
  982. }
  983. console.log('[wx.config] ready, htmlUrl=', htmlUrl);
  984. resolve(true);
  985. });
  986. wx.error(function (err) {
  987. weChatJssdkConfigured = false;
  988. document.body.classList.remove('wx-jssdk-ready');
  989. wxJssdkInitPromise = null;
  990. var errMsg = getWxErrMsg(err);
  991. setWxInitError(formatWxConfigErrorTip(err, htmlUrl));
  992. if (isWxDebugOn()) window.alert(wxInitLastError);
  993. console.warn('[wx.config]', errMsg, 'htmlUrl=', htmlUrl);
  994. var baseUrl = getWxHtmlUrlBase();
  995. var fullUrl = getWxSignPageUrlForApi();
  996. if (
  997. !/invalid signature/i.test(errMsg) ||
  998. wxConfigSignRetriedBaseUrl ||
  999. baseUrl === fullUrl ||
  1000. htmlUrl === baseUrl
  1001. ) {
  1002. resolve(false);
  1003. return;
  1004. }
  1005. wxConfigSignRetriedBaseUrl = true;
  1006. requestWeChatJssdkSignAndConfig(baseUrl).then(resolve);
  1007. });
  1008. });
  1009. }
  1010. function refreshWxLaunchTagAttrs() {
  1011. var tag = document.getElementById('launch-btn');
  1012. if (!tag) return;
  1013. try {
  1014. tag.setAttribute('appid', WECHAT_OPEN_APP_ID);
  1015. tag.setAttribute('extinfo', buildWeChatLaunchExtinfo());
  1016. } catch (eA) {}
  1017. }
  1018. function bindWeChatLaunchTagEvents() {
  1019. var tag = document.getElementById('launch-btn');
  1020. if (!tag || tag._wxLaunchBound) return;
  1021. tag._wxLaunchBound = true;
  1022. refreshWxLaunchTagAttrs();
  1023. tag.addEventListener('launch', function () {
  1024. console.log('[wx-open-launch-app] launch ok');
  1025. // showFabToast('正在打开 U店在哪…');
  1026. });
  1027. tag.addEventListener('error', function (e) {
  1028. var detail = e && e.detail;
  1029. var errMsg =
  1030. detail && detail.errMsg
  1031. ? String(detail.errMsg)
  1032. : detail && detail.errmsg
  1033. ? String(detail.errmsg)
  1034. : '';
  1035. console.warn('[wx-open-launch-app]', detail);
  1036. showAppOpenFailTip(
  1037. '请确复制链接进入U店在哪'
  1038. );
  1039. });
  1040. }
  1041. function initWeChatOpenLaunchApp(fromUserClick) {
  1042. if (!shouldFetchWxConfig(!!fromUserClick)) {
  1043. return Promise.resolve(false);
  1044. }
  1045. if (wxJssdkInitPromise && !fromUserClick) return wxJssdkInitPromise;
  1046. wxConfigSignRetriedBaseUrl = false;
  1047. wxInitLastError = '';
  1048. var htmlUrl = getWxConfigSignUrl();
  1049. bindWeChatLaunchTagEvents();
  1050. wxJssdkInitPromise = requestWeChatJssdkSignAndConfig(htmlUrl)
  1051. .then(function (ok) {
  1052. if (ok === true) return true;
  1053. if (!wxInitLastError) {
  1054. setWxInitError('wx.config 失败,可加 ?wxDebug=1');
  1055. }
  1056. return false;
  1057. })
  1058. .catch(function (e) {
  1059. var msg = e && e.message ? e.message : 'getWxConfig 请求失败';
  1060. setWxInitError(msg);
  1061. console.warn('[wx] init failed', msg, 'htmlUrl=', htmlUrl);
  1062. if (isWeChatInAppBrowser() && isWxDebugOn()) window.alert(msg);
  1063. return false;
  1064. })
  1065. .finally(function () {
  1066. if (!weChatJssdkConfigured) wxJssdkInitPromise = null;
  1067. });
  1068. return wxJssdkInitPromise;
  1069. }
  1070. function scheduleWeChatJssdkBootstrap() {
  1071. if (!shouldInitWeChatJssdkOnLoad()) return;
  1072. var attempts = 0;
  1073. function tick() {
  1074. attempts += 1;
  1075. if (weChatJssdkConfigured) return;
  1076. initWeChatOpenLaunchApp();
  1077. if (!weChatJssdkConfigured && attempts < 8 && typeof wx === 'undefined') {
  1078. setTimeout(tick, 400);
  1079. }
  1080. }
  1081. /* iOS 微信分享卡片可能稍后才写入 from 等参数,过早验签会 invalid signature */
  1082. if (isIOSWeChatBrowser()) {
  1083. setTimeout(tick, 350);
  1084. } else {
  1085. tick();
  1086. }
  1087. }
  1088. function showFabToast(msg, ms) {
  1089. var tip = String(msg || '').trim();
  1090. if (!tip) return;
  1091. var el = document.getElementById('openAppToast');
  1092. if (el) {
  1093. el.textContent = tip;
  1094. el.style.display = 'block';
  1095. if (showFabToast._t) clearTimeout(showFabToast._t);
  1096. showFabToast._t = setTimeout(function () {
  1097. el.style.display = 'none';
  1098. }, ms || 2800);
  1099. }
  1100. console.log('[openApp]', tip);
  1101. }
  1102. function showAppOpenFailTip(msg) {
  1103. var tip = msg || '未能打开 App,请确认已安装最新版「U店在哪」。';
  1104. if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
  1105. uni.showToast({ title: tip, icon: 'none', duration: 2800 });
  1106. } else if (isWeChatInAppBrowser()) {
  1107. window.alert(tip);
  1108. } else {
  1109. console.warn('[openApp]', tip);
  1110. }
  1111. }
  1112. function tryFetchWxConfigOnPcClick() {
  1113. if (!isWxPcBrowser()) return Promise.resolve(false);
  1114. showFabToast('正在请求 getWxConfig…');
  1115. wxJssdkInitPromise = null;
  1116. return initWeChatOpenLaunchApp(true).then(function (ok) {
  1117. if (ok) {
  1118. showFabToast(
  1119. weChatJssdkConfigured
  1120. ? 'getWxConfig 成功,wx.config 已就绪'
  1121. : 'getWxConfig 成功'
  1122. );
  1123. } else {
  1124. showFabToast(wxInitLastError || 'getWxConfig 失败');
  1125. }
  1126. return ok;
  1127. });
  1128. }
  1129. function launchAppDeepLink(deepLink) {
  1130. try {
  1131. var a = document.createElement('a');
  1132. a.href = deepLink;
  1133. a.setAttribute('target', '_self');
  1134. document.body.appendChild(a);
  1135. a.click();
  1136. document.body.removeChild(a);
  1137. } catch (e1) {}
  1138. try {
  1139. window.location.href = deepLink;
  1140. } catch (e2) {}
  1141. }
  1142. function tryOpenHBuilderApp() {
  1143. if (isWeChatInAppBrowser()) return;
  1144. tryFetchWxConfigOnPcClick().then(function () {
  1145. if (isWxConfigOnClickDebug() && !isWxForceDebug() && !isWxPcAutoDebugHost()) {
  1146. return;
  1147. }
  1148. tryOpenHBuilderAppViaScheme();
  1149. });
  1150. }
  1151. function tryOpenHBuilderAppViaScheme() {
  1152. // showFabToast('正在打开 U店在哪…');
  1153. var deepLink = buildAppDeepLink();
  1154. if (typeof plus !== 'undefined' && plus.runtime) {
  1155. var installed = null;
  1156. try {
  1157. if (typeof plus.runtime.isApplicationExist === 'function') {
  1158. installed = plus.runtime.isApplicationExist({
  1159. pname: APP_ANDROID_PACKAGE,
  1160. action: APP_IOS_URL_SCHEME
  1161. });
  1162. }
  1163. } catch (e) {
  1164. console.warn(e);
  1165. }
  1166. if (installed === false) {
  1167. showDownloadTip();
  1168. return;
  1169. }
  1170. try {
  1171. plus.runtime.openURL(deepLink);
  1172. } catch (e2) {
  1173. console.warn(e2);
  1174. showDownloadTip();
  1175. }
  1176. return;
  1177. }
  1178. var t0 = Date.now();
  1179. var done = false;
  1180. function finish() {
  1181. if (done) return;
  1182. done = true;
  1183. document.removeEventListener('visibilitychange', onVis);
  1184. window.removeEventListener('pagehide', onHide);
  1185. }
  1186. function onVis() {
  1187. if (document.visibilityState === 'hidden') finish();
  1188. }
  1189. function onHide() {
  1190. finish();
  1191. }
  1192. document.addEventListener('visibilitychange', onVis);
  1193. window.addEventListener('pagehide', onHide);
  1194. try {
  1195. launchAppDeepLink(deepLink);
  1196. } catch (e3) {
  1197. finish();
  1198. showDownloadTip();
  1199. return;
  1200. }
  1201. window.setTimeout(function () {
  1202. finish();
  1203. }, 2600);
  1204. }
  1205. function tryDecode(s) {
  1206. s = String(s || '');
  1207. if (!s) return '';
  1208. try {
  1209. return decodeURIComponent(s.replace(/\+/g, ' '));
  1210. } catch (e) {
  1211. return s;
  1212. }
  1213. }
  1214. var DEFAULT_HERO = '';
  1215. /** 二手商品接口前缀;可用 URL 参数 alienSecondBase / secondApiBase 覆盖 */
  1216. function resolveSecondApiBase() {
  1217. var custom = (q('alienSecondBase') || q('secondApiBase') || '').trim();
  1218. if (custom) {
  1219. return custom.replace(/\/+$/, '');
  1220. }
  1221. var host = (location.hostname || '').toLowerCase();
  1222. if (host === 'test.ailien.shop' || host === 'uat.ailien.shop') {
  1223. return 'https://test.ailien.shop/alienSecond';
  1224. }
  1225. return 'http://120.26.186.130:8000/alienSecond';
  1226. }
  1227. var API_BASE_SECOND = resolveSecondApiBase();
  1228. /** 与 querySecondGoodsDetailWithOutJWT 表单一致;优先地址栏,否则默认坐标 */
  1229. var DEFAULT_REQUEST_LONGITUDE =
  1230. (q('longitude') || q('lon') || q('jingdu') || '').trim() || '121.662527';
  1231. var DEFAULT_REQUEST_LATITUDE =
  1232. (q('latitude') || q('lat') || q('weidu') || '').trim() || '38.925754';
  1233. var COMMENT_SOURCE_TYPE = 4;
  1234. function apiGet(path) {
  1235. return fetch(API_BASE + path, {
  1236. method: 'GET',
  1237. mode: 'cors',
  1238. credentials: 'omit',
  1239. headers: { Accept: 'application/json' }
  1240. }).then(function (res) {
  1241. if (!res.ok) throw new Error('HTTP ' + res.status);
  1242. return res.json();
  1243. });
  1244. }
  1245. function apiPostJson(path, body) {
  1246. return fetch(API_BASE + path, {
  1247. method: 'POST',
  1248. mode: 'cors',
  1249. credentials: 'omit',
  1250. headers: {
  1251. Accept: 'application/json',
  1252. 'Content-Type': 'application/json'
  1253. },
  1254. body: JSON.stringify(body || {})
  1255. }).then(function (res) {
  1256. if (!res.ok) throw new Error('HTTP ' + res.status);
  1257. return res.json();
  1258. });
  1259. }
  1260. function pick(d, keys) {
  1261. if (!d || typeof d !== 'object') return null;
  1262. for (var i = 0; i < keys.length; i++) {
  1263. var k = keys[i];
  1264. if (d[k] != null && d[k] !== '') return d[k];
  1265. }
  1266. return null;
  1267. }
  1268. function isApiOk(res) {
  1269. if (!res || typeof res !== 'object') return false;
  1270. if (res.success === false) return false;
  1271. var c = res.code;
  1272. return c === 200 || c === '200' || Number(c) === 200;
  1273. }
  1274. /** 二手详情存在性接口:兼容 code 0 / success:true(与 alienSecond 常见返回一致) */
  1275. function isSecondGoodsExistOk(res) {
  1276. if (!res || typeof res !== 'object') return false;
  1277. if (res.success === false) return false;
  1278. if (res.success === true) return true;
  1279. var c = res.code;
  1280. if (c === 200 || c === '200' || Number(c) === 200) return true;
  1281. if (c === 0 || c === '0' || Number(c) === 0) return true;
  1282. return false;
  1283. }
  1284. function pickSecondGoodsDetailData(res) {
  1285. if (!res || typeof res !== 'object') return null;
  1286. var d = res.data != null ? res.data : res.result;
  1287. if (d == null) return null;
  1288. if (Array.isArray(d)) {
  1289. return d.length && typeof d[0] === 'object' ? d[0] : null;
  1290. }
  1291. if (typeof d !== 'object') return null;
  1292. var inner =
  1293. d.secondGoodsDetail ||
  1294. d.goodsDetail ||
  1295. d.goodsInfo ||
  1296. d.detail ||
  1297. d.record;
  1298. if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
  1299. return inner;
  1300. }
  1301. return d;
  1302. }
  1303. /** 详情里是否有可识别的商品实体(有则视为存在,不依赖 goodsStatus 数值) */
  1304. function hasSecondGoodsDetailPayload(d) {
  1305. if (!d || typeof d !== 'object') return false;
  1306. var id = d.goodsId != null ? d.goodsId : d.id;
  1307. if (id != null && String(id).trim() !== '' && String(id).trim() !== '0') {
  1308. return true;
  1309. }
  1310. var name = d.goodsName || d.title || d.secondGoodsTitle || d.name;
  1311. if (name != null && String(name).trim() !== '') return true;
  1312. var img =
  1313. d.coverUrl ||
  1314. d.mainImage ||
  1315. d.goodsImage ||
  1316. d.firstImage ||
  1317. (Array.isArray(d.goodsImageList) && d.goodsImageList[0]);
  1318. if (img != null && String(img).trim() !== '') return true;
  1319. return Object.keys(d).length > 0;
  1320. }
  1321. function isDetailDataExplicitlyDeleted(d) {
  1322. if (!d || typeof d !== 'object') return false;
  1323. if (d.deleteFlag === 1 || d.deleteFlag === '1' || Number(d.deleteFlag) === 1) {
  1324. return true;
  1325. }
  1326. if (d.deleted === true || d.isDelete === 1 || d.isDelete === '1') {
  1327. return true;
  1328. }
  1329. return false;
  1330. }
  1331. /** 与店铺 getClientStoreDetail 一致:无商品时 code 仍可能为 200 */
  1332. function isNoCarryingDataMsg(res) {
  1333. if (!res || typeof res !== 'object') return false;
  1334. var msg = res.msg != null ? String(res.msg).trim() : '';
  1335. return msg === '暂无承载数据';
  1336. }
  1337. function isMsgIndicateGoodsGone(msg) {
  1338. msg = String(msg || '').trim();
  1339. if (!msg) return false;
  1340. if (msg === '暂无承载数据') return true;
  1341. return /已删除|已下架|已卖出|商品不存在|不存在该|查无此商品|找不到该商品|商品不可用/.test(
  1342. msg
  1343. );
  1344. }
  1345. function fmtPriceNum(v) {
  1346. var n = v != null ? Number(v) : NaN;
  1347. return !isNaN(n) ? n.toFixed(2) : String(v != null ? v : '');
  1348. }
  1349. /** kind: topic(#话题 棕橙)| label(分类名 深灰,不带 #) */
  1350. function appendGoodsTag(mount, raw, kind) {
  1351. if (!mount || raw == null) return;
  1352. var s = String(raw).trim();
  1353. if (!s) return;
  1354. var span = document.createElement('span');
  1355. span.className =
  1356. 'goods-tag ' + (kind === 'label' ? 'goods-tag--label' : 'goods-tag--topic');
  1357. if (kind === 'label') {
  1358. span.textContent = s.replace(/^#+/, '');
  1359. } else {
  1360. span.textContent = s.indexOf('#') === 0 ? s : '#' + s;
  1361. }
  1362. mount.appendChild(span);
  1363. }
  1364. function renderGoodsDetail(d) {
  1365. if (!d || typeof d !== 'object') return;
  1366. var title = pick(d, ['goodsName', 'title', 'secondGoodsTitle', 'name']);
  1367. if (title) {
  1368. var el = document.getElementById('goodsTitle');
  1369. if (el) el.textContent = String(title);
  1370. }
  1371. var price = pick(d, ['price', 'goodsPrice', 'salePrice', 'currentPrice', 'amount']);
  1372. if (price != null) {
  1373. var pe = document.getElementById('goodsPrice');
  1374. if (pe) pe.textContent = fmtPriceNum(price);
  1375. }
  1376. var dist = pick(d, ['dist', 'distance', 'distanceText', 'distanceDesc', 'distanceDescription', 'distanceKm']);
  1377. if (dist != null) {
  1378. var dt = String(dist).trim();
  1379. if (dt) {
  1380. if (!/^距离/.test(dt)) dt = '距离 ' + dt;
  1381. if (!/km$/i.test(dt) && !/千米/.test(dt) && !/米/.test(dt) && !/\s*m\s*$/i.test(dt)) {
  1382. dt = dt.replace(/\s+$/, '') + 'km';
  1383. }
  1384. var de = document.getElementById('goodsDistance');
  1385. if (de) de.textContent = dt;
  1386. }
  1387. }
  1388. var img =
  1389. pick(d, ['coverUrl', 'mainImage', 'goodsImage', 'firstImage', 'bannerUrl']) ||
  1390. (Array.isArray(d.goodsImageList) && d.goodsImageList[0]) ||
  1391. (Array.isArray(d.imageList) && d.imageList[0]);
  1392. if (img) {
  1393. var hi = document.getElementById('goodsHeroImg');
  1394. if (hi) {
  1395. hi.src = String(img);
  1396. hi.alt = '商品图';
  1397. }
  1398. }
  1399. var nick = pick(d, ['publishUserNick', 'userNickName', 'nickName', 'userName', 'publisherName']);
  1400. if (nick) {
  1401. var ne = document.getElementById('goodsUserName');
  1402. if (ne) ne.textContent = String(nick);
  1403. }
  1404. var av = pick(d, ['publishUserAvatar', 'userAvatar', 'userHead', 'avatar', 'headImg']);
  1405. if (av) {
  1406. var ae = document.getElementById('goodsUserAvatar');
  1407. if (ae) ae.src = String(av);
  1408. }
  1409. var tm = pick(d, ['timeAgo', 'releaseTime', 'publishTime', 'createTime', 'publishTimeStr']);
  1410. if (tm) {
  1411. var te = document.getElementById('goodsTimeAgo');
  1412. if (te) te.textContent = String(tm);
  1413. }
  1414. var s1 = pick(d, ['promotionLineOne', 'promotionTitle', 'subtitle', 'slogan1', 'line1']);
  1415. var s2 = pick(d, ['promotionLineTwo', 'subTitle', 'slogan2', 'line2']);
  1416. var wrap = document.getElementById('goodsHeroSloganWrap');
  1417. var e1 = document.getElementById('goodsSlogan1');
  1418. var e2 = document.getElementById('goodsSlogan2');
  1419. if (wrap && e1 && e2) {
  1420. if (s1 || s2) {
  1421. if (s1) e1.textContent = String(s1);
  1422. if (s2) e2.textContent = String(s2);
  1423. wrap.style.display = '';
  1424. } else {
  1425. wrap.style.display = 'none';
  1426. }
  1427. }
  1428. var hotStat = document.getElementById('goodsHotStat');
  1429. var cs = pick(d, ['collectStatus']);
  1430. if (hotStat) {
  1431. var hideHot =
  1432. cs != null &&
  1433. (Number(cs) === 0 || String(cs).trim() === '0');
  1434. hotStat.style.display = hideHot ? 'none' : '';
  1435. }
  1436. var hot = pick(d, ['browseCount', 'viewCount', 'hotCount', 'readCount']);
  1437. if (hot != null) {
  1438. var he = document.getElementById('goodsHotCount');
  1439. if (he) he.textContent = String(hot);
  1440. }
  1441. var fav = pick(d, ['collectCount', 'wantCount', 'favCount', 'favoriteCount']);
  1442. if (fav != null) {
  1443. var fe = document.getElementById('goodsFavCount');
  1444. if (fe) fe.textContent = String(fav);
  1445. }
  1446. var topicStr = d.topic != null ? String(d.topic).trim() : '';
  1447. var labelStr = d.label != null ? String(d.label).trim() : '';
  1448. var hasApiTags = topicStr !== '' || labelStr !== '';
  1449. var mount = document.getElementById('goodsTags');
  1450. if (mount) {
  1451. if (hasApiTags) {
  1452. mount.innerHTML = '';
  1453. if (topicStr) appendGoodsTag(mount, topicStr, 'topic');
  1454. if (labelStr) appendGoodsTag(mount, labelStr, 'label');
  1455. } else {
  1456. var tags = d.tagList || d.tags || d.labelList || d.goodsTags;
  1457. if (tags) {
  1458. mount.innerHTML = '';
  1459. var arr = Array.isArray(tags) ? tags : String(tags).split(/[,,]/);
  1460. arr.forEach(function (t) {
  1461. if (t == null || t === '') return;
  1462. var label =
  1463. typeof t === 'object'
  1464. ? (t.tagName || t.name || t.label || '')
  1465. : String(t);
  1466. label = String(label).trim();
  1467. if (!label) return;
  1468. var isTopic = label.indexOf('#') === 0;
  1469. appendGoodsTag(mount, label, isTopic ? 'topic' : 'label');
  1470. });
  1471. }
  1472. }
  1473. }
  1474. }
  1475. var SVG_LIKE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>';
  1476. var SVG_REPLY = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
  1477. var SVG_CHEV = '<svg class="goods-cmt__thread-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>';
  1478. function cmtAvatarUrl(c) {
  1479. return String(
  1480. pick(c, ['headImg', 'userAvatar', 'userHead', 'avatar', 'avatarUrl']) || ''
  1481. ).trim();
  1482. }
  1483. function cmtUserName(c) {
  1484. var s = String(pick(c, ['headName', 'userName', 'nickName', 'userNickName', 'commentUserName', 'headPhone']) || '—').trim();
  1485. return s || '—';
  1486. }
  1487. function cmtTime(c) {
  1488. return String(pick(c, ['createTime', 'createdTime', 'commentTime']) || '');
  1489. }
  1490. function cmtContent(c) {
  1491. return String(pick(c, ['content', 'commentContent', 'text']) || '');
  1492. }
  1493. function cmtLikeCount(c) {
  1494. var v = pick(c, ['likeCount', 'likeNum', 'praiseCount']);
  1495. return v != null ? String(v) : '0';
  1496. }
  1497. function cmtReplies(c) {
  1498. if (!c || typeof c !== 'object') return [];
  1499. var sub =
  1500. c.childCommonComments ||
  1501. c.replyList ||
  1502. c.replyVOList ||
  1503. c.children ||
  1504. c.subComments ||
  1505. c.replies;
  1506. if (sub == null) return [];
  1507. return Array.isArray(sub) ? sub.filter(Boolean) : [];
  1508. }
  1509. function appendCmtRow(parentBody, c, showReply) {
  1510. var row = document.createElement('div');
  1511. row.className = 'goods-cmt__row';
  1512. var img = document.createElement('img');
  1513. img.className = 'goods-cmt__avatar';
  1514. img.alt = '';
  1515. var au = cmtAvatarUrl(c);
  1516. img.src = au || 'images/demouser.png';
  1517. var body = document.createElement('div');
  1518. body.className = 'goods-cmt__body';
  1519. var name = document.createElement('span');
  1520. name.className = 'goods-cmt__name';
  1521. name.textContent = cmtUserName(c);
  1522. var time = document.createElement('span');
  1523. time.className = 'goods-cmt__time';
  1524. time.textContent = cmtTime(c);
  1525. var text = document.createElement('p');
  1526. text.className = 'goods-cmt__text';
  1527. text.textContent = cmtContent(c);
  1528. var actions = document.createElement('div');
  1529. actions.className = 'goods-cmt__actions';
  1530. var likeBtn = document.createElement('button');
  1531. likeBtn.type = 'button';
  1532. likeBtn.className = 'goods-cmt__action';
  1533. likeBtn.setAttribute('aria-label', '点赞');
  1534. likeBtn.innerHTML = SVG_LIKE + '<span>' + cmtLikeCount(c) + '</span>';
  1535. actions.appendChild(likeBtn);
  1536. if (showReply) {
  1537. var repBtn = document.createElement('button');
  1538. repBtn.type = 'button';
  1539. repBtn.className = 'goods-cmt__action';
  1540. repBtn.setAttribute('aria-label', '回复');
  1541. repBtn.innerHTML = SVG_REPLY + '<span>回复</span>';
  1542. actions.appendChild(repBtn);
  1543. }
  1544. body.appendChild(name);
  1545. body.appendChild(time);
  1546. body.appendChild(text);
  1547. body.appendChild(actions);
  1548. row.appendChild(img);
  1549. row.appendChild(body);
  1550. parentBody.appendChild(row);
  1551. return body;
  1552. }
  1553. function buildNestedCmt(c) {
  1554. var wrap = document.createElement('div');
  1555. wrap.className = 'goods-cmt goods-cmt--nested';
  1556. appendCmtRow(wrap, c, false);
  1557. return wrap;
  1558. }
  1559. function filterTopLevelComments(list) {
  1560. if (!Array.isArray(list) || !list.length) return [];
  1561. var top = list.filter(function (c) {
  1562. if (!c) return false;
  1563. var pid = c.parentId != null ? c.parentId : c.pid;
  1564. if (typeof pid === 'string') pid = pid.trim();
  1565. return pid == null || pid === '' || Number(pid) === 0;
  1566. });
  1567. return top.length ? top : list.slice();
  1568. }
  1569. function countCommentsInApiList(records) {
  1570. if (!Array.isArray(records)) return 0;
  1571. var tops = filterTopLevelComments(records);
  1572. var n = 0;
  1573. for (var i = 0; i < tops.length; i++) {
  1574. var c = tops[i];
  1575. if (!c) continue;
  1576. n += 1 + cmtReplies(c).length;
  1577. }
  1578. return n;
  1579. }
  1580. function normalizeCommentListPayload(d) {
  1581. if (typeof d === 'string') {
  1582. try {
  1583. d = JSON.parse(d);
  1584. } catch (e) {
  1585. return { records: [], total: 0 };
  1586. }
  1587. }
  1588. if (Array.isArray(d)) {
  1589. return { records: d, total: countCommentsInApiList(d) };
  1590. }
  1591. if (d && typeof d === 'object') {
  1592. var arr = d.records || d.list || d.rows;
  1593. if (!Array.isArray(arr) && Array.isArray(d.data)) arr = d.data;
  1594. if (!Array.isArray(arr)) arr = [];
  1595. var total =
  1596. d.total != null && !isNaN(Number(d.total))
  1597. ? Number(d.total)
  1598. : countCommentsInApiList(arr);
  1599. return { records: arr, total: total };
  1600. }
  1601. return { records: [], total: 0 };
  1602. }
  1603. function renderCommentsList(rows, total) {
  1604. var mount = document.getElementById('goodsCommentList');
  1605. var totEl = document.getElementById('goodsCommentTotal');
  1606. if (!mount) return;
  1607. mount.innerHTML = '';
  1608. var list = filterTopLevelComments(Array.isArray(rows) ? rows : []);
  1609. var n = total != null && !isNaN(Number(total)) ? Number(total) : countCommentsInApiList(Array.isArray(rows) ? rows : []);
  1610. if (totEl) totEl.textContent = String(n);
  1611. if (!list.length) {
  1612. var empty = document.createElement('li');
  1613. empty.className = 'goods-comments__empty';
  1614. empty.setAttribute('role', 'status');
  1615. empty.textContent = '暂无留言';
  1616. mount.appendChild(empty);
  1617. return;
  1618. }
  1619. list.forEach(function (c) {
  1620. if (!c) return;
  1621. var li = document.createElement('li');
  1622. li.className = 'goods-cmt';
  1623. appendCmtRow(li, c, true);
  1624. var replies = cmtReplies(c);
  1625. if (replies.length) {
  1626. var thread = document.createElement('div');
  1627. thread.className = 'goods-cmt__thread';
  1628. var inner = document.createElement('div');
  1629. inner.className = 'goods-cmt__replies-inner';
  1630. replies.forEach(function (r) {
  1631. if (r) inner.appendChild(buildNestedCmt(r));
  1632. });
  1633. var bar = document.createElement('button');
  1634. bar.type = 'button';
  1635. bar.className = 'goods-cmt__thread-bar';
  1636. bar.setAttribute('aria-expanded', 'true');
  1637. bar.innerHTML =
  1638. '<span>共<span class="js-reply-count">' + replies.length + '</span>条回复</span>' +
  1639. '<span class="goods-cmt__thread-bar-right">' +
  1640. '<span class="js-thread-label">收起</span>' + SVG_CHEV + '</span>';
  1641. thread.appendChild(inner);
  1642. thread.appendChild(bar);
  1643. li.appendChild(thread);
  1644. }
  1645. mount.appendChild(li);
  1646. });
  1647. }
  1648. function bindCommentThreadDelegation() {
  1649. var list = document.getElementById('goodsCommentList');
  1650. if (!list || list.dataset.threadDeleg) return;
  1651. list.dataset.threadDeleg = '1';
  1652. list.addEventListener('click', function (e) {
  1653. var bar = e.target.closest('.goods-cmt__thread-bar');
  1654. if (!bar) return;
  1655. var inner = bar.previousElementSibling;
  1656. if (!inner || !inner.classList.contains('goods-cmt__replies-inner')) return;
  1657. var collapsed = inner.classList.toggle('is-collapsed');
  1658. bar.classList.toggle('is-collapsed', collapsed);
  1659. bar.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
  1660. var lab = bar.querySelector('.js-thread-label');
  1661. if (lab) lab.textContent = collapsed ? '展开' : '收起';
  1662. });
  1663. }
  1664. function buildShareUndefinedRedirectQuery(gs, ctx) {
  1665. ctx = ctx || {};
  1666. var uq = new URLSearchParams();
  1667. uq.set('goodsUnavailable', '1');
  1668. if (gs != null && String(gs).trim() !== '') {
  1669. uq.set('goodsStatus', String(gs).trim());
  1670. }
  1671. if (ctx.userId) uq.set('userId', String(ctx.userId));
  1672. if (ctx.longitude) uq.set('longitude', String(ctx.longitude));
  1673. if (ctx.latitude) uq.set('latitude', String(ctx.latitude));
  1674. if (ctx.userCity) uq.set('userCity', String(ctx.userCity));
  1675. var gid = ctx.goodsId || '';
  1676. if (gid) {
  1677. uq.set('goodsId', String(gid));
  1678. uq.set('id', String(gid));
  1679. }
  1680. return uq;
  1681. }
  1682. function redirectToShareUndefined(gs, ctx) {
  1683. window.location.replace(
  1684. 'shareUndefined.html?' + buildShareUndefinedRedirectQuery(gs, ctx).toString()
  1685. );
  1686. }
  1687. /**
  1688. * 存在性接口:msg 为「暂无承载数据」即无商品(即使 code=200);
  1689. * 有有效 data 再看 deleteFlag;不用 goodsStatus 数值。
  1690. */
  1691. function isDetailResponseGoodsUnavailable(res) {
  1692. if (!res || typeof res !== 'object') return true;
  1693. if (isNoCarryingDataMsg(res)) {
  1694. return true;
  1695. }
  1696. var d = pickSecondGoodsDetailData(res);
  1697. if (hasSecondGoodsDetailPayload(d)) {
  1698. return isDetailDataExplicitlyDeleted(d);
  1699. }
  1700. var msg = res.msg != null ? String(res.msg) : '';
  1701. if (isMsgIndicateGoodsGone(msg)) {
  1702. return true;
  1703. }
  1704. if (isSecondGoodsExistOk(res)) {
  1705. return false;
  1706. }
  1707. return true;
  1708. }
  1709. /**
  1710. * 仅用 querySecondGoodsDetailWithOutJWT 判断商品是否存在,不渲染接口返回的详情字段
  1711. * @returns {Promise<{redirected:boolean, exists:boolean}>}
  1712. */
  1713. function checkSecondGoodsExists(goodsId, lon, lat, phoneId, redirectCtx) {
  1714. var lonStr =
  1715. lon != null && String(lon).trim() !== ''
  1716. ? String(lon).trim()
  1717. : String(DEFAULT_REQUEST_LONGITUDE);
  1718. var latStr =
  1719. lat != null && String(lat).trim() !== ''
  1720. ? String(lat).trim()
  1721. : String(DEFAULT_REQUEST_LATITUDE);
  1722. var pid =
  1723. phoneId != null && String(phoneId).trim() !== ''
  1724. ? String(phoneId).trim()
  1725. : '';
  1726. /** 表单字段:键为 string,值一律 string(x-www-form-urlencoded) */
  1727. var formBody = {
  1728. goodsId: String(goodsId != null ? goodsId : '').trim(),
  1729. longitude: String(lonStr),
  1730. latitude: String(latStr),
  1731. phoneId: pid
  1732. };
  1733. var form = new URLSearchParams();
  1734. form.set('goodsId', formBody.goodsId);
  1735. form.set('longitude', formBody.longitude);
  1736. form.set('latitude', formBody.latitude);
  1737. form.set('phoneId', formBody.phoneId);
  1738. var detailUrl =
  1739. API_BASE_SECOND + '/recommend/querySecondGoodsDetailWithOutJWT';
  1740. var formStr = form.toString();
  1741. return fetch(detailUrl, {
  1742. method: 'POST',
  1743. mode: 'cors',
  1744. credentials: 'omit',
  1745. headers: {
  1746. Accept: 'application/json',
  1747. 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
  1748. },
  1749. body: formStr
  1750. })
  1751. .then(function (res) {
  1752. if (!res.ok) throw new Error('HTTP ' + res.status);
  1753. return res.json();
  1754. })
  1755. .then(function (res) {
  1756. redirectCtx = redirectCtx || {};
  1757. var d = pickSecondGoodsDetailData(res);
  1758. if (isDetailResponseGoodsUnavailable(res)) {
  1759. console.warn(
  1760. '[querySecondGoodsDetailWithOutJWT] unavailable',
  1761. res && res.code,
  1762. res && res.msg,
  1763. d
  1764. );
  1765. redirectToShareUndefined(null, redirectCtx);
  1766. return { redirected: true, exists: false };
  1767. }
  1768. return { redirected: false, exists: true };
  1769. });
  1770. }
  1771. function fetchCommentList(goodsId, userId, pageNum, pageSize) {
  1772. var st = (q('sourceType') || '').trim();
  1773. var sourceType = st && !isNaN(Number(st)) ? Number(st) : COMMENT_SOURCE_TYPE;
  1774. var path = '/commonComment/getListBySourceType?' +
  1775. 'sourceType=' + encodeURIComponent(String(sourceType)) +
  1776. '&sourceId=' + encodeURIComponent(String(goodsId)) +
  1777. '&pageNum=' + encodeURIComponent(String(pageNum || 1)) +
  1778. '&pageSize=' + encodeURIComponent(String(pageSize || 10)) +
  1779. '&userId=' + encodeURIComponent(userId != null ? String(userId) : '');
  1780. return apiGet(path).then(function (res) {
  1781. if (!isApiOk(res)) {
  1782. if (res && res.msg) console.warn('[getListBySourceType]', res.msg);
  1783. renderCommentsList([], 0);
  1784. return;
  1785. }
  1786. var raw = res.data != null ? res.data : res.result;
  1787. var pack = normalizeCommentListPayload(raw);
  1788. renderCommentsList(pack.records, pack.total);
  1789. });
  1790. }
  1791. /** 分享卡片常用 sourceId;与留言 sourceId 一致 */
  1792. function getShareGoodsId() {
  1793. return (q('goodsId') || q('id') || q('sourceId') || '').trim();
  1794. }
  1795. function run() {
  1796. var goodsId = getShareGoodsId();
  1797. var userId = (q('userId') || '').trim();
  1798. var phoneId = (q('phoneId') || q('phone_id') || '').trim();
  1799. var lon = (q('longitude') || q('lon') || q('jingdu') || '').trim();
  1800. var lat = (q('latitude') || q('lat') || q('weidu') || '').trim();
  1801. var userCity = (q('userCity') || q('city') || '').trim();
  1802. var lonResolved = lon || DEFAULT_REQUEST_LONGITUDE;
  1803. var latResolved = lat || DEFAULT_REQUEST_LATITUDE;
  1804. var pageNum = (q('pageNum') || '1').trim();
  1805. var pageSize = (q('pageSize') || '10').trim();
  1806. var redirectCtx = {
  1807. goodsId: goodsId,
  1808. userId: userId,
  1809. longitude: lonResolved,
  1810. latitude: latResolved,
  1811. userCity: userCity
  1812. };
  1813. bindCommentThreadDelegation();
  1814. /* 分享卡片应带 goodsId;勿因 URL 残留 goodsUnavailable=1 跳过存在性校验直接进不可用页 */
  1815. if (
  1816. !goodsId &&
  1817. String(q('goodsUnavailable') || '').trim() === '1'
  1818. ) {
  1819. redirectToShareUndefined(q('goodsStatus'), redirectCtx);
  1820. return;
  1821. }
  1822. if (!goodsId) {
  1823. applyFromUrl();
  1824. return;
  1825. }
  1826. checkSecondGoodsExists(goodsId, lon, lat, phoneId, redirectCtx)
  1827. .catch(function (e) {
  1828. console.warn('[querySecondGoodsDetailWithOutJWT]', e);
  1829. return { redirected: false, exists: null, checkFailed: true };
  1830. })
  1831. .then(function (checkResult) {
  1832. if (checkResult && checkResult.redirected) {
  1833. return;
  1834. }
  1835. applyFromUrl();
  1836. return fetchCommentList(goodsId, userId, pageNum, pageSize).catch(function (e) {
  1837. console.warn('[getListBySourceType]', e);
  1838. renderCommentsList([], 0);
  1839. });
  1840. });
  1841. }
  1842. function applyFromUrl() {
  1843. var heroImg = document.getElementById('goodsHeroImg');
  1844. var imgUrl = q('image') || q('goodsImage') || q('coverUrl') || q('img');
  1845. imgUrl = tryDecode(imgUrl).trim();
  1846. if (imgUrl) {
  1847. heroImg.src = imgUrl;
  1848. heroImg.alt = '商品图';
  1849. } else {
  1850. heroImg.src = DEFAULT_HERO;
  1851. heroImg.alt = '';
  1852. }
  1853. var av = tryDecode(q('userImage') || q('avatar')).trim();
  1854. document.getElementById('goodsUserAvatar').src = av || 'images/demouser.png';
  1855. var uname = tryDecode(q('userName') || q('nickname')).trim();
  1856. if (uname) document.getElementById('goodsUserName').textContent = uname;
  1857. var ago = tryDecode(q('timeAgo') || q('publishTime')).trim();
  1858. if (ago) document.getElementById('goodsTimeAgo').textContent = ago;
  1859. var s1 = tryDecode(q('slogan1') || q('line1')).trim();
  1860. var s2 = tryDecode(q('slogan2') || q('line2')).trim();
  1861. var elS1 = document.getElementById('goodsSlogan1');
  1862. var elS2 = document.getElementById('goodsSlogan2');
  1863. var elWrap = document.getElementById('goodsHeroSloganWrap');
  1864. if (s1 && elS1) elS1.textContent = s1;
  1865. if (s2 && elS2) elS2.textContent = s2;
  1866. if (elWrap && (s1 || s2)) elWrap.style.display = '';
  1867. var price = tryDecode(q('price')).trim();
  1868. if (price) {
  1869. var pn = parseFloat(price.replace(/[^\d.]/g, ''));
  1870. document.getElementById('goodsPrice').textContent =
  1871. !isNaN(pn) ? pn.toFixed(2) : price;
  1872. }
  1873. var dist = tryDecode(q('distance') || q('distanceKm') || q('dist')).trim();
  1874. if (dist) {
  1875. if (!/^距离/.test(dist)) dist = '距离 ' + dist;
  1876. if (!/km$/i.test(dist) && !/千米/.test(dist) && !/米/.test(dist) && !/\s*m\s*$/i.test(dist)) {
  1877. dist = dist.replace(/\s+$/, '') + 'km';
  1878. }
  1879. document.getElementById('goodsDistance').textContent = dist;
  1880. }
  1881. var title = tryDecode(q('title') || q('goodsTitle')).trim();
  1882. if (title) document.getElementById('goodsTitle').textContent = title;
  1883. var hot = tryDecode(q('hotCount') || q('hot')).trim();
  1884. if (hot) document.getElementById('goodsHotCount').textContent = hot;
  1885. var fav = tryDecode(q('favCount') || q('fav') || q('collectCount')).trim();
  1886. if (fav) document.getElementById('goodsFavCount').textContent = fav;
  1887. var cc = tryDecode(q('commentCount') || q('comments')).trim();
  1888. if (cc) {
  1889. var elTot = document.getElementById('goodsCommentTotal');
  1890. if (elTot) elTot.textContent = cc;
  1891. }
  1892. var tagsRaw = tryDecode(q('tags') || q('tag')).trim();
  1893. var mount = document.getElementById('goodsTags');
  1894. var hasGoodsId = (q('goodsId') || q('id') || '').trim();
  1895. if (mount) {
  1896. if (tagsRaw) {
  1897. mount.innerHTML = '';
  1898. tagsRaw.split(/[,,]/).map(function (t) { return t.trim(); }).filter(Boolean).forEach(function (t) {
  1899. appendGoodsTag(mount, t, t.indexOf('#') === 0 ? 'topic' : 'label');
  1900. });
  1901. } else if (!hasGoodsId) {
  1902. mount.innerHTML = '';
  1903. ['复古冰箱', '网红冰箱', '复古烤漆', '冰箱'].forEach(function (t) {
  1904. appendGoodsTag(mount, t, 'label');
  1905. });
  1906. }
  1907. }
  1908. }
  1909. function sharePage() {
  1910. var title = document.getElementById('goodsTitle').textContent || '商品分享';
  1911. var url = location.href;
  1912. if (navigator.share) {
  1913. navigator.share({ title: title, text: title, url: url }).catch(function () {});
  1914. return;
  1915. }
  1916. if (navigator.clipboard && navigator.clipboard.writeText) {
  1917. navigator.clipboard.writeText(url).then(function () {
  1918. window.alert('链接已复制');
  1919. }).catch(function () {
  1920. window.prompt('复制链接', url);
  1921. });
  1922. } else {
  1923. window.prompt('复制链接', url);
  1924. }
  1925. }
  1926. var shareBtn = document.getElementById('goodsShareBtn');
  1927. if (shareBtn) shareBtn.addEventListener('click', sharePage);
  1928. function boot() {
  1929. var launchTag = document.getElementById('launch-btn');
  1930. if (launchTag) launchTag.setAttribute('appid', WECHAT_OPEN_APP_ID);
  1931. bindWeChatLaunchTagEvents();
  1932. if (isWeChatInAppBrowser()) {
  1933. document.body.classList.add('is-wechat');
  1934. applyWxOpenLaunchSceneBodyClass();
  1935. logWxEntryDiagnostics();
  1936. }
  1937. if (shouldInitWeChatJssdkOnLoad()) {
  1938. if (isWxPcBrowser() && (isWxForceDebug() || isWxPcAutoDebugHost())) {
  1939. document.body.classList.add('wx-pc-debug');
  1940. }
  1941. scheduleWeChatJssdkBootstrap();
  1942. }
  1943. document.addEventListener('WeixinOpenTagsError', function (e) {
  1944. console.warn('[WeixinOpenTagsError]', e && e.detail);
  1945. applyWxOpenLaunchSceneBodyClass();
  1946. });
  1947. if (isWeChatInAppBrowser()) {
  1948. var openBtnWx = document.getElementById('openApp');
  1949. if (openBtnWx) {
  1950. openBtnWx.addEventListener('click', function () {
  1951. if (!weChatJssdkConfigured) {
  1952. showFabToast(
  1953. wxInitLastError || '微信 SDK 初始化中,请稍候再点底部按钮'
  1954. );
  1955. return;
  1956. }
  1957. if (!isWxOpenLaunchAppSceneSupported()) {
  1958. showWxCopyLinkEntryTip();
  1959. }
  1960. });
  1961. }
  1962. } else {
  1963. var openAppBtn = document.getElementById('openApp');
  1964. if (openAppBtn) {
  1965. openAppBtn.addEventListener('click', tryOpenHBuilderApp);
  1966. }
  1967. }
  1968. run();
  1969. }
  1970. if (document.readyState === 'complete') {
  1971. boot();
  1972. } else {
  1973. window.onload = boot;
  1974. }
  1975. })();
  1976. </script>
  1977. </body>
  1978. </html>