最佳实践

https://www.yuque.com/boyan-avfmj/aviatorscript/ou23gy

STEP1: Aviator 执行表达式引擎==》规则引擎

最简单的例子

因为布尔值只是对应的 boolean,每一个对应一个条件。

所以最后的结果只是一个类似于 (c1 && c2) || (c3 || c4) 的表达式。

public static void main(String[] args) {
    Map<String, Object> env = new HashMap<>();
    env.put("c1", true);
    env.put("c2", false);
    env.put("c3", true);
    env.put("c4", false);
    env.put("c5", true);
    boolean result = (boolean) AviatorEvaluator.execute("(c1 && c2) || (c3 || c4)", env);
    System.out.println(result);
}

STEP2: 页面构建

如何把表达式在页面中渲染

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Expression Renderer</title>
    <style>
        .expr-element {
            display: inline-block;
            margin: 5px;
            padding: 5px;
            background-color: #e0e0e0;
            cursor: pointer;
        }
        .operator {
            background-color: #d1c4e9;
        }
        .variable {
            background-color: #c8e6c9;
        }
        .paren {
            background-color: #ffccbc;
        }
    </style>
</head>
<body>

<div id="expression-container"></div>

<script>
    // The expression to be rendered
    const expression = "(c1 && c2) || (c3 || c4)";

    // Function to render the expression as HTML elements
    function renderExpression(expr) {
        const container = document.getElementById('expression-container');
        container.innerHTML = ''; // Clear previous content

        const regex = /(\(|\)|\&\&|\|\||[a-zA-Z0-9_]+)/g;
        const tokens = expr.match(regex);

        tokens.forEach(token => {
            const div = document.createElement('div');
            div.classList.add('expr-element');

            if (token === '&&' || token === '||') {
                div.classList.add('operator');
                div.textContent = token;
            } else if (token === '(' || token === ')') {
                div.classList.add('paren');
                div.textContent = token;
            } else {
                div.classList.add('variable');
                div.textContent = token;
            }

            // Add the div element to the container
            container.appendChild(div);
        });
    }

    // Render the expression
    renderExpression(expression);
</script>

</body>
</html>

STEP3: 页面动态渲染

基本需求

我想实现一个效果,首先可以定义各种变量,比如 c1,c2,c3。

然后让用户选择他们之间的与或非,用括号控制优先级。最简单的方式是让用户手动填写,但是可能会选错。如何设计一个简单的页面操作,首先将变量 c1,c2,c3.. 渲染为普通的 div 元素块,选择对应元素,点击下方的操作【且】【或】为元素加上对应的关系和括号。

比如选择 c1,c2,点击【且】,则页面渲染为 (c1 且 c2); 然后括号内的元素 (c1且c2)下次选择就会变成一个整体,选中后,此时再选择c3,,操作选择【或】,会变成((c1且c2) 或 c3)。依次类推,给出通用的 html 是实现代码。

初步实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Logical Expression Builder</title>
    <style>
        .expr-container {
            margin-bottom: 10px;
        }

        .expr-element {
            display: inline-block;
            margin: 5px;
            padding: 5px;
            background-color: #c8e6c9;
            cursor: pointer;
            border-radius: 5px;
        }

        .selected {
            background-color: #ffeb3b;  /* Highlight selected element */
        }

        .operator, .paren {
            background-color: #ffccbc;
            cursor: pointer;
            border-radius: 5px;
            padding: 5px;
        }

        #operation-buttons {
            margin-top: 20px;
        }

        .expr {
            margin-bottom: 10px;
        }

        #result {
            margin-top: 20px;
            padding: 10px;
            background-color: #e0e0e0;
        }
    </style>
</head>
<body>

<div id="variables-container">
    <div class="expr-container" id="variables">
        <div class="expr-element" data-value="c1">c1</div>
        <div class="expr-element" data-value="c2">c2</div>
        <div class="expr-element" data-value="c3">c3</div>
        <div class="expr-element" data-value="c4">c4</div>
        <div class="expr-element" data-value="c5">c5</div>
    </div>
</div>

<div id="operation-buttons">
    <button class="operation" data-op="&&"></button>
    <button class="operation" data-op="||"></button>
</div>

<div id="expression-container" class="expr-container"></div>

<div id="result"></div>

