JSF组件中的按键处理,尤其是<p:tree>

姆多斯

目标:我想用自己的行为丰富预定义的组件。列表,表和树通常是这种情况,实现了我的动作,例如“删除”,“添加前”,“添加后”,“上移”,...(对于文本字段,这似乎很简单... )

我以为必须有一种在组件本身上附加键侦听器的方法(假设存在“焦点”之类的东西),例如,如果我在页面上有两棵树,则按“ Ctrl +”将通过侦听器A向树A添加一次A另一个B通过listenerB到treeB。

在树节点或树本身上添加ajax侦听器不起作用。因此,似乎有必要(请参阅下面的两个答案)全局地捕获密钥并自己“适当地”分发它们。至少在一棵树上,这应该毫无麻烦。

根据下面的答案,这只能使用JavaScript或使用非标准的JSF标签来完成。

因为我一年最多要关注两次JSF问题,所以我认为更多参与其中的人可以在JSF和JavaScript之间的这个暮色区域提供最佳实践的见解。

在此代码段中,我想在按下“ +”时创建一个新的子项。

<h:form>
    <p:tree id="document" value="#{demo.root}" var="node"
        selectionMode="single" selection="#{demo.selection}">
        <p:treeNode>
            <h:outputText value="#{node.label}" />
        </p:treeNode>
    </p:tree>
</h:form>

标签

<f:ajax event="keypress" listener="#{demo.doTest}" />

在“ treeNode”和“ tree”中不被接受,并且在“ form”中没有任何功能。

=编辑

从答案中可以看出,只需使用即可支持此具体方案<p:hotkey>该解决方案有两个缺点,即Primefaces绑定,如果我们添加这样的输入组件,它将失败

<h:form>
    <p:tree id="document" value="#{demo.root}" var="node"
        selectionMode="single" selection="#{demo.selection}">
        <p:treeNode>
            <p:inputText value="#{node.label}" />
        </p:treeNode>
    </p:tree>
</h:form>

实施此类事情的最佳实践是什么?至少,在纯JSF中根本有可能吗?如果我只使用普通的JSF,那将是最丑陋的成语。

=编辑

我想指出一个简短的发现历史,作为下面的回答,以详细说明该问题背后的问题

米歇尔·马里奥蒂(Michele Mariotti)

此实现还可以导航和添加/删除。

恕我直言,它具有最佳的功能/努力比。

我不知道您对标准JSF标签纯JSF的含义,但是在此示例中没有一行JavaScript

注意,p:hotkey组件行为是全局的p:tree像您指出的那样,非输入组件(例如,非输入组件)不能拥有关键的侦听器,因为它们不能被“聚焦”(或至少是默认行为)。

但是,这里是:

<h:form>
    <p:hotkey bind="left" actionListener="#{testBean.onLeft}" process="@form" update="target" />
    <p:hotkey bind="right" actionListener="#{testBean.onRight}" process="@form" update="target" />
    <p:hotkey bind="up" actionListener="#{testBean.onUp}" process="@form" update="target" />
    <p:hotkey bind="down" actionListener="#{testBean.onDown}" process="@form" update="target" />
    <p:hotkey bind="ctrl+a" actionListener="#{testBean.onAdd}" process="@form" update="target" />
    <p:hotkey bind="ctrl+d" actionListener="#{testBean.onDelete}" process="@form" update="target" />

    <h:panelGroup id="target">

        <p:tree value="#{testBean.root}" var="data" selectionMode="single"
            selection="#{testBean.selection}" dynamic="true">
            <p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
                <h:outputText value="#{data}" />
            </p:treeNode>
        </p:tree>

        <br />

        <h3>current selection: #{testBean.selection.data}</h3>

    </h:panelGroup>
</h:form>

这是托管bean:

@ManagedBean
@ViewScoped
public class TestBean implements Serializable
{
    private static final long serialVersionUID = 1L;

    private DefaultTreeNode root;

    private TreeNode selection;

    @PostConstruct
    public void init()
    {
        root = new DefaultTreeNode("node");
        root.setSelectable(false);

        DefaultTreeNode node_0 = new DefaultTreeNode("node_0");
        DefaultTreeNode node_1 = new DefaultTreeNode("node_1");
        DefaultTreeNode node_0_0 = new DefaultTreeNode("node_0_0");
        DefaultTreeNode node_0_1 = new DefaultTreeNode("node_0_1");
        DefaultTreeNode node_1_0 = new DefaultTreeNode("node_1_0");
        DefaultTreeNode node_1_1 = new DefaultTreeNode("node_1_1");

        node_0.setParent(root);
        root.getChildren().add(node_0);

        node_1.setParent(root);
        root.getChildren().add(node_1);

        node_0_0.setParent(node_0);
        node_0.getChildren().add(node_0_0);

        node_0_1.setParent(node_0);
        node_0.getChildren().add(node_0_1);

        node_1_0.setParent(node_1);
        node_1.getChildren().add(node_1_0);

        node_1_1.setParent(node_1);
        node_1.getChildren().add(node_1_1);

        selection = node_0;
        node_0.setSelected(true);
    }

