Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android P之Smart Linkify

Android P之Smart Linkify

作者头像
天天P图攻城狮
修改于 2018-11-21 09:35:54
修改于 2018-11-21 09:35:54
1.4K00
代码可运行
举报
文章被收录于专栏:天天P图攻城狮天天P图攻城狮
运行总次数:0
代码可运行

Linkify

这是个很老的工具类了,就是使textview称为可点击链接,它默认支持:

  • web
  • email
  • phone
  • map
  • all

有2种方式设置点击链接:

  • xml方式:通过android:autoLink设置。这种方式不是很灵活,并且不能自定义。
  • code方式

code方式详解

系统已有的模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask)

private static boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask, @Nullable Context context)

public static final boolean addLinks(@NonNull TextView text, @LinkifyMask int mask)

如果不需要定制的话,则直接使用上面的方式进行添加可点击链接即可。

核心代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
        @Nullable Context context) {
    if (mask == 0) {
        return false;
    }
    
    URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
    
    for (int i = old.length - 1; i >= 0; i--) {
        text.removeSpan(old[i]);
    }
    
    ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
    
    if ((mask & WEB_URLS) != 0) {
        gatherLinks(links, text, Patterns.AUTOLINK_WEB_URL,
            new String[] { "http://", "https://", "rtsp://" },
            sUrlMatchFilter, null);
    }
    
    if ((mask & EMAIL_ADDRESSES) != 0) {
        gatherLinks(links, text, Patterns.AUTOLINK_EMAIL_ADDRESS,
            new String[] { "mailto:" },
            null, null);
    }
    
    if ((mask & PHONE_NUMBERS) != 0) {
        gatherTelLinks(links, text, context);
    }
    
    if ((mask & MAP_ADDRESSES) != 0) {
        gatherMapLinks(links, text);
    }
    
    pruneOverlaps(links);
    
    if (links.size() == 0) {
        return false;
    }
    
    for (LinkSpec link: links) {
        applyLink(link.url, link.start, link.end, text);
    }
    
    return true;
}

步骤:

  1. 清除textview里的所有的span(所有系统和自定义混合使用的时候,一定要先设置系统的
  2. 如果是web模式,则解析textview里有的web链接
  3. 如果是email模式,则解析textview里有的email链接
  4. 如果是phone模式,则解析textview里有的phone链接
  5. 如果是map模式,则解析textview里有的map链接
  6. 去掉textview的overlap
  7. 调用applyLink()方法给所有的可点击链接加上下划线

自定义模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static final void addLinks(@NonNull TextView text, @NonNull Pattern pattern, @Nullable String scheme)

public static final void addLinks(@NonNull TextView text, @NonNull Pattern pattern,@Nullable String scheme, @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter)

... ...

如果是自定义模式,则需要调用上面的方法(方法很多,未完全列出来),其核心就是通过正则去匹配,所以这种自定义模式必须要传入一个Pattern值。

核心代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
        @Nullable  String defaultScheme, @Nullable String[] schemes,
        @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter) {
    final String[] schemesCopy;
    if (defaultScheme == null) defaultScheme = "";
    if (schemes == null || schemes.length < 1) {
        schemes = EmptyArray.STRING;
    }

    schemesCopy = new String[schemes.length + 1];
    schemesCopy[0] = defaultScheme.toLowerCase(Locale.ROOT);
    for (int index = 0; index < schemes.length; index++) {
        String scheme = schemes[index];
        schemesCopy[index + 1] = (scheme == null) ? "" : scheme.toLowerCase(Locale.ROOT);
    }

    boolean hasMatches = false;
    Matcher m = pattern.matcher(spannable);

    while (m.find()) {
        int start = m.start();
        int end = m.end();
        boolean allowed = true;

        if (matchFilter != null) {
            allowed = matchFilter.acceptMatch(spannable, start, end);
        }

        if (allowed) {
            String url = makeUrl(m.group(0), schemesCopy, m, transformFilter);

            applyLink(url, start, end, spannable);
            hasMatches = true;
        }
    }

    return hasMatches;
}

步骤:

  1. 通过正则去查找匹配项
  2. 生成展现的可点击url
  3. 调用applyLink()方法给所有的可点击链接加上下划线
  4. 重复1、2、3,知道字符串扫描结束

