HomeLập trìnhJavaScriptCách chuyển đổi...

Cách chuyển đổi bảng HTML tĩnh thành lưới dữ liệu JavaScript động


Các bảng HTML rất đơn giản để sử dụng để hiển thị một lượng nhỏ dữ liệu. Nhưng người dùng có thể khó làm việc với chúng khi chúng hiển thị nhiều dữ liệu.

Các tính năng như sắp xếp, lọc và phân trang giúp làm việc với nhiều hàng dữ liệu dễ dàng hơn. Chúng tôi có thể dễ dàng triển khai các tính năng đó bằng cách di chuyển từ Bảng HTML sang thành phần Lưới dữ liệu JavaScript.

Trong bài đăng này, chúng tôi sẽ sử dụng phiên bản cộng đồng miễn phí của Lưới dữ liệu JavaScript AG Grid để chuyển đổi từ một bảng HTML tĩnh dài sang một Lưới dữ liệu tương tác dễ sử dụng. Lượng JavaScript chúng ta cần để làm điều này là tối thiểu và rất đơn giản.

Chúng tôi sẽ xây dựng mã ví dụ theo ba bước:

  • Hiển thị danh sách tĩnh dữ liệu Mục Todo trong Bảng HTML.
  • Tải danh sách các mục Todo từ API REST và hiển thị trong bảng.
  • Chuyển đổi Bảng HTML thành Lưới dữ liệu để cho phép sắp xếp, lọc và phân trang.

Cách kết xuất dữ liệu bằng bảng HTML

Phiên bản đầu tiên của ứng dụng của chúng tôi sẽ cho phép chúng tôi tạo cấu trúc trang cơ bản và đảm bảo rằng chúng tôi đang hiển thị đúng dữ liệu cho người dùng.

tôi tạo ra một đơn giản index.html tập tin như hình dưới đây:

<!DOCTYPE html>
<html>

<head>
    <title>Table Example</title>
</head>

<body>

    <style>
        table {
            border-collapse: collapse;
            width: 100%;
        }

        td,
        th {
            border: 1px solid #000000;
            text-align: left;
            padding: 8px;
        }
    </style>

    <h1>TODO List</h1>

    <div id="data-table">
        <table id="html-data-table">
            <tr>
                <th>userId</th>
                <th>id</th>
                <th>title</th>
                <th>completed</th>
            </tr>
            <tr>
                <td>1</td>
                <td>1</td>
                <td>My todo 1</td>
                <td>false</td>
            </tr>
        </table>    
    </div>

</body>

</html>

Thao tác này sẽ hiển thị một Mục Công việc duy nhất trong một bảng.

Mục Todo duy nhất được hiển thị trong bảng HTML
Mục Todo duy nhất được hiển thị trong bảng HTML

Dưới đây là ví dụ Trang bảng HTML tĩnh.

Các table được tạo kiểu để có chiều rộng 100% của trang bằng cách sử dụng width:100% và các đường viền giữa các ô trong bảng đã được tạo kiểu để hiển thị dưới dạng một dòng với border-collapse: collapse.

không có border-collapse giá trị bảng sẽ trông giống như hình dưới đây:

Bảng được tạo kiểu mà không thu gọn đường viền
Bảng được tạo kiểu mà không thu gọn đường viền

Lợi ích của các bảng HTML ngắn

Bảng HTML là một cách rất nhanh để hiển thị một lượng nhỏ dữ liệu ở dạng bảng trên một trang.

Các bảng yêu cầu kiểu dáng vì kiểu dáng mặc định của một table khác nhau giữa các trình duyệt và thường được hiển thị không có đường viền khiến dữ liệu khó đọc.

Hiện tại, danh sách các Mục cần làm của chúng tôi được mã hóa tĩnh vào trang. Đối với bước tiếp theo, chúng tôi sẽ fetch danh sách từ API REST bằng JavaScript.

Cách đọc JSON từ API để kết xuất trong bảng HTML