    private void initSelection()
    {
        List<TreeNode> children = root.getChildren();
        if(!children.isEmpty())
        {
            selection = children.get(0);
            selection.setSelected(true);
        }
    }

    public void onLeft()
    {
        if(selection == null)
        {
            initSelection();
            return;
        }

        if(selection.isExpanded())
        {
            selection.setExpanded(false);
            return;
        }

        TreeNode parent = selection.getParent();
        if(parent != null && !parent.equals(root))
        {
            selection.setSelected(false);
            selection = parent;
            selection.setSelected(true);
        }
    }

    public void onRight()
    {
        if(selection == null)
        {
            initSelection();
            return;
        }

        if(selection.isLeaf())
        {
            return;
        }

        if(!selection.isExpanded())
        {
            selection.setExpanded(true);
            return;
        }

        List<TreeNode> children = selection.getChildren();
        if(!children.isEmpty())
        {
            selection.setSelected(false);
            selection = children.get(0);
            selection.setSelected(true);
        }
    }

    public void onUp()
    {
        if(selection == null)
        {
            initSelection();
            return;
        }

        TreeNode prev = findPrev(selection);
        if(prev != null)
        {
            selection.setSelected(false);
            selection = prev;
            selection.setSelected(true);
        }
    }

    public void onDown()
    {
        if(selection == null)
        {
            initSelection();
            return;
        }

        if(selection.isExpanded())
        {
            List<TreeNode> children = selection.getChildren();
            if(!children.isEmpty())
            {
                selection.setSelected(false);
                selection = children.get(0);
                selection.setSelected(true);
                return;
            }
        }

        TreeNode next = findNext(selection);
        if(next != null)
        {
            selection.setSelected(false);
            selection = next;
            selection.setSelected(true);
        }
    }

    public void onAdd()
    {
        if(selection == null)
        {
            selection = root;
        }

        TreeNode node = createNode();
        node.setParent(selection);
        selection.getChildren().add(node);
        selection.setExpanded(true);

        selection.setSelected(false);
        selection = node;
        selection.setSelected(true);
    }

    public void onDelete()
    {
        if(selection == null)
        {
            return;
        }

        TreeNode parent = selection.getParent();
        parent.getChildren().remove(selection);

        if(!parent.equals(root))
        {
            selection = parent;
            selection.setSelected(true);

            if(selection.isLeaf())
            {
                selection.setExpanded(false);
            }
        }
        else
        {
            selection = null;
        }

    }

    // create the new node the way you like, this is an example
    private TreeNode createNode()
    {
        int prog = 0;
        TreeNode lastNode = Iterables.getLast(selection.getChildren(), null);
        if(lastNode != null)
        {
            prog = NumberUtils.toInt(StringUtils.substringAfterLast(String.valueOf(lastNode.getData()), "_"), -1) + 1;
        }

        return new DefaultTreeNode(selection.getData() + "_" + prog);
    }

    private TreeNode findNext(TreeNode node)
    {
        TreeNode parent = node.getParent();
        if(parent == null)
        {
            return null;
        }

        List<TreeNode> brothers = parent.getChildren();
        int index = brothers.indexOf(node);
        if(index < brothers.size() - 1)
        {
            return brothers.get(index + 1);
        }

        return findNext(parent);
    }

    private TreeNode findPrev(TreeNode node)
    {
        TreeNode parent = node.getParent();
        if(parent == null)
        {
            return null;
        }

        List<TreeNode> brothers = parent.getChildren();
        int index = brothers.indexOf(node);
        if(index > 0)
        {
            return findLastUnexpanded(brothers.get(index - 1));
        }

        if(!parent.equals(root))
        {
            return parent;
        }

        return null;

    }

    private TreeNode findLastUnexpanded(TreeNode node)
    {
        if(!node.isExpanded())
        {
            return node;
        }

        List<TreeNode> children = node.getChildren();
        if(children.isEmpty())
        {
            return node;
        }

        return findLastUnexpanded(Iterables.getLast(children));
    }

    public TreeNode getRoot()
    {
        return root;
    }

