nlp_structbert_sentence-similarity_chinese-large保姆级教程:Streamlit Session State管理多用户会话

nlp_structbert_sentence-similarity_chinese-large保姆级教程:Streamlit Session State管理多用户会话

你是不是也遇到过这样的问题?用Streamlit做了一个很酷的AI应用,比如这个中文句子相似度分析工具,但每次刷新页面,输入框里的文字就没了,计算过的结果也清空了。或者,当你想同时为多个用户提供服务时,发现他们的数据会互相干扰。

今天,我就来手把手教你解决这个问题。我们将以nlp_structbert_sentence-similarity_chinese-large这个强大的中文语义匹配工具为例,深入讲解如何用Streamlit的Session State来优雅地管理多用户会话,让你的应用从“玩具”升级为“工具”。

学完这篇教程,你将掌握:

  1. Session State的核心概念和工作原理。
  2. 如何为你的AI应用(如句子相似度计算)添加稳固的会话记忆。
  3. 实现多用户数据隔离的实战技巧。
  4. 避免常见陷阱,打造更专业的Web应用。

准备好了吗?让我们开始吧。

1. 环境准备与项目回顾

在开始改造之前,我们先确保环境就绪,并快速回顾一下我们的基础应用。

1.1 确保核心库已安装

打开你的终端,运行以下命令来安装或更新必要的Python库。如果你已经安装过,它会确保版本兼容。

pip install torch transformers streamlit 

安装说明

  • torch:PyTorch深度学习框架,我们的模型运行基础。
  • transformers:Hugging Face的库,用于加载和运行StructBERT等预训练模型。
  • streamlit:用于构建交互式Web应用的框架。

1.2 项目结构与模型准备

假设你的项目文件夹结构如下:

your_project/ ├── app.py # 主Streamlit应用文件 └── /root/ai-models/iic/nlp_structbert_sentence-similarity_chinese-large/ # 模型权重目录(根据你的实际路径调整) 

关键一步:请确认StructBERT模型权重文件已经下载并放置在正确的路径下。本教程预设路径为/root/ai-models/iic/nlp_structbert_sentence-similarity_chinese-large,你需要根据自己服务器的实际情况进行调整。

1.3 快速启动基础应用

让我们先看看没有Session State的原始应用是什么样子。创建一个简单的app.py文件,内容如下:

import streamlit as st from transformers import AutoTokenizer, AutoModel import torch import torch.nn.functional as F import numpy as np # 设置页面标题 st.set_page_config(page_title="StructBERT 句子相似度分析") # 加载模型和分词器 - 使用缓存避免重复加载 @st.cache_resource def load_model(): model_path = "/root/ai-models/iic/nlp_structbert_sentence-similarity_chinese-large" tokenizer = AutoTokenizer.from_pretrained(model_path) model = AutoModel.from_pretrained(model_path).cuda() # 假设使用CUDA return tokenizer, model tokenizer, model = load_model() # 定义句子编码函数 def encode_sentence(sentence): inputs = tokenizer(sentence, return_tensors='pt', padding=True, truncation=True, max_length=128) inputs = {k: v.cuda() for k, v in inputs.items()} # 将输入数据也放到GPU上 with torch.no_grad(): outputs = model(**inputs) # 均值池化:获取句子向量 attention_mask = inputs['attention_mask'] last_hidden_state = outputs.last_hidden_state input_mask_expanded = attention_mask.unsqueeze(-1).expand(last_hidden_state.size()).float() sum_embeddings = torch.sum(last_hidden_state * input_mask_expanded, 1) sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9) mean_embeddings = sum_embeddings / sum_mask return mean_embeddings.cpu().numpy() # 计算余弦相似度 def cosine_similarity(vec_a, vec_b): return F.cosine_similarity(torch.from_numpy(vec_a), torch.from_numpy(vec_b), dim=0).item() # 应用界面 st.title("⚖️ StructBERT 中文句子相似度分析") col1, col2 = st.columns(2) with col1: sentence_a = st.text_area("句子 A (基准句)", value="今天天气真好", height=100) with col2: sentence_b = st.text_area("句子 B (对比句)", value="阳光明媚的一天", height=100) if st.button("🔍 计算相似度", type="primary"): if sentence_a and sentence_b: with st.spinner('正在计算语义相似度...'): vec_a = encode_sentence(sentence_a) vec_b = encode_sentence(sentence_b) similarity = cosine_similarity(vec_a, vec_b) # 显示结果 st.metric("语义相似度得分", f"{similarity:.4f}") # 进度条可视化 st.progress(similarity, text=f"匹配程度: {similarity*100:.1f}%") # 语义判定 if similarity > 0.85: st.success("✅ 语义非常相似") elif similarity > 0.5: st.warning("⚠️ 语义相关") else: st.error("❌ 语义不相关") else: st.warning("请输入两个句子进行比较。") # 侧边栏信息 with st.sidebar: st.header("关于") st.info(""" **StructBERT** 是阿里达摩院对BERT的强化升级,通过理解语言结构,在中文语义匹配上表现卓越。 本工具适用于: - 文本去重 - 智能客服问答匹配 - 语义搜索 """) 

