目录

1.前端发送请求生成订单

  1.前端点击支付按钮生成订单

  2.结算成功之后应该清除结算页面的数据

  3.后端计算结算页面总原价格和总的真实价格并存到数据库订单表中

2.优惠劵

  1.准备工作

  2.前端展示优惠券信息-初始化

  3.优惠券-前端获取优惠券数据+后端接口

  4.结算页面计算真实总价格

  5.优惠券是否真的能够使用+优惠劵前端计算

    1.优惠劵的选中效果

    2.不可点击的不应该具有点击效果

    3.优惠券箭头收缩之后 取消优惠券的选中状态

    4.当选中的优惠劵发生变化时 重新计算总价

  6.优惠劵后端对优惠劵进行校验

1.前端发送请求生成订单

1.前端点击支付按钮生成订单

昨日讲到了后端如何生成订单(order/add_money),但是前端的请求还没有发,现在来做一下前端发送请求来生成订单。

点击支付按钮,触发生成订单事件,生成一个订单

order.vue

<!-- html -->
<!-- 给支付按钮绑定一个事件 该事件向后端发起请求来生成订单 -->
<el-col :span="4" class="cart-pay"><span @click="payhander">支付</span></el-col>
// js
// 生成订单
payhander(){
let token = localStorage.token || sessionStorage.token;
this.$axios.post(`${this.$settings.Host}/order/add_money/`,{ // 生成订单需要提交的支付类型、优惠券、积分
"pay_type":this.pay_type,
"coupon":this.current_coupon,
"credit":0 },{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success('订单已经生成,马上跳转支付页面')
}).catch((error)=>{
this.$message.error(error.response.data.msg);
}) }

2.结算成功之后应该清除结算页面的数据

结算成功之后应该清除结算界面的数据,如果用户还想购买课程的话,应该去购物车去再次选中自己想要购买的商品再购买,然后再重新生成订单。

所以应该redis中删除用户选中的课程id,也就是selected_cart的数据

order/serializers.py

# serializers.py
def create(self, validated_data): # 结算成功之后,再清除
conn = get_redis_connection('cart')
conn.delete('selected_cart_%s' % user_id) return order_obj

3.后端计算结算页面总原价格和总的真实价格并存到数据库订单表中

order/serializers.py

def create(self, validated_data):
try:
# 生成订单号 [日期,用户id,自增数据] total_price = 0 # 总原价
total_real_price = 0 # 总真实价格 with transaction.atomic(): # 添加事务
# 生成订单,保存到数据库中
......
# 生成订单详情
......
# 计算所有课程的总原价
total_price += course_obj.price # 计算所有课程的总真实价格
total_real_price += course_obj.real_price(expire_id) # 将订单总原价和总真实价格存到数据库表中
order_obj.total_price = total_price
order_obj.real_price = total_real_price
order_obj.save() except Exception:
raise models.Order.DoesNotExist return order_obj

2.优惠劵

1.准备工作

1.创建coupon应用,并配置INSTALLAPP

python3 ../../manage.py startapp coupon

2.coupon/models.py

from django.db import models
from lyapi.utils.models import BaseModel
from users.models import User
from datetime import timedelta # Create your models here.
class Coupon(BaseModel):
"""优惠券"""
coupon_choices = (
(0, '折扣优惠'),
(1, '减免优惠')
)
name = models.CharField(max_length=32, verbose_name="优惠券标题")
coupon_type = models.SmallIntegerField(choices=coupon_choices, default=0, verbose_name="优惠券类型")
timer = models.IntegerField(verbose_name="优惠券有效期", default=7, help_text="默认当前优惠券7天有效,如果设置值为-1则表示当前优惠券永久有效")
condition = models.IntegerField(blank=True, default=0, verbose_name="满足使用优惠券的价格条件,如果设置值为0,则表示没有任何条件")
sale = models.TextField(verbose_name="优惠公式", help_text="""
*号开头表示折扣价,例如*0.82表示八二折;<br>
-号开头表示减免价,例如-10表示在总价基础上减免10元<br>
""") class Meta:
db_table = "ly_coupon"
verbose_name="优惠券"
verbose_name_plural="优惠券" def __str__(self):
return "%s" % (self.name) class UserCoupon(BaseModel):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="coupons", verbose_name="用户")
coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="users", verbose_name="优惠券")
start_time = models.DateTimeField(verbose_name="优惠策略的开始时间")
is_use = models.BooleanField(default=False,verbose_name="优惠券是否使用过") class Meta:
db_table = "ly_user_coupon"
verbose_name = "用户的优惠券"
verbose_name_plural = "用户的优惠券" def __str__(self):
return "优惠券:%s,用户:%s" % (self.coupon.name, self.user.username) @property
def end_time(self):
s_time = self.start_time
timer = self.coupon.timer #天数 return s_time + timedelta(days=timer)