Vì chúng ta sẽ tải dữ liệu từ một API nên tôi sẽ không mã hóa cứng bất kỳ dữ liệu nào trong bảng. Để hỗ trợ tải động, tôi chỉ cần xóa dòng dữ liệu khỏi table bởi vì tôi sẽ tạo các hàng dữ liệu bằng JavaScript:

    <div id="data-table">
        <table id="html-data-table">
            <tr>
                <th>userId</th>
                <th>id</th>
                <th>title</th>
                <th>completed</th>
            </tr>
        </table>    
    </div>

Tôi sẽ thêm JavaScript vào index.html trang ngay trước khi kết thúc body nhãn.

    <script type="text/javascript" charset="utf-8">
    </script>
</body>

Để bắt đầu, tôi sẽ viết mã đọc dữ liệu.

Tôi sẽ sử dụng ứng dụng API REST “{JSON} Placeholder” cho phần trình diễn này. Bằng cách làm một GET yêu cầu trên URL https://jsonplaceholder.typicode.com/todos, chúng tôi sẽ nhận được phản hồi JSON là danh sách các Mục công việc.

Bạn có thể tự mình dùng thử mà không cần JavaScript bằng cách nhấp vào liên kết ở trên.

Cách dễ nhất để thực hiện một GET yêu cầu trên API là bằng cách sử dụng fetch chức năng được tích hợp trong JavaScript.

    <script type="text/javascript" charset="utf-8">

        fetch('https://jsonplaceholder.typicode.com/todos')
            .then(function (response) {
                return response.json();
            }).then(function (apiJsonData) {
                console.log(apiJsonData);
            })

    </script>
</body>

Để giải thích đoạn mã trên, tôi sẽ mô tả nó trong các phần bên dưới:

  • Đưa ra yêu cầu GET tới https://jsonplaceholder.typicode.com/todos
fetch('https://jsonplaceholder.typicode.com/todos')
  • Sau đó, khi yêu cầu kết thúc, hãy chuyển đổi phản hồi thành Đối tượng JavaScript – trong trường hợp của chúng tôi, đây sẽ là một mảng chứa tất cả các Mục Công việc.
.then(function (response) {
	return response.json();
})
  • Sau đó viết đối tượng JavaScript vào bàn điều khiển
.then(function (apiJsonData) {
	console.log(apiJsonData);
})

Với mã này trong ứng dụng của chúng tôi, chúng tôi sẽ không thấy bất kỳ thứ gì trong bảng, nhưng chúng tôi sẽ thấy mảng được hiển thị trong Bảng điều khiển công cụ dành cho nhà phát triển trình duyệt nơi chúng tôi có thể xem dữ liệu.

Đọc thêm  Mối nối JavaScript – Cách sử dụng Phương thức mảng .splice() JS
Dữ liệu hiển thị khi sử dụng console.log
Dữ liệu hiển thị khi sử dụng console.log

Lệnh gọi API trả về 200 mục và mỗi mục là một Đối tượng Todo:

  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  }

Bước tiếp theo của chúng tôi là hiển thị dữ liệu trong bảng:

    <script type="text/javascript" charset="utf-8">

        fetch('https://jsonplaceholder.typicode.com/todos')
            .then(function (response) {
                return response.json();
            }).then(function (apiJsonData) {
                console.log(apiJsonData);
                renderDataInTheTable(apiJsonData);
            })

        function renderDataInTheTable(todos) {
            const mytable = document.getElementById("html-data-table");
            todos.forEach(todo => {
                let newRow = document.createElement("tr");
                Object.values(todo).forEach((value) => {
                    let cell = document.createElement("td");
                    cell.innerText = value;
                    newRow.appendChild(cell);
                })
                mytable.appendChild(newRow);
            });
        }
    </script>
</body>

Các renderDataInTheTable chức năng tìm bảng trong DOM để chúng tôi có thể nối các hàng mới vào bảng đó, sau đó lặp lại tất cả các Mục công việc được trả về từ lệnh gọi API.

Đối với mỗi Mục Todo, mã sẽ tạo một mục mới tr phần tử, sau đó thêm từng giá trị trong Mục việc cần làm vào bảng dưới dạng td thành phần.

let newRow = document.createElement("tr");
Object.values(todo).forEach((value) => {
    let cell = document.createElement("td");
    cell.innerText = value;
    newRow.appendChild(cell);
})

