上次错误
This commit is contained in:
@@ -76,35 +76,6 @@ class AdminController extends Controller
|
||||
return 'assets/uploads/' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理多文件上传(name="gallery[]"),返回相对站点根路径数组。
|
||||
* 逐张复用 uploadFile 的校验逻辑(真实图像/体积上限/类型白名单/SVG 消毒),
|
||||
* 任一文件失败不影响其余文件。
|
||||
*/
|
||||
protected function uploadFiles(string $key): array
|
||||
{
|
||||
if (empty($_FILES[$key]['tmp_name']) || !is_array($_FILES[$key]['tmp_name'])) return [];
|
||||
$names = $_FILES[$key]['name'] ?? [];
|
||||
$errors = $_FILES[$key]['error'] ?? [];
|
||||
$sizes = $_FILES[$key]['size'] ?? [];
|
||||
$out = [];
|
||||
foreach ($_FILES[$key]['tmp_name'] as $i => $tmp) {
|
||||
if (empty($tmp)) continue;
|
||||
// 桥接到单文件校验逻辑
|
||||
$_FILES['_multi_tmp'] = [
|
||||
'name' => $names[$i] ?? 'x.bin',
|
||||
'type' => '',
|
||||
'tmp_name' => $tmp,
|
||||
'error' => $errors[$i] ?? UPLOAD_ERR_OK,
|
||||
'size' => $sizes[$i] ?? 0,
|
||||
];
|
||||
$rel = $this->uploadFile('_multi_tmp');
|
||||
unset($_FILES['_multi_tmp']);
|
||||
if ($rel) $out[] = $rel;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 消毒 SVG:移除脚本、事件处理器与危险协议,阻断存储型 XSS */
|
||||
protected function sanitizeSvg(string $svg): string
|
||||
{
|
||||
|
||||
@@ -13,17 +13,17 @@ class AuthController extends Controller
|
||||
{
|
||||
if (is_admin()) { $this->redirect(login_landing()); }
|
||||
$error = '';
|
||||
$blocked = false;
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// 质量红线:登录必须校验 CSRF + 验证码 + IP 级双窗口限速(失败 10 分钟/5 次、成功 30 分钟/5 次),杜绝机器人暴力破解
|
||||
// 质量红线:登录必须校验 CSRF + 验证码 + IP/会话双重失败限速,杜绝机器人暴力破解
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
if (!csrf_check()) {
|
||||
$error = '表单已过期,请刷新页面后重试';
|
||||
} elseif (ip_login_blocked($ip)) {
|
||||
$error = '尝试次数过多,请 30 分钟后再试';
|
||||
$blocked = true;
|
||||
$error = '尝试次数过多,请 15 分钟后再试';
|
||||
} elseif (!captcha_check($this->post('captcha'))) {
|
||||
$error = '验证码错误,请重新计算';
|
||||
} elseif ($this->isBlocked()) {
|
||||
$error = '尝试次数过多,请 15 分钟后再试';
|
||||
} else {
|
||||
$u = trim($this->post('username'));
|
||||
$p = $this->post('password');
|
||||
@@ -38,7 +38,8 @@ class AuthController extends Controller
|
||||
$user = ['id' => 0, 'username' => $u, 'name' => '管理员', 'role' => 'super_admin'];
|
||||
}
|
||||
if ($ok) {
|
||||
ip_login_register_success($ip); // 记录成功登录(纳入 30 分钟 5 次上限),并重置失败计数
|
||||
$this->clearAttempts();
|
||||
ip_login_clear($ip);
|
||||
session_regenerate_id(true); // 防会话固定
|
||||
$_SESSION['admin_logged'] = true;
|
||||
$_SESSION['admin_id'] = $user['id'] ?? 0;
|
||||
@@ -51,15 +52,11 @@ class AuthController extends Controller
|
||||
$_SESSION['psi_perms'] = $dec($user['psi_perms'] ?? null);
|
||||
$this->redirect(login_landing());
|
||||
}
|
||||
ip_login_register_fail($ip); // 记录一次失败(纳入 10 分钟 5 次上限)
|
||||
$this->registerAttempt();
|
||||
ip_login_register($ip);
|
||||
$error = '用户名或密码错误';
|
||||
}
|
||||
}
|
||||
// 被限速(IP 失败/成功过多)时返回 429 + Retry-After,明确告知客户端稍后再试
|
||||
if ($blocked && !headers_sent()) {
|
||||
http_response_code(429);
|
||||
header('Retry-After: ' . ip_login_remaining($ip));
|
||||
}
|
||||
$captcha = captcha_make(); // 每次渲染都发放新的算术验证码
|
||||
return $this->view('admin/login', ['error' => $error, 'captcha' => $captcha]);
|
||||
}
|
||||
@@ -71,6 +68,25 @@ class AuthController extends Controller
|
||||
&& \Core\App::config('app.driver', 'file') !== 'mysql';
|
||||
}
|
||||
|
||||
/** 暴力破解限速:单会话 15 分钟内失败 5 次即锁定 */
|
||||
private function isBlocked(): bool
|
||||
{
|
||||
$t = $_SESSION['login_attempts'] ?? null;
|
||||
if (!$t || ($t['time'] + 900) < time()) return false;
|
||||
return $t['count'] >= 5;
|
||||
}
|
||||
private function registerAttempt(): void
|
||||
{
|
||||
$t = $_SESSION['login_attempts'] ?? ['count' => 0, 'time' => time()];
|
||||
if (($t['time'] + 900) < time()) { $t = ['count' => 0, 'time' => time()]; }
|
||||
$t['count']++;
|
||||
$_SESSION['login_attempts'] = $t;
|
||||
}
|
||||
private function clearAttempts(): void
|
||||
{
|
||||
unset($_SESSION['login_attempts']);
|
||||
}
|
||||
|
||||
/** 修改当前登录账号的密码 */
|
||||
public function password()
|
||||
{
|
||||
|
||||
@@ -19,18 +19,13 @@ class CaseController extends AdminController
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->view('admin/cases_form', [
|
||||
'c' => null,
|
||||
'mode' => 'fixed',
|
||||
]);
|
||||
return $this->view('admin/cases_form', ['c' => null]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/cases'); }
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
$cover = $this->uploadFile('cover') ?? $this->post('cover_url', '');
|
||||
$content = ($mode === 'fixed') ? $this->post('content', '') : '';
|
||||
$model = new CustomerCase();
|
||||
$id = $model->insert([
|
||||
'title' => $this->post('title'),
|
||||
@@ -39,85 +34,48 @@ class CaseController extends AdminController
|
||||
'industry' => $this->post('industry', ''),
|
||||
'cover' => $cover,
|
||||
'summary' => $this->post('summary'),
|
||||
'content' => $content,
|
||||
'content' => $this->post('content'),
|
||||
'published_at' => $this->post('published_at', date('Y-m-d')),
|
||||
'sort_order' => (int) $this->post('sort_order', 0),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'views' => 0,
|
||||
'layout' => $this->post('layout', ''),
|
||||
'mode' => $mode,
|
||||
]);
|
||||
// URL 标识留空时按记录序号顺序生成(短、稳定),避免中文标题导致过长
|
||||
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
|
||||
$model->update($id, ['slug' => $slug]);
|
||||
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版
|
||||
if ($mode === 'builder') {
|
||||
$this->redirect('admin/cases/edit/' . $id);
|
||||
}
|
||||
$this->redirect('admin/cases');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$model = new CustomerCase();
|
||||
$c = $model->find($id);
|
||||
if (!$c) { $this->redirect('admin/cases'); }
|
||||
$mode = empty($c['mode']) ? 'fixed' : $c['mode'];
|
||||
if ($mode === 'builder') {
|
||||
$layout = [];
|
||||
if (!empty($c['layout'])) {
|
||||
$dec = json_decode($c['layout'], true);
|
||||
if (is_array($dec)) $layout = $dec;
|
||||
}
|
||||
return $this->view('admin/cases_builder', ['c' => $c, 'layout' => $layout, 'mode' => $mode]);
|
||||
}
|
||||
return $this->view('admin/cases_form', ['c' => $c, 'mode' => $mode]);
|
||||
}
|
||||
|
||||
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
|
||||
public function switchMode($id)
|
||||
{
|
||||
$c = (new CustomerCase())->find($id);
|
||||
if (!$c) { $this->redirect('admin/cases'); }
|
||||
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
|
||||
(new CustomerCase())->update($id, ['mode' => $target]);
|
||||
$this->redirect('admin/cases/edit/' . $id);
|
||||
$case = (new CustomerCase())->find($id);
|
||||
if (!$case) { $this->redirect('admin/cases'); }
|
||||
return $this->view('admin/cases_form', ['c' => $case]);
|
||||
}
|
||||
|
||||
public function update($id)
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/cases'); }
|
||||
$model = new CustomerCase();
|
||||
$c = $model->find($id);
|
||||
if (!$c) { $this->redirect('admin/cases'); }
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
|
||||
$case = $model->find($id);
|
||||
if (!$case) { $this->redirect('admin/cases'); }
|
||||
$cover = $this->uploadFile('cover');
|
||||
if (!$cover && $this->post('cover_url')) $cover = $this->post('cover_url');
|
||||
if (!$cover) $cover = $c['cover'] ?? '';
|
||||
|
||||
$data = [
|
||||
if (!$cover) $cover = $case['cover'] ?? '';
|
||||
$model->update($id, [
|
||||
'title' => $this->post('title'),
|
||||
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
|
||||
'customer' => $this->post('customer', ''),
|
||||
'industry' => $this->post('industry', ''),
|
||||
'cover' => $cover,
|
||||
'summary' => $this->post('summary'),
|
||||
'content' => $this->post('content'),
|
||||
'published_at' => $this->post('published_at', date('Y-m-d')),
|
||||
'sort_order' => (int) $this->post('sort_order', 0),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'mode' => $mode,
|
||||
];
|
||||
|
||||
if ($mode === 'fixed') {
|
||||
$data['content'] = $this->post('content', '');
|
||||
} else {
|
||||
$layout = $this->post('layout', '');
|
||||
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
|
||||
$data['layout'] = $layout;
|
||||
}
|
||||
|
||||
$model->update($id, $data);
|
||||
'layout' => $this->post('layout', ''),
|
||||
]);
|
||||
$this->redirect('admin/cases');
|
||||
}
|
||||
|
||||
|
||||
@@ -15,91 +15,47 @@ class CategoryController extends AdminController
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->view('admin/category_form', [
|
||||
'c' => null,
|
||||
'mode' => 'fixed',
|
||||
]);
|
||||
return $this->view('admin/category_form', ['c' => null]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/categories'); }
|
||||
$cat = new Category();
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
$description = ($mode === 'fixed') ? $this->post('description', '') : '';
|
||||
$id = $cat->insert([
|
||||
'name' => $this->post('name'),
|
||||
'slug' => '',
|
||||
'icon' => $this->post('icon', '❄'),
|
||||
'description' => $description,
|
||||
'description' => $this->post('description'),
|
||||
'sort_order' => (int)$this->post('sort_order', 0),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'layout' => $this->post('layout', ''),
|
||||
'mode' => $mode,
|
||||
]);
|
||||
// URL 标识留空时按记录序号顺序生成(短、稳定)
|
||||
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
|
||||
$cat->update($id, ['slug' => $slug]);
|
||||
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版
|
||||
if ($mode === 'builder') {
|
||||
$this->redirect('admin/categories/edit/' . $id);
|
||||
}
|
||||
$this->redirect('admin/categories');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$cat = new Category();
|
||||
$c = $cat->find($id);
|
||||
if (!$c) { $this->redirect('admin/categories'); }
|
||||
$mode = empty($c['mode']) ? 'fixed' : $c['mode'];
|
||||
if ($mode === 'builder') {
|
||||
$layout = [];
|
||||
if (!empty($c['layout'])) {
|
||||
$dec = json_decode($c['layout'], true);
|
||||
if (is_array($dec)) $layout = $dec;
|
||||
}
|
||||
return $this->view('admin/category_builder', ['c' => $c, 'layout' => $layout, 'mode' => $mode]);
|
||||
}
|
||||
return $this->view('admin/category_form', ['c' => $c, 'mode' => $mode]);
|
||||
}
|
||||
|
||||
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
|
||||
public function switchMode($id)
|
||||
{
|
||||
$c = (new Category())->find($id);
|
||||
if (!$c) { $this->redirect('admin/categories'); }
|
||||
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
|
||||
(new Category())->update($id, ['mode' => $target]);
|
||||
$this->redirect('admin/categories/edit/' . $id);
|
||||
return $this->view('admin/category_form', ['c' => $c]);
|
||||
}
|
||||
|
||||
public function update($id)
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/categories'); }
|
||||
$cat = new Category();
|
||||
$c = $cat->find($id);
|
||||
if (!$c) { $this->redirect('admin/categories'); }
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
|
||||
$data = [
|
||||
(new Category())->update($id, [
|
||||
'name' => $this->post('name'),
|
||||
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
|
||||
'icon' => $this->post('icon', '❄'),
|
||||
'description' => $this->post('description'),
|
||||
'sort_order' => (int)$this->post('sort_order', 0),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'mode' => $mode,
|
||||
];
|
||||
|
||||
if ($mode === 'fixed') {
|
||||
$data['description'] = $this->post('description', '');
|
||||
} else {
|
||||
$layout = $this->post('layout', '');
|
||||
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
|
||||
$data['layout'] = $layout;
|
||||
}
|
||||
|
||||
$cat->update($id, $data);
|
||||
'layout' => $this->post('layout', ''),
|
||||
]);
|
||||
$this->redirect('admin/categories');
|
||||
}
|
||||
|
||||
|
||||
@@ -15,67 +15,37 @@ class NewsController extends AdminController
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->view('admin/news_form', [
|
||||
'n' => null,
|
||||
'mode' => 'fixed',
|
||||
]);
|
||||
return $this->view('admin/news_form', ['n' => null]);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/news'); }
|
||||
$news = new News();
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
$cover = $this->uploadFile('cover') ?? $this->post('cover_url', '');
|
||||
$content = ($mode === 'fixed') ? $this->post('content', '') : '';
|
||||
$news = new News();
|
||||
$id = $news->insert([
|
||||
'title' => $this->post('title'),
|
||||
'slug' => '',
|
||||
'cover' => $cover,
|
||||
'summary' => $this->post('summary'),
|
||||
'content' => $content,
|
||||
'content' => $this->post('content'),
|
||||
'author' => $this->post('author', '酷冰甲'),
|
||||
'published_at' => $this->post('published_at', date('Y-m-d')),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'views' => 0,
|
||||
'layout' => $this->post('layout', ''),
|
||||
'mode' => $mode,
|
||||
]);
|
||||
// URL 标识留空时按记录序号顺序生成(短、稳定)
|
||||
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
|
||||
$news->update($id, ['slug' => $slug]);
|
||||
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版
|
||||
if ($mode === 'builder') {
|
||||
$this->redirect('admin/news/edit/' . $id);
|
||||
}
|
||||
$this->redirect('admin/news');
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$news = new News();
|
||||
$n = $news->find($id);
|
||||
if (!$n) { $this->redirect('admin/news'); }
|
||||
$mode = empty($n['mode']) ? 'fixed' : $n['mode'];
|
||||
if ($mode === 'builder') {
|
||||
$layout = [];
|
||||
if (!empty($n['layout'])) {
|
||||
$dec = json_decode($n['layout'], true);
|
||||
if (is_array($dec)) $layout = $dec;
|
||||
}
|
||||
return $this->view('admin/news_builder', ['n' => $n, 'layout' => $layout, 'mode' => $mode]);
|
||||
}
|
||||
return $this->view('admin/news_form', ['n' => $n, 'mode' => $mode]);
|
||||
}
|
||||
|
||||
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
|
||||
public function switchMode($id)
|
||||
{
|
||||
$n = (new News())->find($id);
|
||||
if (!$n) { $this->redirect('admin/news'); }
|
||||
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
|
||||
(new News())->update($id, ['mode' => $target]);
|
||||
$this->redirect('admin/news/edit/' . $id);
|
||||
return $this->view('admin/news_form', ['n' => $n]);
|
||||
}
|
||||
|
||||
public function update($id)
|
||||
@@ -84,32 +54,20 @@ class NewsController extends AdminController
|
||||
$news = new News();
|
||||
$n = $news->find($id);
|
||||
if (!$n) { $this->redirect('admin/news'); }
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
|
||||
$cover = $this->uploadFile('cover');
|
||||
if (!$cover && $this->post('cover_url')) $cover = $this->post('cover_url');
|
||||
if (!$cover) $cover = $n['cover'] ?? '';
|
||||
|
||||
$data = [
|
||||
$news->update($id, [
|
||||
'title' => $this->post('title'),
|
||||
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
|
||||
'cover' => $cover,
|
||||
'summary' => $this->post('summary'),
|
||||
'content' => $this->post('content'),
|
||||
'author' => $this->post('author', '酷冰甲'),
|
||||
'published_at' => $this->post('published_at', date('Y-m-d')),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'mode' => $mode,
|
||||
];
|
||||
|
||||
if ($mode === 'fixed') {
|
||||
$data['content'] = $this->post('content', '');
|
||||
} else {
|
||||
$layout = $this->post('layout', '');
|
||||
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
|
||||
$data['layout'] = $layout;
|
||||
}
|
||||
|
||||
$news->update($id, $data);
|
||||
'layout' => $this->post('layout', ''),
|
||||
]);
|
||||
$this->redirect('admin/news');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,56 +13,32 @@ class PageController extends AdminController
|
||||
]);
|
||||
}
|
||||
|
||||
/** 编辑:按页面 mode 渲染对应编辑器(固定版面 / 可视化编辑) */
|
||||
public function edit($id)
|
||||
{
|
||||
$p = (new Page())->find($id);
|
||||
if (!$p) { $this->redirect('admin/pages'); }
|
||||
$mode = empty($p['mode']) ? 'fixed' : $p['mode'];
|
||||
if ($mode === 'builder') {
|
||||
$layout = [];
|
||||
if (!empty($p['layout'])) {
|
||||
$dec = json_decode($p['layout'], true);
|
||||
if (is_array($dec)) $layout = $dec;
|
||||
}
|
||||
return $this->view('admin/page_builder', ['p' => $p, 'layout' => $layout, 'mode' => $mode]);
|
||||
$layout = [];
|
||||
if (!empty($p['layout'])) {
|
||||
$dec = json_decode($p['layout'], true);
|
||||
if (is_array($dec)) $layout = $dec;
|
||||
}
|
||||
return $this->view('admin/page_form', ['p' => $p, 'mode' => $mode]);
|
||||
return $this->view('admin/page_builder', ['p' => $p, 'layout' => $layout]);
|
||||
}
|
||||
|
||||
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段 */
|
||||
public function switchMode($id)
|
||||
{
|
||||
$p = (new Page())->find($id);
|
||||
if (!$p) { $this->redirect('admin/pages'); }
|
||||
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
|
||||
(new Page())->update($id, ['mode' => $target, 'updated_at' => date('Y-m-d')]);
|
||||
$this->redirect('admin/pages/edit/' . $id);
|
||||
}
|
||||
|
||||
/** 保存:兼容两种编辑器,按实际提交的字段写入(content / layout / mode) */
|
||||
public function update($id)
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/pages'); }
|
||||
$data = [
|
||||
$layout = $this->post('layout', '');
|
||||
// 校验:非空的 layout 必须是合法 JSON 数组
|
||||
if ($layout !== '') {
|
||||
$dec = json_decode($layout, true);
|
||||
if (!is_array($dec)) $layout = '';
|
||||
}
|
||||
(new Page())->update($id, [
|
||||
'title' => $this->post('title'),
|
||||
'layout' => $layout,
|
||||
'updated_at' => date('Y-m-d'),
|
||||
];
|
||||
if ($this->post('content') !== null) {
|
||||
$data['content'] = $this->post('content');
|
||||
}
|
||||
if ($this->post('layout') !== null) {
|
||||
$layout = $this->post('layout', '');
|
||||
if ($layout !== '' && !is_array(json_decode($layout, true))) {
|
||||
$layout = '';
|
||||
}
|
||||
$data['layout'] = $layout;
|
||||
}
|
||||
$mode = $this->post('mode');
|
||||
if ($mode === 'builder' || $mode === 'fixed') {
|
||||
$data['mode'] = $mode;
|
||||
}
|
||||
(new Page())->update($id, $data);
|
||||
]);
|
||||
$this->redirect('admin/pages');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ class ProductController extends AdminController
|
||||
return $this->view('admin/product_form', [
|
||||
'p' => null,
|
||||
'cats' => (new Category())->all(),
|
||||
'mode' => 'fixed',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -42,33 +41,25 @@ class ProductController extends AdminController
|
||||
if (!csrf_check()) { $this->redirect('admin/products'); }
|
||||
$product = new Product();
|
||||
$cover = $this->uploadFile('cover') ?? $this->post('cover_url', '');
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
$gallery = ($mode === 'fixed') ? $this->uploadFiles('gallery') : [];
|
||||
$description = ($mode === 'fixed') ? $this->post('description', '') : '';
|
||||
$id = $product->insert([
|
||||
'category_id' => (int)$this->post('category_id'),
|
||||
'name' => $this->post('name'),
|
||||
'slug' => '',
|
||||
'cover' => $cover,
|
||||
'summary' => $this->post('summary'),
|
||||
'description' => $description,
|
||||
'description' => $this->post('description'),
|
||||
'price' => (float)$this->post('price', 0),
|
||||
'specs' => $this->specsToJson($this->post('specs', '')),
|
||||
'gallery' => json_encode($gallery, JSON_UNESCAPED_UNICODE),
|
||||
'gallery' => '[]',
|
||||
'tags' => $this->post('tags', ''),
|
||||
'sort_order' => (int)$this->post('sort_order', 0),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'created_at' => date('Y-m-d'),
|
||||
'layout' => $this->post('layout', ''),
|
||||
'mode' => $mode,
|
||||
]);
|
||||
// URL 标识留空时按记录序号顺序生成(短、稳定)
|
||||
$slug = $this->post('slug') ? slugify($this->post('slug')) : (string)$id;
|
||||
$product->update($id, ['slug' => $slug]);
|
||||
// 新建时若选择「可视化编辑」,保存后直接进入可视化编辑器排版,无需再手动点编辑
|
||||
if ($mode === 'builder') {
|
||||
$this->redirect('admin/products/edit/' . $id);
|
||||
}
|
||||
$this->redirect('admin/products');
|
||||
}
|
||||
|
||||
@@ -77,88 +68,39 @@ class ProductController extends AdminController
|
||||
$product = new Product();
|
||||
$p = $product->find($id);
|
||||
if (!$p) { $this->redirect('admin/products'); }
|
||||
$mode = empty($p['mode']) ? 'fixed' : $p['mode'];
|
||||
$specs = $product->specsArray($p);
|
||||
$specText = '';
|
||||
foreach ($specs as $s) $specText .= ($s['k'] ?? '') . '|' . ($s['v'] ?? '') . "\n";
|
||||
$cats = (new Category())->all();
|
||||
if ($mode === 'builder') {
|
||||
$layout = [];
|
||||
if (!empty($p['layout'])) {
|
||||
$dec = json_decode($p['layout'], true);
|
||||
if (is_array($dec)) $layout = $dec;
|
||||
}
|
||||
return $this->view('admin/product_builder', [
|
||||
'p' => $p,
|
||||
'cats' => $cats,
|
||||
'specText' => $specText,
|
||||
'layout' => $layout,
|
||||
'mode' => $mode,
|
||||
]);
|
||||
}
|
||||
return $this->view('admin/product_form', [
|
||||
'p' => $p,
|
||||
'cats' => $cats,
|
||||
'specText' => $specText,
|
||||
'mode' => $mode,
|
||||
'p' => $p,
|
||||
'cats' => (new Category())->all(),
|
||||
'specText'=> $specText,
|
||||
]);
|
||||
}
|
||||
|
||||
/** 切换编辑模式(固定版面 <-> 可视化编辑),仅更新 mode 字段,其余数据保留 */
|
||||
public function switchMode($id)
|
||||
{
|
||||
$p = (new Product())->find($id);
|
||||
if (!$p) { $this->redirect('admin/products'); }
|
||||
$target = ($this->get('mode') === 'builder') ? 'builder' : 'fixed';
|
||||
(new Product())->update($id, ['mode' => $target]);
|
||||
$this->redirect('admin/products/edit/' . $id);
|
||||
}
|
||||
|
||||
public function update($id)
|
||||
{
|
||||
if (!csrf_check()) { $this->redirect('admin/products'); }
|
||||
$product = new Product();
|
||||
$p = $product->find($id);
|
||||
if (!$p) { $this->redirect('admin/products'); }
|
||||
$mode = $this->post('mode') === 'builder' ? 'builder' : 'fixed';
|
||||
|
||||
$cover = $this->uploadFile('cover');
|
||||
if (!$cover && $this->post('cover_url')) $cover = $this->post('cover_url');
|
||||
if (!$cover) $cover = $p['cover'] ?? '';
|
||||
|
||||
$data = [
|
||||
$product->update($id, [
|
||||
'category_id' => (int)$this->post('category_id'),
|
||||
'name' => $this->post('name'),
|
||||
'slug' => $this->post('slug') ? slugify($this->post('slug')) : (string)$id,
|
||||
'cover' => $cover,
|
||||
'summary' => $this->post('summary'),
|
||||
'description' => $this->post('description'),
|
||||
'price' => (float)$this->post('price', 0),
|
||||
'specs' => $this->specsToJson($this->post('specs', '')),
|
||||
'tags' => $this->post('tags', ''),
|
||||
'sort_order' => (int)$this->post('sort_order', 0),
|
||||
'status' => $this->post('status', 1) ? 1 : 0,
|
||||
'mode' => $mode,
|
||||
];
|
||||
|
||||
if ($mode === 'fixed') {
|
||||
// 固定版面:保存图集与详细描述,保留原有 layout 不被覆盖
|
||||
$newGal = $this->uploadFiles('gallery');
|
||||
if (!empty($newGal)) {
|
||||
$data['gallery'] = json_encode($newGal, JSON_UNESCAPED_UNICODE);
|
||||
} elseif ($this->post('clear_gallery')) {
|
||||
$data['gallery'] = '[]';
|
||||
} else {
|
||||
$data['gallery'] = $p['gallery'] ?? '[]';
|
||||
}
|
||||
$data['description'] = $this->post('description', '');
|
||||
} else {
|
||||
// 可视化编辑:更新 layout,保留图集/描述原值
|
||||
$layout = $this->post('layout', '');
|
||||
if ($layout !== '' && !is_array(json_decode($layout, true))) { $layout = ''; }
|
||||
$data['layout'] = $layout;
|
||||
}
|
||||
|
||||
$product->update($id, $data);
|
||||
'layout' => $this->post('layout', ''),
|
||||
]);
|
||||
$this->redirect('admin/products');
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class SettingController extends AdminController
|
||||
];
|
||||
private $siteFields = [
|
||||
'site_name', 'site_slogan', 'contact_phone', 'contact_email',
|
||||
'contact_address', 'site_logo', 'icp', 'gongan', 'seo_title', 'seo_keywords', 'seo_description',
|
||||
'contact_address', 'icp', 'seo_title', 'seo_keywords', 'seo_description',
|
||||
];
|
||||
private $payFields = [
|
||||
'pay_enabled', 'pay_mode',
|
||||
@@ -30,18 +30,6 @@ class SettingController extends AdminController
|
||||
if (csrf_check()) {
|
||||
$pairs = [];
|
||||
foreach ($this->siteFields as $k) $pairs[$k] = $this->post($k, '');
|
||||
// 网站 Logo:优先用上传文件,其次用填写的图片路径/网址;两者皆空则保留现值
|
||||
$logoUp = $this->uploadFile('logo');
|
||||
if ($logoUp !== null) {
|
||||
$pairs['site_logo'] = $logoUp;
|
||||
} else {
|
||||
$url = trim((string) $this->post('logo_url', ''));
|
||||
if ($url !== '') {
|
||||
$pairs['site_logo'] = $url;
|
||||
} else {
|
||||
unset($pairs['site_logo']); // 不覆盖,保留数据库现有值(含默认商标)
|
||||
}
|
||||
}
|
||||
$setting->saveMany($pairs, 'site');
|
||||
}
|
||||
$this->redirect('admin/settings');
|
||||
|
||||
Reference in New Issue
Block a user