优惠劵表结构设计

3.数据库迁移指令

python3 ../../manage.py makemigrations
python3 ../../manage.py migrate

4.adminx注册

coupon/adminx.py

import xadmin
from .models import Coupon
class CouponModelAdmin(object):
"""优惠券模型管理类"""
list_display = ["name","coupon_type","timer"]
xadmin.site.register(Coupon, CouponModelAdmin) from .models import UserCoupon
class UserCouponModelAdmin(object):
"""我的优惠券模型管理类"""
list_display = ["user","coupon","start_time","is_use"] xadmin.site.register(UserCoupon, UserCouponModelAdmin)

5.插入一些数据

INSERT INTO `ly_coupon` VALUES (1,1,1,0,'2019-08-21 15:59:04.568037','2019-08-21 15:59:04.568061','十元优惠券',1,30,10,'-10'),(2,2,1,0,'2019-08-21 15:59:33.764807','2019-08-21 15:59:33.764830','五十元优惠券',1,30,50,'-50'),(3,3,1,0,'2019-08-21 16:00:10.090100','2019-08-21 16:00:10.090126','9折优惠券',2,7,0,'*0.9');

INSERT INTO `ly_user_coupon` VALUES
(1,1,1,0,'2019-08-21 16:00:40.823977','2019-08-23 19:23:58.117600','2019-08-21 01:00:00.000000',1,3,1),
(2,2,1,0,'2019-08-21 16:00:49.868597','2019-08-22 09:37:46.010037','2019-10-01 01:00:00.000000',0,2,1),
(3,3,1,0,'2019-08-21 16:01:09.051862','2019-08-23 19:31:02.605253','2019-08-21 01:01:00.000000',1,1,1),
(4,5,1,0,'2019-08-22 08:48:56.406671','2019-08-22 08:48:56.406694','2019-08-22 17:48:00.000000',0,2,1);

2.前端展示优惠券信息-初始化

<!-- html -->
<div class="discount">
<div id="accordion">
<div class="coupon-box">
<div class="icon-box">
<span class="select-coupon">使用优惠劵:</span>
<a class="select-icon unselect" :class="use_coupon?'is_selected':''" @click="use_coupon=!use_coupon"><img class="sign is_show_select" src="../../static/img/12.png" alt=""></a>
<span class="coupon-num">有0张可用</span>
</div>
<p class="sum-price-wrap">商品总金额:<span class="sum-price">0.00元</span></p>
</div>
<div id="collapseOne" v-if="use_coupon">
<ul class="coupon-list" v-if="coupon_list.length>0">
<li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
<p class="coupon-name">8.5折优惠券</p>
<p class="coupon-condition">满0元可以使用</p>
<p class="coupon-time start_time">开始时间:</p>
<p class="coupon-time end_time">过期时间:</p>
</li> </ul>
<div class="no-coupon" v-if="coupon_list.length<1">
<span class="no-coupon-tips">暂无可用优惠券</span>
</div>
</div>
</div>
<div class="credit-box">
<label class="my_el_check_box"><el-checkbox class="my_el_checkbox" v-model="use_credit"></el-checkbox></label>
<p class="discount-num1" v-if="!use_credit">使用我的贝里</p>
<p class="discount-num2" v-else><span>总积分:100,已抵扣 ¥0.00,本次花费0积分</span></p>
</div>
<p class="sun-coupon-num">优惠券抵扣:<span>0.00元</span></p>
</div>

