完善的反馈系统,带有图片上传,视频上传,后台带有标记已处理,回复用户等

 

1.index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>反馈中心</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            -webkit-tap-highlight-color: transparent;
        }

        body {
            background: #f5f7fb;
            font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, sans-serif;
            padding: 20px 16px 40px;
            color: #1e293b;
        }

        .container {
            max-width: 600px;
            margin: 0 auto;
        }

        .card {
            background: #ffffff;
            border-radius: 32px;
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.02), 0 2px 4px rgba(0, 0, 0, 0.03);
            padding: 24px 20px;
            margin-bottom: 24px;
            border: 1px solid #eef2f8;
        }

        h1 {
            font-size: 1.75rem;
            font-weight: 620;
            letter-spacing: -0.3px;
            background: linear-gradient(135deg, #1e2b3c, #2d3e50);
            background-clip: text;
            -webkit-background-clip: text;
            color: transparent;
        }

        .subhead {
            font-size: 0.85rem;
            color: #5b6e8c;
            border-left: 3px solid #3b82f6;
            padding-left: 12px;
            margin: 8px 0 20px 0;
        }

        .form-group {
            margin-bottom: 24px;
        }

        label {
            font-weight: 500;
            font-size: 0.9rem;
            color: #1e293b;
            display: flex;
            align-items: center;
            gap: 6px;
            margin-bottom: 8px;
        }

        textarea {
            width: 100%;
            border: 1.5px solid #e2e8f0;
            border-radius: 24px;
            padding: 14px 18px;
            font-size: 0.95rem;
            font-family: inherit;
            resize: vertical;
            background: #fff;
            transition: 0.2s;
        }

        textarea:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59,130,246,0.1);
        }

        .upload-area {
            border: 1.5px dashed #cbd5e1;
            border-radius: 28px;
            padding: 16px;
            background: #fefefe;
        }

        .upload-trigger {
            background: #f1f5f9;
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 10px 20px;
            border-radius: 40px;
            font-size: 0.85rem;
            font-weight: 500;
            color: #1e293b;
            cursor: pointer;
            margin-bottom: 16px;
            transition: 0.2s;
        }

        .upload-trigger:active {
            background: #e2e8f0;
        }

        .selected-files {
            display: flex;
            flex-direction: column;
            gap: 12px;
            margin-top: 8px;
        }

        .file-item {
            display: flex;
            align-items: center;
            gap: 12px;
            background: #f8fafc;
            padding: 8px 12px;
            border-radius: 20px;
            border: 1px solid #eef2ff;
        }

        .file-thumb {
            width: 48px;
            height: 48px;
            background: #eef2ff;
            border-radius: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
        }

        .file-thumb img, .file-thumb video {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        .file-info {
            flex: 1;
            font-size: 0.75rem;
            color: #334155;
            word-break: break-all;
        }

        .remove-file {
            background: none;
            border: none;
            font-size: 20px;
            cursor: pointer;
            color: #94a3b8;
            padding: 6px;
        }

        .progress-wrap {
            display: none;
            align-items: center;
            gap: 12px;
            background: #f1f5f9;
            padding: 10px 16px;
            border-radius: 60px;
            margin: 16px 0 8px;
        }
        .progress-bar-bg {
            flex: 1;
            height: 6px;
            background: #e2e8f0;
            border-radius: 6px;
            overflow: hidden;
        }
        .progress-fill {
            width: 0%;
            height: 100%;
            background: #3b82f6;
            border-radius: 6px;
            transition: width 0.2s;
        }
        .spinner {
            width: 20px;
            height: 20px;
            border: 2px solid #cbd5e1;
            border-top-color: #3b82f6;
            border-radius: 50%;
            animation: spin 0.7s linear infinite;
        }
        @keyframes spin { to { transform: rotate(360deg); } }

        button {
            background: #3b82f6;
            border: none;
            padding: 14px 20px;
            border-radius: 60px;
            font-weight: 600;
            font-size: 1rem;
            color: white;
            width: 100%;
            cursor: pointer;
            transition: 0.2s;
            margin-top: 12px;
        }
        button:active { transform: scale(0.97); background: #2563eb; }
        button:disabled { opacity: 0.6; transform: none; }

        .feedback-item {
            background: white;
            border-radius: 24px;
            padding: 18px;
            margin-bottom: 16px;
            border: 1px solid #edf2f7;
        }
        .feedback-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            flex-wrap: wrap;
            gap: 8px;
        }
        .time {
            font-size: 0.7rem;
            color: #6c7a91;
        }
        .status-badge {
            font-size: 0.7rem;
            padding: 4px 12px;
            border-radius: 40px;
            font-weight: 500;
        }
        .status-resolved {
            background: #dcfce7;
            color: #15803d;
        }
        .status-pending {
            background: #fff3e3;
            color: #b45309;
        }
        .feedback-content {
            font-size: 0.9rem;
            line-height: 1.45;
            margin: 8px 0;
        }
        .reply-section {
            background: #f8fafc;
            border-radius: 20px;
            padding: 12px;
            margin-top: 12px;
            font-size: 0.85rem;
            color: #2c3e66;
            border-left: 3px solid #3b82f6;
        }
        .reply-label {
            font-weight: 600;
            margin-bottom: 4px;
            display: flex;
            align-items: center;
            gap: 6px;
        }
        .media-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin-top: 12px;
        }
        .media-grid a {
            display: block;
            width: 80px;
            height: 80px;
            border-radius: 16px;
            overflow: hidden;
            background: #f1f5f9;
            border: 1px solid #eef2ff;
        }
        .media-grid img, .media-grid video {
            width: 100%;
            height: 100%;
            object-fit: cover;
            cursor: pointer;
        }
        .refresh-btn {
            background: #f1f5f9;
            color: #1e293b;
            margin-top: 0;
            width: auto;
            padding: 10px 18px;
            font-size: 0.85rem;
        }
        .flex-between {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 18px;
        }
        .modal-mask {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            backdrop-filter: blur(4px);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 1000;
            visibility: hidden;
            opacity: 0;
            transition: 0.2s;
        }
        .modal-mask.active {
            visibility: visible;
            opacity: 1;
        }
        .modal-card {
            background: white;
            border-radius: 48px;
            padding: 28px 24px;
            text-align: center;
            width: 280px;
        }
        .success-icon {
            width: 56px;
            height: 56px;
            background: #3b82f6;
            border-radius: 60px;
            margin: 0 auto 16px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .svg-icon {
            width: 24px;
            height: 24px;
            stroke-width: 1.5;
            stroke: currentColor;
            fill: none;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="card">
        <h1>反馈中心</h1>
        <div class="subhead">告诉我们你的想法,我们会认真对待</div>
        <form id="feedbackForm">
            <div class="form-group">
                <label>反馈内容</label>
                <textarea id="content" rows="4" placeholder="请详细描述... (必填)" required></textarea>
            </div>
            <div class="form-group">
                <label>附件 (图片/视频,最多4个)</label>
                <div class="upload-area">
                    <div class="upload-trigger" id="uploadTrigger">
                        <svg class="svg-icon" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 4v16m8-8H4"/></svg>
                        <span>选择文件</span>
                    </div>
                    <div id="selectedFilesList" class="selected-files"></div>
                    <input type="file" id="fileInput" multiple accept="image/jpeg,image/png,image/gif,video/mp4,video/quicktime" style="display:none">
                </div>
            </div>
            <div id="progressContainer" class="progress-wrap">
                <div class="spinner"></div>
                <div class="progress-bar-bg"><div class="progress-fill" id="progressFill"></div></div>
                <span id="progressPercent" style="font-size:0.75rem; min-width:45px;">0%</span>
            </div>
            <button type="submit" id="submitBtn">提交反馈</button>
        </form>
    </div>

    <div class="card">
        <div class="flex-between">
            <h2 style="font-size:1.3rem;">所有反馈</h2>
            <button id="refreshListBtn" class="refresh-btn">刷新状态</button>
        </div>
        <div id="feedbacksList" style="display: flex; flex-direction: column; gap: 14px;">
            <div style="text-align:center; padding:24px;">加载中...</div>
        </div>
    </div>
</div>

<div id="successModal" class="modal-mask">
    <div class="modal-card">
        <div class="success-icon">
            <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5">
                <path d="M20 6L9 17L4 12" stroke="white" stroke-linecap="round"/>
            </svg>
        </div>
        <h3 style="margin-bottom: 8px;">提交成功</h3>
        <p style="color:#475569;">感谢您的反馈!</p>
        <button onclick="closeModal()" style="margin-top: 20px; background:#eef2ff; color:#1e293b;">关闭</button>
    </div>
</div>

<script>
    // 多文件管理
    let selectedFiles = [];
    const fileInput = document.getElementById('fileInput');
    const selectedContainer = document.getElementById('selectedFilesList');
    const maxFiles = 4;

    function updateFileListUI() {
        if (!selectedContainer) return;
        if (selectedFiles.length === 0) {
            selectedContainer.innerHTML = '<div style="font-size:0.7rem; color:#94a3b8;">未选择任何文件</div>';
            return;
        }
        selectedContainer.innerHTML = '';
        selectedFiles.forEach((file, idx) => {
            const isImage = file.type.startsWith('image/');
            const isVideo = file.type.startsWith('video/');
            let previewHTML = '';
            if (isImage) {
                const url = URL.createObjectURL(file);
                previewHTML = `<img src="${url}" style="width:100%; height:100%; object-fit:cover;" onload="URL.revokeObjectURL('${url}')">`;
            } else if (isVideo) {
                const url = URL.createObjectURL(file);
                previewHTML = `<video src="${url}" style="width:100%; height:100%; object-fit:cover;" preload="metadata"></video>`;
            } else {
                previewHTML = '<span>文件</span>';
            }
            const itemDiv = document.createElement('div');
            itemDiv.className = 'file-item';
            itemDiv.innerHTML = `
                <div class="file-thumb">${previewHTML}</div>
                <div class="file-info">${file.name.length>30?file.name.slice(0,27)+'...':file.name}<br>${(file.size/1024).toFixed(0)} KB</div>
                <button class="remove-file" data-index="${idx}">✕</button>
            `;
            selectedContainer.appendChild(itemDiv);
        });
        document.querySelectorAll('.remove-file').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const idx = parseInt(btn.getAttribute('data-index'));
                if (!isNaN(idx)) {
                    selectedFiles.splice(idx, 1);
                    updateFileListUI();
                    fileInput.value = '';
                }
            });
        });
    }

    document.getElementById('uploadTrigger').addEventListener('click', () => fileInput.click());
    fileInput.addEventListener('change', (e) => {
        const newFiles = Array.from(e.target.files);
        if (selectedFiles.length + newFiles.length > maxFiles) {
            alert(`最多只能上传 ${maxFiles} 个文件`);
            return;
        }
        for (let f of newFiles) {
            if (f.size > 20 * 1024 * 1024) {
                alert(`文件 ${f.name} 超过 20MB,请压缩后上传`);
                return;
            }
        }
        selectedFiles.push(...newFiles);
        updateFileListUI();
        fileInput.value = '';
    });

    // 表单提交
    const form = document.getElementById('feedbackForm');
    const submitBtn = document.getElementById('submitBtn');
    const progressContainer = document.getElementById('progressContainer');
    const progressFill = document.getElementById('progressFill');
    const progressPercent = document.getElementById('progressPercent');
    const contentText = document.getElementById('content');

    form.addEventListener('submit', async (e) => {
        e.preventDefault();
        const content = contentText.value.trim();
        if (!content) {
            alert("请填写反馈内容");
            return;
        }
        if (selectedFiles.length > maxFiles) {
            alert(`最多上传 ${maxFiles} 个文件`);
            return;
        }

        const formData = new FormData();
        formData.append('content', content);
        for (let i = 0; i < selectedFiles.length; i++) {
            formData.append('media[]', selectedFiles[i]);
        }

        progressContainer.style.display = 'flex';
        submitBtn.disabled = true;
        progressFill.style.width = '0%';
        progressPercent.innerText = '0%';

        const xhr = new XMLHttpRequest();
        xhr.open('POST', 'submit.php', true);
        xhr.upload.onprogress = (ev) => {
            if (ev.lengthComputable) {
                const percent = Math.round((ev.loaded / ev.total) * 100);
                progressFill.style.width = percent + '%';
                progressPercent.innerText = percent + '%';
            }
        };
        xhr.onload = () => {
            progressContainer.style.display = 'none';
            submitBtn.disabled = false;
            if (xhr.status === 200) {
                try {
                    const resp = JSON.parse(xhr.responseText);
                    if (resp.success) {
                        contentText.value = '';
                        selectedFiles = [];
                        updateFileListUI();
                        showSuccessModal();
                        loadFeedbacks();
                    } else {
                        alert("提交失败: " + (resp.error || "未知错误"));
                    }
                } catch(err) { alert("服务器响应异常"); }
            } else {
                alert("网络错误,请检查网络后重试");
            }
        };
        xhr.onerror = () => {
            progressContainer.style.display = 'none';
            submitBtn.disabled = false;
            alert("请求失败,请稍后重试");
        };
        xhr.send(formData);
    });

    function showSuccessModal() {
        const modal = document.getElementById('successModal');
        modal.classList.add('active');
        setTimeout(() => closeModal(), 2000);
    }
    window.closeModal = function() {
        document.getElementById('successModal').classList.remove('active');
    };

    // 加载反馈列表
    async function loadFeedbacks() {
        try {
            const res = await fetch('admin.php?action=getFeedbacks');
            const list = await res.json();
            const container = document.getElementById('feedbacksList');
            if (!list.length) {
                container.innerHTML = '<div style="text-align:center; padding:24px;">暂无反馈,来提交第一条吧~</div>';
                return;
            }
            container.innerHTML = list.map(item => {
                const statusClass = item.status == 1 ? 'status-resolved' : 'status-pending';
                const statusText = item.status == 1 ? '已处理' : '待处理';
                const replyHtml = item.reply ? `
                    <div class="reply-section">
                        <div class="reply-label">官方回复:</div>
                        <div>${escapeHtml(item.reply)}</div>
                    </div>
                ` : '';
                return `
                    <div class="feedback-item">
                        <div class="feedback-header">
                            <span class="time">${escapeHtml(item.time)}</span>
                            <span class="status-badge ${statusClass}">${statusText}</span>
                        </div>
                        <div class="feedback-content">${escapeHtml(item.content).replace(/\n/g,'<br>')}</div>
                        ${replyHtml}
                        <div class="media-grid">
                            ${(item.media || []).map(url => {
                                const ext = url.split('.').pop().toLowerCase();
                                if (['jpg','jpeg','png','gif'].includes(ext)) {
                                    return `<a href="${url}" target="_blank"><img src="${url}" loading="lazy"></a>`;
                                } else if (['mp4','mov','avi','webm'].includes(ext)) {
                                    return `<a href="${url}" target="_blank"><video src="${url}" preload="metadata" style="width:100%;height:100%;object-fit:cover;"></video></a>`;
                                }
                                return '';
                            }).join('')}
                        </div>
                    </div>
                `;
            }).join('');
        } catch(e) {
            console.error(e);
            document.getElementById('feedbacksList').innerHTML = '<div style="text-align:center;">加载失败,刷新重试</div>';
        }
    }

    function escapeHtml(str) {
        return str.replace(/[&<>]/g, function(m) {
            if (m === '&') return '&amp;';
            if (m === '<') return '&lt;';
            if (m === '>') return '&gt;';
            return m;
        });
    }

    document.getElementById('refreshListBtn').addEventListener('click', loadFeedbacks);
    loadFeedbacks();
    updateFileListUI();