<script>
    let selectedElements = []; // Stores the current selected elements or expressions
    const expressionContainer = document.getElementById('expression-container');
    const resultContainer = document.getElementById('result');

    // Function to render the current expression
    function renderExpression() {
        expressionContainer.innerHTML = '';
        selectedElements.forEach(el => {
            const div = document.createElement('div');
            div.classList.add('expr-element');
            div.textContent = el;
            expressionContainer.appendChild(div);
        });
        updateResult();
    }

    // Function to update the result container
    function updateResult() {
        resultContainer.textContent = selectedElements.join(' ');
    }

    // Handle selection of variables (c1, c2, etc.)
    document.getElementById('variables').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('expr-element')) {
            const value = e.target.getAttribute('data-value');
            const element = e.target;

            // Toggle selected class
            if (element.classList.contains('selected')) {
                element.classList.remove('selected');
                const index = selectedElements.indexOf(value);
                if (index !== -1) {
                    selectedElements.splice(index, 1);
                }
            } else {
                element.classList.add('selected');
                selectedElements.push(value);
            }
            renderExpression();
        }
    });

    // Handle the operation buttons (&&, ||)
    document.getElementById('operation-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('operation')) {
            const operator = e.target.getAttribute('data-op');
            if (selectedElements.length >= 2) {
                // Add the operator between the last two elements
                const lastElement = selectedElements.pop();
                const secondLastElement = selectedElements.pop();
                selectedElements.push(`(${secondLastElement} ${operator} ${lastElement})`);
                renderExpression();
            }
        }
    });

    // Render initial state (empty expression)
    renderExpression();
</script>

</body>
</html>

页面优化

看了下你的实现,非常不错。让我们按照这个方向继续优化

1)上方最基本的 c1,c2…cn 元素

2)中间是操作关系栏:【且】【或】

3)下方是关系效果栏

4)最下方是最后的表达式输出。

将你上面的代码加一个对应的分块标签描述,让其看起来更加清晰。

优化后的实现:

实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Logical Expression Builder</title>
    <style>
        /* Basic styling for layout */
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            padding: 0;
            background-color: #f5f5f5;
        }

        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #ffffff;
            border-radius: 10px;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
        }

        .section {
            margin-bottom: 20px;
        }

        .expr-container {
            margin-bottom: 10px;
            display: flex;
            flex-wrap: wrap;
        }

        .expr-element {
            display: inline-block;
            margin: 5px;
            padding: 8px;
            background-color: #c8e6c9;
            cursor: pointer;
            border-radius: 5px;
            transition: background-color 0.2s;
        }

        .selected {
            background-color: #ffeb3b; /* Highlight selected element */
        }

        .operator, .paren {
            background-color: #ffccbc;
            cursor: pointer;
            border-radius: 5px;
            padding: 5px;
            margin: 5px;
            transition: background-color 0.2s;
        }

        .operator:hover, .paren:hover, .expr-element:hover {
            background-color: #ff9800;
        }

        #operation-buttons {
            display: flex;
            justify-content: space-between;
            margin-top: 20px;
        }

        #operation-buttons button {
            width: 45%;
        }

        #expression-container {
            margin-top: 20px;
            display: flex;
            flex-wrap: wrap;
            background-color: #f1f8e9;
            padding: 10px;
            border-radius: 5px;
        }

        #result {
            margin-top: 20px;
            padding: 10px;
            background-color: #e0e0e0;
            border-radius: 5px;
            font-weight: bold;
        }

    </style>
</head>
<body>

<div class="container">

    <!-- Section 1: Variable Elements -->
    <div class="section">
        <h3>1) 选择变量:</h3>
        <div class="expr-container" id="variables">
            <div class="expr-element" data-value="c1">c1</div>
            <div class="expr-element" data-value="c2">c2</div>
            <div class="expr-element" data-value="c3">c3</div>
            <div class="expr-element" data-value="c4">c4</div>
            <div class="expr-element" data-value="c5">c5</div>
        </div>
    </div>

    <!-- Section 2: Operator Buttons -->
    <div class="section">
        <h3>2) 选择操作符:</h3>
        <div id="operation-buttons">
            <button class="operation" data-op="&&"></button>
            <button class="operation" data-op="||"></button>
        </div>
    </div>

    <!-- Section 3: Expression Preview -->
    <div class="section">
        <h3>3) 当前关系效果:</h3>
        <div id="expression-container" class="expr-container"></div>
    </div>

    <!-- Section 4: Final Expression Output -->
    <div class="section">
        <h3>4) 最终表达式:</h3>
        <div id="result"></div>
    </div>

</div>

<script>
    let selectedElements = []; // Stores the current selected elements or expressions
    const expressionContainer = document.getElementById('expression-container');
    const resultContainer = document.getElementById('result');

    // Function to render the current expression
    function renderExpression() {
        expressionContainer.innerHTML = '';
        selectedElements.forEach(el => {
            const div = document.createElement('div');
            div.classList.add('expr-element');
            div.textContent = el;
            expressionContainer.appendChild(div);
        });
        updateResult();
    }

    // Function to update the result container
    function updateResult() {
        resultContainer.textContent = selectedElements.join(' ');
    }

    // Handle selection of variables (c1, c2, etc.)
    document.getElementById('variables').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('expr-element')) {
            const value = e.target.getAttribute('data-value');
            const element = e.target;

            // Toggle selected class
            if (element.classList.contains('selected')) {
                element.classList.remove('selected');
                const index = selectedElements.indexOf(value);
                if (index !== -1) {
                    selectedElements.splice(index, 1);
                }
            } else {
                element.classList.add('selected');
                selectedElements.push(value);
            }
            renderExpression();
        }
    });

    // Handle the operation buttons (&&, ||)
    document.getElementById('operation-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('operation')) {
            const operator = e.target.getAttribute('data-op');
            if (selectedElements.length >= 2) {
                // Add the operator between the last two elements
                const lastElement = selectedElements.pop();
                const secondLastElement = selectedElements.pop();
                selectedElements.push(`(${secondLastElement} ${operator} ${lastElement})`);
                renderExpression();
            }
        }
    });

    // Render initial state (empty expression)
    renderExpression();