    public TreeNode getSelection()
    {
        return selection;
    }

    public void setSelection(TreeNode selection)
    {
        this.selection = selection;
    }
}

更新

也许我找到了一个有趣的解决方案,将键绑定附加到单个DOM元素:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite" xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
    xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions" xmlns:p="http://primefaces.org/ui"
    xmlns:o="http://omnifaces.org/ui" xmlns:of="http://omnifaces.org/functions"
    xmlns:s="http://shapeitalia.com/jsf2" xmlns:sc="http://xmlns.jcp.org/jsf/composite/shape"
    xmlns:e="http://java.sun.com/jsf/composite/cc" xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">

<h:head>
    <title>test hotkey</title>
</h:head>

<h:body>
    <h:form>
        <h:panelGroup id="container1">
            <s:hotkey bind="left" actionListener="#{testBean.onLeft}" update="container1" />
            <s:hotkey bind="right" actionListener="#{testBean.onRight}" update="container1" />
            <s:hotkey bind="up" actionListener="#{testBean.onUp}" update="container1" />
            <s:hotkey bind="down" actionListener="#{testBean.onDown}" update="container1" />
            <s:hotkey bind="ctrl+a" actionListener="#{testBean.onAdd}" update="container1" />
            <s:hotkey bind="ctrl+d" actionListener="#{testBean.onDelete}" update="container1" />

            <p:tree value="#{testBean.root}" var="data" selectionMode="single"
                selection="#{testBean.selection}" dynamic="true" pt:tabindex="1">
                <p:treeNode expandedIcon="ui-icon-folder-open"
                    collapsedIcon="ui-icon-folder-collapsed">
                    <h:outputText value="#{data}" />
                </p:treeNode>
            </p:tree>
            <br />

            <h3>current selection: #{testBean.selection.data}</h3>
        </h:panelGroup>
    </h:form>
</h:body>
</html>

三件重要的事情:

  1. h:panelGroup属性id是必需的,否则它不呈现为DOM元素。stylestyleClass以及其他可启用渲染的属性可以与或一起使用。
  2. 注意pt:tabindex=1on p:tree:需要启用“聚焦”。pt是用于“直通”属性的名称空间,仅在JSF 2.2中有效。
  3. HotkeyRenderer为了将DOM事件侦听器附加到特定的DOM元素而不是整个文档,我必须进行自定义:现在是,s:hotkey而不是p:hotkey我的实现将其附加到与父组件关联的DOM元素上,继续阅读以进行实现。

修改后的渲染器:

@FacesRenderer(componentFamily = Hotkey.COMPONENT_FAMILY, rendererType = "it.shape.HotkeyRenderer")
public class HotkeyRenderer extends org.primefaces.component.hotkey.HotkeyRenderer
{
    @SuppressWarnings("resource")
    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException
    {
        ResponseWriter writer = context.getResponseWriter();
        Hotkey hotkey = (Hotkey) component;
        String clientId = hotkey.getClientId(context);

        String targetClientId = hotkey.getParent().getClientId();

        writer.startElement("script", null);
        writer.writeAttribute("type", "text/javascript", null);

        writer.write("$(function() {");
        writer.write("$(PrimeFaces.escapeClientId('" + targetClientId + "')).bind('keydown', '" + hotkey.getBind() + "', function(){");

        if(hotkey.isAjaxified())
        {
            UIComponent form = ComponentUtils.findParentForm(context, hotkey);

            if(form == null)
            {
                throw new FacesException("Hotkey '" + clientId + "' needs to be enclosed in a form when ajax mode is enabled");
            }

            AjaxRequestBuilder builder = RequestContext.getCurrentInstance().getAjaxRequestBuilder();

            String request = builder.init()
                .source(clientId)
                .form(form.getClientId(context))
                .process(component, hotkey.getProcess())
                .update(component, hotkey.getUpdate())
                .async(hotkey.isAsync())
                .global(hotkey.isGlobal())
                .delay(hotkey.getDelay())
                .timeout(hotkey.getTimeout())
                .partialSubmit(hotkey.isPartialSubmit(), hotkey.isPartialSubmitSet())
                .resetValues(hotkey.isResetValues(), hotkey.isResetValuesSet())
                .ignoreAutoUpdate(hotkey.isIgnoreAutoUpdate())
                .onstart(hotkey.getOnstart())
                .onerror(hotkey.getOnerror())
                .onsuccess(hotkey.getOnsuccess())
                .oncomplete(hotkey.getOncomplete())
                .params(hotkey)
                .build();

            writer.write(request);

        }
        else
        {
            writer.write(hotkey.getHandler());
        }

        writer.write(";return false;});});");

        writer.endElement("script");
    }
}