前端展示优惠劵信息-HTML

/* css */
.coupon-box{
text-align: left;
padding-bottom: 22px;
padding-left:30px;
border-bottom: 1px solid #e8e8e8;
}
.coupon-box::after{
content: "";
display: block;
clear: both;
}
.icon-box{
float: left;
}
.icon-box .select-coupon{
float: left;
color: #666;
font-size: 16px;
}
.icon-box::after{
content:"";
clear:both;
display: block;
}
.select-icon{
width: 20px;
height: 20px;
float: left;
}
.select-icon img{
max-height:100%;
max-width: 100%;
margin-top: 2px;
transform: rotate(-90deg);
transition: transform .5s;
}
.is_show_select{
transform: rotate(0deg)!important;
}
.coupon-num{
height: 22px;
line-height: 22px;
padding: 0 5px;
text-align: center;
font-size: 12px;
float: left;
color: #fff;
letter-spacing: .27px;
background: #fa6240;
border-radius: 2px;
margin-left: 20px;
}
.sum-price-wrap{
float: right;
font-size: 16px;
color: #4a4a4a;
margin-right: 45px;
}
.sum-price-wrap .sum-price{
font-size: 18px;
color: #fa6240;
} .no-coupon{
text-align: center;
width: 100%;
padding: 50px 0px;
align-items: center;
justify-content: center; /* 文本两端对其 */
border-bottom: 1px solid rgb(232, 232, 232);
}
.no-coupon-tips{
font-size: 16px;
color: #9b9b9b;
}
.credit-box{
height: 30px;
margin-top: 40px;
display: flex;
align-items: center;
justify-content: flex-end
}
.my_el_check_box{
position: relative;
}
.my_el_checkbox{
margin-right: 10px;
width: 16px;
height: 16px;
}
.discount{
overflow: hidden;
}
.discount-num1{
color: #9b9b9b;
font-size: 16px;
margin-right: 45px;
}
.discount-num2{
margin-right: 45px;
font-size: 16px;
color: #4a4a4a;
}
.sun-coupon-num{
margin-right: 45px;
margin-bottom:43px;
margin-top: 40px;
font-size: 16px;
color: #4a4a4a;
display: inline-block;
float: right;
}
.sun-coupon-num span{
font-size: 18px;
color: #fa6240;
}
.coupon-list{
margin: 20px 0;
}
.coupon-list::after{
display: block;
content:"";
clear: both;
}
.coupon-item{
float: left;
margin: 15px 8px;
width: 180px;
height: 100px;
padding: 5px;
background-color: #fa3030;
cursor: pointer;
}
.coupon-list .active{
background-color: #fa9000;
}
.coupon-list .disable{
cursor: not-allowed;
background-color: #fa6060;
}
.coupon-condition{
font-size: 12px;
text-align: center;
color: #fff;
}
.coupon-name{
color: #fff;
font-size: 24px;
text-align: center;
}
.coupon-time{
text-align: left;
color: #fff;
font-size: 12px;
}
.unselect{
margin-left: 0px;
transform: rotate(-90deg);
}
.is_selected{
transform: rotate(-1turn)!important;
}
.coupon-item p{
margin: 0;
padding: 0;
}

前端展示优惠劵信息样式-CSS

3.优惠券-前端获取优惠券数据+后端接口

1.优惠劵后端接口

coupon/urls.py

# coupon/urls.py
from django.urls import path,re_path
from . import views urlpatterns = [
re_path(r'list/', views.CouponView.as_view(),), ]

lyapi/urls.py

# lyapi/urls.py

from xadmin.plugins import xversion
xversion.register_models() urlpatterns = [
......
path(r'coupon/',include('coupon.urls')), ]

coupon/views.py

# coupon/views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView
from . import models
from rest_framework.permissions import IsAuthenticated from .serializers import UserCouponModelSerializer class CouponView(ListAPIView): serializer_class = UserCouponModelSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self): return models.UserCoupon.objects.filter(is_show=True,is_deleted=False,is_use=False, user_id=self.request.user.id)

coupon/serializers.py