</script>

</body>
</html>

STEP4: 拓展操作符

需求

非常好。

让我们继续改进,选择操作符这里栏目前只有【且】【或】两个关系按钮;让我们继续增加几个快捷操作,方便用户操作:

1)满足全部:关系效果为全部变量的关系为 &&

2)满足任一:关系效果为全部变量的关系为  
页面布局为 【满足全部】【满足任一】 【且】【或】

实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Logical Expression Builder</title>
    <style>
        /* Basic styling for layout */
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            padding: 0;
            background-color: #f5f5f5;
        }

        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #ffffff;
            border-radius: 10px;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
        }

        .section {
            margin-bottom: 20px;
        }

        .expr-container {
            margin-bottom: 10px;
            display: flex;
            flex-wrap: wrap;
        }

        .expr-element {
            display: inline-block;
            margin: 5px;
            padding: 8px;
            background-color: #c8e6c9;
            cursor: pointer;
            border-radius: 5px;
            transition: background-color 0.2s;
        }

        .selected {
            background-color: #ffeb3b; /* Highlight selected element */
        }

        .operator, .paren, .quick-operation {
            background-color: #ffccbc;
            cursor: pointer;
            border-radius: 5px;
            padding: 5px;
            margin: 5px;
            transition: background-color 0.2s;
            width: 45%; /* Keep the buttons consistent in size */
        }

        .operator:hover, .paren:hover, .expr-element:hover, .quick-operation:hover {
            background-color: #ff9800;
        }

        #operation-buttons, #quick-buttons {
            display: flex;
            justify-content: space-between;
            margin-top: 20px;
        }

        #operation-buttons button, #quick-buttons button {
            width: 45%;
        }

        #expression-container {
            margin-top: 20px;
            display: flex;
            flex-wrap: wrap;
            background-color: #f1f8e9;
            padding: 10px;
            border-radius: 5px;
        }

        #result {
            margin-top: 20px;
            padding: 10px;
            background-color: #e0e0e0;
            border-radius: 5px;
            font-weight: bold;
        }

    </style>
</head>
<body>

<div class="container">

    <!-- Section 1: Variable Elements -->
    <div class="section">
        <h3>1) 选择变量:</h3>
        <div class="expr-container" id="variables">
            <div class="expr-element" data-value="c1">c1</div>
            <div class="expr-element" data-value="c2">c2</div>
            <div class="expr-element" data-value="c3">c3</div>
            <div class="expr-element" data-value="c4">c4</div>
            <div class="expr-element" data-value="c5">c5</div>
        </div>
    </div>

    <!-- Section 2: Operator Buttons -->
    <div class="section">
        <h3>2) 选择操作符:</h3>
        <div id="quick-buttons">
            <button class="quick-operation" data-op="all">满足全部</button>
            <button class="quick-operation" data-op="any">满足任一</button>
        </div>
        <div id="operation-buttons">
            <button class="operation" data-op="&&"></button>
            <button class="operation" data-op="||"></button>
        </div>
    </div>

    <!-- Section 3: Expression Preview -->
    <div class="section">
        <h3>3) 当前关系效果:</h3>
        <div id="expression-container" class="expr-container"></div>
    </div>

    <!-- Section 4: Final Expression Output -->
    <div class="section">
        <h3>4) 最终表达式:</h3>
        <div id="result"></div>
    </div>

</div>