例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView tv = (TextView) findViewById(R.id.tv);
    Pattern p = Pattern.compile("tencent://\\S*");
    Linkify.addLinks(tv, p, "mm"); 
}

对于自定义的模式,由于没有具体的activity去处理,所以会crash,需要定义可处理的activity:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<activity android:name=".MmActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />   
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="tencent" />
    </intent-filter>
</activity>

补充

其实要实现类似Linkify的功能还有其他的方式:

  • Html.fromHtml():将需要的字符串解析成html
  • SpannableString:就是通过SpannableString来装饰字符串(Linkify内部就是这种方式)

Smart Linkify

Smart Linkify是在基于Android O发布的Smart Text Selection的基础上,加入机器学习和神经网络相关的内容来进一步提升识别推断的能力。

前馈神经网络

Smart Linkify首先引入小型的前馈神经网络来寻找对象,因为在文本中找出风格迥异写法的手机号和邮箱地址是很困难的一件事情。

理论基础

前馈神经网络是目前三大神经网络结构中的一种:

  • 前馈网络
  • 反馈网络
  • 图网络

前馈网络将各个神经元按照接收信息的先后顺序分为不同的组,每一组相当于一个神经层,每一次神经元仅接收前一层神经元的输出作为自己的输入,整个网络只会在一个方向上进行传播。

前馈网络将第0层称之为输入层,最后一层称之为输出层,其他中间层称之为隐藏层。

这里会涉及到一些数学概念:

  • L:表示神经网络的层数;
  • m(l):表示第l层神经元的个数
  • fl(·):表示l层神经元的激活函数
  • W(l):表示l−1层到第l层的权重矩阵
  • b(l):表示l−1层到第l层的偏置
  • z(l):表示l层神经元的净输入
  • a(l):表示l层神经元的输出

为了更好的理解层的输入输出,我们先了解下神经元的输入输出。

神经元

神经元的生物特征就是通过多个树突接受信号,通过唯一轴突来传递信息。人工神经元的功能就是对生物神经元的抽象。

最早的神经元是心理学家McCulloch和数学家Pitts提出的M-P神经元,它的特点是激活函数是阶跃函数,现代神经元在M-P神经元的基础上发展,最大区别就是激活函数是连续可导的。

人工神经元处理数据的过程如下:

  • 接受信号:x = [x1, x2, x3,...,xn]
  • 信号权重:w = [w1, w2, w3,...,wn]
  • 偏置:b
    • 偏置可以理解成为了使图像有平移的功能而加入的常量值,如果没有偏置,那么所有图像就必交于一固定点

神经元首先用输入信号做加权计算,得到X的加权和:

将结果z经过激活函数(activation function)的非线性化处理得到该神经元的活性值(activation)a。

整个的流程可以用一张图表示为:

为啥需要激活函数呢?

从上面可以看出,加权求和后的方程是线性的,线性方程的特点就是复杂性较低,无法去模拟复杂类型的数据,如音视频之类的。没有经过激活函数处理的神经网络仅仅可以理解成线性回归模型。

而且线性方程是无法收敛的,对于神经网络需要梯度下降来做收敛是无法满足的。

目前流行的激活函数有:

  • Sigmoid
  • tanh
  • softplus
  • softsign
  • ELU
  • ReLU
  • ... ...

激活函数的具体内容不分析,感兴趣的同学可以自行google。

理解完了神经元的运行,再回到神经网络上,他的计算就是一层一层的往下计算了:

z(l) = w(l)*a(l-1) + b(l)

a(l-1) = f(l-1)(z(l-1))

前馈神经网络通过逐层的信息传递,最后得到的输出是a(L)。

在Smart Linkify中的应用

Smart Linkify里采用了两个紧凑低延时的前馈神经网络。神经网络的训练集是来源于是从网络上收集过来的,生成地址、电话等其他对应的列表。据了解,google的训练集的采集做到了针对不同的语言特征而采用了不同的算法。

大致流程如下:

  1. 对输入文本进行分词的提取,生成输入向量A
  2. 将输入向量A投入到神经网络中,第一层的神经网络为每个分词分配一个值(0~1),根据分词所代表的有效性和置信度,得分低的分词将从列表中删除,从而得到下一层的输入向量B
  3. 第二层神经网络对输入向量B进行分类

图形可描述为:

PS:对于google的具体操作是对网上各个片段信息的整合,也不一定是准确的。

TextClassifier API