# coupon/serializers.py
from rest_framework import serializers
from .models import Coupon, UserCoupon
class CouponModelSerializer(serializers.ModelSerializer):
class Meta:
model = Coupon
fields = ("name","coupon_type","timer","condition","sale") class UserCouponModelSerializer(serializers.ModelSerializer):
coupon = CouponModelSerializer()
class Meta:
model = UserCoupon
fields = ("id","start_time","coupon","end_time")

2.间接计算优惠劵的结束时间

# coupon/models.py
class UserCoupon(BaseModel):
......A
@property # 调用类中该方法时不需要加大括号 将其视作为属性
def end_time(self):
s_time = self.start_time
timer = self.coupon.timer #天数
return s_time + timedelta(days=timer)

3.前端发送请求获取优惠券数据

order.vue

// order.vue -js
get_user_coupon(){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/coupon/list/`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.coupon_list = res.data; // 获取到的优惠劵数据
}).catch((error)=>{
this.$message.error('优惠券获取错误')
}) },
<!-- order.vue  html  -->
<ul class="coupon-list" v-if="coupon_list.length>0">
<li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
<p class="coupon-name">{{coupon.coupon.name}}</p>
<p class="coupon-condition">满{{coupon.coupon.condition}}元可以使用</p>
<p class="coupon-time start_time">开始时间:{{coupon.start_time.replace('T',' ')}}</p>
<p class="coupon-time end_time">过期时间:{{coupon.end_time.replace('T',' ')}}</p>
</li> </ul>

4.结算页面计算真实总价格

之前的结算页面只差真实的总价格没有计算了。现在通过后端计算真实总价格然后发送给前端。

1.后端计算好结算页面的总价格和总真实价格

cart/views.py

# cart/views.py

# 结算页面数据
def show_pay_info(self,request):
user_id = request.user.id
conn = get_redis_connection('cart')
select_list = conn.smembers('selected_cart_%s' % user_id)
data = [] ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} total_price = 0
total_real_price = 0 for cid, eid in ret.items():
expire_id = int(eid.decode('utf-8'))
if cid in select_list: course_id = int(cid.decode('utf-8'))
course_obj = models.Course.objects.get(id=course_id) if expire_id > 0:
expire_obj = models.CourseExpire.objects.get(id=expire_id) # 查询到每个已勾选课程的真实价格
course_real_price = course_obj.real_price(expire_id) # 计算出所有课程的总真实价格
total_real_price += course_real_price data.append({
'course_id':course_obj.id,
'name':course_obj.name,
'course_img':contains.SERVER_ADDR + course_obj.course_img.url , # 结算页面的每条数据都显示为每个课程的真实价格
'real_price':course_real_price, 'expire_text':expire_obj.expire_text,
})
else:
course_real_price = course_obj.real_price(expire_id)
total_real_price += course_real_price
data.append({
'course_id': course_obj.id,
'name': course_obj.name,
'course_img': contains.SERVER_ADDR + course_obj.course_img.url,
'real_price': course_real_price,
'expire_text': '永久有效',
}) return Response({'data':data,'total_real_price':total_real_price})

2.前端发送请求 获取结算页面的数据、总价格、总真实价格

order.vue

// Order.vue
get_order_data(){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/cart/expires/`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
// 获取到课程名称、课程封面图、有效期信息等
this.course_list = res.data.data; // 获取到后端发送过来的总真实价格
this.total_real_price = res.data.total_real_price; // 获取到后端发送过来的总原价格
this.total_price = res.data.total_real_price;
})
},

5.优惠券是否真的能够使用+优惠劵前端计算

1.优惠劵的选中效果

不在活动范围内的优惠劵应该设置不可选中的效果

如果选中了当期优惠劵,则应该给优惠劵设置为选中的效果

order.vue

// order.vue  js
select_coupon(index,coupon_id){ // 拿到你当前点击的那条优惠劵数据
let current_c = this.coupon_list[index]
if (this.total_real_price < current_c.coupon.condition){
return 'disable'
} // '/1000'拿到时间戳
let current_time = new Date() / 1000;
let s_time = new Date(current_c.start_time) / 1000
let e_time = new Date(current_c.end_time) / 1000 // 如果优惠劵不在活动时间范围内 则设置效果为不可选中
if (current_time < s_time || current_time > e_time){
return 'disable'
} // 如果优惠劵是当前被选中的优惠劵 则设置效果为选中
if (this.current_coupon === coupon_id){
return 'active'
} return ''
},
<!-- order.vue  html -->
<li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">