<script>
    let selectedElements = []; // Stores the current selected elements or expressions
    let currentExpression = ""; // Stores the current expression as a string
    const expressionContainer = document.getElementById('expression-container');
    const resultContainer = document.getElementById('result');

    // Function to render the current expression
    function renderExpression() {
        expressionContainer.innerHTML = '';
        const elements = currentExpression.split(' ').map(el => {
            const div = document.createElement('div');
            div.classList.add('expr-element');
            div.textContent = el;
            return div;
        });
        elements.forEach(div => expressionContainer.appendChild(div));
        updateResult();
    }

    // Function to update the result container
    function updateResult() {
        resultContainer.textContent = currentExpression;
    }

    // Handle selection of variables (c1, c2, etc.)
    document.getElementById('variables').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('expr-element')) {
            const value = e.target.getAttribute('data-value');
            const element = e.target;

            // Toggle selected class
            if (element.classList.contains('selected')) {
                element.classList.remove('selected');
                const index = selectedElements.indexOf(value);
                if (index !== -1) {
                    selectedElements.splice(index, 1);
                }
            } else {
                element.classList.add('selected');
                selectedElements.push(value);
            }
            updateCurrentExpression();
            renderExpression();
        }
    });

    // Handle the operation buttons (&&, ||)
    document.getElementById('operation-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('operation')) {
            const operator = e.target.getAttribute('data-op');
            if (selectedElements.length >= 2) {
                // Add the operator between the last two elements
                const lastElement = selectedElements.pop();
                const secondLastElement = selectedElements.pop();
                selectedElements.push(`(${secondLastElement} ${operator} ${lastElement})`);
                updateCurrentExpression();
                renderExpression();
            }
        }
    });

    // Handle the quick operation buttons (满足全部, 满足任一)
    document.getElementById('quick-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('quick-operation')) {
            const quickOp = e.target.getAttribute('data-op');
            if (selectedElements.length >= 1) {
                if (quickOp === 'all') {
                    // For "满足全部", use && for all selected elements
                    currentExpression = selectedElements.join(' && ');
                } else if (quickOp === 'any') {
                    // For "满足任一", use || for all selected elements
                    currentExpression = selectedElements.join(' || ');
                }
                currentExpression = `(${currentExpression})`; // Wrap the result in parentheses
                renderExpression();
            }
        }
    });

    // Update the current expression string based on the selected elements
    function updateCurrentExpression() {
        currentExpression = selectedElements.join(' ');
    }

    // Render initial state (empty expression)
    renderExpression();
</script>

</body>
</html>

STEP5: 拓展元素

需求

实际使用过程中,变量一般不是固定死的。

让我们在最下方加一个条件变量模块。

1)变量名固定为 c1, c2 这种形式,从 1 开始递增。

2)变量拥有下面的属性

a. 变量名,系统自动生成,按照 1 的要求

b. 变量描述,允许用户输入 input,方便用户理解。

3)初始化固定一个变量 c1,点击【新增条件】按钮,可以增加一个变量,点击后方的【删除条件】删除对应条件

4)至少保留1个变量,最多10个变量

这个变量同步更新到我们原来的选择变量栏中,给出完整的实现:

实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Logical Expression Builder</title>
    <style>
        /* Basic styling for layout */
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            padding: 0;
            background-color: #f5f5f5;
        }

        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #ffffff;
            border-radius: 10px;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
        }

        .section {
            margin-bottom: 20px;
        }

        .expr-container {
            margin-bottom: 10px;
            display: flex;
            flex-wrap: wrap;
        }

        .expr-element {
            display: inline-block;
            margin: 5px;
            padding: 8px;
            background-color: #c8e6c9;
            cursor: pointer;
            border-radius: 5px;
            transition: background-color 0.2s;
        }

        .selected {
            background-color: #ffeb3b; /* Highlight selected element */
        }

        .operator, .paren, .quick-operation {
            background-color: #ffccbc;
            cursor: pointer;
            border-radius: 5px;
            padding: 5px;
            margin: 5px;
            transition: background-color 0.2s;
            width: 45%; /* Keep the buttons consistent in size */
        }

        .operator:hover, .paren:hover, .expr-element:hover, .quick-operation:hover {
            background-color: #ff9800;
        }

        #operation-buttons, #quick-buttons {
            display: flex;
            justify-content: space-between;
            margin-top: 20px;
        }

        #operation-buttons button, #quick-buttons button {
            width: 45%;
        }

        #expression-container {
            margin-top: 20px;
            display: flex;
            flex-wrap: wrap;
            background-color: #f1f8e9;
            padding: 10px;
            border-radius: 5px;
        }

        #result {
            margin-top: 20px;
            padding: 10px;
            background-color: #e0e0e0;
            border-radius: 5px;
            font-weight: bold;
        }

        /* New styles for condition variables section */
        .condition-container {
            margin-top: 20px;
            padding: 10px;
            background-color: #f1f8e9;
            border-radius: 5px;
        }

        .condition-variable {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }

        .condition-variable input {
            margin-left: 10px;
            padding: 5px;
            margin-right: 10px;
            flex-grow: 1;
        }

        .condition-variable button {
            background-color: #ffccbc;
            border-radius: 5px;
            padding: 5px;
            cursor: pointer;
        }

        .condition-variable button:hover {
            background-color: #ff9800;
        }
    </style>
</head>
<body>