这个是google提供出来的基于上面神经网络对文本进行分类的API。跟上层开发比较有关系的几个类如下:

TextClassificationManager.java

这个是文本分类服务,主要提供了访问和设置TextClassifier的方法。和其他的系统服务一样,也是通过getSystemService()来获取:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
TextClassificationManager tcm = getSystemService(TextClassificationManager.class);
TextClassifier.java

是文本分类的接口定义,系统缺省的实现是TextClassifierImpl

它目前定义了文本分类的类别:

  • TYPE_UNKNOWN
  • TYPE_OTHER
  • TYPE_EMAIL
  • TYPE_PHONE
  • TYPE_ADDRESS
  • TYPE_URL

核心的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public TextSelection suggestSelection(
   CharSequence text,
   int selectionStartIndex,
   int selectionEndIndex,
   LocaleList defaultLocales
)

public TextClassification classifyText(
    CharSequence text, 
    int startIndex, 
    int endIndex, 
    LocaleList defaultLocales
)

上面2个方法都是得到文本的分类,TextClassificationTextSelection都记录了文本分类的一些基本信息。

TextSelection.java

文本分类的基本信息,包括:

  • getText():被分类的文本
  • getEntityCount():可能的分类个数
  • getConfidenceScore():每个分类的置信度值(0~1)
  • getEntity():被归为的类别
  • getSelectionStartIndex():文本被分类的开始位置
  • getSelectionEndIndex():文本被分类的结束位置
TextClassification.java

文本分类的基本信息,包括:

  • getText():被分类的文本
  • getEntityCount():可能的分类个数
  • getConfidenceScore():每个分类的置信度值(0~1)
  • getEntity():被归为的类别
  • getIcon()、getLabel()、getIntent()、getOnClickListener():这些可以看成是为了显示快捷方式和点击直接调转,如图(红框):
TextClassifierImpl.java

TextClassifier.java的默认实现类。当自己不设置的时候,系统就默认使用这个。

SmartSelection.java

native library interface的java包装层:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static {
    System.loadLibrary("textclassifier");
}

private static native int[] nativeSuggest(
      long context, String text, int selectionBegin, int selectionEnd);

private static native ClassificationResult[] nativeClassifyText(
      long context, String text, int selectionBegin, int selectionEnd, int hintFlags);

大致的关系图为:

实例介绍

我们通过一个实例来看看,输出的结果是怎样的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MainActivity2 extends Activity {
    public static final String TAG = "111";

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextClassificationManager tcm = getSystemService(TextClassificationManager.class);
        final TextClassifier tc = tcm.getTextClassifier();

        String phone = "13671283112";
        String phone2 = "+8613671283112";
        String url = "http://www.baidu.com";
        String url2 = "www.baidu.com";
        String email = "hcjext@163.com";
        String address = "No.58,CengLin Road,Lingang,Shanghai,China";
        String chinese = "中国上海临港层林路58号";
        String other = "This is an apple";
        String other2 = "This is an 13671283112 apple";
        final List<String> texts = new ArrayList<>(Arrays.asList(phone, phone2, url, url2, email, address,chinese, other, other2));

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (String text : texts) {
                    TextClassification result = tc.classifyText(text, 0, text.length(), LocaleList.getDefault());
                    int entCount = result.getEntityCount();
                    Log.e(TAG, "Text : " + text);
                    Log.e(TAG, "EntityCount : " + String.valueOf(entCount));
                    for (int i = 0; i < entCount; i++) {
                        String ent = result.getEntity(i);
                        Log.e(TAG, "entity: " + ent
                                + ", score: " + result.getConfidenceScore(ent)
                                + ", label: " + result.getLabel()
                                + ", intent:  " + result.getIntent());
                    }
                }

                for (String text : texts) {
                    TextSelection result = tc.suggestSelection(text, 0, text.length(), LocaleList.getDefault());
                    int entCount = result.getEntityCount();
                    Log.e(TAG, "Text : " + text);
                    Log.e(TAG, "EntityCount : " + String.valueOf(entCount));
                    for (int i = 0; i < entCount; i++) {
                        String ent = result.getEntity(i);
                        Log.e(TAG, "entity: " + ent
                                + ", score: " + result.getConfidenceScore(ent)
                                + ", start: " + result.getSelectionStartIndex()
                                + ", end:  " + result.getSelectionEndIndex());
                    }
                }
            }
        }).start();
    }
}

