Extreme Programming

832201128熊致学 2024-11-25 00:08:22
Course for This Assignment2401_MU_SE_EE308
Student ID and Name832201128_Xiong ZhiXue & 832201121_Gao Yu
Assignment RequirementsDevelop a web-based contact management system with bookmarking, multiple contact methods, and import/export features.
Objectives of This AssignmentPractice modular design and collaborative software engineering techniques and achieve functional and collaborative milestones

目录

  

  • 1.Project address
  • 2.Commit logs from Github
  • 3.Description of functional implementation ideas
  •   3.1 User interface
  •   3.2 Bookmark contacts
  •   3.3 Add multiple contact methods
  •   3.4 Import and Export Contacts
  •   3.5 Database
  • 4.Division of labor among team members
  • 5.Difficulties encountered in cooperation and solutions
  • 6.PSP Table

1.Project address

https://github.com/xzx904/Contact-book.git

2.Commit logs from Github

img

3.Description of functional implementation ideas

3.1 User interface

This image shows an intuitive interface to an electronic address book, clearly listing the details of many contacts. Each contact is equipped with their name, phone number, email address, and a series of useful action buttons such as "Delete," "Edit," and "Top." This information is carefully organized in a tabular format, allowing users to view and manage their contacts at a glance.
In addition, the address book interface also provides the "Export Excel" function, which allows users to export contact information to an Excel table with one click for further data processing or backup. In one corner of the interface, there is also an option to "select File" to import an Excel table with contact information into the address book.

img

3.2 Bookmark contacts

Description:

This GIF demonstrates the Bookmark Contacts feature of the Contact book application. Users can move a specific contact to the top of the list by clicking the "top" button, and the font of all the information of the top contact will be bolded and blackened, making it easy to access important contacts. In addition, the app also supports the "untop" function, allowing users to untop at any time.

Implementation :

  1. Front-end integration: A "Top" button appears next to each contact in the list to toggle the top status. The bold and black font of the contact information indicates whether the contact is in the top state.
  2. Event handling: The user clicks the "Top" button to trigger the top function. This function identifies the contact by its unique ID and updates the status of the contac t at the top of the local contact number group.
  3. Status Update: Updates the top status of a contact in the local contact list. If the contact does not have a top, it is marked as the top, and a topOrder timestamp is assigned to determine the order between the top contacts. If the status of the contact is already top, cancel the top status and clear topOrder.
  4. Data synchronization: After the local status update, the renderContacts function is called to rerender the contact list. This function sorts contacts according to their highest state and order, and then updates the DOM to reflect the current state of the contact.

img

Code Reference:

// 渲染联系人列表
    function renderContacts() {
        // 将联系人按置顶顺序排序
        contacts.sort((a, b) => {
            if (a.isTop && b.isTop) {
                return a.topOrder - b.topOrder; // 如果都置顶,按置顶顺序排列
            }
            return b.isTop - a.isTop; // 置顶的联系人排在最前面
        });

        contactTable.innerHTML = "";
        contacts.forEach((contact) => {
            const row = document.createElement("tr");
            const rowClass = contact.isTop ? "highlight" : ""; // 如果置顶,添加加粗样式

            const phoneList = contact.phone
                .map((p, index) => `${p} <button class="delete-phone" data-id="${contact.id}" data-phone-index="${index}">删除</button>`)
                .join("<br>");

            const emailList = contact.email.join("<br>");

            row.innerHTML = `
                <td class="${rowClass}">${contact.name}</td>
                <td class="${rowClass}">${phoneList || "无"}</td>
                <td class="${rowClass}">${emailList || "无"}</td>
                <td>
                    <button class="edit" data-id="${contact.id}">编辑</button>
                    <button class="delete" data-id="${contact.id}">删除</button>
                    <button class="top ${contact.isTop ? 'top-button' : ''}" data-id="${contact.id}">
                        ${contact.isTop ? '取消置顶' : '置顶'}
                    </button>
                </td>
            `;
            contactTable.appendChild(row);
        });
    }

    // 置顶按钮点击事件
    contactTable.addEventListener("click", (e) => {
        if (e.target.classList.contains("top")) {
            const contactId = e.target.dataset.id;
            const contact = contacts.find((c) => c.id === contactId);
            if (contact) {
                if (!contact.isTop) {
                    // 置顶
                    contact.isTop = true;
                    contact.topOrder = Date.now(); // 用时间戳记录置顶顺序
                } else {
                    // 取消置顶
                    contact.isTop = false;
                    contact.topOrder = null; // 取消置顶时清除顺序
                }
                renderContacts(); // 重新渲染
            }
        }

        if (e.target.classList.contains("edit")) {
            const contactId = e.target.dataset.id;
            const contact = contacts.find((c) => c.id === contactId);
            if (contact) {
                document.getElementById("contactId").value = contact.id;
                document.getElementById("name").value = contact.name;
                document.getElementById("phone").value = contact.phone.join(", ");
                document.getElementById("email").value = contact.email.join(", ");
            }
        }

        if (e.target.classList.contains("delete")) {
            const contactId = e.target.dataset.id;
            contacts = contacts.filter((c) => c.id !== contactId);
            renderContacts();
        }

        if (e.target.classList.contains("delete-phone")) {
            const contactId = e.target.dataset.id;
            const phoneIndex = e.target.dataset.phoneIndex;
            const contact = contacts.find((c) => c.id === contactId);
            if (contact) {
                contact.phone.splice(phoneIndex, 1);
                renderContacts();
            }
        }
    });