Khi mà fetchrenderDataInTheTable mã được thêm vào ứng dụng của chúng tôi và tải trang, chúng tôi sẽ thấy rằng bảng HTML hiện có tất cả 200 Mục Công việc được hiển thị trong bảng.

Danh sách việc cần làm được tải động
Danh sách việc cần làm được tải động

Dưới đây là ví dụ Trang bảng HTML động.

Lợi ích và Nhược điểm của Bảng HTML dài

Bảng HTML là một cách dễ dàng để hiển thị dữ liệu trên một trang nhưng không khả dụng lắm đối với các danh sách dữ liệu dài.

Các mục dữ liệu có thể khó tìm, mặc dù người dùng có thể tìm kiếm dữ liệu bằng cách sử dụng chức năng ‘tìm trong trang’ tích hợp sẵn của trình duyệt.

Bằng cách hiển thị trong bảng HTML, người dùng của chúng tôi không có cách nào để sắp xếp dữ liệu hoặc lọc dữ liệu để chỉ hiển thị các Mục công việc đã hoàn thành. Chúng tôi sẽ phải thêm mã bổ sung vào ứng dụng của mình để triển khai chức năng sắp xếp và lọc.

Bảng HTML sẽ tự động phát triển khi có nhiều hàng hơn được thêm vào bảng. Điều này có thể làm cho chúng khó sử dụng hơn trong một ứng dụng khi nhiều dữ liệu đã được thêm vào.

Khi chúng tôi thêm nhiều dữ liệu, chúng tôi có thể muốn phân trang để hạn chế bảng dữ liệu chỉ hiển thị một số hàng nhất định và cho phép người dùng nhấp qua trang tiếp theo để xem thêm các mục. Một lần nữa, đây là chức năng mà chúng ta sẽ phải viết thêm mã để xử lý.

Khi ứng dụng của chúng ta đạt đến điểm cần tương tác với người dùng nhiều hơn, chúng ta nên cân nhắc sử dụng thành phần Lưới dữ liệu.

Chúng ta có thể sử dụng nó để thêm chức năng bổ sung như:

  • phân loại
  • lọc
  • thay đổi kích thước cột
  • phân trang

Thư viện và thành phần lưới dữ liệu

Có sẵn nhiều Thành phần lưới dữ liệu miễn phí, nhưng hầu hết chúng đều dành riêng cho khung nên chúng yêu cầu viết mã bằng React, Angular hoặc Vue.

Tôi đang sử dụng AG Grid cho ví dụ này vì phiên bản miễn phí có thể được sử dụng với JavaScript, TypeScript, React, Angular hoặc Vue. “AG” là viết tắt của Agnostic, có nghĩa là có thể được sử dụng với bất kỳ khuôn khổ nào.

Khi bạn học cách sử dụng AG Grid trong một khung, API tương tự sẽ có sẵn cho các khung khác, giúp kiến ​​thức của bạn có thể chuyển giao cho các dự án khác.

Phiên bản miễn phí của AG Grid có thể được sử dụng trong các ứng dụng thương mại, vì vậy nếu bạn quản lý để mở rộng ứng dụng demo được hiển thị ở đây thành Ứng dụng quản lý danh sách Todo thương mại, bạn vẫn có thể sử dụng AG Grid miễn phí. Nhiều ứng dụng thương mại đã được xây dựng bằng phiên bản miễn phí của AG Grid.

Ngoài ra, Lưới AG thường được tìm kiếm như một kỹ năng trong các đơn xin việc, vì vậy rất đáng để thử nghiệm.

Phiên bản thương mại của AG Grid có các tính năng bổ sung như Xuất Excel và tạo Biểu đồ, nhưng chúng tôi không cần bất kỳ chức năng nào trong bản trình diễn này.

Sử dụng Lưới dữ liệu có nghĩa là chúng ta định cấu hình Lưới dữ liệu, cung cấp dữ liệu để kết xuất và Lưới xử lý tất cả các chức năng khác như sắp xếp, lọc và phân trang.

Đọc thêm  JavaScript split() a String – Phương thức JS String to Array

Chúng tôi có thể chuyển đổi mã hiện tại của mình sang sử dụng AG Grid chỉ với một vài thay đổi.