运行这个应用:

streamlit run app.py 

你会看到一个简洁的界面,可以输入两个句子并计算相似度。但是,如果你尝试刷新页面,或者打开两个浏览器标签同时使用,就会发现它存在我们开头提到的问题。接下来,我们就来修复它。

2. 理解Streamlit Session State

在动手修改代码之前,我们需要先搞清楚Session State到底是什么,以及它为什么能解决我们的问题。

2.1 什么是Session State?

你可以把Session State想象成每个用户访问你的应用时,Streamlit为他们分配的一个“私人储物柜”。这个储物柜存在于服务器端(或者在某些部署环境下是客户端),专门用来存储这个用户在当前会话中的各种数据。

关键特性

  • 会话隔离:用户A的储物柜和用户B的完全独立,互不干扰。
  • 页面刷新保持:只要用户不关闭浏览器标签,即使刷新页面,储物柜里的东西也不会丢。
  • 键值对存储:像Python字典一样,用st.session_state[‘key’] = value的方式存取数据。

2.2 为什么我们的应用需要它?

回顾我们刚才的基础应用,它有两个主要问题:

  1. 状态丢失:点击“计算相似度”后,结果会显示。但如果你不小心按了F5刷新页面,输入框里的句子和计算出的结果都会消失,一切归零。用户体验很糟糕。
  2. 无用户隔离:这只是个本地演示问题不明显。但如果部署到服务器,两个用户同时访问,理论上他们使用的是同一个全局变量空间(虽然Streamlit的执行模型使得直接的变量冲突不常见,但缺乏会话状态管理会导致逻辑混乱)。

Session State就是为解决这些问题而生的。它为每个用户会话提供了一个持久化的存储空间。

2.3 Session State的基本用法

使用起来非常简单,主要就两个操作:

# 1. 写入数据到Session State st.session_state[‘my_key’] = ‘my_value’ st.session_state.user_name = ‘张三’ # 也可以像属性一样访问 # 2. 从Session State读取数据 my_value = st.session_state.get(‘my_key’, ‘default_value’) # 安全获取,避免KeyError user_name = st.session_state.user_name 

初始化技巧:通常,我们会在用户执行某个操作(如点击按钮)时更新Session State,而在页面渲染时从Session State读取数据来恢复状态。为了避免在第一次访问时出现KeyError,可以使用.get()方法提供默认值,或者在页面顶部进行初始化。

理解了这些基础概念,我们就可以开始改造我们的句子相似度应用了。

3. 为相似度应用添加会话记忆

现在,我们将一步步把Session State集成到app.py中,让应用变得“聪明”起来,能够记住用户的操作。

3.1 初始化Session State

在代码开始处理用户输入之前,我们应该先初始化所有需要用到的会话状态变量。一个好的做法是在所有st.*函数调用之前完成初始化。

我们在load_model()函数调用之后,添加初始化代码:

# ... [之前的代码:导入库、加载模型] ... # 初始化Session State if ‘sentence_a’ not in st.session_state: st.session_state.sentence_a = “今天天气真好” if ‘sentence_b’ not in st.session_state: st.session_state.sentence_b = “阳光明媚的一天” if ‘similarity_score’ not in st.session_state: st.session_state.similarity_score = None if ‘calculation_done’ not in st.session_state: st.session_state.calculation_done = False 

