vip.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <template>
  2. <view class="vip">
  3. <view class="vip_top">
  4. <headerInfo title="会员中心" :isBack="true"></headerInfo>
  5. <view class="flex-center-between color_fff mg20">
  6. <u-avatar @click="toUser"
  7. size="100rpx"
  8. shape="circle"
  9. :src="appStore?.userInfo?.avatar"
  10. mode="aspectFill"
  11. ></u-avatar>
  12. <view class="flex_1 flex-center-between ml20">
  13. <view class="flex-column flex_1">
  14. <view>
  15. <text class="font_size32 nickName bold mr20">{{appStore.userInfo?.nickName}}</text>
  16. <text class="memberPlanName" v-if="appStore.userInfo?.memberStatus==1">{{appStore.userInfo?.memberPlanName}}</text>
  17. </view>
  18. <text class="font_size32 nickName mt10" v-if="appStore.userInfo?.memberStatus==1">
  19. <text class="vipTime">到期时间: {{appStore.userInfo?.memberExpire}}</text>
  20. </text>
  21. <text class="font_size32 nickName mt10" v-else>未开通VIP,无法享受会员</text>
  22. </view>
  23. <image src="/static/img/vip/vip.png" mode="" style="width:204rpx;height:204rpx;"/>
  24. </view>
  25. </view>
  26. </view>
  27. <view class="padding20 vip_content">
  28. <!-- 会员充值 -->
  29. <view class="flex-center mt10">
  30. <view class="flex-center-between">
  31. <view class="line_vip"></view>
  32. <text class="title_vip font_size32 bold">会员充值</text>
  33. <view class="line_vip reverse"></view>
  34. </view>
  35. </view>
  36. <view class="mt20 vip_pay">
  37. <view class="grid-container3 mt30">
  38. <view class="flex-column-center border_radius_20 paddingTB20 plan"
  39. :class="{'active':planId==item.planId}"
  40. @click="planId=item.planId;orderAmt=item.price;"
  41. v-for="(item, index) in planList" :key="index">
  42. <view class="bold font_size28 planName">{{item.planName}}</view>
  43. <view class="font_size35 mt20 color_price"><text class="font_size20">¥</text>{{item.price}}</view>
  44. <view class="font_size20 mt20 gray line_through">¥{{item.originalPrice}}</view>
  45. <view class="bold font_size28 planName mt20">{{item.durationUnit=='lifetime'?'':item.duration}}{{item.durationUnitName}}</view>
  46. <view class="hot" v-if="item.durationUnit=='lifetime'">
  47. <image src="/static/img/vip/fire.png" mode="" style="width:20rpx;height:20rpx"/>
  48. <text class="font_size20 color_fff">推荐</text>
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. <!-- 会员专享4大权益 -->
  54. <view class="flex-center mt30">
  55. <view class="flex-center-between">
  56. <view class="line_vip"></view>
  57. <text class="title_vip font_size32 bold">会员专享4大权益</text>
  58. <view class="line_vip reverse"></view>
  59. </view>
  60. </view>
  61. <view class="grid-container mt30">
  62. <view class="flex-center-between border_radius_20 bg_color_fff padding20" v-for="(item, index) in quanyilList" :key="index">
  63. <image :src="item.icon" class="icon mr20" mode="" />
  64. <view class="flex_1">
  65. <view class="bold font_size28">{{item.title}}</view>
  66. <view class="font_size24 mt10 gray">{{item.content}}</view>
  67. </view>
  68. </view>
  69. </view>
  70. <!-- 付款方式 -->
  71. <view class="padding30 border_radius_20 bg_color_fff mt20" v-if="isEdit!=1">
  72. <view class="font_size32 bold mb20">付款方式</view>
  73. <view class="flex-center-between mt20 paddingTB20" v-for="(item, index) in payments" :key="index" @click="payType=item.value">
  74. <view class="flex-center-between">
  75. <image :src="item.icon" mode="widthFix" class="weixin_img mr20"></image>
  76. <text>{{item.label}}</text>
  77. <text class="color_price ml20 font_size24" v-if="index==1"> 余额:{{appStore.userInfo?.rechargeBalance}} ({{appStore.moneyUnit}})</text>
  78. </view>
  79. <view class="payment-radio">
  80. <view
  81. class="radio-circle"
  82. :class="{ checked: payType === item.value }"
  83. ></view>
  84. </view>
  85. </view>
  86. </view>
  87. <agreeVip ref="agreeVip" @setAloneChecked="setAloneChecked"></agreeVip>
  88. </view>
  89. <!-- foot -->
  90. <view class="foot bg_color_fff">
  91. <view class="order_btn paddingTB20 flex_1 text_align_center" @click="submitForm">
  92. <text>{{appStore.userInfo?.memberStatus==1?'续费':'立即解锁'}}</text>
  93. <text>¥{{orderAmt}}</text>
  94. </view>
  95. </view>
  96. </view>
  97. </template>
  98. <script setup>
  99. import { ref } from "vue";
  100. import { wxPay,getUserInfo } from "@/utils/util.js";
  101. import headerInfo from "@/components/headerInfo.vue";
  102. import { createOrder } from "@/api/order";
  103. import { useToast } from "@/hooks/useToast";
  104. const { Toast } = useToast();
  105. import { getPlanList } from "@/api/user";
  106. import { agreeVip } from "@/components/agreeVip"
  107. import { onLoad } from '@dcloudio/uni-app'
  108. import { useAppStore } from "@/stores/app"
  109. const appStore = useAppStore();
  110. const aloneChecked = ref(false);
  111. const planId = ref('');
  112. const orderId = ref('');
  113. const orderAmt = ref('');
  114. const planList = ref([]);
  115. const payType = ref(0);
  116. const payments = ref([
  117. {
  118. value: 0,
  119. label: "微信支付",
  120. icon: "/static/img/weixin.png"
  121. },
  122. {
  123. value: 1,
  124. label: "钱包支付",
  125. icon: "/static/img/qianbao.png"
  126. },
  127. ]);
  128. const quanyilList = ref([
  129. {
  130. title: "问答无限制",
  131. content: "最新AI聊天模型接口",
  132. icon: "/static/img/vip/question.png"
  133. },
  134. {
  135. title: "学习帮手",
  136. content: "帮您提高创作效率",
  137. icon: "/static/img/vip/xuexi.png"
  138. },
  139. {
  140. title: "疑问解答",
  141. content: "互动性聊天体验",
  142. icon: "/static/img/vip/yiwen.png"
  143. },
  144. {
  145. title: "私人助理",
  146. content: "提供最优解决方案",
  147. icon: "/static/img/vip/siren.png"
  148. },
  149. ]);
  150. async function submitForm(){
  151. if(!aloneChecked.value){
  152. Toast({ title: "请先阅读并同意VIP会员开通协议", icon: 'none' });
  153. return;
  154. }
  155. const res = await createOrder({
  156. payMethod:payType.value,//支付方式 0微信 1余额
  157. productType:0,//商品类型 0购买会员 1充值
  158. productId:planId.value,
  159. orderNum:1,//订单数量
  160. orderAmt:orderAmt.value//订单金额
  161. });
  162. orderId.value = res.data.orderId;
  163. // 余额支付
  164. if(payType.value==1){
  165. //1成功 0失败;
  166. const paySuccess = res.data.paySuccess;
  167. to_success_pay({isSuccess:paySuccess?1:0});
  168. }else{
  169. const payInfo = res.data.payData.prepayWithRequestPaymentResponse
  170. wxPay({
  171. timeStamp:payInfo.timeStamp,
  172. nonceStr:payInfo.nonceStr,
  173. packageVal:payInfo.packageVal,
  174. signType:payInfo.signType,
  175. paySign:payInfo.paySign,
  176. },to_success_pay);
  177. }
  178. }
  179. function to_success_pay({isSuccess}){
  180. console.log('to_success_pay',isSuccess);
  181. uni.navigateTo({
  182. url: `/pages/recharge/success_pay?orderId=${orderId.value}&isSuccess=${isSuccess}&payMethod=${payType.value}`
  183. })
  184. }
  185. onLoad(() => {
  186. getPlanListFn();
  187. });
  188. function getPlanListFn(){
  189. getPlanList().then(res => {
  190. if(res.code == 200){
  191. planList.value = res.data || [];
  192. //默认单位是durationUnit=='lifetime'
  193. if(planList.value.length>0){
  194. let plan = planList.value.find(item=>item.durationUnit=='lifetime');
  195. planId.value = plan?.planId || planList.value[0].planId;
  196. orderAmt.value = plan?.price || planList.value[0].price;
  197. }
  198. }
  199. })
  200. }
  201. function setAloneChecked(value){
  202. aloneChecked.value = value;
  203. }
  204. async function toUser(){
  205. uni.navigateTo({
  206. url: '/pages/user/user'
  207. });
  208. }
  209. </script>
  210. <style lang="less" scoped>
  211. .vip{
  212. height: 100vh;
  213. background: #f5f5f5;
  214. .vip_top{
  215. //#51402E #2B2724 渐变色,从上向下
  216. background: linear-gradient(90deg, #51402E 0%, #2B2724 100%);
  217. padding: 0 0 50rpx 30rpx;
  218. .nickName{
  219. color: #DDD3D0;
  220. }
  221. }
  222. .vip_content{
  223. position: relative;
  224. top: -40rpx;
  225. border-radius: 48rpx 48rpx 0 0;
  226. background: #f5f5f5;
  227. padding-bottom: 100rpx;
  228. .title_vip{
  229. color: #333333 ;
  230. margin: 0 30rpx;
  231. }
  232. .line_vip{
  233. width: 92rpx;
  234. height: 6rpx;
  235. background: linear-gradient(90deg, #D8D8D8 0%, #333333 100%);
  236. &.reverse{
  237. transform: rotate(180deg);
  238. }
  239. }
  240. }
  241. .gray{
  242. color: #999;
  243. }
  244. .grid-container{
  245. gap: 20rpx;
  246. .icon{
  247. width: 70rpx;
  248. height: 70rpx;
  249. }
  250. }
  251. .vip_pay{
  252. .plan{
  253. padding: 30rpx 20rpx 40rpx;
  254. border:1px solid #CCCCCC ;
  255. position: relative;
  256. background-color: #ffffff;
  257. &.active{
  258. background-color: #FEF7EA ;
  259. border-color: #874605
  260. }
  261. .color_price{
  262. color: #874605;
  263. }
  264. .planName{
  265. color: #333333;
  266. }
  267. .hot{
  268. position: absolute;
  269. top: -20rpx;
  270. left: 0rpx;
  271. background: linear-gradient(90deg, #2B2724 0%, #503F2E 100%);
  272. color: #fff;
  273. font-size: 20rpx;
  274. padding: 5rpx 10rpx;
  275. border-radius: 10rpx 0 10rpx 0;
  276. }
  277. }
  278. }
  279. .weixin_img{
  280. width: 60rpx;
  281. height: 60rpx;
  282. }
  283. .order_btn ,.memberPlanName{
  284. background: linear-gradient(90deg, #FDE492 0%, #FDB85D 100%);
  285. color: #874605 ;
  286. }
  287. .memberPlanName{
  288. padding: 5rpx 10rpx;
  289. border-radius: 20rpx;
  290. font-size: 24rpx;
  291. }
  292. }
  293. </style>