Cách thêm JavaScript và CSS lưới AG

AG Grid là một thư viện nên chúng tôi sẽ bao gồm JavaScript cần thiết.

Nếu bạn đang sử dụng các công cụ xây dựng như npmsau đó khác nhau npm install các lệnh được liệt kê trong tài liệu Bắt đầu với Lưới AG.

Chúng tôi đang sử dụng JavaScript đơn giản, vì vậy chúng tôi có thể bao gồm script trong chúng tôi head tiết diện.

<head>
    <title>Data Grid Example</title>
    <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.noStyle.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/dist/styles/ag-grid.css">
    <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/dist/styles/ag-theme-balham.css">
</head>

Điều này bao gồm phiên bản cộng đồng của Lưới AG và CSS cần thiết để hiển thị Lưới đúng cách.

Của chúng ta data-table div không còn cần phải có bất kỳ table thành phần:

    <div id="data-table" class="ag-theme-balham">
    </div>

Lưới AG sẽ tạo HTML cho Lưới dữ liệu khi chúng tôi thiết lập. Chúng tôi thêm class để sử dụng chủ đề Lưới AG. Trong ví dụ này, chúng tôi đang sử dụng chủ đề ag-theme-balham.

Lưới AG yêu cầu đặt chiều rộng và chiều cao cho div. Tôi đã chọn thêm cái này làm style phần trong mã:

    <style>
        #data-table {
            height: 500px;
            width: 100%;
        }
    </style>

Lưới sẽ được hiển thị cao 500 pixel và lấp đầy 100% chiều rộng của màn hình. Điều này sao chép kiểu dáng cơ bản mà chúng ta đã có với bảng HTML. Nhưng nó cũng cho thấy một trong những lợi ích của việc sử dụng Lưới dữ liệu. Kích thước của bảng được hiển thị có thể được kiểm soát dễ dàng và các thanh cuộn sẽ được tự động thêm vào khi cần thiết bởi chính Lưới.

Cách định cấu hình lưới AG và kết xuất dữ liệu

Các script phần mã thay đổi vì chúng tôi cần:

  • Định cấu hình lưới dữ liệu.
  • Tạo lưới dữ liệu mới bằng cách sử dụng cấu hình.
  • Lấy dữ liệu và thêm nó vào lưới.

Tôi sẽ hiển thị sửa đổi ban đầu script phần dưới đây và sau đó giải thích nó trong các đoạn sau.

    <script type="text/javascript" charset="utf-8">

        const columnDefs = [
            { field: 'userId' },
            { field: 'id' },
            { field: 'title' },
            { field: 'completed' },
        ];

        const gridOptions = {
            columnDefs: columnDefs,
            onGridReady: (event) =>{renderDataInTheTable(event.api)}
        };

        const eGridDiv = document.getElementById('data-table');
        new agGrid.Grid(eGridDiv, gridOptions);

        function renderDataInTheTable(api) {
            fetch('https://jsonplaceholder.typicode.com/todos')
                .then(function (response) {
                    return response.json();
                }).then(function (data) {
                    api.setRowData(data);
                    api.sizeColumnsToFit();
                })
        }
    </script>

Lưới dữ liệu được điều khiển bởi dữ liệu và cấu hình – chúng ta không phải viết nhiều mã để tạo Lưới dữ liệu chức năng.

Đầu tiên, chúng ta tạo một mảng các Đối tượng Cột để xác định các cột trong Lưới Dữ liệu. Các cột này ánh xạ vào dữ liệu.

Dữ liệu mà chúng tôi nhận được từ lệnh gọi API có bốn thuộc tính: “userId”, “id”, “title” và “completed”:

  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  }

Để hiển thị những thứ này trong Lưới dữ liệu dưới dạng cột, chúng tôi tạo một Đối tượng có field trong đó giá trị là tên của thuộc tính trong Đối tượng dữ liệu.

        const columnDefs = [
            { field: 'userId' },
            { field: 'id' },
            { field: 'title' },
            { field: 'completed' },
        ];

Tiếp theo chúng ta tạo gridOptions vật. Thao tác này sẽ định cấu hình Lưới dữ liệu:

        const gridOptions = {
            columnDefs: columnDefs,
            onGridReady: (event) =>{renderDataInTheTable(event.api)}
        };