当在英文环境下时TextClassification返回的信息:

有没有注意到这个这个EntityCount是0??

因为没有使用到中文的网络语言模型,所以识别不了,反馈网络是要基于已经学习的模型来判断的。

关于语言模型

语言模型是怎么使用的呢?我们看下TextClassifierImpl类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final String MODEL_DIR = "/etc/textclassifier/";
private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model";
private static final String UPDATED_MODEL_FILE_PATH = "/data/misc/textclassifier/textclassifier.smartselection.model";

这3个参数其实告知语言模型存放的位置,从哪里可以读取到模型。我的测试机上的【android 26的模拟器】:

可以看到只有en的语言模型。

实际的读取是在getFactoryModelFilePathsLocked()里操作的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Map<Locale, String> getFactoryModelFilePathsLocked() {
    if (mModelFilePaths == null) {
        final Map<Locale, String> modelFilePaths = new HashMap<>();
        final File modelsDir = new File(MODEL_DIR);
        if (modelsDir.exists() && modelsDir.isDirectory()) {
            final File[] models = modelsDir.listFiles();
            final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX);
            final int size = models.length;
            for (int i = 0; i < size; i++) {
                final File modelFile = models[i];
                final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName());
                if (matcher.matches() && modelFile.isFile()) {
                    final String language = matcher.group(1);
                    final Locale locale = Locale.forLanguageTag(language);
                    modelFilePaths.put(locale, modelFile.getAbsolutePath());
                }
            }
        }
        mModelFilePaths = modelFilePaths;
    }
    return mModelFilePaths;
}

如何安装其他模型?

目前google支持如下语言的模型:

安装的方式有预安装,或将模型作为系统映像来更新。这块的google的介绍比较少,我也没太弄明白~

参考文献

https://nndl.github.io/

http://www.atyun.com/26326.html

https://source.android.com/devices/tech/display/textclassifier

https://android.googlesource.com/platform/external/libtextclassifier/+/refs/heads/oreo-dev/Android.mk


作者简介:paynepan,天天P图Android工程师

文章后记: 天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习! 加入我们: 天天P图技术团队长期招聘:(1) 图像处理算法工程师,(2) Android / iOS 开发工程师,期待对我们感兴趣或者有推荐的技术牛人加入我们(base 上海)!联系方式:ttpic_dev@tencent.com

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-11-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天天P图攻城狮 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Web开发---单页面应用(签到日报--技术实现)
疫情前期,员工分布在各个地区,需要上报个人的健康状态和位置信息,于是做了一个单页面应用(当时钉钉和微信上的健康上报模板还没出现)
MiaoGIS
2020/03/16
8580
Web开发---单页面应用(签到日报--技术实现)
基于web页面开发串口程序界面---html代码
代码实现只展示读取设备参数的功能,写入即设置设备参数目前没有开发需求。 首先是html代码如下: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <
MiaoGIS
2020/09/14
4K0
基于web页面开发串口程序界面---html代码
Python进阶31-Django 分页器
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/09/26
1.6K0
Python进阶31-Django 分页器
Bootstrap Bootstrap表格插件bootstrap-table配置与应用小结
https://gitee.com/ishouke/front_end_plugin/blob/master/jquery-3.2.1.min.js
授客
2019/09/10
13.4K0
Bootstrap Bootstrap表格插件bootstrap-table配置与应用小结
[机器学习]线性回归-基于tensorflow.js
《传热学》横掠管外对流换热系数测定实验中,奴赛尔数Nu与雷诺数Re的关系式,通过实验测定,并确定公式中的系数C和指数n。这里使用机器学习进行线性回归。
周星星9527
2021/07/20
8650
家乡主题网页设计代码 旅游主题网页设计 html静态网页设计制作 dw静态网页成品模板素材网页 web前端网页设计与制作 div静态网页设计
家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法,如盒子的嵌套、浮动、margin、border、background等属性的使用,外部大盒子设定居中,内部左中右布局,下方横向浮动排列,大学学习的前端知识点和布局方式都有运用,CSS的代码量也很足、很细致,使用hover来完成过渡效果、鼠标滑过效果等,使用表格、表单补充模块,为方便新手学习页面中没有使用js有需要的可以自行添加。 <font color='#b44846' size='4px'> ❤</font> 【作者主页——🔥获取更多优质
IT司马青衫
2022/08/18
5.9K0
家乡主题网页设计代码 旅游主题网页设计 html静态网页设计制作 dw静态网页成品模板素材网页 web前端网页设计与制作 div静态网页设计
(三)Java高并发秒杀系统API之Web层开发
SpringMvc默认就会默认去WEB-INF下查找默认规范的配置文件,像我这里配置的servlet-name是seckill-dispatchServlet的话,则默认会寻找WEB-INF一个名为seckill-dispatchServlet-Servlet.xml的配置文件
Java团长
2018/12/25
2.8K0
最火的秒杀是如何实现的?
使用联合查询避免同一用户多次秒杀同一商品(利用在插入购物明细表中的秒杀id和用户的唯一标识来避免)。
互扯程序
2018/10/25
1.6K0
最火的秒杀是如何实现的?
Python测试开发-创建模态框及保存数据
模态框是指的在覆盖在父窗体上的子窗体。可用来做交互,我们经常会看到模态框用来登录、确定等等,到底是怎么实现这种弹出效果,bootstrap已经为我们提供了相应的组件。
测试开发社区
2019/09/20
1.3K0
Python测试开发-创建模态框及保存数据
python测试开发django-121.bootstrap-table弹出模态框修表格数据提交
整个body内容如下,模态框设置id属性id=”myModal” 修改按钮的id属性id=”btn_edit”
上海-悠悠
2021/09/14
1.4K0
前端开发---异步上传文件
有一个名为ajaxFileUpload的JQuery插件可以利用iframe来实现前端页面中异步上传文件。
MiaoGIS
2020/11/25
1.5K0
前端开发---异步上传文件
基于Jquery WeUI的微信开发H5页面控件的经验总结(2)
  在微信开发H5页面的时候,往往借助于WeUI或者Jquery WeUI等基础上进行界面效果的开发,由于本人喜欢在Asp.net的Web界面上使用JQuery,因此比较倾向于使用 jQuery WeUI,本篇随笔结合官方案例和自己的项目实际开发过程的经验总结,对在H5页面开发过程中设计到的界面控件进行逐一的分析和总结,以期能够给大家在H5页面开发过程中提供有用的参考。