代码解释

  • 我们为两个输入框sentence_asentence_b设置了默认值。这样,即使用户第一次访问,输入框里也有内容。
  • similarity_score用来存储计算出的相似度分数。
  • calculation_done是一个标志,用来记录用户是否已经点击过计算按钮。这有助于我们控制结果的显示逻辑。

3.2 改造输入框:绑定到Session State

接下来,我们需要修改输入框,让它们的值来自Session State,并且当用户修改输入框时,能自动更新Session State。

找到原来创建输入框的代码,将其修改为:

# 应用界面 st.title(“⚖️ StructBERT 中文句子相似度分析”) col1, col2 = st.columns(2) with col1: # 使用on_change参数,当输入框内容变化时,更新session_state sentence_a = st.text_area(“句子 A (基准句)”, value=st.session_state.sentence_a, height=100, key=“input_a”, # 为widget指定一个唯一的key,Streamlit会自动将其状态与session_state[‘input_a’]绑定 on_change=lambda: update_sentence(‘a’)) with col2: sentence_b = st.text_area(“句子 B (对比句)”, value=st.session_state.sentence_b, height=100, key=“input_b”, on_change=lambda: update_sentence(‘b’)) 

注意,我们添加了key参数和on_change回调函数。key是必须的,它建立了widget状态和Session State之间的双向绑定。on_change允许我们在值改变时执行自定义逻辑。

我们需要定义update_sentence这个回调函数:

# 定义更新句子的回调函数 def update_sentence(sentence_id): # 根据widget的key从session_state中获取最新的值 if sentence_id == ‘a’: st.session_state.sentence_a = st.session_state.input_a elif sentence_id == ‘b’: st.session_state.sentence_b = st.session_state.input_b # 当句子被修改,重置计算结果状态 st.session_state.calculation_done = False st.session_state.similarity_score = None 

这个函数做了两件事:

  1. 将输入框(通过key绑定到st.session_state.input_a/b)的最新值,同步到我们自定义的st.session_state.sentence_a/b中。
  2. 一旦句子被修改,就将calculation_done标志设为False,并清空之前的分数。这符合直觉:输入变了,旧的结果自然就失效了。

3.3 改造计算按钮与结果显示逻辑

这是核心部分。我们需要让按钮的点击事件去更新Session State,然后根据Session State的状态来决定显示什么。

首先,修改按钮和其关联的逻辑:

# 计算按钮 if st.button(“🔍 计算相似度”, type=“primary”, key=“calc_button”): if st.session_state.sentence_a and st.session_state.sentence_b: with st.spinner(‘正在计算语义相似度…’): vec_a = encode_sentence(st.session_state.sentence_a) vec_b = encode_sentence(st.session_state.sentence_b) similarity = cosine_similarity(vec_a, vec_b) # 将结果存入Session State st.session_state.similarity_score = similarity st.session_state.calculation_done = True else: st.warning(“请输入两个句子进行比较。”) st.session_state.calculation_done = False # 根据Session State显示结果 if st.session_state.calculation_done and st.session_state.similarity_score is not None: similarity = st.session_state.similarity_score # 显示结果 st.metric(“语义相似度得分”, f“{similarity:.4f}”) # 进度条可视化 st.progress(similarity, text=f“匹配程度: {similarity*100:.1f}%”) # 语义判定 if similarity > 0.85: st.success(“✅ 语义非常相似”) elif similarity > 0.5: st.warning(“⚠️ 语义相关”) else: st.error(“❌ 语义不相关”) 

逻辑梳理

  1. 用户点击按钮,触发计算。
  2. 计算完成后,将分数和完成标志存入st.session_state
  3. 页面下方的显示逻辑不再直接依赖于按钮点击事件,而是读取st.session_state.calculation_donest.session_state.similarity_score
  4. 只要这两个状态为真,就会显示结果。这意味着即使页面刷新,只要计算完成过,结果依然会显示出来。

3.4 添加会话管理功能:重置

一个好的应用应该允许用户重置状态。我们在侧边栏添加一个重置按钮。

在侧边栏的代码块内添加:

with st.sidebar: st.header(“关于”) # … [原有的关于信息] … st.divider() st.header(“会话管理”) if st.button(“🔄 重置当前会话”, type=“secondary”): # 重置所有相关的Session State值 st.session_state.sentence_a = “” st.session_state.sentence_b = “” st.session_state.similarity_score = None st.session_state.calculation_done = False # 注意:widget的状态(input_a, input_b)也会因为其value被清空而更新 # 但为了彻底,我们也可以使用Streamlit的experimental_rerun来强制刷新widget状态 st.rerun() # 使用rerun来立即刷新界面,看到输入框被清空 st.caption(“点击此按钮将清空当前输入和计算结果。”) 

现在,你的应用已经具备了基本的会话记忆功能!你可以尝试:

  1. 输入句子并计算相似度。
  2. 刷新浏览器页面,你会发现句子和结果都还在。
  3. 点击侧边栏的“重置”按钮,一切恢复初始状态。

4. 深入实践:实现多用户会话隔离

上面的改造解决了单用户页面的状态持久化问题。那么,多用户隔离是如何自动实现的呢?我们通过一个简单的实验来理解。

4.1 理解多用户隔离机制

Streamlit为每个连接到应用的浏览器会话(通常是一个标签页)创建一个独立的Python运行时环境和对应的st.session_state字典。这是通过会话ID(Session ID)来实现的。

我们来验证一下:在侧边栏添加一个显示会话ID和访问计数的功能。

首先,在初始化Session State的部分,添加一个计数器:

# 初始化Session State if ‘sentence_a’ not in st.session_state: st.session_state.sentence_a = “今天天气真好” if ‘sentence_b’ not in st.session_state: st.session_state.sentence_b = “阳光明媚的一天” if ‘similarity_score’ not in st.session_state: st.session_state.similarity_score = None if ‘calculation_done’ not in st.session_state: st.session_state.calculation_done = False # 新增:访问计数器 if ‘visit_count’ not in st.session_state: st.session_state.visit_count = 0 st.session_state.visit_count += 1 # 每次脚本运行(交互)都增加 

然后,在侧边栏“会话管理”部分显示这个信息:

with st.sidebar: # … [之前的关于和重置按钮代码] … st.divider() st.subheader(“会话信息”) # 注意:Streamlit没有直接暴露session_id的API,我们用计数器模拟 st.write(f“本会话交互次数: **{st.session_state.visit_count}**”) st.caption(“(每次点击按钮、修改输入都会增加)”) 

实验步骤

  1. 运行streamlit run app.py
  2. 打开浏览器访问 http://localhost:8501。这是用户A会话。侧边栏的计数器会随着你的操作增加。
  3. 再打开一个浏览器(或者新的无痕窗口),访问相同的地址。这是用户B会话。你会看到它的计数器从1开始,并且它的输入框、计算结果与用户A的完全独立。

这就是多用户会话隔离。Streamlit在后台为你处理了所有复杂的连接和状态映射。

4.2 扩展:一个更健壮的相似度计算历史

让我们把应用做得更实用,比如记录用户本次会话中计算过的所有句子对和结果。

在Session State初始化部分,添加一个历史记录列表:

# … [其他初始化] … if ‘history’ not in st.session_state: st.session_state.history = [] # 列表,用于存放历史记录 

修改计算按钮的逻辑,在计算完成后将结果存入历史:

if st.button(“🔍 计算相似度”, type=“primary”, key=“calc_button”): if st.session_state.sentence_a and st.session_state.sentence_b: with st.spinner(‘正在计算语义相似度…’): vec_a = encode_sentence(st.session_state.sentence_a) vec_b = encode_sentence(st.session_state.sentence_b) similarity = cosine_similarity(vec_a, vec_b) # 存入当前结果状态 st.session_state.similarity_score = similarity st.session_state.calculation_done = True # 添加到历史记录 history_entry = { “sentence_a”: st.session_state.sentence_a, “sentence_b”: st.session_state.sentence_b, “score”: similarity, “time”: st.session_state.visit_count # 用交互次数模拟时间戳 } st.session_state.history.append(history_entry) else: st.warning(“请输入两个句子进行比较。”) st.session_state.calculation_done = False 

最后,在页面上显示历史记录。可以在结果下方或侧边栏添加:

# 在显示当前结果之后,添加历史记录展示 if st.session_state.history: st.divider() st.subheader(“📜 本次会话计算历史”) # 只显示最近5条,避免页面过长 for i, entry in enumerate(reversed(st.session_state.history[-5:])): with st.expander(f“记录 {len(st.session_state.history)-i}: {entry[‘sentence_a’][:20]}… vs {entry[‘sentence_b’][:20]}…”): st.write(f“**句子A:** {entry[‘sentence_a’]}”) st.write(f“**句子B:** {entry[‘sentence_b’]}”) st.write(f“**相似度:** {entry[‘score’]:.4f}”) # 可以添加一个按钮,点击后将历史记录填充回输入框 if st.button(f“使用此对句子”, key=f“use_{i}”): st.session_state.sentence_a = entry[‘sentence_a’] st.session_state.sentence_b = entry[‘sentence_b’] st.session_state.calculation_done = False # 触发重新计算或等待用户点击 st.rerun() 

现在,每个用户都可以在自己的会话中查看和复用之前的计算记录,而这些记录对其他用户是不可见的。这完美体现了多用户会话隔离的价值。

5. 总结与最佳实践

通过本教程,我们成功地将一个简单的、无状态的Streamlit应用,升级成了一个能持久化会话状态、支持多用户隔离的健壮应用。让我们回顾一下关键点,并分享一些最佳实践。

5.1 核心要点回顾

  1. Session State是会话记忆的核心:它本质上是一个为每个用户会话分配的、持久的Python字典。用于存储跨页面交互和刷新的数据。
  2. 初始化是关键:在尝试读取st.session_state中的键之前,务必检查其是否存在并进行初始化,避免KeyError
  3. 双向绑定简化开发:为Streamlit的widget(如st.text_input, st.slider)设置唯一的key,可以自动将其值与st.session_state[key]同步,省去手动更新的麻烦。
  4. 逻辑与状态分离:将应用逻辑(计算、判断)与界面显示分离。界面根据st.session_state中的状态来决定显示什么,而不是依赖于某个按钮是否刚刚被点击。这使得应用行为更可预测,状态更易管理。
  5. 多用户隔离是自动的:Streamlit框架底层已经处理了会话隔离。你只需要关心如何使用st.session_state,无需担心用户A的数据会泄露给用户B。

5.2 进阶技巧与最佳实践

  • 谨慎使用st.rerun()st.rerun()会从头重新执行整个脚本。在回调函数中使用它来响应状态重置是合适的,但过度使用可能导致不必要的性能开销和闪烁。
  • 清理无用状态:对于长期运行的应用,如果某些会话状态数据很大(如缓存的计算结果),可以考虑在适当的时候(如重置时、会话结束时)手动将其设置为None,以帮助Python垃圾回收。
  • 结合st.cache_datast.cache_data用于缓存函数返回的数据(如从数据库读取的数据、昂贵的转换结果),它在所有用户会话间共享。st.session_state用于存储用户特定的交互状态。根据需求合理选择。
  • 部署注意事项:当部署到云服务器时,确保你的Streamlit服务配置了合适的会话管理。对于需要极高可靠性的场景,可能需要考虑将Session State存储到外部数据库(如Redis),但这超出了基础教程的范围。

状态结构化:对于复杂应用,不要把所有状态都平铺在st.session_state根目录下。可以考虑使用嵌套字典或数据类来组织。

if ‘app_state’ not in st.session_state: st.session_state.app_state = { ‘inputs’: {‘a’: ‘’, ‘b’: ‘’}, ‘results’: {‘score’: None, ‘done’: False}, ‘history’: [] } 

5.3 下一步你可以做什么?

现在你已经掌握了Streamlit Session State的精髓,可以尝试以下挑战来巩固技能:

  1. 扩展功能:为我们的相似度工具添加“批量计算”功能,允许用户上传一个包含多对句子的CSV文件,并将所有结果记录在Session State中供查看和导出。
  2. 美化界面:利用Session State来记住用户选择的主题(深色/浅色)、偏好的相似度阈值等设置。
  3. 构建更复杂的应用:尝试用Session State管理一个多步骤的向导式应用,比如一个数据清洗或报告生成工具,每一步的结果都保存在会话中。