3.3 Add multiple contact methods

Description:

This GIF shows the ability to add additional information about a contact in your address book. Users can re-edit the contact information by clicking the "Edit" button, then change the phone or email, and then click the "Add/Modify" button, you can reflect a variety of phones and email in the contact information, and equipped with a "delete" button for deleting redundant phones.

Implementation:

  1. Custom Input:
    1. Users can edit the same contact, enter a different phone and email, click the "Add" button.
    2. These fields are dynamically added to the contact form.
  2. Data Handling:
    1. The additional fields are collected and stored in an additionalInfo object within the contact object.
    2. When the form is submitted, the additionalInfo object is serialized into a JSON string.
  3. Front-End Storage:
    The JSON string representing the additional information is stored in the contacts array along with the rest of the contact details.
  4. Dynamic Rendering:
    1. When rendering the contact list, the renderContacts function checks for the presence of additionalInfo.
    2. If present, the additional information is parsed from JSON and displayed in the contact’s row.

img

Code Reference:

// 添加或修改联系人
    contactForm.addEventListener("submit", (e) => {
        e.preventDefault();
        const id = document.getElementById("contactId").value; // 隐藏字段
        const name = document.getElementById("name").value.trim();
        const phone = document.getElementById("phone").value.trim();
        const email = document.getElementById("email").value.trim();

        if (id) {
            // 编辑联系人
            const contact = contacts.find((c) => c.id === id);
            if (contact) {
                contact.name = name;
                if (phone && !contact.phone.includes(phone)) {
                    contact.phone.push(phone);
                }
                if (email && !contact.email.includes(email)) {
                    contact.email.push(email);
                }
            }
        } else {
            // 添加新联系人
            let existingContact = contacts.find((c) => c.name === name);

            if (existingContact) {
                if (phone && !existingContact.phone.includes(phone)) {
                    existingContact.phone.push(phone);
                }
                if (email && !existingContact.email.includes(email)) {
                    existingContact.email.push(email);
                }
            } else {
                contacts.push({
                    id: Date.now().toString(),
                    name,
                    phone: phone ? [phone] : [],
                    email: email ? [email] : [],
                });
            }
        }

        renderContacts();
        contactForm.reset();
        document.getElementById("contactId").value = ""; // 重置隐藏字段
    });

    // 编辑联系人信息
    contactTable.addEventListener("click", (e) => {
        const target = e.target;
        const id = target.dataset.id;

        if (target.classList.contains("edit")) {
            const contact = contacts.find((c) => c.id === id);
            if (contact) {
                document.getElementById("contactId").value = contact.id;
                document.getElementById("name").value = contact.name;
                document.getElementById("phone").value = "";
                document.getElementById("email").value = "";
            }
        }

        if (target.classList.contains("delete")) {
            contacts = contacts.filter((c) => c.id !== id);
            renderContacts();
        }

        if (target.classList.contains("delete-phone")) {
            const phoneIndex = target.dataset.phoneIndex;
            const contact = contacts.find((c) => c.id === id);
            if (contact) {
                contact.phone.splice(phoneIndex, 1);
            }
            renderContacts();
        }
    });

3.4 Import and Export Contacts

Descruiption:

This GIF illustrates the Import and Export Contacts feature of the contact book application.

Export Function: Allows users to export all contact information from the address book into an Excel file, with each row representing a contact and each column corresponding to a specific contact information field (e.g., name, phone number, backup phone number, email,).

Import function: After clicking the "Select File" button in the address book, the user can choose to upload an Excel file with contact information. The system will automatically parse the content of the file and import the contact information strictly according to the order of name, phone number, and email address. After the "Select File" button, the file name of the imported Excel table will be displayed. If no file is imported, "No file selected" will be displayed.

Implementation:

Import function implementation:

  1. The user selects an Excel file by clicking the "Import" button or selecting the file input.
  2. The system uses a file reader (FileReader) to read user-selected Excel files.
  3. After reading, the system uses the XLSX library to convert the contents of the Excel file into an array of JavaScript objects.
  4. The system iterates over these objects, extracting name, phone, and email information.
  5. For each contact, the system checks to see if it already exists in the current contact list:
    1. If yes, the system adds a new phone number and email address to the corresponding contact information to avoid duplication.
    2. If it does not exist, a new contact record is created and added to the list.
  6. After the contact list is updated, the user is prompted "Import successful" and the contact list is re-rendered to display the newly imported data.

