Initial commit: Flutter 无书应用项目
This commit is contained in:
78
ht/inc/conn.php
Normal file
78
ht/inc/conn.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 数据库连接及全局配置文件
|
||||
*/
|
||||
|
||||
// 设置时区
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
// 网站全局配置
|
||||
$CONFIG = [
|
||||
// 网站标题
|
||||
'site_title' => '简约投票简洁投票系统',
|
||||
|
||||
// 数据库配置
|
||||
'db' => [
|
||||
'host' => 'localhost',
|
||||
'user' => 'toupiao',
|
||||
'pass' => 'toupiao',
|
||||
'name' => 'toupiao',
|
||||
'port' => 3306,
|
||||
'prefix' => 'tp_',
|
||||
'charset' => 'utf8'
|
||||
],
|
||||
|
||||
// 资源文件缓存控制(修改版本号可更新前端缓存)
|
||||
'version' => [
|
||||
'css' => '1.0.0',
|
||||
'js' => '1.0.0'
|
||||
]
|
||||
];
|
||||
|
||||
// 数据库连接
|
||||
function dbConnect() {
|
||||
global $CONFIG;
|
||||
|
||||
$conn = new mysqli(
|
||||
$CONFIG['db']['host'],
|
||||
$CONFIG['db']['user'],
|
||||
$CONFIG['db']['pass'],
|
||||
$CONFIG['db']['name'],
|
||||
$CONFIG['db']['port']
|
||||
);
|
||||
|
||||
// 检查连接
|
||||
if ($conn->connect_error) {
|
||||
die("数据库连接失败: " . $conn->connect_error);
|
||||
}
|
||||
|
||||
// 设置字符集
|
||||
$conn->set_charset($CONFIG['db']['charset']);
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
// 数据表前缀函数
|
||||
function getTable($table) {
|
||||
global $CONFIG;
|
||||
return $CONFIG['db']['prefix'] . $table;
|
||||
}
|
||||
|
||||
// 获取带版本号的CSS路径
|
||||
function getCssPath($file) {
|
||||
global $CONFIG;
|
||||
return "/assets/css/{$file}?v=" . $CONFIG['version']['css'];
|
||||
}
|
||||
|
||||
// 获取带版本号的JS路径
|
||||
function getJsPath($file) {
|
||||
global $CONFIG;
|
||||
return "/assets/js/{$file}?v=" . $CONFIG['version']['js'];
|
||||
}
|
||||
|
||||
// 获取网站标题
|
||||
function getSiteTitle() {
|
||||
global $CONFIG;
|
||||
return $CONFIG['site_title'];
|
||||
}
|
||||
495
ht/inc/css.css
Normal file
495
ht/inc/css.css
Normal file
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* 公共CSS样式
|
||||
*/
|
||||
|
||||
/* 全局重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 顶部菜单样式 */
|
||||
.header {
|
||||
background-color: #2c3e50;
|
||||
color: #fff;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-left: 20px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
/* 主体内容 */
|
||||
.main {
|
||||
max-width: 1200px;
|
||||
margin: 80px auto 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 遮罩层样式 */
|
||||
.mask-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mask-dialog {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.mask-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.mask-title h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.mask-close {
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.mask-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mask-content {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mask-buttons {
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
.mask-button {
|
||||
padding: 8px 15px;
|
||||
margin-left: 10px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-default:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #e74c3c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
/* 表格通用样式 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f8f8f8;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
th.sort-asc::after {
|
||||
content: '↑';
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
th.sort-desc::after {
|
||||
content: '↓';
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 表单通用样式 */
|
||||
.form-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #3498db;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
color: #e74c3c;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.form-submit {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.form-submit:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.pagination.disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
padding: 8px 12px;
|
||||
margin: 0 3px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.page-item:hover:not(.disabled) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-item.disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-select {
|
||||
margin: 0 10px;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* 提示框样式 */
|
||||
.suggest-container {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-top: none;
|
||||
border-radius: 0 0 3px 3px;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.suggest-item {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.suggest-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.suggest-close {
|
||||
padding: 10px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* 按钮通用样式 */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 12px 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-blue:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn-green {
|
||||
background-color: #2ecc71;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-green:hover {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
background-color: #e74c3c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-red:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.btn-gray {
|
||||
background-color: #95a5a6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-gray:hover {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
/* 投票选项样式 */
|
||||
.vote-option {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vote-option:hover {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.vote-option.selected {
|
||||
border-color: #3498db;
|
||||
background-color: #ebf5fb;
|
||||
}
|
||||
|
||||
.vote-option-img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.vote-result {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.vote-bar {
|
||||
height: 20px;
|
||||
background-color: #eee;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.vote-bar-fill {
|
||||
height: 100%;
|
||||
background-color: #3498db;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.header-container {
|
||||
height: auto;
|
||||
padding: 10px 15px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-top: 120px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.mask-dialog {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
458
ht/inc/js.js
Normal file
458
ht/inc/js.js
Normal file
@@ -0,0 +1,458 @@
|
||||
/**
|
||||
* 公共JavaScript函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ajax通信函数
|
||||
* @param {string} url - 请求地址
|
||||
* @param {Object} data - 请求数据
|
||||
* @param {Function} callback - 成功回调函数
|
||||
* @param {Function} errorCallback - 错误回调函数
|
||||
* @param {string} method - 请求方法(GET或POST)
|
||||
*/
|
||||
function ajaxRequest(url, data, callback, errorCallback, method) {
|
||||
// 默认使用POST方法,除非act为get
|
||||
if (!method) {
|
||||
method = (data && data.act === 'get') ? 'GET' : 'POST';
|
||||
}
|
||||
|
||||
// 创建XMLHttpRequest对象
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
// 准备发送请求
|
||||
xhr.open(method, url, true);
|
||||
|
||||
// 设置请求头
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
|
||||
if (method === 'POST') {
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
var response;
|
||||
try {
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(response);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析JSON失败:', e);
|
||||
if (errorCallback && typeof errorCallback === 'function') {
|
||||
errorCallback('解析响应失败');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('请求失败,状态码:', xhr.status);
|
||||
if (errorCallback && typeof errorCallback === 'function') {
|
||||
errorCallback('请求失败,状态码: ' + xhr.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理请求超时
|
||||
xhr.ontimeout = function() {
|
||||
console.error('请求超时');
|
||||
if (errorCallback && typeof errorCallback === 'function') {
|
||||
errorCallback('请求超时');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理网络错误
|
||||
xhr.onerror = function() {
|
||||
console.error('网络错误');
|
||||
if (errorCallback && typeof errorCallback === 'function') {
|
||||
errorCallback('网络错误');
|
||||
}
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
if (method === 'POST' && data) {
|
||||
// 将对象转换为查询字符串
|
||||
var params = Object.keys(data).map(function(key) {
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
|
||||
}).join('&');
|
||||
xhr.send(params);
|
||||
} else if (method === 'GET' && data) {
|
||||
// 将查询参数添加到URL
|
||||
var queryString = Object.keys(data).map(function(key) {
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
|
||||
}).join('&');
|
||||
|
||||
var separator = url.indexOf('?') !== -1 ? '&' : '?';
|
||||
xhr.send();
|
||||
} else {
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页显示函数
|
||||
* @param {string} containerId - 分页容器ID
|
||||
* @param {number} currentPage - 当前页码
|
||||
* @param {number} totalPages - 总页数
|
||||
* @param {Function} callback - 页码点击回调函数
|
||||
*/
|
||||
function pagination(containerId, currentPage, totalPages, callback) {
|
||||
var container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
// 清空容器
|
||||
container.innerHTML = '';
|
||||
|
||||
// 如果没有数据或只有一页,不显示分页
|
||||
if (totalPages <= 1) {
|
||||
container.classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
container.classList.remove('disabled');
|
||||
|
||||
// 创建分页元素
|
||||
var paginationHtml = '';
|
||||
|
||||
// 首页按钮
|
||||
var firstDisabled = currentPage === 1 ? ' disabled' : '';
|
||||
paginationHtml += '<a href="javascript:;" class="page-item first' + firstDisabled + '" data-page="1">首页</a>';
|
||||
|
||||
// 上一页按钮
|
||||
var prevDisabled = currentPage === 1 ? ' disabled' : '';
|
||||
var prevPage = Math.max(1, currentPage - 1);
|
||||
paginationHtml += '<a href="javascript:;" class="page-item prev' + prevDisabled + '" data-page="' + prevPage + '">上一页</a>';
|
||||
|
||||
// 页码下拉选择
|
||||
paginationHtml += '<select class="page-select">';
|
||||
for (var i = 1; i <= totalPages; i++) {
|
||||
var selected = i === currentPage ? ' selected' : '';
|
||||
paginationHtml += '<option value="' + i + '"' + selected + '>第 ' + i + ' 页</option>';
|
||||
}
|
||||
paginationHtml += '</select>';
|
||||
|
||||
// 下一页按钮
|
||||
var nextDisabled = currentPage === totalPages ? ' disabled' : '';
|
||||
var nextPage = Math.min(totalPages, currentPage + 1);
|
||||
paginationHtml += '<a href="javascript:;" class="page-item next' + nextDisabled + '" data-page="' + nextPage + '">下一页</a>';
|
||||
|
||||
// 尾页按钮
|
||||
var lastDisabled = currentPage === totalPages ? ' disabled' : '';
|
||||
paginationHtml += '<a href="javascript:;" class="page-item last' + lastDisabled + '" data-page="' + totalPages + '">尾页</a>';
|
||||
|
||||
// 设置HTML
|
||||
container.innerHTML = paginationHtml;
|
||||
|
||||
// 绑定事件
|
||||
// 点击页码按钮
|
||||
var pageItems = container.querySelectorAll('.page-item');
|
||||
for (var j = 0; j < pageItems.length; j++) {
|
||||
var item = pageItems[j];
|
||||
if (!item.classList.contains('disabled')) {
|
||||
item.addEventListener('click', function() {
|
||||
var page = parseInt(this.getAttribute('data-page'));
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(page);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉选择页码
|
||||
var pageSelect = container.querySelector('.page-select');
|
||||
if (pageSelect) {
|
||||
pageSelect.addEventListener('change', function() {
|
||||
var page = parseInt(this.value);
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(page);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格排序函数
|
||||
* @param {string} tableId - 表格ID
|
||||
* @param {number} colIndex - 排序列索引
|
||||
* @param {string} type - 排序类型(string, number, date)
|
||||
*/
|
||||
function sortTable(tableId, colIndex, type) {
|
||||
var table = document.getElementById(tableId);
|
||||
if (!table) return;
|
||||
|
||||
var tbody = table.getElementsByTagName('tbody')[0];
|
||||
var rows = tbody.getElementsByTagName('tr');
|
||||
var sortedRows = Array.prototype.slice.call(rows, 0);
|
||||
|
||||
// 确定排序方向
|
||||
var sortDirection = 1; // 1为升序,-1为降序
|
||||
if (table.getAttribute('data-sort-col') == colIndex) {
|
||||
sortDirection = table.getAttribute('data-sort-dir') == 'asc' ? -1 : 1;
|
||||
}
|
||||
|
||||
// 保存排序信息
|
||||
table.setAttribute('data-sort-col', colIndex);
|
||||
table.setAttribute('data-sort-dir', sortDirection == 1 ? 'asc' : 'desc');
|
||||
|
||||
// 排序
|
||||
sortedRows.sort(function(a, b) {
|
||||
var cellA = a.cells[colIndex].textContent.trim();
|
||||
var cellB = b.cells[colIndex].textContent.trim();
|
||||
|
||||
if (type === 'number') {
|
||||
return sortDirection * (parseFloat(cellA) - parseFloat(cellB));
|
||||
} else if (type === 'date') {
|
||||
var dateA = new Date(cellA);
|
||||
var dateB = new Date(cellB);
|
||||
return sortDirection * (dateA - dateB);
|
||||
} else {
|
||||
return sortDirection * cellA.localeCompare(cellB);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新表格显示
|
||||
for (var i = 0; i < sortedRows.length; i++) {
|
||||
tbody.appendChild(sortedRows[i]);
|
||||
}
|
||||
|
||||
// 更新表格头部排序指示器
|
||||
var headers = table.getElementsByTagName('th');
|
||||
for (var j = 0; j < headers.length; j++) {
|
||||
headers[j].classList.remove('sort-asc', 'sort-desc');
|
||||
}
|
||||
|
||||
if (headers[colIndex]) {
|
||||
headers[colIndex].classList.add(sortDirection == 1 ? 'sort-asc' : 'sort-desc');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示遮罩层
|
||||
* @param {string} title - 标题
|
||||
* @param {string} content - 内容
|
||||
* @param {Array} buttons - 按钮配置数组,格式:[{text: '按钮文字', callback: 回调函数, class: 'btn-class'}]
|
||||
*/
|
||||
function showMask(title, content, buttons) {
|
||||
// 移除已存在的遮罩
|
||||
closeMask();
|
||||
|
||||
// 创建遮罩层
|
||||
var mask = document.createElement('div');
|
||||
mask.className = 'mask-container';
|
||||
|
||||
// 创建对话框
|
||||
var dialog = document.createElement('div');
|
||||
dialog.className = 'mask-dialog';
|
||||
|
||||
// 创建标题栏
|
||||
var titleBar = document.createElement('div');
|
||||
titleBar.className = 'mask-title';
|
||||
titleBar.innerHTML = '<h3>' + title + '</h3><span class="mask-close">×</span>';
|
||||
|
||||
// 创建内容区
|
||||
var contentArea = document.createElement('div');
|
||||
contentArea.className = 'mask-content';
|
||||
contentArea.innerHTML = content;
|
||||
|
||||
// 创建按钮区
|
||||
var buttonArea = document.createElement('div');
|
||||
buttonArea.className = 'mask-buttons';
|
||||
|
||||
if (buttons && buttons.length > 0) {
|
||||
buttons.forEach(function(btn) {
|
||||
var button = document.createElement('button');
|
||||
button.className = 'mask-button ' + (btn.class || '');
|
||||
button.textContent = btn.text;
|
||||
|
||||
if (btn.callback && typeof btn.callback === 'function') {
|
||||
button.addEventListener('click', function() {
|
||||
btn.callback();
|
||||
});
|
||||
}
|
||||
|
||||
buttonArea.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
// 组装对话框
|
||||
dialog.appendChild(titleBar);
|
||||
dialog.appendChild(contentArea);
|
||||
dialog.appendChild(buttonArea);
|
||||
mask.appendChild(dialog);
|
||||
|
||||
// 添加到页面
|
||||
document.body.appendChild(mask);
|
||||
|
||||
// 绑定关闭事件
|
||||
var closeBtn = mask.querySelector('.mask-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', closeMask);
|
||||
}
|
||||
|
||||
// 绑定点击遮罩层关闭
|
||||
mask.addEventListener('click', function(e) {
|
||||
if (e.target === mask) {
|
||||
closeMask();
|
||||
}
|
||||
});
|
||||
|
||||
// 禁止页面滚动
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 调整内容区域最大高度
|
||||
var contentMaxHeight = window.innerHeight * 0.7;
|
||||
contentArea.style.maxHeight = contentMaxHeight + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭遮罩层
|
||||
*/
|
||||
function closeMask() {
|
||||
var mask = document.querySelector('.mask-container');
|
||||
if (mask) {
|
||||
mask.parentNode.removeChild(mask);
|
||||
}
|
||||
|
||||
// 恢复页面滚动
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入提示功能
|
||||
* @param {string} inputId - 输入框ID
|
||||
* @param {string} url - 获取提示数据的URL
|
||||
* @param {Object} params - 附加参数
|
||||
* @param {Function} callback - 选择项后的回调函数
|
||||
*/
|
||||
function searchSuggest(inputId, url, params, callback) {
|
||||
var input = document.getElementById(inputId);
|
||||
if (!input) return;
|
||||
|
||||
// 创建提示容器
|
||||
var suggestContainer = document.createElement('div');
|
||||
suggestContainer.className = 'suggest-container';
|
||||
suggestContainer.style.display = 'none';
|
||||
input.parentNode.appendChild(suggestContainer);
|
||||
|
||||
// 定位提示容器
|
||||
function positionSuggest() {
|
||||
var rect = input.getBoundingClientRect();
|
||||
suggestContainer.style.top = (rect.bottom + window.scrollY) + 'px';
|
||||
suggestContainer.style.left = (rect.left + window.scrollX) + 'px';
|
||||
suggestContainer.style.width = rect.width + 'px';
|
||||
}
|
||||
|
||||
// 暂存上一次请求的关键词
|
||||
var lastKeyword = '';
|
||||
var debounceTimer = null;
|
||||
|
||||
// 处理输入事件
|
||||
input.addEventListener('input', function() {
|
||||
var keyword = input.value.trim();
|
||||
|
||||
// 清除上一次的定时器
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
// 如果关键词为空,隐藏提示
|
||||
if (!keyword) {
|
||||
suggestContainer.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果关键词与上一次相同,不重新请求
|
||||
if (keyword === lastKeyword) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置300ms的防抖定时器
|
||||
debounceTimer = setTimeout(function() {
|
||||
lastKeyword = keyword;
|
||||
|
||||
// 准备请求参数
|
||||
var requestParams = Object.assign({}, params || {}, {
|
||||
keyword: keyword
|
||||
});
|
||||
|
||||
// 发送请求获取提示数据
|
||||
ajaxRequest(url, requestParams, function(response) {
|
||||
if (response.code === 0 && Array.isArray(response.data) && response.data.length > 0) {
|
||||
// 清空容器
|
||||
suggestContainer.innerHTML = '';
|
||||
|
||||
// 添加关闭按钮
|
||||
var closeButton = document.createElement('div');
|
||||
closeButton.className = 'suggest-close';
|
||||
closeButton.textContent = '关闭';
|
||||
closeButton.addEventListener('click', function() {
|
||||
suggestContainer.style.display = 'none';
|
||||
});
|
||||
suggestContainer.appendChild(closeButton);
|
||||
|
||||
// 限制最多显示10条
|
||||
var maxItems = Math.min(10, response.data.length);
|
||||
|
||||
// 添加提示项
|
||||
for (var i = 0; i < maxItems; i++) {
|
||||
var item = response.data[i];
|
||||
var suggestionItem = document.createElement('div');
|
||||
suggestionItem.className = 'suggest-item';
|
||||
suggestionItem.textContent = item.text || item.name || item;
|
||||
suggestionItem.setAttribute('data-value', item.value || item.id || item);
|
||||
|
||||
// 绑定点击事件
|
||||
suggestionItem.addEventListener('click', function() {
|
||||
var value = this.getAttribute('data-value');
|
||||
var text = this.textContent;
|
||||
|
||||
// 设置输入框值
|
||||
input.value = text;
|
||||
|
||||
// 隐藏提示
|
||||
suggestContainer.style.display = 'none';
|
||||
|
||||
// 触发回调
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(value, text);
|
||||
}
|
||||
});
|
||||
|
||||
suggestContainer.appendChild(suggestionItem);
|
||||
}
|
||||
|
||||
// 显示提示容器
|
||||
positionSuggest();
|
||||
suggestContainer.style.display = 'block';
|
||||
} else {
|
||||
suggestContainer.style.display = 'none';
|
||||
}
|
||||
}, function() {
|
||||
suggestContainer.style.display = 'none';
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// 点击输入框时,如果有数据则显示提示
|
||||
input.addEventListener('click', function() {
|
||||
if (lastKeyword && suggestContainer.childElementCount > 1) {
|
||||
positionSuggest();
|
||||
suggestContainer.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// 点击页面其他区域隐藏提示
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target !== input && !suggestContainer.contains(e.target)) {
|
||||
suggestContainer.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// 窗口调整大小时重新定位
|
||||
window.addEventListener('resize', function() {
|
||||
if (suggestContainer.style.display === 'block') {
|
||||
positionSuggest();
|
||||
}
|
||||
});
|
||||
}
|
||||
147
ht/inc/pubs.php
Normal file
147
ht/inc/pubs.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 公共函数文件
|
||||
*/
|
||||
|
||||
// 引入数据库连接
|
||||
require_once 'conn.php';
|
||||
|
||||
/**
|
||||
* 输出JSON格式响应
|
||||
* @param int $code 状态码,0表示成功,非0表示失败
|
||||
* @param string $msg 提示信息
|
||||
* @param array $data 返回数据
|
||||
*/
|
||||
function ajaxReturn($code = 0, $msg = '', $data = []) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'code' => $code,
|
||||
'msg' => $msg,
|
||||
'data' => $data
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户登录状态
|
||||
* @return array|bool 已登录返回用户信息数组,未登录返回false
|
||||
*/
|
||||
function checkLogin() {
|
||||
session_start();
|
||||
if (isset($_SESSION['user']) && !empty($_SESSION['user']['id'])) {
|
||||
return $_SESSION['user'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查管理员权限
|
||||
* @return bool 是否具有管理员权限
|
||||
*/
|
||||
function checkAdmin() {
|
||||
$user = checkLogin();
|
||||
if ($user && $user['irole'] == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入安全过滤
|
||||
* @param mixed $data 需要过滤的数据
|
||||
* @return mixed 过滤后的数据
|
||||
*/
|
||||
function safeFilter($data) {
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $key => $val) {
|
||||
$data[$key] = safeFilter($val);
|
||||
}
|
||||
} else {
|
||||
// 去除空格
|
||||
$data = trim($data);
|
||||
// 转义特殊字符
|
||||
$data = htmlspecialchars($data, ENT_QUOTES);
|
||||
// 防止SQL注入
|
||||
$data = addslashes($data);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统日志记录
|
||||
* @param int $userId 用户ID
|
||||
* @param string $action 操作类型
|
||||
* @param string $content 操作内容
|
||||
*/
|
||||
function writeLog($userId, $action, $content = '') {
|
||||
$conn = dbConnect();
|
||||
$table = getTable('logs');
|
||||
$userId = (int)$userId;
|
||||
$action = $conn->real_escape_string($action);
|
||||
$content = $conn->real_escape_string($content);
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$time = date('Y-m-d H:i:s');
|
||||
|
||||
$sql = "INSERT INTO {$table} (user_id, action, idesc, ip, logtime) VALUES ($userId, '$action', '$content', '$ip', '$time')";
|
||||
$conn->query($sql);
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限检查函数
|
||||
* @param string $action 操作名称
|
||||
* @param bool $adminRequired 是否需要管理员权限
|
||||
* @return bool|array 成功返回用户信息,失败返回false
|
||||
*/
|
||||
function checkAuth($action, $adminRequired = false) {
|
||||
$user = checkLogin();
|
||||
|
||||
// 未登录
|
||||
if (!$user) {
|
||||
ajaxReturn(403, '请先登录后再操作');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 需要管理员权限但不是管理员
|
||||
if ($adminRequired && $user['irole'] != 1) {
|
||||
ajaxReturn(403, '权限不足,需要管理员权限');
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分页参数
|
||||
* @param int $total 总记录数
|
||||
* @param int $page 当前页码
|
||||
* @param int $pageSize 每页记录数
|
||||
* @return array 分页信息
|
||||
*/
|
||||
function getPagination($total, $page = 1, $pageSize = 10) {
|
||||
$page = max(1, intval($page));
|
||||
$pageSize = max(1, intval($pageSize));
|
||||
|
||||
$totalPage = ceil($total / $pageSize);
|
||||
$totalPage = max(1, $totalPage);
|
||||
$page = min($page, $totalPage);
|
||||
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'pageSize' => $pageSize,
|
||||
'totalPage' => $totalPage,
|
||||
'offset' => $offset
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间
|
||||
* @return string 格式化的时间字符串
|
||||
*/
|
||||
function getCurrentTime() {
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
281
ht/inc/sqls.php
Normal file
281
ht/inc/sqls.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 数据库操作类
|
||||
*/
|
||||
|
||||
require_once 'conn.php';
|
||||
|
||||
class DB {
|
||||
private $conn;
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化数据库连接
|
||||
*/
|
||||
public function __construct() {
|
||||
global $CONFIG;
|
||||
$this->conn = dbConnect();
|
||||
$this->prefix = $CONFIG['db']['prefix'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 析构函数,关闭数据库连接
|
||||
*/
|
||||
public function __destruct() {
|
||||
if ($this->conn) {
|
||||
$this->conn->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义字符串
|
||||
* @param string $str 需要转义的字符串
|
||||
* @return string 转义后的字符串
|
||||
*/
|
||||
public function escape($str) {
|
||||
return $this->conn->real_escape_string($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带前缀的表名
|
||||
* @param string $table 表名
|
||||
* @return string 带前缀的表名
|
||||
*/
|
||||
public function table($table) {
|
||||
return $this->prefix . $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL查询
|
||||
* @param string $sql SQL语句
|
||||
* @return mysqli_result|bool 查询结果
|
||||
*/
|
||||
public function query($sql) {
|
||||
return $this->conn->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一条记录
|
||||
* @param string $table 表名(不带前缀)
|
||||
* @param string|array $where 查询条件
|
||||
* @param string $fields 返回字段
|
||||
* @return array|null 查询结果
|
||||
*/
|
||||
public function getOne($table, $where = '', $fields = '*') {
|
||||
$table = $this->table($table);
|
||||
$whereStr = $this->parseWhere($where);
|
||||
|
||||
$sql = "SELECT {$fields} FROM {$table} {$whereStr} LIMIT 1";
|
||||
$result = $this->query($sql);
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
return $result->fetch_assoc();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多条记录
|
||||
* @param string $table 表名(不带前缀)
|
||||
* @param string|array $where 查询条件
|
||||
* @param string $fields 返回字段
|
||||
* @param string $order 排序方式
|
||||
* @param string $limit 限制条数
|
||||
* @return array 查询结果
|
||||
*/
|
||||
public function getAll($table, $where = '', $fields = '*', $order = '', $limit = '') {
|
||||
$table = $this->table($table);
|
||||
$whereStr = $this->parseWhere($where);
|
||||
$orderStr = $order ? "ORDER BY {$order}" : '';
|
||||
$limitStr = $limit ? "LIMIT {$limit}" : '';
|
||||
|
||||
$sql = "SELECT {$fields} FROM {$table} {$whereStr} {$orderStr} {$limitStr}";
|
||||
$result = $this->query($sql);
|
||||
|
||||
$rows = [];
|
||||
if ($result) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入数据
|
||||
* @param string $table 表名(不带前缀)
|
||||
* @param array $data 数据数组
|
||||
* @return int|bool 成功返回插入ID,失败返回false
|
||||
*/
|
||||
public function insert($table, $data) {
|
||||
$table = $this->table($table);
|
||||
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$fields[] = "`{$key}`";
|
||||
if ($value === null) {
|
||||
$values[] = "NULL";
|
||||
} else {
|
||||
$values[] = "'" . $this->escape($value) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
$fieldsStr = implode(', ', $fields);
|
||||
$valuesStr = implode(', ', $values);
|
||||
|
||||
$sql = "INSERT INTO {$table} ({$fieldsStr}) VALUES ({$valuesStr})";
|
||||
$result = $this->query($sql);
|
||||
|
||||
if ($result) {
|
||||
return $this->conn->insert_id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
* @param string $table 表名(不带前缀)
|
||||
* @param array $data 数据数组
|
||||
* @param string|array $where 更新条件
|
||||
* @return bool 更新结果
|
||||
*/
|
||||
public function update($table, $data, $where) {
|
||||
$table = $this->table($table);
|
||||
$whereStr = $this->parseWhere($where);
|
||||
|
||||
$set = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if ($value === null) {
|
||||
$set[] = "`{$key}` = NULL";
|
||||
} else {
|
||||
$set[] = "`{$key}` = '" . $this->escape($value) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
$setStr = implode(', ', $set);
|
||||
|
||||
$sql = "UPDATE {$table} SET {$setStr} {$whereStr}";
|
||||
$result = $this->query($sql);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
* @param string $table 表名(不带前缀)
|
||||
* @param string|array $where 删除条件
|
||||
* @return bool 删除结果
|
||||
*/
|
||||
public function delete($table, $where) {
|
||||
$table = $this->table($table);
|
||||
$whereStr = $this->parseWhere($where);
|
||||
|
||||
if (empty($whereStr)) {
|
||||
return false; // 防止误删除全表
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM {$table} {$whereStr}";
|
||||
$result = $this->query($sql);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记录数量
|
||||
* @param string $table 表名(不带前缀)
|
||||
* @param string|array $where 查询条件
|
||||
* @return int 记录数量
|
||||
*/
|
||||
public function count($table, $where = '') {
|
||||
$table = $this->table($table);
|
||||
$whereStr = $this->parseWhere($where);
|
||||
|
||||
$sql = "SELECT COUNT(*) AS count FROM {$table} {$whereStr}";
|
||||
$result = $this->query($sql);
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
$row = $result->fetch_assoc();
|
||||
return (int) $row['count'];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始事务
|
||||
*/
|
||||
public function startTransaction() {
|
||||
$this->conn->autocommit(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交事务
|
||||
*/
|
||||
public function commit() {
|
||||
$this->conn->commit();
|
||||
$this->conn->autocommit(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚事务
|
||||
*/
|
||||
public function rollback() {
|
||||
$this->conn->rollback();
|
||||
$this->conn->autocommit(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析查询条件
|
||||
* @param string|array $where 查询条件
|
||||
* @return string 解析后的WHERE子句
|
||||
*/
|
||||
private function parseWhere($where) {
|
||||
if (empty($where)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 如果是字符串,直接返回
|
||||
if (is_string($where)) {
|
||||
return "WHERE {$where}";
|
||||
}
|
||||
|
||||
// 如果是数组,解析为查询条件
|
||||
if (is_array($where)) {
|
||||
$conditions = [];
|
||||
|
||||
foreach ($where as $key => $value) {
|
||||
if ($value === null) {
|
||||
$conditions[] = "`{$key}` IS NULL";
|
||||
} else {
|
||||
$conditions[] = "`{$key}` = '" . $this->escape($value) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
return "WHERE " . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后的错误信息
|
||||
* @return string 错误信息
|
||||
*/
|
||||
public function getError() {
|
||||
return $this->conn->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后执行的SQL影响行数
|
||||
* @return int 影响行数
|
||||
*/
|
||||
public function affectedRows() {
|
||||
return $this->conn->affected_rows;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user