</script>
</body>
</html>

2.admin.php(管理员后台)

<?php
session_start();

$data_dir = __DIR__ . '/data';
$password_file = $data_dir . '/password.txt';
if (!is_dir($data_dir)) mkdir($data_dir, 0777, true);
if (!file_exists($password_file)) {
    file_put_contents($password_file, "admin:admin123\n");
}

function authenticate($username, $password) {
    global $password_file;
    $lines = file($password_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    foreach ($lines as $line) {
        $parts = explode(':', $line, 2);
        if (count($parts) === 2 && trim($parts[0]) === $username && trim($parts[1]) === $password) {
            return true;
        }
    }
    return false;
}

// 处理 API 请求
$request_uri = $_SERVER['REQUEST_URI'];
$query_str = parse_url($request_uri, PHP_URL_QUERY);
parse_str($query_str, $query_params);

if (isset($query_params['action'])) {
    $action = $query_params['action'];
    $data_file = $data_dir . '/feedbacks.json';
    $feedbacks = [];
    if (file_exists($data_file)) {
        $feedbacks = json_decode(file_get_contents($data_file), true);
        if (!is_array($feedbacks)) $feedbacks = [];
    }

    // 获取列表(公开,无需登录)
    if ($action === 'getFeedbacks') {
        header('Content-Type: application/json');
        echo json_encode(array_reverse($feedbacks));
        exit;
    }
    
    // 以下操作需要管理员登录
    $is_logged_in = isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
    if (!$is_logged_in) {
        header('HTTP/1.1 401 Unauthorized');
        echo json_encode(['success' => false, 'error' => '未登录']);
        exit;
    }

    // 切换处理状态
    if ($action === 'updateStatus') {
        header('Content-Type: application/json');
        $input = json_decode(file_get_contents('php://input'), true);
        $id = $input['id'] ?? '';
        $newStatus = isset($input['status']) ? (int)$input['status'] : -1;
        if (!$id || !in_array($newStatus, [0,1])) {
            echo json_encode(['success' => false]);
            exit;
        }
        foreach ($feedbacks as &$fb) {
            if ($fb['id'] === $id) {
                $fb['status'] = $newStatus;
                break;
            }
        }
        file_put_contents($data_file, json_encode($feedbacks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
        echo json_encode(['success' => true]);
        exit;
    }

    // 回复反馈
    if ($action === 'reply') {
        header('Content-Type: application/json');
        $input = json_decode(file_get_contents('php://input'), true);
        $id = $input['id'] ?? '';
        $reply = isset($input['reply']) ? trim($input['reply']) : '';
        if (!$id || $reply === '') {
            echo json_encode(['success' => false, 'error' => '回复内容不能为空']);
            exit;
        }
        $found = false;
        foreach ($feedbacks as &$fb) {
            if ($fb['id'] === $id) {
                $fb['reply'] = $reply;
                $found = true;
                break;
            }
        }
        if ($found) {
            file_put_contents($data_file, json_encode($feedbacks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
            echo json_encode(['success' => true]);
        } else {
            echo json_encode(['success' => false, 'error' => '反馈不存在']);
        }
        exit;
    }

    // 删除反馈(同时删除关联的媒体文件)
    if ($action === 'delete') {
        header('Content-Type: application/json');
        $input = json_decode(file_get_contents('php://input'), true);
        $id = $input['id'] ?? '';
        if (!$id) {
            echo json_encode(['success' => false]);
            exit;
        }
        $newFeedbacks = [];
        $deleted = false;
        foreach ($feedbacks as $fb) {
            if ($fb['id'] === $id) {
                // 删除对应的媒体文件
                if (isset($fb['media']) && is_array($fb['media'])) {
                    foreach ($fb['media'] as $filePath) {
                        $fullPath = __DIR__ . '/' . $filePath;
                        if (file_exists($fullPath)) {
                            unlink($fullPath);
                        }
                    }
                }
                $deleted = true;
                continue;
            }
            $newFeedbacks[] = $fb;
        }
        if ($deleted) {
            file_put_contents($data_file, json_encode($newFeedbacks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
            echo json_encode(['success' => true]);
        } else {
            echo json_encode(['success' => false]);
        }
        exit;
    }
}

// 普通页面请求:显示登录表单或管理界面
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';
    if (authenticate($username, $password)) {
        $_SESSION['admin_logged_in'] = true;
        header("Location: admin.php");
        exit();
    } else {
        $error = "用户名或密码错误!";
    }
}

$is_logged_in = isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
if (!$is_logged_in) {
    // 显示登录表单
    ?>
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
        <title>管理员登录</title>
        <style>
            * { box-sizing: border-box; }
            body {
                font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
                background: #f0f2f5;
                margin: 0;
                padding: 20px;
                min-height: 100vh;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .login-card {
                background: white;
                padding: 24px 20px 32px;
                border-radius: 32px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.05);
                width: 100%;
                max-width: 340px;
            }
            h2 {
                margin: 0 0 20px 0;
                text-align: center;
                font-size: 1.6rem;
                font-weight: 500;
            }
            input {
                width: 100%;
                padding: 14px 12px;
                margin-bottom: 16px;
                border: 1px solid #ddd;
                border-radius: 60px;
                font-size: 1rem;
                background: #fff;
            }
            input:focus {
                outline: none;
                border-color: #3b82f6;
                box-shadow: 0 0 0 3px rgba(59,130,246,0.2);
            }
            button {
                width: 100%;
                padding: 14px;
                background: #3b82f6;
                color: white;
                border: none;
                border-radius: 60px;
                font-size: 1.1rem;
                font-weight: 500;
                cursor: pointer;
            }
            .error {
                color: #e5484d;
                font-size: 0.85rem;
                margin-bottom: 16px;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div class="login-card">
            <h2>管理员登录</h2>
            <?php if (isset($error)) echo "<div class='error'>$error</div>"; ?>
            <form method="POST">
                <input type="hidden" name="login" value="1">
                <input type="text" name="username" placeholder="用户名" required autofocus>
                <input type="password" name="password" placeholder="密码" required>
                <button type="submit">登录</button>
            </form>
        </div>
    </body>
    </html>

 

3.submit.php(文件处理)

 

<?php
header('Content-Type: application/json');
session_start();

$data_dir = __DIR__ . '/data';
$upload_dir = __DIR__ . '/uploads/';
if (!is_dir($data_dir)) mkdir($data_dir, 0777, true);
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);

$data_file = $data_dir . '/feedbacks.json';

$content = isset($_POST['content']) ? trim($_POST['content']) : '';
if (empty($content)) {
    echo json_encode(['success' => false, 'error' => '内容不能为空']);
    exit;
}

$mediaPaths = [];
if (isset($_FILES['media']) && is_array($_FILES['media']['name'])) {
    $fileCount = count($_FILES['media']['name']);
    if ($fileCount > 4) {
        echo json_encode(['success' => false, 'error' => '最多上传4个文件']);
        exit;
    }
    $allowedImg = ['jpg','jpeg','png','gif'];
    $allowedVideo = ['mp4','mov','avi','webm'];
    for ($i = 0; $i < $fileCount; $i++) {
        if ($_FILES['media']['error'][$i] !== UPLOAD_ERR_OK) continue;
        $ext = strtolower(pathinfo($_FILES['media']['name'][$i], PATHINFO_EXTENSION));
        if (!in_array($ext, array_merge($allowedImg, $allowedVideo))) {
            continue;
        }
        if ($_FILES['media']['size'][$i] > 20 * 1024 * 1024) {
            continue;
        }
        $newName = time() . '_' . uniqid() . '.' . $ext;
        $target = $upload_dir . $newName;
        if (move_uploaded_file($_FILES['media']['tmp_name'][$i], $target)) {
            $mediaPaths[] = 'uploads/' . $newName;
        }
    }
}

$feedbacks = [];
if (file_exists($data_file)) {
    $feedbacks = json_decode(file_get_contents($data_file), true);
    if (!is_array($feedbacks)) $feedbacks = [];
}

$newFeedback = [
    'id' => uniqid(),
    'content' => $content,
    'media' => $mediaPaths,
    'time' => date('Y-m-d H:i:s'),
    'status' => 0,      // 0未处理 1已处理
    'reply' => ''       // 管理员回复内容
];
$feedbacks[] = $newFeedback;
file_put_contents($data_file, json_encode($feedbacks, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

echo json_encode(['success' => true]);

 

说明:在使用的时候,确保三个文件在同一个目录,然后呢,新建一个文件夹,名字为data,文件夹里新建一个文件名为:password.txt,这个文件里用于存放你的后台管理账号和密码,格式为:后台账号:后台密码

 

以下是效果