Outsystem中文教程2025

<!DOCTYPE html>

<html lang="zh-CN">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>备忘录与待办事项管理</title>

    <style>

        * {

            margin: 0;

            padding: 0;

            box-sizing: border-box;

        }

        body {

            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

            display: flex;

            min-height: 100vh;

        }

        /* 左侧菜单栏样式 */

        .sidebar {

            width: 200px;

            background-color: #f5f5f5;

            padding: 20px;

            border-right: 1px solid #e0e0e0;

        }

        .menu-item {

            padding: 10px 15px;

            margin-bottom: 5px;

            cursor: pointer;

            border-radius: 4px;

            transition: background-color 0.3s;

        }

        .menu-item:hover {

            background-color: #e0e0e0;

        }

        .menu-item.active {

            background-color: #007bff;

            color: white;

        }

        /* 右侧内容区样式 */

        .main-content {

            flex: 1;

            padding: 20px;

            background-color: #ffffff;

        }

        /* 顶部功能区样式 */

        .top-actions {

            margin-bottom: 20px;

            padding-bottom: 15px;

            border-bottom: 1px solid #e0e0e0;

        }

        .btn {

            padding: 8px 16px;

            background-color: #007bff;

            color: white;

            border: none;

            border-radius: 4px;

            cursor: pointer;

            margin-right: 10px;

            transition: background-color 0.3s;

        }

        .btn:hover {

            background-color: #0056b3;

        }

        /* 内容区域样式 */

        .content-section {

            display: none;

        }

        .content-section.active {

            display: block;

        }

        /* 备忘录样式 */

        .memo-item {

            display: flex;

            align-items: center;

            margin-bottom: 10px;

            padding: 10px;

            border: 1px solid #e0e0e0;

            border-radius: 4px;

        }

        .memo-checkbox {

            width: 20px;

            height: 20px;

            border: 2px solid #007bff;

            border-radius: 50%;

            margin-right: 10px;

            cursor: pointer;

            position: relative;

        }

        .memo-checkbox.checked {

            background-color: #007bff;

        }

        .memo-checkbox.checked::after {

            content: '✓';

            color: white;

            position: absolute;

            top: 50%;

            left: 50%;

            transform: translate(-50%, -50%);

            font-size: 12px;

        }

        .memo-content {

            flex: 1;

        }

        .memo-content.completed {

            color: #888;

            text-decoration: line-through;

        }

        .memo-date {

            color: #666;

            font-size: 0.9em;

            margin-right: 10px;

        }

        .memo-delete {

            color: #dc3545;

            cursor: pointer;

            padding: 5px;

        }

        .memo-date-group {

            margin-bottom: 20px;

            border: 1px solid #e0e0e0;

            border-radius: 4px;

            padding: 10px;

        }

        .memo-date-title {

            color: #007bff;

            margin-bottom: 10px;

            padding-bottom: 5px;

            border-bottom: 1px solid #e0e0e0;

        }

        /* 待办事项区域样式 */

        .todo-container {

            display: grid;

            grid-template-columns: repeat(4, 1fr);

            gap: 15px;

            margin-top: 20px;

        }

        .todo-section {

            border: 1px solid #e0e0e0;

            border-radius: 4px;

            padding: 15px;

        }

        .todo-section h3 {

            margin-bottom: 15px;

            color: #333;

        }

        .todo-item {

            background-color: #f8f9fa;

            border: 1px solid #dee2e6;

            border-radius: 4px;

            padding: 10px;

            margin-bottom: 10px;

            cursor: move;

            user-select: none;

        }

        .todo-items {

            min-height: 100px;

            background-color: #f8f9fa;

            border: 1px solid #dee2e6;

            border-radius: 4px;

            padding: 10px;

            margin-bottom: 10px;

        }

        .todo-items.dragover {

            background-color: #e9ecef;

            border: 2px dashed #007bff;

        }

        .todo-item-content {

            margin-bottom: 5px;

        }

        .todo-item-actions {

            display: flex;

            gap: 10px;

        }

        .todo-item-actions button {

            font-size: 0.8em;

            padding: 3px 8px;

        }

        .add-item {

            margin-top: 10px;

        }

        .add-item input[type="text"] {

            width: 400px;

            padding: 8px;

            font-size: 16px;

        }

       

        .add-item input[type="date"] {

            padding: 8px;

            font-size: 16px;

        }

        /* 添加响应式设计 */

        @media (max-width: 768px) {

            body {

                flex-direction: column;

            }

            .sidebar {

                width: 100%;

                border-right: none;

                border-bottom: 1px solid #e0e0e0;

            }

            .todo-container {

                grid-template-columns: 1fr;

            }

        }

        /* 添加打印样式 */

        @media print {

            .sidebar, .top-actions {

                display: none;

            }

            .main-content {

                padding: 0;

            }

            .memo-item, .todo-item {

                page-break-inside: avoid;

            }

        }

        /* 悬浮提示框样式 */

        .toast {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            background-color: #28a745;

            color: white;

            padding: 15px 25px;

            border-radius: 4px;

            box-shadow: 0 2px 5px rgba(0,0,0,0.2);

            opacity: 0;

            z-index: 1000;

            transition: opacity 0.5s, transform 0.5s;

        }

        .toast.show {

            opacity: 1;

            transform: translate(-50%, 0);

        }

        .toast.hide {

            opacity: 0;

            transform: translate(-50%, -20px);

        }

        .toast.error {

            background-color: #dc3545;

        }

    </style>