<div class="container">

    <!-- Section 1: Variable Elements -->
    <div class="section">
        <h3>1) 选择变量:</h3>
        <div class="expr-container" id="variables">
            <div class="expr-element" data-value="c1">c1</div>
        </div>
    </div>

    <!-- Section 2: Operator Buttons -->
    <div class="section">
        <h3>2) 选择操作符:</h3>
        <div id="quick-buttons">
            <button class="quick-operation" data-op="all">满足全部</button>
            <button class="quick-operation" data-op="any">满足任一</button>
        </div>
        <div id="operation-buttons">
            <button class="operation" data-op="&&"></button>
            <button class="operation" data-op="||"></button>
        </div>
    </div>

    <!-- Section 3: Expression Preview -->
    <div class="section">
        <h3>3) 当前关系效果:</h3>
        <div id="expression-container" class="expr-container"></div>
    </div>

    <!-- Section 4: Final Expression Output -->
    <div class="section">
        <h3>4) 最终表达式:</h3>
        <div id="result"></div>
    </div>

    <!-- Section 5: Condition Variables -->
    <div class="section condition-container">
        <h3>5) 条件变量:</h3>
        <div id="condition-variables">
            <div class="condition-variable" data-index="1">
                <span>c1</span>
                <input type="text" placeholder="变量描述" class="description" />
                <button class="remove-condition" onclick="removeCondition(1)">删除条件</button>
            </div>
        </div>
        <button id="add-condition" onclick="addCondition()">新增条件</button>
    </div>

</div>

<script>
    let selectedElements = []; // Stores the current selected elements or expressions
    let currentExpression = ""; // Stores the current expression as a string
    let variableCount = 1; // Variable count starts at 1 (c1)

    const expressionContainer = document.getElementById('expression-container');
    const resultContainer = document.getElementById('result');
    const conditionVariablesContainer = document.getElementById('condition-variables');
    const variablesContainer = document.getElementById('variables');

    // Function to render the current expression
    function renderExpression() {
        expressionContainer.innerHTML = '';
        const elements = currentExpression.split(' ').map(el => {
            const div = document.createElement('div');
            div.classList.add('expr-element');
            div.textContent = el;
            return div;
        });
        elements.forEach(div => expressionContainer.appendChild(div));
        updateResult();
    }

    // Function to update the result container
    function updateResult() {
        resultContainer.textContent = currentExpression;
    }

    // Handle selection of variables (c1, c2, etc.)
    variablesContainer.addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('expr-element')) {
            const value = e.target.getAttribute('data-value');
            const element = e.target;

            // Toggle selected class
            if (element.classList.contains('selected')) {
                element.classList.remove('selected');
                const index = selectedElements.indexOf(value);
                if (index !== -1) {
                    selectedElements.splice(index, 1);
                }
            } else {
                element.classList.add('selected');
                selectedElements.push(value);
            }
            updateCurrentExpression();
            renderExpression();
        }
    });

    // Handle the operation buttons (&&, ||)
    document.getElementById('operation-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('operation')) {
            const operator = e.target.getAttribute('data-op');
            if (selectedElements.length >= 2) {
                // Add the operator between the last two elements
                const lastElement = selectedElements.pop();
                const secondLastElement = selectedElements.pop();
                selectedElements.push(`(${secondLastElement} ${operator} ${lastElement})`);
                updateCurrentExpression();
                renderExpression();
            }
        }
    });

    // Handle the quick operation buttons (满足全部, 满足任一)
    document.getElementById('quick-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('quick-operation')) {
            const quickOp = e.target.getAttribute('data-op');
            if (selectedElements.length >= 1) {
                if (quickOp === 'all') {
                    currentExpression = selectedElements.join(' && ');
                } else if (quickOp === 'any') {
                    currentExpression = selectedElements.join(' || ');
                }
                currentExpression = `(${currentExpression})`; // Wrap the result in parentheses
                renderExpression();
            }
        }
    });

    // Update the current expression string based on the selected elements
    function updateCurrentExpression() {
        currentExpression = selectedElements.join(' ');
    }

    // Add a new condition variable
    function addCondition() {
        if (variableCount < 10) {
            variableCount++;
            const newCondition = document.createElement('div');
            newCondition.classList.add('condition-variable');
            newCondition.setAttribute('data-index', variableCount);
            newCondition.innerHTML = `
                <span>c${variableCount}</span>
                <input type="text" placeholder="变量描述" class="description" />
                <button class="remove-condition" onclick="removeCondition(${variableCount})">删除条件</button>            `;
            conditionVariablesContainer.appendChild(newCondition);

            // Add the new variable to the selection list (variables container)
            const newVariable = document.createElement('div');
            newVariable.classList.add('expr-element');
            newVariable.setAttribute('data-value', `c${variableCount}`);
            newVariable.textContent = `c${variableCount}`;
            variablesContainer.appendChild(newVariable);
        } else {
            alert("最多只能有 10 个变量");
        }
    }

    // Remove a condition variable
    function removeCondition(index) {
        const conditionElement = document.querySelector(`.condition-variable[data-index="${index}"]`);
        conditionElement.remove();

        // Also remove the corresponding variable element from the selection list
        const variableElement = document.querySelector(`.expr-element[data-value="c${index}"]`);
        variableElement.remove();

        // Decrease variable count and prevent less than 1 condition variable
        if (variableCount > 1) {
            variableCount--;
        } else {
            alert("至少需要保留一个变量");
        }
    }

    // Initialize: Ensure at least one variable is present
    document.addEventListener('DOMContentLoaded', function () {
        if (variableCount === 1) {
            const conditionElement = document.createElement('div');
            conditionElement.classList.add('condition-variable');
            conditionElement.setAttribute('data-index', '1');
            conditionVariablesContainer.appendChild(conditionElement);
        }
    });

