文章目录[隐藏]
详细教程:为WordPress网站集成电子签名与合同管理工具
引言:为什么WordPress网站需要电子签名功能
在数字化时代,电子签名已成为商业活动中不可或缺的一环。无论是自由职业者与客户签订服务协议,还是企业与员工签署劳动合同,电子签名都能显著提高效率、降低成本并确保法律合规性。对于WordPress网站所有者而言,集成电子签名与合同管理功能可以:
- 提升专业形象:为客户提供完整的在线签约体验
- 简化工作流程:自动化合同创建、发送、签署和存档过程
- 增强安全性:确保合同文件的完整性和不可否认性
- 节省成本:减少纸质合同打印、邮寄和存储费用
- 提高转化率:减少客户签约过程中的摩擦点
本教程将指导您通过WordPress代码二次开发,为您的网站集成完整的电子签名与合同管理系统,而无需依赖昂贵的第三方插件。
第一部分:准备工作与环境配置
1.1 系统要求与工具准备
在开始开发之前,请确保您的环境满足以下要求:
- WordPress 5.0或更高版本
- PHP 7.4或更高版本(推荐8.0+)
- MySQL 5.6或更高版本
- 文本编辑器(如VS Code、Sublime Text等)
- FTP客户端或服务器文件管理器访问权限
- 基本的PHP、JavaScript和WordPress开发知识
1.2 创建开发子主题
为了避免直接修改主题文件导致更新时丢失更改,我们首先创建一个子主题:
- 在
wp-content/themes/目录下创建新文件夹my-esignature-theme -
创建
style.css文件,添加以下内容:/* Theme Name: My eSignature Theme Template: your-parent-theme-folder-name Version: 1.0.0 Description: 子主题,用于集成电子签名功能 */ -
创建
functions.php文件,添加以下代码:<?php // 子主题functions.php add_action('wp_enqueue_scripts', 'my_esignature_theme_enqueue_styles'); function my_esignature_theme_enqueue_styles() { wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); } ?> - 在WordPress后台激活此子主题
1.3 创建插件目录结构
我们将创建一个独立的插件来管理电子签名功能:
wp-content/plugins/my-esignature-system/
├── my-esignature-system.php
├── includes/
│ ├── class-database.php
│ ├── class-contract-manager.php
│ ├── class-signature-handler.php
│ └── class-pdf-generator.php
├── assets/
│ ├── css/
│ ├── js/
│ └── images/
├── templates/
│ ├── contract-form.php
│ ├── signature-pad.php
│ └── contract-view.php
└── vendor/ (第三方库)
第二部分:数据库设计与合同存储
2.1 创建数据库表
在includes/class-database.php中,我们将创建必要的数据库表:
<?php
class ESIGN_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_prefix = $wpdb->prefix . 'esign_';
// 合同表
$contracts_table = $table_prefix . 'contracts';
$sql1 = "CREATE TABLE IF NOT EXISTS $contracts_table (
id INT(11) NOT NULL AUTO_INCREMENT,
contract_id VARCHAR(50) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
content LONGTEXT NOT NULL,
status ENUM('draft', 'sent', 'signed', 'expired', 'cancelled') DEFAULT 'draft',
created_by INT(11) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
expires_at DATETIME,
PRIMARY KEY (id),
INDEX (contract_id),
INDEX (status),
INDEX (created_by)
) $charset_collate;";
// 签署方表
$parties_table = $table_prefix . 'parties';
$sql2 = "CREATE TABLE IF NOT EXISTS $parties_table (
id INT(11) NOT NULL AUTO_INCREMENT,
contract_id VARCHAR(50) NOT NULL,
user_id INT(11),
email VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role ENUM('sender', 'signer', 'witness', 'approver') DEFAULT 'signer',
signing_order INT(3) DEFAULT 1,
signature_data TEXT,
signed_at DATETIME,
ip_address VARCHAR(45),
user_agent TEXT,
PRIMARY KEY (id),
INDEX (contract_id),
INDEX (email),
FOREIGN KEY (contract_id) REFERENCES $contracts_table(contract_id) ON DELETE CASCADE
) $charset_collate;";
// 审计日志表
$audit_table = $table_prefix . 'audit_log';
$sql3 = "CREATE TABLE IF NOT EXISTS $audit_log_table (
id INT(11) NOT NULL AUTO_INCREMENT,
contract_id VARCHAR(50) NOT NULL,
action VARCHAR(100) NOT NULL,
details TEXT,
performed_by INT(11),
performed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
PRIMARY KEY (id),
INDEX (contract_id),
INDEX (performed_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql1);
dbDelta($sql2);
dbDelta($sql3);
}
// 生成唯一合同ID
public function generate_contract_id() {
return 'CONTRACT-' . date('Ymd') . '-' . strtoupper(wp_generate_password(8, false));
}
}
?>
2.2 合同数据模型
在includes/class-contract-manager.php中,创建合同管理类:
<?php
class ESIGN_Contract_Manager {
private $db;
public function __construct() {
$this->db = ESIGN_Database::get_instance();
}
// 创建新合同
public function create_contract($data) {
global $wpdb;
$contract_id = $this->db->generate_contract_id();
$current_user_id = get_current_user_id();
$contract_data = array(
'contract_id' => $contract_id,
'title' => sanitize_text_field($data['title']),
'content' => wp_kses_post($data['content']),
'created_by' => $current_user_id,
'status' => 'draft',
'expires_at' => !empty($data['expires_at']) ? $data['expires_at'] : null
);
$table_name = $wpdb->prefix . 'esign_contracts';
$result = $wpdb->insert($table_name, $contract_data);
if ($result) {
// 添加签署方
if (!empty($data['parties'])) {
$this->add_parties($contract_id, $data['parties']);
}
// 记录审计日志
$this->log_audit($contract_id, 'contract_created', '合同创建');
return $contract_id;
}
return false;
}
// 添加签署方
private function add_parties($contract_id, $parties) {
global $wpdb;
$table_name = $wpdb->prefix . 'esign_parties';
foreach ($parties as $index => $party) {
$party_data = array(
'contract_id' => $contract_id,
'email' => sanitize_email($party['email']),
'name' => sanitize_text_field($party['name']),
'role' => sanitize_text_field($party['role']),
'signing_order' => $index + 1
);
$wpdb->insert($table_name, $party_data);
}
}
// 获取合同详情
public function get_contract($contract_id) {
global $wpdb;
$contracts_table = $wpdb->prefix . 'esign_contracts';
$parties_table = $wpdb->prefix . 'esign_parties';
$contract = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $contracts_table WHERE contract_id = %s",
$contract_id
));
if ($contract) {
$contract->parties = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $parties_table WHERE contract_id = %s ORDER BY signing_order ASC",
$contract_id
));
}
return $contract;
}
// 记录审计日志
private function log_audit($contract_id, $action, $details = '') {
global $wpdb;
$table_name = $wpdb->prefix . 'esign_audit_log';
$log_data = array(
'contract_id' => $contract_id,
'action' => $action,
'details' => $details,
'performed_by' => get_current_user_id(),
'ip_address' => $this->get_client_ip()
);
$wpdb->insert($table_name, $log_data);
}
// 获取客户端IP
private function get_client_ip() {
$ipaddress = '';
if (isset($_SERVER['HTTP_CLIENT_IP']))
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
else if(isset($_SERVER['HTTP_X_FORWARDED']))
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
else if(isset($_SERVER['HTTP_FORWARDED']))
$ipaddress = $_SERVER['HTTP_FORWARDED'];
else if(isset($_SERVER['REMOTE_ADDR']))
$ipaddress = $_SERVER['REMOTE_ADDR'];
else
$ipaddress = 'UNKNOWN';
return $ipaddress;
}
}
?>
第三部分:电子签名功能实现
3.1 签名画板实现
创建templates/signature-pad.php模板文件:
<div class="esignature-container">
<div class="signature-pad-wrapper">
<h3>请在下方签署您的名字</h3>
<div class="signature-pad-container">
<canvas id="signature-pad" width="600" height="200"></canvas>
</div>
<div class="signature-actions">
<button type="button" id="clear-signature" class="button button-secondary">
<span class="dashicons dashicons-trash"></span> 清除
</button>
<button type="button" id="undo-signature" class="button button-secondary">
<span class="dashicons dashicons-undo"></span> 撤销
</button>
<button type="button" id="save-signature" class="button button-primary">
<span class="dashicons dashicons-yes-alt"></span> 确认签名
</button>
</div>
<div class="signature-preview" style="display:none;">
<h4>签名预览:</h4>
<img id="signature-preview" src="" alt="签名预览" />
</div>
<div class="signature-consent">
<label>
<input type="checkbox" id="signature-consent" required>
我确认此电子签名具有法律约束力,并同意电子签名条款
</label>
</div>
</div>
</div>
3.2 签名画板JavaScript实现
创建assets/js/signature-pad.js:
(function($) {
'use strict';
class SignaturePad {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.signatureData = null;
this.isDrawing = false;
this.lastX = 0;
this.lastY = 0;
this.init();
}
init() {
// 设置画布样式
this.ctx.strokeStyle = '#000';
this.ctx.lineWidth = 2;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
// 绑定事件
this.bindEvents();
// 清除画布
this.clear();
}
bindEvents() {
const canvas = this.canvas;
// 鼠标事件
canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
canvas.addEventListener('mousemove', (e) => this.draw(e));
canvas.addEventListener('mouseup', () => this.stopDrawing());
canvas.addEventListener('mouseout', () => this.stopDrawing());
// 触摸事件(移动设备支持)
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
});
canvas.dispatchEvent(mouseEvent);
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
});
canvas.dispatchEvent(mouseEvent);
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
const mouseEvent = new MouseEvent('mouseup', {});
canvas.dispatchEvent(mouseEvent);
});
}
startDrawing(e) {
this.isDrawing = true;
const rect = this.canvas.getBoundingClientRect();
this.lastX = e.clientX - rect.left;
this.lastY = e.clientY - rect.top;
}
draw(e) {
if (!this.isDrawing) return;
const rect = this.canvas.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
this.ctx.beginPath();
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(currentX, currentY);
this.ctx.stroke();
this.lastX = currentX;
this.lastY = currentY;
}
stopDrawing() {
this.isDrawing = false;
this.saveSignature();
}
saveSignature() {
this.signatureData = this.canvas.toDataURL('image/png');
}
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = '#f8f9fa';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.signatureData = null;
}
undo() {
// 简单实现:清除整个画布
this.clear();
}
getSignatureData() {
return this.signatureData;
}
setSignatureData(data) {
const img = new Image();
img.onload = () => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0);
this.signatureData = data;
};
img.src = data;
}
}
// 初始化签名板
$(document).ready(function() {
if ($('#signature-pad').length) {
window.signaturePad = new SignaturePad('signature-pad');
// 绑定按钮事件
$('#clear-signature').on('click', function() {
signaturePad.clear();
$('#signature-preview').hide();
});
$('#undo-signature').on('click', function() {
signaturePad.undo();
});
$('#save-signature').on('click', function() {
const signatureData = signaturePad.getSignatureData();
if (signatureData) {
$('#signature-preview').attr('src', signatureData).show();
$('#signature-data').val(signatureData);
// 显示成功消息
$('.signature-preview').show();
alert('签名已保存!');
} else {
alert('请先绘制您的签名');
}
});
// 表单提交验证
$('form.esignature-form').on('submit', function(e) {
if (!$('#signature-consent').is(':checked')) {
e.preventDefault();
alert('请同意电子签名条款');
return false;
}
if (!signaturePad.getSignatureData()) {
e.preventDefault();
alert('请提供您的签名');
return false;
}
});
}
});
})(jQuery);
3.3 签名处理类
创建includes/class-signature-handler.php:
<?php
class ESIGN_Signature_Handler {
public function __construct() {
add_action('wp_ajax_submit_signature', array($this, 'handle_signature_submission'));
ature_submission'));
}
// 处理签名提交
public function handle_signature_submission() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'esignature_nonce')) {
wp_die('安全验证失败');
}
$contract_id = sanitize_text_field($_POST['contract_id']);
$signature_data = $_POST['signature_data'];
$party_email = sanitize_email($_POST['party_email']);
// 验证签名数据
if (!$this->validate_signature_data($signature_data)) {
wp_send_json_error('无效的签名数据');
}
// 保存签名
$result = $this->save_signature($contract_id, $party_email, $signature_data);
if ($result) {
// 更新合同状态
$this->update_contract_status($contract_id);
// 发送通知邮件
$this->send_notification_email($contract_id, $party_email);
wp_send_json_success('签名提交成功');
} else {
wp_send_json_error('签名保存失败');
}
}
// 验证签名数据
private function validate_signature_data($signature_data) {
// 检查是否为有效的base64图像数据
if (preg_match('/^data:image/(png|jpeg);base64,/', $signature_data)) {
return true;
}
return false;
}
// 保存签名到数据库
private function save_signature($contract_id, $party_email, $signature_data) {
global $wpdb;
$table_name = $wpdb->prefix . 'esign_parties';
$result = $wpdb->update(
$table_name,
array(
'signature_data' => $signature_data,
'signed_at' => current_time('mysql'),
'ip_address' => $this->get_client_ip(),
'user_agent' => $_SERVER['HTTP_USER_AGENT']
),
array(
'contract_id' => $contract_id,
'email' => $party_email
)
);
return $result !== false;
}
// 更新合同状态
private function update_contract_status($contract_id) {
global $wpdb;
$contracts_table = $wpdb->prefix . 'esign_contracts';
$parties_table = $wpdb->prefix . 'esign_parties';
// 检查是否所有签署方都已签名
$unsigned_parties = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $parties_table
WHERE contract_id = %s AND signature_data IS NULL",
$contract_id
));
$new_status = ($unsigned_parties == 0) ? 'signed' : 'sent';
$wpdb->update(
$contracts_table,
array('status' => $new_status),
array('contract_id' => $contract_id)
);
}
// 发送通知邮件
private function send_notification_email($contract_id, $party_email) {
$contract = ESIGN_Contract_Manager::get_instance()->get_contract($contract_id);
$to = $party_email;
$subject = '合同签署通知 - ' . $contract->title;
$message = "
<html>
<body>
<h2>合同签署通知</h2>
<p>您好,</p>
<p>您已成功签署合同:<strong>{$contract->title}</strong></p>
<p>合同ID:{$contract_id}</p>
<p>签署时间:" . date('Y-m-d H:i:s') . "</p>
<p>您可以通过以下链接查看合同详情:</p>
<p><a href='" . home_url("/contract-view/{$contract_id}") . "'>查看合同</a></p>
<hr>
<p>此邮件为系统自动发送,请勿回复。</p>
</body>
</html>
";
$headers = array('Content-Type: text/html; charset=UTF-8');
wp_mail($to, $subject, $message, $headers);
}
}
?>
## 第四部分:PDF生成与合同管理
### 4.1 集成TCPDF库生成PDF
创建`includes/class-pdf-generator.php`:
<?php
require_once(plugin_dir_path(__FILE__) . '../vendor/tcpdf/tcpdf.php');
class ESIGN_PDF_Generator extends TCPDF {
private $contract_data;
public function __construct($contract_data) {
parent::__construct(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
$this->contract_data = $contract_data;
$this->init_pdf();
}
private function init_pdf() {
// 设置文档信息
$this->SetCreator('WordPress eSignature System');
$this->SetAuthor(get_bloginfo('name'));
$this->SetTitle($this->contract_data->title);
$this->SetSubject('电子合同');
// 设置默认字体
$this->SetFont('stsongstdlight', '', 12);
// 设置边距
$this->SetMargins(15, 15, 15);
$this->SetHeaderMargin(5);
$this->SetFooterMargin(10);
// 自动分页
$this->SetAutoPageBreak(TRUE, 15);
// 设置图像比例因子
$this->setImageScale(PDF_IMAGE_SCALE_RATIO);
}
// 生成合同PDF
public function generate_contract_pdf() {
$this->AddPage();
// 添加合同标题
$this->SetFont('stsongstdlight', 'B', 16);
$this->Cell(0, 10, $this->contract_data->title, 0, 1, 'C');
$this->Ln(10);
// 添加合同信息
$this->SetFont('stsongstdlight', '', 10);
$this->Cell(0, 6, '合同编号:' . $this->contract_data->contract_id, 0, 1);
$this->Cell(0, 6, '创建日期:' . $this->contract_data->created_at, 0, 1);
$this->Cell(0, 6, '状态:' . $this->get_status_text($this->contract_data->status), 0, 1);
$this->Ln(10);
// 添加合同内容
$this->SetFont('stsongstdlight', '', 12);
$this->writeHTML($this->contract_data->content, true, false, true, false, '');
$this->Ln(20);
// 添加签署方信息
$this->add_signature_section();
// 添加审计信息
$this->add_audit_info();
return $this;
}
// 添加签署方部分
private function add_signature_section() {
$this->SetFont('stsongstdlight', 'B', 14);
$this->Cell(0, 10, '签署方信息', 0, 1);
$this->Ln(5);
$this->SetFont('stsongstdlight', '', 11);
foreach ($this->contract_data->parties as $index => $party) {
$this->Cell(0, 6, '签署方 ' . ($index + 1) . ':', 0, 1);
$this->Cell(40, 6, '姓名:', 0, 0);
$this->Cell(0, 6, $party->name, 0, 1);
$this->Cell(40, 6, '邮箱:', 0, 0);
$this->Cell(0, 6, $party->email, 0, 1);
$this->Cell(40, 6, '角色:', 0, 0);
$this->Cell(0, 6, $this->get_role_text($party->role), 0, 1);
if ($party->signature_data) {
$this->Cell(40, 6, '签署时间:', 0, 0);
$this->Cell(0, 6, $party->signed_at, 0, 1);
// 添加签名图像
$signature_data = $party->signature_data;
$temp_file = $this->save_signature_image($signature_data);
if ($temp_file) {
$this->Image($temp_file, 50, $this->GetY(), 40, 20);
$this->Ln(25);
unlink($temp_file); // 删除临时文件
}
}
$this->Ln(10);
}
}
// 保存签名图像到临时文件
private function save_signature_image($signature_data) {
$upload_dir = wp_upload_dir();
$temp_dir = $upload_dir['basedir'] . '/esignature_temp/';
if (!file_exists($temp_dir)) {
wp_mkdir_p($temp_dir);
}
$temp_file = $temp_dir . uniqid('sig_') . '.png';
// 移除base64前缀
$signature_data = preg_replace('/^data:image/w+;base64,/', '', $signature_data);
$signature_data = base64_decode($signature_data);
if (file_put_contents($temp_file, $signature_data)) {
return $temp_file;
}
return false;
}
// 添加审计信息
private function add_audit_info() {
global $wpdb;
$audit_table = $wpdb->prefix . 'esign_audit_log';
$audit_logs = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $audit_table
WHERE contract_id = %s
ORDER BY performed_at DESC
LIMIT 10",
$this->contract_data->contract_id
));
if ($audit_logs) {
$this->SetFont('stsongstdlight', 'B', 12);
$this->Cell(0, 10, '操作记录', 0, 1);
$this->Ln(5);
$this->SetFont('stsongstdlight', '', 9);
foreach ($audit_logs as $log) {
$user = get_userdata($log->performed_by);
$username = $user ? $user->display_name : '系统';
$this->Cell(0, 5, date('Y-m-d H:i:s', strtotime($log->performed_at)) .
' - ' . $username . ' - ' . $log->action .
' - ' . $log->details, 0, 1);
}
}
}
// 获取状态文本
private function get_status_text($status) {
$status_map = array(
'draft' => '草稿',
'sent' => '已发送',
'signed' => '已签署',
'expired' => '已过期',
'cancelled' => '已取消'
);
return isset($status_map[$status]) ? $status_map[$status] : $status;
}
// 获取角色文本
private function get_role_text($role) {
$role_map = array(
'sender' => '发起方',
'signer' => '签署方',
'witness' => '见证方',
'approver' => '审批方'
);
return isset($role_map[$role]) ? $role_map[$role] : $role;
}
// 输出PDF到浏览器
public function output_pdf($filename = 'contract.pdf') {
$this->Output($filename, 'I');
}
// 保存PDF到服务器
public function save_pdf($filename) {
$this->Output($filename, 'F');
return file_exists($filename);
}
}
?>
### 4.2 合同管理界面
创建`templates/contract-manager.php`:
<?php
/**
- 合同管理界面模板
*/
if (!defined('ABSPATH')) {
exit;
}
global $wpdb;
$current_user_id = get_current_user_id();
// 获取用户相关的合同
$contracts_table = $wpdb->prefix . 'esign_contracts';
$parties_table = $wpdb->prefix . 'esign_parties';
$contracts = $wpdb->get_results($wpdb->prepare(
"SELECT c.* FROM $contracts_table c
LEFT JOIN $parties_table p ON c.contract_id = p.contract_id
WHERE c.created_by = %d OR p.email = %s
GROUP BY c.id
ORDER BY c.created_at DESC",
$current_user_id,
wp_get_current_user()->user_email
));
?>
<div class="esignature-manager-container">
<div class="esignature-header">
<h1>合同管理</h1>
<button id="create-new-contract" class="button button-primary">
<span class="dashicons dashicons-plus"></span> 创建新合同
</button>
</div>
<!-- 合同筛选 -->
<div class="contract-filters">
<select id="status-filter">
<option value="">所有状态</option>
<option value="draft">草稿</option>
<option value="sent">已发送</option>
<option value="signed">已签署</option>
<option value="expired">已过期</option>
</select>
<input type="text" id="search-contracts" placeholder="搜索合同标题或ID...">
<button id="apply-filters" class="button">筛选</button>
<button id="reset-filters" class="button button-secondary">重置</button>
</div>
<!-- 合同列表 -->
<div class="contracts-list">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>合同标题</th>
<th>合同ID</th>
<th>状态</th>
<th>创建时间</th>
<th>过期时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if ($contracts): ?>
<?php foreach ($contracts as $contract): ?>
<?php
$parties = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $parties_table WHERE contract_id = %s",
$contract->contract_id
));
?>
<tr data-contract-id="<?php echo esc_attr($contract->contract_id); ?>">
<td>
<strong><?php echo esc_html($contract->title); ?></strong>
<div class="row-actions">
<span class="view">
<a href="#" class="view-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>">查看</a> |
</span>
<span class="edit">
<a href="#" class="edit-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>">编辑</a> |
</span>
<span class="delete">
<a href="#" class="delete-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>">删除</a>
</span>
</div>
</td>
<td><?php echo esc_html($contract->contract_id); ?></td>
<td>
<span class="contract-status status-<?php echo esc_attr($contract->status); ?>">
<?php echo $this->get_status_badge($contract->status); ?>
</span>
</td>
<td><?php echo date('Y-m-d H:i', strtotime($contract->created_at)); ?></td>
<td>
<?php echo $contract->expires_at ? date('Y-m-d H:i', strtotime($contract->expires_at)) : '无'; ?>
</td>
<td>
<div class="contract-actions">
<?php if ($contract->status == 'draft'): ?>
<button class="button button-small send-contract"
data-id="<?php echo esc_attr($contract->contract_id); ?>">
发送签署
</button>
<?php endif; ?>
<?php if ($contract->status == 'sent'): ?>
<?php
$user_can_sign = false;
foreach ($parties as $party) {
if ($party->email == wp_get_current_user()->user_email && !$party->signature_data) {
$user_can_sign = true;
break;
}
}
?>
<?php if ($user_can_sign): ?>
<a href="<?php echo home_url("/sign-contract/{$contract->contract_id}"); ?>"
class="button button-small button-primary">
签署合同
</a>
<?php endif; ?>
<?php endif; ?>
<button class="button button-small download-pdf"
data-id="<?php echo esc_attr($contract->contract_id); ?>">
下载PDF
</button>
<button class="button button-small view-audit"
data-id="<?php echo esc_attr($contract->contract_id); ?>">
查看记录
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="no-contracts">
暂无合同记录。点击"创建新合同"按钮开始创建。
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="tablenav bottom">
<
