merge: 合并 main(登录/主题/深色模式修复)到 master,保留 master 历史线
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"pages":["pages/index/index","pages/shop/index","pages/shop/detail/index","pages/product/index","pages/diy/index","pages/diy/stickerEdit/index","pages/checkout/index","pages/designList/index","pages/orders/index","pages/profile/index","pages/service/index","pages/wordcloud/index","pages/orderDetail/index","pages/address/index","pages/settings/index","pages/agreement/index","pages/userDatabase/index"],"window":{"backgroundTextStyle":"dark","navigationStyle":"custom","navigationBarTextStyle":"black","backgroundColor":"#ffffff"},"tabBar":{"custom":true,"list":[{"pagePath":"pages/index/index","text":"首页"},{"pagePath":"pages/shop/index","text":"商品"},{"pagePath":"pages/designList/index","text":"设计清单"},{"pagePath":"pages/orders/index","text":"订单"},{"pagePath":"pages/profile/index","text":"我的"}]}}
|
||||
{"pages":["pages/index/index","pages/shop/index","pages/shop/detail/index","pages/product/index","pages/diy/index","pages/diy/stickerEdit/index","pages/checkout/index","pages/designList/index","pages/orders/index","pages/profile/index","pages/service/index","pages/wordcloud/index","pages/orderDetail/index","pages/address/index","pages/settings/index","pages/agreement/index","pages/userDatabase/index"],"window":{"backgroundTextStyle":"dark","navigationStyle":"custom","navigationBarTextStyle":"black"},"tabBar":{"custom":true,"list":[{"pagePath":"pages/index/index","text":"首页"},{"pagePath":"pages/shop/index","text":"商品"},{"pagePath":"pages/designList/index","text":"设计清单"},{"pagePath":"pages/orders/index","text":"订单"},{"pagePath":"pages/profile/index","text":"我的"}]}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[777],{653:function(n,e,t){var a=t(7842),c=t(118),i=t(758),o=t.n(i),s=t(4848),p=[{pagePath:"/pages/index/index",text:"\u9996\u9875",icon:"/icon/\u9996\u9875.png"},{pagePath:"/pages/shop/index",text:"\u5546\u54c1",icon:"/icon/\u5546\u54c1.png"},{pagePath:"/pages/designList/index",text:"\u8bbe\u8ba1\u6e05\u5355",icon:"/icon/\u8c03\u8272\u76d8.png"},{pagePath:"/pages/orders/index",text:"\u8ba2\u5355",icon:"/icon/\u5305\u88f9.png"},{pagePath:"/pages/profile/index",text:"\u6211\u7684",icon:"/icon/\u4e2a\u4eba.png"}];function r(){var n,e="/".concat((null===(n=o().getCurrentInstance().router)||void 0===n?void 0:n.path)||"pages/index/index"),t=function(n){o().switchTab({url:n})};return(0,s.jsx)(c.Ss,{className:"custom-tab-bar",children:p.map(function(n){var a=e===n.pagePath;return(0,s.jsxs)(c.Ss,{className:"tab-item ".concat(a?"active":""),onTap:function(){return t(n.pagePath)},children:[(0,s.jsx)(c._V,{className:"tab-icon-img ".concat(a?"active":""),src:n.icon,mode:"aspectFit"}),(0,s.jsx)(c.EY,{className:"tab-label",children:n.text})]},n.pagePath)})})}Component((0,a.createComponentConfig)(r,"custom-tab-bar/index"))}},function(n){var e=function(e){return n(n.s=e)};n.O(0,[907,96],function(){return e(653)});n.O()}]);
|
||||
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[777],{653:function(n,e,t){var a=t(7842),c=t(5544),i=t(118),o=t(758),s=t.n(o),r=t(6540),p=t(8594),u=t(9703),g=t(6606),x=t(4848),f=[{pagePath:"/pages/index/index",text:"\u9996\u9875",icon:"/icon/\u9996\u9875.png"},{pagePath:"/pages/shop/index",text:"\u5546\u54c1",icon:"/icon/\u5546\u54c1.png"},{pagePath:"/pages/designList/index",text:"\u8bbe\u8ba1\u6e05\u5355",icon:"/icon/\u8c03\u8272\u76d8.png"},{pagePath:"/pages/orders/index",text:"\u8ba2\u5355",icon:"/icon/\u5305\u88f9.png"},{pagePath:"/pages/profile/index",text:"\u6211\u7684",icon:"/icon/\u4e2a\u4eba.png"}];function d(){var n,e=(0,r.useState)((0,p.eW)((0,p.O4)())),t=(0,c.A)(e,2),a=t[0],o=t[1],d="/".concat((null===(n=s().getCurrentInstance().router)||void 0===n?void 0:n.path)||"pages/index/index");(0,r.useEffect)(function(){var n=function(n){return o(n)};return s().eventCenter.on(u.Wo,n),function(){s().eventCenter.off(u.Wo,n)}},[]),(0,r.useEffect)(function(){(0,g.G)(a)},[a]);var h=function(n){s().switchTab({url:n})};return(0,x.jsx)(i.Ss,{className:"custom-tab-bar theme-".concat(a),children:f.map(function(n){var e=d===n.pagePath;return(0,x.jsxs)(i.Ss,{className:"tab-item ".concat(e?"active":""),onTap:function(){return h(n.pagePath)},children:[(0,x.jsx)(i._V,{className:"tab-icon-img ".concat(e?"active":""),src:n.icon,mode:"aspectFit"}),(0,x.jsx)(i.EY,{className:"tab-label",children:n.text})]},n.pagePath)})})}Component((0,a.createComponentConfig)(d,"custom-tab-bar/index"))}},function(n){var e=function(e){return n(n.s=e)};n.O(0,[907,96,76],function(){return e(653)});n.O()}]);
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
.custom-tab-bar{-ms-flex-pack:distribute;-ms-flex-align:center;-webkit-align-items:center;align-items:center;background:#fff;border-top:3rpx dashed #a0c4ff;bottom:0;display:-webkit-flex;display:-ms-flexbox;display:flex;height:120rpx;-webkit-justify-content:space-around;justify-content:space-around;left:0;padding-bottom:env(safe-area-inset-bottom);position:fixed;right:0;z-index:1000}.tab-item{-ms-flex-align:center;-ms-flex-pack:center;-webkit-align-items:center;align-items:center;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-justify-content:center;justify-content:center;padding:10rpx 20rpx;position:relative}.tab-icon-img{height:28rpx;margin-bottom:4rpx;opacity:.6;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;width:28rpx}.tab-item.active .tab-icon-img{opacity:1;-webkit-transform:scale(1.15);-ms-transform:scale(1.15);transform:scale(1.15)}.tab-label{color:#b08d8d;font-size:20rpx;-webkit-transition:color .2s;transition:color .2s}.tab-item.active .tab-label{color:#ff9a9e;font-weight:600}
|
||||
.custom-tab-bar{-ms-flex-pack:distribute;-ms-flex-align:center;-webkit-align-items:center;align-items:center;background:#fff;border-top:3rpx dashed #a0c4ff;bottom:0;display:-webkit-flex;display:-ms-flexbox;display:flex;height:120rpx;-webkit-justify-content:space-around;justify-content:space-around;left:0;padding-bottom:env(safe-area-inset-bottom);position:fixed;right:0;z-index:1000}.custom-tab-bar.theme-dark{background:#1e1e2f;border-top-color:#5b8cff}.tab-item{-ms-flex-align:center;-ms-flex-pack:center;-webkit-align-items:center;align-items:center;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-justify-content:center;justify-content:center;padding:10rpx 20rpx;position:relative}.tab-icon-img{height:28rpx;margin-bottom:4rpx;opacity:.6;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;width:28rpx}.tab-item.active .tab-icon-img{opacity:1;-webkit-transform:scale(1.15);-ms-transform:scale(1.15);transform:scale(1.15)}.tab-label{color:#b08d8d;font-size:20rpx;-webkit-transition:color .2s;transition:color .2s}.custom-tab-bar.theme-dark .tab-label{color:#a0a0c0}.tab-item.active .tab-label{color:#ff9a9e;font-weight:600}.custom-tab-bar.theme-dark .tab-item.active .tab-label{color:#ff7eb3}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"navigationBarTitleText":"收货地址","usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"收货地址","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[301],{6855:function(e,n,t){var a=t(7842),c=t(118),s=t(9703),r=t(9201),i=t(5414),l=t(4848);function o(){var e=(0,s.wR)(),n=(e.theme,e.resolvedTheme),t=(0,r.c)();(0,i.l)(n);var a=[{title:"\u4e00\u3001\u7528\u6237\u6743\u8d23",content:"\u7528\u6237\u786e\u8ba4\u63d0\u4ea4\u7684\u8bbe\u8ba1\u5185\u5bb9\u4e0d\u4fb5\u72af\u7b2c\u4e09\u65b9\u77e5\u8bc6\u4ea7\u6743\uff0c\u5b9a\u5236\u5546\u54c1\u4e00\u7ecf\u786e\u8ba4\u4e0b\u5355\u5373\u8fdb\u5165\u751f\u4ea7\u6d41\u7a0b\uff0c\u975e\u8d28\u91cf\u95ee\u9898\u4e0d\u652f\u6301\u9000\u6362\u3002"},{title:"\u4e8c\u3001\u9690\u79c1\u4fdd\u62a4",content:"\u6211\u4eec\u4e25\u683c\u4fdd\u62a4\u7528\u6237\u4e2a\u4eba\u4fe1\u606f\uff0c\u5934\u50cf\u4e0e\u6635\u79f0\u4ec5\u7528\u4e8e\u63d0\u5347\u670d\u52a1\u4f53\u9a8c\uff0c\u4e0d\u4e0e\u7b2c\u4e09\u65b9\u5206\u4eab\u3002"},{title:"\u4e09\u3001\u552e\u540e\u8bf4\u660e",content:"\u6536\u5230\u5546\u54c1\u540e\u5982\u6709\u8d28\u91cf\u95ee\u9898\uff0c\u8bf7\u57287\u5929\u5185\u8054\u7cfb\u5ba2\u670d\uff0c\u6211\u4eec\u5c06\u4e3a\u60a8\u514d\u8d39\u91cd\u505a\u6216\u9000\u6b3e\u3002"},{title:"\u56db\u3001\u5b9a\u5236\u6d41\u7a0b",content:"1. \u9009\u62e9\u5546\u54c1\u54c1\u7c7b\u5e76\u52a0\u5165\u8bbe\u8ba1\u6e05\u5355\n2. \u5728DIY\u5de5\u4f5c\u53f0\u4e0a\u4f20\u56fe\u7247\u6216\u8f93\u5165\u6587\u5b57\n3. \u786e\u8ba4\u8bbe\u8ba1\u6548\u679c\u5e76\u63d0\u4ea4\u8ba2\u5355\n4. \u652f\u4ed8\u540e\u8fdb\u5165\u751f\u4ea7\uff0c\u7ea63-7\u4e2a\u5de5\u4f5c\u65e5\u53d1\u8d27\n5. \u7269\u6d41\u8ddf\u8e2a\u81f3\u786e\u8ba4\u6536\u8d27"}];return(0,l.jsx)(c.Ss,{className:"theme-".concat(n),children:(0,l.jsx)(c.Ss,{className:"agreement-page",children:(0,l.jsxs)(c.Ss,{className:"agreement-content",style:{marginTop:"".concat(t.headerPaddingTop,"px")},children:[(0,l.jsx)(c.EY,{className:"agreement-title",children:"\u5b9a\u5236\u534f\u8bae"}),a.map(function(e,n){return(0,l.jsxs)(c.Ss,{className:"agreement-section",children:[(0,l.jsx)(c.EY,{className:"agreement-h3",children:e.title}),(0,l.jsx)(c.EY,{className:"agreement-p",children:e.content})]},n)})]})})})}var m={navigationBarTitleText:"\u5b9a\u5236\u534f\u8bae"};Page((0,a.createPageConfig)(o,"pages/agreement/index",{root:{cn:[]}},m||{}))}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[907,96,76],function(){return n(6855)});e.O()}]);
|
||||
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[301],{6855:function(e,n,t){var a=t(7842),c=t(118),s=t(9703),r=t(9201),i=t(5414),o=t(4848);function l(){var e=(0,s.wR)(),n=(e.theme,e.resolvedTheme),t=(0,r.c)();(0,i.l)(n);var a=[{title:"\u4e00\u3001\u7528\u6237\u6743\u8d23",content:"\u7528\u6237\u786e\u8ba4\u63d0\u4ea4\u7684\u8bbe\u8ba1\u5185\u5bb9\u4e0d\u4fb5\u72af\u7b2c\u4e09\u65b9\u77e5\u8bc6\u4ea7\u6743\uff0c\u5b9a\u5236\u5546\u54c1\u4e00\u7ecf\u786e\u8ba4\u4e0b\u5355\u5373\u8fdb\u5165\u751f\u4ea7\u6d41\u7a0b\uff0c\u975e\u8d28\u91cf\u95ee\u9898\u4e0d\u652f\u6301\u9000\u6362\u3002"},{title:"\u4e8c\u3001\u9690\u79c1\u4fdd\u62a4",content:"\u6211\u4eec\u4e25\u683c\u4fdd\u62a4\u7528\u6237\u4e2a\u4eba\u4fe1\u606f\uff0c\u5934\u50cf\u4e0e\u6635\u79f0\u4ec5\u7528\u4e8e\u63d0\u5347\u670d\u52a1\u4f53\u9a8c\uff0c\u4e0d\u4e0e\u7b2c\u4e09\u65b9\u5206\u4eab\u3002"},{title:"\u4e09\u3001\u552e\u540e\u8bf4\u660e",content:"\u6536\u5230\u5546\u54c1\u540e\u5982\u6709\u8d28\u91cf\u95ee\u9898\uff0c\u8bf7\u57287\u5929\u5185\u8054\u7cfb\u5ba2\u670d\uff0c\u6211\u4eec\u5c06\u4e3a\u60a8\u514d\u8d39\u91cd\u505a\u6216\u9000\u6b3e\u3002"},{title:"\u56db\u3001\u5b9a\u5236\u6d41\u7a0b",content:"1. \u9009\u62e9\u5546\u54c1\u54c1\u7c7b\u5e76\u52a0\u5165\u8bbe\u8ba1\u6e05\u5355\n2. \u5728DIY\u5de5\u4f5c\u53f0\u4e0a\u4f20\u56fe\u7247\u6216\u8f93\u5165\u6587\u5b57\n3. \u786e\u8ba4\u8bbe\u8ba1\u6548\u679c\u5e76\u63d0\u4ea4\u8ba2\u5355\n4. \u652f\u4ed8\u540e\u8fdb\u5165\u751f\u4ea7\uff0c\u7ea63-7\u4e2a\u5de5\u4f5c\u65e5\u53d1\u8d27\n5. \u7269\u6d41\u8ddf\u8e2a\u81f3\u786e\u8ba4\u6536\u8d27"}];return(0,o.jsx)(c.Ss,{className:"theme-".concat(n),children:(0,o.jsx)(c.Ss,{className:"agreement-page",children:(0,o.jsxs)(c.Ss,{className:"agreement-content",style:{marginTop:"".concat(t.headerPaddingTop,"px")},children:[(0,o.jsx)(c.EY,{className:"agreement-title",children:"\u5b9a\u5236\u534f\u8bae"}),a.map(function(e,n){return(0,o.jsxs)(c.Ss,{className:"agreement-section",children:[(0,o.jsx)(c.EY,{className:"agreement-h3",children:e.title}),(0,o.jsx)(c.EY,{className:"agreement-p",children:e.content})]},n)})]})})})}var m={navigationStyle:"custom",navigationBarTitleText:"\u5b9a\u5236\u534f\u8bae"};Page((0,a.createPageConfig)(l,"pages/agreement/index",{root:{cn:[]}},m||{}))}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[907,96,76],function(){return n(6855)});e.O()}]);
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"navigationBarTitleText":"定制协议","usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"定制协议","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"确认下单","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"设计清单","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"DIY工作台","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
{"navigationBarTitleText":"编辑贴纸","usingComponents":{"comp":"../../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"编辑贴纸","usingComponents":{"comp":"../../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"智绘微刻","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"navigationBarTitleText":"订单详情","usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"订单详情","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"订单列表","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"商品详情","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"个人中心","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"客服中心","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"navigationBarTitleText":"设置","usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"设置","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[205],{6296:function(s,e,a){var c=a(7842),n=a(118),o=a(758),i=a.n(o),r=a(9703),d=a(9201),l=a(5414),t=a(5234),h=a(4848);function p(){var s=(0,r.wR)(),e=s.resolvedTheme,a=(0,d.c)();(0,l.l)(e);var c=function(s){i().navigateTo({url:"/pages/shop/detail/index?id=".concat(s)})};return(0,h.jsx)(n.Ss,{className:"shop-page theme-".concat(e),children:(0,h.jsxs)(n.Ss,{className:"shop-list",children:[(0,h.jsxs)(n.Ss,{className:"shop-header",style:{paddingTop:"".concat(a.headerPaddingTop,"px")},children:[(0,h.jsx)(n.EY,{className:"shop-header-title",children:"\u667a\u7ed8\u7cbe\u9009"}),(0,h.jsx)(n.EY,{className:"shop-header-sub",children:"\u6fc0\u5149\u5fae\u96d5\u5b9a\u5236\u597d\u7269"})]}),(0,h.jsx)(n.Ss,{className:"shop-grid",children:t.zv.map(function(s){var e,a,o,i;return(0,h.jsxs)(n.Ss,{className:"shop-card",onTap:function(){return c(s.id)},children:[(0,h.jsx)(n.Ss,{className:"shop-card-img-wrap",children:(0,h.jsx)(n._V,{className:"shop-card-img",src:(null===(e=s.images)||void 0===e?void 0:e[0])||s.iconImg||"",mode:"aspectFill",lazyLoad:!0})}),(0,h.jsxs)(n.Ss,{className:"shop-card-info",style:{backgroundColor:"rgba(".concat((null===(a=s.tone)||void 0===a?void 0:a[0])||28,", ").concat((null===(o=s.tone)||void 0===o?void 0:o[1])||22,", ").concat((null===(i=s.tone)||void 0===i?void 0:i[2])||18,", 0.92)")},children:[(0,h.jsx)(n.EY,{className:"shop-card-name",children:s.name}),(0,h.jsxs)(n.EY,{className:"shop-card-price",children:["\xa5",s.price]})]})]},s.id)})}),(0,h.jsx)(n.Ss,{style:{height:"160px"}})]})})}var m={};Page((0,c.createPageConfig)(p,"pages/shop/index",{root:{cn:[]}},m||{}))}},function(s){var e=function(e){return s(s.s=e)};s.O(0,[907,96,76],function(){return e(6296)});s.O()}]);
|
||||
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[205],{6296:function(s,a,e){var n=e(7842),c=e(118),o=e(758),i=e.n(o),r=e(9703),l=e(9201),d=e(5414),t=e(5234),h=e(4848);function p(){var s=(0,r.wR)(),a=s.resolvedTheme,e=(0,l.c)();(0,d.l)(a);var n=function(s){i().navigateTo({url:"/pages/shop/detail/index?id=".concat(s)})};return(0,h.jsx)(c.Ss,{className:"shop-page theme-".concat(a),children:(0,h.jsxs)(c.Ss,{className:"shop-list",children:[(0,h.jsxs)(c.Ss,{className:"shop-header",style:{paddingTop:"".concat(e.headerPaddingTop,"px")},children:[(0,h.jsx)(c.EY,{className:"shop-header-title",children:"\u667a\u7ed8\u7cbe\u9009"}),(0,h.jsx)(c.EY,{className:"shop-header-sub",children:"\u6fc0\u5149\u5fae\u96d5\u5b9a\u5236\u597d\u7269"})]}),(0,h.jsx)(c.Ss,{className:"shop-grid",children:t.zv.map(function(s){var a,e,o,i;return(0,h.jsxs)(c.Ss,{className:"shop-card",onTap:function(){return n(s.id)},children:[(0,h.jsx)(c.Ss,{className:"shop-card-img-wrap",children:(0,h.jsx)(c._V,{className:"shop-card-img",src:(null===(a=s.images)||void 0===a?void 0:a[0])||s.iconImg||"",mode:"aspectFill",lazyLoad:!0})}),(0,h.jsxs)(c.Ss,{className:"shop-card-info",style:{backgroundColor:"rgba(".concat((null===(e=s.tone)||void 0===e?void 0:e[0])||28,", ").concat((null===(o=s.tone)||void 0===o?void 0:o[1])||22,", ").concat((null===(i=s.tone)||void 0===i?void 0:i[2])||18,", 0.92)")},children:[(0,h.jsx)(c.EY,{className:"shop-card-name",children:s.name}),(0,h.jsxs)(c.EY,{className:"shop-card-price",children:["\xa5",s.price]})]})]},s.id)})}),(0,h.jsx)(c.Ss,{style:{height:"160px"}})]})})}var m={navigationStyle:"custom"};Page((0,n.createPageConfig)(p,"pages/shop/index",{root:{cn:[]}},m||{}))}},function(s){var a=function(a){return s(s.s=a)};s.O(0,[907,96,76],function(){return a(6296)});s.O()}]);
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"navigationBarTitleText":"用户数据库","backgroundColor":"#ffffff","usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"用户数据库","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"usingComponents":{"comp":"../../comp"}}
|
||||
{"navigationStyle":"custom","navigationBarTitleText":"词云生成","usingComponents":{"comp":"../../comp"}}
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
(function(){"use strict";var n={},t={};function r(e){var o=t[e];if(void 0!==o)return o.exports;var u=t[e]={exports:{}};return n[e](u,u.exports,r),u.exports}r.m=n,function(){var n=[];r.O=function(t,e,o,u){if(!e){var i=1/0;for(l=0;l<n.length;l++){e=n[l][0],o=n[l][1],u=n[l][2];for(var f=!0,c=0;c<e.length;c++)(!1&u||i>=u)&&Object.keys(r.O).every(function(n){return r.O[n](e[c])})?e.splice(c--,1):(f=!1,u<i&&(i=u));if(f){n.splice(l--,1);var a=o();void 0!==a&&(t=a)}}return t}u=u||0;for(var l=n.length;l>0&&n[l-1][2]>u;l--)n[l]=n[l-1];n[l]=[e,o,u]}}(),function(){r.n=function(n){var t=n&&n.__esModule?function(){return n["default"]}:function(){return n};return r.d(t,{a:t}),t}}(),function(){var n,t=Object.getPrototypeOf?function(n){return Object.getPrototypeOf(n)}:function(n){return n.__proto__};r.t=function(e,o){if(1&o&&(e=this(e)),8&o)return e;if("object"===typeof e&&e){if(4&o&&e.__esModule)return e;if(16&o&&"function"===typeof e.then)return e}var u=Object.create(null);r.r(u);var i={};n=n||[null,t({}),t([]),t(t)];for(var f=2&o&&e;"object"==typeof f&&!~n.indexOf(f);f=t(f))Object.getOwnPropertyNames(f).forEach(function(n){i[n]=function(){return e[n]}});return i["default"]=function(){return e},r.d(u,i),u}}(),function(){r.d=function(n,t){for(var e in t)r.o(t,e)&&!r.o(n,e)&&Object.defineProperty(n,e,{enumerable:!0,get:t[e]})}}(),function(){r.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"===typeof window)return window}}()}(),function(){r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)}}(),function(){r.r=function(n){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})}}(),function(){var n={121:0};r.O.j=function(t){return 0===n[t]};var t=function(t,e){var o,u,i=e[0],f=e[1],c=e[2],a=0;if(i.some(function(t){return 0!==n[t]})){for(o in f)r.o(f,o)&&(r.m[o]=f[o]);if(c)var l=c(r)}for(t&&t(e);a<i.length;a++)u=i[a],r.o(n,u)&&n[u]&&n[u][0](),n[u]=0;return r.O(l)},e=wx["webpackJsonp"]=wx["webpackJsonp"]||[];e.forEach(t.bind(null,0)),e.push=t.bind(null,e.push.bind(e))}()})();
|
||||
(function(){"use strict";var n={},t={};function r(e){var o=t[e];if(void 0!==o)return o.exports;var u=t[e]={exports:{}};return n[e](u,u.exports,r),u.exports}r.m=n,function(){var n=[];r.O=function(t,e,o,u){if(!e){var i=1/0;for(l=0;l<n.length;l++){e=n[l][0],o=n[l][1],u=n[l][2];for(var f=!0,c=0;c<e.length;c++)(!1&u||i>=u)&&Object.keys(r.O).every(function(n){return r.O[n](e[c])})?e.splice(c--,1):(f=!1,u<i&&(i=u));if(f){n.splice(l--,1);var a=o();void 0!==a&&(t=a)}}return t}u=u||0;for(var l=n.length;l>0&&n[l-1][2]>u;l--)n[l]=n[l-1];n[l]=[e,o,u]}}(),function(){r.n=function(n){var t=n&&n.__esModule?function(){return n["default"]}:function(){return n};return r.d(t,{a:t}),t}}(),function(){var n,t=Object.getPrototypeOf?function(n){return Object.getPrototypeOf(n)}:function(n){return n.__proto__};r.t=function(e,o){if(1&o&&(e=this(e)),8&o)return e;if("object"===typeof e&&e){if(4&o&&e.__esModule)return e;if(16&o&&"function"===typeof e.then)return e}var u=Object.create(null);r.r(u);var i={};n=n||[null,t({}),t([]),t(t)];for(var f=2&o&&e;"object"==typeof f&&!~n.indexOf(f);f=t(f))Object.getOwnPropertyNames(f).forEach(function(n){i[n]=function(){return e[n]}});return i["default"]=function(){return e},r.d(u,i),u}}(),function(){r.d=function(n,t){for(var e in t)r.o(t,e)&&!r.o(n,e)&&Object.defineProperty(n,e,{enumerable:!0,get:t[e]})}}(),function(){r.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"===typeof window)return window}}()}(),function(){r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)}}(),function(){r.r=function(n){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})}}(),function(){var n={502:0};r.O.j=function(t){return 0===n[t]};var t=function(t,e){var o,u,i=e[0],f=e[1],c=e[2],a=0;if(i.some(function(t){return 0!==n[t]})){for(o in f)r.o(f,o)&&(r.m[o]=f[o]);if(c)var l=c(r)}for(t&&t(e);a<i.length;a++)u=i[a],r.o(n,u)&&n[u]&&n[u][0](),n[u]=0;return r.O(l)},e=wx["webpackJsonp"]=wx["webpackJsonp"]||[];e.forEach(t.bind(null,0)),e.push=t.bind(null,e.push.bind(e))}()})();
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"projectname": "wechat_wc",
|
||||
"condition": {},
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
|
||||
@@ -23,7 +23,6 @@ export default defineAppConfig({
|
||||
navigationStyle: 'custom',
|
||||
// 应用启动时先保证浅色页面的 iOS 状态栏为黑字;页面 Hook 会在主题变化时覆盖它。
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#ffffff'
|
||||
},
|
||||
tabBar: {
|
||||
custom: true,
|
||||
|
||||
+4
-2
@@ -8,18 +8,19 @@
|
||||
这里不再用 env(safe-area-inset-top) 隐式下移,避免与 header 的避让叠加导致顶部留白过大 */
|
||||
.theme-light {
|
||||
min-height: 100vh;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--bg-page, #ffffff);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
min-height: 100vh;
|
||||
background-color: #1e1e2f;
|
||||
background-color: var(--bg-page, #1e1e2f);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 变量定义 */
|
||||
.theme-light {
|
||||
--bg-page: #ffffff;
|
||||
--line-card: 3px dashed #ffb7c5;
|
||||
--line-star: #ffb7c5;
|
||||
--text-primary: #5c3a3a;
|
||||
@@ -34,6 +35,7 @@
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
--bg-page: #1e1e2f;
|
||||
--line-card: 3px dashed #5b8cff;
|
||||
--line-star: #5b8cff;
|
||||
--text-primary: #e0e0f0;
|
||||
|
||||
+6
-1
@@ -1,9 +1,14 @@
|
||||
import { Component, PropsWithChildren } from 'react'
|
||||
import { ThemeProvider } from './context/ThemeContext'
|
||||
import { getTheme, resolveTheme } from './utils/store'
|
||||
import { applyPageBackground } from './utils/themeBackground'
|
||||
import './app.scss'
|
||||
|
||||
class App extends Component<PropsWithChildren> {
|
||||
componentDidMount() {}
|
||||
componentDidMount() {
|
||||
// 启动最早时机同步页面背景,避免自定义导航区在深色模式下闪白
|
||||
applyPageBackground(resolveTheme(getTheme()))
|
||||
}
|
||||
|
||||
componentDidShow() {}
|
||||
|
||||
|
||||
@@ -1,13 +1,68 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { getMe, logout } from '../../utils/api'
|
||||
import { getUserInfoRaw, clearUserInfo, isMockOpenid } from '../../utils/store'
|
||||
|
||||
export default function LoginGuard(
|
||||
props: {
|
||||
children: React.ReactNode
|
||||
type GuardState = 'checking' | 'ok' | 'denied'
|
||||
|
||||
/**
|
||||
* 登录守卫:不仅看本地是否有 openid,还要求本地用户与云端校验一致。
|
||||
* - 本地 openid 是历史 mock 残留 -> 判定不一致,清掉并引导重新登录
|
||||
* - 本地有 openid 但后端 getMe 校验失败/用户不存在 -> 判定不一致,清掉并引导重新登录
|
||||
* - 本地与云端 ID/openid 一致 -> 放行
|
||||
*/
|
||||
export default function LoginGuard(props: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState<GuardState>('checking')
|
||||
|
||||
useEffect(() => {
|
||||
const localUser = getUserInfoRaw()
|
||||
|
||||
// 本地根本没登录信息
|
||||
if (!localUser?.openid) {
|
||||
setState('denied')
|
||||
return
|
||||
}
|
||||
|
||||
// 本地是旧版 mock 残留:与真实云端不一致,强制重新登录
|
||||
if (isMockOpenid(localUser.openid)) {
|
||||
clearUserInfo()
|
||||
logout()
|
||||
setState('denied')
|
||||
return
|
||||
}
|
||||
|
||||
// 本地有真实 openid:到后端校验是否与云端一致
|
||||
getMe()
|
||||
.then((me) => {
|
||||
const consistent = !!me && !!me.id && me.openid === localUser.openid
|
||||
if (consistent) {
|
||||
setState('ok')
|
||||
} else {
|
||||
clearUserInfo()
|
||||
logout()
|
||||
setState('denied')
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 后端不可用时的谨慎处理:不静默放行,也不抹掉本地,
|
||||
// 返回 denied 让用户尝试重新登录(后端恢复后即可通过)
|
||||
setState('denied')
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (state === 'checking') {
|
||||
return (
|
||||
<View className='login-guard'>
|
||||
<View className='login-guard-inner'>
|
||||
<Text className='login-guard-icon'>⏳</Text>
|
||||
<Text className='login-guard-title'>校验登录状态</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
) {
|
||||
const user = Taro.getStorageSync('smart_user_info')
|
||||
if (!user?.openid) {
|
||||
|
||||
if (state === 'denied') {
|
||||
return (
|
||||
<View className='login-guard'>
|
||||
<View className='login-guard-inner'>
|
||||
@@ -21,5 +76,6 @@ export default function LoginGuard(
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return props.children as any
|
||||
}
|
||||
|
||||
@@ -3,32 +3,75 @@ import Taro from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import './index.scss'
|
||||
import { setUserInfo } from '../../utils/store'
|
||||
import { login, getMe } from '../../utils/api'
|
||||
import { safeHideLoading } from '../../utils/request'
|
||||
|
||||
/**
|
||||
* 登录弹窗(一步流程,无需手机号)
|
||||
*
|
||||
* 个人主体小程序无 getPhoneNumber 权限,故以 openid 作为用户唯一标识:
|
||||
* 用户填头像+昵称 -> 点「登录」-> wx.login 拿 code -> 后端换 openid 自动注册/续登
|
||||
* -> getMe 取用户信息 -> 完成
|
||||
*/
|
||||
export default function LoginModal({ visible, onClose, onLogin }: { visible: boolean; onClose: () => void; onLogin: () => void }) {
|
||||
const [nickName, setNickName] = useState('')
|
||||
const [avatarUrl, setAvatarUrl] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
if (!visible) return null
|
||||
|
||||
const handleLogin = () => {
|
||||
Taro.login({
|
||||
success: (res) => {
|
||||
// 无后端时 mock openid
|
||||
const mockOpenid = 'mock_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 6)
|
||||
const info = {
|
||||
openid: mockOpenid,
|
||||
nickName: nickName || '微信用户',
|
||||
avatarUrl: avatarUrl || '',
|
||||
loginAt: Date.now()
|
||||
}
|
||||
setUserInfo(info)
|
||||
Taro.showToast({ title: '登录成功', icon: 'success' })
|
||||
onLogin()
|
||||
},
|
||||
fail: () => {
|
||||
Taro.showToast({ title: '登录失败', icon: 'none' })
|
||||
const fail = (msg: string) => {
|
||||
setLoading(false)
|
||||
safeHideLoading()
|
||||
Taro.showToast({ title: msg, icon: 'none' })
|
||||
}
|
||||
|
||||
const finish = (accessToken: string, openid: string) => {
|
||||
const info = {
|
||||
openid,
|
||||
nickName: nickName || '微信用户',
|
||||
avatarUrl: avatarUrl || '',
|
||||
accessToken,
|
||||
loginAt: Date.now()
|
||||
}
|
||||
setUserInfo(info)
|
||||
setLoading(false)
|
||||
safeHideLoading()
|
||||
Taro.showToast({ title: '登录成功', icon: 'success' })
|
||||
onLogin()
|
||||
}
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (loading) return
|
||||
console.log('[LoginModal] 登录按钮被点击,开始登录流程')
|
||||
setLoading(true)
|
||||
Taro.showLoading({ title: '登录中', mask: true })
|
||||
try {
|
||||
const wxLogin = await Taro.login()
|
||||
console.log('[LoginModal] wx.login 成功,code=', wxLogin.code)
|
||||
const result = await login(wxLogin.code)
|
||||
console.log('[LoginModal] 后端 /auth/login 返回:', JSON.stringify(result))
|
||||
if (!result?.accessToken) {
|
||||
fail('登录失败,未拿到 token')
|
||||
return
|
||||
}
|
||||
})
|
||||
let openid = ''
|
||||
try {
|
||||
const me = await getMe()
|
||||
openid = me?.openid || ''
|
||||
console.log('[LoginModal] getMe 返回:', JSON.stringify(me))
|
||||
} catch {
|
||||
openid = ''
|
||||
}
|
||||
if (!openid) {
|
||||
fail('登录异常,请重试')
|
||||
return
|
||||
}
|
||||
finish(result.accessToken, openid)
|
||||
} catch (e) {
|
||||
console.error('[LoginModal] 登录流程出错:', e)
|
||||
fail((e as Error)?.message || '后端连接失败,请检查网络')
|
||||
}
|
||||
}
|
||||
|
||||
const onChooseAvatar = (e: any) => {
|
||||
@@ -39,8 +82,9 @@ export default function LoginModal({ visible, onClose, onLogin }: { visible: boo
|
||||
<View className='modal-overlay'>
|
||||
<View className='login-card dashed-card'>
|
||||
<View className='star-badge' />
|
||||
<Text className='login-title'>登录智绘微刻</Text>
|
||||
<Text className='login-title'>智绘微刻</Text>
|
||||
<Text className='login-desc'>登录后同步您的设计清单和订单</Text>
|
||||
|
||||
<Button
|
||||
className='avatar-btn'
|
||||
openType='chooseAvatar'
|
||||
@@ -52,6 +96,7 @@ export default function LoginModal({ visible, onClose, onLogin }: { visible: boo
|
||||
<Text className='avatar-placeholder'>点击选择头像</Text>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Input
|
||||
type='nickname'
|
||||
className='nickname-input'
|
||||
@@ -59,6 +104,7 @@ export default function LoginModal({ visible, onClose, onLogin }: { visible: boo
|
||||
value={nickName}
|
||||
onInput={(e: any) => setNickName(e.detail.value)}
|
||||
/>
|
||||
|
||||
<View className='login-actions'>
|
||||
<View className='btn-outline' onTap={onClose}>
|
||||
<Text>取消</Text>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createContext, useContext, useState, useCallback, useEffect } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { getTheme, setTheme as saveTheme, resolveTheme, type ThemeMode } from '../utils/store'
|
||||
import { applyPageBackground } from '../utils/themeBackground'
|
||||
|
||||
interface ThemeContextValue {
|
||||
theme: ThemeMode
|
||||
@@ -25,6 +26,9 @@ type ThemeChangeApi = {
|
||||
offThemeChange?: (listener: ThemeChangeListener) => void
|
||||
}
|
||||
|
||||
/** 主题广播事件名:自定义 tabBar 等无法继承 React Context 的独立组件通过它同步主题 */
|
||||
export const THEME_CHANGE_EVENT = 'themeChange'
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, set] = useState<ThemeMode>(getTheme())
|
||||
const [resolvedTheme, setResolved] = useState<'light' | 'dark'>(resolveTheme(getTheme()))
|
||||
@@ -44,6 +48,17 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
setTheme(next)
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
// 广播实际生效主题,供 custom-tab-bar 等独立组件订阅
|
||||
useEffect(() => {
|
||||
Taro.eventCenter.trigger(THEME_CHANGE_EVENT, resolvedTheme)
|
||||
}, [resolvedTheme])
|
||||
|
||||
// App 启动时立即同步页面背景(自定义导航区在 iOS 上由 backgroundColorTop 控制),
|
||||
// 避免首屏/切换 tab 时顶部导航区先显示白色再变主题色
|
||||
useEffect(() => {
|
||||
applyPageBackground(resolvedTheme)
|
||||
}, [resolvedTheme])
|
||||
|
||||
// 页面每次显示时(从小程序后台切回前台)重新检测系统主题
|
||||
useDidShow(() => {
|
||||
if (theme === 'auto') {
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* 自定义组件样式隔离:两套主题配色在组件内自包含,不依赖全局 app.wxss */
|
||||
.custom-tab-bar.theme-dark {
|
||||
background: #1e1e2f;
|
||||
border-top-color: #5b8cff;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -42,7 +48,15 @@
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.custom-tab-bar.theme-dark .tab-label {
|
||||
color: #a0a0c0;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-label {
|
||||
color: #ff9a9e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.custom-tab-bar.theme-dark .tab-item.active .tab-label {
|
||||
color: #ff7eb3;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getTheme, resolveTheme } from '../utils/store'
|
||||
import { THEME_CHANGE_EVENT } from '../context/ThemeContext'
|
||||
import { applyPageBackground } from '../utils/themeBackground'
|
||||
import './index.scss'
|
||||
|
||||
const TABS = [
|
||||
@@ -11,14 +15,31 @@ const TABS = [
|
||||
]
|
||||
|
||||
export default function CustomTabBar() {
|
||||
// 自定义 tabBar 是独立组件,无法继承页面里的 React Context,
|
||||
// 初始值从本地主题配置读取,之后通过 eventCenter 跟随主题切换
|
||||
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(resolveTheme(getTheme()))
|
||||
const currentPath = `/${Taro.getCurrentInstance().router?.path || 'pages/index/index'}`
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (t: 'light' | 'dark') => setResolvedTheme(t)
|
||||
Taro.eventCenter.on(THEME_CHANGE_EVENT, handler)
|
||||
return () => {
|
||||
Taro.eventCenter.off(THEME_CHANGE_EVENT, handler)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// tabBar 是 tab 页切换时必定会挂载/更新的组件:在这里也刷新一次页面背景,
|
||||
// 兜底处理“登录后切到受保护页面”时自定义导航区背景,避免残留白色
|
||||
useEffect(() => {
|
||||
applyPageBackground(resolvedTheme)
|
||||
}, [resolvedTheme])
|
||||
|
||||
const switchTab = (url: string) => {
|
||||
Taro.switchTab({ url })
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='custom-tab-bar'>
|
||||
<View className={`custom-tab-bar theme-${resolvedTheme}`}>
|
||||
{TABS.map((tab) => {
|
||||
const isActive = currentPath === tab.pagePath
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { applyPageBackground } from '../utils/themeBackground'
|
||||
|
||||
export interface UseStatusBarOptions {
|
||||
/** 顶部系统状态栏(iOS 时间/电量)文字颜色模式:
|
||||
@@ -23,6 +24,11 @@ export function useStatusBar(resolvedTheme: 'light' | 'dark', options: UseStatus
|
||||
const bgColor = isDarkText ? '#ffffff' : '#1e1e2f'
|
||||
|
||||
Taro.setNavigationBarColor({ frontColor, backgroundColor: bgColor })
|
||||
// 同步页面背景色,确保自定义导航区(状态栏下方、胶囊按钮区域)跟随主题,
|
||||
// 避免深色模式下顶部残留白色条带。
|
||||
// 注意:iOS 上顶部/底部窗口背景需分别用 backgroundColorTop / Bottom 指定,
|
||||
// 仅设置 backgroundColor 在 iOS 上无法覆盖顶部导航区。
|
||||
applyPageBackground(resolvedTheme, bgColor)
|
||||
}, [mode, resolvedTheme])
|
||||
|
||||
// 主题切换后立即更新当前页面。
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '收货地址'
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '定制协议'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '确认下单'
|
||||
})
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "确认下单"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '设计清单'
|
||||
})
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "设计清单"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: 'DIY工作台'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "DIY工作台",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '编辑贴纸'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '智绘微刻'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "智绘微刻",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '订单详情'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '订单列表'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "订单列表",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '商品详情'
|
||||
})
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "商品详情"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '个人中心'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
+164
-47
@@ -6,6 +6,8 @@ import { getOrderList, getUserInfo, setUserInfo, getDesignList } from '../../uti
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import { login, getMe, updateProfile, getToken } from '../../utils/api'
|
||||
import { safeHideLoading } from '../../utils/request'
|
||||
|
||||
const ICON_MAP: Record<string, string> = {
|
||||
'我的设计': '/icon/调色盘.png',
|
||||
@@ -46,8 +48,11 @@ export default function ProfilePage() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false)
|
||||
const [userInfo, setUserInfoState] = useState({ nickName: '', avatarUrl: '' })
|
||||
const [showLogin, setShowLogin] = useState(false)
|
||||
// loginStep: 'entry' 显示登录按钮;'fill' 新用户补全资料
|
||||
const [loginStep, setLoginStep] = useState<'entry' | 'fill'>('entry')
|
||||
const [loginNickName, setLoginNickName] = useState('')
|
||||
const [loginAvatarUrl, setLoginAvatarUrl] = useState('')
|
||||
const [loginLoading, setLoginLoading] = useState(false)
|
||||
const [orderStats, setOrderStats] = useState({
|
||||
toDesign: 0, pending: 0, paid: 0, shipping: 0, done: 0
|
||||
})
|
||||
@@ -80,29 +85,124 @@ export default function ProfilePage() {
|
||||
sync()
|
||||
})
|
||||
|
||||
const handleOpenLogin = () => setShowLogin(true)
|
||||
const handleOpenLogin = () => {
|
||||
setLoginStep('entry')
|
||||
setLoginNickName('')
|
||||
setLoginAvatarUrl('')
|
||||
setShowLogin(true)
|
||||
}
|
||||
const handleCloseLogin = () => setShowLogin(false)
|
||||
|
||||
const handleLoginSubmit = () => {
|
||||
Taro.login({
|
||||
success: () => {
|
||||
const mockOpenid = 'mock_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 6)
|
||||
const info = {
|
||||
openid: mockOpenid,
|
||||
nickName: loginNickName || '微信用户',
|
||||
avatarUrl: loginAvatarUrl || '',
|
||||
loginAt: Date.now()
|
||||
}
|
||||
setUserInfo(info)
|
||||
setUserInfoState({ nickName: info.nickName, avatarUrl: info.avatarUrl })
|
||||
setIsLoggedIn(true)
|
||||
setShowLogin(false)
|
||||
Taro.showToast({ title: '登录成功', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
Taro.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
const loginFail = (msg: string) => {
|
||||
setLoginLoading(false)
|
||||
safeHideLoading()
|
||||
Taro.showToast({ title: msg, icon: 'none' })
|
||||
}
|
||||
|
||||
const loginFinish = (accessToken: string, openid: string, nickName: string, avatarUrl: string) => {
|
||||
const info = {
|
||||
openid,
|
||||
nickName: nickName || '微信用户',
|
||||
avatarUrl: avatarUrl || '',
|
||||
accessToken,
|
||||
loginAt: Date.now()
|
||||
}
|
||||
setUserInfo(info)
|
||||
setUserInfoState({ nickName: info.nickName, avatarUrl: info.avatarUrl })
|
||||
setIsLoggedIn(true)
|
||||
setShowLogin(false)
|
||||
setLoginStep('entry')
|
||||
setLoginLoading(false)
|
||||
safeHideLoading()
|
||||
Taro.showToast({ title: '登录成功', icon: 'success' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录:wx.login 拿 code -> 后端判断新老用户
|
||||
* - 老用户(isNewUser=false) -> 直接用库里已有的昵称/头像完成,免设置
|
||||
* - 新用户(isNewUser=true) -> 切到 'fill' 步骤补全资料
|
||||
*/
|
||||
const handleLogin = async () => {
|
||||
if (loginLoading) return
|
||||
console.log('[Profile] 登录按钮被点击,开始登录流程')
|
||||
setLoginLoading(true)
|
||||
Taro.showLoading({ title: '登录中', mask: true })
|
||||
try {
|
||||
const wxLogin = await Taro.login()
|
||||
console.log('[Profile] wx.login 成功,code=', wxLogin.code)
|
||||
const result = await login(wxLogin.code)
|
||||
console.log('[Profile] 后端 /auth/login 返回:', JSON.stringify(result))
|
||||
if (!result?.accessToken) {
|
||||
loginFail('登录失败,未拿到 token')
|
||||
return
|
||||
}
|
||||
})
|
||||
// 取 openid 落本地
|
||||
let openid = ''
|
||||
try {
|
||||
const me = await getMe()
|
||||
openid = me?.openid || ''
|
||||
} catch {
|
||||
openid = ''
|
||||
}
|
||||
if (!openid) {
|
||||
loginFail('登录异常,请重试')
|
||||
return
|
||||
}
|
||||
|
||||
// 资料不完整(新用户,或老用户从未设置过资料)都引导补填
|
||||
const needProfile = result.isNewUser || !result.nickname || !result.avatar
|
||||
if (needProfile) {
|
||||
// 预填已有值(若老用户有一部分已设置)
|
||||
setLoginNickName(result.nickname || '')
|
||||
setLoginAvatarUrl(result.avatar || '')
|
||||
safeHideLoading()
|
||||
setLoginLoading(false)
|
||||
setLoginStep('fill')
|
||||
Taro.showToast({ title: '请设置头像和昵称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 资料完整的老用户:直接用库里已有资料
|
||||
loginFinish(result.accessToken, openid, result.nickname || '', result.avatar || '')
|
||||
} catch (e) {
|
||||
console.error('[Profile] 登录流程出错:', e)
|
||||
loginFail((e as Error)?.message || '后端连接失败,请检查网络')
|
||||
}
|
||||
}
|
||||
|
||||
/** 新用户补全资料后保存到后端并完成登录 */
|
||||
const handleSaveProfile = async () => {
|
||||
if (loginLoading) return
|
||||
setLoginLoading(true)
|
||||
Taro.showLoading({ title: '保存中', mask: true })
|
||||
try {
|
||||
// 把昵称/头像存到后端(如需真实 openid 可再 getMe)
|
||||
await updateProfile({
|
||||
nickname: loginNickName || undefined,
|
||||
avatar: loginAvatarUrl || undefined
|
||||
})
|
||||
let openid = ''
|
||||
try {
|
||||
const me = await getMe()
|
||||
openid = me?.openid || ''
|
||||
} catch {
|
||||
openid = ''
|
||||
}
|
||||
if (!openid) {
|
||||
loginFail('保存异常,请重试')
|
||||
return
|
||||
}
|
||||
const stored = getUserInfo()
|
||||
loginFinish(
|
||||
stored?.accessToken || getToken(),
|
||||
openid,
|
||||
loginNickName,
|
||||
loginAvatarUrl
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('[Profile] 保存资料出错:', e)
|
||||
loginFail((e as Error)?.message || '保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
const onChooseAvatar = (e: any) => {
|
||||
@@ -268,33 +368,50 @@ export default function ProfilePage() {
|
||||
<View className='login-card dashed-card'>
|
||||
<View className='star-badge' />
|
||||
<Text className='login-title'>登录智绘微刻</Text>
|
||||
<Text className='login-desc'>登录后同步您的设计清单和订单</Text>
|
||||
<Button
|
||||
className='avatar-btn'
|
||||
openType='chooseAvatar'
|
||||
onChooseAvatar={onChooseAvatar}
|
||||
>
|
||||
{loginAvatarUrl ? (
|
||||
<Image className='avatar-img' src={loginAvatarUrl} mode='aspectFill' />
|
||||
) : (
|
||||
<Image className='avatar-placeholder-img' src='/icon/个人.png' mode='aspectFit' />
|
||||
)}
|
||||
</Button>
|
||||
<Input
|
||||
type='nickname'
|
||||
className='nickname-input'
|
||||
placeholder='请输入昵称'
|
||||
value={loginNickName}
|
||||
onInput={(e: any) => setLoginNickName(e.detail.value)}
|
||||
/>
|
||||
<View className='login-actions'>
|
||||
<View className='btn-outline' onTap={handleCloseLogin}>
|
||||
<Text>取消</Text>
|
||||
</View>
|
||||
<View className='btn-gradient' onTap={handleLoginSubmit}>
|
||||
<Text>登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{loginStep === 'entry' ? (
|
||||
<>
|
||||
<Text className='login-desc'>登录后同步您的设计清单和订单</Text>
|
||||
<View className='login-actions'>
|
||||
<View className='btn-outline' onTap={handleCloseLogin}>
|
||||
<Text>取消</Text>
|
||||
</View>
|
||||
<View className='btn-gradient' onTap={handleLogin}>
|
||||
<Text>微信登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text className='login-desc'>请设置您的头像和昵称</Text>
|
||||
<Button
|
||||
className='avatar-btn'
|
||||
openType='chooseAvatar'
|
||||
onChooseAvatar={onChooseAvatar}
|
||||
>
|
||||
{loginAvatarUrl ? (
|
||||
<Image className='avatar-img' src={loginAvatarUrl} mode='aspectFill' />
|
||||
) : (
|
||||
<Image className='avatar-placeholder-img' src='/icon/个人.png' mode='aspectFit' />
|
||||
)}
|
||||
</Button>
|
||||
<Input
|
||||
type='nickname'
|
||||
className='nickname-input'
|
||||
placeholder='请输入昵称'
|
||||
value={loginNickName}
|
||||
onInput={(e: any) => setLoginNickName(e.detail.value)}
|
||||
/>
|
||||
<View className='login-actions'>
|
||||
<View className='btn-outline' onTap={handleCloseLogin}>
|
||||
<Text>取消</Text>
|
||||
</View>
|
||||
<View className='btn-gradient' onTap={handleSaveProfile}>
|
||||
<Text>保存</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '客服中心'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "客服中心",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '设置'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom'
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '用户数据库',
|
||||
backgroundColor: '#ffffff'
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '用户数据库'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '词云生成'
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "词云生成",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import http, { setToken, clearToken, getToken } from './request'
|
||||
|
||||
export { getToken }
|
||||
|
||||
/**
|
||||
* 后端 API 方法集合
|
||||
* 对应 wxmp_backend 的路由(见后端 Swagger /docs)
|
||||
* 说明:登录会调用后端 /api/auth/login,成功后把 accessToken 存入本地,
|
||||
* 供后续请求自动携带。购物车/订单等仍可用本地 store.ts 作为离线兜底。
|
||||
*/
|
||||
|
||||
export interface LoginResult {
|
||||
accessToken: string
|
||||
isNewUser: boolean // 首次登录(未设资料)为 true,前端需引导补填头像/姓名
|
||||
nickname: string | null
|
||||
avatar: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录:把 wx.login() 的 code 交给后端,后端用 code2Session 换 openid,
|
||||
* 自动注册/续登并签发 accessToken(个人主体无需手机号)。
|
||||
* 返回 isNewUser 供前端判断是否需补全资料。
|
||||
*/
|
||||
export async function login(code: string): Promise<LoginResult> {
|
||||
const data = await http.post<LoginResult>('/api/auth/login', { code }, { auth: false })
|
||||
if (data?.accessToken) {
|
||||
setToken(data.accessToken)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
/** 更新当前用户资料(昵称/头像),新用户在补填后提交到后端持久化 */
|
||||
export async function updateProfile(profile: { nickname?: string; avatar?: string }): Promise<UserProfile> {
|
||||
return http.patch<UserProfile>('/api/users/me', profile)
|
||||
}
|
||||
|
||||
/**
|
||||
* 首次注册:用 registerTicket + wx.getPhoneNumber 的 code 验证手机号,完成后自动登录
|
||||
*/
|
||||
export async function registerWithPhone(
|
||||
registerTicket: string,
|
||||
phoneCode: string,
|
||||
profile?: { nickname?: string; avatar?: string },
|
||||
): Promise<{ accessToken: string }> {
|
||||
const data = await http.post<{ accessToken: string }>(
|
||||
'/api/auth/register',
|
||||
{ registerTicket, phoneCode, ...profile },
|
||||
{ auth: false },
|
||||
)
|
||||
setToken(data.accessToken)
|
||||
return data
|
||||
}
|
||||
|
||||
/** 登出:仅清除本地 token(后端 JWT 无状态,无需撤销) */
|
||||
export function logout(): void {
|
||||
clearToken()
|
||||
}
|
||||
|
||||
/** 后端用户信息(对应 User 模型;openpid 为主标识,openid 可空) */
|
||||
export interface UserProfile {
|
||||
id: string
|
||||
openpid?: string | null
|
||||
openid?: string | null
|
||||
unionid?: string | null
|
||||
nickname?: string | null
|
||||
avatar?: string | null
|
||||
phone?: string | null
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
/** 获取当前登录用户信息 */
|
||||
export async function getMe(): Promise<UserProfile> {
|
||||
return http.get<UserProfile>('/api/users/me')
|
||||
}
|
||||
|
||||
/** 商品列表 */
|
||||
export async function fetchProducts() {
|
||||
return http.get('/api/products', { auth: false })
|
||||
}
|
||||
|
||||
/** 商品详情 */
|
||||
export async function fetchProduct(id: string) {
|
||||
return http.get(`/api/products/${id}`, { auth: false })
|
||||
}
|
||||
|
||||
/** 分类列表 */
|
||||
export async function fetchCategories() {
|
||||
return http.get('/api/categories', { auth: false })
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
/**
|
||||
* 后端接口请求封装
|
||||
* 后端统一响应:{ code, message, data },code === 0 表示成功
|
||||
* token 通过 Authorization: Bearer <token> 传入
|
||||
*/
|
||||
export const BASE_URL = 'https://wxbackend.tokenleaping.com'
|
||||
|
||||
const TOKEN_KEY = 'smart_access_token'
|
||||
|
||||
/** 安全隐藏 loading:没有活动 loading 时调用 hideLoading 会抛 hideToast:fail,这里吞掉 */
|
||||
export function safeHideLoading(): void {
|
||||
try { Taro.hideLoading() } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// ---------- token 存取 ----------
|
||||
|
||||
export function getToken(): string {
|
||||
try {
|
||||
return Taro.getStorageSync(TOKEN_KEY) || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function setToken(token: string): void {
|
||||
Taro.setStorageSync(TOKEN_KEY, token)
|
||||
}
|
||||
|
||||
export function clearToken(): void {
|
||||
try {
|
||||
Taro.removeStorageSync(TOKEN_KEY)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 类型 ----------
|
||||
|
||||
interface ApiResponse<T = unknown> {
|
||||
code: number
|
||||
message: string
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
||||
data?: unknown
|
||||
/** 额外请求头 */
|
||||
headers?: Record<string, string>
|
||||
/** 是否自动携带 token,默认 true */
|
||||
auth?: boolean
|
||||
}
|
||||
|
||||
// ---------- request ----------
|
||||
|
||||
/**
|
||||
* 发请求。成功(code===0)返回 data;业务失败抛 Error(message);HTTP 401 清 token 并抛错。
|
||||
* 调用方可自行 catch 或用 onUnauthorized 钩子统一处理跳登录。
|
||||
*/
|
||||
export async function request<T = unknown>(
|
||||
path: string,
|
||||
options: RequestOptions = {},
|
||||
): Promise<T> {
|
||||
const { method = 'GET', data, headers = {}, auth = true } = options
|
||||
|
||||
const token = getToken()
|
||||
const finalHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...(auth && token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...headers,
|
||||
}
|
||||
|
||||
let res: Taro.request.SuccessCallbackResult
|
||||
try {
|
||||
res = await Taro.request({
|
||||
url: `${BASE_URL}${path}`,
|
||||
method,
|
||||
data,
|
||||
header: finalHeaders,
|
||||
})
|
||||
} catch (e) {
|
||||
// 网络层异常
|
||||
throw new Error('网络异常,请稍后重试')
|
||||
}
|
||||
|
||||
// HTTP 未授权:清 token,交回调处理
|
||||
if (res.statusCode === 401) {
|
||||
clearToken()
|
||||
onUnauthorized?.()
|
||||
throw new Error('登录已过期,请重新登录')
|
||||
}
|
||||
|
||||
const body = res.data as ApiResponse<T>
|
||||
|
||||
// 后端业务码非 0:当作错误抛出
|
||||
if (body && typeof body.code === 'number' && body.code !== 0) {
|
||||
throw new Error(body.message || '请求失败')
|
||||
}
|
||||
|
||||
return (body && typeof body.code === 'number' ? body.data : body) as T
|
||||
}
|
||||
|
||||
/** 401 钩子,由业务方注册(如跳转登录页) */
|
||||
export let onUnauthorized: (() => void) | null = null
|
||||
export function setOnUnauthorized(handler: () => void): void {
|
||||
onUnauthorized = handler
|
||||
}
|
||||
|
||||
// ---------- 便捷方法 ----------
|
||||
|
||||
export const http = {
|
||||
get: <T = unknown>(path: string, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'GET' }),
|
||||
post: <T = unknown>(path: string, data?: unknown, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'POST', data }),
|
||||
put: <T = unknown>(path: string, data?: unknown, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'PUT', data }),
|
||||
patch: <T = unknown>(path: string, data?: unknown, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'PATCH', data }),
|
||||
del: <T = unknown>(path: string, options?: RequestOptions) =>
|
||||
request<T>(path, { ...options, method: 'DELETE' }),
|
||||
}
|
||||
|
||||
export default http
|
||||
+9
-1
@@ -1,11 +1,19 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import type { DesignItem, OrderItem, AddressItem } from '../types'
|
||||
import type { DesignItem, OrderItem, AddressItem, StickerItem } from '../types'
|
||||
import { PRODUCT_ICON_MAP } from './productConfig'
|
||||
|
||||
// 重新导出类型,供各页面从 store 直接引用(修正原「声明但不导出」的编译错误)
|
||||
export type { DesignItem, OrderItem, AddressItem, StickerItem }
|
||||
|
||||
const THEME_KEY = 'smart_theme'
|
||||
const USER_KEY = 'smart_user_info'
|
||||
const ACTIVE_USER_KEY = 'smart_active_openid'
|
||||
|
||||
/** 判断 openid 是否为历史版 mock 残留(形如 mock_xxx) */
|
||||
export function isMockOpenid(openid?: string | null): boolean {
|
||||
return !!openid && (openid.startsWith('mock_') || openid.startsWith('mock-'))
|
||||
}
|
||||
|
||||
// ---------- 当前用户 openid 管理 ----------
|
||||
|
||||
function getActiveOpenid(): string {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
/** 实际生效主题对应的窗口/页面背景色 */
|
||||
export function themePageColor(theme: 'light' | 'dark'): string {
|
||||
return theme === 'dark' ? '#1e1e2f' : '#ffffff'
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步窗口/页面背景到当前主题:
|
||||
* - 自定义导航区(状态栏下方、胶囊按钮区域)在 iOS 上由 backgroundColorTop 控制,
|
||||
* 仅设 backgroundColor 覆盖不到,因此需要同时设置三项
|
||||
* - 下拉背景文字样式一并切换,避免深色模式下白字看不清
|
||||
*
|
||||
* @param bgOverride 传入时使用该背景色(用于强制顶部深色的页面,如商品详情)
|
||||
*/
|
||||
export function applyPageBackground(theme: 'light' | 'dark', bgOverride?: string) {
|
||||
const bg = bgOverride ?? themePageColor(theme)
|
||||
Taro.setBackgroundColor({
|
||||
backgroundColor: bg,
|
||||
backgroundColorTop: bg,
|
||||
backgroundColorBottom: bg
|
||||
})
|
||||
Taro.setBackgroundTextStyle({ textStyle: theme === 'dark' ? 'light' : 'dark' })
|
||||
}
|
||||
Reference in New Issue
Block a user