</script>

</body>
</html>

STEP6: 时光机

需求

到这里都非常棒。不过还是有一个点需要改进,我们在设置变量关系时,如果设置错了,就没有任何回旋的余地了。

你觉得是添加一个关系重置的按钮,清空效果+表达式比较好;还是添加一个类似于 ps 的那种上一步/下一步 的操作按钮比较好?

解答

确实在用户设置变量关系时,可能会不小心设错,导致无法轻松回到之前的状态。

对于这种情况,我觉得有两种解决方案可以考虑:

1. 添加关系重置按钮(清空效果和表达式)

这种方式直接、简单,用户可以在任何时候点击一个“重置”按钮,将当前的设置清空,恢复到初始状态。它不会干扰当前的流程,并且对用户来说是一个明确的操作,特别适用于用户希望彻底重置当前表达式的情况。

优点:

  • 实现简单,用户一目了然。
  • 如果用户希望从头开始设置,清空所有设置是非常直观的操作。
  • 没有复杂的状态管理,操作简便。

缺点:

  • 如果用户误点击重置,所有的工作都会丢失,恢复困难。
  • 对于较复杂的操作,可能会让用户感到有些不便。

2. 上一步/下一步按钮(类似 PS)

这种方式可以提供更多的灵活性,允许用户在设置过程中回到之前的状态,而无需重新开始。每次变更都会记录操作的历史,用户可以轻松撤销和重做。

优点:

  • 用户能够有更多的控制权,回到之前的状态,减少操作错误带来的困扰。
  • 比较适用于复杂的逻辑表达式构建,允许用户逐步调整。

缺点:

  • 实现较为复杂,需要管理状态历史。
  • 如果操作记录过多,可能导致内存和性能的开销,特别是在涉及大量状态变化时。
  • 用户需要理解“上一步”和“下一步”机制,可能需要一定的学习成本。

个人建议

考虑到我们的应用场景是构建逻辑表达式,且用户不一定会非常频繁地更改设置,同时提供“重置”按钮会更加简单和直观。

重置按钮能够清晰地将用户从当前设置恢复到初始状态,且操作更简单,不需要过多的逻辑和内存管理。

重置按钮实现

在现有的设计上,我们可以增加一个重置按钮,来清空当前选择和表达式。

<!-- Add a Reset button -->
<button id="reset-btn" onclick="resetExpression()">重置</button>

对应的 js 方法

    // Function to reset the expression and selections
    function resetExpression() {
        // Clear selected elements
        selectedElements = [];
        currentExpression = "";

        // Reset the appearance of the selected elements
        const selectedElems = document.querySelectorAll('.expr-element.selected');
        selectedElems.forEach(elem => {
            elem.classList.remove('selected');
        });

        // Clear the expression container and result
        expressionContainer.innerHTML = '';
        resultContainer.textContent = '';

        // Optionally, reset the condition variables back to c1 if needed
		// 这里不变,变化了反而麻烦。
        <!-- variableCount = 1; -->
        <!-- conditionVariablesContainer.innerHTML = ` -->
            <!-- <div class="condition-variable" data-index="1"> -->
                <!-- <span>c1</span> -->
                <!-- <input type="text" placeholder="变量描述" class="description" /> -->
                <!-- <button class="remove-condition" onclick="removeCondition(1)">删除条件</button> -->
            <!-- </div> -->
        <!-- `; -->
        <!-- variablesContainer.innerHTML = ` -->
            <!-- <div class="expr-element" data-value="c1">c1</div> -->
        <!-- `; -->
    }

STEP7: 样式调整

需求

当现在为止特别棒。

选择操作符这一栏目前有 5 个按钮,让我们做一下样式优化,

1)让五个按钮在一行。且符合操作习惯的远近关系。【满足全部】【满足任一】 【且】【或】 【重置】

2)颜色标注,给出合适的按钮颜色,比如【重置】一般为 warning 警告的黄色

3)只给出按钮相关的代码+css 样式改动即可