Các columnDefs thuộc tính được gán cho mảng các Đối tượng Cột mà chúng ta đã xác định trước đó.

Các onGridReady thuộc tính được gán một chức năng sẽ gọi renderDataInTheTable hoạt động khi lưới đã được tạo và hiển thị trong DOM (nghĩa là khi lưới đã sẵn sàng).

Để thêm lưới vào trang, chúng tôi tìm thấy div phần tử sẽ chứa lưới, sau đó khởi tạo một đối tượng Lưới AG mới cho phần tử đó và với các tùy chọn mà chúng tôi đã định cấu hình:

        const eGridDiv = document.getElementById('data-table');
        new agGrid.Grid(eGridDiv, gridOptions);

Chức năng tìm nạp dữ liệu và hiển thị dữ liệu trong lưới cũng giống như vậy fetch mã mà chúng tôi đã sử dụng cho bảng HTML động. Sự khác biệt là renderDataInTheTable chức năng nhận một đối tượng AG Grid Api làm tham số, cho phép chúng tôi gọi chức năng AG Grid để đặt dữ liệu hàng và kích thước các cột cho vừa với lưới:

        function renderDataInTheTable(api) {
            fetch('https://jsonplaceholder.typicode.com/todos')
                .then(function (response) {
                    return response.json();
                }).then(function (data) {
                    api.setRowData(data);
                    api.sizeColumnsToFit();
                })
        }

Khi mã này chạy, về cơ bản chúng ta sẽ sao chép chức năng tương tự của bảng HTML động, nhưng bây giờ tất cả dữ liệu được hiển thị trong Lưới dữ liệu có thanh cuộn.

lưới dữ liệu ban đầu

Để nhận được lợi ích của việc sử dụng Lưới dữ liệu và cho phép người dùng sắp xếp, lọc và điều hướng qua dữ liệu, chúng tôi chỉ phải sửa đổi cấu hình.

Đọc thêm  Bản đồ JavaScript - JS.map() 함수 사용 방법 (배열 메소드)

Cách thực hiện sắp xếp, lọc và phân trang

Đây là những gì chúng tôi đã định cấu hình trong Lưới dữ liệu cho đến nay:

  • trường nào từ dữ liệu để hiển thị
  • sử dụng dữ liệu nào

Để thêm sắp xếp, lọc, cột có thể thay đổi kích thước và phân trang, chúng tôi sửa đổi gridOptions cấu hình:

        const gridOptions = {

            defaultColDef: {
                sortable: true,
                filter: 'agTextColumnFilter',
                resizable: true
            },

            pagination: true,

            columnDefs: columnDefs,
            onGridReady: (event) =>{renderDataInTheTable(event.api)}
        };

Chúng tôi có thể định cấu hình các cột trong Lưới AG riêng lẻ bằng cách thêm các thuộc tính bổ sung vào columnDefs các đối tượng. Hoặc nếu chức năng tương tự được yêu cầu theo mặc định trong tất cả các cột, chúng tôi có thể định cấu hình defaultColDef.

Ở đây chúng tôi định cấu hình nó để có thể sắp xếp, có thể lọc và thay đổi kích thước:

            defaultColDef: {
                sortable: true,
                filter: 'agTextColumnFilter',
                resizable: true
            },

Bộ lọc mặc định mà chúng tôi đã xác định cho tất cả các cột là bộ lọc văn bản.

Để thêm phân trang tự động vào lưới, chúng tôi thêm pagination: true property và AG Grid sẽ tự động phân trang dữ liệu cho chúng tôi.

Lưới dữ liệu với sắp xếp, lọc và phân trang
Lưới dữ liệu với sắp xếp, lọc và phân trang

Lưới dữ liệu thân thiện với người dùng

Với đoạn mã trên, chúng tôi đã tạo một Lưới dữ liệu thân thiện với người dùng để tự động tìm nạp dữ liệu và thêm nó vào một Lưới dữ liệu hỗ trợ sắp xếp, lọc và phân trang.

Đây là ví dụ Trang HTML lưới dữ liệu:

<!DOCTYPE html>
<html>