不会飞的小鸟
2020/03/25
1.6K0
常用的CSS框架
常用的CSS框架 之前在写自己的个人网站的时候,由于自己Web前端不是特别好,于是就去找相关的CSS框架来搭建页面了。 找到以下这么一篇文章(列出了很多常用的CSS框架): http://w3schools.wang/report/top-UI-open-source-framework-summary.html Bootstrap Semantic-ui Foundation Materialize Material-ui Phantomjs Pure Flat-ui Jquery-ui React-bo
Java3y
2018/03/15
3.4K0
常用的CSS框架
bootstrap
花了一天时间学了下bootstrap入门,想必大家用css写前端页面的时候都很痛苦,bootstrap就是来解决这个问题的,它封装了css的很多样式,开发的时候直接拿来用就可以了,提高了开发效率
用户3112896
2019/09/26
3.5K0
bootstrap
【合肥信息技术职业学院】《PHP网站开发》作业设计
作业名称 系 别 信息工程学院 专业班级 2021级计算机应用技术*班 学 号 学生姓名
德宏大魔王
2023/08/08
2930
【合肥信息技术职业学院】《PHP网站开发》作业设计
BootStrap
iconfont的使用:https://www.cnblogs.com/changxin7/p/11479216.html
changxin7
2022/05/06
5.7K0
BootStrap
Spring Security 自定义登陆页面
在这个 Spring Security 教程中,我们将学到怎么创建一个自定义登陆页面来实现 Spring Security 基于表单的验证。
Jimmy_is_jimmy
2023/11/28
3830
Spring Security 自定义登陆页面
vue2基础
半月无霜
2023/10/18
3360
Larave-vue-crud-laravel-和vue-增删改查
看到successfully代表laravel安装成功,如果没有成功请换淘宝镜像重写安装.
胡哥有话说
2019/07/25
2.4K0
Larave-vue-crud-laravel-和vue-增删改查
【玩转全栈】----Django制作部门管理页面
我先给个大致效果,基本融合了Django、Bootstrap、css、html等等。
用户11404404
2025/01/24
1450
【玩转全栈】----Django制作部门管理页面
推荐阅读
相关推荐
Web开发---单页面应用(签到日报--技术实现)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验