最后的效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Logical Expression Builder</title>
    <style>
        /* Basic styling for layout */
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            padding: 0;
            background-color: #f5f5f5;
        }

        .container {
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #ffffff;
            border-radius: 10px;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
        }

        .section {
            margin-bottom: 20px;
        }

        .expr-container {
            margin-bottom: 10px;
            display: flex;
            flex-wrap: wrap;
        }

        .expr-element {
            display: inline-block;
            margin: 5px;
            padding: 8px;
            background-color: #c8e6c9;
            cursor: pointer;
            border-radius: 5px;
            transition: background-color 0.2s;
        }

        .selected {
            background-color: #ffeb3b; /* Highlight selected element */
        }

        .operator, .paren, .quick-operation {
            background-color: #ffccbc;
            cursor: pointer;
            border-radius: 5px;
            padding: 5px;
            margin: 5px;
            transition: background-color 0.2s;
            width: 45%; /* Keep the buttons consistent in size */
        }

        .operator:hover, .paren:hover, .expr-element:hover, .quick-operation:hover {
            background-color: #ff9800;
        }

        #expression-container {
            margin-top: 20px;
            display: flex;
            flex-wrap: wrap;
            background-color: #f1f8e9;
            padding: 10px;
            border-radius: 5px;
        }

        #result {
            margin-top: 20px;
            padding: 10px;
            background-color: #e0e0e0;
            border-radius: 5px;
            font-weight: bold;
        }

        /* New styles for condition variables section */
        .condition-container {
            margin-top: 20px;
            padding: 10px;
            background-color: #f1f8e9;
            border-radius: 5px;
        }

        .condition-variable {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }

        .condition-variable input {
            margin-left: 10px;
            padding: 5px;
            margin-right: 10px;
            flex-grow: 1;
        }

        .condition-variable button {
            background-color: #ffccbc;
            border-radius: 5px;
            padding: 5px;
            cursor: pointer;
        }

        .condition-variable button:hover {
            background-color: #ff9800;
        }
		
		/* Container for quick operation buttons */
		#quick-buttons {
			display: flex;
			gap: 10px;
			margin-bottom: 10px;
		}

		/* Container for main operation buttons */
		#operation-buttons {
			display: flex;
			gap: 10px;
			margin-bottom: 10px;
		}

		/* Quick operation buttons styling (满足全部, 满足任一) */
		.quick-operation {
			padding: 8px 15px;
			font-size: 14px;
			cursor: pointer;
			border: 1px solid #ccc;
			border-radius: 4px;
			background-color: #f0f0f0;
			transition: background-color 0.3s;
		}

		.quick-operation:hover {
			background-color: #e0e0e0;
		}

		/* Main operation buttons styling (且, 或) */
		.operation {
			padding: 8px 15px;
			font-size: 14px;
			cursor: pointer;
			border: 1px solid #ccc;
			border-radius: 4px;
			background-color: #f0f0f0;
			transition: background-color 0.3s;
		}

		.operation:hover {
			background-color: #e0e0e0;
		}

		/* Reset button styling */
		#reset-btn {
			padding: 8px 15px;
			font-size: 14px;
			cursor: pointer;
			border: 1px solid #f1c40f;
			border-radius: 4px;
			background-color: #f39c12;
			color: white;
			transition: background-color 0.3s;
		}

		#reset-btn:hover {
			background-color: #e67e22;
		}

		/* Styling for the section header */
		.section h3 {
			font-size: 16px;
			margin-bottom: 10px;
		}

    </style>
</head>
<body>

<div class="container">

    <!-- Section 1: Variable Elements -->
    <div class="section">
        <h3>1) 选择变量:</h3>
        <div class="expr-container" id="variables">
            <div class="expr-element" data-value="c1">c1</div>
        </div>
    </div>

    <!-- Section 2: Operator Buttons -->
    <div class="section">
        <h3>2) 选择操作符:</h3>
        <div id="quick-buttons">
            <button class="quick-operation" data-op="all">满足全部</button>
            <button class="quick-operation" data-op="any">满足任一</button>
        </div>
        <div id="operation-buttons">
            <button class="operation" data-op="&&"></button>
            <button class="operation" data-op="||"></button>
        </div>
		
		<!-- Add a Reset button -->
		<button id="reset-btn" onclick="resetExpression()">重置</button>
    </div>

    <!-- Section 3: Expression Preview -->
    <div class="section">
        <h3>3) 当前关系效果:</h3>
        <div id="expression-container" class="expr-container"></div>
    </div>

    <!-- Section 4: Final Expression Output -->
    <div class="section">
        <h3>4) 最终表达式:</h3>
        <div id="result"></div>
    </div>

    <!-- Section 5: Condition Variables -->
    <div class="section condition-container">
        <h3>5) 条件变量:</h3>
        <div id="condition-variables">
            <div class="condition-variable" data-index="1">
                <span>c1</span>
                <input type="text" placeholder="变量描述" class="description" />
                <button class="remove-condition" onclick="removeCondition(1)">删除条件</button>
            </div>
        </div>
        <button id="add-condition" onclick="addCondition()">新增条件</button>
    </div>

</div>