<head>
    <title>Data Grid Example</title>
    <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.noStyle.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/dist/styles/ag-grid.css">
    <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/dist/styles/ag-theme-balham.css">
</head>

<body>
    <style>
        #data-table {
            height: 500px;
            width: 100%;
        }
    </style>

    <h1>TODO List</h1>

    <div id="data-table" class="ag-theme-balham">
    </div>

    <script type="text/javascript" charset="utf-8">

        const columnDefs = [
            { field: 'userId' },
            { field: 'id' },
            { field: 'title' },
            { field: 'completed' },
        ];

        const gridOptions = {

            defaultColDef: {
                sortable: true,
                filter: 'agTextColumnFilter',
                resizable: true
            },

            pagination: true,
            
            columnDefs: columnDefs,
            onGridReady: (event) =>{renderDataInTheTable(event.api)}
        };

        const eGridDiv = document.getElementById('data-table');

        new agGrid.Grid(eGridDiv, gridOptions);

        function renderDataInTheTable(api) {
            fetch('https://jsonplaceholder.typicode.com/todos')
                .then(function (response) {
                    return response.json();
                }).then(function (data) {
                    api.setRowData(data);
                    api.sizeColumnsToFit();
                })
        }
    </script>
</body>
</html>

Bộ lọc số

Kể từ khi userIdid cột là số, chúng tôi có thể tạo sau đó sử dụng bộ lọc số bằng cách sửa đổi columnDefs:

        const columnDefs = [
            { field: 'userId', filter: 'agNumberColumnFilter'},
            { field: 'id', filter: 'agNumberColumnFilter'},
            { field: 'title' },
            { field: 'completed' },
        ];

Dưới đây là ví dụ Trang HTML Bộ lọc Số lưới Dữ liệu.

Có rất nhiều tùy chọn cấu hình cho các cột được liệt kê trong Tài liệu lưới AG, chẳng hạn như cấu hình chiều rộng, kiểu dáng và làm cho các ô có thể chỉnh sửa được.

Lợi ích của lưới dữ liệu

Đối với nhiều trang web, một bảng HTML đơn giản sẽ là một cách hoàn toàn hợp lý để hiển thị dữ liệu dạng bảng. Nó nhanh và dễ hiểu, và với một chút CSS, bảng có thể trông đẹp mắt cho người dùng của bạn.

Khi các trang của bạn trở nên phức tạp hơn, hiển thị nhiều dữ liệu hơn hoặc yêu cầu nhiều tương tác hơn cho người dùng, thì việc sử dụng thành phần hoặc thư viện Lưới dữ liệu sẽ bắt đầu có ý nghĩa hơn.

Lưới dữ liệu cung cấp nhiều chức năng mà người dùng của bạn cần mà không phải viết nhiều mã. Trong ví dụ được trình bày trong bài đăng này, chúng tôi đã chuyển từ bảng động đọc dữ liệu từ API sang Lưới dữ liệu đọc từ API có sắp xếp, lọc, phân trang và thay đổi kích thước cột.

Đây là rất nhiều chức năng bổ sung, nhưng mã HTML của chúng tôi có cùng độ dài và JavaScript mà chúng tôi đã thêm ít phức tạp hơn vì Lưới dữ liệu đã thực hiện tất cả công việc hiển thị dữ liệu.

Lưới dữ liệu có thể xử lý hàng trăm nghìn hàng và cập nhật nhanh chóng nên chúng thường được sử dụng trong các hệ thống giao dịch tài chính thời gian thực với giá trong các ô cập nhật cứ sau vài mili giây.

Nếu bạn đang sử dụng React, thì ngoài AG Grid, bạn có thể xem Material UI hoặc React Table. Bảng phản ứng là một ‘bảng’ chứ không phải là Lưới dữ liệu nên ban đầu nó yêu cầu thêm một chút mã để hiển thị bảng.

Cả UI UI và React Table đều chỉ khả dụng cho React. AG Grid là khuôn khổ bất khả tri và sẽ hoạt động với JavaScript, TypeScript, React, Angular và Vue.

Bạn có thể tìm thấy mã nguồn của bài đăng này trong repo Github này trong thư mục docs/html-table-to-data-grid.



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status