一步步教你,在WordPress中添加网站暗黑模式切换与个性化主题工具 引言:为什么网站需要暗黑模式与个性化主题工具? 在当今互联网时代,用户体验已成为网站成功的关键因素之一。随着用户对个性化体验需求的增加,以及健康用眼意识的提升,暗黑模式已成为现代网站的标配功能。根据2023年的一项调查显示,超过82%的用户表示更喜欢使用支持暗黑模式的网站,尤其是在夜间浏览时。 WordPress作为全球最流行的内容管理系统,拥有超过40%的市场份额,但其默认主题往往缺乏现代化的暗黑模式切换功能。通过代码二次开发,我们不仅可以为WordPress网站添加暗黑模式,还能创建一套完整的个性化主题工具,让用户根据自己的偏好调整网站外观。 本文将详细介绍如何通过WordPress代码二次开发,实现暗黑模式切换与个性化主题工具,让你的网站在众多WordPress站点中脱颖而出。 第一部分:准备工作与环境配置 1.1 创建子主题 在进行任何WordPress代码修改之前,最佳实践是创建一个子主题。这样可以确保你的修改不会在主题更新时丢失。 /* Theme Name: 我的个性化主题 Template: twentytwentythree Version: 1.0.0 Description: 添加暗黑模式与个性化工具的WordPress子主题 */ 在WordPress的wp-content/themes目录下创建新文件夹"my-custom-theme",并创建style.css文件,添加上述代码。然后创建functions.php文件,用于添加功能代码。 1.2 设置开发环境 确保你的开发环境满足以下要求: WordPress 5.0或更高版本 PHP 7.4或更高版本 支持JavaScript的现代浏览器 代码编辑器(如VS Code、Sublime Text等) 1.3 备份原始文件 在进行任何代码修改前,请务必备份以下文件: 当前主题的functions.php 当前主题的style.css 当前主题的JavaScript文件 第二部分:实现暗黑模式切换功能 2.1 理解暗黑模式的实现原理 暗黑模式的核心是通过CSS变量或类名切换来改变网站的颜色方案。我们将采用以下两种方法结合的方式: CSS变量方法:定义两套颜色变量(亮色和暗色) 类名切换方法:通过JavaScript在body标签上添加或移除"dark-mode"类 2.2 创建CSS颜色变量系统 在子主题的style.css文件中,添加CSS变量定义: :root { /* 亮色主题变量 */ --primary-color: #3498db; --secondary-color: #2ecc71; --background-color: #ffffff; --text-color: #333333; --header-bg: #f8f9fa; --border-color: #e0e0e0; --card-bg: #ffffff; --shadow-color: rgba(0, 0, 0, 0.1); } .dark-mode { /* 暗色主题变量 */ --primary-color: #5dade2; --secondary-color: #58d68d; --background-color: #121212; --text-color: #e0e0e0; --header-bg: #1e1e1e; --border-color: #333333; --card-bg: #1e1e1e; --shadow-color: rgba(0, 0, 0, 0.3); } /* 应用CSS变量到网站元素 */ body { background-color: var(--background-color); color: var(--text-color); transition: background-color 0.3s ease, color 0.3s ease; } header { background-color: var(--header-bg); border-bottom: 1px solid var(--border-color); } .card { background-color: var(--card-bg); box-shadow: 0 2px 10px var(--shadow-color); border-radius: 8px; padding: 20px; margin-bottom: 20px; } 2.3 创建暗黑模式切换开关 在主题的合适位置(通常是页眉或页脚)添加暗黑模式切换按钮。首先,在functions.php中添加切换按钮的HTML代码: function add_dark_mode_switch() { echo '<button id="dark-mode-toggle" class="dark-mode-toggle" aria-label="切换暗黑模式"> <span class="toggle-icon light-icon">☀️</span> <span class="toggle-icon dark-icon">🌙</span> </button>'; } add_action('wp_body_open', 'add_dark_mode_switch'); 2.4 添加切换功能的JavaScript 创建新的JavaScript文件(如dark-mode.js)并添加到主题中: // dark-mode.js document.addEventListener('DOMContentLoaded', function() { const darkModeToggle = document.getElementById('dark-mode-toggle'); const body = document.body; // 检查用户之前的偏好设置 const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); const savedTheme = localStorage.getItem('theme'); // 初始化主题 function initTheme() { if (savedTheme === 'dark' || (!savedTheme && prefersDarkScheme.matches)) { body.classList.add('dark-mode'); updateToggleButton(true); } else { body.classList.remove('dark-mode'); updateToggleButton(false); } } // 更新切换按钮状态 function updateToggleButton(isDarkMode) { if (isDarkMode) { darkModeToggle.setAttribute('aria-label', '切换到亮色模式'); darkModeToggle.classList.add('active'); } else { darkModeToggle.setAttribute('aria-label', '切换到暗黑模式'); darkModeToggle.classList.remove('active'); } } // 切换主题 function toggleTheme() { if (body.classList.contains('dark-mode')) { body.classList.remove('dark-mode'); localStorage.setItem('theme', 'light'); updateToggleButton(false); } else { body.classList.add('dark-mode'); localStorage.setItem('theme', 'dark'); updateToggleButton(true); } } // 监听系统主题变化 prefersDarkScheme.addEventListener('change', function(e) { if (!localStorage.getItem('theme')) { if (e.matches) { body.classList.add('dark-mode'); updateToggleButton(true); } else { body.classList.remove('dark-mode'); updateToggleButton(false); } } }); // 绑定点击事件 darkModeToggle.addEventListener('click', toggleTheme); // 初始化 initTheme(); }); 在functions.php中注册并加载这个JavaScript文件: function enqueue_dark_mode_scripts() { wp_enqueue_script( 'dark-mode-script', get_stylesheet_directory_uri() . '/js/dark-mode.js', array(), '1.0.0', true ); } add_action('wp_enqueue_scripts', 'enqueue_dark_mode_scripts'); 2.5 添加切换按钮样式 为暗黑模式切换按钮添加CSS样式: .dark-mode-toggle { position: fixed; bottom: 30px; right: 30px; width: 60px; height: 60px; border-radius: 50%; background-color: var(--primary-color); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px var(--shadow-color); z-index: 9999; transition: all 0.3s ease; } .dark-mode-toggle:hover { transform: scale(1.1); box-shadow: 0 6px 16px var(--shadow-color); } .toggle-icon { font-size: 24px; position: absolute; transition: opacity 0.3s ease, transform 0.3s ease; } .dark-icon { opacity: 0; transform: rotate(90deg); } .light-icon { opacity: 1; transform: rotate(0); } .dark-mode-toggle.active .dark-icon { opacity: 1; transform: rotate(0); } .dark-mode-toggle.active .light-icon { opacity: 0; transform: rotate(-90deg); } 第三部分:创建个性化主题工具面板 3.1 设计个性化工具面板界面 我们将创建一个滑出式面板,用户可以通过它调整网站的各种视觉设置。首先,在functions.php中添加面板HTML结构: function add_theme_customizer_panel() { ?> <div id="theme-customizer" class="theme-customizer"> <button id="customizer-toggle" class="customizer-toggle" aria-label="打开主题定制面板"> <span class="customizer-icon">⚙️</span> </button> <div class="customizer-panel"> <div class="customizer-header"> <h3>主题定制</h3> <button id="close-customizer" class="close-customizer" aria-label="关闭面板">×</button> </div> <div class="customizer-body"> <div class="customizer-section"> <h4>颜色主题</h4> <div class="color-option"> <label for="primary-color">主色调</label> <input type="color" id="primary-color" value="#3498db"> </div> <div class="color-option"> <label for="secondary-color">辅助色</label> <input type="color" id="secondary-color" value="#2ecc71"> </div> <div class="color-option"> <label for="background-color">背景色</label> <input type="color" id="background-color" value="#ffffff"> </div> </div> <div class="customizer-section"> <h4>字体设置</h4> <div class="font-option"> <label for="font-family">字体家族</label> <select id="font-family"> <option value="'Helvetica Neue', Arial, sans-serif">系统默认</option> <option value="'Roboto', sans-serif">Roboto</option> <option value="'Open Sans', sans-serif">Open Sans</option> <option value="'Montserrat', sans-serif">Montserrat</option> <option value="'Georgia', serif">Georgia</option> </select> </div> <div class="font-option"> <label for="font-size">基础字号</label> <input type="range" id="font-size" min="12" max="20" value="16"> <span id="font-size-value">16px</span> </div> </div> <div class="customizer-section"> <h4>布局设置</h4> <div class="layout-option"> <label for="layout-width">内容宽度</label> <input type="range" id="layout-width" min="800" max="1400" value="1200"> <span id="layout-width-value">1200px</span> </div> <div class="layout-option"> <label> <input type="checkbox" id="rounded-corners" checked> 圆角元素 </label> </div> </div> <div class="customizer-actions"> <button id="reset-customizations" class="button-secondary">重置设置</button> <button id="save-customizations" class="button-primary">保存设置</button> </div> </div> </div> </div> <?php } add_action('wp_footer', 'add_theme_customizer_panel'); 3.2 添加个性化工具面板样式 为个性化工具面板添加CSS样式: .theme-customizer { position: fixed; top: 0; right: 0; z-index: 10000; } .customizer-toggle { position: fixed; top: 50%; right: 0; transform: translateY(-50%); background-color: var(--primary-color); color: white; border: none; border-radius: 5px 0 0 5px; padding: 15px 10px; cursor: pointer; font-size: 20px; transition: all 0.3s ease; box-shadow: -2px 0 10px var(--shadow-color); } .customizer-toggle:hover { padding-right: 15px; } .customizer-panel { position: fixed; top: 0; right: -400px; width: 380px; height: 100vh; background-color: var(--card-bg); box-shadow: -5px 0 15px var(--shadow-color); transition: right 0.3s ease; overflow-y: auto; } .customizer-panel.open { right: 0; } .customizer-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid var(--border-color); } .customizer-header h3 { margin: 0; color: var(--text-color); } .close-customizer { background: none; border: none; font-size: 28px; cursor: pointer; color: var(--text-color); line-height: 1; } .customizer-body { padding: 20px; } .customizer-section { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid var(--border-color); } .customizer-section h4 { margin-top: 0; margin-bottom: 15px; color: var(--text-color); } .color-option, .font-option, .layout-option { margin-bottom: 15px; display: flex; align-items: center; justify-content: space-between; } .color-option label, .font-option label, .layout-option label { color: var(--text-color); margin-right: 15px; } .color-option input[type="color"] { width: 50px; height: 40px; border: 2px solid var(--border-color); border-radius: 4px; cursor: pointer; } .font-option select, .font-option input[type="range"], .layout-option input[type="range"] { flex-grow: 1; max-width: 200px; } .customizer-actions { display: flex; justify-content: space-between; margin-top: 30px; } .button-primary, .button-secondary { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: all 0.2s ease; } .button-primary { background-color: var(--primary-color); color: white; } .button-secondary { background-color: var(--border-color); color: var(--text-color); } .button-primary:hover, .button-secondary:hover { transform: translateY(-2px); box-shadow: 0 4px 8px var(--shadow-color); } 3.3 实现个性化设置功能 创建customizer.js文件,实现个性化设置功能: // customizer.js document.addEventListener('DOMContentLoaded', function() { const customizerToggle = document.getElementById('customizer-toggle'); const closeCustomizer = document.getElementById('close-customizer'); const customizerPanel = document.querySelector('.customizer-panel'); const saveButton = document.getElementById('save-customizations'); const resetButton = document.getElementById('reset-customizations'); // 获取所有可定制的选项 const primaryColorInput = document.getElementById('primary-color'); const secondaryColorInput = document.getElementById('secondary-color'); const backgroundColorInput = document.getElementById('background-color'); const fontFamilySelect = document.getElementById('font-family'); const fontSizeInput = document.getElementById('font-size'); const fontSizeValue = document.getElementById('font-size-value'); const layoutWidthInput = document.getElementById('layout-width'); const layoutWidthValue = document.getElementById('layout-width-value'); const roundedCornersCheckbox = document.getElementById('rounded-corners'); // 初始化面板状态 function initCustomizer() { // 从localStorage加载保存的设置 const savedSettings = JSON.parse(localStorage.getItem('themeSettings')) || {}; // 设置输入控件的值 if (savedSettings.primaryColor) { primaryColorInput.value = savedSettings.primaryColor; updateCSSVariable('--primary-color', savedSettings.primaryColor); } if (savedSettings.secondaryColor) { secondaryColorInput.value = savedSettings.secondaryColor; updateCSSVariable('--secondary-color', savedSettings.secondaryColor); } if (savedSettings.backgroundColor) { backgroundColorInput.value = savedSettings.backgroundColor; updateCSSVariable('--background-color', savedSettings.backgroundColor); } if (savedSettings.fontFamily) { fontFamilySelect.value = savedSettings.fontFamily; updateCSSVariable('--font-family', savedSettings.fontFamily); } if (savedSettings.fontSize) { fontSizeInput.value = savedSettings.fontSize; fontSizeValue.textContent = savedSettings.fontSize + 'px'; updateCSSVariable('--base-font-size', savedSettings.fontSize + 'px'); } if (savedSettings.layoutWidth) { layoutWidthInput.value = savedSettings.layoutWidth; layoutWidthValue.textContent = savedSettings.layoutWidth + 'px'; updateCSSVariable('--container-width', savedSettings.layoutWidth + 'px'); } if (savedSettings.roundedCorners !== undefined) { roundedCornersCheckbox.checked = savedSettings.roundedCorners; toggleRoundedCorners(savedSettings.roundedCorners); } } // 更新CSS变量 function updateCSSVariable(variable, value) { document.documentElement.style.setProperty(variable, value); } // 切换圆角样式 function toggleRoundedCorners(enabled) { document.body.classList.add('rounded-elements'); } else { document.body.classList.remove('rounded-elements'); } } // 保存设置到localStorage function saveSettings() { const settings = { primaryColor: primaryColorInput.value, secondaryColor: secondaryColorInput.value, backgroundColor: backgroundColorInput.value, fontFamily: fontFamilySelect.value, fontSize: parseInt(fontSizeInput.value), layoutWidth: parseInt(layoutWidthInput.value), roundedCorners: roundedCornersCheckbox.checked }; localStorage.setItem('themeSettings', JSON.stringify(settings)); // 显示保存成功的提示 showNotification('设置已保存!', 'success'); } // 重置所有设置 function resetSettings() { // 清除localStorage中的设置 localStorage.removeItem('themeSettings'); // 重置CSS变量为默认值 const root = document.documentElement; root.style.removeProperty('--primary-color'); root.style.removeProperty('--secondary-color'); root.style.removeProperty('--background-color'); root.style.removeProperty('--font-family'); root.style.removeProperty('--base-font-size'); root.style.removeProperty('--container-width'); // 重置输入控件 primaryColorInput.value = '#3498db'; secondaryColorInput.value = '#2ecc71'; backgroundColorInput.value = '#ffffff'; fontFamilySelect.value = "'Helvetica Neue', Arial, sans-serif"; fontSizeInput.value = 16; fontSizeValue.textContent = '16px'; layoutWidthInput.value = 1200; layoutWidthValue.textContent = '1200px'; roundedCornersCheckbox.checked = true; toggleRoundedCorners(true); // 显示重置成功的提示 showNotification('设置已重置为默认值!', 'info'); } // 显示通知 function showNotification(message, type) { // 移除已存在的通知 const existingNotification = document.querySelector('.customizer-notification'); if (existingNotification) { existingNotification.remove(); } // 创建新通知 const notification = document.createElement('div'); notification.className = `customizer-notification ${type}`; notification.textContent = message; // 添加到页面 document.body.appendChild(notification); // 3秒后自动移除 setTimeout(() => { notification.classList.add('fade-out'); setTimeout(() => notification.remove(), 300); }, 3000); } // 事件监听器 customizerToggle.addEventListener('click', () => { customizerPanel.classList.add('open'); }); closeCustomizer.addEventListener('click', () => { customizerPanel.classList.remove('open'); }); // 实时更新颜色预览 primaryColorInput.addEventListener('input', (e) => { updateCSSVariable('--primary-color', e.target.value); }); secondaryColorInput.addEventListener('input', (e) => { updateCSSVariable('--secondary-color', e.target.value); }); backgroundColorInput.addEventListener('input', (e) => { updateCSSVariable('--background-color', e.target.value); }); // 实时更新字体设置 fontFamilySelect.addEventListener('change', (e) => { updateCSSVariable('--font-family', e.target.value); }); fontSizeInput.addEventListener('input', (e) => { const value = e.target.value; fontSizeValue.textContent = value + 'px'; updateCSSVariable('--base-font-size', value + 'px'); }); // 实时更新布局设置 layoutWidthInput.addEventListener('input', (e) => { const value = e.target.value; layoutWidthValue.textContent = value + 'px'; updateCSSVariable('--container-width', value + 'px'); }); roundedCornersCheckbox.addEventListener('change', (e) => { toggleRoundedCorners(e.target.checked); }); // 保存和重置按钮 saveButton.addEventListener('click', saveSettings); resetButton.addEventListener('click', resetSettings); // 初始化定制器 initCustomizer(); }); 在functions.php中注册并加载这个JavaScript文件: function enqueue_customizer_scripts() { wp_enqueue_script( 'customizer-script', get_stylesheet_directory_uri() . '/js/customizer.js', array(), '1.0.0', true ); } add_action('wp_enqueue_scripts', 'enqueue_customizer_scripts'); 3.4 添加通知样式 为个性化工具面板的通知功能添加CSS样式: .customizer-notification { position: fixed; bottom: 100px; right: 30px; padding: 15px 25px; border-radius: 8px; color: white; font-weight: bold; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 10001; animation: slideIn 0.3s ease; max-width: 300px; } .customizer-notification.success { background-color: #2ecc71; } .customizer-notification.info { background-color: #3498db; } .customizer-notification.error { background-color: #e74c3c; } .customizer-notification.fade-out { animation: fadeOut 0.3s ease forwards; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes fadeOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } 第四部分:集成常用互联网小工具 4.1 添加阅读进度条 阅读进度条是许多现代网站的实用功能,可以显示用户在页面上的阅读进度。 在functions.php中添加阅读进度条: function add_reading_progress_bar() { echo '<div id="reading-progress" class="reading-progress"> <div class="reading-progress-bar"></div> </div>'; } add_action('wp_body_open', 'add_reading_progress_bar'); 添加阅读进度条样式: .reading-progress { position: fixed; top: 0; left: 0; width: 100%; height: 4px; background-color: transparent; z-index: 9998; } .reading-progress-bar { height: 100%; background-color: var(--primary-color); width: 0%; transition: width 0.2s ease; } 添加阅读进度条JavaScript功能: // reading-progress.js document.addEventListener('DOMContentLoaded', function() { const progressBar = document.querySelector('.reading-progress-bar'); if (!progressBar) return; function updateReadingProgress() { const windowHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight - windowHeight; const scrolled = window.scrollY; const progress = (scrolled / documentHeight) * 100; progressBar.style.width = progress + '%'; } // 监听滚动事件 window.addEventListener('scroll', updateReadingProgress); // 初始化 updateReadingProgress(); }); 4.2 添加回到顶部按钮 回到顶部按钮是长页面中非常实用的功能。 在functions.php中添加回到顶部按钮: function add_back_to_top_button() { echo '<button id="back-to-top" class="back-to-top" aria-label="回到顶部">↑</button>'; } add_action('wp_footer', 'add_back_to_top_button'); 添加回到顶部按钮样式: .back-to-top { position: fixed; bottom: 100px; right: 30px; width: 50px; height: 50px; border-radius: 50%; background-color: var(--primary-color); color: white; border: none; cursor: pointer; display: none; align-items: center; justify-content: center; font-size: 20px; box-shadow: 0 4px 12px var(--shadow-color); z-index: 9997; transition: all 0.3s ease; } .back-to-top.visible { display: flex; } .back-to-top:hover { transform: translateY(-5px); box-shadow: 0 6px 16px var(--shadow-color); } 添加回到顶部按钮JavaScript功能: // back-to-top.js document.addEventListener('DOMContentLoaded', function() { const backToTopButton = document.getElementById('back-to-top'); if (!backToTopButton) return; function toggleBackToTopButton() { if (window.scrollY > 300) { backToTopButton.classList.add('visible'); } else { backToTopButton.classList.remove('visible'); } } function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } // 监听滚动事件 window.addEventListener('scroll', toggleBackToTopButton); // 点击事件 backToTopButton.addEventListener('click', scrollToTop); // 初始化 toggleBackToTopButton(); }); 4.3 添加代码高亮功能 对于技术博客或教程网站,代码高亮是必不可少的功能。 首先,在functions.php中集成Prism.js代码高亮库: function enqueue_prism_assets() { // Prism.js核心CSS wp_enqueue_style( 'prism-css', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css', array(), '1.29.0' ); // Prism.js核心JS wp_enqueue_script( 'prism-js', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js', array(), '1.29.0', true ); // 添加常用语言支持 wp_enqueue_script( 'prism-js-php', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-php.min.js', array('prism-js'), '1.29.0', true ); wp_enqueue_script( 'prism-js-javascript', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js', array('prism-js'), '1.29.0', true ); wp_enqueue_script( 'prism-js-css', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js', array('prism-js'), '1.29.0', true ); } add_action('wp_enqueue_scripts', 'enqueue_prism_assets'); 4.4 添加复制代码按钮 为代码块添加复制功能,提升用户体验。 添加复制代码按钮的JavaScript功能: // copy-code.js document.addEventListener('DOMContentLoaded', function() { // 为所有代码块添加复制按钮 document.querySelectorAll('pre code').forEach(function(codeBlock) { // 创建复制按钮 const copyButton = document.createElement('button'); copyButton.className = 'copy-code-button'; copyButton.textContent = '复制'; copyButton.setAttribute('aria-label', '复制代码'); // 将按钮添加到代码块容器 const pre = codeBlock.parentNode; if (pre.tagName === 'PRE') { pre.style.position = 'relative'; pre.appendChild(copyButton); } // 复制功能 copyButton.addEventListener('click', function() { const textToCopy = codeBlock.textContent; navigator.clipboard.writeText(textToCopy).then(function() { // 复制成功反馈 copyButton.textContent = '已复制!'; copyButton.classList.add('copied'); setTimeout(function() { copyButton.textContent = '复制'; copyButton.classList.remove('copied'); }, 2000); }).catch(function(err) { console.error('复制失败: ', err); copyButton.textContent = '复制失败'; }); }); }); }); 添加复制按钮样式: .copy-code-button { position: absolute; top: 10px; right: 10px; padding: 5px 10px; background-color: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; } pre:hover .copy-code-button { opacity: 1; } .copy-code-button.copied { background-color: #2ecc71; } 第五部分:优化与高级功能 5.1 添加主题设置导入/导出功能 允许用户导出自己的主题设置,并在其他设备上导入。 在个性化工具面板中添加导入/导出功能: // 在customizer-body部分添加以下代码 <div class="customizer-section"> <h4>导入/导出设置</h4> <div class="import-export-option"> <button id="export-settings" class="button-secondary">导出设置</button> <button id="import-settings" class="button-secondary">导入设置</button> <input type="file" id="import-file" accept=".json" style="display: none;"> </div> <div id="export-result" class="export-result" style="display: none;"> <textarea id="export-data" readonly rows="4"></textarea> <button id="copy-export" class="button-secondary">复制JSON</button> </div> </div> 添加导入/导出功能的JavaScript: // import-export.js document.addEventListener('DOMContentLoaded', function() { const exportButton = document.getElementById('export-settings'); const importButton = document.getElementById('import-settings'); const importFileInput = document.getElementById('import-file'); const exportResult = document.getElementById('export-result'); const exportData = document.getElementById('export-data'); const copyExportButton = document.getElementById('copy-export'); // 导出设置 exportButton.addEventListener('click', function() { const settings = JSON.parse(localStorage.getItem('themeSettings')) || {}; const settingsJSON = JSON.stringify(settings, null, 2); exportData.value = settingsJSON; exportResult.style.display = 'block'; // 自动滚动到导出结果 exportResult.scrollIntoView({ behavior: 'smooth' }); }); // 复制导出数据 copyExportButton.addEventListener('click', function() { exportData.select(); document.execCommand('copy'); // 显示复制成功提示 showNotification('设置已复制到剪贴板!', 'success'); }); // 导入设置 importButton.addEventListener('click', function() { importFileInput.click(); }); importFileInput.addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(event) { try { const settings = JSON.parse(event.target.result); // 验证设置格式 if (typeof settings === 'object' && settings !== null) { // 保存设置到localStorage localStorage.setItem('themeSettings', JSON.stringify(settings)); // 应用新设置 applySettings(settings); // 显示成功提示 showNotification('设置导入成功!', 'success'); } else { throw new Error('无效的设置文件格式'); } } catch (error) { showNotification('导入失败:' + error.message, 'error'); } }; reader.readAsText(file); // 重置文件输入 importFileInput.value = ''; }); // 应用设置函数 function applySettings(settings) { // 更新所有输入控件 if (settings.primaryColor) { primaryColorInput.value = settings.primaryColor; updateCSSVariable('--primary-color', settings.primaryColor); } if (settings.secondaryColor) { secondaryColorInput.value = settings.secondaryColor; updateCSSVariable('--secondary-color', settings.secondaryColor); } if (settings.backgroundColor) { backgroundColorInput.value = settings.backgroundColor; updateCSSVariable('--background-color', settings.backgroundColor); } if (settings.fontFamily) { fontFamilySelect.value = settings.fontFamily; updateCSSVariable('--font-family', settings.fontFamily); } if (settings.fontSize) { fontSizeInput.value = settings.fontSize; fontSizeValue.textContent = settings.fontSize + 'px'; updateCSSVariable('--base-font-size', settings.fontSize + 'px'); } if (settings.layoutWidth) { layoutWidthInput.value = settings.layoutWidth; layoutWidthValue.textContent = settings.layoutWidth + 'px'; updateCSSVariable('--container-width', settings.layoutWidth + 'px'); } if (settings.roundedCorners !== undefined) { roundedCornersCheckbox.checked = settings.roundedCorners; toggleRoundedCorners(settings.roundedCorners); } } }); 5.2 添加键盘快捷键支持 为高级用户添加键盘快捷键,提升操作效率。 添加键盘快捷键功能: // keyboard-shortcuts.js document.addEventListener('DOMContentLoaded', function() { // 键盘快捷键映射 const shortcuts = { // 切换暗黑模式: Ctrl+Shift+D 'toggleDarkMode': { key: 'D', ctrlKey: true, shiftKey: true, action: function() { const darkModeToggle = document.getElementById('dark-mode-toggle'); if (darkModeToggle) darkModeToggle.click(); } }, // 打开/关闭定制面板: Ctrl+Shift+C 'toggleCustomizer': { key: 'C', ctrlKey: true,
发表评论分类: 网站建设
实战教程:为WordPress网站集成智能化的内容相似度检测与防抄袭系统 引言:内容原创性在数字时代的重要性 在当今信息爆炸的互联网环境中,内容创作已成为网站运营的核心。然而,随着内容数量的急剧增加,抄袭和内容重复问题也日益严重。对于WordPress网站管理员和内容创作者而言,保护原创内容不仅是维护品牌声誉的需要,更是提升搜索引擎排名、吸引忠实读者的关键。 传统的防抄袭方法主要依赖人工检查或基础文本比对,效率低下且难以应对海量内容。本教程将指导您通过WordPress代码二次开发,集成智能化的内容相似度检测与防抄袭系统,将您的网站提升到一个新的智能化水平。 第一部分:系统架构设计与技术选型 1.1 系统核心功能需求分析 在开始开发之前,我们需要明确系统应具备的核心功能: 实时内容检测:在文章发布时自动检测内容相似度 批量历史内容扫描:对网站现有内容进行全面检查 智能相似度算法:采用先进的文本相似度计算方法 外部资源比对:能够与互联网上的公开内容进行比对 可视化报告系统:直观展示检测结果和相似度分析 自动化处理机制:根据预设规则自动处理疑似抄袭内容 1.2 技术栈选择与原理 我们将采用以下技术方案: WordPress钩子机制:利用save_post、publish_post等动作钩子实现自动化检测 PHP文本处理库:使用PHP内置函数和扩展进行文本预处理 相似度算法:实现余弦相似度、Jaccard相似系数和编辑距离算法 外部API集成:通过第三方原创检测API增强检测能力 数据库优化:合理设计数据表结构,确保系统性能 前端展示:使用AJAX和Chart.js实现交互式报告界面 1.3 系统架构图 用户发布内容 → WordPress钩子触发 → 文本预处理 → 特征提取 → 相似度计算 → 结果评估 → 数据库存储 → 报告生成 → 用户通知 第二部分:开发环境搭建与基础配置 2.1 开发环境准备 首先,确保您的开发环境满足以下要求: WordPress 5.0或更高版本 PHP 7.3或更高版本(支持mbstring、curl扩展) MySQL 5.6或更高版本 至少100MB的可用磁盘空间(用于存储文本指纹和缓存) 2.2 创建自定义插件 我们将创建一个独立的WordPress插件来实现所有功能: 在wp-content/plugins/目录下创建新文件夹smart-content-checker 创建主插件文件smart-content-checker.php: <?php /** * Plugin Name: 智能内容相似度检测与防抄袭系统 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站提供智能化的内容相似度检测与防抄袭功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: smart-content-checker */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SCC_VERSION', '1.0.0'); define('SCC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SCC_PLUGIN_URL', plugin_dir_url(__FILE__)); define('SCC_CACHE_TIME', 3600); // 缓存时间1小时 // 初始化插件 require_once SCC_PLUGIN_DIR . 'includes/class-core.php'; require_once SCC_PLUGIN_DIR . 'includes/class-text-processor.php'; require_once SCC_PLUGIN_DIR . 'includes/class-similarity-checker.php'; require_once SCC_PLUGIN_DIR . 'includes/class-database.php'; require_once SCC_PLUGIN_DIR . 'includes/class-admin-interface.php'; // 启动插件 function scc_init_plugin() { $core = new SCC_Core(); $core->init(); } add_action('plugins_loaded', 'scc_init_plugin'); 第三部分:核心文本处理与特征提取模块 3.1 文本预处理类实现 创建includes/class-text-processor.php文件: <?php class SCC_Text_Processor { /** * 文本清洗和标准化 */ public function clean_text($text) { // 移除HTML标签 $text = strip_tags($text); // 转换所有字符为小写 $text = mb_strtolower($text, 'UTF-8'); // 移除特殊字符和标点符号,保留中文、英文和数字 $text = preg_replace('/[^p{L}p{N}s]/u', ' ', $text); // 移除多余空格 $text = preg_replace('/s+/', ' ', $text); return trim($text); } /** * 中文文本分词 * 注意:需要服务器安装中文分词扩展,这里提供简单实现 */ public function chinese_segmentation($text) { // 如果服务器安装了scws或jieba分词,可以调用相关函数 // 这里提供一个简单的按字符分割的方法(适用于基础需求) if (function_exists('scws_new')) { // 使用scws分词 $so = scws_new(); $so->set_charset('utf8'); $so->send_text($text); $words = array(); while ($tmp = $so->get_result()) { foreach ($tmp as $word) { if (strlen($word['word']) > 1) { $words[] = $word['word']; } } } $so->close(); return $words; } else { // 简单分词:按空格和标点分割 return preg_split('/s+/', $text); } } /** * 提取文本特征(词频向量) */ public function extract_features($text, $max_features = 100) { $cleaned_text = $this->clean_text($text); // 分词 $words = $this->chinese_segmentation($cleaned_text); // 计算词频 $word_freq = array_count_values($words); // 移除停用词 $word_freq = $this->remove_stopwords($word_freq); // 按词频排序并取前N个特征 arsort($word_freq); $features = array_slice($word_freq, 0, $max_features, true); return $features; } /** * 移除停用词 */ private function remove_stopwords($word_freq) { // 中文停用词列表(部分示例) $stopwords = array( '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这', '那', '他', '她', '它' ); // 英文停用词 $english_stopwords = array( 'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing' ); $all_stopwords = array_merge($stopwords, $english_stopwords); foreach ($all_stopwords as $stopword) { if (isset($word_freq[$stopword])) { unset($word_freq[$stopword]); } } return $word_freq; } /** * 生成文本指纹(Simhash算法简化版) */ public function generate_simhash($text) { $features = $this->extract_features($text, 64); $vector = array_fill(0, 64, 0); foreach ($features as $word => $weight) { $hash = crc32($word); for ($i = 0; $i < 64; $i++) { $bit = ($hash >> $i) & 1; if ($bit == 1) { $vector[$i] += $weight; } else { $vector[$i] -= $weight; } } } // 生成64位指纹 $fingerprint = 0; for ($i = 0; $i < 64; $i++) { if ($vector[$i] > 0) { $fingerprint |= (1 << $i); } } return $fingerprint; } } 3.2 相似度计算算法实现 创建includes/class-similarity-checker.php文件: <?php class SCC_Similarity_Checker { private $text_processor; public function __construct() { $this->text_processor = new SCC_Text_Processor(); } /** * 计算余弦相似度 */ public function cosine_similarity($text1, $text2) { $features1 = $this->text_processor->extract_features($text1); $features2 = $this->text_processor->extract_features($text2); // 获取所有特征的并集 $all_features = array_unique(array_merge( array_keys($features1), array_keys($features2) )); // 创建向量 $vector1 = array(); $vector2 = array(); foreach ($all_features as $feature) { $vector1[] = isset($features1[$feature]) ? $features1[$feature] : 0; $vector2[] = isset($features2[$feature]) ? $features2[$feature] : 0; } // 计算点积 $dot_product = 0; for ($i = 0; $i < count($vector1); $i++) { $dot_product += $vector1[$i] * $vector2[$i]; } // 计算模长 $magnitude1 = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $vector1))); $magnitude2 = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $vector2))); // 避免除以零 if ($magnitude1 == 0 || $magnitude2 == 0) { return 0; } return $dot_product / ($magnitude1 * $magnitude2); } /** * 计算Jaccard相似系数 */ public function jaccard_similarity($text1, $text2) { $features1 = $this->text_processor->extract_features($text1); $features2 = $this->text_processor->extract_features($text2); $set1 = array_keys($features1); $set2 = array_keys($features2); $intersection = array_intersect($set1, $set2); $union = array_unique(array_merge($set1, $set2)); if (count($union) == 0) { return 0; } return count($intersection) / count($union); } /** * 计算Simhash海明距离 */ public function simhash_distance($text1, $text2) { $hash1 = $this->text_processor->generate_simhash($text1); $hash2 = $this->text_processor->generate_simhash($text2); // 计算海明距离 $xor = $hash1 ^ $hash2; $distance = 0; while ($xor) { $distance += $xor & 1; $xor >>= 1; } return $distance; } /** * 综合相似度评估 */ public function comprehensive_similarity($text1, $text2) { $cosine = $this->cosine_similarity($text1, $text2); $jaccard = $this->jaccard_similarity($text1, $text2); $simhash_distance = $this->simhash_distance($text1, $text2); // 将Simhash距离转换为相似度(距离越小,相似度越高) $simhash_similarity = max(0, 1 - ($simhash_distance / 64)); // 加权平均 $weights = array( 'cosine' => 0.5, 'jaccard' => 0.3, 'simhash' => 0.2 ); $similarity = ($cosine * $weights['cosine']) + ($jaccard * $weights['jaccard']) + ($simhash_similarity * $weights['simhash']); return round($similarity, 4); } /** * 与外部API集成进行深度检测 */ public function external_api_check($text, $api_type = 'copyscape') { // 这里以Copyscape API为例 $api_key = get_option('scc_copyscape_api_key', ''); if (empty($api_key)) { return array( 'success' => false, 'message' => 'API密钥未配置' ); } $encoded_text = urlencode($text); $url = "https://www.copyscape.com/api/?o=search&k={$api_key}&t={$encoded_text}&f=xml"; $response = wp_remote_get($url, array( 'timeout' => 30, 'sslverify' => false )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $body = wp_remote_retrieve_body($response); // 解析XML响应 $xml = simplexml_load_string($body); if (!$xml) { return array( 'success' => false, 'message' => 'API响应解析失败' ); } $results = array(); if (isset($xml->result)) { foreach ($xml->result as $result) { $results[] = array( 'url' => (string)$result->url, 'title' => (string)$result->title, 'similarity' => (float)$result->minwordsmatched / 100 ); } } return array( 'success' => true, 'results' => $results ); } } 第四部分:数据库设计与数据管理 4.1 数据库表结构设计 创建includes/class-database.php文件: <?php class SCC_Database { /** * 创建必要的数据库表 */ public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'scc_content_fingerprints'; $results_table = $wpdb->prefix . 'scc_scan_results'; // 内容指纹表 $sql1 = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, fingerprint_64 bigint(20) UNSIGNED NOT NULL, fingerprint_128 varchar(255) DEFAULT NULL, content_hash varchar(64) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY post_id (post_id), KEY fingerprint_64 (fingerprint_64), KEY content_hash (content_hash) ) $charset_collate;"; // 扫描结果表 $sql2 = "CREATE TABLE IF NOT EXISTS $results_table ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, scan_type varchar(50) NOT NULL, similarity_score float NOT NULL, matched_urls text, details text, scan_date datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'pending', PRIMARY KEY (id), KEY post_id (post_id), KEY scan_date (scan_date), KEY status (status) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); } /** * 保存内容指纹 */ public function save_fingerprint($post_id, $fingerprint_64, $content_hash, $fingerprint_128 = null) { global $wpdb; $table_name = $wpdb->prefix . 'scc_content_fingerprints'; // 检查是否已存在 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE post_id = %d", $post_id )); if ($existing) { // 更新现有记录 $wpdb->update( $table_name, array( 'fingerprint_64' => $fingerprint_64, 'fingerprint_128' => $fingerprint_128, 'content_hash' => $content_hash, 'updated_at' => current_time('mysql') ), ), array('%d', '%s', '%s', '%s'), array('%d') ); } else { // 插入新记录 $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'fingerprint_64' => $fingerprint_64, 'fingerprint_128' => $fingerprint_128, 'content_hash' => $content_hash ), array('%d', '%d', '%s', '%s') ); } return $wpdb->insert_id; } /** * 查找相似内容 */ public function find_similar_content($fingerprint_64, $threshold = 5, $exclude_post_id = 0) { global $wpdb; $table_name = $wpdb->prefix . 'scc_content_fingerprints'; // 查找海明距离小于阈值的指纹 $query = $wpdb->prepare( "SELECT p1.post_id, BIT_COUNT(p1.fingerprint_64 ^ %d) as hamming_distance, p.post_title, p.post_date FROM $table_name p1 INNER JOIN {$wpdb->posts} p ON p1.post_id = p.ID WHERE BIT_COUNT(p1.fingerprint_64 ^ %d) <= %d AND p1.post_id != %d AND p.post_status = 'publish' ORDER BY hamming_distance ASC LIMIT 10", $fingerprint_64, $fingerprint_64, $threshold, $exclude_post_id ); return $wpdb->get_results($query, ARRAY_A); } /** * 保存扫描结果 */ public function save_scan_result($post_id, $scan_type, $similarity_score, $matched_urls = '', $details = '') { global $wpdb; $table_name = $wpdb->prefix . 'scc_scan_results'; $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'scan_type' => $scan_type, 'similarity_score' => $similarity_score, 'matched_urls' => is_array($matched_urls) ? json_encode($matched_urls) : $matched_urls, 'details' => is_array($details) ? json_encode($details) : $details, 'status' => $similarity_score > 0.7 ? 'high_risk' : ($similarity_score > 0.3 ? 'medium_risk' : 'low_risk') ), array('%d', '%s', '%f', '%s', '%s', '%s') ); return $wpdb->insert_id; } /** * 获取文章的扫描历史 */ public function get_scan_history($post_id, $limit = 10) { global $wpdb; $table_name = $wpdb->prefix . 'scc_scan_results'; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE post_id = %d ORDER BY scan_date DESC LIMIT %d", $post_id, $limit ), ARRAY_A); } /** * 获取高风险内容统计 */ public function get_risk_statistics($days = 30) { global $wpdb; $table_name = $wpdb->prefix . 'scc_scan_results'; $query = $wpdb->prepare( "SELECT COUNT(CASE WHEN status = 'high_risk' THEN 1 END) as high_risk_count, COUNT(CASE WHEN status = 'medium_risk' THEN 1 END) as medium_risk_count, COUNT(CASE WHEN status = 'low_risk' THEN 1 END) as low_risk_count, DATE(scan_date) as scan_date FROM $table_name WHERE scan_date >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY DATE(scan_date) ORDER BY scan_date DESC", $days ); return $wpdb->get_results($query, ARRAY_A); } } 第五部分:WordPress集成与自动化检测 5.1 核心插件类实现 创建includes/class-core.php文件: <?php class SCC_Core { private $db; private $text_processor; private $similarity_checker; public function __construct() { $this->db = new SCC_Database(); $this->text_processor = new SCC_Text_Processor(); $this->similarity_checker = new SCC_Similarity_Checker(); } /** * 初始化插件 */ public function init() { // 创建数据库表 register_activation_hook(__FILE__, array($this->db, 'create_tables')); // 添加WordPress钩子 add_action('save_post', array($this, 'on_post_save'), 10, 3); add_action('publish_post', array($this, 'on_post_publish'), 10, 2); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 添加AJAX处理 add_action('wp_ajax_scc_manual_scan', array($this, 'ajax_manual_scan')); add_action('wp_ajax_scc_bulk_scan', array($this, 'ajax_bulk_scan')); // 添加文章列表列 add_filter('manage_posts_columns', array($this, 'add_post_columns')); add_action('manage_posts_custom_column', array($this, 'render_post_columns'), 10, 2); // 添加文章编辑页面元框 add_action('add_meta_boxes', array($this, 'add_meta_boxes')); } /** * 文章保存时触发 */ public function on_post_save($post_id, $post, $update) { // 跳过自动保存和修订 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (wp_is_post_revision($post_id)) { return; } // 只处理特定文章类型 $allowed_types = array('post', 'page'); if (!in_array($post->post_type, $allowed_types)) { return; } // 获取文章内容 $content = $post->post_content; // 生成内容哈希 $content_hash = md5($content); // 生成指纹 $fingerprint_64 = $this->text_processor->generate_simhash($content); // 保存指纹 $this->db->save_fingerprint($post_id, $fingerprint_64, $content_hash); // 如果是更新操作,检查与历史版本的相似度 if ($update) { $this->check_self_similarity($post_id, $content); } } /** * 文章发布时触发 */ public function on_post_publish($post_id, $post) { // 执行相似度检测 $this->perform_similarity_check($post_id, $post->post_content); } /** * 执行相似度检测 */ private function perform_similarity_check($post_id, $content) { // 1. 内部相似度检测 $fingerprint_64 = $this->text_processor->generate_simhash($content); $similar_posts = $this->db->find_similar_content($fingerprint_64, 5, $post_id); $internal_similarity = 0; $matched_posts = array(); if (!empty($similar_posts)) { foreach ($similar_posts as $similar_post) { $similar_post_content = get_post_field('post_content', $similar_post['post_id']); $similarity = $this->similarity_checker->comprehensive_similarity($content, $similar_post_content); if ($similarity > $internal_similarity) { $internal_similarity = $similarity; } if ($similarity > 0.3) { $matched_posts[] = array( 'post_id' => $similar_post['post_id'], 'title' => $similar_post['post_title'], 'similarity' => $similarity, 'url' => get_permalink($similar_post['post_id']) ); } } } // 2. 外部API检测(可选) $external_results = array(); if (get_option('scc_enable_external_check', false)) { $external_check = $this->similarity_checker->external_api_check($content); if ($external_check['success']) { $external_results = $external_check['results']; } } // 计算综合相似度 $total_similarity = $internal_similarity; if (!empty($external_results)) { $external_similarity = max(array_column($external_results, 'similarity')); $total_similarity = max($total_similarity, $external_similarity); } // 保存结果 $this->db->save_scan_result( $post_id, 'auto', $total_similarity, array_merge($matched_posts, $external_results), array( 'internal_similarity' => $internal_similarity, 'matched_posts' => $matched_posts, 'external_results' => $external_results ) ); // 发送通知 if ($total_similarity > get_option('scc_notification_threshold', 0.7)) { $this->send_notification($post_id, $total_similarity); } return $total_similarity; } /** * 检查与历史版本的相似度 */ private function check_self_similarity($post_id, $current_content) { $revisions = wp_get_post_revisions($post_id); if (empty($revisions)) { return; } // 获取最新修订版 $latest_revision = reset($revisions); $revision_content = $latest_revision->post_content; $similarity = $this->similarity_checker->comprehensive_similarity($current_content, $revision_content); // 如果相似度低于阈值,记录重大修改 if ($similarity < 0.5) { $this->db->save_scan_result( $post_id, 'revision_check', $similarity, array(), array( 'message' => '检测到文章内容发生重大修改', 'revision_id' => $latest_revision->ID, 'similarity_with_revision' => $similarity ) ); } } /** * 发送通知 */ private function send_notification($post_id, $similarity) { $post = get_post($post_id); $author = get_userdata($post->post_author); $admin_email = get_option('admin_email'); $subject = sprintf('【内容相似度警报】文章 "%s" 检测到高相似度内容', $post->post_title); $message = sprintf( "文章标题:%sn" . "文章ID:%dn" . "作者:%sn" . "检测相似度:%.2f%%n" . "文章链接:%sn" . "编辑链接:%snn" . "请及时审核该文章内容。", $post->post_title, $post_id, $author->display_name, $similarity * 100, get_permalink($post_id), admin_url('post.php?post=' . $post_id . '&action=edit') ); // 发送给管理员 wp_mail($admin_email, $subject, $message); // 如果设置了作者通知,也发送给作者 if (get_option('scc_notify_author', false)) { wp_mail($author->user_email, $subject, $message); } } } 5.2 管理界面实现 创建includes/class-admin-interface.php文件: <?php class SCC_Admin_Interface { private $db; public function __construct() { $this->db = new SCC_Database(); } /** * 添加管理菜单 */ public function add_admin_menu() { // 主菜单 add_menu_page( '内容相似度检测', '内容检测', 'manage_options', 'scc-dashboard', array($this, 'render_dashboard_page'), 'dashicons-search', 30 ); // 子菜单 add_submenu_page( 'scc-dashboard', '批量检测', '批量检测', 'manage_options', 'scc-bulk-scan', array($this, 'render_bulk_scan_page') ); add_submenu_page( 'scc-dashboard', '检测设置', '设置', 'manage_options', 'scc-settings', array($this, 'render_settings_page') ); add_submenu_page( 'scc-dashboard', '检测报告', '报告统计', 'manage_options', 'scc-reports', array($this, 'render_reports_page') ); } /** * 渲染仪表盘页面 */ public function render_dashboard_page() { ?> <div class="wrap scc-dashboard"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <div class="scc-stats-container"> <div class="scc-stat-card"> <h3>今日检测</h3> <div class="stat-number"><?php echo $this->get_today_scan_count(); ?></div> </div> <div class="scc-stat-card"> <h3>高风险内容</h3> <div class="stat-number" style="color: #dc3232;"><?php echo $this->get_high_risk_count(); ?></div> </div> <div class="scc-stat-card"> <h3>平均相似度</h3> <div class="stat-number"><?php echo $this->get_average_similarity(); ?>%</div> </div> <div class="scc-stat-card"> <h3>已保护文章</h3> <div class="stat-number"><?php echo $this->get_protected_post_count(); ?></div> </div> </div> <div class="scc-quick-actions"> <h2>快速操作</h2> <button class="button button-primary" onclick="sccQuickScan()">快速扫描最新文章</button> <button class="button" onclick="window.location.href='?page=scc-bulk-scan'">批量检测</button> <button class="button" onclick="window.location.href='?page=scc-reports'">查看完整报告</button> </div> <div class="scc-recent-scans"> <h2>最近检测记录</h2> <?php $this->render_recent_scans_table(); ?> </div> <script> function sccQuickScan() { jQuery.post(ajaxurl, { action: 'scc_manual_scan', post_ids: 'recent', nonce: '<?php echo wp_create_nonce('scc_manual_scan'); ?>' }, function(response) { if (response.success) { alert('扫描完成!检测到 ' + response.data.high_risk + ' 篇高风险文章'); location.reload(); } else { alert('扫描失败:' + response.data); } }); } </script> <style> .scc-stats-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .scc-stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center; } .scc-stat-card h3 { margin-top: 0; color: #666; } .scc-stat-card .stat-number { font-size: 2.5em; font-weight: bold; color: #0073aa; } .scc-quick-actions { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 20px 0; } .scc-quick-actions button { margin-right: 10px; margin-bottom: 10px; } </style> </div> <?php } /** * 渲染批量检测页面 */ public function render_bulk_scan_page() { ?> <div class="wrap"> <h1>批量内容检测</h1> <div class="card"> <h2>选择检测范围</h2> <form id="scc-bulk-scan-form"> <table class="form-table"> <tr> <th scope="row">文章类型</th> <td> <select name="post_type" id="post_type"> <option value="post">文章</option> <option value="page">页面</option> <option value="all">所有内容</option>
发表评论详细指南:在WordPress中开发集成在线简历生成与职位推荐工具 摘要 本文提供了一份全面的技术指南,详细介绍了如何在WordPress平台中通过代码二次开发,创建一个集在线简历生成与智能职位推荐功能于一体的专业工具。我们将从系统架构设计开始,逐步深入到具体实现步骤,包括数据库设计、前端界面开发、核心功能实现以及系统优化等方面。本指南适合有一定WordPress开发经验的中级开发者,旨在帮助您构建一个功能完善、用户体验优秀的职业服务平台。 一、项目概述与需求分析 1.1 项目背景与目标 在当今数字化招聘市场中,求职者需要一个能够快速创建专业简历并获取个性化职位推荐的平台。而招聘方则希望找到与职位要求高度匹配的候选人。通过WordPress这一广泛使用的内容管理系统,我们可以开发一个集成这两大功能的工具,为用户提供一站式职业发展服务。 本项目的主要目标包括: 开发一个用户友好的在线简历创建和编辑系统 实现基于用户技能和经验的智能职位推荐算法 创建可自定义的简历模板系统 确保数据安全性和用户隐私保护 提供响应式设计,支持多设备访问 1.2 功能需求详细说明 简历生成模块需求: 用户注册与个人资料管理 分步骤简历创建向导(个人信息、教育背景、工作经历、技能等) 多种专业简历模板选择 实时预览功能 导出为PDF/Word格式 简历分享链接生成 职位推荐模块需求: 职位数据库管理 基于用户简历内容的智能匹配算法 个性化推荐排序 职位收藏与申请跟踪 推荐职位邮件通知 1.3 技术栈选择 核心平台:WordPress 5.8+ 开发语言:PHP 7.4+,JavaScript (ES6+) 前端框架:React/Vue.js(可选,用于复杂交互) 数据库:MySQL 5.7+ PDF生成:TCPDF或Dompdf库 缓存机制:Redis或Memcached(可选) 安全性:WordPress Nonce,数据验证与清理 二、系统架构设计 2.1 整体架构图 用户层 (前端界面) ↓ 表示层 (WordPress主题 + 自定义页面模板) ↓ 应用层 (自定义插件 + REST API) ↓ 数据层 (WordPress数据库 + 自定义表) ↓ 服务层 (第三方API集成:职位数据源) 2.2 数据库设计 2.2.1 核心数据表结构 -- 简历主表 CREATE TABLE wp_resume_builder_resumes ( resume_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, title VARCHAR(255) NOT NULL, template_id INT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_public TINYINT(1) DEFAULT 0, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); -- 简历内容表(JSON格式存储,便于扩展) CREATE TABLE wp_resume_builder_content ( content_id INT AUTO_INCREMENT PRIMARY KEY, resume_id INT NOT NULL, section_type ENUM('personal', 'education', 'experience', 'skills', 'projects', 'certifications') NOT NULL, content_data JSON NOT NULL, display_order INT DEFAULT 0, FOREIGN KEY (resume_id) REFERENCES wp_resume_builder_resumes(resume_id) ON DELETE CASCADE ); -- 职位信息表 CREATE TABLE wp_resume_builder_jobs ( job_id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, description TEXT, requirements JSON, location VARCHAR(255), salary_range VARCHAR(100), job_type ENUM('full-time', 'part-time', 'contract', 'remote', 'hybrid') DEFAULT 'full-time', posted_date DATETIME DEFAULT CURRENT_TIMESTAMP, expiry_date DATETIME, is_active TINYINT(1) DEFAULT 1 ); -- 用户技能标签表 CREATE TABLE wp_resume_builder_skills ( skill_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, skill_name VARCHAR(100) NOT NULL, proficiency_level ENUM('beginner', 'intermediate', 'advanced', 'expert') DEFAULT 'intermediate', years_of_experience DECIMAL(3,1), FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); -- 职位推荐记录表 CREATE TABLE wp_resume_builder_recommendations ( recommendation_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, job_id INT NOT NULL, match_score DECIMAL(5,2), viewed TINYINT(1) DEFAULT 0, applied TINYINT(1) DEFAULT 0, recommended_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE, FOREIGN KEY (job_id) REFERENCES wp_resume_builder_jobs(job_id) ON DELETE CASCADE ); 2.2.2 数据库优化策略 为频繁查询的字段添加索引 使用InnoDB引擎支持事务和外键 定期归档历史数据 实施查询缓存机制 2.3 插件架构设计 resume-builder-plugin/ ├── resume-builder.php # 主插件文件 ├── includes/ │ ├── class-database-handler.php # 数据库操作类 │ ├── class-resume-builder.php # 简历构建核心类 │ ├── class-job-matcher.php # 职位匹配算法类 │ ├── class-pdf-generator.php # PDF生成类 │ └── class-api-handler.php # REST API处理类 ├── admin/ │ ├── class-admin-menu.php # 管理菜单 │ ├── class-settings-page.php # 设置页面 │ └── class-job-manager.php # 职位管理界面 ├── public/ │ ├── css/ # 前端样式 │ ├── js/ # 前端脚本 │ └── templates/ # 前端模板 ├── assets/ # 静态资源 ├── templates/ # 简历模板 └── vendor/ # 第三方库 三、开发环境搭建与基础配置 3.1 本地开发环境配置 安装本地服务器环境: 使用XAMPP、MAMP或Local by Flywheel 确保PHP版本≥7.4,MySQL≥5.7 设置WordPress开发环境: # 创建插件目录 mkdir -p wp-content/plugins/resume-builder # 启用WordPress调试模式 # 在wp-config.php中添加 define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 创建主插件文件: <?php /** * Plugin Name: 简历生成与职位推荐工具 * Plugin URI: https://yourwebsite.com/ * Description: 一个集在线简历生成与智能职位推荐功能的WordPress插件 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: resume-builder */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('RB_PLUGIN_VERSION', '1.0.0'); define('RB_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('RB_PLUGIN_URL', plugin_dir_url(__FILE__)); define('RB_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 初始化插件 require_once RB_PLUGIN_PATH . 'includes/class-resume-builder-init.php'; // 激活/停用钩子 register_activation_hook(__FILE__, ['Resume_Builder_Init', 'activate']); register_deactivation_hook(__FILE__, ['Resume_Builder_Init', 'deactivate']); // 初始化插件 add_action('plugins_loaded', ['Resume_Builder_Init', 'get_instance']); 3.2 数据库表创建 创建数据库初始化类: // includes/class-database-handler.php class Resume_Builder_Database { private static $instance = null; private $charset_collate; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { global $wpdb; $this->charset_collate = $wpdb->get_charset_collate(); } public function create_tables() { require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); $sql = []; // 简历主表 $sql[] = "CREATE TABLE {$wpdb->prefix}resume_builder_resumes ( resume_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, title VARCHAR(255) NOT NULL, template_id INT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_public TINYINT(1) DEFAULT 0, FOREIGN KEY (user_id) REFERENCES {$wpdb->prefix}users(ID) ON DELETE CASCADE ) {$this->charset_collate};"; // 其他表创建语句... foreach ($sql as $query) { dbDelta($query); } // 添加默认数据 $this->add_default_data(); } private function add_default_data() { // 添加默认简历模板 $default_templates = [ [ 'name' => '经典专业', 'description' => '简洁专业的简历模板', 'thumbnail' => RB_PLUGIN_URL . 'assets/templates/classic.png', 'file_path' => RB_PLUGIN_PATH . 'templates/classic.php' ], // 更多模板... ]; // 保存到选项表 update_option('rb_default_templates', $default_templates); } public function update_tables() { // 数据库升级逻辑 } } 四、简历生成模块开发 4.1 用户界面设计 4.1.1 创建简历构建器短代码 // includes/class-resume-builder.php class Resume_Builder_Frontend { public function init() { // 注册短代码 add_shortcode('resume_builder', [$this, 'render_resume_builder']); // 注册前端脚本和样式 add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']); } public function enqueue_frontend_assets() { // 仅在有短代码的页面加载 global $post; if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'resume_builder')) { // 样式文件 wp_enqueue_style( 'rb-frontend-style', RB_PLUGIN_URL . 'public/css/frontend.css', [], RB_PLUGIN_VERSION ); // JavaScript文件 wp_enqueue_script( 'rb-frontend-script', RB_PLUGIN_URL . 'public/js/frontend.js', ['jquery', 'wp-api'], RB_PLUGIN_VERSION, true ); // 本地化脚本 wp_localize_script('rb-frontend-script', 'rb_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('rb_ajax_nonce'), 'user_id' => get_current_user_id(), 'i18n' => [ 'save_success' => __('保存成功', 'resume-builder'), 'save_error' => __('保存失败,请重试', 'resume-builder'), // 更多翻译字符串... ] ]); } } public function render_resume_builder($atts) { // 检查用户是否登录 if (!is_user_logged_in()) { return $this->render_login_prompt(); } ob_start(); ?> <div id="resume-builder-app" class="resume-builder-container"> <!-- 简历构建器界面 --> <div class="rb-stepper"> <!-- 步骤指示器 --> <div class="stepper-header"> <div class="step active" data-step="1">个人信息</div> <div class="step" data-step="2">教育背景</div> <div class="step" data-step="3">工作经历</div> <div class="step" data-step="4">技能专长</div> <div class="step" data-step="5">预览与导出</div> </div> <!-- 步骤内容 --> <div class="stepper-content"> <!-- 步骤1: 个人信息 --> <div class="step-content active" id="step-1"> <h3>个人信息</h3> <form id="personal-info-form"> <div class="form-group"> <label for="full_name">全名</label> <input type="text" id="full_name" name="full_name" required> </div> <!-- 更多表单字段... --> </form> </div> <!-- 其他步骤内容... --> </div> <!-- 导航按钮 --> <div class="stepper-nav"> <button class="btn btn-secondary" id="prev-step">上一步</button> <button class="btn btn-primary" id="next-step">下一步</button> <button class="btn btn-success" id="save-resume">保存简历</button> </div> </div> <!-- 实时预览面板 --> <div class="resume-preview"> <h3>简历预览</h3> <div id="resume-preview-content"> <!-- 通过JavaScript动态加载预览 --> </div> <div class="preview-actions"> <button class="btn btn-outline" id="change-template">更换模板</button> <button class="btn btn-primary" id="export-pdf">导出PDF</button> <button class="btn btn-secondary" id="share-resume">分享链接</button> </div> </div> </div> <?php return ob_get_clean(); } private function render_login_prompt() { ob_start(); ?> <div class="rb-login-prompt"> <h3>请登录以创建简历</h3> <p>登录后您可以创建、编辑和管理您的专业简历</p> <div class="login-actions"> <a href="<?php echo wp_login_url(get_permalink()); ?>" class="btn btn-primary"> 登录 </a> <a href="<?php echo wp_registration_url(); ?>" class="btn btn-secondary"> 注册 </a> </div> </div> <?php return ob_get_clean(); } } 4.1.2 前端JavaScript交互 // public/js/frontend.js (function($) { 'use strict'; class ResumeBuilder { constructor() { this.currentStep = 1; this.totalSteps = 5; this.resumeData = {}; this.init(); } init() { this.bindEvents(); this.loadUserData(); } bindEvents() { // 步骤导航 $('#next-step').on('click', () => this.nextStep()); $('#prev-step').on('click', () => this.prevStep()); // 表单保存 $('.step-content form').on('change', 'input, textarea, select', (e) => { this.saveStepData($(e.target).closest('form')); }); // 导出功能 $('#export-pdf').on('click', () => this.exportToPDF()); $('#share-resume').on('click', () => this.generateShareLink()); // 模板切换 $('#change-template').on('click', () => this.showTemplateSelector()); } nextStep() { if (this.currentStep < this.totalSteps) { this.validateCurrentStep(); this.currentStep++; this.updateStepper(); } } prevStep() { if (this.currentStep > 1) { this.currentStep--; this.updateStepper(); } } updateStepper() { // 更新步骤指示器 $('.step').removeClass('active'); $(`.step[data-step="${this.currentStep}"]`).addClass('active'); // 更新内容显示 $('.step-content').removeClass('active'); $(`#step-${this.currentStep}`).addClass('active'); // 更新按钮状态 $('#prev-step').toggle(this.currentStep > 1); $('#next-step').toggle(this.currentStep < this.totalSteps); $('#save-resume').toggle(this.currentStep === this.totalSteps); // 更新预览 this.updatePreview(); } saveStepData(form) { const formData = new FormData(form[0]); const stepData = {}; for (let [key, value] of formData.entries()) { stepData[key] = value; } resumeData[step${this.currentStep}] = stepData; // 自动保存到服务器 this.autoSaveToServer(); } autoSaveToServer() { $.ajax({ url: rb_ajax.ajax_url, type: 'POST', data: { action: 'save_resume_data', nonce: rb_ajax.nonce, user_id: rb_ajax.user_id, resume_data: this.resumeData }, success: (response) => { if (response.success) { console.log('自动保存成功'); } } }); } updatePreview() { // 使用AJAX获取HTML预览 $.ajax({ url: rb_ajax.ajax_url, type: 'POST', data: { action: 'get_resume_preview', nonce: rb_ajax.nonce, resume_data: this.resumeData, template_id: this.currentTemplate }, success: (response) => { if (response.success) { $('#resume-preview-content').html(response.data.preview); } } }); } exportToPDF() { // 生成PDF window.open( `${rb_ajax.ajax_url}?action=generate_pdf&nonce=${rb_ajax.nonce}&resume_id=${this.currentResumeId}`, '_blank' ); } generateShareLink() { $.ajax({ url: rb_ajax.ajax_url, type: 'POST', data: { action: 'generate_share_link', nonce: rb_ajax.nonce, resume_id: this.currentResumeId }, success: (response) => { if (response.success) { this.showShareModal(response.data.share_url); } } }); } showShareModal(shareUrl) { // 创建分享模态框 const modalHtml = ` <div class="rb-modal" id="share-modal"> <div class="modal-content"> <h3>分享您的简历</h3> <div class="share-url"> <input type="text" value="${shareUrl}" readonly> <button class="btn-copy" data-clipboard-text="${shareUrl}">复制</button> </div> <div class="share-options"> <button class="share-option" data-platform="linkedin">LinkedIn</button> <button class="share-option" data-platform="email">电子邮件</button> <button class="share-option" data-platform="whatsapp">WhatsApp</button> </div> </div> </div> `; $('body').append(modalHtml); this.initClipboard(); } initClipboard() { // 初始化剪贴板功能 new ClipboardJS('.btn-copy'); } } // 初始化简历构建器 $(document).ready(function() { if ($('#resume-builder-app').length) { window.resumeBuilder = new ResumeBuilder(); } }); })(jQuery); ### 4.2 简历模板系统 #### 4.2.1 模板引擎设计 // includes/class-template-engine.phpclass Resume_Builder_Template_Engine { private $templates = []; private $current_template = null; public function __construct() { $this->load_templates(); } private function load_templates() { $template_dir = RB_PLUGIN_PATH . 'templates/'; $template_files = glob($template_dir . '*.php'); foreach ($template_files as $file) { $template_name = basename($file, '.php'); $template_data = $this->get_template_metadata($file); $this->templates[$template_name] = [ 'name' => $template_data['name'] ?? ucfirst($template_name), 'description' => $template_data['description'] ?? '', 'thumbnail' => $template_data['thumbnail'] ?? '', 'file_path' => $file, 'version' => $template_data['version'] ?? '1.0', 'category' => $template_data['category'] ?? 'general' ]; } } private function get_template_metadata($file) { $metadata = [ 'name' => '', 'description' => '', 'thumbnail' => '', 'version' => '1.0', 'category' => 'general' ]; $content = file_get_contents($file); // 解析模板头部注释 if (preg_match('//**s*n(.*?)*//s', $content, $matches)) { $comment = $matches[1]; if (preg_match('/Template Name:s*(.+)/', $comment, $name_match)) { $metadata['name'] = trim($name_match[1]); } if (preg_match('/Description:s*(.+)/', $comment, $desc_match)) { $metadata['description'] = trim($desc_match[1]); } } return $metadata; } public function render_template($template_name, $resume_data) { if (!isset($this->templates[$template_name])) { $template_name = 'classic'; // 默认模板 } $template_file = $this->templates[$template_name]['file_path']; // 提取数据 $data = $this->prepare_template_data($resume_data); // 开始输出缓冲 ob_start(); // 包含模板文件 include $template_file; // 获取缓冲内容 $html = ob_get_clean(); // 应用CSS样式 $html = $this->apply_template_styles($template_name, $html); return $html; } private function prepare_template_data($resume_data) { $data = [ 'personal' => $resume_data['step1'] ?? [], 'education' => $this->format_education_data($resume_data['step2'] ?? []), 'experience' => $this->format_experience_data($resume_data['step3'] ?? []), 'skills' => $this->format_skills_data($resume_data['step4'] ?? []), 'summary' => $resume_data['step5']['summary'] ?? '' ]; return $data; } private function format_education_data($education_data) { // 格式化教育背景数据 $formatted = []; if (isset($education_data['institutions'])) { foreach ($education_data['institutions'] as $edu) { $formatted[] = [ 'institution' => $edu['school'] ?? '', 'degree' => $edu['degree'] ?? '', 'field' => $edu['field'] ?? '', 'period' => $this->format_period($edu['start_date'] ?? '', $edu['end_date'] ?? ''), 'description' => $edu['description'] ?? '', 'gpa' => $edu['gpa'] ?? '' ]; } } // 按时间倒序排列 usort($formatted, function($a, $b) { return strtotime($b['period']) - strtotime($a['period']); }); return $formatted; } private function format_experience_data($experience_data) { // 格式化工作经历数据 $formatted = []; if (isset($experience_data['positions'])) { foreach ($experience_data['positions'] as $exp) { $formatted[] = [ 'company' => $exp['company'] ?? '', 'position' => $exp['position'] ?? '', 'period' => $this->format_period($exp['start_date'] ?? '', $exp['end_date'] ?? ''), 'description' => $exp['description'] ?? '', 'achievements' => isset($exp['achievements']) ? explode("n", $exp['achievements']) : [], 'skills_used' => isset($exp['skills']) ? explode(',', $exp['skills']) : [] ]; } } // 按时间倒序排列 usort($formatted, function($a, $b) { return strtotime($b['period']) - strtotime($a['period']); }); return $formatted; } private function format_skills_data($skills_data) { // 格式化技能数据 $formatted = [ 'technical' => [], 'professional' => [], 'languages' => [] ]; if (isset($skills_data['technical_skills'])) { foreach ($skills_data['technical_skills'] as $skill) { $formatted['technical'][] = [ 'name' => $skill['name'] ?? '', 'level' => $skill['level'] ?? 'intermediate', 'years' => $skill['years'] ?? 0 ]; } } // 类似处理其他技能类型... return $formatted; } private function format_period($start_date, $end_date) { $start = date('M Y', strtotime($start_date)); $end = $end_date ? date('M Y', strtotime($end_date)) : '至今'; return "$start - $end"; } private function apply_template_styles($template_name, $html) { $css_file = RB_PLUGIN_PATH . "templates/css/{$template_name}.css"; if (file_exists($css_file)) { $css = file_get_contents($css_file); $html = "<style>{$css}</style>" . $html; } return $html; } public function get_available_templates() { return $this->templates; } } #### 4.2.2 示例简历模板 <!-- templates/classic.php --><?php/** Template Name: 经典专业 Description: 简洁专业的简历模板,适合传统行业 Version: 1.0 Category: professional */ ?><!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo esc_html($data['personal']['full_name'] ?? '个人简历'); ?></title> </head><body class="rb-resume-template classic"> <div class="resume-container"> <!-- 头部信息 --> <header class="resume-header"> <div class="personal-info"> <h1 class="name"><?php echo esc_html($data['personal']['full_name'] ?? ''); ?></h1> <div class="contact-info"> <?php if (!empty($data['personal']['email'])): ?> <span class="contact-item"> <i class="icon-email"></i> <?php echo esc_html($data['personal']['email']); ?> </span> <?php endif; ?> <?php if (!empty($data['personal']['phone'])): ?> <span class="contact-item"> <i class="icon-phone"></i> <?php echo esc_html($data['personal']['phone']); ?> </span> <?php endif; ?> <?php if (!empty($data['personal']['location'])): ?> <span class="contact-item"> <i class="icon-location"></i> <?php echo esc_html($data['personal']['location']); ?> </span> <?php endif; ?> <?php if (!empty($data['personal']['linkedin'])): ?> <span class="contact-item"> <i class="icon-linkedin"></i> <?php echo esc_html($data['personal']['linkedin']); ?> </span> <?php endif; ?> </div> </div> <?php if (!empty($data['personal']['title'])): ?> <div class="professional-title"> <h2><?php echo esc_html($data['personal']['title']); ?></h2> </div> <?php endif; ?> </header> <!-- 个人简介 --> <?php if (!empty($data['summary'])): ?> <section class="resume-section summary"> <h3 class="section-title">个人简介</h3> <div class="section-content"> <p><?php echo nl2br(esc_html($data['summary'])); ?></p> </div> </section> <?php endif; ?> <!-- 工作经历 --> <?php if (!empty($data['experience'])): ?> <section class="resume-section experience"> <h3 class="section-title">工作经历</h3> <div class="section-content"> <?php foreach ($data['experience'] as $job): ?> <div class="experience-item"> <div class="job-header"> <h4 class="job-title"><?php echo esc_html($job['position']); ?></h4> <span class="company"><?php echo esc_html($job['company']); ?></span> <span class="period"><?php echo esc_html($job['period']); ?></span> </div> <?php if (!empty($job['description'])): ?> <div class="job-description"> <p><?php echo nl2br(esc_html($job['description'])); ?></p> </div> <?php endif; ?> <?php if (!empty($job['achievements'])): ?> <ul class="achievements"> <?php foreach ($job['achievements'] as $achievement): ?> <?php if (!empty(trim($achievement))): ?> <li><?php echo esc_html(trim($achievement)); ?></li> <?php endif; ?> <?php endforeach; ?> </ul> <?php endif; ?> </div> <?php endforeach; ?> </div> </section> <?php endif; ?> <!-- 教育背景 --> <?php if (!empty($data['education'])): ?> <section class="resume-section education"> <h3 class="section-title">教育背景</h3> <div class="section-content"> <?php foreach ($data['education'] as $edu): ?> <div class="education-item"> <h4 class="degree"><?php echo esc_html($edu['degree']); ?></h4> <span class="institution"><?php echo esc_html($edu['institution']); ?></span> <span class="period"><?php echo esc_html($edu['period']); ?></span> <?php if (!empty($edu['field'])): ?> <div class="field-of-study">专业:<?php echo esc_html($edu['field']); ?></div> <?php endif; ?> <?php if (!empty($edu['gpa'])): ?> <div class="gpa">GPA:<?php echo esc_html($edu['gpa']); ?></div> <?php endif; ?> </div> <?php endforeach; ?> </div> </section> <?php endif; ?> <!-- 技能专长 --> <?php if (!empty($data['skills']['technical'])): ?> <section class="resume-section skills"> <h3 class="section-title">技能专长</h3> <div class="section-content"> <div class="skills-grid"> <?php foreach ($data['skills']['technical'] as $skill): ?> <div class="skill-item"> <span class="skill-name"><?php echo esc_html($skill['name']); ?></span> <div class="skill-level"> <?php for ($i = 1; $i <= 5; $i++): ?> <span class="level-dot <?php echo $i <= $this->get_level_number($skill['level']) ? 'filled' : ''; ?>"></span> <?php endfor; ?> </div> </div> <?php endforeach; ?> </div> </div> </section> <?php endif; ?> </div> </body></html> ### 4.3 PDF导出功能 // includes/class-pdf-generator.phpclass Resume_Builder_PDF_Generator { private $pdf; private $template_engine; public function __construct() { // 引入TCPDF库 require_once RB_PLUGIN_PATH . 'vendor/tcpdf/tcpdf.php'; $this->template_engine = new Resume_Builder_Template_Engine(); } public function generate_pdf($resume_id, $template_name = 'classic') { // 获取简历数据 $resume_data = $this->get_resume_data($resume_id); if (!$resume_data) { return false; } // 创建PDF实例 $this->pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); // 设置文档信息 $this->pdf->SetCreator('WordPress Resume Builder'); $this->pdf->SetAuthor($resume_data['personal']['full_name'] ?? ''); $this->pdf->SetTitle('简历 - ' . ($resume_data['personal']['full_name'] ?? '')); $this->pdf->SetSubject('个人简历'); // 设置页眉页脚 $this->pdf->setHeaderFont([PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN]); $this->pdf->setFooterFont([PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA]); // 设置默认等宽字体 $this->pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); // 设置边距 $this->pdf->SetMargins(15, 20, 15); $this->pdf->SetHeaderMargin(5); $this->pdf->SetFooterMargin(10); // 设置自动分页 $this->pdf->SetAutoPageBreak(TRUE, 15
发表评论手把手教学:为你的WordPress网站添加在线白板与团队头脑风暴功能 引言:为什么你的网站需要协作工具? 在当今数字化工作环境中,远程协作已成为常态。无论是教育机构、创意团队还是企业项目组,都需要高效的在线协作工具来促进沟通和创意产出。然而,许多专业协作工具价格昂贵,且难以与现有网站无缝集成。 本文将指导你通过WordPress代码二次开发,为你的网站添加在线白板和团队头脑风暴功能。这不仅能为你的用户提供价值,还能显著提升网站的互动性和专业性。我们将从零开始,逐步构建一个功能完整的协作系统。 第一部分:准备工作与环境搭建 1.1 理解WordPress开发基础 在开始之前,你需要具备以下基础知识: 基本的PHP编程能力 HTML/CSS/JavaScript前端知识 WordPress主题或插件开发经验 对REST API的基本了解 如果你已经熟悉这些技术,可以直接进入下一部分。如果你是初学者,建议先学习WordPress官方开发文档。 1.2 开发环境配置 首先,确保你有一个本地开发环境: 安装XAMPP、MAMP或Local by Flywheel 下载最新版WordPress并安装 启用调试模式(在wp-config.php中添加define('WP_DEBUG', true);) 安装代码编辑器(如VS Code、Sublime Text或PHPStorm) 1.3 创建自定义插件 我们将创建一个独立插件来实现功能,而不是修改主题文件,这样可以确保功能独立且易于维护。 创建插件目录结构: wp-content/plugins/team-whiteboard/ ├── team-whiteboard.php ├── includes/ │ ├── class-database.php │ ├── class-whiteboard.php │ └── class-brainstorm.php ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── templates/ └── vendor/ 第二部分:构建在线白板核心功能 2.1 数据库设计与实现 首先,我们需要设计数据库表来存储白板数据。在includes/class-database.php中: <?php class TeamWhiteboard_Database { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action('init', array($this, 'create_tables')); } public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'team_whiteboards'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, content longtext, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'active', settings text, PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建白板元素表 $elements_table = $wpdb->prefix . 'whiteboard_elements'; $sql_elements = "CREATE TABLE IF NOT EXISTS $elements_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, whiteboard_id mediumint(9) NOT NULL, element_type varchar(50) NOT NULL, element_data text NOT NULL, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, position_x float DEFAULT 0, position_y float DEFAULT 0, z_index int DEFAULT 0, PRIMARY KEY (id), FOREIGN KEY (whiteboard_id) REFERENCES $table_name(id) ON DELETE CASCADE ) $charset_collate;"; dbDelta($sql_elements); } } ?> 2.2 白板前端界面开发 接下来,我们创建白板的HTML结构和CSS样式。在templates/whiteboard.php中: <div class="team-whiteboard-container"> <div class="whiteboard-header"> <h2 id="whiteboard-title">新白板</h2> <div class="whiteboard-actions"> <button id="save-whiteboard" class="btn btn-primary">保存</button> <button id="clear-whiteboard" class="btn btn-secondary">清空</button> <button id="export-whiteboard" class="btn btn-success">导出</button> </div> </div> <div class="whiteboard-toolbar"> <div class="tool-group"> <button class="tool-btn active" data-tool="select" title="选择工具"> <i class="fas fa-mouse-pointer"></i> </button> <button class="tool-btn" data-tool="pen" title="画笔"> <i class="fas fa-pen"></i> </button> <button class="tool-btn" data-tool="line" title="直线"> <i class="fas fa-minus"></i> </button> <button class="tool-btn" data-tool="rectangle" title="矩形"> <i class="fas fa-square"></i> </button> <button class="tool-btn" data-tool="circle" title="圆形"> <i class="fas fa-circle"></i> </button> <button class="tool-btn" data-tool="text" title="文本"> <i class="fas fa-font"></i> </button> </div> <div class="tool-group"> <input type="color" id="color-picker" value="#000000"> <input type="range" id="brush-size" min="1" max="50" value="3"> <span id="brush-size-value">3px</span> </div> <div class="tool-group"> <button class="tool-btn" data-action="undo" title="撤销"> <i class="fas fa-undo"></i> </button> <button class="tool-btn" data-action="redo" title="重做"> <i class="fas fa-redo"></i> </button> <button class="tool-btn" data-action="delete" title="删除选中"> <i class="fas fa-trash"></i> </button> </div> </div> <div class="whiteboard-wrapper"> <canvas id="whiteboard-canvas"></canvas> <div class="whiteboard-grid"></div> </div> <div class="whiteboard-sidebar"> <div class="sidebar-section"> <h4>参与者</h4> <ul id="participants-list"> <!-- 参与者列表将通过JavaScript动态生成 --> </ul> </div> <div class="sidebar-section"> <h4>聊天</h4> <div id="chat-messages"></div> <div class="chat-input"> <input type="text" id="chat-input" placeholder="输入消息..."> <button id="send-chat">发送</button> </div> </div> </div> </div> 2.3 白板画布与绘图功能实现 现在,我们使用JavaScript和Canvas API实现白板的绘图功能。在assets/js/whiteboard.js中: class Whiteboard { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.isDrawing = false; this.currentTool = 'pen'; this.currentColor = '#000000'; this.brushSize = 3; this.lastX = 0; this.lastY = 0; this.history = []; this.historyIndex = -1; this.elements = []; this.selectedElement = null; this.initCanvas(); this.setupEventListeners(); this.setupWebSocket(); } initCanvas() { // 设置画布尺寸 this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); // 设置初始样式 this.ctx.lineCap = 'round'; this.ctx.lineJoin = 'round'; this.ctx.strokeStyle = this.currentColor; this.ctx.lineWidth = this.brushSize; // 绘制网格背景 this.drawGrid(); } resizeCanvas() { const container = this.canvas.parentElement; this.canvas.width = container.clientWidth; this.canvas.height = container.clientHeight; this.drawGrid(); this.redraw(); } drawGrid() { const gridSize = 20; const width = this.canvas.width; const height = this.canvas.height; this.ctx.save(); this.ctx.strokeStyle = '#f0f0f0'; this.ctx.lineWidth = 1; // 绘制垂直线 for (let x = 0; x <= width; x += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(x, 0); this.ctx.lineTo(x, height); this.ctx.stroke(); } // 绘制水平线 for (let y = 0; y <= height; y += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(0, y); this.ctx.lineTo(width, y); this.ctx.stroke(); } this.ctx.restore(); } setupEventListeners() { // 鼠标事件 this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e)); this.canvas.addEventListener('mousemove', (e) => this.draw(e)); this.canvas.addEventListener('mouseup', () => this.stopDrawing()); this.canvas.addEventListener('mouseout', () => this.stopDrawing()); // 触摸事件(移动设备支持) this.canvas.addEventListener('touchstart', (e) => { e.preventDefault(); const touch = e.touches[0]; this.startDrawing(touch); }); this.canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const touch = e.touches[0]; this.draw(touch); }); this.canvas.addEventListener('touchend', () => this.stopDrawing()); // 工具选择 document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); this.currentTool = e.target.dataset.tool; }); }); // 颜色选择 document.getElementById('color-picker').addEventListener('change', (e) => { this.currentColor = e.target.value; this.ctx.strokeStyle = this.currentColor; }); // 笔刷大小 document.getElementById('brush-size').addEventListener('input', (e) => { this.brushSize = e.target.value; document.getElementById('brush-size-value').textContent = `${this.brushSize}px`; this.ctx.lineWidth = this.brushSize; }); // 动作按钮 document.querySelector('[data-action="undo"]').addEventListener('click', () => this.undo()); document.querySelector('[data-action="redo"]').addEventListener('click', () => this.redo()); document.querySelector('[data-action="delete"]').addEventListener('click', () => this.deleteSelected()); } startDrawing(e) { this.isDrawing = true; const rect = this.canvas.getBoundingClientRect(); this.lastX = e.clientX - rect.left; this.lastY = e.clientY - rect.top; // 保存当前状态到历史记录 this.saveState(); // 根据工具类型执行不同操作 if (this.currentTool === 'text') { this.addTextElement(this.lastX, this.lastY); } } draw(e) { if (!this.isDrawing) return; const rect = this.canvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; switch (this.currentTool) { case 'pen': this.drawFreehand(currentX, currentY); break; case 'line': this.drawLine(currentX, currentY); break; case 'rectangle': this.drawRectangle(currentX, currentY); break; case 'circle': this.drawCircle(currentX, currentY); break; } this.lastX = currentX; this.lastY = currentY; } drawFreehand(x, y) { this.ctx.beginPath(); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(x, y); this.ctx.stroke(); // 发送绘图数据到服务器 this.sendDrawingData({ type: 'freehand', from: {x: this.lastX, y: this.lastY}, to: {x, y}, color: this.currentColor, size: this.brushSize }); } stopDrawing() { this.isDrawing = false; this.ctx.beginPath(); } saveState() { // 只保留最近50个状态 if (this.historyIndex < this.history.length - 1) { this.history = this.history.slice(0, this.historyIndex + 1); } const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); this.history.push(imageData); this.historyIndex++; // 限制历史记录数量 if (this.history.length > 50) { this.history.shift(); this.historyIndex--; } } undo() { if (this.historyIndex > 0) { this.historyIndex--; const imageData = this.history[this.historyIndex]; this.ctx.putImageData(imageData, 0, 0); } } redo() { if (this.historyIndex < this.history.length - 1) { this.historyIndex++; const imageData = this.history[this.historyIndex]; this.ctx.putImageData(imageData, 0, 0); } } setupWebSocket() { // 这里将实现WebSocket连接,用于实时协作 // 实际实现需要服务器端WebSocket支持 console.log('WebSocket连接将在服务器端配置后实现'); } sendDrawingData(data) { // 发送绘图数据到服务器,以便其他参与者可以看到 if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'drawing', data: data, timestamp: Date.now(), userId: window.userId || 'anonymous' })); } } redraw() { // 重绘画布上的所有元素 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.drawGrid(); // 重绘所有保存的元素 this.elements.forEach(element => { this.drawElement(element); }); } drawElement(element) { // 根据元素类型绘制 switch (element.type) { case 'freehand': this.ctx.beginPath(); this.ctx.moveTo(element.from.x, element.from.y); this.ctx.lineTo(element.to.x, element.to.y); this.ctx.strokeStyle = element.color; this.ctx.lineWidth = element.size; this.ctx.stroke(); break; // 其他元素类型的绘制逻辑 } } } // 初始化白板 document.addEventListener('DOMContentLoaded', () => { const whiteboard = new Whiteboard('whiteboard-canvas'); }); 第三部分:实现团队头脑风暴功能 3.1 头脑风暴数据库设计 在includes/class-brainstorm.php中,我们扩展数据库设计: <?php class TeamWhiteboard_Brainstorm { public function create_brainstorm_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 头脑风暴会话表 $sessions_table = $wpdb->prefix . 'brainstorm_sessions'; $sql_sessions = "CREATE TABLE IF NOT EXISTS $sessions_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, description text, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'active', settings text, PRIMARY KEY (id) ) $charset_collate;"; // 想法/便签表 $ideas_table = $wpdb->prefix . 'brainstorm_ideas'; $sql_ideas = "CREATE TABLE IF NOT EXISTS $ideas_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, session_id mediumint(9) NOT NULL, content text NOT NULL, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, category varchar(50), votes int DEFAULT 0, position_x float DEFAULT 0, position_y float DEFAULT 0, color varchar(20) DEFAULT '#FFFF99', PRIMARY KEY (id), FOREIGN KEY (session_id) REFERENCES $sessions_table(id) ON DELETE CASCADE ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_sessions); dbDelta($sql_ideas); } } ?> 3.2 头脑风暴前端界面 创建templates/brainstorm.php: <div class="brainstorm-container"> <div class="brainstorm-header"> 手把手教学:为你的WordPress网站添加在线白板与团队头脑风暴功能(续) 第三部分:实现团队头脑风暴功能(续) 3.2 头脑风暴前端界面(续) <div class="brainstorm-container"> <div class="brainstorm-header"> <h2 id="session-title">头脑风暴会议</h2> <div class="session-info"> <span id="participant-count">0 参与者</span> <span id="idea-count">0 个想法</span> </div> <div class="session-controls"> <button id="new-idea-btn" class="btn btn-primary"> <i class="fas fa-plus"></i> 添加想法 </button> <button id="timer-toggle" class="btn btn-secondary"> <i class="fas fa-clock"></i> 计时器 </button> <button id="export-ideas" class="btn btn-success"> <i class="fas fa-download"></i> 导出 </button> </div> </div> <div class="brainstorm-main"> <!-- 左侧工具栏 --> <div class="brainstorm-sidebar"> <div class="sidebar-section"> <h4>分类</h4> <div class="category-list"> <div class="category-item active" data-category="all"> <span class="category-color" style="background:#f0f0f0"></span> <span>全部想法</span> </div> <div class="category-item" data-category="feature"> <span class="category-color" style="background:#FF9999"></span> <span>功能建议</span> </div> <div class="category-item" data-category="improvement"> <span class="category-color" style="background:#99FF99"></span> <span>改进建议</span> </div> <div class="category-item" data-category="question"> <span class="category-color" style="background:#9999FF"></span> <span>问题反馈</span> </div> </div> <div class="add-category"> <input type="text" id="new-category-name" placeholder="新分类名称"> <input type="color" id="new-category-color" value="#CCCCCC"> <button id="add-category-btn">添加</button> </div> </div> <div class="sidebar-section"> <h4>投票设置</h4> <div class="voting-settings"> <label>每人票数:</label> <input type="number" id="votes-per-user" min="1" max="20" value="5"> <div class="voting-status"> <p>已用票数:<span id="used-votes">0</span>/<span id="total-votes">5</span></p> </div> </div> </div> <div class="sidebar-section"> <h4>计时器</h4> <div class="timer-container"> <div class="timer-display" id="timer-display">10:00</div> <div class="timer-controls"> <button id="start-timer">开始</button> <button id="pause-timer">暂停</button> <button id="reset-timer">重置</button> </div> <div class="timer-presets"> <button class="timer-preset" data-time="300">5分钟</button> <button class="timer-preset" data-time="600">10分钟</button> <button class="timer-preset" data-time="900">15分钟</button> </div> </div> </div> </div> <!-- 主工作区 --> <div class="brainstorm-workspace"> <div class="workspace-tools"> <button class="workspace-tool" data-action="arrange" title="自动排列"> <i class="fas fa-th"></i> </button> <button class="workspace-tool" data-action="cluster" title="按分类分组"> <i class="fas fa-object-group"></i> </button> <button class="workspace-tool" data-action="clear" title="清空工作区"> <i class="fas fa-broom"></i> </button> </div> <div class="ideas-container" id="ideas-container"> <!-- 想法卡片将通过JavaScript动态生成 --> </div> </div> <!-- 右侧聊天和参与者面板 --> <div class="brainstorm-participants"> <div class="participants-section"> <h4>参与者 (<span id="active-participants">0</span>)</h4> <ul id="participants-list"> <!-- 参与者列表 --> </ul> </div> <div class="chat-section"> <h4>讨论区</h4> <div class="chat-messages" id="brainstorm-chat"> <!-- 聊天消息 --> </div> <div class="chat-input"> <input type="text" id="brainstorm-chat-input" placeholder="输入消息..."> <button id="send-brainstorm-chat"> <i class="fas fa-paper-plane"></i> </button> </div> </div> </div> </div> </div> 3.3 头脑风暴JavaScript实现 创建assets/js/brainstorm.js: class BrainstormSession { constructor(sessionId) { this.sessionId = sessionId; this.ideas = []; this.categories = []; this.participants = []; this.currentUser = null; this.votesUsed = 0; this.votesTotal = 5; this.timer = null; this.timerSeconds = 600; // 默认10分钟 this.isTimerRunning = false; this.init(); this.loadSessionData(); this.setupEventListeners(); this.connectWebSocket(); } init() { // 初始化用户信息 this.currentUser = { id: window.userId || 'user_' + Math.random().toString(36).substr(2, 9), name: window.userName || '匿名用户', color: this.getRandomColor() }; // 初始化默认分类 this.categories = [ { id: 'all', name: '全部想法', color: '#f0f0f0' }, { id: 'feature', name: '功能建议', color: '#FF9999' }, { id: 'improvement', name: '改进建议', color: '#99FF99' }, { id: 'question', name: '问题反馈', color: '#9999FF' } ]; // 更新UI this.updateParticipantCount(); this.renderCategories(); } loadSessionData() { // 从服务器加载会话数据 fetch(`/wp-json/team-whiteboard/v1/brainstorm/${this.sessionId}`) .then(response => response.json()) .then(data => { this.ideas = data.ideas || []; this.categories = data.categories || this.categories; this.participants = data.participants || []; this.renderIdeas(); this.renderParticipants(); }) .catch(error => { console.error('加载会话数据失败:', error); }); } setupEventListeners() { // 添加想法按钮 document.getElementById('new-idea-btn').addEventListener('click', () => { this.showIdeaForm(); }); // 分类点击事件 document.querySelectorAll('.category-item').forEach(item => { item.addEventListener('click', (e) => { const category = e.currentTarget.dataset.category; this.filterIdeasByCategory(category); // 更新活动状态 document.querySelectorAll('.category-item').forEach(i => { i.classList.remove('active'); }); e.currentTarget.classList.add('active'); }); }); // 添加分类 document.getElementById('add-category-btn').addEventListener('click', () => { this.addCategory(); }); // 投票设置 document.getElementById('votes-per-user').addEventListener('change', (e) => { this.votesTotal = parseInt(e.target.value); document.getElementById('total-votes').textContent = this.votesTotal; }); // 计时器控制 document.getElementById('start-timer').addEventListener('click', () => { this.startTimer(); }); document.getElementById('pause-timer').addEventListener('click', () => { this.pauseTimer(); }); document.getElementById('reset-timer').addEventListener('click', () => { this.resetTimer(); }); // 预设时间按钮 document.querySelectorAll('.timer-preset').forEach(btn => { btn.addEventListener('click', (e) => { const seconds = parseInt(e.currentTarget.dataset.time); this.setTimer(seconds); }); }); // 工作区工具 document.querySelectorAll('.workspace-tool').forEach(tool => { tool.addEventListener('click', (e) => { const action = e.currentTarget.dataset.action; this.handleWorkspaceAction(action); }); }); // 聊天发送 document.getElementById('send-brainstorm-chat').addEventListener('click', () => { this.sendChatMessage(); }); document.getElementById('brainstorm-chat-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.sendChatMessage(); } }); } showIdeaForm() { // 创建想法表单模态框 const modal = document.createElement('div'); modal.className = 'idea-form-modal'; modal.innerHTML = ` <div class="modal-content"> <h3>添加新想法</h3> <div class="form-group"> <label>想法内容</label> <textarea id="idea-content" rows="4" placeholder="输入你的想法..."></textarea> </div> <div class="form-group"> <label>分类</label> <select id="idea-category"> ${this.categories.filter(c => c.id !== 'all').map(c => `<option value="${c.id}">${c.name}</option>` ).join('')} </select> </div> <div class="form-group"> <label>颜色</label> <input type="color" id="idea-color" value="#FFFF99"> </div> <div class="modal-actions"> <button id="cancel-idea" class="btn btn-secondary">取消</button> <button id="submit-idea" class="btn btn-primary">提交</button> </div> </div> `; document.body.appendChild(modal); // 事件监听 document.getElementById('cancel-idea').addEventListener('click', () => { document.body.removeChild(modal); }); document.getElementById('submit-idea').addEventListener('click', () => { this.submitIdea(); document.body.removeChild(modal); }); } submitIdea() { const content = document.getElementById('idea-content').value; const category = document.getElementById('idea-category').value; const color = document.getElementById('idea-color').value; if (!content.trim()) { alert('请输入想法内容'); return; } const idea = { id: 'idea_' + Date.now(), content: content, category: category, color: color, createdBy: this.currentUser, createdAt: new Date().toISOString(), votes: 0, position: { x: Math.random() * 600, y: Math.random() * 400 } }; this.ideas.push(idea); this.renderIdea(idea); this.updateIdeaCount(); // 发送到服务器 this.saveIdea(idea); // 广播给其他参与者 this.broadcast({ type: 'new_idea', data: idea }); } renderIdeas() { const container = document.getElementById('ideas-container'); container.innerHTML = ''; this.ideas.forEach(idea => { this.renderIdea(idea); }); this.updateIdeaCount(); } renderIdea(idea) { const container = document.getElementById('ideas-container'); const ideaElement = document.createElement('div'); ideaElement.className = 'idea-card'; ideaElement.dataset.id = idea.id; ideaElement.dataset.category = idea.category; ideaElement.style.left = idea.position.x + 'px'; ideaElement.style.top = idea.position.y + 'px'; ideaElement.style.backgroundColor = idea.color; ideaElement.innerHTML = ` <div class="idea-header"> <span class="idea-author">${idea.createdBy.name}</span> <span class="idea-time">${this.formatTime(idea.createdAt)}</span> </div> <div class="idea-content">${this.escapeHtml(idea.content)}</div> <div class="idea-footer"> <button class="vote-btn" data-id="${idea.id}"> <i class="fas fa-thumbs-up"></i> <span class="vote-count">${idea.votes}</span> </button> <button class="delete-btn" data-id="${idea.id}"> <i class="fas fa-trash"></i> </button> </div> `; container.appendChild(ideaElement); // 添加拖拽功能 this.makeDraggable(ideaElement, idea); // 投票按钮事件 ideaElement.querySelector('.vote-btn').addEventListener('click', (e) => { e.stopPropagation(); this.voteForIdea(idea.id); }); // 删除按钮事件 ideaElement.querySelector('.delete-btn').addEventListener('click', (e) => { e.stopPropagation(); if (confirm('确定要删除这个想法吗?')) { this.deleteIdea(idea.id); } }); } makeDraggable(element, idea) { let isDragging = false; let offsetX, offsetY; element.addEventListener('mousedown', startDrag); element.addEventListener('touchstart', startDrag); function startDrag(e) { isDragging = true; if (e.type === 'mousedown') { offsetX = e.clientX - element.offsetLeft; offsetY = e.clientY - element.offsetTop; document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDrag); } else { const touch = e.touches[0]; offsetX = touch.clientX - element.offsetLeft; offsetY = touch.clientY - element.offsetTop; document.addEventListener('touchmove', drag); document.addEventListener('touchend', stopDrag); } e.preventDefault(); } const drag = (e) => { if (!isDragging) return; let clientX, clientY; if (e.type === 'mousemove') { clientX = e.clientX; clientY = e.clientY; } else { clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; } const container = document.getElementById('ideas-container'); const maxX = container.clientWidth - element.offsetWidth; const maxY = container.clientHeight - element.offsetHeight; let x = clientX - offsetX; let y = clientY - offsetY; // 限制在容器内 x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); element.style.left = x + 'px'; element.style.top = y + 'px'; // 更新想法位置 idea.position.x = x; idea.position.y = y; // 广播位置更新 this.broadcast({ type: 'move_idea', data: { id: idea.id, position: idea.position } }); }.bind(this); function stopDrag() { isDragging = false; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('touchmove', drag); document.removeEventListener('touchend', stopDrag); } } voteForIdea(ideaId) { if (this.votesUsed >= this.votesTotal) { alert('你的投票次数已用完!'); return; } const idea = this.ideas.find(i => i.id === ideaId); if (!idea) return; idea.votes++; this.votesUsed++; // 更新UI const voteBtn = document.querySelector(`.vote-btn[data-id="${ideaId}"] .vote-count`); if (voteBtn) { voteBtn.textContent = idea.votes; } document.getElementById('used-votes').textContent = this.votesUsed; // 发送到服务器 this.updateIdeaVotes(ideaId, idea.votes); // 广播投票 this.broadcast({ type: 'vote', data: { ideaId: ideaId, votes: idea.votes } }); } filterIdeasByCategory(category) { const ideas = document.querySelectorAll('.idea-card'); ideas.forEach(idea => { if (category === 'all' || idea.dataset.category === category) { idea.style.display = 'block'; } else { idea.style.display = 'none'; } }); } addCategory() { const nameInput = document.getElementById('new-category-name'); const colorInput = document.getElementById('new-category-color'); const name = nameInput.value.trim(); const color = colorInput.value; if (!name) { alert('请输入分类名称'); return; }
发表评论WordPress插件开发教程:集成网站实时翻译与多语言聊天工具 引言:WordPress插件开发的无限可能 在当今全球化的互联网环境中,多语言支持和实时互动功能已成为网站提升用户体验的关键要素。WordPress作为全球最流行的内容管理系统,其强大的插件架构为开发者提供了无限的可能性。本教程将深入探讨如何通过WordPress插件开发,集成网站实时翻译与多语言聊天工具,实现常用互联网小工具功能。 WordPress插件开发不仅仅是简单的功能添加,更是对现有系统进行二次开发,创造独特价值的过程。通过本教程,您将学习到如何从零开始构建一个功能全面的插件,将实时翻译和聊天工具无缝集成到您的WordPress网站中,从而提升网站的国际化水平和用户互动体验。 第一章:WordPress插件开发基础 1.1 WordPress插件架构概述 WordPress插件系统基于PHP语言构建,采用事件驱动的钩子(Hooks)机制。插件通过动作(Actions)和过滤器(Filters)与WordPress核心进行交互,这种设计模式使得开发者可以在不修改核心代码的情况下扩展功能。 每个WordPress插件至少需要一个主文件,其中包含插件头部信息,用于向WordPress系统标识插件: <?php /** * Plugin Name: 多语言实时工具套件 * Plugin URI: https://yourwebsite.com/multilingual-tools * Description: 集成实时翻译与多语言聊天功能的WordPress插件 * Version: 1.0.0 * Author: 您的名字 * License: GPL v2 or later * Text Domain: multilingual-tools */ 1.2 开发环境搭建 在开始插件开发前,需要搭建合适的开发环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel 代码编辑器:VS Code、PHPStorm或Sublime Text 调试工具:安装Query Monitor、Debug Bar等调试插件 版本控制:使用Git进行代码版本管理 1.3 插件文件结构规划 合理的文件结构是插件可维护性的基础: multilingual-tools/ ├── multilingual-tools.php # 主插件文件 ├── includes/ # 核心功能文件 │ ├── class-translation-engine.php │ ├── class-chat-system.php │ └── class-admin-settings.php ├── assets/ # 静态资源 │ ├── css/ │ ├── js/ │ └── images/ ├── languages/ # 国际化文件 ├── templates/ # 前端模板 └── vendor/ # 第三方库 第二章:实时翻译引擎集成 2.1 翻译API选择与比较 集成实时翻译功能首先需要选择合适的翻译API。目前市场上有多种选择: Google Cloud Translation API:准确度高,支持100多种语言 Microsoft Azure Translator:企业级解决方案,稳定性好 DeepL API:欧洲语言翻译质量优秀 百度翻译API:中文相关翻译效果较好 本教程将以Google Cloud Translation API为例,但代码设计将保持灵活性,便于切换不同服务商。 2.2 翻译功能类设计 创建一个翻译引擎类,封装所有翻译相关功能: class MLT_Translation_Engine { private $api_key; private $api_endpoint = 'https://translation.googleapis.com/language/translate/v2'; public function __construct($api_key) { $this->api_key = $api_key; } /** * 检测文本语言 */ public function detect_language($text) { $response = wp_remote_post($this->api_endpoint . '/detect', [ 'body' => [ 'q' => $text, 'key' => $this->api_key ] ]); if (is_wp_error($response)) { return false; } $body = json_decode(wp_remote_retrieve_body($response), true); return isset($body['data']['detections'][0][0]['language']) ? $body['data']['detections'][0][0]['language'] : false; } /** * 执行翻译 */ public function translate($text, $target_language, $source_language = null) { $args = [ 'q' => $text, 'target' => $target_language, 'key' => $this->api_key ]; if ($source_language) { $args['source'] = $source_language; } $response = wp_remote_post($this->api_endpoint, [ 'body' => $args ]); if (is_wp_error($response)) { return $text; // 翻译失败时返回原文 } $body = json_decode(wp_remote_retrieve_body($response), true); return isset($body['data']['translations'][0]['translatedText']) ? $body['data']['translations'][0]['translatedText'] : $text; } /** * 批量翻译 */ public function translate_batch($texts, $target_language, $source_language = null) { $results = []; // 免费API通常有频率限制,这里添加延迟避免超限 foreach ($texts as $index => $text) { $results[$index] = $this->translate($text, $target_language, $source_language); // 每翻译5个文本暂停1秒 if ($index % 5 === 0 && $index > 0) { sleep(1); } } return $results; } } 2.3 前端翻译界面实现 在前端添加翻译控件,让用户可以选择翻译页面内容: class MLT_Frontend_Translator { public function __construct() { add_action('wp_footer', [$this, 'add_translation_widget']); add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']); add_action('wp_ajax_mlt_translate_content', [$this, 'ajax_translate_content']); add_action('wp_ajax_nopriv_mlt_translate_content', [$this, 'ajax_translate_content']); } public function enqueue_scripts() { wp_enqueue_style( 'mlt-frontend-style', plugin_dir_url(__FILE__) . '../assets/css/frontend.css', [], '1.0.0' ); wp_enqueue_script( 'mlt-frontend-script', plugin_dir_url(__FILE__) . '../assets/js/frontend.js', ['jquery'], '1.0.0', true ); wp_localize_script('mlt-frontend-script', 'mlt_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mlt_translate_nonce') ]); } public function add_translation_widget() { if (!is_singular()) return; $languages = [ 'en' => 'English', 'es' => 'Español', 'fr' => 'Français', 'de' => 'Deutsch', 'zh-CN' => '中文(简体)', 'ja' => '日本語' ]; include plugin_dir_path(__FILE__) . '../templates/translation-widget.php'; } public function ajax_translate_content() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'mlt_translate_nonce')) { wp_die('安全验证失败'); } $post_id = intval($_POST['post_id']); $target_lang = sanitize_text_field($_POST['target_lang']); $post = get_post($post_id); if (!$post) { wp_send_json_error('文章不存在'); } // 获取翻译引擎实例 $api_key = get_option('mlt_google_api_key'); $translator = new MLT_Translation_Engine($api_key); // 翻译标题和内容 $translated_title = $translator->translate($post->post_title, $target_lang); $translated_content = $translator->translate($post->post_content, $target_lang); wp_send_json_success([ 'title' => $translated_title, 'content' => apply_filters('the_content', $translated_content) ]); } } 第三章:多语言聊天系统开发 3.1 聊天系统架构设计 多语言聊天系统需要处理实时通信、消息存储和语言转换。我们将采用以下架构: 前端界面:使用WebSocket或AJAX轮询实现实时通信 消息处理:PHP处理消息接收、存储和转发 数据库设计:创建自定义表存储聊天记录 翻译集成:在消息发送/接收时自动翻译 3.2 数据库设计与消息存储 创建聊天消息数据库表: class MLT_Chat_Database { public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'mlt_chat_messages'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, session_id varchar(100) NOT NULL, sender_id bigint(20) DEFAULT 0, sender_type enum('user','admin') DEFAULT 'user', message_text text NOT NULL, original_language varchar(10) DEFAULT '', target_language varchar(10) DEFAULT '', translated_text text, is_translated tinyint(1) DEFAULT 0, timestamp datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY session_id (session_id), KEY timestamp (timestamp) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } public static function save_message($data) { global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; return $wpdb->insert($table_name, $data); } public static function get_chat_history($session_id, $limit = 50) { global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE session_id = %s ORDER BY timestamp ASC LIMIT %d", $session_id, $limit )); } } 3.3 实时通信实现 使用AJAX轮询实现实时聊天功能(为简化示例,生产环境建议使用WebSocket): class MLT_Chat_System { private $translation_engine; public function __construct() { $api_key = get_option('mlt_google_api_key'); $this->translation_engine = new MLT_Translation_Engine($api_key); add_action('wp_ajax_mlt_send_message', [$this, 'handle_send_message']); add_action('wp_ajax_nopriv_mlt_send_message', [$this, 'handle_send_message']); add_action('wp_ajax_mlt_get_messages', [$this, 'handle_get_messages']); add_action('wp_ajax_nopriv_mlt_get_messages', [$this, 'handle_get_messages']); } public function handle_send_message() { // 验证nonce和安全检查 if (!wp_verify_nonce($_POST['nonce'], 'mlt_chat_nonce')) { wp_send_json_error('安全验证失败'); } $session_id = sanitize_text_field($_POST['session_id']); $message = sanitize_textarea_field($_POST['message']); $user_lang = sanitize_text_field($_POST['user_lang']); $target_lang = sanitize_text_field($_POST['target_lang']); // 检测消息语言 $detected_lang = $this->translation_engine->detect_language($message); // 如果需要翻译,则翻译消息 $translated_message = $message; if ($detected_lang && $detected_lang !== $target_lang) { $translated_message = $this->translation_engine->translate( $message, $target_lang, $detected_lang ); } // 保存消息到数据库 $message_id = MLT_Chat_Database::save_message([ 'session_id' => $session_id, 'sender_type' => 'user', 'message_text' => $message, 'original_language' => $detected_lang, 'target_language' => $target_lang, 'translated_text' => $translated_message, 'is_translated' => ($detected_lang !== $target_lang) ? 1 : 0 ]); if ($message_id) { // 这里可以添加通知管理员新消息的逻辑 wp_send_json_success([ 'message_id' => $message_id, 'translated_message' => $translated_message, 'timestamp' => current_time('mysql') ]); } else { wp_send_json_error('消息发送失败'); } } public function handle_get_messages() { $session_id = sanitize_text_field($_POST['session_id']); $last_message_id = intval($_POST['last_message_id']); global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; // 获取新消息 $new_messages = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE session_id = %s AND id > %d ORDER BY timestamp ASC", $session_id, $last_message_id )); wp_send_json_success([ 'messages' => $new_messages, 'count' => count($new_messages) ]); } } 3.4 前端聊天界面 创建美观的聊天界面: <!-- templates/chat-widget.php --> <div id="mlt-chat-widget" class="mlt-chat-container"> <div class="mlt-chat-header"> <h3>多语言在线客服</h3> <div class="mlt-language-selector"> <select id="mlt-chat-language"> <option value="auto">自动检测</option> <option value="zh-CN">中文</option> <option value="en">English</option> <option value="es">Español</option> <option value="fr">Français</option> <option value="de">Deutsch</option> <option value="ja">日本語</option> </select> </div> <button class="mlt-chat-close">×</button> </div> <div class="mlt-chat-messages"> <!-- 消息将在这里动态加载 --> <div class="mlt-welcome-message"> <p>您好!我是多语言客服助手。请选择您的语言开始聊天。</p> </div> </div> <div class="mlt-chat-input-area"> <textarea id="mlt-chat-input" placeholder="输入消息... (按Enter发送,Shift+Enter换行)" rows="2" ></textarea> <button id="mlt-send-button">发送</button> </div> </div> <button id="mlt-chat-toggle" class="mlt-chat-toggle-button"> <span class="mlt-chat-icon">💬</span> <span class="mlt-chat-label">在线聊天</span> </button> 第四章:管理后台与设置界面 4.1 插件设置页面 创建完整的插件设置页面,让管理员可以配置API密钥和其他选项: class MLT_Admin_Settings { public function __construct() { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_init', [$this, 'register_settings']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']); } public function add_admin_menu() { add_menu_page( '多语言工具设置', '多语言工具', 'manage_options', 'mlt-settings', [$this, 'render_settings_page'], 'dashicons-translation', 80 ); add_submenu_page( 'mlt-settings', '聊天记录', '聊天记录', 'manage_options', 'mlt-chat-logs', [$this, 'render_chat_logs_page'] ); add_submenu_page( 'mlt-settings', '翻译统计', '翻译统计', 'manage_options', 'mlt-translation-stats', [$this, 'render_stats_page'] ); } public function register_settings() { register_setting('mlt_settings_group', 'mlt_google_api_key'); register_setting('mlt_settings_group', 'mlt_default_language'); register_setting('mlt_settings_group', 'mlt_chat_enabled'); register_setting('mlt_settings_group', 'mlt_translation_enabled'); register_setting('mlt_settings_group', 'mlt_supported_languages'); add_settings_section( 'mlt_api_section', 'API设置', [$this, 'render_api_section'], 'mlt-settings' ); add_settings_field( 'mlt_google_api_key', 'Google翻译API密钥', [$this, 'render_api_key_field'], 'mlt-settings', 'mlt_api_section' ); 4.2 设置字段与选项 public function render_api_section() { echo '<p>配置翻译和聊天功能所需的API密钥和基本设置。</p>'; } public function render_api_key_field() { $api_key = get_option('mlt_google_api_key', ''); echo '<input type="password" id="mlt_google_api_key" name="mlt_google_api_key" value="' . esc_attr($api_key) . '" class="regular-text" /> <p class="description">获取Google Cloud Translation API密钥:<a href="https://cloud.google.com/translate/docs/setup" target="_blank">点击这里</a></p>'; } public function render_settings_page() { if (!current_user_can('manage_options')) { return; } // 检查API密钥是否有效 $api_key = get_option('mlt_google_api_key'); $api_status = $this->check_api_status($api_key); ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <?php if ($api_status['valid'] === false): ?> <div class="notice notice-error"> <p><?php echo esc_html($api_status['message']); ?></p> </div> <?php endif; ?> <form action="options.php" method="post"> <?php settings_fields('mlt_settings_group'); do_settings_sections('mlt-settings'); submit_button('保存设置'); ?> </form> <div class="mlt-settings-extra"> <h2>功能测试</h2> <div class="mlt-test-area"> <h3>翻译功能测试</h3> <textarea id="mlt-test-text" rows="3" class="large-text" placeholder="输入要测试翻译的文本..."></textarea> <select id="mlt-test-target-lang"> <option value="en">英语</option> <option value="es">西班牙语</option> <option value="fr">法语</option> <option value="zh-CN">中文</option> </select> <button id="mlt-test-translate" class="button button-secondary">测试翻译</button> <div id="mlt-test-result" class="mlt-test-result"></div> </div> </div> </div> <?php } private function check_api_status($api_key) { if (empty($api_key)) { return ['valid' => false, 'message' => 'API密钥未设置']; } // 简单的API测试 $test_url = 'https://translation.googleapis.com/language/translate/v2/languages?key=' . $api_key; $response = wp_remote_get($test_url); if (is_wp_error($response)) { return ['valid' => false, 'message' => '网络连接错误: ' . $response->get_error_message()]; } $status_code = wp_remote_retrieve_response_code($response); if ($status_code === 200) { return ['valid' => true, 'message' => 'API连接正常']; } elseif ($status_code === 403) { return ['valid' => false, 'message' => 'API密钥无效或未启用翻译API服务']; } else { return ['valid' => false, 'message' => 'API连接失败,状态码: ' . $status_code]; } } public function render_chat_logs_page() { if (!current_user_can('manage_options')) { return; } global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; // 分页参数 $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; // 获取聊天会话列表 $sessions = $wpdb->get_results( "SELECT session_id, COUNT(*) as message_count, MIN(timestamp) as start_time, MAX(timestamp) as end_time FROM $table_name GROUP BY session_id ORDER BY end_time DESC LIMIT $offset, $per_page" ); $total_sessions = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name"); $total_pages = ceil($total_sessions / $per_page); ?> <div class="wrap"> <h1>聊天记录管理</h1> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>会话ID</th> <th>消息数量</th> <th>开始时间</th> <th>最后活动</th> <th>操作</th> </tr> </thead> <tbody> <?php if (empty($sessions)): ?> <tr> <td colspan="5">暂无聊天记录</td> </tr> <?php else: ?> <?php foreach ($sessions as $session): ?> <tr> <td><?php echo esc_html($session->session_id); ?></td> <td><?php echo intval($session->message_count); ?></td> <td><?php echo esc_html($session->start_time); ?></td> <td><?php echo esc_html($session->end_time); ?></td> <td> <a href="#" class="button button-small view-chat-details" data-session="<?php echo esc_attr($session->session_id); ?>"> 查看详情 </a> <a href="<?php echo wp_nonce_url( admin_url('admin.php?page=mlt-chat-logs&action=delete_session&session_id=' . $session->session_id), 'delete_chat_session' ); ?>" class="button button-small button-link-delete" onclick="return confirm('确定删除此会话的所有记录吗?')"> 删除 </a> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> <?php if ($total_pages > 1): ?> <div class="tablenav bottom"> <div class="tablenav-pages"> <?php echo paginate_links([ 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $total_pages, 'current' => $current_page ]); ?> </div> </div> <?php endif; ?> <!-- 聊天详情模态框 --> <div id="mlt-chat-details-modal" class="mlt-modal" style="display:none;"> <div class="mlt-modal-content"> <div class="mlt-modal-header"> <h3>聊天详情 - <span id="modal-session-id"></span></h3> <span class="mlt-modal-close">×</span> </div> <div class="mlt-modal-body"> <div id="mlt-chat-details-content"></div> </div> </div> </div> </div> <?php } } 第五章:高级功能与优化 5.1 缓存机制实现 为了减少API调用次数和提高性能,实现翻译缓存: class MLT_Translation_Cache { private $cache_group = 'mlt_translations'; private $cache_expiry = WEEK_IN_SECONDS; // 缓存一周 public function get_cached_translation($text, $target_lang, $source_lang = null) { $cache_key = $this->generate_cache_key($text, $target_lang, $source_lang); $cached = wp_cache_get($cache_key, $this->cache_group); if ($cached !== false) { return $cached; } // 检查数据库缓存 global $wpdb; $table_name = $wpdb->prefix . 'mlt_translation_cache'; $result = $wpdb->get_row($wpdb->prepare( "SELECT translated_text FROM $table_name WHERE original_text_hash = %s AND target_language = %s AND (source_language = %s OR source_language IS NULL) AND expiry_time > NOW()", md5($text), $target_lang, $source_lang )); if ($result) { wp_cache_set($cache_key, $result->translated_text, $this->cache_group, $this->cache_expiry); return $result->translated_text; } return false; } public function cache_translation($text, $translated_text, $target_lang, $source_lang = null) { $cache_key = $this->generate_cache_key($text, $target_lang, $source_lang); // 设置内存缓存 wp_cache_set($cache_key, $translated_text, $this->cache_group, $this->cache_expiry); // 存储到数据库 global $wpdb; $table_name = $wpdb->prefix . 'mlt_translation_cache'; $wpdb->replace($table_name, [ 'original_text_hash' => md5($text), 'original_text' => $text, 'translated_text' => $translated_text, 'source_language' => $source_lang, 'target_language' => $target_lang, 'expiry_time' => date('Y-m-d H:i:s', time() + $this->cache_expiry), 'created_at' => current_time('mysql') ]); } private function generate_cache_key($text, $target_lang, $source_lang) { return 'trans_' . md5($text . $target_lang . $source_lang); } public static function create_cache_table() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'mlt_translation_cache'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, original_text_hash varchar(32) NOT NULL, original_text text NOT NULL, translated_text text NOT NULL, source_language varchar(10), target_language varchar(10) NOT NULL, expiry_time datetime NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY text_hash_lang (original_text_hash, target_language, source_language), KEY expiry_time (expiry_time) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } 5.2 性能优化与异步处理 对于大量翻译请求,使用异步处理避免阻塞: class MLT_Async_Processor { private $queue_table; public function __construct() { global $wpdb; $this->queue_table = $wpdb->prefix . 'mlt_async_queue'; add_action('mlt_process_queue', [$this, 'process_queue']); add_action('wp_ajax_nopriv_mlt_async_translate', [$this, 'handle_async_translate']); } public function add_translation_job($data) { global $wpdb; return $wpdb->insert($this->queue_table, [ 'job_type' => 'translation', 'job_data' => json_encode($data), 'status' => 'pending', 'created_at' => current_time('mysql') ]); } public function process_queue() { global $wpdb; // 获取待处理的任务 $jobs = $wpdb->get_results( "SELECT * FROM {$this->queue_table} WHERE status = 'pending' ORDER BY created_at ASC LIMIT 10" ); foreach ($jobs as $job) { $this->process_job($job); } } private function process_job($job) { global $wpdb; $job_data = json_decode($job->job_data, true); try { // 更新状态为处理中 $wpdb->update( $this->queue_table, ['status' => 'processing'], ['id' => $job->id] ); // 执行翻译 $api_key = get_option('mlt_google_api_key'); $translator = new MLT_Translation_Engine($api_key); $result = $translator->translate( $job_data['text'], $job_data['target_lang'], $job_data['source_lang'] ?? null ); // 更新状态为完成 $wpdb->update( $this->queue_table, [ 'status' => 'completed', 'result_data' => json_encode(['translated_text' => $result]), 'completed_at' => current_time('mysql') ], ['id' => $job->id] ); } catch (Exception $e) { $wpdb->update( $this->queue_table, [ 'status' => 'failed', 'error_message' => $e->getMessage(), 'completed_at' => current_time('mysql') ], ['id' => $job->id] ); } } public static function create_queue_table() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'mlt_async_queue'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, job_type varchar(50) NOT NULL, job_data text NOT NULL, status varchar(20) DEFAULT 'pending', result_data text, error_message text, created_at datetime DEFAULT CURRENT_TIMESTAMP, completed_at datetime, PRIMARY KEY (id), KEY status (status), KEY job_type (job_type) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } 5.3 安全增强措施 class MLT_Security_Manager { public static function sanitize_chat_input($input) { // 移除危险标签但保留基本格式 $allowed_tags = [ 'b' => [], 'i' => [], 'u' => [], 'em' => [], 'strong' => [], 'br' => [], 'p' => [], 'span' => ['class' => []] ]; $sanitized = wp_kses($input, $allowed_tags); // 限制长度 if (strlen($sanitized) > 1000) { $sanitized = substr($sanitized, 0, 1000); } return $sanitized; } public static function validate_language_code($lang_code) { $supported_languages = [ 'en', 'es', 'fr', 'de', 'zh-CN', 'zh-TW', 'ja', 'ko', 'ru', 'ar', 'pt', 'it', 'nl', 'pl', 'tr', 'th', 'vi' ]; return in_array($lang_code, $supported_languages) ? $lang_code : 'en'; } public static function prevent_flood_attack($session_id, $limit = 10, $time_window = 60) { $transient_key = 'mlt_chat_flood_' . md5($session_id); $request_count = get_transient($transient_key); if ($request_count === false) { set_transient($transient_key, 1, $time_window); return true; } if ($request_count >= $limit) { return false; } set_transient($transient_key, $request_count + 1, $time_window); return true; } public static function encrypt_sensitive_data($data) { if (!extension_loaded('openssl')) { return $data; // 回退到基本编码 } $method = 'AES-256-CBC'; $key = defined('MLT_ENCRYPTION_KEY') ? MLT_ENCRYPTION_KEY : wp_salt(); $iv_length = openssl_cipher_iv_length($method); $iv = openssl_random_pseudo_bytes($iv_length); $encrypted = openssl_encrypt($data, $method, $key, 0, $iv); return base64_encode($iv . $encrypted); } } 第六章:插件部署与维护 6.1 插件激活与卸载处理 class MLT_Plugin_Manager { public static function activate() { // 创建数据库表 MLT_Chat_Database::create_tables(); MLT_Translation_Cache::create_cache_table(); MLT_Async_Processor::create_queue_table(); // 设置默认选项 add_option('mlt_chat_enabled', '1'); add_option('mlt_translation_enabled', '1'); add_option('mlt_default_language', 'zh-CN'); add_option('mlt_supported_languages', ['en', 'zh-CN', 'es', 'fr', 'de']); // 创建定时任务 if (!wp_next_scheduled('mlt_process_queue')) { wp_schedule_event(time(), 'hourly', 'mlt_process_queue'); } // 创建必要的目录
发表评论手把手教程:为WordPress实现智能化的网站内容版权检测与保护工具 引言:数字时代的内容版权挑战 在当今数字化时代,内容创作已成为网站运营的核心。无论是个人博客、企业官网还是电子商务平台,原创内容都是吸引流量、建立品牌权威的关键要素。然而,随着互联网信息的快速传播,内容盗用、抄袭和未经授权的转载问题日益严重。根据最新统计,超过60%的网站管理员表示曾遭遇过内容被盗用的情况,这直接影响了原创者的权益和网站的SEO表现。 WordPress作为全球最流行的内容管理系统,占据了互联网近43%的网站份额。虽然WordPress拥有丰富的插件生态系统,但在内容版权保护方面,大多数现有解决方案要么功能有限,要么需要高昂的订阅费用。本文将手把手指导您通过WordPress代码二次开发,实现一个智能化的网站内容版权检测与保护工具,让您的原创内容得到更好的保护。 第一章:理解WordPress内容保护的基本原理 1.1 WordPress内容盗用的常见形式 在开始开发之前,我们需要了解内容盗用的主要形式: 直接复制粘贴:用户通过浏览器右键复制或查看源代码获取内容 RSS订阅盗用:通过网站的RSS源批量抓取内容 爬虫程序抓取:使用自动化脚本抓取网站内容 截图盗用:对内容进行截图后重新发布 1.2 现有版权保护方法的局限性 当前常见的WordPress版权保护方法包括: 禁用右键功能:简单但用户体验差,且技术用户可轻松绕过 添加水印:适用于图片,但对文本内容无效 使用JavaScript干扰:可以防止简单复制,但无法阻止源代码查看 法律声明:仅有威慑作用,缺乏实际保护能力 1.3 智能化版权保护的核心思路 我们将要开发的工具基于以下核心思路: 内容指纹技术:为每篇文章生成唯一数字指纹 智能监控系统:定期搜索互联网上的相似内容 动态防护机制:根据访问者行为动态调整保护策略 版权声明自动化:自动在内容中嵌入版权信息 第二章:开发环境准备与基础架构设计 2.1 开发环境配置 首先,确保您具备以下开发环境: WordPress安装(建议5.6以上版本) PHP开发环境(7.4以上版本) 代码编辑器(VS Code、PHPStorm等) 本地或测试服务器环境 2.2 创建插件基础结构 在WordPress的wp-content/plugins/目录下创建新文件夹smart-content-protector,并建立以下基础文件结构: smart-content-protector/ ├── smart-content-protector.php # 主插件文件 ├── includes/ │ ├── class-content-fingerprint.php # 内容指纹生成类 │ ├── class-content-monitor.php # 内容监控类 │ ├── class-protection-engine.php # 保护引擎类 │ └── class-settings-manager.php # 设置管理类 ├── admin/ │ ├── css/ │ │ └── admin-style.css # 后台样式 │ ├── js/ │ │ └── admin-script.js # 后台脚本 │ └── class-admin-interface.php # 后台界面类 ├── public/ │ ├── css/ │ │ └── public-style.css # 前端样式 │ ├── js/ │ │ └── public-script.js # 前端脚本 │ └── class-public-handler.php # 前端处理类 ├── assets/ # 静态资源 └── uninstall.php # 插件卸载处理 2.3 主插件文件配置 编辑smart-content-protector.php文件,添加插件基本信息: <?php /** * Plugin Name: Smart Content Protector * Plugin URI: https://yourwebsite.com/smart-content-protector * Description: 智能化的WordPress网站内容版权检测与保护工具 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later * Text Domain: smart-content-protector */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SCP_VERSION', '1.0.0'); define('SCP_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SCP_PLUGIN_URL', plugin_dir_url(__FILE__)); define('SCP_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { $prefix = 'SCP_'; $base_dir = SCP_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class_name, $len) !== 0) { return; } $relative_class = substr($class_name, $len); $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function scp_init_plugin() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.6', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo __('Smart Content Protector需要WordPress 5.6或更高版本。', 'smart-content-protector'); echo '</p></div>'; }); return; } // 初始化核心类 $content_fingerprint = new SCP_Content_Fingerprint(); $content_monitor = new SCP_Content_Monitor(); $protection_engine = new SCP_Protection_Engine(); $settings_manager = new SCP_Settings_Manager(); // 根据环境加载前端或后台 if (is_admin()) { require_once SCP_PLUGIN_DIR . 'admin/class-admin-interface.php'; new SCP_Admin_Interface($settings_manager); } else { require_once SCP_PLUGIN_DIR . 'public/class-public-handler.php'; new SCP_Public_Handler($protection_engine); } } add_action('plugins_loaded', 'scp_init_plugin'); // 激活插件时的操作 register_activation_hook(__FILE__, 'scp_activate_plugin'); function scp_activate_plugin() { // 创建必要的数据库表 scp_create_database_tables(); // 设置默认选项 $default_options = array( 'protection_level' => 'medium', 'auto_monitor' => true, 'monitor_frequency' => 'weekly', 'watermark_text' => '本文来自{site_name},原文链接:{post_url}', 'disable_right_click' => false, 'enable_fingerprint' => true, 'search_engines' => array('google', 'bing'), ); add_option('scp_settings', $default_options); // 安排定期监控任务 if (!wp_next_scheduled('scp_daily_monitor')) { wp_schedule_event(time(), 'daily', 'scp_daily_monitor'); } } // 停用插件时的操作 register_deactivation_hook(__FILE__, 'scp_deactivate_plugin'); function scp_deactivate_plugin() { // 清除定时任务 wp_clear_scheduled_hook('scp_daily_monitor'); } // 创建数据库表 function scp_create_database_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'scp_content_fingerprints'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, fingerprint varchar(64) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY post_id (post_id), KEY fingerprint (fingerprint) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建侵权记录表 $infringement_table = $wpdb->prefix . 'scp_infringements'; $sql = "CREATE TABLE IF NOT EXISTS $infringement_table ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, infringing_url varchar(500) NOT NULL, similarity_score float NOT NULL, detected_at datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'pending', action_taken varchar(50) DEFAULT NULL, PRIMARY KEY (id), KEY post_id (post_id), KEY status (status) ) $charset_collate;"; dbDelta($sql); } 第三章:实现内容指纹生成系统 3.1 内容指纹算法设计 编辑includes/class-content-fingerprint.php文件: <?php class SCP_Content_Fingerprint { private $hash_algo = 'sha256'; public function __construct() { add_action('save_post', array($this, 'generate_post_fingerprint'), 10, 3); add_action('scp_daily_monitor', array($this, 'update_all_fingerprints')); } /** * 为文章生成内容指纹 */ public function generate_post_fingerprint($post_id, $post, $update) { // 跳过自动保存和修订 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (wp_is_post_revision($post_id)) { return; } // 只处理已发布的文章 if ($post->post_status !== 'publish') { return; } // 获取文章内容 $content = $this->extract_content_for_fingerprint($post); // 生成指纹 $fingerprint = $this->calculate_fingerprint($content); // 保存到数据库 $this->save_fingerprint($post_id, $fingerprint); return $fingerprint; } /** * 提取用于生成指纹的内容 */ private function extract_content_for_fingerprint($post) { $content = ''; // 添加标题 $content .= strip_tags($post->post_title) . "n"; // 添加正文内容(去除HTML标签) $post_content = strip_tags($post->post_content); $post_content = preg_replace('/s+/', ' ', $post_content); $content .= $post_content . "n"; // 添加前300个字符作为特征(即使内容被部分修改也能检测) $content .= substr($post_content, 0, 300); // 添加文章摘要(如果有) if (!empty($post->post_excerpt)) { $content .= strip_tags($post->post_excerpt) . "n"; } return $content; } /** * 计算内容指纹 */ private function calculate_fingerprint($content) { // 标准化内容:转换为小写,移除多余空格和标点 $normalized = $this->normalize_content($content); // 使用simhash算法生成指纹(更适合文本相似度检测) $fingerprint = $this->simhash($normalized); return $fingerprint; } /** * 内容标准化处理 */ private function normalize_content($content) { // 转换为小写 $content = mb_strtolower($content, 'UTF-8'); // 移除所有标点符号和特殊字符 $content = preg_replace('/[^p{L}p{N}s]/u', ' ', $content); // 将多个空格合并为一个 $content = preg_replace('/s+/', ' ', $content); // 移除停用词(常见但无实际意义的词) $stop_words = $this->get_stop_words(); $words = explode(' ', $content); $words = array_diff($words, $stop_words); return implode(' ', $words); } /** * 获取停用词列表 */ private function get_stop_words() { // 中文常见停用词 $chinese_stop_words = array( '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这' ); // 英文常见停用词 $english_stop_words = array( 'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being' ); return array_merge($chinese_stop_words, $english_stop_words); } /** * SimHash算法实现 */ private function simhash($content) { $features = array(); $words = explode(' ', $content); // 统计词频 $word_counts = array_count_values($words); // 初始化特征向量(64位) $vector = array_fill(0, 64, 0); foreach ($word_counts as $word => $count) { // 为每个词生成hash $hash = hexdec(substr(md5($word), 0, 16)); for ($i = 0; $i < 64; $i++) { // 检查hash的每一位 $bit = ($hash >> $i) & 1; if ($bit == 1) { $vector[$i] += $count; } else { $vector[$i] -= $count; } } } // 生成最终的fingerprint $fingerprint = 0; for ($i = 0; $i < 64; $i++) { if ($vector[$i] > 0) { $fingerprint |= (1 << $i); } } return dechex($fingerprint); } /** * 保存指纹到数据库 */ private function save_fingerprint($post_id, $fingerprint) { global $wpdb; $table_name = $wpdb->prefix . 'scp_content_fingerprints'; // 检查是否已存在记录 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE post_id = %d", $post_id )); if ($existing) { // 更新现有记录 $wpdb->update( $table_name, array('fingerprint' => $fingerprint), array('post_id' => $post_id), array('%s'), array('%d') ); } else { // 插入新记录 $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'fingerprint' => $fingerprint ), array('%d', '%s') ); } } /** * 更新所有文章的指纹 */ public function update_all_fingerprints() { $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => -1, ); $posts = get_posts($args); foreach ($posts as $post) { $this->generate_post_fingerprint($post->ID, $post, true); } } /** * 计算两个指纹的相似度 */ public function calculate_similarity($fingerprint1, $fingerprint2) { // 将十六进制转换为二进制 $bin1 = hex2bin(str_pad($fingerprint1, 16, '0', STR_PAD_LEFT)); $bin2 = hex2bin(str_pad($fingerprint2, 16, '0', STR_PAD_LEFT)); // 计算海明距离 $hamming_distance = 0; for ($i = 0; $i < strlen($bin1); $i++) { $xor = ord($bin1[$i]) ^ ord($bin2[$i]); while ($xor) { $hamming_distance += $xor & 1; $xor >>= 1; } } // 转换为相似度百分比 $max_distance = strlen($bin1) * 8; $similarity = 100 * (1 - $hamming_distance / $max_distance); return round($similarity, 2); } } 第四章:构建智能内容监控系统 4.1 监控系统设计 编辑includes/class-content-monitor.php文件: <?php class SCP_Content_Monitor { private $search_engines = array(); private $api_keys = array(); public function __construct() { $settings = get_option('scp_settings', array()); $this->search_engines = isset($settings['search_engines']) ? $settings['search_engines'] : array('google'); // 设置API密钥(实际使用时需要从设置中获取) $this->api_keys = array( 'google' => defined('SCP_GOOGLE_API_KEY') ? SCP_GOOGLE_API_KEY : '', _API_KEY') ? SCP_BING_API_KEY : '' ); add_action('scp_daily_monitor', array($this, 'run_daily_monitoring')); add_action('scp_manual_monitor', array($this, 'monitor_specific_post')); } /** * 执行每日监控任务 */ public function run_daily_monitoring() { // 获取最近30天内发布的文章 $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'date_query' => array( array( 'after' => '30 days ago', ) ), 'posts_per_page' => 20, // 每天监控20篇文章,避免API限制 ); $posts = get_posts($args); foreach ($posts as $post) { $this->monitor_post($post); // 避免请求过于频繁 sleep(2); } // 记录监控日志 $this->log_monitoring_result(count($posts)); } /** * 监控特定文章 */ public function monitor_specific_post($post_id) { $post = get_post($post_id); if ($post && $post->post_status === 'publish') { return $this->monitor_post($post); } return false; } /** * 监控单篇文章 */ private function monitor_post($post) { $results = array(); // 生成搜索查询 $search_queries = $this->generate_search_queries($post); foreach ($this->search_engines as $engine) { if (method_exists($this, "search_with_{$engine}")) { foreach ($search_queries as $query) { $engine_results = call_user_func( array($this, "search_with_{$engine}"), $query, $post->ID ); if (!empty($engine_results)) { $results = array_merge($results, $engine_results); } } } } // 分析结果并保存侵权记录 if (!empty($results)) { $this->analyze_and_save_results($results, $post->ID); } return $results; } /** * 生成搜索查询 */ private function generate_search_queries($post) { $queries = array(); // 提取文章标题中的关键词 $title = strip_tags($post->post_title); $title_words = explode(' ', preg_replace('/[^p{L}p{N}s]/u', ' ', $title)); $title_words = array_filter($title_words, function($word) { return mb_strlen($word, 'UTF-8') > 1; }); // 使用标题中的前5个词作为查询 if (count($title_words) > 0) { $title_query = implode(' ', array_slice($title_words, 0, 5)); $queries[] = $title_query; } // 提取文章中的独特短语(连续3-5个词) $content = strip_tags($post->post_content); $content = preg_replace('/s+/', ' ', $content); $words = explode(' ', $content); // 生成独特短语 $phrases = array(); for ($i = 0; $i < count($words) - 3; $i++) { $phrase = implode(' ', array_slice($words, $i, 4)); if (strlen($phrase) > 15 && strlen($phrase) < 50) { $phrases[] = $phrase; } } // 选择最独特的3个短语 $unique_phrases = $this->select_unique_phrases($phrases); $queries = array_merge($queries, array_slice($unique_phrases, 0, 3)); // 添加包含网站名称的查询 $site_name = get_bloginfo('name'); if (!empty($site_name) && !empty($title_query)) { $queries[] = ""{$title_query}" "{$site_name}""; } return array_unique($queries); } /** * 选择独特短语 */ private function select_unique_phrases($phrases) { // 简单的独特性评分:基于词频和长度 $scored_phrases = array(); foreach ($phrases as $phrase) { $score = 0; // 长度适中得分高 $length = strlen($phrase); if ($length > 20 && $length < 40) { $score += 3; } elseif ($length >= 40 && $length < 60) { $score += 2; } // 包含较少常见词得分高 $common_words = array('的', '了', '在', '是', '和', '就', '不', '人', '都'); $phrase_words = explode(' ', $phrase); $common_count = count(array_intersect($phrase_words, $common_words)); $score += max(0, 5 - $common_count); $scored_phrases[$phrase] = $score; } arsort($scored_phrases); return array_keys($scored_phrases); } /** * 使用Google搜索 */ private function search_with_google($query, $post_id) { $api_key = $this->api_keys['google']; $search_engine_id = defined('SCP_GOOGLE_CSE_ID') ? SCP_GOOGLE_CSE_ID : ''; if (empty($api_key) || empty($search_engine_id)) { // 如果没有API密钥,使用简单的HTTP请求模拟搜索 return $this->search_with_google_public($query, $post_id); } $url = 'https://www.googleapis.com/customsearch/v1'; $params = array( 'key' => $api_key, 'cx' => $search_engine_id, 'q' => $query, 'num' => 10, 'fields' => 'items(link,title,snippet)' ); $response = wp_remote_get(add_query_arg($params, $url)); if (is_wp_error($response)) { return array(); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); $results = array(); if (isset($data['items'])) { foreach ($data['items'] as $item) { // 排除自己的网站 if (strpos($item['link'], home_url()) !== false) { continue; } $results[] = array( 'url' => $item['link'], 'title' => $item['title'], 'snippet' => $item['snippet'], 'engine' => 'google', 'query' => $query ); } } return $results; } /** * 使用公开的Google搜索(无API) */ private function search_with_google_public($query, $post_id) { // 注意:这种方法可能违反Google的服务条款,仅作为示例 // 实际使用时建议使用官方API $url = 'https://www.google.com/search'; $params = array( 'q' => $query, 'num' => 10 ); $response = wp_remote_get(add_query_arg($params, $url), array( 'headers' => array( 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) )); if (is_wp_error($response)) { return array(); } $body = wp_remote_retrieve_body($response); // 简单的HTML解析提取结果 $results = array(); if (preg_match_all('/<div class="g">(.*?)</div></div></div>/s', $body, $matches)) { foreach ($matches[1] as $match) { if (preg_match('/<a[^>]+href="([^"]+)"[^>]*>(.*?)</a>/', $match, $link_match)) { $url = $link_match[1]; $title = strip_tags($link_match[2]); // 排除自己的网站 if (strpos($url, home_url()) !== false) { continue; } // 提取摘要 $snippet = ''; if (preg_match('/<div[^>]*class="[^"]*VwiC3b[^"]*"[^>]*>(.*?)</div>/', $match, $snippet_match)) { $snippet = strip_tags($snippet_match[1]); } $results[] = array( 'url' => $url, 'title' => $title, 'snippet' => $snippet, 'engine' => 'google_public', 'query' => $query ); } } } return $results; } /** * 使用Bing搜索 */ private function search_with_bing($query, $post_id) { $api_key = $this->api_keys['bing']; if (empty($api_key)) { return array(); } $url = 'https://api.bing.microsoft.com/v7.0/search'; $params = array( 'q' => $query, 'count' => 10, 'responseFilter' => 'Webpages' ); $response = wp_remote_get(add_query_arg($params, $url), array( 'headers' => array( 'Ocp-Apim-Subscription-Key' => $api_key ) )); if (is_wp_error($response)) { return array(); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); $results = array(); if (isset($data['webPages']['value'])) { foreach ($data['webPages']['value'] as $item) { // 排除自己的网站 if (strpos($item['url'], home_url()) !== false) { continue; } $results[] = array( 'url' => $item['url'], 'title' => $item['name'], 'snippet' => $item['snippet'], 'engine' => 'bing', 'query' => $query ); } } return $results; } /** * 分析并保存结果 */ private function analyze_and_save_results($results, $post_id) { global $wpdb; $table_name = $wpdb->prefix . 'scp_infringements'; $post_content = get_post_field('post_content', $post_id); $post_content_plain = strip_tags($post_content); foreach ($results as $result) { // 获取疑似侵权页面的内容 $infringing_content = $this->fetch_page_content($result['url']); if (empty($infringing_content)) { continue; } // 计算相似度 $similarity = $this->calculate_content_similarity( $post_content_plain, $infringing_content ); // 如果相似度超过阈值,保存记录 $threshold = 70; // 70%相似度阈值 if ($similarity >= $threshold) { // 检查是否已存在记录 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE post_id = %d AND infringing_url = %s", $post_id, $result['url'] )); if (!$existing) { $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'infringing_url' => $result['url'], 'similarity_score' => $similarity, 'detected_at' => current_time('mysql'), 'status' => 'pending' ), array('%d', '%s', '%f', '%s', '%s') ); // 发送通知 $this->send_infringement_notification($post_id, $result['url'], $similarity); } } } } /** * 获取页面内容 */ private function fetch_page_content($url) { $response = wp_remote_get($url, array( 'timeout' => 10, 'headers' => array( 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) )); if (is_wp_error($response)) { return ''; } $body = wp_remote_retrieve_body($response); // 提取正文内容 $content = ''; // 尝试多种方法提取正文 if (preg_match('/<body[^>]*>(.*?)</body>/s', $body, $matches)) { $body_content = $matches[1]; // 移除脚本和样式 $body_content = preg_replace('/<script[^>]*>.*?</script>/s', '', $body_content); $body_content = preg_replace('/<style[^>]*>.*?</style>/s', '', $body_content); // 提取文本 $content = strip_tags($body_content); $content = preg_replace('/s+/', ' ', $content); $content = trim($content); } return $content; } /** * 计算内容相似度 */ private function calculate_content_similarity($content1, $content2) { // 使用文本相似度算法 $similarity = 0; // 方法1:基于共享词的比例 $words1 = $this->extract_significant_words($content1); $words2 = $this->extract_significant_words($content2); $common_words = array_intersect($words1, $words2); $total_unique_words = count(array_unique(array_merge($words1, $words2))); if ($total_unique_words > 0) { $similarity = (count($common_words) / $total_unique_words) * 100; } // 方法2:检查长字符串匹配 $long_strings1 = $this->extract_long_strings($content1); $long_strings2 = $this->extract_long_strings($content2); $string_similarity = 0; foreach ($long_strings1 as $str1) { foreach ($long_strings2 as $str2) { similar_text($str1, $str2, $percent); if ($percent > 80) { // 80%相似的长字符串 $string_similarity = max($string_similarity, $percent); } } } // 取两种方法的较高值 $similarity = max($similarity, $string_similarity); return min(100, round($similarity, 2)); } /** * 提取重要词汇 */ private function extract_significant_words($content, $min_length = 2) { $content = mb_strtolower($content, 'UTF-8'); $content = preg_replace('/[^p{L}p{N}s]/u', ' ', $content); $words = explode(' ', $content); // 过滤短词和停用词 $stop_words = $this->get_stop_words(); $words = array_filter($words, function($word) use ($min_length, $stop_words) { return mb_strlen($word, 'UTF-8') >= $min_length && !in_array($word, $stop_words); }); return array_values($words); } /** * 提取长字符串 */ private function extract_long_strings($content, $min_length = 20) { $sentences = preg_split('/[。.!??]/u', $content); $long_strings = array(); foreach ($sentences as $sentence) { $sentence = trim($sentence); if (mb_strlen($sentence, 'UTF-8') >= $min_length) { $long_strings[] = $sentence; } } return $long_strings; } /** * 发送侵权通知 */ private function send_infringement_notification($post_id, $infringing_url, $similarity) { $admin_email = get_option('admin_email'); $post_title = get_the_title($post_id); $post_url = get_permalink($post_id); $subject = sprintf( __('[内容侵权警报] 文章 "%s" 可能被抄袭', 'smart-content-protector'), $post_title ); $message = sprintf( __('检测到可能的内容侵权: 您的文章:%s文章链接:%s 疑似侵权页面:%s相似度:%.2f%% 请登录WordPress后台查看详细信息并采取相应措施。 Smart Content Protector%s', 'smart-content-protector'), $post_title, $post_url, $infringing_url, $similarity, home_url() ); wp_mail($admin_email, $subject, $message); } /** * 记录监控日志 */ private function log_monitoring_result($posts_monitored) { $log_entry = sprintf( '[%s] 监控完成,检查了 %d 篇文章', current_time('mysql'), $posts_monitored ); // 保存到选项
发表评论开发指南:打造网站内嵌的在线抽奖与幸运大转盘互动营销组件 摘要 在当今数字营销时代,互动式营销工具已成为提升用户参与度和转化率的关键手段。本文将详细介绍如何通过WordPress程序的代码二次开发,实现一个功能完整的在线抽奖与幸运大转盘互动营销组件。我们将从需求分析、技术选型、代码实现到部署测试,全面解析开发流程,帮助开发者快速构建这一常用互联网小工具功能。 目录 引言:互动营销组件的重要性 需求分析与功能规划 技术架构与开发环境搭建 数据库设计与数据模型 前端实现:转盘界面与交互设计 后端开发:抽奖逻辑与数据管理 WordPress集成与插件化开发 安全性与性能优化 测试与部署策略 维护与扩展建议 结语:未来发展方向 1. 引言:互动营销组件的重要性 在竞争激烈的互联网环境中,网站需要不断创新以吸引和留住用户。互动营销组件,如在线抽奖和幸运大转盘,已成为提升用户参与度、增加页面停留时间、促进转化的有效工具。这些组件不仅能够增强用户体验,还能为网站运营者提供宝贵的用户行为数据。 WordPress作为全球最流行的内容管理系统,拥有庞大的用户基础和丰富的扩展生态。通过二次开发,我们可以将定制化的互动营销组件无缝集成到WordPress网站中,满足特定业务需求,同时保持与现有主题和插件的兼容性。 2. 需求分析与功能规划 2.1 核心功能需求 转盘可视化界面:美观、可自定义的转盘界面,支持多种奖品设置 抽奖逻辑系统:可配置的中奖概率、奖品库存管理 用户参与管理:用户身份验证、参与次数限制、中奖记录 数据统计与分析:参与数据、中奖率、用户行为分析 后台管理界面:奖品管理、概率设置、数据监控 2.2 非功能性需求 响应式设计:适配各种设备屏幕尺寸 性能优化:快速加载和流畅的动画效果 安全性:防止作弊和恶意攻击 可扩展性:便于未来功能扩展和定制 2.3 用户角色与权限 网站管理员:完全控制抽奖设置和数据管理 编辑人员:管理奖品内容和基本设置 普通用户:参与抽奖活动,查看中奖记录 未注册访客:有限次数的参与机会 3. 技术架构与开发环境搭建 3.1 技术选型 前端技术:HTML5、CSS3、JavaScript (ES6+)、Canvas/SVG(转盘绘制) 动画库:GSAP或CSS3动画实现流畅转盘效果 后端技术:PHP 7.4+(WordPress核心语言) 数据库:MySQL(WordPress默认数据库) WordPress开发:遵循WordPress插件开发规范 3.2 开发环境配置 本地开发环境:安装XAMPP/MAMP或Local by Flywheel WordPress安装:最新稳定版WordPress 代码编辑器:VS Code或PHPStorm 版本控制:Git 调试工具:浏览器开发者工具、Xdebug 3.3 项目结构规划 wp-content/plugins/lucky-wheel-roulette/ ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── includes/ │ ├── class-database.php │ ├── class-wheel-ui.php │ ├── class-lottery-logic.php │ └── class-admin-panel.php ├── templates/ │ ├── frontend-wheel.php │ └── admin-settings.php ├── languages/ ├── lucky-wheel-roulette.php (主插件文件) └── uninstall.php 4. 数据库设计与数据模型 4.1 自定义数据表设计 除了使用WordPress默认的数据表,我们需要创建几个自定义表来存储抽奖相关数据: -- 奖品表 CREATE TABLE wp_lwr_prizes ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, image_url VARCHAR(500), probability DECIMAL(5,4) DEFAULT 0.1, stock INT DEFAULT 0, daily_limit INT DEFAULT 0, type ENUM('physical', 'virtual', 'coupon') DEFAULT 'virtual', value VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 抽奖记录表 CREATE TABLE wp_lwr_records ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED, prize_id INT, ip_address VARCHAR(45), user_agent TEXT, result ENUM('win', 'lose') DEFAULT 'lose', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE SET NULL, FOREIGN KEY (prize_id) REFERENCES wp_lwr_prizes(id) ON DELETE SET NULL ); -- 用户抽奖次数表 CREATE TABLE wp_lwr_user_attempts ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED, attempts_today INT DEFAULT 0, total_attempts INT DEFAULT 0, last_attempt_date DATE, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); 4.2 数据模型类实现 // includes/class-database.php class LWR_Database { private static $instance = null; private $wpdb; private $table_prizes; private $table_records; private $table_user_attempts; private function __construct() { global $wpdb; $this->wpdb = $wpdb; $this->table_prizes = $wpdb->prefix . 'lwr_prizes'; $this->table_records = $wpdb->prefix . 'lwr_records'; $this->table_user_attempts = $wpdb->prefix . 'lwr_user_attempts'; } public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } // 创建数据表 public function create_tables() { require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); $charset_collate = $this->wpdb->get_charset_collate(); // 创建奖品表 $sql_prizes = "CREATE TABLE IF NOT EXISTS {$this->table_prizes} ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, image_url VARCHAR(500), probability DECIMAL(5,4) DEFAULT 0.1, stock INT DEFAULT 0, daily_limit INT DEFAULT 0, type ENUM('physical', 'virtual', 'coupon') DEFAULT 'virtual', value VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) {$charset_collate};"; dbDelta($sql_prizes); // 创建其他表的SQL语句... // 插入默认奖品数据 $this->insert_default_prizes(); } // 插入默认奖品 private function insert_default_prizes() { $default_prizes = array( array('name' => '一等奖', 'probability' => 0.01, 'type' => 'virtual'), array('name' => '二等奖', 'probability' => 0.05, 'type' => 'virtual'), array('name' => '三等奖', 'probability' => 0.1, 'type' => 'virtual'), array('name' => '谢谢参与', 'probability' => 0.84, 'type' => 'virtual') ); foreach ($default_prizes as $prize) { $this->wpdb->insert( $this->table_prizes, $prize ); } } // 获取所有奖品 public function get_prizes() { return $this->wpdb->get_results( "SELECT * FROM {$this->table_prizes} ORDER BY probability ASC" ); } // 保存抽奖记录 public function save_record($user_id, $prize_id, $ip, $user_agent, $result) { return $this->wpdb->insert( $this->table_records, array( 'user_id' => $user_id, 'prize_id' => $prize_id, 'ip_address' => $ip, 'user_agent' => $user_agent, 'result' => $result ) ); } // 其他数据库操作方法... } 5. 前端实现:转盘界面与交互设计 5.1 HTML结构 <!-- templates/frontend-wheel.php --> <div id="lucky-wheel-container" class="lwr-container"> <div class="lwr-wheel-wrapper"> <canvas id="lucky-wheel-canvas" width="500" height="500"></canvas> <div class="lwr-wheel-pointer"> <div class="pointer-arrow"></div> </div> <button id="spin-button" class="lwr-spin-button">开始抽奖</button> </div> <div class="lwr-sidebar"> <div class="lwr-prize-list"> <h3>奖品列表</h3> <ul id="prize-list"> <!-- 通过JavaScript动态加载 --> </ul> </div> <div class="lwr-user-info"> <h3>我的信息</h3> <p>今日剩余次数: <span id="remaining-attempts">3</span></p> <p>总参与次数: <span id="total-attempts">0</span></p> </div> <div class="lwr-winning-history"> <h3>中奖记录</h3> <ul id="winning-history"> <!-- 通过JavaScript动态加载 --> </ul> </div> </div> <!-- 中奖结果弹窗 --> <div id="result-modal" class="lwr-modal"> <div class="lwr-modal-content"> <span class="lwr-close-modal">×</span> <h2 id="result-title">恭喜中奖!</h2> <div id="result-details"> <!-- 中奖详情 --> </div> <button id="claim-prize" class="lwr-claim-button">领取奖品</button> </div> </div> </div> 5.2 CSS样式设计 /* assets/css/lucky-wheel.css */ .lwr-container { display: flex; flex-wrap: wrap; max-width: 1200px; margin: 0 auto; padding: 20px; font-family: 'Arial', sans-serif; } .lwr-wheel-wrapper { position: relative; flex: 1; min-width: 300px; max-width: 500px; margin: 0 auto 30px; } #lucky-wheel-canvas { width: 100%; height: auto; border-radius: 50%; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); transition: transform 0.1s; } .lwr-wheel-pointer { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60px; height: 60px; z-index: 10; } .pointer-arrow { width: 0; height: 0; border-left: 30px solid transparent; border-right: 30px solid transparent; border-top: 50px solid #e74c3c; position: absolute; top: -40px; left: 0; } .lwr-spin-button { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100px; height: 100px; border-radius: 50%; background: linear-gradient(145deg, #e74c3c, #c0392b); color: white; border: none; font-size: 18px; font-weight: bold; cursor: pointer; z-index: 5; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); transition: all 0.3s; } .lwr-spin-button:hover { transform: translate(-50%, -50%) scale(1.05); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4); } .lwr-spin-button:disabled { background: #95a5a6; cursor: not-allowed; transform: translate(-50%, -50%) scale(1); } .lwr-sidebar { flex: 1; min-width: 300px; padding: 20px; background: #f8f9fa; border-radius: 10px; margin-left: 20px; } .lwr-prize-list, .lwr-user-info, .lwr-winning-history { margin-bottom: 30px; padding: 15px; background: white; border-radius: 8px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08); } /* 响应式设计 */ @media (max-width: 768px) { .lwr-container { flex-direction: column; } .lwr-sidebar { margin-left: 0; margin-top: 20px; } .lwr-wheel-wrapper { max-width: 100%; } } 5.3 JavaScript交互逻辑 // assets/js/lucky-wheel.js class LuckyWheel { constructor() { this.canvas = document.getElementById('lucky-wheel-canvas'); this.ctx = this.canvas.getContext('2d'); this.spinButton = document.getElementById('spin-button'); this.resultModal = document.getElementById('result-modal'); this.remainingAttempts = document.getElementById('remaining-attempts'); this.totalAttempts = document.getElementById('total-attempts'); this.prizes = []; this.isSpinning = false; this.currentRotation = 0; this.wheelRadius = Math.min(this.canvas.width, this.canvas.height) / 2; this.centerX = this.canvas.width / 2; this.centerY = this.canvas.height / 2; this.init(); } async init() { // 加载奖品数据 await this.loadPrizes(); // 绘制转盘 this.drawWheel(); // 绑定事件 this.bindEvents(); // 加载用户数据 this.loadUserData(); } async loadPrizes() { try { const response = await fetch('/wp-json/lwr/v1/prizes'); this.prizes = await response.json(); this.updatePrizeList(); } catch (error) { console.error('加载奖品数据失败:', error); } } drawWheel() { const ctx = this.ctx; const totalPrizes = this.prizes.length; const angleStep = (2 * Math.PI) / totalPrizes; // 清空画布 ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 绘制转盘扇形 for (let i = 0; i < totalPrizes; i++) { const startAngle = i * angleStep + this.currentRotation; const endAngle = (i + 1) * angleStep + this.currentRotation; // 交替颜色 const color = i % 2 === 0 ? '#3498db' : '#2ecc71'; // 绘制扇形 ctx.beginPath(); ctx.moveTo(this.centerX, this.centerY); ctx.arc(this.centerX, this.centerY, this.wheelRadius, startAngle, endAngle); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); // 绘制奖品文字 ctx.save(); ctx.translate(this.centerX, this.centerY); ctx.rotate(startAngle + angleStep / 2); ctx.textAlign = 'right'; ctx.fillStyle = '#fff'; ctx.font = 'bold 16px Arial'; ctx.fillText(this.prizes[i].name, this.wheelRadius - 20, 10); ctx.restore(); } // 绘制中心圆 ctx.beginPath(); ctx.arc(this.centerX, this.centerY, 30, 0, 2 * Math.PI); ctx.fillStyle = '#e74c3c'; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 4; ctx.stroke(); } bindEvents() { this.spinButton.addEventListener('click', () => this.spinWheel()); // 关闭弹窗 document.querySelector('.lwr-close-modal').addEventListener('click', () => { this.resultModal.style.display = 'none'; }); // 领取奖品按钮 document.getElementById('claim-prize').addEventListener('click', () => { }); } async spinWheel() { if (this.isSpinning) return; // 检查抽奖次数 const remaining = parseInt(this.remainingAttempts.textContent); if (remaining <= 0) { alert('今日抽奖次数已用完!'); return; } this.isSpinning = true; this.spinButton.disabled = true; // 发送抽奖请求 try { const response = await fetch('/wp-json/lwr/v1/spin', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': lwr_ajax.nonce }, body: JSON.stringify({}) }); const result = await response.json(); if (result.success) { // 执行转盘动画 await this.animateWheel(result.prize_index); // 显示中奖结果 this.showResult(result.prize); // 更新用户数据 this.updateUserData(result.user_data); } else { alert(result.message || '抽奖失败,请重试!'); this.isSpinning = false; this.spinButton.disabled = false; } } catch (error) { console.error('抽奖请求失败:', error); this.isSpinning = false; this.spinButton.disabled = false; } } animateWheel(prizeIndex) { return new Promise((resolve) => { const totalPrizes = this.prizes.length; const targetRotation = this.currentRotation + 5 * Math.PI + (prizeIndex * (2 * Math.PI / totalPrizes)); const duration = 5000; // 动画持续时间(毫秒) const startTime = Date.now(); const startRotation = this.currentRotation; const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); // 缓动函数,使动画先快后慢 const easeOut = 1 - Math.pow(1 - progress, 3); this.currentRotation = startRotation + (targetRotation - startRotation) * easeOut; this.drawWheel(); if (progress < 1) { requestAnimationFrame(animate); } else { this.isSpinning = false; this.spinButton.disabled = false; resolve(); } }; animate(); }); } showResult(prize) { const resultTitle = document.getElementById('result-title'); const resultDetails = document.getElementById('result-details'); if (prize.name === '谢谢参与') { resultTitle.textContent = '很遗憾,未中奖'; resultDetails.innerHTML = ` <p>您抽中了: <strong>${prize.name}</strong></p> <p>不要灰心,明天再来试试!</p> `; } else { resultTitle.textContent = '恭喜您中奖了!'; resultDetails.innerHTML = ` <div class="prize-result"> <h3>${prize.name}</h3> <p>${prize.description || ''}</p> <p class="prize-value">奖品价值: ${prize.value || '无'}</p> </div> `; } this.resultModal.style.display = 'block'; } updateUserData(userData) { if (userData.remaining_attempts !== undefined) { this.remainingAttempts.textContent = userData.remaining_attempts; } if (userData.total_attempts !== undefined) { this.totalAttempts.textContent = userData.total_attempts; } } updatePrizeList() { const prizeList = document.getElementById('prize-list'); prizeList.innerHTML = ''; this.prizes.forEach(prize => { const li = document.createElement('li'); li.className = 'prize-item'; li.innerHTML = ` <span class="prize-name">${prize.name}</span> <span class="prize-probability">${(prize.probability * 100).toFixed(2)}%</span> ${prize.stock > 0 ? `<span class="prize-stock">剩余: ${prize.stock}</span>` : ''} `; prizeList.appendChild(li); }); } async loadUserData() { try { const response = await fetch('/wp-json/lwr/v1/user-data'); const data = await response.json(); if (data.success) { this.updateUserData(data.data); } } catch (error) { console.error('加载用户数据失败:', error); } } async claimPrize() { // 领取奖品的逻辑 alert('奖品领取功能开发中...'); this.resultModal.style.display = 'none'; } } // 页面加载完成后初始化document.addEventListener('DOMContentLoaded', () => { new LuckyWheel(); }); ## 6. 后端开发:抽奖逻辑与数据管理 ### 6.1 抽奖逻辑实现 // includes/class-lottery-logic.phpclass LWR_Lottery_Logic { private $db; public function __construct() { $this->db = LWR_Database::get_instance(); } /** * 执行抽奖逻辑 */ public function spin_wheel($user_id = 0) { // 检查用户抽奖资格 $can_spin = $this->check_user_eligibility($user_id); if (!$can_spin['success']) { return $can_spin; } // 获取可用奖品 $available_prizes = $this->get_available_prizes(); if (empty($available_prizes)) { return array( 'success' => false, 'message' => '暂无可用奖品' ); } // 根据概率计算中奖奖品 $winning_prize = $this->calculate_winning_prize($available_prizes); // 记录抽奖结果 $record_id = $this->record_spin_result($user_id, $winning_prize); if (!$record_id) { return array( 'success' => false, 'message' => '记录抽奖结果失败' ); } // 更新用户抽奖次数 $this->update_user_attempts($user_id); // 更新奖品库存 if ($winning_prize->id && $winning_prize->stock > 0) { $this->update_prize_stock($winning_prize->id); } // 准备返回数据 $prize_index = array_search($winning_prize, array_values($available_prizes)); return array( 'success' => true, 'prize' => $winning_prize, 'prize_index' => $prize_index, 'user_data' => $this->get_user_spin_data($user_id) ); } /** * 检查用户抽奖资格 */ private function check_user_eligibility($user_id) { global $wpdb; // 检查IP限制(防止刷奖) $ip_address = $this->get_client_ip(); $today = date('Y-m-d'); // 同一IP今日抽奖次数限制 $ip_attempts = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}lwr_records WHERE ip_address = %s AND DATE(created_at) = %s", $ip_address, $today )); if ($ip_attempts >= 10) { // IP限制10次/天 return array( 'success' => false, 'message' => '今日抽奖次数已达上限' ); } // 注册用户检查 if ($user_id > 0) { $user_attempts = $this->get_user_attempts($user_id); // 每日次数限制 if ($user_attempts['attempts_today'] >= 5) { return array( 'success' => false, 'message' => '您今日的抽奖次数已用完' ); } // 总次数限制 if ($user_attempts['total_attempts'] >= 100) { return array( 'success' => false, 'message' => '您的总抽奖次数已用完' ); } } else { // 未登录用户限制 $guest_attempts = $this->get_guest_attempts($ip_address); if ($guest_attempts >= 3) { return array( 'success' => false, 'message' => '请登录后继续抽奖' ); } } return array('success' => true); } /** * 获取可用奖品列表 */ private function get_available_prizes() { global $wpdb; $today = date('Y-m-d'); $prizes = $wpdb->get_results( "SELECT p.*, (SELECT COUNT(*) FROM {$wpdb->prefix}lwr_records r WHERE r.prize_id = p.id AND DATE(r.created_at) = '{$today}') as today_wins FROM {$wpdb->prefix}lwr_prizes p WHERE p.stock > 0 OR p.stock = -1 ORDER BY p.probability ASC" ); // 过滤掉达到每日限制的奖品 $available_prizes = array(); foreach ($prizes as $prize) { if ($prize->daily_limit == 0 || $prize->today_wins < $prize->daily_limit) { $available_prizes[] = $prize; } } // 确保至少有一个"谢谢参与"选项 $has_thank_you = false; foreach ($available_prizes as $prize) { if (strpos($prize->name, '谢谢参与') !== false) { $has_thank_you = true; break; } } if (!$has_thank_you) { $thank_you_prize = (object) array( 'id' => 0, 'name' => '谢谢参与', 'description' => '感谢参与,下次好运!', 'probability' => 0.5, 'stock' => -1, 'type' => 'virtual' ); array_push($available_prizes, $thank_you_prize); } return $available_prizes; } /** * 根据概率计算中奖奖品 */ private function calculate_winning_prize($prizes) { // 计算总概率 $total_probability = 0; foreach ($prizes as $prize) { $total_probability += floatval($prize->probability); } // 生成随机数 $random = mt_rand() / mt_getrandmax() * $total_probability; // 根据概率选择奖品 $current_probability = 0; foreach ($prizes as $prize) { $current_probability += floatval($prize->probability); if ($random <= $current_probability) { return $prize; } } // 默认返回最后一个奖品(谢谢参与) return end($prizes); } /** * 获取客户端IP */ private function get_client_ip() { $ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'); foreach ($ip_keys as $key) { if (array_key_exists($key, $_SERVER) === true) { foreach (explode(',', $_SERVER[$key]) as $ip) { $ip = trim($ip); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; } // 其他辅助方法... } ### 6.2 REST API端点注册 // 在主插件文件中添加add_action('rest_api_init', function() { // 获取奖品列表 register_rest_route('lwr/v1', '/prizes', array( 'methods' => 'GET', 'callback' => 'lwr_api_get_prizes', 'permission_callback' => '__return_true' )); // 执行抽奖 register_rest_route('lwr/v1', '/spin', array( 'methods' => 'POST', 'callback' => 'lwr_api_spin_wheel', 'permission_callback' => function() { return is_user_logged_in() || $this->check_guest_permission(); } )); // 获取用户数据 register_rest_route('lwr/v1', '/user-data', array( 'methods' => 'GET', 'callback' => 'lwr_api_get_user_data', 'permission_callback' => '__return_true' )); }); function lwr_api_get_prizes() { $db = LWR_Database::get_instance(); $prizes = $db->get_prizes(); return new WP_REST_Response(array( 'success' => true, 'data' => $prizes ), 200); } function lwr_api_spin_wheel($request) { $user_id = get_current_user_id(); $logic = new LWR_Lottery_Logic(); $result = $logic->spin_wheel($user_id); return new WP_REST_Response($result, $result['success'] ? 200 : 400); } function lwr_api_get_user_data($request) { $user_id = get_current_user_id(); $logic = new LWR_Lottery_Logic(); $user_data = $logic->get_user_spin_data($user_id); return new WP_REST_Response(array( 'success' => true, 'data' => $user_data ), 200); } ## 7. WordPress集成与插件化开发 ### 7.1 主插件文件结构 <?php/** Plugin Name: 幸运大转盘抽奖系统 Plugin URI: https://yourwebsite.com/lucky-wheel Description: 为WordPress网站添加在线抽奖和幸运大转盘功能 Version: 1.0.0 Author: Your Name License: GPL v2 or later */ // 防止直接访问if (!defined('ABSPATH')) { exit; } // 定义插件常量define('LWR_VERSION', '1.0.0');define('LWR_PLUGIN_DIR', plugin_dir_path(__FILE__));define('LWR_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件spl_autoload_register(function($class_name) { if (strpos($class_name, 'LWR_') === 0) { $file = LWR_PLUGIN_DIR . 'includes/' . 'class-' . strtolower(str_replace('_', '-', $class_name)) . '.php'; if (file_exists($file)) { require_once $file; } } }); // 初始化插件class Lucky_Wheel_Roulette { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // 激活/停用钩子 register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); // 初始化 add_action('plugins_loaded', array($this, 'init')); // 添加短代码 add_shortcode('lucky_wheel', array($this, 'shortcode_handler')); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 加载脚本和样式 add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } public function activate() { // 创建数据库表 $db = LWR_Database::get_instance(); $db->create_tables(); // 设置默认选项 $default_options = array( 'daily_limit_logged_in' => 5, 'daily_limit_guest' => 3, 'enable_sound' => true, 'wheel_colors' => array('#3498db', '#2ecc71', '#e74c3c', '#f39c12'), 'require_email' => false ); add_option('lwr_settings', $default_options); // 创建必要的页面 $this->create_pages(); } public function deactivate() { // 清理临时数据 // 注意:不删除用户数据和设置 } public function init() { // 初始化组件 if (is_admin()) { new LWR_Admin_Panel(); } } public function shortcode_handler($atts) { $atts = shortcode_atts(array( 'width' => '100%', '
发表评论WordPress集成教程:连接快递物流接口实现订单跟踪地图展示 引言:WordPress的无限扩展可能 在当今数字化商业环境中,一个功能完善的网站已不仅仅是信息展示的平台,更是企业与客户互动、提供服务的关键枢纽。WordPress作为全球最受欢迎的内容管理系统,其真正的强大之处在于可扩展性——通过代码二次开发,我们可以将各种互联网小工具集成到网站中,创造出独特而实用的功能。本教程将深入探讨如何在WordPress中集成快递物流接口,实现订单跟踪地图展示功能,同时展示WordPress代码二次开发实现常用互联网小工具的方法论。 对于电商网站而言,订单跟踪功能是提升客户体验的重要环节。传统的物流跟踪通常只提供文字状态的更新,而将物流信息与地图可视化结合,能够直观展示包裹的运输路径和当前位置,显著增强用户的信任感和参与度。这种功能的实现,正是WordPress扩展能力的绝佳例证。 第一章:准备工作与环境配置 1.1 开发环境搭建 在开始集成开发之前,我们需要确保拥有合适的开发环境。建议使用本地开发环境如XAMPP、MAMP或Local by Flywheel,这些工具能够快速搭建PHP、MySQL和Apache/Nginx环境。对于WordPress开发,确保你的环境满足以下要求:PHP 7.4或更高版本、MySQL 5.6或更高版本、以及适当的内存限制(建议256MB以上)。 除了基础环境,我们还需要一些开发工具:代码编辑器(如VS Code、PHPStorm)、Git版本控制系统、浏览器开发者工具,以及用于API测试的工具(如Postman或Insomnia)。这些工具将在开发过程中发挥重要作用。 1.2 WordPress开发基础 理解WordPress的开发模式是成功集成的关键。WordPress采用主题和插件两种扩展方式:主题控制网站的外观和展示,而插件则添加特定功能。对于物流跟踪功能,我们通常选择创建专用插件,这样可以保持功能的独立性,便于维护和迁移。 WordPress插件的基本结构包括主插件文件、资产文件(CSS、JavaScript)、模板文件以及可能的语言文件。所有插件文件应放置在wp-content/plugins目录下的独立文件夹中。插件的主文件必须包含标准的插件头信息,这样WordPress才能识别并激活它。 1.3 选择快递物流API 市场上有多种快递物流API可供选择,如快递鸟、快递100、阿里云物流跟踪等。选择API时需要考虑以下因素:支持的快递公司数量、查询稳定性、更新频率、费用结构以及文档完整性。本教程将以快递鸟API为例,但方法和原理适用于大多数物流API。 注册选定的API服务后,我们将获得关键的访问凭证:通常是API Key和Secret Key。这些凭证需要安全地存储在WordPress中,建议使用WordPress的选项API或自定义数据库表进行存储,避免硬编码在插件文件中。 第二章:创建物流跟踪插件框架 2.1 插件基础结构 首先创建插件目录和主文件。在wp-content/plugins目录下创建新文件夹“shipment-tracker”,然后在该文件夹中创建主文件“shipment-tracker.php”: <?php /** * Plugin Name: Shipment Tracker * Plugin URI: https://yourwebsite.com/shipment-tracker * Description: 集成快递物流接口,实现订单跟踪地图展示功能 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later * Text Domain: shipment-tracker */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('ST_VERSION', '1.0.0'); define('ST_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('ST_PLUGIN_URL', plugin_dir_url(__FILE__)); define('ST_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 初始化插件 require_once ST_PLUGIN_DIR . 'includes/class-shipment-tracker.php'; function run_shipment_tracker() { $plugin = new Shipment_Tracker(); $plugin->run(); } run_shipment_tracker(); 2.2 创建核心类 在includes目录下创建核心类文件class-shipment-tracker.php: <?php class Shipment_Tracker { private $loader; public function __construct() { $this->load_dependencies(); $this->define_admin_hooks(); $this->define_public_hooks(); } private function load_dependencies() { require_once ST_PLUGIN_DIR . 'includes/class-api-handler.php'; require_once ST_PLUGIN_DIR . 'includes/class-database-handler.php'; require_once ST_PLUGIN_DIR . 'includes/class-shortcode-handler.php'; require_once ST_PLUGIN_DIR . 'includes/class-admin-settings.php'; } private function define_admin_hooks() { // 管理员相关钩子 $admin_settings = new Admin_Settings(); add_action('admin_menu', array($admin_settings, 'add_admin_menu')); add_action('admin_init', array($admin_settings, 'register_settings')); } private function define_public_hooks() { // 前端相关钩子 $shortcode_handler = new Shortcode_Handler(); add_shortcode('track_shipment', array($shortcode_handler, 'render_tracking_form')); add_shortcode('shipment_map', array($shortcode_handler, 'render_tracking_map')); // 添加前端脚本和样式 add_action('wp_enqueue_scripts', array($this, 'enqueue_public_scripts')); } public function enqueue_public_scripts() { wp_enqueue_style('shipment-tracker-style', ST_PLUGIN_URL . 'assets/css/public.css', array(), ST_VERSION); wp_enqueue_script('shipment-tracker-script', ST_PLUGIN_URL . 'assets/js/public.js', array('jquery'), ST_VERSION, true); // 本地化脚本,传递数据到JavaScript wp_localize_script('shipment-tracker-script', 'st_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('st_nonce') )); } public function run() { // 插件运行入口 } } 2.3 数据库设计 物流跟踪数据需要适当的数据库结构来存储。我们将创建自定义数据库表来存储物流查询结果,减少对API的重复调用: // 在class-database-handler.php中 class Database_Handler { public function create_tables() { global $wpdb; $table_name = $wpdb->prefix . 'shipment_tracking'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, order_id varchar(50) NOT NULL, tracking_number varchar(100) NOT NULL, carrier_code varchar(50) NOT NULL, status varchar(50) DEFAULT '', last_update datetime DEFAULT CURRENT_TIMESTAMP, raw_data longtext, PRIMARY KEY (id), INDEX tracking_idx (tracking_number, carrier_code) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } public function get_tracking_data($tracking_number, $carrier_code) { global $wpdb; $table_name = $wpdb->prefix . 'shipment_tracking'; return $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE tracking_number = %s AND carrier_code = %s", $tracking_number, $carrier_code )); } public function update_tracking_data($data) { global $wpdb; $table_name = $wpdb->prefix . 'shipment_tracking'; // 检查记录是否存在 $existing = $this->get_tracking_data($data['tracking_number'], $data['carrier_code']); if ($existing) { // 更新现有记录 return $wpdb->update( $table_name, array( 'status' => $data['status'], 'last_update' => current_time('mysql'), 'raw_data' => maybe_serialize($data['raw_data']) ), array('id' => $existing->id) ); } else { // 插入新记录 return $wpdb->insert( $table_name, array( 'order_id' => $data['order_id'], 'tracking_number' => $data['tracking_number'], 'carrier_code' => $data['carrier_code'], 'status' => $data['status'], 'last_update' => current_time('mysql'), 'raw_data' => maybe_serialize($data['raw_data']) ) ); } } } 第三章:集成快递物流API 3.1 API处理类设计 API处理类负责与物流API进行通信,处理请求和响应。我们创建一个独立的类来处理这些逻辑: <?php class API_Handler { private $api_key; private $api_secret; private $api_url = 'https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx'; public function __construct() { $options = get_option('st_settings'); $this->api_key = isset($options['api_key']) ? $options['api_key'] : ''; $this->api_secret = isset($options['api_secret']) ? $options['api_secret'] : ''; } public function track_shipment($tracking_number, $carrier_code) { // 首先检查本地数据库是否有缓存数据 $db_handler = new Database_Handler(); $cached_data = $db_handler->get_tracking_data($tracking_number, $carrier_code); // 如果缓存数据在1小时内更新过,直接返回 if ($cached_data && strtotime($cached_data->last_update) > time() - 3600) { return maybe_unserialize($cached_data->raw_data); } // 否则调用API $request_data = array( 'OrderCode' => '', 'ShipperCode' => $carrier_code, 'LogisticCode' => $tracking_number ); $data_json = json_encode($request_data); $datasign = $this->encrypt($data_json, $this->api_secret); $post_data = array( 'RequestData' => urlencode($data_json), 'EBusinessID' => $this->api_key, 'RequestType' => '1002', 'DataSign' => urlencode($datasign), 'DataType' => '2' ); $response = $this->send_request($this->api_url, $post_data); if ($response && isset($response['Success']) && $response['Success']) { // 保存到数据库 $db_handler->update_tracking_data(array( 'tracking_number' => $tracking_number, 'carrier_code' => $carrier_code, 'status' => $response['State'] ?? '', 'raw_data' => $response )); return $response; } return false; } private function send_request($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } private function encrypt($data, $app_key) { return urlencode(base64_encode(md5($data . $app_key))); } public function get_carriers() { // 获取支持的快递公司列表 $carriers = array( 'SF' => '顺丰速运', 'HTKY' => '百世快递', 'ZTO' => '中通快递', 'STO' => '申通快递', 'YTO' => '圆通速递', 'YD' => '韵达速递', 'YZPY' => '邮政快递包裹', 'EMS' => 'EMS', 'HHTT' => '天天快递', 'JD' => '京东物流' ); return apply_filters('st_available_carriers', $carriers); } } 3.2 错误处理与日志记录 健壮的API集成需要完善的错误处理机制。我们添加错误处理和日志记录功能: class Logger { public static function log($message, $level = 'info') { if (!defined('WP_DEBUG') || !WP_DEBUG) { return; } $log_dir = ST_PLUGIN_DIR . 'logs/'; // 确保日志目录存在 if (!file_exists($log_dir)) { wp_mkdir_p($log_dir); } $log_file = $log_dir . 'shipment-tracker-' . date('Y-m-d') . '.log'; $timestamp = current_time('mysql'); $log_message = "[$timestamp] [$level] $message" . PHP_EOL; file_put_contents($log_file, $log_message, FILE_APPEND); } public static function log_api_error($tracking_number, $carrier_code, $error) { $message = sprintf( 'API查询失败 - 运单号: %s, 快递公司: %s, 错误: %s', $tracking_number, $carrier_code, $error ); self::log($message, 'error'); } } // 在API_Handler类中添加错误处理 public function track_shipment($tracking_number, $carrier_code) { try { // ... 原有代码 ... } catch (Exception $e) { Logger::log_api_error($tracking_number, $carrier_code, $e->getMessage()); return false; } } 第四章:前端展示与地图集成 4.1 创建跟踪表单短代码 短代码是WordPress中在前端嵌入功能的便捷方式。我们创建跟踪表单短代码: class Shortcode_Handler { public function render_tracking_form($atts) { $atts = shortcode_atts(array( 'title' => '物流跟踪查询', 'button_text' => '查询' ), $atts, 'track_shipment'); ob_start(); ?> <div class="shipment-tracker-form"> <h3><?php echo esc_html($atts['title']); ?></h3> <form id="st-tracking-form" method="post"> <?php wp_nonce_field('st_tracking_action', 'st_tracking_nonce'); ?> <div class="form-group"> <label for="tracking_number">运单号:</label> <input type="text" id="tracking_number" name="tracking_number" required> </div> <div class="form-group"> <label for="carrier_code">快递公司:</label> <select id="carrier_code" name="carrier_code" required> <option value="">请选择快递公司</option> <?php $api_handler = new API_Handler(); $carriers = $api_handler->get_carriers(); foreach ($carriers as $code => $name) { echo '<option value="' . esc_attr($code) . '">' . esc_html($name) . '</option>'; } ?> </select> </div> <div class="form-group"> <button type="submit"><?php echo esc_html($atts['button_text']); ?></button> </div> </form> <div id="st-tracking-result"></div> </div> <?php return ob_get_clean(); } } 4.2 集成地图展示功能 地图展示是物流跟踪的亮点功能。我们使用Leaflet.js,这是一个开源的移动友好交互地图库: public function render_tracking_map($atts) { $atts = shortcode_atts(array( 'height' => '400px', 'width' => '100%' ), $atts, 'shipment_map'); wp_enqueue_script('leaflet', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.js', array(), '1.7.1'); wp_enqueue_style('leaflet-css', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.css', array(), '1.7.1'); ob_start(); ?> <div id="shipment-map" style="height: <?php echo esc_attr($atts['height']); ?>; width: <?php echo esc_attr($atts['width']); ?>;"></div> <script> jQuery(document).ready(function($) { // 初始化地图 var map = L.map('shipment-map').setView([39.9042, 116.4074], 5); // 默认北京中心 // 添加地图图层 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map); // 如果有跟踪数据,显示路径 <?php if (isset($_GET['tracking_data'])) : ?> var trackingData = <?php echo json_encode($_GET['tracking_data']); ?>; displayTrackingPath(map, trackingData); <?php endif; ?> function displayTrackingPath(map, data) { var path = []; var markers = []; // 处理轨迹点 if (data.Traces && data.Traces.length > 0) { data.Traces.forEach(function(trace, index) { // 这里需要根据实际API返回的地址信息转换为坐标 // 实际应用中可能需要地理编码服务 var coords = geocodeAddress(trace.AcceptStation); if (coords) { path.push(coords); // 添加标记点 var marker = L.marker(coords) .bindPopup('<b>' + trace.AcceptTime + '</b><br>' + trace.AcceptStation) .addTo(map); markers.push(marker); // 如果是第一个或最后一个点,特别标记 if (index === 0) { marker.setIcon(L.icon({ iconUrl: '<?php echo ST_PLUGIN_URL; ?>assets/images/start-marker.png', iconSize: [32, 32] })); } else if (index === data.Traces.length - 1) { marker.setIcon(L.icon({ iconUrl: '<?php echo ST_PLUGIN_URL; ?>assets/images/end-marker.png', iconSize: [32, 32] })); } } }); // 绘制路径线 if (path.length > 1) { var polyline = L.polyline(path, { color: '#3498db', weight: 3, opacity: 0.7 }).addTo(map); // 调整地图视野以显示完整路径 map.fitBounds(polyline.getBounds()); } } } // 简化的地理编码函数(实际应用中应使用专业地理编码服务) function geocodeAddress(address) { // 这里应调用地理编码API // 为示例目的,返回随机坐标 return [ 39.9042 + (Math.random() - 0.5) * 10, 116.4074 + (Math.random() - 0.5) * 10 ]; } }); </script> <?php return ob_get_clean(); } #### 4.3 AJAX交互实现 为了实现无需页面刷新的查询体验,我们添加AJAX处理功能: // 在Shortcode_Handler类中添加AJAX处理方法public function register_ajax_handlers() { add_action('wp_ajax_st_track_shipment', array($this, 'handle_tracking_request')); add_action('wp_ajax_nopriv_st_track_shipment', array($this, 'handle_tracking_request')); } public function handle_tracking_request() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'st_nonce')) { wp_die('安全验证失败'); } $tracking_number = sanitize_text_field($_POST['tracking_number']); $carrier_code = sanitize_text_field($_POST['carrier_code']); $api_handler = new API_Handler(); $result = $api_handler->track_shipment($tracking_number, $carrier_code); if ($result) { wp_send_json_success(array( 'data' => $result, 'html' => $this->generate_tracking_html($result) )); } else { wp_send_json_error('查询失败,请检查运单号和快递公司是否正确'); } } private function generate_tracking_html($data) { ob_start(); ?> <div class="tracking-result"> <div class="tracking-header"> <h4>物流跟踪信息</h4> <div class="tracking-status"> 状态:<span class="status-badge status-<?php echo esc_attr(strtolower($data['State'] ?? '')); ?>"> <?php echo $this->get_status_text($data['State'] ?? ''); ?> </span> </div> </div> <div class="tracking-timeline"> <?php if (!empty($data['Traces'])) : ?> <?php foreach (array_reverse($data['Traces']) as $trace) : ?> <div class="timeline-item"> <div class="timeline-dot"></div> <div class="timeline-content"> <div class="timeline-time"><?php echo esc_html($trace['AcceptTime']); ?></div> <div class="timeline-desc"><?php echo esc_html($trace['AcceptStation']); ?></div> </div> </div> <?php endforeach; ?> <?php else : ?> <p>暂无物流信息</p> <?php endif; ?> </div> <div class="tracking-actions"> <button class="view-map-btn" data-tracking='<?php echo json_encode($data); ?>'> 查看运输路径地图 </button> </div> </div> <?php return ob_get_clean(); } ### 第五章:后台管理与设置 #### 5.1 创建设置页面 为插件创建专业的设置页面,让管理员可以配置API密钥和其他选项: class Admin_Settings { public function add_admin_menu() { add_menu_page( '物流跟踪设置', '物流跟踪', 'manage_options', 'shipment-tracker', array($this, 'render_settings_page'), 'dashicons-location-alt', 30 ); add_submenu_page( 'shipment-tracker', 'API设置', 'API设置', 'manage_options', 'shipment-tracker-api', array($this, 'render_api_settings_page') ); add_submenu_page( 'shipment-tracker', '查询记录', '查询记录', 'manage_options', 'shipment-tracker-logs', array($this, 'render_logs_page') ); } public function register_settings() { register_setting('st_settings_group', 'st_settings'); add_settings_section( 'st_api_section', 'API配置', array($this, 'render_api_section'), 'shipment-tracker-api' ); add_settings_field( 'api_key', 'API Key', array($this, 'render_api_key_field'), 'shipment-tracker-api', 'st_api_section' ); add_settings_field( 'api_secret', 'API Secret', array($this, 'render_api_secret_field'), 'shipment-tracker-api', 'st_api_section' ); } public function render_settings_page() { ?> <div class="wrap"> <h1>物流跟踪设置</h1> <div class="st-admin-container"> <div class="st-admin-card"> <h2>插件使用说明</h2> <p>1. 在<a href="<?php echo admin_url('admin.php?page=shipment-tracker-api'); ?>">API设置</a>页面配置您的物流API密钥</p> <p>2. 使用短代码在页面中插入跟踪表单:<code>[track_shipment]</code></p> <p>3. 使用短代码插入地图展示:<code>[shipment_map]</code></p> <p>4. 可选参数:<code>[track_shipment title="我的标题" button_text="查询物流"]</code></p> </div> <div class="st-admin-card"> <h2>统计信息</h2> <?php global $wpdb; $table_name = $wpdb->prefix . 'shipment_tracking'; $total_queries = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); $today_queries = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE DATE(last_update) = %s", current_time('Y-m-d') )); ?> <p>总查询次数:<?php echo $total_queries; ?></p> <p>今日查询:<?php echo $today_queries; ?></p> </div> </div> </div> <?php } public function render_api_settings_page() { ?> <div class="wrap"> <h1>API设置</h1> <form method="post" action="options.php"> <?php settings_fields('st_settings_group'); do_settings_sections('shipment-tracker-api'); submit_button(); ?> </form> <div class="st-api-test"> <h3>API连接测试</h3> <input type="text" id="test-tracking-number" placeholder="测试运单号"> <select id="test-carrier-code"> <option value="">选择快递公司</option> <?php $api_handler = new API_Handler(); $carriers = $api_handler->get_carriers(); foreach ($carriers as $code => $name) { echo '<option value="' . esc_attr($code) . '">' . esc_html($name) . '</option>'; } ?> </select> <button id="test-api-btn" class="button">测试连接</button> <div id="test-result"></div> </div> </div> <?php } public function render_logs_page() { ?> <div class="wrap"> <h1>查询记录</h1> <?php $this->render_logs_table(); ?> </div> <?php } private function render_logs_table() { global $wpdb; $table_name = $wpdb->prefix . 'shipment_tracking'; // 分页逻辑 $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; $total_items = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); $logs = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name ORDER BY last_update DESC LIMIT %d OFFSET %d", $per_page, $offset ) ); ?> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>运单号</th> <th>快递公司</th> <th>状态</th> <th>最后更新</th> <th>操作</th> </tr> </thead> <tbody> <?php if (empty($logs)) : ?> <tr> <td colspan="6">暂无查询记录</td> </tr> <?php else : ?> <?php foreach ($logs as $log) : ?> <tr> <td><?php echo $log->id; ?></td> <td><?php echo esc_html($log->tracking_number); ?></td> <td><?php echo esc_html($log->carrier_code); ?></td> <td> <span class="status-badge status-<?php echo strtolower($log->status); ?>"> <?php echo $this->get_status_text($log->status); ?> </span> </td> <td><?php echo $log->last_update; ?></td> <td> <button class="button view-log-details" data-log-id="<?php echo $log->id; ?>"> 查看详情 </button> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> <div class="tablenav bottom"> <div class="tablenav-pages"> <?php $total_pages = ceil($total_items / $per_page); echo paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $total_pages, 'current' => $current_page )); ?> </div> </div> <?php } } #### 5.2 添加管理员通知和帮助标签 public function add_admin_notices() { $options = get_option('st_settings'); if (empty($options['api_key']) || empty($options['api_secret'])) { ?> <div class="notice notice-warning"> <p>物流跟踪插件需要配置API密钥才能正常工作。请前往<a href="<?php echo admin_url('admin.php?page=shipment-tracker-api'); ?>">设置页面</a>进行配置。</p> </div> <?php } } public function add_help_tab() { $screen = get_current_screen(); if ($screen->id === 'toplevel_page_shipment-tracker') { $screen->add_help_tab(array( 'id' => 'st_help_tab', 'title' => '使用帮助', 'content' => ' <h3>物流跟踪插件使用指南</h3> <p><strong>1. 配置API</strong><br> 首先需要在API设置页面填写从快递鸟或其他物流API服务商获取的API密钥。</p> <p><strong>2. 插入短代码</strong><br> 在文章或页面中使用以下短代码:<br> - 跟踪表单:<code>[track_shipment]</code><br> - 地图展示:<code>[shipment_map]</code></p> <p><strong>3. 自定义样式</strong><br> 可以通过CSS自定义插件外观,样式文件位于:<code>/wp-content/plugins/shipment-tracker/assets/css/</code></p> ' )); } } ### 第六章:样式优化与响应式设计 #### 6.1 创建CSS样式文件 在assets/css目录下创建public.css文件: / 物流跟踪插件样式 /.shipment-tracker-form { max-width: 600px; margin: 20px auto; padding: 30px; background: #f8f9fa; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .shipment-tracker-form h3 { margin-top: 0; color: #333; border-bottom: 2px solid #3498db; padding-bottom: 10px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #555; } .form-group input[type="text"],.form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; transition: border-color 0.3s; } .form-group input[type="text"]:focus,.form-group select:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 2px rgba(52,152,219,0.2); } .form-group button { background: #3498db; color: white; border: none; padding: 12px 30px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background 0.3s; } .form-group button:hover { background: #2980b9; } / 跟踪结果样式 /.tracking-result { margin-top: 30px; padding: 20px; background: white; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .tracking-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .status-badge { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; } .status-2 { background: #2ecc71; color: white; } / 已签收 /.status-3 { background: #e74c3c; color: white; } / 问题件 /.status-1 { background: #3498db; color: white; } / 运输中 /.status-0 { background: #95a5a6; color: white; } / 无信息 / / 时间线样式 /.tracking-timeline { position: relative; padding-left: 30px; } .tracking-timeline::before { content: ''; position: absolute; left: 10px; top: 0; bottom: 0; width: 2px; background: #3498db; } .timeline-item { position: relative; margin-bottom: 20px; } .timeline-dot { position: absolute; left: -25px; top: 5px; width: 12px; height: 12px; background: white; border: 2px solid #3498db; border-radius: 50%; } .timeline-content { padding: 10px; background: #f8f9fa; border-radius: 4px; } .timeline-time { font-weight: 600; color: #2c3e50; margin-bottom: 5px; } .timeline-desc { color: #555; line-height: 1.5; } / 地图容器 / shipment-map { border-radius: 8px; overflow: hidden; box-shadow: 0
发表评论WordPress网站员工排班与考勤管理应用开发详细教程 引言:为什么选择WordPress开发企业应用? 在当今数字化时代,企业管理系统正逐渐从传统的桌面软件转向基于Web的解决方案。WordPress作为全球最流行的内容管理系统,不仅适用于博客和内容网站,其强大的扩展性和灵活性也使其成为开发企业级应用的理想平台。通过WordPress开发员工排班与考勤管理系统,企业可以享受以下优势: 成本效益:相比定制开发或购买专业软件,基于WordPress的解决方案成本更低 易于维护:WordPress拥有庞大的开发者社区和丰富的文档资源 高度可定制:可以根据企业具体需求进行灵活调整 集成能力:可与现有WordPress网站无缝集成,无需额外登录系统 本教程将详细指导您如何通过WordPress代码二次开发,创建一个功能完整的员工排班与考勤管理应用。 第一部分:开发环境准备与项目规划 1.1 开发环境搭建 在开始开发之前,我们需要准备合适的开发环境: // 推荐开发环境配置 - WordPress版本:5.8或更高 - PHP版本:7.4或更高 - MySQL版本:5.6或更高 - 本地开发环境:XAMPP、MAMP或Local by Flywheel - 代码编辑器:VS Code、PHPStorm或Sublime Text 1.2 创建自定义插件 为了避免主题更新导致功能丢失,我们将创建一个独立的插件: <?php /** * Plugin Name: 员工排班与考勤管理系统 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站添加员工排班与考勤管理功能 * Version: 1.0.0 * Author: 您的姓名 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('EMP_SCHEDULE_VERSION', '1.0.0'); define('EMP_SCHEDULE_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('EMP_SCHEDULE_PLUGIN_URL', plugin_dir_url(__FILE__)); 1.3 数据库表设计 我们需要创建几个数据库表来存储员工、排班和考勤数据: // 在插件激活时创建数据库表 register_activation_hook(__FILE__, 'emp_schedule_create_tables'); function emp_schedule_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 员工信息表 $employees_table = $wpdb->prefix . 'emp_employees'; $sql1 = "CREATE TABLE IF NOT EXISTS $employees_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, employee_id varchar(50) NOT NULL, full_name varchar(100) NOT NULL, department varchar(100), position varchar(100), hire_date date, status varchar(20) DEFAULT 'active', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY employee_id (employee_id) ) $charset_collate;"; // 排班表 $schedules_table = $wpdb->prefix . 'emp_schedules'; $sql2 = "CREATE TABLE IF NOT EXISTS $schedules_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, employee_id mediumint(9) NOT NULL, schedule_date date NOT NULL, shift_start time NOT NULL, shift_end time NOT NULL, shift_type varchar(50), notes text, created_by bigint(20), created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY employee_date (employee_id, schedule_date) ) $charset_collate;"; // 考勤记录表 $attendance_table = $wpdb->prefix . 'emp_attendance'; $sql3 = "CREATE TABLE IF NOT EXISTS $attendance_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, employee_id mediumint(9) NOT NULL, attendance_date date NOT NULL, check_in datetime, check_out datetime, status varchar(50), late_minutes int(11) DEFAULT 0, overtime_minutes int(11) DEFAULT 0, notes text, verified_by bigint(20), verified_at datetime, PRIMARY KEY (id), UNIQUE KEY employee_date (employee_id, attendance_date) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); dbDelta($sql3); } 第二部分:员工管理模块开发 2.1 员工信息管理界面 创建员工管理后台页面,允许管理员添加、编辑和删除员工信息: // 添加管理菜单 add_action('admin_menu', 'emp_schedule_admin_menu'); function emp_schedule_admin_menu() { // 主菜单 add_menu_page( '员工排班与考勤系统', '员工考勤', 'manage_options', 'emp-schedule', 'emp_schedule_dashboard_page', 'dashicons-calendar-alt', 30 ); // 子菜单 add_submenu_page( 'emp-schedule', '员工管理', '员工管理', 'manage_options', 'emp-employees', 'emp_employees_page' ); add_submenu_page( 'emp-schedule', '排班管理', '排班管理', 'manage_options', 'emp-schedules', 'emp_schedules_page' ); add_submenu_page( 'emp-schedule', '考勤记录', '考勤记录', 'manage_options', 'emp-attendance', 'emp_attendance_page' ); add_submenu_page( 'emp-schedule', '报表统计', '报表统计', 'manage_options', 'emp-reports', 'emp_reports_page' ); } // 员工管理页面 function emp_employees_page() { global $wpdb; // 处理表单提交 if (isset($_POST['add_employee'])) { // 验证和清理数据 $employee_data = array( 'user_id' => intval($_POST['user_id']), 'employee_id' => sanitize_text_field($_POST['employee_id']), 'full_name' => sanitize_text_field($_POST['full_name']), 'department' => sanitize_text_field($_POST['department']), 'position' => sanitize_text_field($_POST['position']), 'hire_date' => sanitize_text_field($_POST['hire_date']), 'status' => sanitize_text_field($_POST['status']) ); $table_name = $wpdb->prefix . 'emp_employees'; $wpdb->insert($table_name, $employee_data); echo '<div class="notice notice-success"><p>员工添加成功!</p></div>'; } // 获取员工列表 $employees_table = $wpdb->prefix . 'emp_employees'; $employees = $wpdb->get_results("SELECT * FROM $employees_table ORDER BY id DESC"); ?> <div class="wrap"> <h1 class="wp-heading-inline">员工管理</h1> <a href="#add-employee-form" class="page-title-action">添加新员工</a> <hr class="wp-header-end"> <!-- 员工列表 --> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>员工ID</th> <th>姓名</th> <th>部门</th> <th>职位</th> <th>入职日期</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <?php if ($employees): ?> <?php foreach ($employees as $employee): ?> <tr> <td><?php echo esc_html($employee->employee_id); ?></td> <td><?php echo esc_html($employee->full_name); ?></td> <td><?php echo esc_html($employee->department); ?></td> <td><?php echo esc_html($employee->position); ?></td> <td><?php echo esc_html($employee->hire_date); ?></td> <td> <span class="status-badge status-<?php echo esc_attr($employee->status); ?>"> <?php $status_labels = array( 'active' => '在职', 'inactive' => '离职', 'on_leave' => '休假' ); echo isset($status_labels[$employee->status]) ? $status_labels[$employee->status] : $employee->status; ?> </span> </td> <td> <a href="?page=emp-employees&action=edit&id=<?php echo $employee->id; ?>" class="button button-small">编辑</a> <a href="?page=emp-employees&action=delete&id=<?php echo $employee->id; ?>" class="button button-small button-link-delete" onclick="return confirm('确定要删除此员工吗?')">删除</a> </td> </tr> <?php endforeach; ?> <?php else: ?> <tr> <td colspan="7" style="text-align:center;">暂无员工数据</td> </tr> <?php endif; ?> </tbody> </table> <!-- 添加员工表单 --> <h2 id="add-employee-form">添加新员工</h2> <form method="post" action=""> <table class="form-table"> <tr> <th scope="row"><label for="employee_id">员工编号</label></th> <td><input type="text" id="employee_id" name="employee_id" required class="regular-text"></td> </tr> <tr> <th scope="row"><label for="full_name">姓名</label></th> <td><input type="text" id="full_name" name="full_name" required class="regular-text"></td> </tr> <tr> <th scope="row"><label for="department">部门</label></th> <td> <select id="department" name="department" class="regular-text"> <option value="">选择部门</option> <option value="技术部">技术部</option> <option value="市场部">市场部</option> <option value="销售部">销售部</option> <option value="人事部">人事部</option> <option value="财务部">财务部</option> </select> </td> </tr> <tr> <th scope="row"><label for="position">职位</label></th> <td><input type="text" id="position" name="position" class="regular-text"></td> </tr> <tr> <th scope="row"><label for="hire_date">入职日期</label></th> <td><input type="date" id="hire_date" name="hire_date" class="regular-text"></td> </tr> <tr> <th scope="row"><label for="status">状态</label></th> <td> <select id="status" name="status" class="regular-text"> <option value="active">在职</option> <option value="inactive">离职</option> <option value="on_leave">休假</option> </select> </td> </tr> </table> <?php submit_button('添加员工', 'primary', 'add_employee'); ?> </form> </div> <style> .status-badge { display: inline-block; padding: 3px 8px; border-radius: 3px; font-size: 12px; font-weight: bold; } .status-active { background-color: #d4edda; color: #155724; } .status-inactive { background-color: #f8d7da; color: #721c24; } .status-on_leave { background-color: #fff3cd; color: #856404; } </style> <?php } 2.2 员工数据导入导出功能 为了方便批量管理员工信息,我们添加导入导出功能: // 添加上传处理功能 function emp_handle_employee_import() { if (isset($_POST['import_employees']) && isset($_FILES['import_file'])) { $file = $_FILES['import_file']; if ($file['error'] === UPLOAD_ERR_OK) { $file_type = wp_check_filetype($file['name']); if ($file_type['ext'] === 'csv') { $handle = fopen($file['tmp_name'], 'r'); $header = fgetcsv($handle); // 跳过标题行 global $wpdb; $table_name = $wpdb->prefix . 'emp_employees'; $imported = 0; $skipped = 0; while (($data = fgetcsv($handle)) !== FALSE) { if (count($data) >= 6) { $employee_data = array( 'employee_id' => sanitize_text_field($data[0]), 'full_name' => sanitize_text_field($data[1]), 'department' => sanitize_text_field($data[2]), 'position' => sanitize_text_field($data[3]), 'hire_date' => sanitize_text_field($data[4]), 'status' => sanitize_text_field($data[5]) ); // 检查员工ID是否已存在 $existing = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE employee_id = %s", $employee_data['employee_id'] )); if (!$existing) { $wpdb->insert($table_name, $employee_data); $imported++; } else { $skipped++; } } } fclose($handle); echo '<div class="notice notice-success"><p>导入完成!成功导入 ' . $imported . ' 条记录,跳过 ' . $skipped . ' 条重复记录。</p></div>'; } else { echo '<div class="notice notice-error"><p>请上传CSV格式的文件。</p></div>'; } } } } // 导出功能 function emp_export_employees_csv() { if (isset($_GET['export_employees']) && $_GET['export_employees'] === '1') { global $wpdb; $table_name = $wpdb->prefix . 'emp_employees'; $employees = $wpdb->get_results("SELECT * FROM $table_name", ARRAY_A); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename=employees_' . date('Y-m-d') . '.csv'); $output = fopen('php://output', 'w'); // 添加BOM头,确保Excel正确识别UTF-8编码 fwrite($output, "xEFxBBxBF"); // 写入标题行 fputcsv($output, array('员工编号', '姓名', '部门', '职位', '入职日期', '状态')); // 写入数据 foreach ($employees as $employee) { fputcsv($output, array( $employee['employee_id'], $employee['full_name'], $employee['department'], $employee['position'], $employee['hire_date'], $employee['status'] )); } fclose($output); exit; } } add_action('admin_init', 'emp_export_employees_csv'); 第三部分:排班管理模块开发 3.1 排班日历视图 创建一个直观的日历界面来管理员工排班: // 排班管理页面 function emp_schedules_page() { global $wpdb; // 获取当前月份 $current_month = isset($_GET['month']) ? $_GET['month'] : date('Y-m'); ?> <div class="wrap"> <h1 class="wp-heading-inline">排班管理</h1> <a href="#add-schedule-form" class="page-title-action">添加排班</a> <hr class="wp-header-end"> <!-- 月份导航 --> <div class="month-navigation"> <?php $prev_month = date('Y-m', strtotime($current_month . ' -1 month')); $next_month = date('Y-m', strtotime($current_month . ' +1 month')); ?> <a href="?page=emp-schedules&month=<?php echo $prev_month; ?>" class="button">← 上月</a> <h2 style="display:inline-block; margin:0 20px;"><?php echo date('Y年m月', strtotime($current_month)); ?></h2> <a href="?page=emp-schedules&month=<?php echo $next_month; ?>" class="button">下月 →</a> </div> <!-- 排班日历 --> <div class="schedule-calendar"> <?php // 生成日历 $year = date('Y', strtotime($current_month)); $month = date('m', strtotime($current_month)); $first_day = mktime(0, 0, 0, $month, 1, $year); $days_in_month = date('t', $first_day); $first_day_of_week = date('w', $first_day); // 获取员工列表 $employees_table = $wpdb->prefix . 'emp_employees'; // 获取排班数据 $schedules_table = $wpdb->prefix . 'emp_schedules'; $schedules = $wpdb->get_results($wpdb->prepare( "SELECT s.*, e.full_name FROM $schedules_table s LEFT JOIN $employees_table e ON s.employee_id = e.id WHERE YEAR(schedule_date) = %d AND MONTH(schedule_date) = %d ORDER BY schedule_date, shift_start", $year, $month )); // 按日期组织排班数据 $schedule_by_date = array(); foreach ($schedules as $schedule) { $date = $schedule->schedule_date; if (!isset($schedule_by_date[$date])) { $schedule_by_date[$date] = array(); } $schedule_by_date[$date][] = $schedule; } ?> <table class="widefat fixed schedule-calendar-table"> <thead> <tr> <th>周日</th> <th>周一</th> <th>周二</th> <th>周三</th> <th>周四</th> <th>周五</th> <th>周六</th> </tr> </thead> <tbody> <?php $day_count = 1; echo '<tr>'; // 填充第一个星期前的空白 for ($i = 0; $i < $first_day_of_week; $i++) { echo '<td class="empty-day"></td>'; $day_count++; } // 填充日期 for ($day = 1; $day <= $days_in_month; $day++) { $current_date = sprintf('%04d-%02d-%02d', $year, $month, $day); $is_today = ($current_date == date('Y-m-d')) ? 'today' : ''; echo '<td class="calendar-day ' . $is_today . '">'; echo '<div class="day-number">' . $day . '</div>'; // 显示当天的排班 if (isset($schedule_by_date[$current_date])) { echo '<div class="day-schedules">'; foreach ($schedule_by_date[$current_date] as $schedule) { echo '<div class="schedule-item" data-id="' . $schedule->id . '">'; echo '<span class="employee-name">' . esc_html($schedule->full_name) . '</span>'; echo '<span class="shift-time">' . date('H:i', strtotime($schedule->shift_start)) . '-' . date('H:i', strtotime($schedule->shift_end)) . '</span>'; echo '</div>'; } echo '</div>'; } echo '</td>'; // 换行 if ($day_count % 7 == 0 && $day != $days_in_month) { echo '</tr><tr>'; } $day_count++; } // 填充最后一个星期后的空白 while ($day_count % 7 != 1) { echo '<td class="empty-day"></td>'; $day_count++; } echo '</tr>'; ?> </tbody> </table> </div> <!-- 添加排班表单 --> <h2 id="add-schedule-form">添加排班</h2> <form method="post" action=""> <table class="form-table"> <tr> <th scope="row"><label for="employee_id">员工</label></th> <td> <select id="employee_id" name="employee_id" required class="regular-text"> <option value="">选择员工</option> <?php foreach ($employees as $employee): ?> <option value="<?php echo $employee->id; ?>"> <?php echo esc_html($employee->full_name . ' (' . $employee->employee_id . ')'); ?> </option> <?php endforeach; ?> </select> </td> </tr> <tr> <th scope="row"><label for="schedule_date">日期</label></th> <td><input type="date" id="schedule_date" name="schedule_date" required class="regular-text" value="<?php echo date('Y-m-d'); ?>"></td> </tr> <tr> <th scope="row"><label for="shift_start">上班时间</label></th> <td><input type="time" id="shift_start" name="shift_start" required class="regular-text" value="09:00"></td> </tr> <tr> <th scope="row"><label for="shift_end">下班时间</label></th> <td><input type="time" id="shift_end" name="shift_end" required class="regular-text" value="18:00"></td> </tr> <tr> <th scope="row"><label for="shift_type">班次类型</label></th> <td> <select id="shift_type" name="shift_type" class="regular-text"> <option value="normal">正常班</option> <option value="morning">早班</option> <option value="night">晚班</option> <option value="overtime">加班</option> <option value="weekend">周末班</option> </select> </td> </tr> <tr> <th scope="row"><label for="notes">备注</label></th> <td><textarea id="notes" name="notes" rows="3" class="regular-text"></textarea></td> </tr> </table> <?php submit_button('添加排班', 'primary', 'add_schedule'); ?> </form> </div> <style> .schedule-calendar-table { border-collapse: collapse; margin: 20px 0; } .schedule-calendar-table th { background-color: #f1f1f1; text-align: center; padding: 10px; border: 1px solid #ddd; } .calendar-day { height: 120px; vertical-align: top; border: 1px solid #ddd; padding: 5px; position: relative; } .calendar-day.today { background-color: #e6f7ff; } .day-number { font-weight: bold; margin-bottom: 5px; } .day-schedules { max-height: 90px; overflow-y: auto; } .schedule-item { background-color: #f0f8ff; border-left: 3px solid #1890ff; padding: 3px 5px; margin-bottom: 3px; font-size: 12px; border-radius: 2px; } .schedule-item .employee-name { display: block; font-weight: bold; } .schedule-item .shift-time { color: #666; font-size: 11px; } .empty-day { background-color: #f9f9f9; border: 1px solid #ddd; } .month-navigation { margin: 20px 0; text-align: center; } </style> <?php } 3.2 批量排班功能 为了方便批量设置排班,我们添加批量排班功能: // 批量排班功能 function emp_batch_schedule_form() { ?> <div class="batch-schedule-form" style="margin: 20px 0; padding: 20px; background: #f9f9f9; border: 1px solid #ddd;"> <h3>批量排班设置</h3> <form method="post" action=""> <table class="form-table"> <tr> <th scope="row"><label for="batch_employees">选择员工</label></th> <td> <select id="batch_employees" name="batch_employees[]" multiple class="regular-text" style="height: 150px;"> <?php global $wpdb; $employees_table = $wpdb->prefix . 'emp_employees'; $employees = $wpdb->get_results("SELECT id, full_name, employee_id FROM $employees_table WHERE status = 'active' ORDER BY full_name"); foreach ($employees as $employee) { echo '<option value="' . $employee->id . '">' . esc_html($employee->full_name . ' (' . $employee->employee_id . ')') . '</option>'; } ?> </select> <p class="description">按住Ctrl键可多选</p> </td> </tr> <tr> <th scope="row"><label for="batch_start_date">开始日期</label></th> <td><input type="date" id="batch_start_date" name="batch_start_date" required class="regular-text" value="<?php echo date('Y-m-d'); ?>"></td> </tr> <tr> <th scope="row"><label for="batch_end_date">结束日期</label></th> <td><input type="date" id="batch_end_date" name="batch_end_date" required class="regular-text" value="<?php echo date('Y-m-d', strtotime('+7 days')); ?>"></td> </tr> <tr> <th scope="row"><label for="batch_weekdays">工作日</label></th> <td> <label><input type="checkbox" name="batch_weekdays[]" value="1" checked> 周一</label> <label><input type="checkbox" name="batch_weekdays[]" value="2" checked> 周二</label> <label><input type="checkbox" name="batch_weekdays[]" value="3" checked> 周三</label> <label><input type="checkbox" name="batch_weekdays[]" value="4" checked> 周四</label> <label><input type="checkbox" name="batch_weekdays[]" value="5" checked> 周五</label> <label><input type="checkbox" name="batch_weekdays[]" value="6"> 周六</label> <label><input type="checkbox" name="batch_weekdays[]" value="0"> 周日</label> </td> </tr> <tr> <th scope="row"><label for="batch_shift_start">上班时间</label></th> <td><input type="time" id="batch_shift_start" name="batch_shift_start" required class="regular-text" value="09:00"></td> </tr> <tr> <th scope="row"><label for="batch_shift_end">下班时间</label></th> <td><input type="time" id="batch_shift_end" name="batch_shift_end" required class="regular-text" value="18:00"></td> </tr> <tr> <th scope="row"><label for="batch_shift_type">班次类型</label></th> <td> <select id="batch_shift_type" name="batch_shift_type" class="regular-text"> <option value="normal">正常班</option> <option value="morning">早班</option> <option value="night">晚班</option> </select> </td> </tr> </table> <?php submit_button('批量设置排班', 'primary', 'batch_schedule'); ?> </form> </div> <?php } // 处理批量排班 function emp_process_batch_schedule() { if (isset($_POST['batch_schedule'])) { global $wpdb; $employees = $_POST['batch_employees']; $start_date = $_POST['batch_start_date']; $end_date = $_POST['batch_end_date']; $weekdays = isset($_POST['batch_weekdays']) ? $_POST['batch_weekdays'] : array(); $shift_start = $_POST['batch_shift_start']; $shift_end = $_POST['batch_shift_end']; $shift_type = $_POST['batch_shift_type']; $schedules_table = $wpdb->prefix . 'emp_schedules'; $current_user_id = get_current_user_id(); $start = new DateTime($start_date); $end = new DateTime($end_date); $interval = new DateInterval('P1D'); $period = new DatePeriod($start, $interval, $end->modify('+1 day')); $added_count = 0; foreach ($period as $date) { $weekday = $date->format('w'); // 0=周日, 1=周一... if (in_array($weekday, $weekdays)) { $schedule_date = $date->format('Y-m-d'); foreach ($employees as $employee_id) { // 检查是否已有排班 $existing = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $schedules_table WHERE employee_id = %d AND schedule_date = %s", $employee_id, $schedule_date )); if (!$existing) { $wpdb->insert($schedules_table, array( 'employee_id' => $employee_id, 'schedule_date' => $schedule_date, 'shift_start' => $shift_start, 'shift_end' => $shift_end, 'shift_type' => $shift_type, 'created_by' => $current_user_id )); $added_count++; } } } } echo '<div class="notice notice-success"><p>批量排班完成!成功添加 ' . $added_count . ' 条排班记录。</p></div>'; } } 第四部分:考勤管理模块开发 4.1 考勤打卡功能 创建员工考勤打卡界面: // 添加快捷码支持 add_shortcode('emp_attendance_check', 'emp_attendance_check_shortcode'); function emp_attendance_check_shortcode() { if (!is_user_logged_in()) { return '<p>请先登录系统。</p>'; } $user_id = get_current_user_id(); $today = date('Y-m-d'); // 获取员工信息 global $wpdb; $employees_table = $wpdb->prefix . 'emp_employees'; $employee = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $employees_table WHERE user_id = %d", $user_id )); if (!$employee) { return '<p>您不是注册员工,无法使用考勤功能。</p>'; } // 获取今日排班 $schedules_table = $wpdb->prefix . 'emp_schedules'; $schedule = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $schedules_table WHERE employee_id = %d AND schedule_date = %s", $employee->id, $today )); // 获取今日考勤记录 $attendance_table = $wpdb->prefix . 'emp_attendance'; $attendance = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $attendance_table WHERE employee_id = %d AND attendance_date = %s", $employee->id, $today )); ob_start(); ?> <div class="emp-attendance-check"> <div class="attendance-header"> <h2>员工考勤打卡</h2> <div class="employee-info"> <p><strong>员工:</strong> <?php echo esc_html($employee->full_name); ?></p> <p><strong>日期:</strong> <?php echo date('Y年m月d日'); ?></p> <p><strong>时间:</strong> <span id="current-time"><?php echo date('H:i:s'); ?></span></p> </div> </div> <?php if ($schedule): ?> <div class="schedule-info"> <h3>今日排班信息</h3> <p><strong>上班时间:</strong> <?php echo date('H:i', strtotime($schedule->shift_start)); ?></p> <p><strong>下班时间:</strong> <?php echo date('H:i', strtotime($schedule->shift_end)); ?></p> <p><strong>班次类型:</strong> <?php echo esc_html($schedule->shift_type); ?></p> </div> <?php else: ?> <div class="notice notice-warning"> <p>今日无排班安排。</p> </div> <?php endif; ?> <div class="attendance-actions"> <?php if (!$attendance || !$attendance->check_in): ?> <form method="post" action="" class="checkin-form"> <input type="hidden" name="action" value="check_in"> <input type="hidden" name="employee_id" value="<?php echo $employee->id; ?>"> <button type="submit" class="button button-primary button-large" name="check_in"> <span class="dashicons dashicons-clock"></span> 上班打卡 </button> <p class="description">上班时间: <?php echo $schedule ? date('H:i', strtotime($schedule->shift_start)) : '无排班'; ?></p> </form> <?php elseif ($attendance && $attendance->check_in && !$attendance->check_out): ?> <div class="checked-in-info"> <p class="checked-in-time">上班打卡时间: <?php echo date('H:i:s', strtotime($attendance->check_in)); ?></p> <?php // 计算是否迟到 if ($schedule) { $check_in_time = strtotime($attendance->check_in);
发表评论一步步教你,集成在线法律文书生成与合同智能审查工具到网站,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:数字化时代下的法律工具集成需求 在当今数字化快速发展的时代,越来越多的企业和个人网站需要集成专业工具来提升用户体验和服务价值。特别是法律相关服务,如在线法律文书生成和合同智能审查,已成为许多商业网站、法律服务平台和企业门户的必备功能。通过将这些专业工具集成到网站中,不仅可以为用户提供即时、便捷的法律服务,还能显著提升网站的专业性和实用性。 WordPress作为全球最流行的内容管理系统,以其强大的扩展性和灵活性,成为实现这类功能集成的理想平台。本文将详细介绍如何通过WordPress代码二次开发,将在线法律文书生成与合同智能审查工具集成到您的网站中,并探讨如何实现其他常用互联网小工具功能,帮助您打造一个功能全面、用户体验卓越的专业网站。 第一部分:准备工作与环境搭建 1.1 确定需求与功能规划 在开始技术实现之前,首先需要明确您的具体需求: 法律文书生成功能:确定需要支持哪些类型的法律文书(如劳动合同、租赁合同、保密协议等) 合同智能审查功能:明确审查标准、风险点识别和修改建议的详细程度 用户权限管理:区分普通用户、会员用户和管理员的不同权限 数据安全与隐私保护:确保用户上传的合同文件和个人信息得到充分保护 界面与用户体验:设计直观易用的操作界面和流畅的用户流程 1.2 开发环境搭建 为了进行WordPress代码二次开发,您需要准备以下环境: 本地开发环境:安装XAMPP、MAMP或Local by Flywheel等本地服务器环境 WordPress安装:下载最新版WordPress并完成基本配置 代码编辑器:选择适合的代码编辑器,如VS Code、Sublime Text或PHPStorm 版本控制:设置Git仓库以管理代码版本 调试工具:安装Query Monitor、Debug Bar等WordPress调试插件 1.3 创建自定义插件框架 为了避免主题更新导致功能丢失,我们建议通过创建自定义插件的方式实现功能: <?php /** * Plugin Name: 法律工具集成套件 * Plugin URI: https://yourwebsite.com/ * Description: 集成在线法律文书生成与合同智能审查工具 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('LEGAL_TOOLS_VERSION', '1.0.0'); define('LEGAL_TOOLS_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('LEGAL_TOOLS_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once LEGAL_TOOLS_PLUGIN_DIR . 'includes/class-legal-tools-init.php'; 第二部分:在线法律文书生成功能实现 2.1 文书模板管理系统 首先,我们需要创建一个文书模板管理系统,用于存储和管理各种法律文书模板: // 创建自定义文章类型用于存储文书模板 function legal_tools_register_document_type() { $labels = array( 'name' => '法律文书模板', 'singular_name' => '文书模板', 'menu_name' => '文书模板', 'add_new' => '添加模板', 'add_new_item' => '添加新模板', 'edit_item' => '编辑模板', 'new_item' => '新模板', 'view_item' => '查看模板', 'search_items' => '搜索模板', 'not_found' => '未找到模板', 'not_found_in_trash' => '回收站中无模板' ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'document-template'), 'capability_type' => 'post', 'has_archive' => false, 'hierarchical' => false, 'menu_position' => 25, 'menu_icon' => 'dashicons-media-document', 'supports' => array('title', 'editor', 'custom-fields') ); register_post_type('document_template', $args); } add_action('init', 'legal_tools_register_document_type'); 2.2 动态表单生成系统 文书生成的核心是根据用户输入动态填充模板内容。我们需要创建一个表单生成系统: // 表单生成器类 class Legal_Document_Form_Builder { private $template_id; private $fields; public function __construct($template_id) { $this->template_id = $template_id; $this->fields = $this->get_template_fields($template_id); } // 获取模板字段定义 private function get_template_fields($template_id) { $fields_meta = get_post_meta($template_id, '_document_fields', true); if (empty($fields_meta)) { // 默认字段结构 $fields = array( 'parties' => array( 'label' => '合同双方信息', 'type' => 'section', 'fields' => array( 'party_a_name' => array( 'label' => '甲方名称', 'type' => 'text', 'required' => true, 'placeholder' => '请输入甲方全称' ), 'party_b_name' => array( 'label' => '乙方名称', 'type' => 'text', 'required' => true, 'placeholder' => '请输入乙方全称' ) ) ), 'contract_terms' => array( 'label' => '合同条款', 'type' => 'section', 'fields' => array( 'effective_date' => array( 'label' => '生效日期', 'type' => 'date', 'required' => true ), 'contract_period' => array( 'label' => '合同期限(月)', 'type' => 'number', 'required' => true, 'min' => 1, 'max' => 120 ) ) ) ); // 保存默认字段到模板 update_post_meta($template_id, '_document_fields', $fields); return $fields; } return $fields_meta; } // 渲染表单 public function render_form() { $output = '<form id="legal-document-form" class="legal-document-form" method="post">'; $output .= wp_nonce_field('generate_legal_document', 'legal_document_nonce', true, false); $output .= '<input type="hidden" name="template_id" value="' . esc_attr($this->template_id) . '">'; foreach ($this->fields as $section_key => $section) { if ($section['type'] === 'section') { $output .= '<div class="form-section">'; $output .= '<h3 class="section-title">' . esc_html($section['label']) . '</h3>'; if (isset($section['fields']) && is_array($section['fields'])) { foreach ($section['fields'] as $field_key => $field) { $output .= $this->render_field($field_key, $field); } } $output .= '</div>'; } else { $output .= $this->render_field($section_key, $section); } } $output .= '<div class="form-submit">'; $output .= '<button type="submit" class="btn btn-primary">生成文书</button>'; $output .= '</div>'; $output .= '</form>'; return $output; } // 渲染单个字段 private function render_field($field_key, $field) { $required = isset($field['required']) && $field['required'] ? ' required' : ''; $field_html = ''; switch ($field['type']) { case 'text': case 'email': case 'number': $field_html = sprintf( '<div class="form-group"> <label for="%s">%s</label> <input type="%s" id="%s" name="%s" class="form-control"%s placeholder="%s"> </div>', esc_attr($field_key), esc_html($field['label']), esc_attr($field['type']), esc_attr($field_key), esc_attr($field_key), $required, isset($field['placeholder']) ? esc_attr($field['placeholder']) : '' ); break; case 'textarea': $field_html = sprintf( '<div class="form-group"> <label for="%s">%s</label> <textarea id="%s" name="%s" class="form-control"%s rows="4" placeholder="%s"></textarea> </div>', esc_attr($field_key), esc_html($field['label']), esc_attr($field_key), esc_attr($field_key), $required, isset($field['placeholder']) ? esc_attr($field['placeholder']) : '' ); break; case 'select': if (isset($field['options']) && is_array($field['options'])) { $options_html = ''; foreach ($field['options'] as $option_value => $option_label) { $options_html .= sprintf( '<option value="%s">%s</option>', esc_attr($option_value), esc_html($option_label) ); } $field_html = sprintf( '<div class="form-group"> <label for="%s">%s</label> <select id="%s" name="%s" class="form-control"%s> %s </select> </div>', esc_attr($field_key), esc_html($field['label']), esc_attr($field_key), esc_attr($field_key), $required, $options_html ); } break; } return $field_html; } } 2.3 模板渲染与文档生成 当用户提交表单后,我们需要将用户数据填充到模板中,生成最终的法律文书: // 文档生成处理器 class Legal_Document_Generator { public function process_generation() { // 验证非ce和权限 if (!isset($_POST['legal_document_nonce']) || !wp_verify_nonce($_POST['legal_document_nonce'], 'generate_legal_document')) { return false; } // 获取模板ID和用户数据 $template_id = intval($_POST['template_id']); $user_data = $_POST; unset($user_data['legal_document_nonce']); unset($user_data['template_id']); // 获取模板内容 $template_post = get_post($template_id); if (!$template_post || $template_post->post_type !== 'document_template') { return false; } // 获取模板HTML $template_content = $template_post->post_content; // 替换模板变量 $generated_content = $this->replace_template_variables($template_content, $user_data); // 保存生成的文档 $document_id = $this->save_generated_document($generated_content, $template_id, $user_data); // 返回文档ID或内容 return array( 'document_id' => $document_id, 'content' => $generated_content ); } // 替换模板变量 private function replace_template_variables($template, $data) { foreach ($data as $key => $value) { $placeholder = '{{' . $key . '}}'; $template = str_replace($placeholder, esc_html($value), $template); } // 替换系统变量 $system_vars = array( '{{current_date}}' => date('Y年m月d日'), '{{generation_date}}' => date('Y年m月d日 H:i:s'), '{{site_name}}' => get_bloginfo('name') ); foreach ($system_vars as $placeholder => $value) { $template = str_replace($placeholder, $value, $template); } return $template; } // 保存生成的文档 private function save_generated_document($content, $template_id, $user_data) { $user_id = get_current_user_id(); $document_data = array( 'post_title' => '法律文书-' . date('YmdHis'), 'post_content' => $content, 'post_status' => 'private', 'post_type' => 'legal_document', 'post_author' => $user_id, 'meta_input' => array( '_generated_from_template' => $template_id, '_generation_data' => $user_data, '_generation_date' => current_time('mysql'), '_generated_by_user' => $user_id ) ); $document_id = wp_insert_post($document_data); return $document_id; } } 第三部分:合同智能审查功能实现 3.1 合同上传与解析系统 合同智能审查的第一步是允许用户上传合同文件并解析其内容: // 合同上传处理器 class Contract_Upload_Handler { public function handle_upload() { // 检查文件上传 if (!isset($_FILES['contract_file']) || $_FILES['contract_file']['error'] !== UPLOAD_ERR_OK) { return new WP_Error('upload_failed', '文件上传失败'); } // 验证文件类型 $allowed_types = array('pdf', 'doc', 'docx', 'txt'); $file_ext = pathinfo($_FILES['contract_file']['name'], PATHINFO_EXTENSION); if (!in_array(strtolower($file_ext), $allowed_types)) { return new WP_Error('invalid_type', '不支持的文件类型'); } // 验证文件大小(最大10MB) $max_size = 10 * 1024 * 1024; if ($_FILES['contract_file']['size'] > $max_size) { return new WP_Error('file_too_large', '文件大小超过限制'); } // 处理上传 $upload_result = $this->process_upload($_FILES['contract_file']); if (is_wp_error($upload_result)) { return $upload_result; } // 解析合同内容 $parsed_content = $this->parse_contract_content($upload_result['file_path'], $file_ext); return array( 'file_info' => $upload_result, 'parsed_content' => $parsed_content ); } // 处理文件上传 private function process_upload($file) { $upload_dir = wp_upload_dir(); $legal_dir = $upload_dir['basedir'] . '/legal-contracts/'; // 创建目录 if (!file_exists($legal_dir)) { wp_mkdir_p($legal_dir); } // 生成唯一文件名 $filename = uniqid('contract_') . '_' . sanitize_file_name($file['name']); $filepath = $legal_dir . $filename; // 移动文件 if (move_uploaded_file($file['tmp_name'], $filepath)) { return array( 'file_path' => $filepath, 'file_url' => $upload_dir['baseurl'] . '/legal-contracts/' . $filename, 'file_name' => $filename, 'original_name' => $file['name'] ); } return new WP_Error('move_failed', '文件保存失败'); } // 解析合同内容 private function parse_contract_content($file_path, $file_ext) { $content = ''; switch (strtolower($file_ext)) { case 'txt': $content = file_get_contents($file_path); break; case 'pdf': // 使用PDF解析库(需要安装适当的PHP扩展或库) $content = $this->parse_pdf_content($file_path); break; case 'doc': case 'docx': // 使用Word文档解析库 $content = $this->parse_word_content($file_path); break; } // 清理和标准化内容 $content = $this->clean_contract_content($content); return $content; } // 清理合同内容 private function clean_contract_content($content) { // 移除多余空格和换行 $content = preg_replace('/s+/', ' ', $content); // 提取关键部分(这里可以根据需要扩展) $sections = $this->extract_contract_sections($content); return array( 'full_text' => $content, 'sections' => $sections ); } // 提取合同章节 private function extract_contract_sections($content) { $sections = array(); // 常见合同章节模式 $section_patterns = array( 'parties' => '/(双方|甲方|乙方|当事人).*?(?=条款|约定|如下|:)/u', 'effective_date' => '/(生效|起始).*?(d{4}年d{1,2}月d{1,2}日|d{4}.d{1,2}.d{1,2})/u', +(年|月|日|天))/u', 'payment_terms' => '/(付款|支付|价款).*?(?=违约责任|争议解决|其他)/u', 'liability' => '/(违约|责任).*?(?=争议解决|其他|附则)/u', 'dispute_resolution' => '/(争议|纠纷|仲裁|诉讼).*?(?=其他|附则|签字)/u' ); foreach ($section_patterns as $section_key => $pattern) { if (preg_match($pattern, $content, $matches)) { $sections[$section_key] = $matches[0]; } } return $sections; } } ### 3.2 智能审查规则引擎 合同审查的核心是规则引擎,用于识别潜在风险和问题: // 合同审查规则引擎class Contract_Review_Engine { private $rules; private $risk_levels; public function __construct() { $this->risk_levels = array( 'high' => '高风险', 'medium' => '中风险', 'low' => '低风险', 'info' => '提示信息' ); $this->initialize_rules(); } // 初始化审查规则 private function initialize_rules() { $this->rules = array( // 缺失关键条款规则 'missing_essential_clauses' => array( 'name' => '缺失关键条款', 'description' => '检查合同是否缺少必要条款', 'risk_level' => 'high', 'check_method' => 'check_missing_clauses', 'keywords' => array('违约责任', '争议解决', '保密', '不可抗力', '终止条件') ), // 模糊表述规则 'ambiguous_language' => array( 'name' => '模糊表述', 'description' => '识别合同中可能引起歧义的模糊表述', 'risk_level' => 'medium', 'check_method' => 'check_ambiguous_language', 'patterns' => array( '/合理的|适当的|必要的/u', '/重大变化|特殊情况/u', '/及时通知|尽快办理/u' ) ), // 权利义务不对等规则 'unbalanced_rights' => array( 'name' => '权利义务不对等', 'description' => '检查合同中双方权利义务是否对等', 'risk_level' => 'high', 'check_method' => 'check_rights_balance', 'indicators' => array( 'exclusive_rights' => array('独家', '排他'), 'unilateral_termination' => array('单方解除', '任意解除'), 'excessive_liability' => array('承担一切损失', '无限责任') ) ), // 付款条款风险 'payment_risks' => array( 'name' => '付款条款风险', 'description' => '识别付款条款中的潜在风险', 'risk_level' => 'medium', 'check_method' => 'check_payment_terms', 'risk_patterns' => array( 'advance_full_payment' => '/预付全款|全额预付/u', 'vague_payment_time' => '/完成后付款|验收后付款/u', 'no_late_fee' => '/(?<!逾期)付款(?!.*违约金|滞纳金)/u' ) ), // 法律引用检查 'legal_references' => array( 'name' => '法律引用检查', 'description' => '检查引用的法律法规是否准确有效', 'risk_level' => 'low', 'check_method' => 'check_legal_references', 'valid_laws' => array( '《中华人民共和国民法典》', '《中华人民共和国合同法》', '《中华人民共和国劳动法》' ) ) ); } // 执行合同审查 public function review_contract($contract_content) { $results = array( 'overall_risk' => 'low', 'issues' => array(), 'statistics' => array( 'total_issues' => 0, 'high_risk' => 0, 'medium_risk' => 0, 'low_risk' => 0 ), 'recommendations' => array() ); // 对每个规则执行检查 foreach ($this->rules as $rule_id => $rule) { $method_name = $rule['check_method']; if (method_exists($this, $method_name)) { $rule_results = $this->$method_name($contract_content, $rule); if (!empty($rule_results)) { $results['issues'][$rule_id] = array( 'rule_name' => $rule['name'], 'risk_level' => $rule['risk_level'], 'description' => $rule['description'], 'findings' => $rule_results ); // 更新统计 $results['statistics']['total_issues']++; $results['statistics'][$rule['risk_level'] . '_risk']++; } } } // 确定总体风险等级 $results['overall_risk'] = $this->determine_overall_risk($results['statistics']); // 生成建议 $results['recommendations'] = $this->generate_recommendations($results['issues']); return $results; } // 检查缺失条款 private function check_missing_clauses($content, $rule) { $missing_clauses = array(); foreach ($rule['keywords'] as $keyword) { if (strpos($content, $keyword) === false) { $missing_clauses[] = $keyword; } } if (!empty($missing_clauses)) { return array( 'message' => '合同可能缺少以下关键条款:' . implode('、', $missing_clauses), 'suggestion' => '建议补充相关条款以明确双方权利义务' ); } return array(); } // 检查模糊表述 private function check_ambiguous_language($content, $rule) { $ambiguous_phrases = array(); foreach ($rule['patterns'] as $pattern) { if (preg_match_all($pattern, $content, $matches)) { $ambiguous_phrases = array_merge($ambiguous_phrases, $matches[0]); } } if (!empty($ambiguous_phrases)) { $unique_phrases = array_unique($ambiguous_phrases); return array( 'message' => '发现模糊表述:' . implode('、', array_slice($unique_phrases, 0, 5)), 'suggestion' => '建议将模糊表述具体化、量化,避免未来产生歧义' ); } return array(); } // 确定总体风险等级 private function determine_overall_risk($statistics) { if ($statistics['high_risk'] > 0) { return 'high'; } elseif ($statistics['medium_risk'] > 2) { return 'medium'; } elseif ($statistics['medium_risk'] > 0 || $statistics['low_risk'] > 3) { return 'low'; } else { return 'info'; } } // 生成建议 private function generate_recommendations($issues) { $recommendations = array(); // 高风险问题建议 if (isset($issues['missing_essential_clauses'])) { $recommendations[] = array( 'priority' => 'high', 'content' => '合同缺少关键条款,建议补充相关条款后再签署' ); } if (isset($issues['unbalanced_rights'])) { $recommendations[] = array( 'priority' => 'high', 'content' => '合同权利义务不对等,建议重新协商相关条款' ); } // 中风险问题建议 $medium_issues = array_filter($issues, function($issue) { return $issue['risk_level'] === 'medium'; }); if (!empty($medium_issues)) { $recommendations[] = array( 'priority' => 'medium', 'content' => '合同中存在多处需要明确的表述,建议进一步细化' ); } // 通用建议 $recommendations[] = array( 'priority' => 'info', 'content' => '建议咨询专业律师对合同进行最终审查' ); // 按优先级排序 usort($recommendations, function($a, $b) { $priority_order = array('high' => 3, 'medium' => 2, 'low' => 1, 'info' => 0); return $priority_order[$b['priority']] - $priority_order[$a['priority']]; }); return $recommendations; } } ### 3.3 审查结果可视化展示 将审查结果以直观的方式展示给用户: // 审查结果展示器class Contract_Review_Display { public function display_results($review_results) { $output = '<div class="contract-review-results">'; // 总体风险评估 $output .= $this->display_overall_risk($review_results['overall_risk']); // 问题列表 $output .= $this->display_issues_list($review_results['issues']); // 统计信息 $output .= $this->display_statistics($review_results['statistics']); // 建议 $output .= $this->display_recommendations($review_results['recommendations']); $output .= '</div>'; return $output; } // 显示总体风险评估 private function display_overall_risk($risk_level) { $risk_classes = array( 'high' => 'risk-high', 'medium' => 'risk-medium', 'low' => 'risk-low', 'info' => 'risk-info' ); $risk_labels = array( 'high' => '高风险', 'medium' => '中风险', 'low' => '低风险', 'info' => '信息提示' ); $class = isset($risk_classes[$risk_level]) ? $risk_classes[$risk_level] : 'risk-info'; $label = isset($risk_labels[$risk_level]) ? $risk_labels[$risk_level] : '待评估'; return sprintf( '<div class="overall-risk %s"> <h3>总体风险评估</h3> <div class="risk-indicator"> <span class="risk-level">%s</span> </div> </div>', esc_attr($class), esc_html($label) ); } // 显示问题列表 private function display_issues_list($issues) { if (empty($issues)) { return '<div class="no-issues"> <h3>审查结果</h3> <p>未发现明显风险问题,合同结构基本完整。</p> </div>'; } $output = '<div class="issues-list"> <h3>发现的问题</h3> <div class="issues-container">'; foreach ($issues as $issue_id => $issue) { $output .= $this->display_single_issue($issue_id, $issue); } $output .= '</div></div>'; return $output; } // 显示单个问题 private function display_single_issue($issue_id, $issue) { $risk_class = 'risk-' . $issue['risk_level']; $output = sprintf( '<div class="issue-item %s" id="issue-%s"> <div class="issue-header"> <h4>%s</h4> <span class="risk-badge">%s</span> </div> <div class="issue-description"> <p>%s</p>', esc_attr($risk_class), esc_attr($issue_id), esc_html($issue['rule_name']), esc_html($issue['risk_level'] === 'high' ? '高风险' : ($issue['risk_level'] === 'medium' ? '中风险' : '低风险')), esc_html($issue['description']) ); if (isset($issue['findings']['message'])) { $output .= sprintf( '<div class="issue-finding"> <strong>具体发现:</strong> <p>%s</p> </div>', esc_html($issue['findings']['message']) ); } if (isset($issue['findings']['suggestion'])) { $output .= sprintf( '<div class="issue-suggestion"> <strong>修改建议:</strong> <p>%s</p> </div>', esc_html($issue['findings']['suggestion']) ); } $output .= '</div></div>'; return $output; } // 显示统计信息 private function display_statistics($stats) { return sprintf( '<div class="review-statistics"> <h3>审查统计</h3> <div class="stats-grid"> <div class="stat-item"> <span class="stat-number">%d</span> <span class="stat-label">总问题数</span> </div> <div class="stat-item high-risk"> <span class="stat-number">%d</span> <span class="stat-label">高风险</span> </div> <div class="stat-item medium-risk"> <span class="stat-number">%d</span> <span class="stat-label">中风险</span> </div> <div class="stat-item low-risk"> <span class="stat-number">%d</span> <span class="stat-label">低风险</span> </div> </div> </div>', esc_html($stats['total_issues']), esc_html($stats['high_risk']), esc_html($stats['medium_risk']), esc_html($stats['low_risk']) ); } // 显示建议 private function display_recommendations($recommendations) { if (empty($recommendations)) { return ''; } $output = '<div class="recommendations"> <h3>修改建议</h3> <div class="recommendations-list">'; foreach ($recommendations as $index => $rec) { $output .= sprintf( '<div class="recommendation-item priority-%s"> <span class="recommendation-number">%d.</span> <div class="recommendation-content"> <p>%s</p> </div> </div>', esc_attr($rec['priority']), $index + 1, esc_html($rec['content']) ); } $output .= '</div></div>'; return $output; } } ## 第四部分:WordPress集成与用户界面 ### 4.1 创建短代码系统 为了方便在文章和页面中调用功能,我们创建短代码系统: // 短代码处理器class Legal_Tools_Shortcodes { public function __construct() { // 注册短代码 add_shortcode('legal_document_generator', array($this, 'document_generator_shortcode')); add_shortcode('contract_review_tool', array($this, 'contract_review_shortcode')); add_shortcode('legal_tools_dashboard', array($this, 'dashboard_shortcode')); } // 文书生成器短代码 public function document_generator_shortcode($atts) { $atts = shortcode_atts(array( 'template_id' => 0, 'category' => '', 'title' => '法律文书生成器' ), $atts, 'legal_document_generator'); ob_start(); // 检查用户权限 if (!is_user_logged_in()) { echo $this->login_prompt(); return ob_get_clean(); } // 获取模板 $template_id = intval($atts['template_id']); if ($template_id === 0 && !empty($atts['category'])) { $template_id = $this->get_template_by_category($atts['category']); } if ($template_id === 0) { // 显示模板选择界面 $this->display_template_selector(); } else { // 显示指定模板的表单 $this->display_document_form($template_id, $atts['title']); } return ob_get_clean(); } // 合同审查工具短代码 public function contract_review_shortcode($atts) { $atts = shortcode_atts(array( 'title' => '合同智能审查', 'max_size' => '10', 'allowed_types' => 'pdf,doc,docx,txt' ), $atts, 'contract_review_tool'); ob_start(); // 检查用户权限 if (!is_user_logged_in()) { echo $this->login_prompt(); return ob_get_clean(); } // 显示上传表单 $this->display_upload_form($atts); // 处理上传和审查 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['contract_file'])) { $this->process_contract_review(); } return ob_get_clean(); } // 仪表板短代码 public function dashboard_shortcode($atts) { $atts = shortcode_atts(array( 'show_documents' => 'true', 'show_reviews' => 'true', 'limit' => '10' ), $atts, 'legal_tools_dashboard'); ob_start(); // 检查用户权限 if (!is_user_logged_in()) { echo $this->login_prompt(); return ob_get_clean(); } $user_id = get_current
发表评论