Django 仿博客园练习

news2025/1/16 3:56:59

数据库搭建

在这里插入图片描述

部分功能介绍

【一】注册

(1)效果显示、简单简介

  • 主要亮点
    1. 结合了layui和forms组件
    2. 默认头像可以随着性别的选择发生改变
    3. 自定义头像可以实时更新显示
    4. forms组件报错信息可以局部刷新显示在对应框体下面
      • 没有直接使用layui的前端验证
      • 后端验证更加安全
    5. 报错信息随鼠标的聚焦可以消失

请添加图片描述

(2)重要代码逻辑梳理

(2.1)头像随性别切换
  1. 首先给性别按钮绑定事件监听
  2. 根据当前的性别进行进行img标签的src属性进行修改
<script>
{#头像随性别切换#}
let genderButtons = document.getElementsByName('gender');
genderButtons.forEach(function (button) {
    button.addEventListener('click', function () {
        {#定义默认路径#}
        let defaultMaleAvatar = '/static/avatar/default_male.png'
        let defaultFemaleAvatar = '/static/avatar/default_female.png'
        {#获取当前的值进行修改#}
        let selectGender = $(this).val();
        let avatarImg = $("#avatarImg")
        if (selectGender === 'm') {
            avatarImg.attr('src', defaultMaleAvatar)
        } else if (selectGender === 'f') {
            avatarImg.attr('src', defaultFemaleAvatar)
        }
    });
});
</script>
(2.2)自定义头像预览
  1. 创建FileReader对象
  2. 获取当前头像数据$()[0].files[0]
  3. 转换读取文件内容为DataURL
  4. FileReader对象调用方法onload实时预览
<script>
{#头像预览#}
$("#selectAvatar").change(() => {
    {#创建FileReader对象#}
    let fileReader = new FileReader();
    {#读取头像#}
    let avatarData = $("#selectAvatar")[0].files[0];
    {#读取文件内容转换为Data URL对象#}
    fileReader.readAsDataURL(avatarData)
    {#重新赋值,实现预览功能#}
    fileReader.onload = () => {
        $("#avatarImg").attr('src', fileReader.result)
    }
})
</script>
(2.3)报错信息预览、添加报错红框
  • 视图层代码和介绍
def register(request):
    register_form = RegisterForm()
    if request.method == "POST" and request.is_ajax():
        register_form = RegisterForm(request.POST)
        # 使用forms组件校验,不合法返回错误信息
        if not register_form.is_valid():
            return json_response(code=2001, errors=register_form.errors)
        # 拿出所有检验通过的数据
        clean_data = register_form.cleaned_data
        # 合法信息需要抛出confirm_password,userinfo表中没有
        clean_data.pop('confirm_password')
        # 获取头像数据
        avatar = request.FILES.get("avatar")
        # 拿不到数据就用默认头像
        if not avatar:
            if clean_data.get('gender') == 'm':
                clean_data['avatar'] = "static/avatar/default_male.png"
            else:
                clean_data['avatar'] = "static/avatar/default_female.png"
        else:
            clean_data['avatar'] = avatar
        # 注册,密码加密处理,clean_data解压赋值
        user_obj = UserInfo.objects.create_user(**clean_data)
        # 反向解析跳转地址
        next_url = reverse("login")
        return json_response(message=f"{user_obj.username}注册成功", next_url=next_url)

    return render(request, 'register.html', locals())
  1. 将视图层返回的报错信息response.errors
  2. 根据前端页面的文本框的ID进行标签查找
  3. 链式操作
    • 找到span标签添加errroMessage
    • 找到input标签添加layui红框样式
$.each(response.errors, (spanId, errorMessage) => {
    let tagId = "#id_" + spanId
    {#根据id找到span添加报错信息#}
    {#找到input的标签添加红框#}    $(tagId).next().next().text(errorMessage[0]).parent().find('input').addClass("layui-form-danger")
})

【二】登录

(1)效果显示、简单介绍

  • 主要亮点是
    • 验证码的处理
    • 背景白色、字符颜色偏暗且不会出现很亮的颜色
    • 前端页面验证码刷新方法简单,点击图片即可
    • 验证码图片更新方式简单,在标签内添加事件即可

在这里插入图片描述

(2)重要代码逻辑梳理

(2.1)验证码生成

  1. 使用PIL模块中Image对象生成白板
  2. 使用PIL模块中ImageDraw对象根据白板创建画笔
  3. 使用PIL模块中的ImageFont对象指定画笔的字体和大小
  4. 使用循环和随机颜色的方式
    • 依次在不同的位置绘画不同颜色的验证码字符
  5. 再次使用for循环和随机颜色添加背景噪点
  6. 使用IO模块的BytesIO对象创建内存缓存区
  7. 将图片对象保存为PNG格式
  8. 将验证码字符和验证码图片字节流数据返回
import random
import string

from PIL import Image, ImageFont, ImageDraw
# Image:生成图片
# ImageDraw:图片内容绘制
# ImageFont:字体样式

# BytesIO:临时存储数据,返回二进制数据
from io import BytesIO, StringIO

# 列表推导式生成随机颜色
# 高亮度的还是少一点的好
def rgb_number():
    return tuple([random.randint(0, 200) for _ in range(3)])

def create_captcha(img_type="RGB", img_size=(310, 38)):
    # 白板对象
    # 图片类型,图片大小,图片颜色
    # img_obj = Image.new(img_type, img_size, rgb_number())
    img_obj = Image.new(img_type, img_size, color=(241, 241, 241))  # 白板
    # 画笔对象
    img_draw = ImageDraw.Draw(img_obj)
    # 指定字体和大小
    img_font = ImageFont.truetype('static/font/汉仪晴空体简.ttf', 30)

    captcha = ''
    for i in range(4):
        choices_list = list(string.digits + string.ascii_letters)
        temp_captcha = random.choice(choices_list)
        # 开始绘制
        # 位置、字符、颜色、字体
        img_draw.text((i * 30 + 10, 2), temp_captcha, rgb_number(), img_font)
        captcha += temp_captcha

    # 添加噪点
    for _ in range(100):
        x = random.randint(0, img_size[0] - 1)
        y = random.randint(0, img_size[1] - 1)
        img_draw.point((x, y), fill=rgb_number())

    # 文件对象的内存缓冲区,可以用来读写二进制数据
    io_obj = BytesIO()
    # 将图像对象img_obj保存为PNG格式,并将保存后的数据写入到之前创建的BytesIO对象io_obj中。
    img_obj.save(io_obj, 'png')
    # 从BytesIO对象io_obj中获取保存的图像数据,并将其赋值给变量img_data
    img_data = io_obj.getvalue()
    
    # 返回验证码和图片数据
    return captcha, img_data

(2.2)验证码前端刷新方法

  • 视图层
    • 指定验证码图片大小
    • 将验证码添加到session中用于校验
    • 返回HttpResponse()对象
def get_captcha(request):
    # 获取验证码和图片
    code, img_data = create_captcha(img_size=(122, 36))
    # 将验证码保存在session中用于验证
    request.session['captcha'] = code
    return HttpResponse(img_data)
  • 前端
    • src为视图层的路由映射
    • style指定大小
    • onclick绑定事件
      • 由于在当前路由后添加?携带其他信息仍能找到指定路由
      • 所以在每次点击之后在其后面添加时间就可以完整刷新的功能
<img src="{% url 'get_captcha' %}" style="width: 100%; height: 38px" id="captcha"
     onclick="this.src = '{% url 'get_captcha' %}' + '?t='+ new Date().getTime();">

【三】修改头像

(1)效果显示、简单介绍

  • 使用的是Bootstrap3的模态框

  • 具有登录渲染当前头像的功能

  • 亮点:

    • 后端保存图像的方法写了两种

在这里插入图片描述

(2)修改头像代码梳理

(2.1)使用request修改登录人的头像

  • 首先说弊端,只能修改登录人的头像
  • 优势:简单,自动保存图片,图片名称处理(上传相同的头像保存不同的文件名)
  • 代码逻辑
    1. 首先获取头像数据
    2. 然后使用request.user进行头像的修改
    3. 最后一定要执行save方法保存
@csrf_exempt
@login_required
def set_avatar(request):
    if request.is_ajax():
        if request.FILES:
            # 获取头像
            avatar = request.FILES.get('avatar')
            # 使用保存更改
            user_obj = request.user
            user_obj.avatar = avatar
            user_obj.save()

            return json_response(message='头像修改成功')
        return json_response(code=2001, error="请添加新的头像")
    return json_response(code=2002, error='非Ajax请求')

(2.2)手动保存头像

  • 弊端很明显,麻烦
  • 代码逻辑
    1. 同样的首先获取头像数据
    2. 然后指定文件保存位置的位置
      • 存在的问题就是文件名可能重复
      • 最好自己在手动对文件名进行处理
    3. 手动保存
    4. 再次拼接保存在数据库中的位置
    5. 最后update更新
@csrf_exempt
@login_required
def set_avatar(request):
    if request.is_ajax():
        if request.FILES:
            # 获取头像
            avatar = request.FILES.get('avatar')
            # 需要手动拼接路径,模型层的up_load不起作用,甚至需要图片名字手动加参数才可以不重名
            file_path = os.path.join('media', 'avatar', avatar.name)
            # 保存文件
            with open(file_path, 'wb') as f:
                # 保存图片是个易错点
                for line in avatar.chunks():
                    f.write(line)
            # 拼接保存在数据库中的路径
            path = os.path.join('avatar', avatar.name)
            UserInfo.objects.filter(username='iron').update(avatar=path)
            return json_response(message='头像修改成功')
        return json_response(code=2001, error="请添加新的头像")
    return json_response(code=2002, error='非Ajax请求')

【四】广告后台管理

(1)效果显示、简单介绍

  • 左侧所有广告的轮播图,使用的是layui的轮播图功能
  • 中间使用form表单显示所有的广告内容,form表单提供功能
    1. 编辑广告功能,使用的模态框
    2. 删除广告功能,使用ajax的局部刷新
    3. 右侧预览功能,使用ajax的局部刷新功能
    4. 底部使用了分页器进行分页显示
  • 右侧可以查看指定广告的渲染样式
    在这里插入图片描述

(2)广告编辑功能

  • 我选择使用模态框
    • 及点击不同的广告
    • 模态框显示不同的原始广告信息
    • 所以这个有个局部渲染的要求
    • 需要用到ajax
  • 广告提交
    • 可以使用form表单提交
    • 也可以使用ajax进行提交(选择这个)
  • 代码逻辑(分别发送两次ajax请求)
    1. 获取当前广告ID
    2. 向后端发送ajax请求
    3. 拿到原始数据
    4. 将原始数据渲染到模态框的表单中
      • 要点:还需要保存这篇文章的ID的表单中
      • 用于修改广告信息的ID查找
    5. 等待修改信息
    6. 再次发送ajax请求
    7. 将新的内容发送给后端进行处理
{#编辑广告第一步#}
{#编辑广告第一步#}
$(".set-adv").click(function (event) {
    event.preventDefault()
    {#获取当前广告ID#}
    let id = $(this).attr('value')
    $.ajax({
        url: "{% url 'set_adv_first' %}",
        type: "post",
        data: {"id": id},
        success: function (response) {
            if (response.code === 2000) {
                console.log(response)
                $('input[name="set_adv_mobile"]').attr('value', response.mobile);
                $('input[name="set_adv_title"]').attr('value', response.title);
                $('input[name="set_adv_desc"]').attr('value', response.desc);
                $('#set-adv-img').attr('src', response.img);
                // 在指定的 input 标签后面插入一个新的 input 标签
                $('input[name="set_adv_mobile"]').after(
                    $('<input>').attr({
                        type: 'hidden', // 设置为隐藏类型
                        name: 'now_id', // 设置 input 的 name 属性为 now_id
                        value: response.now_id // 设置 input 的 value 属性为 now_id 的值
                    })
                )
                if (response.is_background_img) {
                    {#修改前端显示效果#}
                    $('#set_adv_background').next().addClass('layui-form-onswitch')
                    {#修改后端可以接收到的值#}
                    $("#set_adv_background").click()
                }
            } else {
                alert(response.error)
            }
        }
    })
})

{#编辑广告第二步#}
$("#set-adv-button").click((event) => {
    event.preventDefault()

    {#创建formData#}
    let formData = new FormData()
    {#获取form数据#}
    $.each($("#set-avd-form").serializeArray(), (_, dataDict) => {
        formData.append(dataDict.name, dataDict.value)
    })
    {#获取广告数据#}
    let advData = $("#setSelectAdv")[0].files[0];
    formData.append('set_adv_img', advData)
    console.log(formData)
    {#发送ajax#}
    $.ajax({
        url: "{% url 'set_adv_second' %}",
        type: "post",
        data: formData,
        processData: false,
        contentType: false,
        success: (response) => {
            console.log(response)
            if (response.code === 2000) {
                {#成功提示并跳转#}
                Swal.fire({
                    title: response.message,
                    icon: "success",
                    customClass: {
                        popup: 'swal2-custom',
                        icon: 'swal2-icon-custom'
                    }
                }).then(() => {
                    window.location.reload()
                });
            } else {
                console.log(response.code)
                Swal.fire({
                    icon: "error",
                    title: response.error,
                    customClass: {
                        popup: 'swal2-custom',
                        icon: 'swal2-icon-custom'
                    }
                });
            }

        }
    })
})

【五】广告个数动态加载

(1)效果显示、简单介绍

  • 右侧的广告
    • 通过循环遍历出来
    • 根据页面中间的内容多少,进行最大存放广告数量的显示
    • 比如
      • 现在页面中间只用5条数据,那么右侧就只显示能存放的2-3个广告
      • 如果页面中间有上百条数据,也就是说页面可以向下混动很多很多,那么右侧的广告也就随之展现更多的

在这里插入图片描述

(2)逻辑梳理

  1. 页面中的广告信息默认是不显示的
    • display属性写成none
  2. 在页面加载完以后再根据页面高度对广告进行展示
  3. 首先在页面加载以后得到页面的总高度
  4. 然后根据广告的高度进行整除向下取整
  5. 以防个数超出已有的广告数量
  6. 对个数进行处理
  7. 最后个根据计算得到的个数进行切分slice显示show
{#右侧广告开始#}
<div class="col-md-2 right-content">
    {% block right-content %}
        {% for adv_obj in adv_queryset %}
            <div class="advertisement" style="display: none;">
                {% adv_show adv_obj.pk %}
            </div>
        {% endfor %}
    {% endblock %}
</div>
{#右侧广告结束#}
<script>
    $(document).ready(() => {
        {#页面总高度#}
        let totalDocumentHeight = $(document).height();
        // 对高度进行400px的整除取整数
        let numAdsToShow = Math.floor(totalDocumentHeight / 400);
        // 确保广告数量不会少于0或超过广告对象的总数
        let advCount = {{ adv_queryset|length }};
        numAdsToShow = Math.min(numAdsToShow, advCount);
        // 根据计算出的数量显示广告
        $('.advertisement').slice(0, numAdsToShow).show();
</script>

【六】点赞点踩

(1)效果显示、简单介绍

  • 首先说明这个样式是来自于博客园的,逻辑代码是自己写的
  • 亮点是
    • 点赞点踩都是绑定的一个前端方法
      • votePost
    • 前端进行三元表达式运算
    • 发送数据给后端进行逻辑处理

请添加图片描述

(2)逻辑代码介绍

  • 这个方法是不能写在$(document).ready()中
  • 因为需要是全局定义域的方法,才可以被页面中直接调用
  • 写在外面可以在页面的任何地方被调用,包括在HTML元素的onclick属性中,或者在页面的其他JavaScript脚本中。
  1. 首先对点赞还是点踩进行三元表达式运算
    • 还可以简写
    • 但是无论怎么简写发送给后端的都是字符串数据
    • 后端需要loads或者字符串判断
  2. 将操作发送给后端进行逻辑处理
    • 后端的处理逻辑有多种
    • 可以操作后了就不能点赞点踩
    • 可以作者不能改自己点赞点踩
    • 可以撤消操作等
  3. 拿到后端处理后的数据
    • 成功进行个数的渲染
    • 失败显示错误信息
<script>
    // 点赞功能
    function votePost(article_id, flag) {
        {#三元表达式 和C的一样 和Python的不一样#}
        {#flag = true ? flag === 'Digg' : false#}
        {#简写#}
        flag = flag === 'Digg'
        {#获取提示信息的div#}
        let divEle = $("#message-error")
        {#用于后续修改点赞点踩数量#}
        let upEle = $("#digg_count")
        let downEle = $("#bury_count")
        $.ajax({
            url: "{% url 'up_down' %}",
            type: "post",
            data: {
                "flag": flag,
                {#右边注意需要是字符串格式#}
                {#{% csrf_token %}写在页面中即可,没有要求说写在表单里#}
                "csrfmiddlewaretoken": "{{ csrf_token }}",
                "article_id": article_id,
            },
            success: function (response) {
                if (response.code === 2000) {
                    {#渲染信息#}
                    divEle.text(response.message)
                    {#修改点赞点踩#}
                    upEle.text(response.up_num)
                    downEle.text(response.down_num)
                } else {
                    {#失败处理#}
                    {#html自动转义#}
                    divEle.html(response.error)
                }
            }
        })
    }
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1547747.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue2 使用mockjs

一、创建项目 vue create mock-demo二、安装 npm install mockjs --save-dev npm install axios --save三、创建mock文件夹 四、修改main.js 五、应用

js选择语句

文章目录 1. if 分支语句1.1. 示例代码1.2. 运行结果 2. if 双分支语句3. if 多分支语句4. switch 语句&#xff08;了解&#xff09;4.1. 注意4.2. case 穿透现象4.3. case 穿透产生的原因 5. switch 语句与选择语句区别别5.1. 语法上的区别5.2. 应用场景上的区别 6. 三元表达…

HCIP—BGP路由发布

R1和R2&#xff0c;R4和R5建立EBGP对等体 R1和R2&#xff08;R4和R5&#xff09;之间属于EBGP对等体&#xff0c;可以使用直连物理接口建立对等体关系&#xff0c;TTL值默认1。由于使用直连物理接口方式建立&#xff0c;刚好一跳到达。 [R1]bgp 100 [R1-bgp]router-i…

燃气官网安全运行监测系统-阀井燃气监测仪-旭华智能

近年来&#xff0c;燃气爆炸事故频发&#xff0c;造成了重大人员伤亡和财产损失。这也再次为我们敲响警钟&#xff0c;燃气是我们日常生活中不可或缺的能源&#xff0c;但其潜在的危险性也是不容小觑。因此在重要节点加装燃气阀井气体监测仪&#xff0c;并将数据上传到系统平台…

【QT+QGIS跨平台编译】之九十四:【QGIS_App跨平台编译】—【错误处理:字符串错误】

文章目录 一、字符串错误二、涉及到的文件一、字符串错误 常量中有换行符错误:(也有const char * 到 LPCWSTR 转换的错误) 需要把对应的文档用记事本打开,另存为 “带有BOM的UTF-8” 二、涉及到的文件 涉及到的文件有: src\app\qgisapp.cpp src\app\qgsprojectpropert…

JetBrains全家桶激活,分享 DataGrip 2024 激活的方案

大家好&#xff0c;欢迎来到金榜探云手&#xff01; DataGrip 公司简介 JetBrains 是一家专注于开发工具的软件公司&#xff0c;总部位于捷克。他们以提供强大的集成开发环境&#xff08;IDE&#xff09;而闻名&#xff0c;如 IntelliJ IDEA、PyCharm、和 WebStorm等。这些工…

金融投贷通--接口测试分析、设计与实现

金融投贷通--接口测试分析、设计与实现 接⼝相关理论ui功能测试和接⼝测试那个先执⾏ui功能测试与接⼝测试的区别ui功能测试和接⼝测试那个更⾼效 投资业务接⼝接口测试流程如何测试分析api文档项目难点 测试点提取注册图⽚验证码、注册验证码注册登录测试点开通登录测试点开通…

API成网络攻击常见载体,如何确保API安全?

根据Imperva发布的《2024年API安全状况报告》&#xff0c;API成为网络攻击者的常见载体&#xff0c;这是因为大部分互联网流量&#xff08;71%&#xff09;都是API调用&#xff0c;API是访问敏感数据的直接途径。根据安全公司Fastly的一项调查显示&#xff0c;95%的企业在过去1…

蓝桥杯刷题-子串简写

子串简写 代码 kint(input()) s,c1,c2input().split() pre[0]*len(s) ans0 for i in range(len(s)):pre[i]pre[i-1]if c1s[i]:pre[i]1elif c2s[i] and i1-k>0:anspre[i-k1] print(ans)

宾大率先推出藤校首个AI专业!25Fall即可申请!

随着人工智能技术的不断发展&#xff0c;当今社会对AI的需求已呈现出日益多样化的趋势。由ChatGPT、Midjourney等AI应用领衔&#xff0c;生成式的人工智能迅速崛起。各个领域都开始意识到AI的潜在应用价值&#xff0c;该领域的人才需求量也越来越大。在这样的人工智能热潮下&am…

【办公类-21-10】三级育婴师 视频转文字docx(等线小五单倍行距),批量改成“宋体小四、1.5倍行距、蓝色字体、去掉五分钟”

作品展示 背景需求 今天将最后3个育婴师操作视频做整理 第1步&#xff1a;视频MP4转MP3 【办公类-40-01】20240311 用Python将MP4转MP3提取音频 &#xff08;家长会系列一&#xff09;-CSDN博客文章浏览阅读393次&#xff0c;点赞9次&#xff0c;收藏6次。【办公类-40-01】20…

java网络原理(四)----tcp特性

一.滑动窗口 滑动窗口&#xff1a;可以提高传输效率&#xff0c;准确的来说是让tcp在可靠传输的前提下&#xff0c;效率不要太拉胯。使用滑动窗口不能使tcp变的比UDP块&#xff0c;但能减少差距。 前面谈过tcp的传输数据的时&#xff0c;会把数据进行编号&#xff0c;每次传固…

Go语言介绍以及如何在Go语言中操作MySQL数据库

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Oracle中实现根据条件对数据的增删改操作——Merge Into

一、需求描述 在我们进行项目开发的过程中&#xff0c;会遇到这样的场景&#xff0c;需要根据某个条件对数据进行增、删、改的操作&#xff1b;遇到这种情况我们有2种方法进行解决&#xff1a; 方法一&#xff1a;①查询指定条件&#xff1b;②根据查询出的指定条件结果在执行…

LeetCode146:LRU缓存

leetCode&#xff1a;146. LRU 缓存 题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#x…

ZnO非线性电阻产品特征技术规范

ZnO非线性电阻是一种多组分的多晶陶瓷半导体。它以ZnO为主体,添加其它各种成分组成。不同厂家及研究机构的添加物成分不完全相同,当添加物含量超过0.001mol时开始呈现非线性&#xff0c;典型的ZnO非线性电阻的显微结构包括四部分: ① ZnO 主体:它是由电阻率为0.0010m~0.10m&…

八股 -- C#

面向对象 &#xff08;三大特性&#xff09; 三大特性目的是为了提供更好的代码组织、可维护性、扩展性和重用性 C#基础——面向对象 - 知乎 (zhihu.com) 封装 理解&#xff1a; 你不需要了解这个方法里面写了什么代码&#xff0c;你只需要了解这个方法能够给你返回什么数据&…

​Edge-TTS:微软推出的,免费、开源、支持多种中文语音语色的AI工具

Edge-TTS是由微软推出的文本转语音Python库&#xff0c;通过微软Azure Cognitive Services转化文本为自然语音。适合需要语音功能的开发者&#xff0c;GitHub上超3000星。作为国内付费TTS服务的替代品&#xff0c;Edge-TTS支持40多种语言和300种声音&#xff0c;提供优质的语音…

实例分割——细胞实例分割数据集

一、重要性及意义 细胞实例分割是单细胞空间研究的基石&#xff0c;有助于我们更深入地理解健康和疾病状态下的细胞相互作用 通过细胞实例分割&#xff0c;研究人员能够探索正常和病理条件下的细胞如何相互影响&#xff0c;进而增强对基本生物过程的理解。这种理解有助于我们揭…

Web API —— BOM 学习(完结)

目录 一、BOM 介绍 二、Window 对象 &#xff08;一&#xff09;基本介绍 &#xff08;二&#xff09;定时器 —— 延时函数 1.语法 2.清除时间函数 3.和 interval 间歇函数的区别 &#xff08;三&#xff09;JS 执行机制 1.介绍 2.同步任务 3.异步任务 4.执行过程…