功能:增加数据预览,记录替换历史替换记录
优化:字段改为中文显示。
| 苹果cms10程序版本 | 2023.1000.3051 |
- 新增文件
application/admin/view/database/rep_plus.html
application/admin/view/database/rep_log.html - 修改文件
application/admin/common/auth.php
application/admin/controller/Database.php
步骤1,找到 `application/admin/common/auth.php`文件找到代码
'102' => array("show"=>1,'name' => lang('menu/database_sql'), 'controller' => 'database', 'action' => 'sql'),
'103' => array("show"=>1,'name' => lang('menu/database_rep'), 'controller' => 'database', 'action' => 'rep'),
在代码下面添加代码:
'104' => array("show"=>1,'name' => '批量替换(优化版)', 'controller' => 'database', 'action' => 'repPlus'),
'10401' => array("show"=>0,'name' => '--查询替换数据', 'controller' => 'database', 'action' => 'queryRepData'),
'105' => array("show"=>1,'name' => '替换操作记录', 'controller' => 'database', 'action' => 'repLog'),
'10501' => array("show"=>0,'name' => '--清空替换记录', 'controller' => 'database', 'action' => 'clearRepLog'),
步骤2. 找到“application/admin/controller/Database.php”在文件内代码的最后,注意粘贴前先删除文件中最后一个大括号“}”然后再粘贴下面的代码,不然会报错
/**
* 批量数据替换(优化版)- 专门用于视频数据的图片和链接替换
*/
public function repPlus()
{
if($this->request->isPost()){
$param = input();
$table = $param['table'];
$field = $param['field'];
$findstr = $param['findstr'];
$tostr = $param['tostr'];
$where = $param['where'];
$validate = \think\Loader::validate('Token');
if(!$validate->check($param)){
return $this->error($validate->getError());
}
if (!empty($table) && !$this->isValidTable($table)) {
return $this->error('数据表无效');
}
if(!empty($field) && !empty($findstr)){
$count_sql = "SELECT COUNT(*) as total FROM ".$table." WHERE ".$field." LIKE '%".$findstr."%' AND 1=1 ". $where;
$count_result = Db::query($count_sql);
$affected_rows = $count_result[0]['total'] ?? 0;
$sql = "UPDATE ".$table." set ".$field."=Replace(".$field.",'".$findstr."','".$tostr."') where 1=1 ". $where;
Db::execute($sql);
if($table == config('database.prefix').'vod'){
Cache::clear();
}
$this->saveRepLog([
'table_name' => $table,
'field_name' => $field,
'find_str' => $findstr,
'replace_str' => $tostr,
'where_condition' => $where,
'affected_rows' => $affected_rows,
]);
return $this->success('执行成功!已替换 '.$affected_rows.' 条数据');
}
return $this->error(lang('param_err'));
}
$list = Db::query("SHOW TABLE STATUS");
$vod_fields = [
['field' => 'vod_pic', 'name' => '视频封面图'],
['field' => 'vod_pic_thumb', 'name' => '视频缩略图'],
['field' => 'vod_pic_slide', 'name' => '视频轮播图'],
['field' => 'vod_pic_screenshot', 'name' => '视频截图'],
['field' => 'vod_play_url', 'name' => '播放地址'],
['field' => 'vod_play_from', 'name' => '播放来源'],
['field' => 'vod_play_server', 'name' => '播放服务器'],
['field' => 'vod_down_url', 'name' => '下载地址'],
['field' => 'vod_down_from', 'name' => '下载来源'],
['field' => 'vod_down_server', 'name' => '下载服务器'],
['field' => 'vod_content', 'name' => '视频简介内容'],
['field' => 'vod_blurb', 'name' => '视频简介摘要'],
];
// 文章表常用字段
$art_fields = [
['field' => 'art_pic', 'name' => '文章封面图'],
['field' => 'art_pic_thumb', 'name' => '文章缩略图'],
['field' => 'art_pic_slide', 'name' => '文章轮播图'],
['field' => 'art_pic_screenshot', 'name' => '文章截图'],
['field' => 'art_content', 'name' => '文章内容'],
['field' => 'art_blurb', 'name' => '文章简介'],
];
// 演员表字段
$actor_fields = [
['field' => 'actor_pic', 'name' => '演员图片'],
['field' => 'actor_pic_thumb', 'name' => '演员缩略图'],
['field' => 'actor_content', 'name' => '演员简介'],
];
// 角色表字段
$role_fields = [
['field' => 'role_pic', 'name' => '角色图片'],
['field' => 'role_content', 'name' => '角色简介'],
];
// 网址表字段
$website_fields = [
['field' => 'website_pic', 'name' => '网址图片'],
['field' => 'website_content', 'name' => '网址简介'],
];
$this->assign('list', $list);
$this->assign('vod_fields', $vod_fields);
$this->assign('art_fields', $art_fields);
$this->assign('actor_fields', $actor_fields);
$this->assign('role_fields', $role_fields);
$this->assign('website_fields', $website_fields);
$this->assign('title', '批量数据替换(优化版)');
return $this->fetch('admin@database/rep_plus');
}
public function queryRepData()
{
if($this->request->isPost()){
$param = input();
$table = $param['table'] ?? '';
$field = $param['field'] ?? '';
$findstr = $param['findstr'] ?? '';
$page = isset($param['page']) ? intval($param['page']) : 1;
$limit = 50; // 每页显示50条
if(empty($table) || empty($field) || empty($findstr)){
return json(['code' => 0, 'msg' => '参数不完整']);
}
if (!$this->isValidTable($table)) {
return json(['code' => 0, 'msg' => '数据表无效']);
}
try {
$count_sql = "SELECT COUNT(*) as total FROM `".$table."` WHERE `".$field."` LIKE ?";
$count_result = Db::query($count_sql, ['%'.$findstr.'%']);
$total = $count_result[0]['total'] ?? 0;
if($total == 0){
return json([
'code' => 1,
'msg' => '未找到匹配的数据',
'data' => [
'total' => 0,
'list' => [],
'page' => $page,
'page_size' => $limit,
'total_page' => 0
]
]);
}
$offset = ($page - 1) * $limit;
$pk_field = $this->getTablePrimaryKey($table);
$name_field = $this->getTableNameField($table);
if($name_field && !$this->checkFieldExists($table, $name_field)){
$name_field = null;
}
if($name_field && $name_field != $field && $name_field != $pk_field){
$query_sql = "SELECT `".$pk_field."`, `".$name_field."`, `".$field."` FROM `".$table."` WHERE `".$field."` LIKE ? LIMIT ?, ?";
} else {
$query_sql = "SELECT `".$pk_field."`, `".$field."` FROM `".$table."` WHERE `".$field."` LIKE ? LIMIT ?, ?";
}
$list = Db::query($query_sql, ['%'.$findstr.'%', $offset, $limit]);
return json([
'code' => 1,
'msg' => '查询成功',
'data' => [
'total' => $total,
'list' => $list,
'page' => $page,
'page_size' => $limit,
'total_page' => ceil($total / $limit),
'field' => $field,
'pk_field' => $pk_field,
'name_field' => $name_field
]
]);
} catch (\Exception $e) {
return json(['code' => 0, 'msg' => '查询失败:'.$e->getMessage()]);
}
}
return json(['code' => 0, 'msg' => '非法请求']);
}
private function getTablePrimaryKey($table)
{
try {
$columns = Db::query("SHOW COLUMNS FROM `".$table."`");
foreach($columns as $column){
if($column['Key'] == 'PRI'){
return $column['Field'];
}
}
return $columns[0]['Field'] ?? 'id';
} catch (\Exception $e) {
return 'id';
}
}
private function checkFieldExists($table, $field)
{
try {
$columns = Db::query("SHOW COLUMNS FROM `".$table."`");
foreach($columns as $column){
if($column['Field'] == $field){
return true;
}
}
return false;
} catch (\Exception $e) {
return false;
}
}
private function getTableNameField($table)
{
try {
if(strpos($table, 'vod') !== false){
return 'vod_name';
}
else if(strpos($table, 'art') !== false){
return 'art_title';
}
else if(strpos($table, 'actor') !== false){
return 'actor_name';
}
else if(strpos($table, 'role') !== false){
return 'role_name';
}
else if(strpos($table, 'website') !== false){
return 'website_name';
}
else if(strpos($table, 'topic') !== false){
return 'topic_name';
}
else if(strpos($table, 'type') !== false){
return 'type_name';
}
else if(strpos($table, 'user') !== false){
return 'user_name';
}
else if(strpos($table, 'comment') !== false){
return 'comment_content';
}
else if(strpos($table, 'gbook') !== false){
return 'gbook_content';
}
$columns = Db::query("SHOW COLUMNS FROM `".$table."`");
$column_names = array_column($columns, 'Field');
// 优先级顺序:name > title > content
$possible_name_fields = ['name', 'title', 'content'];
foreach($possible_name_fields as $field_suffix){
foreach($column_names as $col){
if(strpos($col, $field_suffix) !== false){
return $col;
}
}
}
return null;
} catch (\Exception $e) {
return null;
}
}
private function saveRepLog($data)
{
$log_file = APP_PATH . 'data/backup/rep_log.txt';
$log_dir = dirname($log_file);
if (!is_dir($log_dir)) {
Dir::create($log_dir, 0755, true);
}
$log_data = [
'time' => date('Y-m-d H:i:s'),
'admin' => $this->_admin['admin_name'] ?? 'system',
'admin_id' => $this->_admin['admin_id'] ?? 0,
'ip' => request()->ip(),
'table' => $data['table_name'],
'field' => $data['field_name'],
'find' => mb_substr($data['find_str'], 0, 200),
'replace' => mb_substr($data['replace_str'], 0, 200),
'where' => $data['where_condition'],
'rows' => $data['affected_rows'],
];
$log_line = json_encode($log_data, JSON_UNESCAPED_UNICODE) . "\n";
file_put_contents($log_file, $log_line, FILE_APPEND | LOCK_EX);
}
public function repLog()
{
$param = input();
$page = isset($param['page']) ? intval($param['page']) : 1;
$limit = 20;
$keyword = isset($param['keyword']) ? trim($param['keyword']) : '';
$date_start = isset($param['date_start']) ? trim($param['date_start']) : '';
$date_end = isset($param['date_end']) ? trim($param['date_end']) : '';
$log_file = APP_PATH . 'data/backup/rep_log.txt';
$list = [];
$total = 0;
if (file_exists($log_file)) {
// 读取所有记录(倒序)
$lines = file($log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$lines = array_reverse($lines);
$all_logs = [];
foreach ($lines as $line) {
$log = json_decode($line, true);
if ($log) {
if (!empty($keyword)) {
$search_fields = [
$log['table'],
$log['field'],
$log['find'],
$log['replace'],
$log['admin']
];
$match = false;
foreach ($search_fields as $field) {
if (stripos($field, $keyword) !== false) {
$match = true;
break;
}
}
if (!$match) continue;
}
if (!empty($date_start) && $log['time'] < $date_start.' 00:00:00') {
continue;
}
if (!empty($date_end) && $log['time'] > $date_end.' 23:59:59') {
continue;
}
$all_logs[] = $log;
}
}
$total = count($all_logs);
$list = array_slice($all_logs, ($page - 1) * $limit, $limit);
}
$pagecount = ceil($total / $limit);
$this->assign('list', $list);
$this->assign('total', $total);
$this->assign('page', $page);
$this->assign('limit', $limit);
$this->assign('pagecount', $pagecount);
$this->assign('keyword', $keyword);
$this->assign('date_start', $date_start);
$this->assign('date_end', $date_end);
$this->assign('title', '批量替换操作记录');
return $this->fetch('admin@database/rep_log');
}
public function clearRepLog()
{
$log_file = APP_PATH . 'data/backup/rep_log.txt';
if (file_exists($log_file)) {
$backup_file = APP_PATH . 'data/backup/rep_log_backup_' . date('YmdHis') . '.txt';
copy($log_file, $backup_file);
file_put_contents($log_file, '');
return $this->success('已清空记录,备份文件:' . basename($backup_file));
}
return $this->error('日志文件不存在');
}
}
步骤3.在“application/admin/view/database”文件夹增加文件“rep_plus.html”复制下面代码放入文件
{include file="../../../application/admin/view/public/head" /}
<style>
.layui-form-select ul {max-height:300px}
.layui-btn+.layui-btn{margin-left:0px; }
.field-group {
display: none;
padding: 10px;
background: #f8f8f8;
border-radius: 4px;
margin-top: 10px;
}
.field-btn {
margin: 5px 5px 5px 0;
}
.tips-box {
background: #ecf5ff;
padding: 15px;
border-left: 4px solid #409eff;
margin-bottom: 20px;
border-radius: 4px;
}
.tips-box h4 {
margin: 0 0 10px 0;
color: #409eff;
}
.tips-box p {
margin: 5px 0;
font-size: 13px;
color: #606266;
}
</style>
<div class="page-container">
<form class="layui-form layui-form-pane" action="">
<input type="hidden" name="__token__" value="{$Request.token}" />
<div class="layui-tab">
<ul class="layui-tab-title">
<li class="layui-this">批量数据替换(优化版)</li>
<li style="float:right;">
<a href="{:url('database/repLog')}" class="layui-btn layui-btn-sm layui-btn-normal" style="margin-top: 3px;">
<i class="layui-icon layui-icon-file"></i> 查看替换记录
</a>
</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<!-- 操作提示 -->
<div class="tips-box">
<h4>💡 使用说明</h4>
<p>1. 选择要操作的数据表(视频表、文章表、演员表等)</p>
<p>2. 选择要替换的字段(系统已预设常用字段,支持中文显示)</p>
<p>3. 输入要查找的内容和替换后的内容</p>
<p>4. 可选:添加WHERE条件限制替换范围(如:AND vod_id > 100)</p>
<p style="color: #e6a23c;">⚠️ 重要:替换操作不可逆,建议先备份数据库!</p>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red">*</span> 选择数据表:</label>
<div class="layui-input-inline w400" >
<select name="table" lay-filter="table" lay-verify="required">
<option value="">请选择数据表</option>
{volist name="list" id="vo"}
<option value="{$vo.Name}" {if $vo.Name == $Request.param.prefix.'vod'}selected{/if}>{$vo.Name}{notempty name="vo.Comment"}【{$vo.Comment}】{/notempty}</option>
{/volist}
</select>
</div>
<div class="layui-form-mid layui-word-aux">选择要批量替换的数据表(视频表:mac_vod)(文章表:mac_art)</div>
</div>
<div class="field-group" id="vod-fields">
<label class="layui-form-label">常用字段:</label>
<div class="layui-input-block">
{volist name="vod_fields" id="field"}
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal field-btn" onclick="setField('{$field.field}')">{$field.name}</button>
{/volist}
</div>
</div>
<div class="field-group" id="art-fields">
<label class="layui-form-label">常用字段:</label>
<div class="layui-input-block">
{volist name="art_fields" id="field"}
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal field-btn" onclick="setField('{$field.field}')">{$field.name}</button>
{/volist}
</div>
</div>
<!-- 演员表字段 -->
<div class="field-group" id="actor-fields">
<label class="layui-form-label">常用字段:</label>
<div class="layui-input-block">
{volist name="actor_fields" id="field"}
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal field-btn" onclick="setField('{$field.field}')">{$field.name}</button>
{/volist}
</div>
</div>
<div class="field-group" id="role-fields">
<label class="layui-form-label">常用字段:</label>
<div class="layui-input-block">
{volist name="role_fields" id="field"}
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal field-btn" onclick="setField('{$field.field}')">{$field.name}</button>
{/volist}
</div>
</div>
<div class="field-group" id="website-fields">
<label class="layui-form-label">常用字段:</label>
<div class="layui-input-block">
{volist name="website_fields" id="field"}
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal field-btn" onclick="setField('{$field.field}')">{$field.name}</button>
{/volist}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red">*</span> 字段名:</label>
<div class="layui-input-block" >
<input type="text" id="field" name="field" placeholder="请选择上方字段或手动输入字段名" lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red">*</span> 查找内容:</label>
<div class="layui-input-block" >
<textarea name="findstr" placeholder="输入要查找的内容,如:http://old.com" lay-verify="required" class="layui-textarea" rows="3"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><span style="color:red">*</span> 替换为:</label>
<div class="layui-input-block" >
<textarea name="tostr" placeholder="输入替换后的内容,如:https://new.com(可留空)" class="layui-textarea" rows="3"></textarea>
</div>
</div>
<!-- WHERE条件 -->
<div class="layui-form-item">
<label class="layui-form-label">WHERE条件:</label>
<div class="layui-input-block" >
<input type="text" name="where" placeholder="可选,如:AND vod_id > 100 AND vod_id < 200" value="" class="layui-input">
<div class="layui-form-mid layui-word-aux" style="margin-top:5px;">限制替换范围,留空则替换所有匹配数据</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">使用示例:</label>
<div class="layui-input-block">
<div style="background:#fff3cd;padding:10px;border-radius:4px;font-size:12px;color:#856404;">
<p><strong>示例1:</strong>替换视频封面图的域名</p>
<p>字段名:vod_pic | 查找:http://old.com | 替换:https://new.com</p>
<hr style="margin:10px 0;border:none;border-top:1px solid #ddd;">
<p><strong>示例2:</strong>替换播放地址中的链接</p>
<p>字段名:vod_play_url | 查找:https://player1.com | 替换:https://player2.com</p>
<hr style="margin:10px 0;border:none;border-top:1px solid #ddd;">
<p><strong>示例3:</strong>只替换ID大于100的视频</p>
<p>字段名:vod_pic | 查找:http:// | 替换:https:// | WHERE:AND vod_id > 100</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="layui-form-item center">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-normal layui-btn-lg" id="queryBtn">
<i class="layui-icon layui-icon-search"></i> 查询数据
</button>
<button type="submit" class="layui-btn layui-btn-lg" lay-submit="" lay-filter="formSubmit">
<i class="layui-icon layui-icon-ok"></i> 执行替换
</button>
<button class="layui-btn layui-btn-warm layui-btn-lg" type="reset">
<i class="layui-icon layui-icon-refresh"></i> 重置
</button>
</div>
</div>
</form>
<div id="queryResultBox" style="display:none; margin-top:30px; padding:20px; background:#fff; border-radius:8px; box-shadow:0 2px 12px rgba(0,0,0,0.1);">
<div style="border-bottom:2px solid #409eff; padding-bottom:10px; margin-bottom:20px;">
<h3 style="margin:0; color:#409eff;">
<i class="layui-icon layui-icon-search" style="font-size:20px;"></i> 查询结果
</h3>
</div>
<div id="queryStats" style="padding:15px; background:#ecf5ff; border-radius:4px; margin-bottom:20px;"></div>
<div style="overflow-x:auto;">
<table class="layui-table" lay-skin="line">
<thead>
<tr id="tableHeader">
</tr>
</thead>
<tbody id="tableBody">
</tbody>
</table>
</div>
<!-- 分页 -->
<div id="queryPagination" style="text-align:center; margin-top:20px;"></div>
</div>
</div>
{include file="../../../application/admin/view/public/foot" /}
<script type="text/javascript">
layui.use(['form', 'layer'], function(){
var form = layui.form,
layer = layui.layer,
$ = layui.jquery;
form.on('select(table)', function(data){
$('.field-group').hide();
var tableName = data.value;
if(tableName != ''){
var prefix = '{$Request.param.prefix}';
if(tableName.indexOf('vod') !== -1){
$('#vod-fields').show();
}
else if(tableName.indexOf('art') !== -1){
$('#art-fields').show();
}
else if(tableName.indexOf('actor') !== -1){
$('#actor-fields').show();
}
else if(tableName.indexOf('role') !== -1){
$('#role-fields').show();
}
else if(tableName.indexOf('website') !== -1){
$('#website-fields').show();
}
}
});
form.on('submit(formSubmit)', function(data){
var field = data.field;
layer.confirm('确定要执行批量替换操作吗?<br><br>表:' + field.table + '<br>字段:' + field.field + '<br>查找:' + field.findstr + '<br>替换:' + field.tostr + '<br><br><span style="color:red;">此操作不可逆,请确认已备份数据!</span>', {
icon: 3,
title: '操作确认',
btn: ['确定执行', '我再想想']
}, function(index){
layer.close(index);
var loadIndex = layer.load(1, {shade: [0.3, '#fff']});
$.post("{:url('database/repPlus')}", data.field, function(res) {
layer.close(loadIndex);
if (res.code == 1) {
layer.msg(res.msg, {icon: 1, time: 2000}, function(){
location.reload();
});
} else {
layer.msg(res.msg, {icon: 2});
}
});
return false;
});
return false;
});
var defaultTable = $('select[name="table"]').val();
if(defaultTable){
if(defaultTable.indexOf('vod') !== -1){
$('#vod-fields').show();
}
}
$('#queryBtn').on('click', function(){
var table = $('select[name="table"]').val();
var field = $('input[name="field"]').val();
var findstr = $('textarea[name="findstr"]').val();
if(!table){
layer.msg('请选择数据表', {icon: 2});
return;
}
if(!field){
layer.msg('请输入字段名', {icon: 2});
return;
}
if(!findstr){
layer.msg('请输入查找内容', {icon: 2});
return;
}
queryData(table, field, findstr, 1);
});
});
function setField(fieldName){
$('#field').val(fieldName);
}
var currentQueryParams = {};
function queryData(table, field, findstr, page){
var loadIndex = layer.load(1, {shade: [0.3, '#fff']});
currentQueryParams = {
table: table,
field: field,
findstr: findstr
};
$.post("{:url('database/queryRepData')}", {
table: table,
field: field,
findstr: findstr,
page: page
}, function(res){
layer.close(loadIndex);
if(res.code == 1){
if(res.data.total == 0){
layer.msg('未找到匹配的数据', {icon: 2});
$('#queryResultBox').hide();
return;
}
displayQueryResult(res.data);
$('html, body').animate({
scrollTop: $('#queryResultBox').offset().top - 50
}, 500);
} else {
layer.msg(res.msg, {icon: 2});
}
}).fail(function(){
layer.close(loadIndex);
layer.msg('查询失败,请重试', {icon: 2});
});
}
function displayQueryResult(data){
var statsHtml = '<p style="margin:5px 0; font-size:14px; color:#606266;">' +
'📊 共找到 <strong style="color:#f56c6c; font-size:18px;">' + data.total + '</strong> 条匹配数据' +
'</p>' +
'<p style="margin:5px 0; font-size:13px; color:#909399;">' +
'数据表:<strong>' + currentQueryParams.table + '</strong> | ' +
'字段:<strong>' + data.field + '</strong> | ' +
'查找内容:<strong>' + currentQueryParams.findstr + '</strong>' +
'</p>';
$('#queryStats').html(statsHtml);
var headerHtml = '<th style="width:80px;">序号</th>' +
'<th style="width:100px;">' + data.pk_field + '</th>';
var hasNameField = data.name_field && data.name_field != data.field && data.name_field != data.pk_field;
if(hasNameField){
headerHtml += '<th style="min-width:150px;">' + data.name_field + '</th>';
}
headerHtml += '<th>' + data.field + '</th>';
$('#tableHeader').html(headerHtml);
var bodyHtml = '';
var startIndex = (data.page - 1) * data.page_size;
$.each(data.list, function(index, item){
var rowNum = startIndex + index + 1;
var pkValue = item[data.pk_field];
var fieldValue = item[data.field] || '';
var highlightedValue = highlightKeyword(fieldValue, currentQueryParams.findstr);
bodyHtml += '<tr>' +
'<td style="text-align:center;">' + rowNum + '</td>' +
'<td style="text-align:center;">' + pkValue + '</td>';
if(hasNameField){
var nameValue = item[data.name_field] || '-';
if(nameValue.length > 50){
nameValue = nameValue.substring(0, 50) + '...';
}
bodyHtml += '<td style="word-break:break-all;">' + nameValue + '</td>';
}
var displayValue = formatFieldValue(fieldValue, data.field, currentQueryParams.findstr);
bodyHtml += '<td style="word-break:break-all;">' + displayValue + '</td>' +
'</tr>';
});
$('#tableBody').html(bodyHtml);
if(data.total_page > 1){
var paginationHtml = '';
if(data.page > 1){
paginationHtml += '<button class="layui-btn layui-btn-sm" onclick="queryData(\'' +
currentQueryParams.table + '\', \'' +
currentQueryParams.field + '\', \'' +
currentQueryParams.findstr + '\', ' +
(data.page - 1) + ')">上一页</button> ';
}
paginationHtml += '<span style="margin:0 10px; color:#909399;">第 <strong>' +
data.page + '</strong> / ' + data.total_page + ' 页</span>';
if(data.page < data.total_page){
paginationHtml += ' <button class="layui-btn layui-btn-sm" onclick="queryData(\'' +
currentQueryParams.table + '\', \'' +
currentQueryParams.field + '\', \'' +
currentQueryParams.findstr + '\', ' +
(data.page + 1) + ')">下一页</button>';
}
$('#queryPagination').html(paginationHtml);
} else {
$('#queryPagination').html('');
}
$('#queryResultBox').show();
}
function formatFieldValue(value, fieldName, keyword){
if(!value){
return '-';
}
var originalValue = value;
var maxLength = 200;
if(fieldName.indexOf('content') !== -1 || fieldName.indexOf('text') !== -1){
maxLength = 200;
value = stripHtmlTags(value);
}
else if(fieldName.indexOf('pic') !== -1 || fieldName.indexOf('url') !== -1 ||
fieldName.indexOf('link') !== -1 || fieldName.indexOf('http') !== -1){
maxLength = 300;
}
else if(fieldName.indexOf('remarks') !== -1 || fieldName.indexOf('blurb') !== -1 ||
fieldName.indexOf('desc') !== -1){
maxLength = 150;
value = stripHtmlTags(value);
}
else {
maxLength = 100;
}
if(value.length > maxLength){
if(keyword){
var keywordPos = value.toLowerCase().indexOf(keyword.toLowerCase());
if(keywordPos !== -1){
var start = Math.max(0, keywordPos - 50);
var end = Math.min(value.length, keywordPos + keyword.length + 150);
var excerpt = value.substring(start, end);
if(start > 0) excerpt = '...' + excerpt;
if(end < value.length) excerpt = excerpt + '...';
value = excerpt;
} else {
value = value.substring(0, maxLength) + '...';
}
} else {
value = value.substring(0, maxLength) + '...';
}
}
value = highlightKeyword(value, keyword);
if(originalValue.length > maxLength){
value += '<br><a href="javascript:void(0);" onclick="showFullContent(\'' +
escapeHtml(originalValue) + '\')" style="color:#409eff; font-size:12px;">' +
'[查看完整内容 ' + originalValue.length + ' 字符]</a>';
}
return value;
}
function stripHtmlTags(html){
if(!html) return '';
var tmp = document.createElement('div');
tmp.innerHTML = html;
var text = tmp.textContent || tmp.innerText || '';
text = text.replace(/\s+/g, ' ').trim();
return text;
}
function escapeHtml(text){
if(!text) return '';
return text.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>');
}
function showFullContent(content){
content = content.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>');
layer.open({
type: 1,
title: '完整内容',
area: ['800px', '600px'],
content: '<div style="padding:20px; max-height:520px; overflow-y:auto; word-break:break-all;">' +
'<pre style="white-space:pre-wrap; font-family:inherit;">' + content + '</pre>' +
'</div>',
btn: ['复制内容', '关闭'],
yes: function(index, layero){
// 复制内容到剪贴板
var textarea = document.createElement('textarea');
textarea.value = content;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
layer.msg('内容已复制到剪贴板', {icon: 1});
} catch (err) {
layer.msg('复制失败,请手动复制', {icon: 2});
}
document.body.removeChild(textarea);
}
});
}
function highlightKeyword(text, keyword){
if(!text || !keyword){
return text;
}
var escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
var regex = new RegExp(escapedKeyword, 'gi');
return text.replace(regex, function(match){
return '<span style="background:#ffeb3b; color:#000; padding:2px 4px; border-radius:2px;">' + match + '</span>';
});
}
</script>
</body>
</html>
步骤4.找到文件夹“application/admin/view/database”在文件夹内增加文件“rep_log.html”把下面代码复制到文件内
{include file="../../../application/admin/view/public/head" /}
<style>
.search-box {
background: #fff;
padding: 15px;
margin-bottom: 15px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.layui-table td {
word-break: break-all;
}
.log-detail {
font-size: 12px;
color: #666;
line-height: 1.6;
}
.log-content {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 12px;
background: #409eff;
color: #fff;
}
.stats-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 15px 20px;
border-radius: 4px;
margin-bottom: 15px;
}
.stats-box h3 {
margin: 0;
font-size: 24px;
}
</style>
<div class="page-container p15">
<div class="stats-box">
<h3><i class="layui-icon layui-icon-chart"></i> 共 {$total} 条替换记录</h3>
<p style="margin: 5px 0 0 0; opacity: 0.9;">记录文件:application/data/backup/rep_log.txt</p>
</div>
<div class="search-box">
<form class="layui-form" action="{:url('database/repLog')}" method="get">
<div class="layui-form-item" style="margin-bottom: 0;">
<div class="layui-inline">
<label class="layui-form-label" style="width: 80px;">关键词:</label>
<div class="layui-input-inline" style="width: 200px;">
<input type="text" name="keyword" value="{$keyword}" placeholder="搜索表名/字段/内容/操作人" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label" style="width: 80px;">开始日期:</label>
<div class="layui-input-inline" style="width: 150px;">
<input type="text" name="date_start" id="date_start" value="{$date_start}" placeholder="开始日期" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label" style="width: 80px;">结束日期:</label>
<div class="layui-input-inline" style="width: 150px;">
<input type="text" name="date_end" id="date_end" value="{$date_end}" placeholder="结束日期" class="layui-input">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layui-btn-normal" type="submit">
<i class="layui-icon layui-icon-search"></i> 搜索
</button>
<a href="{:url('database/repLog')}" class="layui-btn layui-btn-primary">
<i class="layui-icon layui-icon-refresh"></i> 重置
</a>
<button type="button" class="layui-btn layui-btn-danger" onclick="clearLog()">
<i class="layui-icon layui-icon-delete"></i> 清空记录
</button>
</div>
</div>
</form>
</div>
<div style="background: #fff; padding: 15px; border-radius: 4px;">
{empty name="list"}
<div style="text-align: center; padding: 50px 0; color: #999;">
<i class="layui-icon layui-icon-file" style="font-size: 60px;"></i>
<p style="margin-top: 10px;">暂无替换记录</p>
</div>
{else/}
<table class="layui-table" lay-skin="line">
<thead>
<tr>
<th width="160">操作时间</th>
<th width="100">操作人</th>
<th width="150">数据表</th>
<th width="120">字段</th>
<th>查找内容</th>
<th>替换内容</th>
<th width="100">影响行数</th>
<th width="120">操作IP</th>
</tr>
</thead>
<tbody>
{volist name="list" id="vo"}
<tr>
<td>{$vo.time}</td>
<td>
<span class="badge">{$vo.admin}</span>
</td>
<td>
<strong style="color: #409eff;">{$vo.table}</strong>
</td>
<td>
<code style="background: #f4f4f5; padding: 2px 6px; border-radius: 3px;">{$vo.field}</code>
</td>
<td>
<div class="log-content" title="{$vo.find}">
{$vo.find|default='(空)'}
</div>
</td>
<td>
<div class="log-content" title="{$vo.replace}">
{$vo.replace|default='(空)'}
</div>
</td>
<td>
<span style="color: #67c23a; font-weight: bold;">{$vo.rows}</span> 条
</td>
<td>{$vo.ip}</td>
</tr>
{if !empty($vo.where)}
<tr style="background: #fafafa;">
<td colspan="8">
<div class="log-detail">
<strong>WHERE条件:</strong> {$vo.where|default='无'}
</div>
</td>
</tr>
{/if}
{/volist}
</tbody>
</table>
{if $pagecount > 1}
<div id="page" style="text-align: right; padding-top: 10px;"></div>
{/if}
{/empty}
</div>
</div>
{include file="../../../application/admin/view/public/foot" /}
<script type="text/javascript">
layui.use(['laydate', 'laypage', 'layer'], function(){
var laydate = layui.laydate,
laypage = layui.laypage,
layer = layui.layer,
$ = layui.jquery;
laydate.render({
elem: '#date_start',
type: 'date'
});
laydate.render({
elem: '#date_end',
type: 'date'
});
// 分页
{if $pagecount > 1}
laypage.render({
elem: 'page',
count: {$total},
limit: {$limit},
curr: {$page},
layout: ['count', 'prev', 'page', 'next', 'skip'],
jump: function(obj, first){
if(!first){
var url = '{:url("database/repLog")}?page=' + obj.curr;
{if !empty($keyword)}
url += '&keyword={$keyword}';
{/if}
{if !empty($date_start)}
url += '&date_start={$date_start}';
{/if}
{if !empty($date_end)}
url += '&date_end={$date_end}';
{/if}
location.href = url;
}
}
});
{/if}
});
function clearLog() {
layui.layer.confirm('确定要清空所有替换记录吗?<br><br><span style="color:#ff5722;">原记录将被备份,不会丢失</span>', {
icon: 3,
title: '清空确认',
btn: ['确定清空', '取消']
}, function(index){
layui.layer.close(index);
var loadIndex = layui.layer.load(1);
layui.jquery.post("{:url('database/clearRepLog')}", {}, function(res) {
layui.layer.close(loadIndex);
if (res.code == 1) {
layui.layer.msg(res.msg, {icon: 1}, function(){
location.reload();
});
} else {
layui.layer.msg(res.msg, {icon: 2});
}
});
});
}
</script>
</body>
</html>
最后在苹果cms10后台的“数据库”中的批量替换(优化版)使用
暂无评论...