</head>

<body>

    <!-- 左侧菜单栏 -->

    <div class="sidebar">

        <div class="menu-item active" data-section="memo">备忘录</div>

        <div class="menu-item" data-section="todo">待办事项</div>

    </div>

    <!-- 右侧内容区 -->

    <div class="main-content">

        <!-- 顶部功能区 -->

        <div class="top-actions">

            <div style="display: flex; align-items: center; gap: 10px;">

                <button class="btn" id="importBtn">导入数据</button>

                <button class="btn" id="exportBtn">导出数据</button>

                <div>

                    <input type="text" id="memosCsvPathInput" value="/Users/baidoufu/Desktop/memos.csv" readonly style="padding: 5px; border: 1px solid #ced4da; border-radius: 4px; width: 300px; background-color: #f8f9fa;">

                </div>

                <div>

                    <input type="text" id="todosCsvPathInput" value="/Users/baidoufu/Desktop/todos.csv" readonly style="padding: 5px; border: 1px solid #ced4da; border-radius: 4px; width: 300px; background-color: #f8f9fa;">

                </div>

                <div>

                    <input type="text" id="backupPathInput" value="/Users/baidoufu/Desktop/backup" readonly style="padding: 5px; border: 1px solid #ced4da; border-radius: 4px; width: 300px; background-color: #f8f9fa;">

                </div>

                <button class="btn" id="backupBtn">备份数据</button>

            </div>

        </div>

        <!-- 备忘录区域 -->

        <div class="content-section active" id="memo-section">

            <h2>备忘录</h2>

            <div class="add-item">

                <input type="text" id="newMemoInput" placeholder="输入新的备忘录内容" style="width: 500px; padding: 10px; font-size: 16px;">

                <input type="date" id="newMemoDate" value="" style="padding: 8px; font-size: 16px;">

                <button class="btn" onclick="addMemo()">添加</button>

            </div>

            <div id="memoList"></div>

        </div>

        <!-- 待办事项区域 -->

        <div class="content-section" id="todo-section">

            <div class="todo-header">

                <h2>待办事项</h2>

                <div class="add-item">

                    <input type="text" id="newTodoInput" placeholder="输入新的待办事项" style="width: 500px; padding: 10px; font-size: 16px;">

                    <button class="btn" onclick="addTodoItem('pending')">创建待办</button>

                </div>

            </div>

            <div class="todo-container">

                <div class="todo-section" id="todo-pending">

                    <h3>待办区</h3>

                    <div class="todo-items" ondrop="drop(event)" ondragover="allowDrop(event)" data-section="pending"></div>

                </div>

                <div class="todo-section" id="todo-inProgress">

                    <h3>进行区</h3>

                    <div class="todo-items" ondrop="drop(event)" ondragover="allowDrop(event)" data-section="inProgress"></div>

                </div>

                <div class="todo-section" id="todo-completed">

                    <h3>完成区</h3>

                    <div class="todo-items" ondrop="drop(event)" ondragover="allowDrop(event)" data-section="completed"></div>

                </div>

                <div class="todo-section" id="todo-onHold">

                    <h3>搁置区</h3>

                    <div class="todo-items" ondrop="drop(event)" ondragover="allowDrop(event)" data-section="onHold"></div>

                </div>

            </div>

        </div>

    </div>

    <script>

        // 数据存储

        let memos = [];

        let todos = {

            pending: [],

            inProgress: [],

            completed: [],

            onHold: []

        };

        // 菜单切换功能

        document.querySelectorAll('.menu-item').forEach(item => {

            item.addEventListener('click', () => {

                document.querySelectorAll('.menu-item').forEach(i => i.classList.remove('active'));

                document.querySelectorAll('.content-section').forEach(i => i.classList.remove('active'));

               

                item.classList.add('active');

                const section = item.dataset.section;

                document.getElementById(`${section}-section`).classList.add('active');

            });

        });

        // 初始化日期选择器默认值为今天

        document.getElementById('newMemoDate').valueAsDate = new Date();

        // 备忘录功能

        function addMemo() {

            const input = document.getElementById('newMemoInput');

            const dateInput = document.getElementById('newMemoDate');

            const content = input.value.trim();

            const selectedDate = dateInput.value;

           

            if (!content) {

                alert('请输入备忘录内容');

                return;

            }

            if (!selectedDate) {

                alert('请选择日期');

                return;

            }

            // 修复日期比较逻辑

            const selectedDateObj = new Date(selectedDate);

            const today = new Date();

           

            // 只比较年月日

            const isPastDate = selectedDateObj.getFullYear() < today.getFullYear() ||

                              (selectedDateObj.getFullYear() === today.getFullYear() &&

                              selectedDateObj.getMonth() < today.getMonth()) ||

                              (selectedDateObj.getFullYear() === today.getFullYear() &&

                              selectedDateObj.getMonth() === today.getMonth() &&

                              selectedDateObj.getDate() < today.getDate());

            if (isPastDate) {

                if (!confirm('您选择的日期是过去日期,确定要添加吗?')) {

                    return;

                }

            }

            const memo = {

                id: Date.now(),

                content: content,

                date: selectedDate,

                completed: false

            };

            memos.push(memo);

            renderMemos();

            input.value = '';

        }

        function renderMemos() {

            const memoList = document.getElementById('memoList');

            memoList.innerHTML = '';

           

            // 按日期对备忘录进行分组

            const groupedMemos = {};

            memos.forEach(memo => {

                if (!groupedMemos[memo.date]) {

                    groupedMemos[memo.date] = [];

                }

                groupedMemos[memo.date].push(memo);

            });

           

            // 按日期排序(从新到旧)

            const sortedDates = Object.keys(groupedMemos).sort((a, b) => new Date(b) - new Date(a));

           

            sortedDates.forEach(date => {

                // 检查该日期下的所有备忘录是否都已完成

                const allCompleted = groupedMemos[date].every(memo => memo.completed);

               

                // 如果该日期下的所有备忘录都已完成,则跳过渲染

                if (allCompleted) return;

                // 创建日期分组标题

                const dateGroup = document.createElement('div');

                dateGroup.className = 'memo-date-group';

                dateGroup.innerHTML = `<h3 class="memo-date-title">${date}</h3>`;

                memoList.appendChild(dateGroup);

               

                // 渲染该日期下的所有备忘录

                groupedMemos[date].forEach(memo => {

                    const memoElement = document.createElement('div');

                    memoElement.className = 'memo-item';

                    memoElement.innerHTML = `

                        <div class="memo-checkbox ${memo.completed ? 'checked' : ''}" onclick="toggleMemo(${memo.id})"></div>

                        <span class="memo-content ${memo.completed ? 'completed' : ''}">${memo.content}</span>

                        <span class="memo-delete" onclick="deleteMemo(${memo.id})">×</span>

                    `;

                    dateGroup.appendChild(memoElement);

                });

            });

        }

        function toggleMemo(id) {

            const memo = memos.find(m => m.id === id);

            if (memo) {

                memo.completed = !memo.completed;

                renderMemos();

            }

        }

        function deleteMemo(id) {

            memos = memos.filter(m => m.id !== id);

            renderMemos();

        }

        // 待办事项功能

        function addTodoItem(section) {

            const input = document.getElementById('newTodoInput');

            if (!input) return;

            const content = input.value.trim();

            if (content) {

                const todo = {

                    id: Date.now(),

                    content: content

                };

                todos[section].push(todo);

                renderTodos();

                input.value = '';

            }

        }

        function deleteTodoItem(section, id) {

            todos[section] = todos[section].filter(item => item.id !== id);

            renderTodos();

        }

        function moveTodoItem(id, fromSection, toSection) {

            const item = todos[fromSection].find(item => item.id === id);

            if (item) {

                todos[fromSection] = todos[fromSection].filter(i => i.id !== id);

                todos[toSection].push(item);

                renderTodos();

            }

        }

        function renderTodos() {

            Object.keys(todos).forEach(section => {

                const container = document.querySelector(`#todo-${section} .todo-items`);

                container.innerHTML = '';

               

                todos[section].forEach(todo => {

                    const todoElement = document.createElement('div');

                    todoElement.className = 'todo-item';

                    todoElement.draggable = true;

                    todoElement.setAttribute('data-id', todo.id);

                    todoElement.setAttribute('data-section', section);

                    todoElement.ondragstart = drag;

                   

                    todoElement.innerHTML = `

                        <div class="todo-item-content">${todo.content}</div>

                        <div class="todo-item-actions">

                            <button class="btn" style="background-color: #dc3545;" onclick="deleteTodoItem('${section}', ${todo.id})">删除</button>

                        </div>

                    `;

                    container.appendChild(todoElement);

                });

            });

        }

        function getSectionName(section) {

            const names = {

                pending: '待办区',

                inProgress: '进行区',

                completed: '完成区',

                onHold: '搁置区'

            };

            return names[section];

        }

        // 拖拽功能

        function allowDrop(event) {

            event.preventDefault();

            const dropTarget = event.target.closest('.todo-items');

            if (dropTarget) {

                dropTarget.classList.add('dragover');

            }

        }

        function drag(event) {

            const todoItem = event.target.closest('.todo-item');

            if (todoItem) {

                event.dataTransfer.setData('text/plain', JSON.stringify({

                    id: parseInt(todoItem.getAttribute('data-id')),

                    fromSection: todoItem.getAttribute('data-section')

                }));

            }

        }

        function drop(event) {

            event.preventDefault();

            const dropTarget = event.target.closest('.todo-items');

            if (!dropTarget) return;

           

            dropTarget.classList.remove('dragover');

            const data = JSON.parse(event.dataTransfer.getData('text/plain'));

            const toSection = dropTarget.dataset.section;

           

            if (data.fromSection !== toSection) {

                moveTodoItem(data.id, data.fromSection, toSection);

            }

        }

        // 添加拖拽结束时移除视觉效果

        document.querySelectorAll('.todo-items').forEach(container => {

            container.addEventListener('dragleave', (event) => {

                event.preventDefault();

                const dropTarget = event.target.closest('.todo-items');

                if (dropTarget) {

                    dropTarget.classList.remove('dragover');

                }

            });

        });

        // CSV导入导出功能

        document.getElementById('importBtn').addEventListener('click', async () => {

            try {

                const memosCsvPath = document.getElementById('memosCsvPathInput').value.trim();

                const todosCsvPath = document.getElementById('todosCsvPathInput').value.trim();

                // 读取memos.csv

                const memosResponse = await fetch('/read-files', {

                    method: 'POST',

                    headers: {

                        'Content-Type': 'application/json'

                    },

                    body: JSON.stringify({

                        memosCsvPath: memosCsvPath,

                        todosCsvPath: todosCsvPath

                    })

                });

                const data = await memosResponse.json();

                if (data.status === 'success') {

                    if (data.memosContent) {

                        importMemosFromCSV(data.memosContent);

                    }

                    if (data.todosContent) {

                        importTodosFromCSV(data.todosContent);

                    }

                    showToast('数据导入成功');

                } else {

                    throw new Error(data.error || '导入数据失败');

                }

            } catch (error) {

                showToast('导入数据失败: ' + error.message, 'error');

            }

        });

        document.getElementById('exportBtn').addEventListener('click', async () => {

            try {

                const memosCsvPath = document.getElementById('memosCsvPathInput').value.trim();

                const todosCsvPath = document.getElementById('todosCsvPathInput').value.trim();

                // 导出所有备忘录数据

                const memosCSV = generateMemosCSV(memos);

                await writeFile(memosCsvPath, memosCSV);

                // 导出所有待办事项数据

                const todosCSV = generateTodosCSV(todos);

                await writeFile(todosCsvPath, todosCSV);

                showToast('数据导出成功');

            } catch (error) {

                showToast('导出数据失败: ' + error.message, 'error');

            }

        });

        // 修改备份按钮点击事件处理

        document.getElementById('backupBtn').addEventListener('click', async () => {

            try {

                const backupPath = document.getElementById('backupPathInput').value.trim();

               

                // 获取当前时间戳

                const timestamp = new Date().toISOString().replace(/[:.]/g, '-');

               

                // 备份备忘录数据

                const memosCSV = generateMemosCSV(memos);

                const memosBackupPath = `${backupPath}/memos_${timestamp}.csv`;

                await writeFile(memosBackupPath, memosCSV);

                // 备份待办事项数据

                const todosCSV = generateTodosCSV(todos);

                const todosBackupPath = `${backupPath}/todos_${timestamp}.csv`;

                await writeFile(todosBackupPath, todosCSV);

                showToast('数据备份成功');

            } catch (error) {

                showToast('备份数据失败: ' + error.message, 'error');

            }

        });

        function readFile(file) {

            return new Promise((resolve, reject) => {

                const reader = new FileReader();

                reader.onload = (e) => resolve(e.target.result);

                reader.onerror = (e) => reject(new Error('文件读取失败'));

                reader.readAsText(file);

            });

        }

        function writeFile(path, content) {

            return new Promise((resolve, reject) => {

                const xhr = new XMLHttpRequest();

                xhr.open('POST', '/export-data', true);

                xhr.setRequestHeader('Content-Type', 'application/json');

                xhr.onload = () => {

                    if (xhr.status === 200) {

                        resolve();

                    } else {

                        reject(new Error('文件写入失败'));

                    }

                };

                xhr.onerror = () => reject(new Error('网络错误'));

                xhr.send(JSON.stringify({

                    path: path,

                    content: content

                }));

            });

        }

        function generateMemosCSV(memosData) {

            let csv = 'id,content,date,completed\n';

            memosData.forEach(memo => {

                const content = memo.content.replace(/"/g, '""');

                csv += `${memo.id},"${content}",${memo.date},${memo.completed}\n`;

            });

            return csv;

        }

        function generateTodosCSV(todosData) {

            let csv = 'section,id,content\n';

            Object.entries(todosData).forEach(([section, items]) => {

                items.forEach(todo => {

                    const content = todo.content.replace(/"/g, '""');

                    csv += `${section},${todo.id},"${content}"\n`;

                });

            });

            return csv;

        }

        function importMemosFromCSV(csv) {

            const lines = csv.split('\n');

            if (lines.length < 2) {

                alert('CSV文件格式不正确');

                return;

            }

            memos = lines.slice(1).filter(line => line.trim()).map(line => {

                // 使用更精确的CSV解析方法

                const cols = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);

                if (cols.length < 4) return null;

               

                return {

                    id: parseInt(cols[0]),

                    content: cols[1].replace(/^"|"$/g, '').replace(/""/g, '"'),

                    date: cols[2],

                    completed: cols[3] === 'true'

                };

            }).filter(Boolean);

            renderMemos();

        }

        function importTodosFromCSV(csv) {

            const lines = csv.split('\n');

            if (lines.length < 2) return;

            Object.keys(todos).forEach(key => todos[key] = []);

            lines.slice(1).filter(line => line.trim()).forEach(line => {

                const cols = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);

                if (cols.length < 3) return;

                const section = cols[0];

                if (todos[section]) {

                    todos[section].push({

                        id: parseInt(cols[1]),

                        content: cols[2].replace(/^"|"$/g, '').replace(/""/g, '"')

                    });

                }

            });

            renderTodos();

        }

        function showToast(message, type = 'success') {

            // 移除所有现有的提示框

            document.querySelectorAll('.toast').forEach(toast => toast.remove());

            const toast = document.createElement('div');

            toast.className = `toast ${type}`;

            toast.textContent = message;

            document.body.appendChild(toast);

            // 显示提示框

            setTimeout(() => {

                toast.classList.add('show');

            }, 10);

            // 3秒后隐藏提示框

            setTimeout(() => {

                toast.classList.remove('show');

                toast.classList.add('hide');

               

                // 在动画结束后移除提示框

                toast.addEventListener('transitionend', () => {

                    if (toast.classList.contains('hide')) {

                        toast.remove();

                    }

                }, { once: true });

            }, 2000);

        }

    </script>

</body>

</html>

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容