最后,这是新标签的taglib定义s:hotkey(这是原始标签的复制/粘贴,唯一的区别是<renderer-type>it.shape.HotkeyRenderer</renderer-type>):

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
    <namespace>http://shapeitalia.com/jsf2</namespace>

    <tag>
        <description><![CDATA[HotKey is a generic key binding component that can bind any formation of keys to javascript event handlers or ajax calls.]]></description>
        <tag-name>hotkey</tag-name>
        <component>
            <component-type>org.primefaces.component.Hotkey</component-type>
            <renderer-type>it.shape.HotkeyRenderer</renderer-type>
        </component>
        <attribute>
            <description><![CDATA[Unique identifier of the component in a namingContainer.]]></description>
            <name>id</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Boolean value to specify the rendering of the component, when set to false component will not be rendered.]]></description>
            <name>rendered</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
        <attribute>
            <description><![CDATA[An el expression referring to a server side UIComponent instance in a backing bean.]]></description>
            <name>binding</name>
            <required>false</required>
            <type>javax.faces.component.UIComponent</type>
        </attribute>
        <attribute>
            <description><![CDATA[An actionlistener that'd be processed in the partial request caused by uiajax.]]></description>
            <name>actionListener</name>
            <required>false</required>
            <type>javax.faces.event.ActionListener</type>
        </attribute>
        <attribute>
            <description><![CDATA[A method expression that'd be processed in the partial request caused by uiajax.]]></description>
            <name>action</name>
            <required>false</required>
            <type>javax.el.MethodExpression</type>
        </attribute>
        <attribute>
            <description><![CDATA[Boolean value that determines the phaseId, when true actions are processed at apply_request_values, when false at invoke_application phase.]]></description>
            <name>immediate</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
        <attribute>
            <description><![CDATA[The Key binding. Required.]]></description>
            <name>bind</name>
            <required>true</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Client side id of the component(s) to be updated after async partial submit request.]]></description>
            <name>update</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Component id(s) to process partially instead of whole view.]]></description>
            <name>process</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Javascript event handler to be executed when the key binding is pressed.]]></description>
            <name>handler</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Javascript handler to execute before ajax request is begins.]]></description>
            <name>onstart</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Javascript handler to execute when ajax request is completed.]]></description>
            <name>oncomplete</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Javascript handler to execute when ajax request fails.]]></description>
            <name>onerror</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Javascript handler to execute when ajax request succeeds.]]></description>
            <name>onsuccess</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Global ajax requests are listened by ajaxStatus component, setting global to false will not trigger ajaxStatus. Default is true.]]></description>
            <name>global</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
        <attribute>
            <description><![CDATA[If less than delay milliseconds elapses between calls to request() only the most recent one is sent and all other requests are discarded. The default value of this option is null. If the value of delay is the literal string 'none' without the quotes or the default, no delay is used.]]></description>
            <name>delay</name>
            <required>false</required>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <description><![CDATA[Defines the timeout for the ajax request.]]></description>
            <name>timeout</name>
            <required>false</required>
            <type>java.lang.Integer</type>
        </attribute>
        <attribute>
            <description><![CDATA[When set to true, ajax requests are not queued. Default is false.]]></description>
            <name>async</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
        <attribute>
            <description><![CDATA[When enabled, only values related to partially processed components would be serialized for ajax 
            instead of whole form.]]></description>
            <name>partialSubmit</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
        <attribute>
            <description><![CDATA[If true, indicate that this particular Ajax transaction is a value reset transaction. This will cause resetValue() to be called on any EditableValueHolder instances encountered as a result of this ajax transaction. If not specified, or the value is false, no such indication is made.]]></description>
            <name>resetValues</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
        <attribute>
            <description><![CDATA[If true, components which autoUpdate="true" will not be updated for this request. If not specified, or the value is false, no such indication is made.]]></description>
            <name>ignoreAutoUpdate</name>
            <required>false</required>
            <type>java.lang.Boolean</type>
        </attribute>
    </tag>
</facelet-taglib>

哇,这很难;)

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

jsf p:tree复选框选择

来自分类Dev

我如何正确处理C中的malloc失败,尤其是当有多个malloc时?

来自分类Dev

在Express / Node.js中处理503错误,尤其是在TryCatch情况下

来自分类Dev

Java中的typedef,尤其是数组类型

来自分类Dev

Java中的typedef,尤其是数组类型

来自分类Dev