希望这篇教程能帮助你构建出更强大、更用户友好的Streamlit AI应用。记住,良好的状态管理是提升应用体验的关键一步。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 ZEEKLOG星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Read more

Java 部署:滚动更新(K8s RollingUpdate 策略)

Java 部署:滚动更新(K8s RollingUpdate 策略)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java部署这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 部署:滚动更新(K8s RollingUpdate 策略) * 什么是滚动更新(Rolling Update)? * 为什么 Java 应用特别需要滚动更新? * Kubernetes 滚动更新的核心机制 * 默认值 * 参数详解 * 构建一个支持滚动更新的 Java 应用 * 1. 创建 Spring Boot 项目 * 2. 编写主类 * 3. 添加控制器 * 4. 配置 Actuator 健康端点 * 5. 构建 Docker 镜像 * 编写 Kubernetes

By Ne0inhk
大模型 AI Token 计费机制详解与成本控制实践

大模型 AI Token 计费机制详解与成本控制实践

摘要 本文系统解析大模型 AI Token 的计费机制,包括 Token 的基本概念、转换规则、计费模式,并结合实际案例分析成本控制策略。文章从技术原理出发,对比主流平台计费规则,提供可落地的成本优化方案,帮助开发者和企业在保证模型效果的前提下降低 AI 应用成本。 一、Token 基础概念与转换规则 Token 是大语言模型处理文本的基本单位,可理解为 "语言积木"。不同于传统字符或单词,Token 是模型通过分词算法对文本进行的语义分割。 1.1 Token 的本质 Token 既可以是完整的词(如英文单词),也可以是字符片段(如中文单字)或标点符号。例如: * 英文句子 "Hello, how are you!" 会拆分为「Hello」「,」「how」

By Ne0inhk
AI赋能专利翻译,八月瓜科技“妙算翻译大模型”亮相国际论坛

AI赋能专利翻译,八月瓜科技“妙算翻译大模型”亮相国际论坛

当前,国家高度重视人工智能与知识产权融合发展,《新一代人工智能发展规划》明确提出“推动人工智能在知识产权检索、分析、翻译等领域的深度应用,提升知识产权服务效率与质量”,《“十四五”国家知识产权保护和运用规划》也强调“加强知识产权信息化、智能化基础设施建设,推动专利信息跨语言互通”。 顺应这一政策导向,专利领域对专业化翻译的需求愈发迫切。八月瓜科技“妙算翻译大模型”立足需求,凭借深厚的技术积累与精准的场景适配,成为破解行业痛点、助力跨境创新的核心力量。 国际论坛亮相获认可,产品实力彰显初心 日前,妙算翻译大模型凭借在专利翻译领域的突出实力与创新成果,亮相东盟+中日韩(10+3)人工智能产业发展论坛,成为论坛上聚焦知识产权服务智能化的亮点成果,获得了行业专家、参会企业及相关机构的高度关注与广泛认可。此次论坛亮相,不仅是对妙算翻译大模型技术实力与应用价值的权威肯定,更彰显了其在推动专利翻译智能化、打破跨国创新语言壁垒方面的重要作用,为其进一步拓展市场、服务更多科技创新主体奠定了坚实基础。 能获得行业广泛认可,核心源于产品本身的专业定位与硬核实力。妙算翻译大模型在语言

By Ne0inhk

尤雨溪力荐!Vue 终于有了自己的 AI 组件库!

在 AI 应用开发的浪潮中,React 生态似乎总是占据着先发优势。Vercel 推出的 AI SDK 和相关组件库,往往优先支持 Next.js 和 React,这让不少 Vue 开发者常常感到“羡慕嫉妒恨”。 但是,局面正在改变。 就在最近,Vue 社区迎来了一个重磅好消息:AI Elements Vue 正式发布。 这是一个基于 shadcn-vue 构建的、专门用于快速搭建 AI 应用的组件库。它的出现填补了 Vue 生态在 AI UI 领域的空白,发布仅两天,就获得了 Vue 作者 尤雨溪(Evan You) 的转发和点赞。 下面我们来聊聊这个让尤雨溪都关注的组件库,到底成色如何?

By Ne0inhk