<script>
    let selectedElements = []; // Stores the current selected elements or expressions
    let currentExpression = ""; // Stores the current expression as a string
    let variableCount = 1; // Variable count starts at 1 (c1)

    const expressionContainer = document.getElementById('expression-container');
    const resultContainer = document.getElementById('result');
    const conditionVariablesContainer = document.getElementById('condition-variables');
    const variablesContainer = document.getElementById('variables');

    // Function to render the current expression
    function renderExpression() {
        expressionContainer.innerHTML = '';
        const elements = currentExpression.split(' ').map(el => {
            const div = document.createElement('div');
            div.classList.add('expr-element');
            div.textContent = el;
            return div;
        });
        elements.forEach(div => expressionContainer.appendChild(div));
        updateResult();
    }

    // Function to update the result container
    function updateResult() {
        resultContainer.textContent = currentExpression;
    }

    // Handle selection of variables (c1, c2, etc.)
    variablesContainer.addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('expr-element')) {
            const value = e.target.getAttribute('data-value');
            const element = e.target;

            // Toggle selected class
            if (element.classList.contains('selected')) {
                element.classList.remove('selected');
                const index = selectedElements.indexOf(value);
                if (index !== -1) {
                    selectedElements.splice(index, 1);
                }
            } else {
                element.classList.add('selected');
                selectedElements.push(value);
            }
            updateCurrentExpression();
            renderExpression();
        }
    });

    // Handle the operation buttons (&&, ||)
    document.getElementById('operation-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('operation')) {
            const operator = e.target.getAttribute('data-op');
            if (selectedElements.length >= 2) {
                // Add the operator between the last two elements
                const lastElement = selectedElements.pop();
                const secondLastElement = selectedElements.pop();
                selectedElements.push(`(${secondLastElement} ${operator} ${lastElement})`);
                updateCurrentExpression();
                renderExpression();
            }
        }
    });

    // Handle the quick operation buttons (满足全部, 满足任一)
    document.getElementById('quick-buttons').addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('quick-operation')) {
            const quickOp = e.target.getAttribute('data-op');
            if (selectedElements.length >= 1) {
                if (quickOp === 'all') {
                    currentExpression = selectedElements.join(' && ');
                } else if (quickOp === 'any') {
                    currentExpression = selectedElements.join(' || ');
                }
                currentExpression = `(${currentExpression})`; // Wrap the result in parentheses
                renderExpression();
            }
        }
    });

    // Update the current expression string based on the selected elements
    function updateCurrentExpression() {
        currentExpression = selectedElements.join(' ');
    }

    // Add a new condition variable
    function addCondition() {
        if (variableCount < 10) {
            variableCount++;
            const newCondition = document.createElement('div');
            newCondition.classList.add('condition-variable');
            newCondition.setAttribute('data-index', variableCount);
            newCondition.innerHTML = `
                <span>c${variableCount}</span>
                <input type="text" placeholder="变量描述" class="description" />
                <button class="remove-condition" onclick="removeCondition(${variableCount})">删除条件</button>            `;
            conditionVariablesContainer.appendChild(newCondition);

            // Add the new variable to the selection list (variables container)
            const newVariable = document.createElement('div');
            newVariable.classList.add('expr-element');
            newVariable.setAttribute('data-value', `c${variableCount}`);
            newVariable.textContent = `c${variableCount}`;
            variablesContainer.appendChild(newVariable);
        } else {
            alert("最多只能有 10 个变量");
        }
    }

    // Remove a condition variable
    function removeCondition(index) {
        const conditionElement = document.querySelector(`.condition-variable[data-index="${index}"]`);
        conditionElement.remove();

        // Also remove the corresponding variable element from the selection list
        const variableElement = document.querySelector(`.expr-element[data-value="c${index}"]`);
        variableElement.remove();

        // Decrease variable count and prevent less than 1 condition variable
        if (variableCount > 1) {
            variableCount--;
        } else {
            alert("至少需要保留一个变量");
        }
    }

    // Initialize: Ensure at least one variable is present
    document.addEventListener('DOMContentLoaded', function () {
        if (variableCount === 1) {
            const conditionElement = document.createElement('div');
            conditionElement.classList.add('condition-variable');
            conditionElement.setAttribute('data-index', '1');
            conditionVariablesContainer.appendChild(conditionElement);
        }
    });


	// Function to reset the expression and selections
    function resetExpression() {
        // Clear selected elements
        selectedElements = [];
        currentExpression = "";

        // Reset the appearance of the selected elements
        const selectedElems = document.querySelectorAll('.expr-element.selected');
        selectedElems.forEach(elem => {
            elem.classList.remove('selected');
        });

        // Clear the expression container and result
        expressionContainer.innerHTML = '';
        resultContainer.textContent = '';

        // Optionally, reset the condition variables back to c1 if needed
		// 这里不变,变化了反而麻烦。
        <!-- variableCount = 1; -->
        <!-- conditionVariablesContainer.innerHTML = ` -->
            <!-- <div class="condition-variable" data-index="1"> -->
                <!-- <span>c1</span> -->
                <!-- <input type="text" placeholder="变量描述" class="description" /> -->
                <!-- <button class="remove-condition" onclick="removeCondition(1)">删除条件</button> -->
            <!-- </div> -->
        <!-- `; -->
        <!-- variablesContainer.innerHTML = ` -->
            <!-- <div class="expr-element" data-value="c1">c1</div> -->
        <!-- `; -->
    }
</script>

</body>
</html>

参考资料

https://www.yuque.com/boyan-avfmj/aviatorscript/guhmrc