何时使用EL 2.2引入的参数化方法调用(尤其是在JSF 2.x中)?

来自分类Dev

如何处理Java编码问题(尤其是xml)?

来自分类Dev

弄乱网页中的字符(尤其是社交媒体)

来自分类Dev

一般缓存,尤其是MVC中的缓存

来自分类Dev

R:如何在函数中添加参数(尤其是“ ...”)?

来自分类Dev

无法打开https,尤其是在phantomjs中打开salesforce网站

来自分类Dev

弄乱网页中的字符(尤其是社交媒体)

来自分类Dev

R中的浮点困难,尤其是Hmisc的minor.tick

来自分类Dev

重置p:tree选定的值

来自分类Dev

Primefaces无法更新p:tree

来自分类Dev

更改路线时,停止重新渲染组件。尤其是路线外的组件

来自分类Dev

如何处理不同的触摸事件,尤其是点击和长按屏幕?

来自分类Dev

Primefaces IE p:tree无法扩展

来自分类Dev

Servlet / JSP MVC模式如何转换为JSF / Facelets(尤其是服务和控制器部分)?

来自分类Dev

Servlet / JSP MVC模式如何转换为JSF / Facelets(尤其是服务和控制器部分)?

来自分类Dev

在方法中创建“匿名枚举”(尤其是在VB.NET中)

来自分类Dev

ffmpeg中的“ -s”和“ -vf scale =”之间有区别,尤其是在两遍转码中?

来自分类Dev

阻止iframe中的链接导航/接管cordova Webview,尤其是在ios中

来自分类Dev

如何在Swift中检查位域(尤其是SCNetworkReachabilityFlags)中的标志?

来自分类Dev

在Frege中与Java的互操作性,尤其是在IO Monad中

来自分类Dev

整数如何存储在iOS中,尤其是.pList和CoreData中

来自分类Dev

防止在Awesome WM中窃取焦点,尤其是从Skype中窃取焦点

来自分类Dev

通常,在shell脚本中,尤其是在Bash中,存在哪些退出模式?

来自分类Dev

编程语言(尤其是在 dart 中)中的死代码是什么意思?

Related 相关文章

  1. 1

    jsf p:tree复选框选择

  2. 2

    我如何正确处理C中的malloc失败,尤其是当有多个malloc时?

  3. 3

    在Express / Node.js中处理503错误,尤其是在TryCatch情况下

  4. 4

    Java中的typedef,尤其是数组类型

  5. 5

    Java中的typedef,尤其是数组类型

  6. 6

    何时使用EL 2.2引入的参数化方法调用(尤其是在JSF 2.x中)?

  7. 7

    如何处理Java编码问题(尤其是xml)?

  8. 8

    弄乱网页中的字符(尤其是社交媒体)

  9. 9

    一般缓存,尤其是MVC中的缓存

  10. 10

    R:如何在函数中添加参数(尤其是“ ...”)?

  11. 11

    无法打开https,尤其是在phantomjs中打开salesforce网站

  12. 12

    弄乱网页中的字符(尤其是社交媒体)

  13. 13

    R中的浮点困难,尤其是Hmisc的minor.tick

  14. 14

    重置p:tree选定的值

  15. 15

    Primefaces无法更新p:tree

  16. 16

    更改路线时,停止重新渲染组件。尤其是路线外的组件

  17. 17

    如何处理不同的触摸事件,尤其是点击和长按屏幕?

  18. 18

    Primefaces IE p:tree无法扩展

  19. 19

    Servlet / JSP MVC模式如何转换为JSF / Facelets(尤其是服务和控制器部分)?

  20. 20

    Servlet / JSP MVC模式如何转换为JSF / Facelets(尤其是服务和控制器部分)?

  21. 21

    在方法中创建“匿名枚举”(尤其是在VB.NET中)

  22. 22

    ffmpeg中的“ -s”和“ -vf scale =”之间有区别,尤其是在两遍转码中?

  23. 23

    阻止iframe中的链接导航/接管cordova Webview,尤其是在ios中

  24. 24

    如何在Swift中检查位域(尤其是SCNetworkReachabilityFlags)中的标志?

  25. 25

    在Frege中与Java的互操作性,尤其是在IO Monad中

  26. 26

    整数如何存储在iOS中,尤其是.pList和CoreData中

  27. 27

    防止在Awesome WM中窃取焦点,尤其是从Skype中窃取焦点

  28. 28

    通常,在shell脚本中,尤其是在Bash中,存在哪些退出模式?

  29. 29

    编程语言(尤其是在 dart 中)中的死代码是什么意思?

热门标签

归档