781 lines
25 KiB
PHP
781 lines
25 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* 用户卡片管理页面
|
|||
|
|
*/
|
|||
|
|
// 注意:在检查POST请求时,我们需要尽早处理并退出,避免输出其他内容
|
|||
|
|
|
|||
|
|
// 首先检查是否为POST请求
|
|||
|
|
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
|
|||
|
|
// 包含必要的文件但不要输出任何内容
|
|||
|
|
include 'common.php';
|
|||
|
|
|
|||
|
|
// 检查权限
|
|||
|
|
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
|||
|
|
$user = Typecho_Widget::widget('Widget_User');
|
|||
|
|
if (!$user->hasLogin() || !$user->pass('administrator', true)) {
|
|||
|
|
header('Content-Type: application/json');
|
|||
|
|
echo json_encode(['success' => false, 'message' => '无权限操作']);
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置响应头为JSON
|
|||
|
|
header('Content-Type: application/json; charset=utf-8');
|
|||
|
|
|
|||
|
|
if ($_POST['action'] == 'save_rss' && isset($_POST['uid'], $_POST['user_feed'])) {
|
|||
|
|
$uid = intval($_POST['uid']);
|
|||
|
|
$user_feed = trim($_POST['user_feed']);
|
|||
|
|
$user_url = isset($_POST['user_url']) ? trim($_POST['user_url']) : '';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$db->query($db->update('table.users')->rows(array(
|
|||
|
|
'user_feed' => $user_feed,
|
|||
|
|
'url' => $user_url
|
|||
|
|
))->where('uid = ?', $uid));
|
|||
|
|
|
|||
|
|
// 清除该用户的RSS缓存
|
|||
|
|
$cacheKey = 'usercard_rss_' . md5($user_feed);
|
|||
|
|
$cacheFile = dirname(__FILE__) . '/cache/' . $cacheKey . '.json';
|
|||
|
|
if (file_exists($cacheFile)) {
|
|||
|
|
@unlink($cacheFile);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
echo json_encode(['success' => true, 'message' => '用户信息已保存!']);
|
|||
|
|
exit;
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
echo json_encode(['success' => false, 'message' => '保存失败:' . $e->getMessage()]);
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} elseif ($_POST['action'] == 'batch_save') {
|
|||
|
|
// 批量保存
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
foreach ($_POST['user_feed'] as $uid => $user_feed) {
|
|||
|
|
$uid = intval($uid);
|
|||
|
|
$user_feed = trim($user_feed);
|
|||
|
|
$user_url = isset($_POST['user_url'][$uid]) ? trim($_POST['user_url'][$uid]) : '';
|
|||
|
|
|
|||
|
|
$db->query($db->update('table.users')->rows(array(
|
|||
|
|
'user_feed' => $user_feed,
|
|||
|
|
'url' => $user_url
|
|||
|
|
))->where('uid = ?', $uid));
|
|||
|
|
|
|||
|
|
// 清除该用户的RSS缓存
|
|||
|
|
if (!empty($user_feed)) {
|
|||
|
|
$cacheKey = 'usercard_rss_' . md5($user_feed);
|
|||
|
|
$cacheFile = dirname(__FILE__) . '/cache/' . $cacheKey . '.json';
|
|||
|
|
if (file_exists($cacheFile)) {
|
|||
|
|
@unlink($cacheFile);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
echo json_encode(['success' => true, 'message' => '批量保存成功!']);
|
|||
|
|
exit;
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
echo json_encode(['success' => false, 'message' => '批量保存失败:' . $e->getMessage()]);
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果不是上述两种action,返回错误
|
|||
|
|
echo json_encode(['success' => false, 'message' => '无效的操作']);
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 正常页面加载(非AJAX请求)
|
|||
|
|
include 'common.php';
|
|||
|
|
include 'header.php';
|
|||
|
|
include 'menu.php';
|
|||
|
|
|
|||
|
|
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
|||
|
|
|
|||
|
|
// 检查权限
|
|||
|
|
$user = Typecho_Widget::widget('Widget_User');
|
|||
|
|
if (!$user->hasLogin() || !$user->pass('administrator', true)) {
|
|||
|
|
exit;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取所有用户
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$users = $db->fetchAll($db->select('uid', 'name', 'screenName', 'url', 'user_feed')
|
|||
|
|
->from('table.users')
|
|||
|
|
->order('uid', Typecho_Db::SORT_ASC));
|
|||
|
|
|
|||
|
|
// 统计信息
|
|||
|
|
$totalUsers = count($users);
|
|||
|
|
$withRss = 0;
|
|||
|
|
$withoutRss = 0;
|
|||
|
|
$withWebsite = 0;
|
|||
|
|
|
|||
|
|
foreach ($users as $user) {
|
|||
|
|
if (!empty($user['user_feed'])) {
|
|||
|
|
$withRss++;
|
|||
|
|
} else {
|
|||
|
|
$withoutRss++;
|
|||
|
|
}
|
|||
|
|
if (!empty($user['url'])) {
|
|||
|
|
$withWebsite++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户头像首字母函数
|
|||
|
|
function getUserInitial($name) {
|
|||
|
|
if (empty($name)) {
|
|||
|
|
return 'U';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 去除首尾空格
|
|||
|
|
$name = trim($name);
|
|||
|
|
|
|||
|
|
// 如果是中文,获取第一个汉字
|
|||
|
|
if (preg_match('/^[\x{4e00}-\x{9fa5}]/u', $name)) {
|
|||
|
|
// 获取第一个字符(支持中文字符)
|
|||
|
|
return mb_substr($name, 0, 1, 'UTF-8');
|
|||
|
|
} else {
|
|||
|
|
// 非中文,获取第一个字母或数字
|
|||
|
|
$firstChar = substr($name, 0, 1);
|
|||
|
|
|
|||
|
|
// 如果是字母,转换为大写
|
|||
|
|
if (ctype_alpha($firstChar)) {
|
|||
|
|
return strtoupper($firstChar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是数字,直接返回
|
|||
|
|
if (ctype_digit($firstChar)) {
|
|||
|
|
return $firstChar;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 其他字符,返回第一个字符的大写形式
|
|||
|
|
return strtoupper($firstChar);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
?>
|
|||
|
|
|
|||
|
|
<div class="main">
|
|||
|
|
<div class="body container">
|
|||
|
|
<!-- 弹窗容器 -->
|
|||
|
|
<div id="toast-container" style="position: fixed; top: 20px; right: 20px; z-index: 9999;"></div>
|
|||
|
|
|
|||
|
|
<style>
|
|||
|
|
/* 弹窗样式 */
|
|||
|
|
.toast {
|
|||
|
|
background: #43e97b;
|
|||
|
|
color: white;
|
|||
|
|
padding: 12px 24px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
box-shadow: 0 4px 12px rgba(67, 233, 123, 0.3);
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
animation: slideInRight 0.3s ease, fadeOut 0.3s ease 2.7s forwards;
|
|||
|
|
max-width: 300px;
|
|||
|
|
word-wrap: break-word;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes slideInRight {
|
|||
|
|
from {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: translateX(100%);
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: translateX(0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes fadeOut {
|
|||
|
|
from {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-section {
|
|||
|
|
background: #fff;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
padding: 20px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-title {
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
padding-bottom: 10px;
|
|||
|
|
border-bottom: 1px solid #eee;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-stats-cards {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-stat-card {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px 15px;
|
|||
|
|
text-align: center;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
border: 1px solid #e9ecef;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-stat-card:hover {
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-stat-value {
|
|||
|
|
font-size: 24px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
margin: 0 0 10px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-stat-label {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #666;
|
|||
|
|
margin: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-stat-card.total .user-stat-value { color: #667eea; }
|
|||
|
|
.user-stat-card.with-rss .user-stat-value { color: #43e97b; }
|
|||
|
|
.user-stat-card.without-rss .user-stat-value { color: #fa709a; }
|
|||
|
|
.user-stat-card.with-website .user-stat-value { color: #4facfe; }
|
|||
|
|
|
|||
|
|
.user-table-container {
|
|||
|
|
overflow-x: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-table {
|
|||
|
|
width: 100%;
|
|||
|
|
border-collapse: collapse;
|
|||
|
|
background: #fff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-table thead {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-table th {
|
|||
|
|
padding: 15px;
|
|||
|
|
text-align: left;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #333;
|
|||
|
|
border-bottom: 2px solid #e9ecef;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-table td {
|
|||
|
|
padding: 15px;
|
|||
|
|
border-bottom: 1px solid #e9ecef;
|
|||
|
|
vertical-align: middle;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-table tbody tr:hover {
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-info {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-avatar {
|
|||
|
|
width: 36px;
|
|||
|
|
height: 36px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
color: white;
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 16px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-details {
|
|||
|
|
flex: 1;
|
|||
|
|
min-width: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-name {
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #333;
|
|||
|
|
margin: 0 0 3px 0;
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-id {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #999;
|
|||
|
|
margin: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.website-input-container,
|
|||
|
|
.rss-input-container {
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.url-input,
|
|||
|
|
.rss-input {
|
|||
|
|
width: 100%;
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 13px;
|
|||
|
|
transition: all 0.2s ease;
|
|||
|
|
background: #fff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.url-input:focus,
|
|||
|
|
.rss-input:focus {
|
|||
|
|
border-color: #667eea;
|
|||
|
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|||
|
|
outline: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.url-input.has-value,
|
|||
|
|
.rss-input.has-value {
|
|||
|
|
border-color: #43e97b;
|
|||
|
|
background: #f8fff9;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.url-input.empty,
|
|||
|
|
.rss-input.empty {
|
|||
|
|
border-color: #ddd;
|
|||
|
|
background: #fff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-status {
|
|||
|
|
position: absolute;
|
|||
|
|
right: 10px;
|
|||
|
|
top: 50%;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
width: 8px;
|
|||
|
|
height: 8px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: #ddd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-status.has-value {
|
|||
|
|
background: #43e97b;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-status.empty {
|
|||
|
|
background: #ddd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.url-input::placeholder,
|
|||
|
|
.rss-input::placeholder {
|
|||
|
|
color: #999;
|
|||
|
|
font-size: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-actions {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 15px;
|
|||
|
|
margin: 30px 0 0 0;
|
|||
|
|
padding: 20px;
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn {
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.2s ease;
|
|||
|
|
line-height: 1.4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-save {
|
|||
|
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-save:hover {
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 4px 12px rgba(67, 233, 123, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-reset {
|
|||
|
|
background: linear-gradient(135deg, #ff6b6b 0%, #ffa8a8 100%);
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-reset:hover {
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-refresh {
|
|||
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-refresh:hover {
|
|||
|
|
transform: translateY(-2px);
|
|||
|
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.message {
|
|||
|
|
padding: 15px 20px;
|
|||
|
|
margin: 20px 0;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 1px solid;
|
|||
|
|
animation: slideDown 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.message.success {
|
|||
|
|
background: #f0fff4;
|
|||
|
|
border-color: #c6f6d5;
|
|||
|
|
color: #22543d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes slideDown {
|
|||
|
|
from {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: translateY(-10px);
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
opacity: 1;
|
|||
|
|
transform: translateY(0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 768px) {
|
|||
|
|
.user-stats-cards {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.user-mgmt-table {
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-actions {
|
|||
|
|
flex-direction: column;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|
|||
|
|
<div class="user-mgmt-section">
|
|||
|
|
<div class="user-stats-cards">
|
|||
|
|
<div class="user-stat-card total">
|
|||
|
|
<div class="user-stat-value"><?php echo $totalUsers; ?></div>
|
|||
|
|
<div class="user-stat-label">总用户数</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="user-stat-card with-website">
|
|||
|
|
<div class="user-stat-value"><?php echo $withWebsite; ?></div>
|
|||
|
|
<div class="user-stat-label">有网站地址</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="user-stat-card with-rss">
|
|||
|
|
<div class="user-stat-value"><?php echo $withRss; ?></div>
|
|||
|
|
<div class="user-stat-label">有RSS地址</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="user-stat-card without-rss">
|
|||
|
|
<div class="user-stat-value"><?php echo $withoutRss; ?></div>
|
|||
|
|
<div class="user-stat-label">无RSS地址</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<form method="post" action="" id="batch-form">
|
|||
|
|
<input type="hidden" name="action" value="batch_save">
|
|||
|
|
|
|||
|
|
<div class="user-mgmt-section">
|
|||
|
|
<!--<div class="user-mgmt-title">RSS及网站地址管理</div>-->
|
|||
|
|
|
|||
|
|
<div class="user-table-container">
|
|||
|
|
<table class="user-mgmt-table">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th width="20%">用户信息</th>
|
|||
|
|
<th width="15%">用户名</th>
|
|||
|
|
<th width="30%">网站地址</th>
|
|||
|
|
<th width="35%">RSS地址</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<?php foreach ($users as $user): ?>
|
|||
|
|
<?php
|
|||
|
|
$displayName = !empty($user['screenName']) ? $user['screenName'] : $user['name'];
|
|||
|
|
$userUrl = !empty($user['url']) ? $user['url'] : '';
|
|||
|
|
$hasWebsite = !empty($user['url']);
|
|||
|
|
$hasRss = !empty($user['user_feed']);
|
|||
|
|
$userInitial = getUserInitial($displayName);
|
|||
|
|
?>
|
|||
|
|
<tr>
|
|||
|
|
<td>
|
|||
|
|
<div class="user-info">
|
|||
|
|
<div class="user-avatar">
|
|||
|
|
<?php echo htmlspecialchars($userInitial); ?>
|
|||
|
|
</div>
|
|||
|
|
<div class="user-details">
|
|||
|
|
<div class="user-name"><?php echo htmlspecialchars($displayName); ?></div>
|
|||
|
|
<div class="user-id">UID: <?php echo $user['uid']; ?></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
<td>
|
|||
|
|
<div class="user-name"><?php echo htmlspecialchars($user['name']); ?></div>
|
|||
|
|
</td>
|
|||
|
|
<td>
|
|||
|
|
<div class="website-input-container">
|
|||
|
|
<input type="text"
|
|||
|
|
name="user_url[<?php echo $user['uid']; ?>]"
|
|||
|
|
value="<?php echo htmlspecialchars($userUrl); ?>"
|
|||
|
|
class="url-input <?php echo $hasWebsite ? 'has-value' : 'empty'; ?>"
|
|||
|
|
placeholder="https://example.com/"
|
|||
|
|
data-uid="<?php echo $user['uid']; ?>"
|
|||
|
|
data-type="url">
|
|||
|
|
<div class="input-status <?php echo $hasWebsite ? 'has-value' : 'empty'; ?>"></div>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
<td>
|
|||
|
|
<div class="rss-input-container">
|
|||
|
|
<input type="text"
|
|||
|
|
name="user_feed[<?php echo $user['uid']; ?>]"
|
|||
|
|
value="<?php echo htmlspecialchars($user['user_feed']); ?>"
|
|||
|
|
class="rss-input <?php echo $hasRss ? 'has-value' : 'empty'; ?>"
|
|||
|
|
placeholder="https://example.com/feed/"
|
|||
|
|
data-uid="<?php echo $user['uid']; ?>"
|
|||
|
|
data-type="rss">
|
|||
|
|
<div class="input-status <?php echo $hasRss ? 'has-value' : 'empty'; ?>"></div>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
<?php endforeach; ?>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="form-actions">
|
|||
|
|
<button type="button" class="btn btn-save" onclick="saveBatch()">
|
|||
|
|
批量保存
|
|||
|
|
</button>
|
|||
|
|
<button type="button" class="btn btn-refresh" onclick="window.location.reload()">
|
|||
|
|
刷新页面
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// 显示弹窗
|
|||
|
|
function showToast(message, type = 'success') {
|
|||
|
|
const container = document.getElementById('toast-container');
|
|||
|
|
|
|||
|
|
const toast = document.createElement('div');
|
|||
|
|
toast.className = 'toast';
|
|||
|
|
toast.textContent = message;
|
|||
|
|
|
|||
|
|
// 设置不同颜色的弹窗
|
|||
|
|
if (type === 'error') {
|
|||
|
|
toast.style.background = 'linear-gradient(135deg, #ff6b6b 0%, #ffa8a8 100%)';
|
|||
|
|
toast.style.boxShadow = '0 4px 12px rgba(255, 107, 107, 0.3)';
|
|||
|
|
} else if (type === 'info') {
|
|||
|
|
toast.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|||
|
|
toast.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
container.appendChild(toast);
|
|||
|
|
|
|||
|
|
// 3秒后移除弹窗
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (toast.parentNode === container) {
|
|||
|
|
toast.style.animation = 'fadeOut 0.3s ease forwards';
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (toast.parentNode === container) {
|
|||
|
|
container.removeChild(toast);
|
|||
|
|
}
|
|||
|
|
}, 300);
|
|||
|
|
}
|
|||
|
|
}, 2500);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量保存
|
|||
|
|
function saveBatch() {
|
|||
|
|
const form = document.getElementById('batch-form');
|
|||
|
|
const formData = new FormData(form);
|
|||
|
|
|
|||
|
|
// 检查是否有数据
|
|||
|
|
let hasData = false;
|
|||
|
|
const urlInputs = document.querySelectorAll('.url-input');
|
|||
|
|
const rssInputs = document.querySelectorAll('.rss-input');
|
|||
|
|
|
|||
|
|
urlInputs.forEach(input => {
|
|||
|
|
if (input.value.trim()) hasData = true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
rssInputs.forEach(input => {
|
|||
|
|
if (input.value.trim()) hasData = true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!hasData) {
|
|||
|
|
if (confirm('当前没有设置任何网站地址和RSS地址,确定要继续吗?')) {
|
|||
|
|
submitBatch(formData);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (confirm('确定要保存所有用户的网站地址和RSS地址吗?')) {
|
|||
|
|
submitBatch(formData);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提交批量保存
|
|||
|
|
function submitBatch(formData) {
|
|||
|
|
fetch('', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: formData
|
|||
|
|
})
|
|||
|
|
.then(response => {
|
|||
|
|
// 检查响应状态
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error('网络响应不正常: ' + response.status);
|
|||
|
|
}
|
|||
|
|
return response.text(); // 先获取文本
|
|||
|
|
})
|
|||
|
|
.then(text => {
|
|||
|
|
try {
|
|||
|
|
// 尝试解析为JSON
|
|||
|
|
const data = JSON.parse(text);
|
|||
|
|
if (data.success) {
|
|||
|
|
showToast(data.message);
|
|||
|
|
|
|||
|
|
// 更新输入框状态
|
|||
|
|
updateInputStatus();
|
|||
|
|
} else {
|
|||
|
|
showToast(data.message || '保存失败,请重试!', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
// 如果不是JSON,显示原始响应以便调试
|
|||
|
|
console.error('响应不是有效的JSON:', text);
|
|||
|
|
showToast('服务器响应异常: ' + text.substring(0, 100), 'error');
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
console.error('Error:', error);
|
|||
|
|
showToast('网络错误: ' + error.message, 'error');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新输入框状态
|
|||
|
|
function updateInputStatus() {
|
|||
|
|
document.querySelectorAll('.url-input, .rss-input').forEach(function(input) {
|
|||
|
|
const inputStatus = input.parentNode.querySelector('.input-status');
|
|||
|
|
if (input.value.trim()) {
|
|||
|
|
input.classList.remove('empty');
|
|||
|
|
input.classList.add('has-value');
|
|||
|
|
inputStatus.classList.remove('empty');
|
|||
|
|
inputStatus.classList.add('has-value');
|
|||
|
|
} else {
|
|||
|
|
input.classList.remove('has-value');
|
|||
|
|
input.classList.add('empty');
|
|||
|
|
inputStatus.classList.remove('has-value');
|
|||
|
|
inputStatus.classList.add('empty');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 单个保存功能(按Enter键保存)
|
|||
|
|
document.querySelectorAll('.url-input, .rss-input').forEach(function(input) {
|
|||
|
|
input.addEventListener('keypress', function(e) {
|
|||
|
|
if (e.key === 'Enter') {
|
|||
|
|
e.preventDefault();
|
|||
|
|
const uid = this.getAttribute('data-uid');
|
|||
|
|
const type = this.getAttribute('data-type');
|
|||
|
|
const value = this.value.trim();
|
|||
|
|
|
|||
|
|
const formData = new FormData();
|
|||
|
|
formData.append('action', 'save_rss');
|
|||
|
|
formData.append('uid', uid);
|
|||
|
|
formData.append('user_feed', type === 'rss' ? value : '');
|
|||
|
|
if (type === 'url') {
|
|||
|
|
formData.append('user_url', value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fetch('', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: formData
|
|||
|
|
})
|
|||
|
|
.then(response => {
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error('网络响应不正常: ' + response.status);
|
|||
|
|
}
|
|||
|
|
return response.text();
|
|||
|
|
})
|
|||
|
|
.then(text => {
|
|||
|
|
try {
|
|||
|
|
const data = JSON.parse(text);
|
|||
|
|
if (data.success) {
|
|||
|
|
showToast(data.message);
|
|||
|
|
|
|||
|
|
// 更新输入框状态
|
|||
|
|
const inputStatus = input.parentNode.querySelector('.input-status');
|
|||
|
|
if (value) {
|
|||
|
|
input.classList.remove('empty');
|
|||
|
|
input.classList.add('has-value');
|
|||
|
|
inputStatus.classList.remove('empty');
|
|||
|
|
inputStatus.classList.add('has-value');
|
|||
|
|
} else {
|
|||
|
|
input.classList.remove('has-value');
|
|||
|
|
input.classList.add('empty');
|
|||
|
|
inputStatus.classList.remove('has-value');
|
|||
|
|
inputStatus.classList.add('empty');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
showToast(data.message || '保存失败,请重试!', 'error');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('响应不是有效的JSON:', text);
|
|||
|
|
showToast('服务器响应异常: ' + text.substring(0, 100), 'error');
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
console.error('Error:', error);
|
|||
|
|
showToast('网络错误: ' + error.message, 'error');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 输入时实时更新状态
|
|||
|
|
input.addEventListener('input', function() {
|
|||
|
|
const inputStatus = this.parentNode.querySelector('.input-status');
|
|||
|
|
if (this.value.trim()) {
|
|||
|
|
this.classList.remove('empty');
|
|||
|
|
this.classList.add('has-value');
|
|||
|
|
inputStatus.classList.remove('empty');
|
|||
|
|
inputStatus.classList.add('has-value');
|
|||
|
|
} else {
|
|||
|
|
this.classList.remove('has-value');
|
|||
|
|
this.classList.add('empty');
|
|||
|
|
inputStatus.classList.remove('has-value');
|
|||
|
|
inputStatus.classList.add('empty');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<?php
|
|||
|
|
include 'copyright.php';
|
|||
|
|
include 'common-js.php';
|
|||
|
|
include 'footer.php';
|
|||
|
|
?>
|