2.不可点击的不应该具有点击效果

change_coupon(index,coupon_id){
let current_c = this.coupon_list[index] // 如果优惠劵不符合条件 则优惠劵是不可点击的
if (this.total_real_price < current_c.coupon.condition){
return false
}
let current_time = new Date() / 1000;
let s_time = new Date(current_c.start_time) / 1000
let e_time = new Date(current_c.end_time) / 1000 // 如果优惠劵不符合条件 则优惠劵是不可点击的
if (current_time < s_time || current_time > e_time){
return false
} this.current_coupon = coupon_id;
this.coupon_obj = current_c; },

3.优惠券箭头收缩之后 取消优惠券的选中状态

order.vue

watch:{
use_coupon(){
// 如果点击箭头收缩 那么就将优惠劵的选中状态取消
if (this.use_coupon === false){
this.current_coupon = 0; }
},

4.当选中的优惠劵发生变化时 重新计算总价

order.vue

// 当选中的优惠券发生变化时,重新计算总价
current_coupon(){
this.cal_total_price();
} methods: { cal_total_price(){
// 当用户选中了某个优惠劵
if (this.current_coupon !== 0){ // 1.获取用户使用优惠劵前的真实价格
let tt = this.total_real_price; // 2.获取当前优惠劵的优惠公式
let sales = this.coupon_obj.coupon.sale; // 3.取出优惠公式的数字部分
let d = parseFloat(this.coupon_obj.coupon.sale.substr(1));
if (sales[0] === '-'){
tt = this.total_real_price - d
}else if (sales[0] === '*'){
tt = this.total_real_price * d
}
this.total_price = tt; } },

6.优惠劵后端对优惠劵进行校验

前端虽然对优惠劵进行校验,但是前端所显示的都是虚假的,后端也要对优惠劵进行校验。

class OrderModelSerializers:
def validate(self, attrs): # 验证支付方式是否合法
pay_type = int(attrs.get('pay_type',0)) # if pay_type not in [i[0] for i in models.Order.pay_choices]:
raise serializers.ValidationError('支付方式不对!') # 优惠券校验,看看是否过期了等等
coupon_id = attrs.get('coupon', 0)
if coupon_id > 0:
try:
user_conpon_obj = UserCoupon.objects.get(id=coupon_id)
except:
raise serializers.ValidationError('订单创建失败,优惠券id不对')
# 校验优惠劵是否在活动时间范围内
now = datetime.datetime.now().timestamp()
start_time = user_conpon_obj.start_time.timestamp()
end_time = user_conpon_obj.end_time.timestamp()
if now < start_time or now > end_time:
raise serializers.ValidationError('订单创建失败,优惠券不在使用范围内,滚犊子') # todo 积分上限校验 return attrs

day86:luffy:前端发送请求生成订单&结算页面优惠劵的实现的更多相关文章

  1. java web前端发送请求的4种方式

    表单 action, 链接href,js 绑定按钮 ajax绑定标签 <h1>通过表单提交参数</h1> <form action="/day46_v1/Ser ...

  2. day87:luffy:结算页面积分&支付宝接口

    目录 1.积分 2.支付 1.积分 1.关于积分的表结构 1.在user表中添加credit字段 + 设计一个积分的表结构 user/models.py class User(AbstractUser ...

  3. java后台发送请求并获取返回值

    项目中需要前端发送请求给后端,而后端需要从另一个平台中取数据然后再透传给前端,通过下述代码将其实现.在此记录一下. package com.autotest.utils; import java.io ...

  4. 用vue.js重构订单计算页面

    在很久很久以前做过一个很糟糕的订单结算页面,虽然里面各区域(收货地址)使用模块化加载,但是偶尔会遇到某个模块加载失败的问题导致订单提交的数据有误. 大致问题如下: 1. 每个模块都采用usercont ...

  5. 向.net后端发送请求获取数据,在前端动态填充表格

    实现效果 实现步骤 通过Ajax请求的方式 1.在前端定义Table 2.通过Ajax向.net后端发送数据请求 3.在.net后端定义方法供前端调用,并返回所需的数据 4.通过构造字符串的方式,将后 ...

  6. angularjs和jquery前端发送以http请求formdata数据

    formdata是比较常见的前端发送给后端的请求,不仅可以上传数据,而且同时可以上传文件. jquery使用http请求上传formdata数据的方法: var formdata = new Form ...

  7. Java生成二进制文件与Postman以二进制流的形式发送请求

    业务描述: 模拟终端(智能家居)发送HTTP POST请求,请求参数为二进制流:而且,二进制流是加密后的数据,因此调试分两步: 1.Java代码生成加密后数据,并保存为二进制流文件 (电脑上的图片就是 ...

  8. Query通过Ajax向PHP服务端发送请求并返回JSON数据

    Query通过Ajax向PHP服务端发送请求并返回JSON数据 服务端PHP读取MYSQL数据,并转换成JSON数据,传递给前端Javascript,并操作JSON数据.本文将通过实例演示了jQuer ...

  9. 爬虫模块介绍--request(发送请求模块)

    爬虫:可见即可爬   # 每个网站都有爬虫协议 基础爬虫需要使用到的三个模块 requests 模块  # 模拟发请求的模块 PS:python原来有两个模块urllib和urllib的升级urlli ...

随机推荐

  1. 介绍使用Cordova和Web Starter Kit开发Android

    介绍 如今,每个人都想制作移动应用程序,为什么不呢?世界上有更多的移动设备比任何其他用户设备.Android尤其流行,但是为什么不从一个众所周知的跨平台应用的基础开始呢?Android的开发显然比其他 ...

  2. LR Optimization-Based Estimator Design for Vision-Aided Inertial Navigation

    Abstract 我们设计了一个 hybrid 估计器, 组合了两种算法, sliding-window EKF 和 EKF-SLAM. 我们的结果表示, hybrid算法比单一的好. 1. Intr ...

  3. JS学习之路一

    1.准备 ①安装vscode 地址:https://vscode.en.softonic.com/ ②安装node.js node -v npm -v 地址:https://nodejs.org/zh ...

  4. CVE-2010-2883-CoolType.dll缓冲区溢出漏洞分析

    前言 此漏洞是根据泉哥的<漏洞战争>来学习分析的,网上已有大量分析文章在此只是做一个独立的分析记录. 复现环境 操作系统 -> Windows XP Sp3 软件版本 -> A ...

  5. go内建方法 append copy delete

    package mainimport "fmt"func main() { testAppend() testCopy() testDelete()}func testAppend ...

  6. lerna管理前端模块实践

    最近在工作中使用了 lerna 进行前端包的管理,效率提升了很多.所以打算总结一下最近几个月使用 lerna 的一些心得.有那些不足的地方,请包涵. 该篇文章主要包括在使用 lerna 的一些注意事项 ...

  7. CentOS8安装本地mail工具-mailx-12.5-29.el8.x86_64

    概述 服务器需要发告警邮件 查找是否已安装 [root@C8-1 ~]# type mail -bash: type: mail: not found [root@C8-1 ~]# which mai ...

  8. Storage API简介和存储限制与逐出策略

    目录 简介 常用的客户端存储方式 data storage的类型 逐出策略 Storage API estimate persist persisted 综合使用 总结 简介 对于现代浏览器来说,为了 ...

  9. zookeeper核心之ZAB协议就这么简单!

    背景 我们都知道 Zookeeper 是基于 ZAB 协议实现的,在介绍 ZAB 协议之前,先回顾一下 Zookeeper 的起源与发展. Zookeeper 究竟是在什么样的时代背景下被提出?为了解 ...

  10. 【Flutter 面试】main入口函数会被调用几次

    老孟导读:这是一个读者面试时被问到的问题,这个问题前段时间我也在VIP交流群和大家一起探讨过. 这个问题涉及引擎的相关知识,如果不了解相关知识,很难回答正确,因为不管说调用几次都是错误的,下面来看一下 ...