Export function implementation:

  1. The user clicks the "Export" button to trigger the export action.
  2. The system converts each contact in the current contact list into an object that contains a name, phone, and email address.
  3. The phone and mailbox fields, if they are arrays, are converted to comma-separated strings.
  4. Using the XLSX library, the system converts these objects into a worksheet.
  5. The system creates a new workbook and adds the worksheet to the workbook.
  6. Finally, the system saves the workbook as an Excel file called "contacts.xlsx" and makes it available for the user to download.

img


Code Reference:

// 导出为 Excel
    exportButton.addEventListener("click", () => {
        const data = contacts.map((contact) => ({
            姓名: contact.name,
            电话: contact.phone.join(", "),
            邮箱: contact.email.join(", "),
        }));

        const ws = XLSX.utils.json_to_sheet(data);
        const wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws, "通讯录");
        XLSX.writeFile(wb, "通讯录.xlsx");
    });

    // 导入 Excel
    importFile.addEventListener("change", (e) => {
        const file = e.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = (e) => {
            try {
                const data = new Uint8Array(e.target.result);
                const workbook = XLSX.read(data, { type: "array" });

                const sheetName = workbook.SheetNames[0];
                const worksheet = workbook.Sheets[sheetName];
                const importedData = XLSX.utils.sheet_to_json(worksheet);

                if (!Array.isArray(importedData) || importedData.length === 0) {
                    alert("文件内容为空或格式不正确!");
                    return;
                }

                importedData.forEach((row) => {
                    const name = (row["姓名"] || "").toString().trim();
                    const phones = (row["电话"] || "")
                        .toString()
                        .split(",")
                        .map((p) => p.trim());
                    const emails = (row["邮箱"] || "")
                        .toString()
                        .split(",")
                        .map((e) => e.trim());

                    if (!name) return;

                    let existingContact = contacts.find((c) => c.name === name);

                    if (existingContact) {
                        phones.forEach((p) => {
                            if (p && !existingContact.phone.includes(p)) {
                                existingContact.phone.push(p);
                            }
                        });
                        emails.forEach((e) => {
                            if (e && !existingContact.email.includes(e)) {
                                existingContact.email.push(e);
                            }
                        });
                    } else {
                        contacts.push({
                            id: Date.now().toString(),
                            name,
                            phone: phones.filter(Boolean),
                            email: emails.filter(Boolean),
                        });
                    }
                });

                renderContacts();
                alert("导入成功!");
            } catch (err) {
                alert("文件解析失败,请检查文件格式!");
                console.error(err);
            }
        };

        reader.readAsArrayBuffer(file);
    });

3.5 Database(MySQL)

img

4.Division of labor among team members

Team MemberRoleResponsibilitiesContribution (%)
Gao YuFrontend DeveloperUI layout creation, implementing forms, integrating APIs, participating in tech stack selection, planning and design, testing, and project evaluation50%
Xiong ZhiXueBackend DeveloperServer and database setup, API development, system architecture design, deployment (server hosting and configuration), and back-end testing for robustness and functionality50%

5.Difficulties encountered in cooperation and solutions

Challenges:

  1. Challenge 1: When developing features to add additional information to contacts, we often encounter the problem that the back end frequently receives empty JSON objects. This phenomenon causes errors in data validation, which in turn affects the normal operation of the function.

  2. Challenge 2: The deployment of multiple projects on low-cost micro-servers significantly increased the server burden, resulting in reduced processing power, especially during peak user hours, and significantly reduced system efficiency.

  3. Challenge 3: In the process of building the Excel file import tool, we noticed that users often submitted incorrectly formatted files, which led to data processing failures and database data inconsistencies.

  4. Challenge 4: In team collaboration, poor communication occasionally leads to unclear priorities, which leads to duplication of effort or delays in integrating back-end functionality with the front end.

6.PSP table

PSP Table for Member Gao Yu

ModulesTask DescriptionEstimated TimeActual Time
Group Discussion and Demand AnalysisDetermine the required functions of the web page1 hour1 hour
Knowledge LearningLearning about Excel Import and Export1 hour2 hour
Frontend DevelopmentCreate a Vue project1 hour2 hour
Frontend Test and DebugTest the web page functions2 hour3 hour
DeployDeploy the application to the server (Huawei Cloud)1 hour2 hour
Total6 hour10 hour

PSP Table for Member Xiong ZhiXue

ModulesTask DescriptionEstimated TimeActual Time
Group Discussion and Demand AnalysisDetermine the required functions of the web page1 hour1 hour
Knowledge LearningLearning about Excel Import and Export1 hour2 hour
Backend DevelopmentCreate a SpringBoot project3 hour5 hour
Backend Test and DebugTest the web page functions1 hour2 hour
DeployDeploy the application to the server1 hour2 hour
Total7 hour12 hour
...全文
51 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

170

社区成员

发帖
与我相关
我的任务
社区描述
2401_MU_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_TeacherL
  • 助教-吴可仪
  • 助教-孔志豪
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