文件还在测试中

This commit is contained in:
2026-08-08 15:53:53 +08:00
parent 5f1e7adb64
commit 8101177cb5
241 changed files with 18074 additions and 22 deletions
+30
View File
@@ -0,0 +1,30 @@
<?php
$v = function ($k, $d = '') use ($b) { return $b ? ($b[$k] ?? $d) : $d; };
$isEdit = !empty($b);
?>
<div class="page-head">
<div><h1><?php echo $isEdit ? '编辑轮播' : '新增轮播'; ?></h1><div class="desc">背景可填 CSS 渐变或图片地址</div></div>
<a class="btn-ghost" href="<?php echo site_url('admin/banners'); ?>">← 返回</a>
</div>
<div class="admin-card">
<form method="post" action="<?php echo site_url($isEdit ? 'admin/banners/update/' . $b['id'] : 'admin/banners/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>标题 *</label><input name="title" value="<?php echo e($v('title')); ?>" required></div>
<div class="field"><label>副标题</label><input name="subtitle" value="<?php echo e($v('subtitle')); ?>"></div>
</div>
<div class="field"><label>链接地址(如 /products</label><input name="link" value="<?php echo e($v('link')); ?>"></div>
<div class="form-grid">
<div class="field"><label>背景图(上传,可选)</label><input type="file" name="image" accept="image/*"></div>
<div class="field"><label>或背景(图片地址 / CSS 渐变)</label><input name="image" value="<?php echo e($v('image')); ?>" placeholder="linear-gradient(135deg,#0ea5e9,#14b8a6)"></div>
</div>
<div class="field"><label>排序</label><input name="sort_order" type="number" value="<?php echo e($v('sort_order', 0)); ?>"></div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">显示</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/banners'); ?>">取消</a>
</div>
</form>
</div>
+23
View File
@@ -0,0 +1,23 @@
<div class="page-head">
<div><h1>轮播管理</h1><div class="desc">首页轮播图(背景支持渐变或图片)</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/banners/create'); ?>">+ 新增轮播</a>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>背景</th><th>标题</th><th>副标题</th><th>链接</th><th>操作</th></tr>
<?php foreach ($banners as $b): ?>
<tr>
<td><div class="thum" style="background:<?php echo e($b['image']); ?>">🖼</div></td>
<td><b><?php echo e($b['title']); ?></b></td>
<td class="muted"><?php echo e(mb_substr($b['subtitle'] ?? '', 0, 20)); ?></td>
<td class="muted"><?php echo e($b['link']); ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/banners/edit/' . $b['id']); ?>">编辑</a>
<form class="del-form" method="post" action="<?php echo site_url('admin/banners/delete/' . $b['id']); ?>"><?php echo csrf_field(); ?><button class="btn-danger btn-sm" type="submit">删除</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
+32
View File
@@ -0,0 +1,32 @@
<div class="page-head">
<div><h1>客户案例管理</h1><div class="desc">客户案例展示,可像新闻一样持续新增</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/cases/create'); ?>">+ 新增案例</a>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>封面</th><th>案例标题</th><th>模式</th><th>客户</th><th>行业</th><th>日期</th><th>状态</th><th>操作</th></tr>
<?php foreach ($cases as $c): ?>
<?php $cm = empty($c['mode']) ? 'fixed' : $c['mode']; ?>
<tr>
<td><div class="thum" style="background:<?php echo gradient($c['id']); ?>">🤝</div></td>
<td><b><?php echo e($c['title']); ?></b></td>
<td><span class="mode-badge <?php echo $cm === 'builder' ? 'builder' : 'fixed'; ?>"><?php echo $cm === 'builder' ? '可视化' : '固定'; ?></span></td>
<td class="muted"><?php echo e($c['customer'] ?? ''); ?></td>
<td class="muted"><?php echo e($c['industry'] ?? ''); ?></td>
<td class="muted"><?php echo e(format_date($c['published_at'])); ?></td>
<td><?php echo ($c['status'] ?? 1) ? '<span class="tag-mini">展示中</span>' : '<span class="muted">隐藏</span>'; ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/cases/edit/' . $c['id']); ?>">编辑</a>
<form class="del-form" method="post" action="<?php echo site_url('admin/cases/delete/' . $c['id']); ?>"><?php echo csrf_field(); ?><button class="btn-danger btn-sm" type="submit">删除</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
<style>
.mode-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600}
.mode-badge.fixed{background:#f1f5f9;color:#475569}
.mode-badge.builder{background:#ede9fe;color:#6d28d9}
</style>
+92
View File
@@ -0,0 +1,92 @@
<?php
/** @var array $c */
/** @var array $layout */
/** @var string $mode */
$v = function ($k, $d = '') use ($c) { return $c ? ($c[$k] ?? $d) : $d; };
$isEdit = !empty($c);
$switchUrl = $isEdit ? site_url('admin/cases/switchMode/' . (int)$c['id']) : '';
$initial = json_encode($layout ?: [], JSON_UNESCAPED_UNICODE);
$coverVal = $v('cover');
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑客户案例' : '新增客户案例'; ?><span class="mode-badge mode-builder">可视化编辑</span></h1>
<div class="desc">可视化编辑:拖拽文字 / 图片 / 按钮自由排版,保存后前台按此布局整页展示。</div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="pbForm" method="post" action="<?php echo site_url($isEdit ? 'admin/cases/update/' . $c['id'] : 'admin/cases/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="builder">
<input type="hidden" name="layout" id="pbLayout">
<div class="field"><label>案例标题 *</label><input name="title" value="<?php echo e($v('title')); ?>" required></div>
<div class="form-grid">
<div class="field"><label>客户名称</label><input name="customer" value="<?php echo e($v('customer')); ?>"></div>
<div class="field"><label>所属行业</label><input name="industry" value="<?php echo e($v('industry')); ?>"></div>
</div>
<div class="form-grid">
<div class="field"><label>服务时间</label><input type="date" name="published_at" value="<?php echo e($v('published_at', date('Y-m-d'))); ?>"></div>
<div class="field"><label>排序权重</label><input type="number" name="sort_order" value="<?php echo e($v('sort_order', 0)); ?>" placeholder="越大越靠前"></div>
</div>
<div class="field"><label>摘要</label><input name="summary" value="<?php echo e($v('summary')); ?>"></div>
<div class="field"><label>URL 标识(留空按序号生成)</label><input name="slug" value="<?php echo e($v('slug')); ?>" placeholder="留空则自动生成如 12"></div>
<div class="form-grid">
<div class="field"><label>封面图(上传,可选)</label><input type="file" name="cover" accept="image/*"></div>
<div class="field"><label>或填写图片地址/路径</label><input name="cover_url" value="<?php echo e($coverVal); ?>" placeholder="assets/uploads/xxx.jpg 或 http(s)://"></div>
</div>
<div class="field">
<label>可视化画布(拖拽排版案例详情)</label>
<?php echo \Core\View::buffer('admin/parts/builder', ['module' => 'case', 'layout' => $layout]); ?>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">在前台展示</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/cases'); ?>">取消</a>
</div>
</form>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
</style>
<script>
window.__PB_INIT__ = <?php echo $initial; ?>;
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
window.__PB_MODULE__ = 'case';
window.__PB_DELETE__ = '<?php echo site_url('admin/media/delete/'); ?>';
</script>
<script src="<?php echo asset('js/page-builder.js'); ?>"></script>
+167
View File
@@ -0,0 +1,167 @@
<?php
/** @var array $c */
/** @var string $mode */
$v = function ($k, $d = '') use ($c) { return $c ? ($c[$k] ?? $d) : $d; };
$isEdit = !empty($c);
$mode = $mode ?? 'fixed';
$switchUrl = $isEdit ? site_url('admin/cases/switchMode/' . (int)$c['id']) : '';
$content = $v('content');
$coverVal = $v('cover');
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑客户案例' : '新增客户案例'; ?><span class="mode-badge mode-fixed">固定版面</span></h1>
<div class="desc">固定版面:填写案例信息 / 封面,详情使用富文本编辑器排版<?php echo $isEdit ? ';可切换为可视化自由排版。' : '。'; ?></div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="fxForm" method="post" action="<?php echo site_url($isEdit ? 'admin/cases/update/' . $c['id'] : 'admin/cases/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="fixed">
<div class="field"><label>案例标题 *</label><input name="title" value="<?php echo e($v('title')); ?>" required></div>
<div class="form-grid">
<div class="field"><label>客户名称</label><input name="customer" value="<?php echo e($v('customer')); ?>"></div>
<div class="field"><label>所属行业</label><input name="industry" value="<?php echo e($v('industry')); ?>"></div>
</div>
<div class="form-grid">
<div class="field"><label>服务时间</label><input type="date" name="published_at" value="<?php echo e($v('published_at', date('Y-m-d'))); ?>"></div>
<div class="field"><label>排序权重</label><input type="number" name="sort_order" value="<?php echo e($v('sort_order', 0)); ?>" placeholder="越大越靠前"></div>
</div>
<div class="field"><label>摘要</label><input name="summary" value="<?php echo e($v('summary')); ?>"></div>
<div class="field"><label>URL 标识(留空按序号生成,短而稳定)</label><input name="slug" value="<?php echo e($v('slug')); ?>" placeholder="留空则自动生成如 12"></div>
<div class="field">
<label>封面图(上传,可选)</label>
<input type="file" name="cover" accept="image/*">
<?php if ($coverVal): ?>
<div class="img-prev"><img src="<?php echo e(site_url($coverVal)); ?>" alt=""><span class="img-prev-path"><?php echo e($coverVal); ?></span></div>
<p class="fx-hint">已上传封面;重新选择文件将替换,留空则保留。</p>
<?php endif; ?>
<input type="text" name="cover_url" value="<?php echo e($coverVal); ?>" placeholder="或填写图片地址 assets/uploads/xxx.jpg 或 http(s)://" style="margin-top:8px">
</div>
<div class="field fx-fixed-only">
<label>案例详情(富文本编辑器)</label>
<div class="fx-toolbar">
<button type="button" data-cmd="bold" title="加粗"><b>B</b></button>
<button type="button" data-cmd="italic" title="斜体"><i>I</i></button>
<button type="button" data-cmd="formatBlock" data-val="H2">H2</button>
<button type="button" data-cmd="formatBlock" data-val="H3">H3</button>
<button type="button" data-cmd="insertUnorderedList" title="无序列表">• 列表</button>
<button type="button" data-cmd="insertOrderedList" title="有序列表">1. 列表</button>
<button type="button" data-cmd="formatBlock" data-val="BLOCKQUOTE" title="引用">引用</button>
<button type="button" id="fxLink" title="插入链接">链接</button>
<button type="button" id="fxImg" title="插入图片">图片</button>
</div>
<div class="fx-editor" id="fxEditor" contenteditable="true"><?php echo $content; ?></div>
<textarea name="content" id="fxContent" hidden><?php echo e($content); ?></textarea>
<p class="fx-hint">直接排版正文即可;点击「图片」可从素材库选择或上传新图。保存后前台固定版式展示。</p>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">在前台展示</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/cases'); ?>">取消</a>
</div>
</form>
</div>
<!-- 图片选择弹层 -->
<div class="fx-modal" id="fxModal" hidden>
<div class="fx-modal-box">
<div class="fx-modal-head"><span>选择图片</span><button type="button" id="fxModalClose">×</button></div>
<div class="fx-modal-body">
<label class="fx-upload"> 上传图片<input type="file" id="fxUp" accept="image/*" hidden></label>
<div class="fx-lib" id="fxLib"></div>
</div>
</div>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
.img-prev{display:flex;align-items:center;gap:10px;margin-top:10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:8px 10px}
.img-prev img{width:72px;height:54px;object-fit:cover;border-radius:8px;display:block}
.img-prev-path{font-size:12px;color:#94a3b8;word-break:break-all}
.fx-toolbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.fx-toolbar button{width:auto;min-width:38px;height:34px;padding:0 10px;border:1px solid #e2e8f0;background:#fff;border-radius:8px;cursor:pointer;font-size:14px;color:#334155}
.fx-toolbar button:hover{border-color:#0ea5e9;color:#0ea5e9}
.fx-editor{min-height:320px;border:1px solid #e2e8f0;border-radius:10px;padding:16px 18px;font-size:16px;line-height:1.9;outline:none;background:#fff;overflow:auto}
.fx-editor:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.fx-editor h2{font-size:24px;margin:.6em 0 .4em}
.fx-editor h3{font-size:20px;margin:.6em 0 .4em}
.fx-editor blockquote{margin:.6em 0;padding:8px 14px;border-left:4px solid #0ea5e9;color:#475569;background:#f8fafc}
.fx-editor img{max-width:100%;height:auto;border-radius:8px;display:block;margin:8px 0}
.fx-editor a{color:#0ea5e9}
.fx-hint{font-size:12px;color:#94a3b8;margin:8px 2px 0}
.fx-modal{position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:80}
.fx-modal[hidden]{display:none}
.fx-modal-box{background:#fff;border-radius:14px;width:420px;max-width:92vw;overflow:hidden}
.fx-modal-head{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #e2e8f0;font-weight:700}
.fx-modal-head button{border:none;background:none;font-size:22px;cursor:pointer;color:#64748b;line-height:1}
.fx-modal-body{padding:16px;max-height:60vh;overflow:auto}
.fx-upload{display:block;text-align:center;background:#0ea5e9;color:#fff;border-radius:10px;padding:10px;cursor:pointer;font-weight:600;margin-bottom:12px}
.fx-lib{display:flex;flex-wrap:wrap;gap:8px}
.fx-lib img{width:72px;height:72px;object-fit:cover;border-radius:8px;cursor:pointer;border:2px solid transparent}
.fx-lib img:hover{border-color:#0ea5e9}
</style>
<script>
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
</script>
<script src="<?php echo asset('js/fixed-editor.js'); ?>"></script>
<script>
// 新增案例时按「排版方式」选择器联动:选可视化编辑则隐藏正文编辑器,并提示保存后进入可视化编辑器
(function(){
var sel = document.getElementById('modeSel');
if(!sel) return;
var isCreate = <?php echo $isEdit ? 'false' : 'true'; ?>;
var modeField = document.querySelector('#fxForm input[name="mode"]');
var fixedOnly = document.querySelectorAll('.fx-fixed-only');
var note = document.createElement('p');
note.className = 'fx-hint fx-builder-note';
note.style.cssText = 'margin-top:10px;color:#6d28d9;background:#f5f3ff;border:1px solid #ddd6fe;border-radius:8px;padding:8px 12px;display:none';
note.textContent = '已选择「可视化编辑」:案例详情无需填写,保存后将进入可视化编辑器排版。';
var firstFixed = fixedOnly[0];
if(firstFixed && firstFixed.parentNode){ firstFixed.parentNode.insertBefore(note, firstFixed); }
function apply(){
var m = sel.value;
if(modeField) modeField.value = m;
if(!isCreate) return;
var isBuilder = (m === 'builder');
for(var i=0;i<fixedOnly.length;i++){ fixedOnly[i].style.display = isBuilder ? 'none' : ''; }
note.style.display = isBuilder ? 'block' : 'none';
}
sel.addEventListener('change', apply);
apply();
})();
</script>
+30
View File
@@ -0,0 +1,30 @@
<div class="page-head">
<div><h1>分类管理</h1><div class="desc">产品系列分类</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/categories/create'); ?>">+ 新增分类</a>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>图标</th><th>名称</th><th>标识</th><th>模式</th><th>描述</th><th>操作</th></tr>
<?php foreach ($cats as $c): ?>
<?php $cm = empty($c['mode']) ? 'fixed' : $c['mode']; ?>
<tr>
<td style="font-size:24px"><?php echo e($c['icon']); ?></td>
<td><b><?php echo e($c['name']); ?></b></td>
<td class="muted"><?php echo e($c['slug']); ?></td>
<td><span class="mode-badge <?php echo $cm === 'builder' ? 'builder' : 'fixed'; ?>"><?php echo $cm === 'builder' ? '可视化' : '固定'; ?></span></td>
<td class="muted"><?php echo e(mb_substr($c['description'] ?? '', 0, 24)); ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/categories/edit/' . $c['id']); ?>">编辑</a>
<form class="del-form" method="post" action="<?php echo site_url('admin/categories/delete/' . $c['id']); ?>"><?php echo csrf_field(); ?><button class="btn-danger btn-sm" type="submit">删除</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
<style>
.mode-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600}
.mode-badge.fixed{background:#f1f5f9;color:#475569}
.mode-badge.builder{background:#ede9fe;color:#6d28d9}
</style>
+83
View File
@@ -0,0 +1,83 @@
<?php
/** @var array $c */
/** @var array $layout */
/** @var string $mode */
$v = function ($k, $d = '') use ($c) { return $c ? ($c[$k] ?? $d) : $d; };
$isEdit = !empty($c);
$switchUrl = $isEdit ? site_url('admin/categories/switchMode/' . (int)$c['id']) : '';
$initial = json_encode($layout ?: [], JSON_UNESCAPED_UNICODE);
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑分类' : '新增分类'; ?><span class="mode-badge mode-builder">可视化编辑</span></h1>
<div class="desc">可视化编辑:拖拽文字 / 图片 / 按钮自由排版分类内容,保存后可用于前台展示或后续扩展。</div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="pbForm" method="post" action="<?php echo site_url($isEdit ? 'admin/categories/update/' . $c['id'] : 'admin/categories/store'); ?>">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="builder">
<input type="hidden" name="layout" id="pbLayout">
<div class="form-grid">
<div class="field"><label>分类名称 *</label><input name="name" value="<?php echo e($v('name')); ?>" required></div>
<div class="field"><label>图标(emoji</label><input name="icon" value="<?php echo e($v('icon', '❄')); ?>"></div>
</div>
<div class="form-grid">
<div class="field"><label>标识(留空按序号生成)</label><input name="slug" value="<?php echo e($v('slug')); ?>"></div>
<div class="field"><label>排序</label><input name="sort_order" type="number" value="<?php echo e($v('sort_order', 0)); ?>"></div>
</div>
<div class="field">
<label>可视化画布(拖拽排版分类内容)</label>
<?php echo \Core\View::buffer('admin/parts/builder', ['module' => 'category', 'layout' => $layout]); ?>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">显示</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/categories'); ?>">取消</a>
</div>
</form>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
</style>
<script>
window.__PB_INIT__ = <?php echo $initial; ?>;
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
window.__PB_MODULE__ = 'category';
window.__PB_DELETE__ = '<?php echo site_url('admin/media/delete/'); ?>';
</script>
<script src="<?php echo asset('js/page-builder.js'); ?>"></script>
+101
View File
@@ -0,0 +1,101 @@
<?php
/** @var array $c */
/** @var string $mode */
$v = function ($k, $d = '') use ($c) { return $c ? ($c[$k] ?? $d) : $d; };
$isEdit = !empty($c);
$mode = $mode ?? 'fixed';
$switchUrl = $isEdit ? site_url('admin/categories/switchMode/' . (int)$c['id']) : '';
$desc = $v('description');
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑分类' : '新增分类'; ?><span class="mode-badge mode-fixed">固定版面</span></h1>
<div class="desc">固定版面:填写分类名称 / 图标 / 描述等基本信息<?php echo $isEdit ? ';可切换为可视化自由排版。' : '。'; ?></div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="fxForm" method="post" action="<?php echo site_url($isEdit ? 'admin/categories/update/' . $c['id'] : 'admin/categories/store'); ?>">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="fixed">
<div class="form-grid">
<div class="field"><label>分类名称 *</label><input name="name" value="<?php echo e($v('name')); ?>" required></div>
<div class="field"><label>图标(emoji</label><input name="icon" value="<?php echo e($v('icon', '❄')); ?>"></div>
</div>
<div class="form-grid">
<div class="field"><label>标识(留空按序号生成)</label><input name="slug" value="<?php echo e($v('slug')); ?>"></div>
<div class="field"><label>排序</label><input name="sort_order" type="number" value="<?php echo e($v('sort_order', 0)); ?>"></div>
</div>
<div class="field fx-fixed-only">
<label>分类描述</label>
<textarea name="description" rows="4" placeholder="分类简介,用于前台分类展示区域"><?php echo e($desc); ?></textarea>
<p class="fx-hint">纯文本描述,保存后前台分类列表展示。如需复杂排版可切换为可视化编辑。</p>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">显示</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/categories'); ?>">取消</a>
</div>
</form>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
.fx-hint{font-size:12px;color:#94a3b8;margin:8px 2px 0}
</style>
<script>
// 新增分类时按「排版方式」选择器联动:选可视化编辑则隐藏描述,并提示保存后进入可视化编辑器
(function(){
var sel = document.getElementById('modeSel');
if(!sel) return;
var isCreate = <?php echo $isEdit ? 'false' : 'true'; ?>;
var modeField = document.querySelector('#fxForm input[name="mode"]');
var fixedOnly = document.querySelectorAll('.fx-fixed-only');
var note = document.createElement('p');
note.className = 'fx-hint fx-builder-note';
note.style.cssText = 'margin-top:10px;color:#6d28d9;background:#f5f3ff;border:1px solid #ddd6fe;border-radius:8px;padding:8px 12px;display:none';
note.textContent = '已选择「可视化编辑」:描述无需填写,保存后将进入可视化编辑器排版分类内容。';
var firstFixed = fixedOnly[0];
if(firstFixed && firstFixed.parentNode){ firstFixed.parentNode.insertBefore(note, firstFixed); }
function apply(){
var m = sel.value;
if(modeField) modeField.value = m;
if(!isCreate) return;
var isBuilder = (m === 'builder');
for(var i=0;i<fixedOnly.length;i++){ fixedOnly[i].style.display = isBuilder ? 'none' : ''; }
note.style.display = isBuilder ? 'block' : 'none';
}
sel.addEventListener('change', apply);
apply();
})();
</script>
+42
View File
@@ -0,0 +1,42 @@
<div class="page-head">
<div><h1>仪表盘</h1><div class="desc">网站数据概览与快捷操作</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/products/create'); ?>"><?php echo admin_icon('plus', 18); ?> 新增产品</a>
</div>
<div class="stat-row">
<div class="stat-box c1"><span class="stat-ic"><?php echo admin_icon('snowflake', 22); ?></span><b><?php echo e($counts['products']); ?></b><span>产品数量</span></div>
<div class="stat-box c2"><span class="stat-ic"><?php echo admin_icon('folders', 22); ?></span><b><?php echo e($counts['categories']); ?></b><span>分类数量</span></div>
<div class="stat-box c3"><span class="stat-ic"><?php echo admin_icon('newspaper', 22); ?></span><b><?php echo e($counts['news']); ?></b><span>新闻数量</span></div>
<div class="stat-box c4"><span class="stat-ic"><?php echo admin_icon('images', 22); ?></span><b><?php echo e($counts['banners']); ?></b><span>轮播数量</span></div>
</div>
<div class="admin-card">
<h3 style="margin:0 0 14px;display:flex;align-items:center;gap:8px"><?php echo admin_icon('newspaper', 18); ?> 最近新闻</h3>
<?php if (empty($news)): ?><p class="muted">暂无新闻</p><?php endif; ?>
<?php if (!empty($news)): ?>
<table class="admin-table">
<tr><th>标题</th><th>发布日期</th><th>操作</th></tr>
<?php foreach ($news as $n): ?>
<tr>
<td><?php echo e($n['title']); ?></td>
<td class="muted"><?php echo e(format_date($n['published_at'])); ?></td>
<td><a class="btn-soft btn-sm" href="<?php echo site_url('admin/news/edit/' . $n['id']); ?>"><?php echo admin_icon('pencil', 14); ?> 编辑</a></td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
</div>
<div class="admin-card" style="margin-top:22px">
<h3 style="margin:0 0 16px;display:flex;align-items:center;gap:8px"><?php echo admin_icon('dashboard', 18); ?> 快捷入口</h3>
<div class="quick-actions">
<?php if (admin_role() === 'super_admin'): ?>
<a class="qa-card" href="<?php echo site_url('admin/users'); ?>"><span class="qa-ico"><?php echo admin_icon('users', 22); ?></span><span class="qa-t">用户管理</span><span class="qa-d">账号与权限分配</span></a>
<?php endif; ?>
<a class="qa-card" href="<?php echo site_url('admin/theme'); ?>"><span class="qa-ico"><?php echo admin_icon('palette', 22); ?></span><span class="qa-t">风格设置</span><span class="qa-d">主题配色与外观</span></a>
<a class="qa-card" href="<?php echo site_url('admin/settings'); ?>"><span class="qa-ico"><?php echo admin_icon('settings', 22); ?></span><span class="qa-t">站点设置</span><span class="qa-d">基础信息与 SEO</span></a>
<a class="qa-card" href="<?php echo site_url('admin/banners'); ?>"><span class="qa-ico"><?php echo admin_icon('images', 22); ?></span><span class="qa-t">轮播管理</span><span class="qa-d">首页横幅图</span></a>
<a class="qa-card" href="<?php echo site_url('admin/password'); ?>"><span class="qa-ico"><?php echo admin_icon('key', 22); ?></span><span class="qa-t">修改密码</span><span class="qa-d">账户安全</span></a>
<a class="qa-card" href="<?php echo site_url(); ?>" target="_blank"><span class="qa-ico"><?php echo admin_icon('globe', 22); ?></span><span class="qa-t">查看前台</span><span class="qa-d">在新窗口打开</span></a>
</div>
</div>
+59
View File
@@ -0,0 +1,59 @@
<?php
/** @var string $table @var array $cols @var array $names @var string $pk
* @var array $rows @var int $page @var int $totalPages @var int $total @var string $q */
?>
<div class="page-head">
<div>
<h1>表:<?php echo e($table); ?></h1>
<div class="desc">共 <b><?php echo number_format($total); ?></b> 条记录,主键:<code><?php echo e($pk); ?></code></div>
</div>
<div class="head-actions">
<a class="btn-soft" href="<?php echo site_url('admin/db'); ?>">← 所有表</a>
<a class="btn-primary" href="<?php echo site_url('admin/db/create/' . $table); ?>"><?php echo admin_icon('plus', 16); ?> 新增记录</a>
</div>
</div>
<div class="admin-card">
<form class="db-search" method="get" action="<?php echo site_url('admin/db/browse/' . $table); ?>">
<input type="text" name="q" value="<?php echo e($q); ?>" placeholder="跨字段搜索…" />
<button class="btn-soft" type="submit">搜索</button>
<?php if ($q !== ''): ?><a class="btn-link" href="<?php echo site_url('admin/db/browse/' . $table); ?>">清除</a><?php endif; ?>
</form>
<div class="tbl-wrap">
<table class="tbl tbl-data">
<thead>
<tr>
<?php foreach ($names as $c): ?><th class="mono"><?php echo e($c); ?></th><?php endforeach; ?>
<th class="num">操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r): ?>
<tr>
<?php foreach ($names as $c): ?>
<td class="cell" title="<?php echo e((string)($r[$c] ?? '')); ?>"><?php echo e(mb_substr((string)($r[$c] ?? ''), 0, 120)); ?></td>
<?php endforeach; ?>
<td class="num nowrap">
<a class="link-ok" href="<?php echo site_url('admin/db/edit/' . $table . '/' . rawurlencode($r[$pk])); ?>">编辑</a>
<a class="link-del" href="<?php echo site_url('admin/db/delete/' . $table . '/' . rawurlencode($r[$pk])); ?>"
data-confirm="确认删除该记录?此操作不可撤销。">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($rows)): ?><tr><td class="muted" colspan="<?php echo count($names) + 1; ?>">无记录</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<div class="pager">
<?php
$base = 'admin/db/browse/' . $table . '?q=' . urlencode($q) . '&page=';
if ($page > 1): ?><a class="btn-soft btn-sm" href="<?php echo site_url($base . ($page - 1)); ?>">← 上一页</a><?php endif;
echo '<span class="pager-info">第 ' . $page . ' / ' . $totalPages . ' 页</span>';
if ($page < $totalPages): ?><a class="btn-soft btn-sm" href="<?php echo site_url($base . ($page + 1)); ?>">下一页 →</a><?php endif;
?>
</div>
<?php endif; ?>
</div>
+52
View File
@@ -0,0 +1,52 @@
<?php
/** @var string $table @var array $cols @var string $pk @var array|null $row @var string $mode */
$isEdit = $mode === 'edit';
$action = $isEdit
? site_url('admin/db/update/' . $table . '/' . rawurlencode($row[$pk]))
: site_url('admin/db/store/' . $table);
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑记录' : '新增记录'; ?></h1>
<div class="desc">表:<code><?php echo e($table); ?></code> 主键:<code><?php echo e($pk); ?></code></div>
</div>
<a class="btn-soft" href="<?php echo site_url('admin/db/browse/' . $table); ?>">← 返回列表</a>
</div>
<div class="admin-card db-form-card">
<form method="post" action="<?php echo $action; ?>">
<?php foreach ($cols as $c): ?>
<?php
$f = $c['Field'];
$autoInc = ($c['Extra'] ?? '') === 'auto_increment';
if ($autoInc && !$isEdit) continue; // 自增主键在新增时由数据库生成
$val = $row !== null ? ($row[$f] ?? '') : '';
$isPk = $f === $pk;
$long = (bool)preg_match('/(text|blob|json)/i', $c['Type']);
$required = $c['Null'] === 'NO' && $c['Default'] === null && !$autoInc;
?>
<div class="form-row">
<label>
<span class="form-label"><?php echo e($f); ?>
<?php if ($isPk): ?><span class="tag-mini tag-amber">PK</span><?php endif; ?>
<?php if ($c['Null'] === 'YES'): ?><span class="tag-mini tag-gray">可空</span><?php endif; ?>
<em class="form-type"><?php echo e($c['Type']); ?></em>
</span>
<?php if ($isPk && $isEdit): ?>
<input type="hidden" name="<?php echo e($f); ?>" value="<?php echo e($val); ?>" />
<input class="input" type="text" value="<?php echo e($val); ?>" disabled />
<?php elseif ($long): ?>
<textarea class="input" name="<?php echo e($f); ?>" rows="4" <?php echo $required ? 'required' : ''; ?>><?php echo e($val); ?></textarea>
<?php else: ?>
<input class="input" type="text" name="<?php echo e($f); ?>" value="<?php echo e($val); ?>" <?php echo $required ? 'required' : ''; ?> />
<?php endif; ?>
</label>
</div>
<?php endforeach; ?>
<div class="form-actions">
<button class="btn-primary" type="submit"><?php echo $isEdit ? '保存修改' : '创建记录'; ?></button>
<a class="btn-soft" href="<?php echo site_url('admin/db/browse/' . $table); ?>">取消</a>
</div>
</form>
</div>
+49
View File
@@ -0,0 +1,49 @@
<?php /** @var array $tables @var int $total @var bool $fileMode @var array $dataFiles */ ?>
<div class="page-head">
<div>
<h1>数据库管理</h1>
<div class="desc">查看并维护数据库中所有表的数据,可进行浏览、编辑、新增与删除。</div>
</div>
<a class="btn-soft" href="<?php echo site_url('admin/upgrade'); ?>"><?php echo admin_icon('upload', 16); ?> 数据库升级</a>
</div>
<?php if (!empty($fileMode)): ?>
<div class="admin-card">
<div class="alert alert-warn">当前为 <b>文件存储模式</b>,不支持在线数据库管理。可用数据文件如下(只读):</div>
<table class="tbl">
<thead><tr><th>数据文件</th><th>大小</th></tr></thead>
<tbody>
<?php foreach (($dataFiles ?? []) as $f): ?>
<tr><td><?php echo e($f['name']); ?></td><td><?php echo number_format($f['size']); ?> B</td></tr>
<?php endforeach; ?>
<?php if (empty($dataFiles)): ?><tr><td colspan="2" class="muted">无数据文件</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="admin-card">
<div class="db-stat">共 <b><?php echo (int)($total ?? 0); ?></b> 张表</div>
<div class="tbl-wrap">
<table class="tbl">
<thead>
<tr>
<th>表名</th><th>引擎</th><th class="num">记录数</th><th class="num">大小</th><th class="num">操作</th>
</tr>
</thead>
<tbody>
<?php foreach (($tables ?? []) as $t): ?>
<tr>
<td class="mono"><?php echo e($t['name']); ?></td>
<td class="muted"><?php echo e($t['engine']); ?></td>
<td class="num"><?php echo number_format($t['rows']); ?></td>
<td class="num"><?php echo human_size($t['size']); ?></td>
<td class="num">
<a class="link-ok" href="<?php echo site_url('admin/db/browse/' . $t['name']); ?>">浏览数据</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
+25
View File
@@ -0,0 +1,25 @@
<div class="login-card">
<div class="login-brand"><span class="brand-mark"><?php echo admin_icon('snowflake', 20); ?></span> 酷冰甲 · 降温服</div>
<h1>后台登录</h1>
<p class="sub">请输入管理员账号密码</p>
<?php if ($error): ?><div class="alert alert-err"><?php echo e($error); ?></div><?php endif; ?>
<form method="post" action="<?php echo site_url('admin/login'); ?>" autocomplete="on">
<?php echo csrf_field(); ?>
<div class="field"><label>账号</label><input name="username" required autofocus autocomplete="username" maxlength="32" placeholder="请输入账号"></div>
<div class="field"><label>密码</label><input type="password" name="password" required autocomplete="current-password" maxlength="64" placeholder="请输入密码"></div>
<div class="field">
<label>验证码</label>
<div style="display:flex;align-items:center;gap:12px">
<input name="captcha" required inputmode="text" autocomplete="off" maxlength="4" placeholder="请输入验证码" style="flex:1;text-transform:uppercase">
<img id="captchaImg" src="<?php echo e($captcha['url']); ?>" alt="验证码" title="点击刷新" style="width:130px;height:44px;border:1px solid #d1d5db;border-radius:6px;cursor:pointer;vertical-align:middle">
</div>
</div>
<button class="btn-primary" style="width:100%" type="submit">登 录</button>
</form>
<script>
document.getElementById('captchaImg').addEventListener('click', function() {
this.src = '<?php echo e($captcha['url']); ?>'.replace(/_t=\w+/, '_t=' + Date.now().toString(16));
});
</script>
<p class="muted" style="margin-top:16px;font-size:13px;display:flex;align-items:center;gap:6px"><?php echo admin_icon('shield', 15); ?> 请使用分配的账号登录;忘记密码请联系超级管理员重置</p>
</div>
+31
View File
@@ -0,0 +1,31 @@
<div class="page-head">
<div><h1>新闻管理</h1><div class="desc">新闻动态与案例</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/news/create'); ?>">+ 写新闻</a>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>封面</th><th>标题</th><th>模式</th><th>作者</th><th>日期</th><th>状态</th><th>操作</th></tr>
<?php foreach ($news as $n): ?>
<?php $nm = empty($n['mode']) ? 'fixed' : $n['mode']; ?>
<tr>
<td><div class="thum" style="background:<?php echo gradient($n['id']); ?>">📰</div></td>
<td><b><?php echo e($n['title']); ?></b></td>
<td><span class="mode-badge <?php echo $nm === 'builder' ? 'builder' : 'fixed'; ?>"><?php echo $nm === 'builder' ? '可视化' : '固定'; ?></span></td>
<td class="muted"><?php echo e($n['author']); ?></td>
<td class="muted"><?php echo e(format_date($n['published_at'])); ?></td>
<td><?php echo ($n['status'] ?? 1) ? '<span class="tag-mini">已发布</span>' : '<span class="muted">草稿</span>'; ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/news/edit/' . $n['id']); ?>">编辑</a>
<form class="del-form" method="post" action="<?php echo site_url('admin/news/delete/' . $n['id']); ?>"><?php echo csrf_field(); ?><button class="btn-danger btn-sm" type="submit">删除</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
<style>
.mode-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600}
.mode-badge.fixed{background:#f1f5f9;color:#475569}
.mode-badge.builder{background:#ede9fe;color:#6d28d9}
</style>
+88
View File
@@ -0,0 +1,88 @@
<?php
/** @var array $n */
/** @var array $layout */
/** @var string $mode */
$v = function ($k, $d = '') use ($n) { return $n ? ($n[$k] ?? $d) : $d; };
$isEdit = !empty($n);
$switchUrl = $isEdit ? site_url('admin/news/switchMode/' . (int)$n['id']) : '';
$initial = json_encode($layout ?: [], JSON_UNESCAPED_UNICODE);
$coverVal = $v('cover');
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑新闻' : '写新闻'; ?><span class="mode-badge mode-builder">可视化编辑</span></h1>
<div class="desc">可视化编辑:拖拽文字 / 图片 / 按钮自由排版,保存后前台按此布局整页展示。</div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="pbForm" method="post" action="<?php echo site_url($isEdit ? 'admin/news/update/' . $n['id'] : 'admin/news/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="builder">
<input type="hidden" name="layout" id="pbLayout">
<div class="field"><label>标题 *</label><input name="title" value="<?php echo e($v('title')); ?>" required></div>
<div class="form-grid">
<div class="field"><label>作者</label><input name="author" value="<?php echo e($v('author', '酷冰甲')); ?>"></div>
<div class="field"><label>发布日期</label><input type="date" name="published_at" value="<?php echo e($v('published_at', date('Y-m-d'))); ?>"></div>
</div>
<div class="field"><label>摘要</label><input name="summary" value="<?php echo e($v('summary')); ?>"></div>
<div class="field"><label>URL 标识(留空按序号生成)</label><input name="slug" value="<?php echo e($v('slug')); ?>" placeholder="留空则自动生成如 12"></div>
<div class="form-grid">
<div class="field"><label>封面图(上传,可选)</label><input type="file" name="cover" accept="image/*"></div>
<div class="field"><label>或填写图片地址/路径</label><input name="cover_url" value="<?php echo e($coverVal); ?>" placeholder="assets/uploads/xxx.jpg 或 http(s)://"></div>
</div>
<div class="field">
<label>可视化画布(拖拽排版新闻详情)</label>
<?php echo \Core\View::buffer('admin/parts/builder', ['module' => 'news', 'layout' => $layout]); ?>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">立即发布</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/news'); ?>">取消</a>
</div>
</form>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
</style>
<script>
window.__PB_INIT__ = <?php echo $initial; ?>;
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
window.__PB_MODULE__ = 'news';
window.__PB_DELETE__ = '<?php echo site_url('admin/media/delete/'); ?>';
</script>
<script src="<?php echo asset('js/page-builder.js'); ?>"></script>
+163
View File
@@ -0,0 +1,163 @@
<?php
/** @var array $n */
/** @var string $mode */
$v = function ($k, $d = '') use ($n) { return $n ? ($n[$k] ?? $d) : $d; };
$isEdit = !empty($n);
$mode = $mode ?? 'fixed';
$switchUrl = $isEdit ? site_url('admin/news/switchMode/' . (int)$n['id']) : '';
$content = $v('content');
$coverVal = $v('cover');
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑新闻' : '写新闻'; ?><span class="mode-badge mode-fixed">固定版面</span></h1>
<div class="desc">固定版面:填写标题 / 摘要 / 封面,正文使用富文本编辑器排版<?php echo $isEdit ? ';可切换为可视化自由排版。' : '。'; ?></div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="fxForm" method="post" action="<?php echo site_url($isEdit ? 'admin/news/update/' . $n['id'] : 'admin/news/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="fixed">
<div class="field"><label>标题 *</label><input name="title" value="<?php echo e($v('title')); ?>" required></div>
<div class="form-grid">
<div class="field"><label>作者</label><input name="author" value="<?php echo e($v('author', '酷冰甲')); ?>"></div>
<div class="field"><label>发布日期</label><input type="date" name="published_at" value="<?php echo e($v('published_at', date('Y-m-d'))); ?>"></div>
</div>
<div class="field"><label>摘要</label><input name="summary" value="<?php echo e($v('summary')); ?>"></div>
<div class="field"><label>URL 标识(留空按序号生成,短而稳定)</label><input name="slug" value="<?php echo e($v('slug')); ?>" placeholder="留空则自动生成如 12"></div>
<div class="field">
<label>封面图(上传,可选)</label>
<input type="file" name="cover" accept="image/*">
<?php if ($coverVal): ?>
<div class="img-prev"><img src="<?php echo e(site_url($coverVal)); ?>" alt=""><span class="img-prev-path"><?php echo e($coverVal); ?></span></div>
<p class="fx-hint">已上传封面;重新选择文件将替换,留空则保留。</p>
<?php endif; ?>
<input type="text" name="cover_url" value="<?php echo e($coverVal); ?>" placeholder="或填写图片地址 assets/uploads/xxx.jpg 或 http(s)://" style="margin-top:8px">
</div>
<div class="field fx-fixed-only">
<label>正文内容(富文本编辑器)</label>
<div class="fx-toolbar">
<button type="button" data-cmd="bold" title="加粗"><b>B</b></button>
<button type="button" data-cmd="italic" title="斜体"><i>I</i></button>
<button type="button" data-cmd="formatBlock" data-val="H2">H2</button>
<button type="button" data-cmd="formatBlock" data-val="H3">H3</button>
<button type="button" data-cmd="insertUnorderedList" title="无序列表">• 列表</button>
<button type="button" data-cmd="insertOrderedList" title="有序列表">1. 列表</button>
<button type="button" data-cmd="formatBlock" data-val="BLOCKQUOTE" title="引用">引用</button>
<button type="button" id="fxLink" title="插入链接">链接</button>
<button type="button" id="fxImg" title="插入图片">图片</button>
</div>
<div class="fx-editor" id="fxEditor" contenteditable="true"><?php echo $content; ?></div>
<textarea name="content" id="fxContent" hidden><?php echo e($content); ?></textarea>
<p class="fx-hint">直接排版正文即可;点击「图片」可从素材库选择或上传新图。保存后前台固定版式展示。</p>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">立即发布</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/news'); ?>">取消</a>
</div>
</form>
</div>
<!-- 图片选择弹层 -->
<div class="fx-modal" id="fxModal" hidden>
<div class="fx-modal-box">
<div class="fx-modal-head"><span>选择图片</span><button type="button" id="fxModalClose">×</button></div>
<div class="fx-modal-body">
<label class="fx-upload"> 上传图片<input type="file" id="fxUp" accept="image/*" hidden></label>
<div class="fx-lib" id="fxLib"></div>
</div>
</div>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
.img-prev{display:flex;align-items:center;gap:10px;margin-top:10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:8px 10px}
.img-prev img{width:72px;height:54px;object-fit:cover;border-radius:8px;display:block}
.img-prev-path{font-size:12px;color:#94a3b8;word-break:break-all}
.fx-toolbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.fx-toolbar button{width:auto;min-width:38px;height:34px;padding:0 10px;border:1px solid #e2e8f0;background:#fff;border-radius:8px;cursor:pointer;font-size:14px;color:#334155}
.fx-toolbar button:hover{border-color:#0ea5e9;color:#0ea5e9}
.fx-editor{min-height:320px;border:1px solid #e2e8f0;border-radius:10px;padding:16px 18px;font-size:16px;line-height:1.9;outline:none;background:#fff;overflow:auto}
.fx-editor:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.fx-editor h2{font-size:24px;margin:.6em 0 .4em}
.fx-editor h3{font-size:20px;margin:.6em 0 .4em}
.fx-editor blockquote{margin:.6em 0;padding:8px 14px;border-left:4px solid #0ea5e9;color:#475569;background:#f8fafc}
.fx-editor img{max-width:100%;height:auto;border-radius:8px;display:block;margin:8px 0}
.fx-editor a{color:#0ea5e9}
.fx-hint{font-size:12px;color:#94a3b8;margin:8px 2px 0}
.fx-modal{position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:80}
.fx-modal[hidden]{display:none}
.fx-modal-box{background:#fff;border-radius:14px;width:420px;max-width:92vw;overflow:hidden}
.fx-modal-head{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #e2e8f0;font-weight:700}
.fx-modal-head button{border:none;background:none;font-size:22px;cursor:pointer;color:#64748b;line-height:1}
.fx-modal-body{padding:16px;max-height:60vh;overflow:auto}
.fx-upload{display:block;text-align:center;background:#0ea5e9;color:#fff;border-radius:10px;padding:10px;cursor:pointer;font-weight:600;margin-bottom:12px}
.fx-lib{display:flex;flex-wrap:wrap;gap:8px}
.fx-lib img{width:72px;height:72px;object-fit:cover;border-radius:8px;cursor:pointer;border:2px solid transparent}
.fx-lib img:hover{border-color:#0ea5e9}
</style>
<script>
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
</script>
<script src="<?php echo asset('js/fixed-editor.js'); ?>"></script>
<script>
// 新增新闻时按「排版方式」选择器联动:选可视化编辑则隐藏正文编辑器,并提示保存后进入可视化编辑器
(function(){
var sel = document.getElementById('modeSel');
if(!sel) return;
var isCreate = <?php echo $isEdit ? 'false' : 'true'; ?>;
var modeField = document.querySelector('#fxForm input[name="mode"]');
var fixedOnly = document.querySelectorAll('.fx-fixed-only');
var note = document.createElement('p');
note.className = 'fx-hint fx-builder-note';
note.style.cssText = 'margin-top:10px;color:#6d28d9;background:#f5f3ff;border:1px solid #ddd6fe;border-radius:8px;padding:8px 12px;display:none';
note.textContent = '已选择「可视化编辑」:正文无需填写,保存后将进入可视化编辑器排版新闻详情。';
var firstFixed = fixedOnly[0];
if(firstFixed && firstFixed.parentNode){ firstFixed.parentNode.insertBefore(note, firstFixed); }
function apply(){
var m = sel.value;
if(modeField) modeField.value = m;
if(!isCreate) return;
var isBuilder = (m === 'builder');
for(var i=0;i<fixedOnly.length;i++){ fixedOnly[i].style.display = isBuilder ? 'none' : ''; }
note.style.display = isBuilder ? 'block' : 'none';
}
sel.addEventListener('change', apply);
apply();
})();
</script>
+33
View File
@@ -0,0 +1,33 @@
<?php /** @var array $o */ /** @var array $payments */ /** @var string $seg */ ?>
<div class="page-head">
<div><h1>订单详情</h1><div class="desc">订单号 <?php echo e($o['order_no']); ?></div></div>
<a class="btn-ghost" href="<?php echo site_url('admin/orders'); ?>">← 返回列表</a>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>商品</th><td><?php echo e($o['product_name']); ?></td></tr>
<tr><th>客户</th><td><?php echo e($o['customer_name']); ?><?php echo e($o['phone']); ?><?php if (!empty($o['email'])): ?>/ <?php echo e($o['email']); ?><?php endif; ?></td></tr>
<tr><th>数量</th><td><?php echo e($o['qty']); ?> 套</td></tr>
<tr><th>金额</th><td>¥<?php echo e($o['amount']); ?></td></tr>
<tr><th>支付通道</th><td><?php echo $o['channel'] === 'wechat' ? '微信' : ($o['channel'] === 'alipay' ? '支付宝' : '—'); ?></td></tr>
<tr><th>状态</th><td><?php echo $o['status'] === 'paid' ? '已支付' : '待支付'; ?><?php if (!empty($o['paid_at'])): echo '' . e($o['paid_at']) . ''; ?><?php endif; ?></td></tr>
<tr><th>网关交易号</th><td><?php echo e($o['gateway_trade_no'] ?? '—'); ?></td></tr>
<tr><th>下单时间</th><td><?php echo e($o['created_at']); ?></td></tr>
</table>
<?php if ($o['status'] !== 'paid'): ?>
<form method="post" action="<?php echo site_url('admin/orders/markPaid/' . $o['id']); ?>" style="margin-top:16px">
<?php echo csrf_field(); ?><button class="btn-primary" type="submit" data-confirm="标记为已支付?">标记为已支付</button>
</form>
<?php endif; ?>
<?php if ($payments): ?>
<h3 style="margin:24px 0 10px">付款记录</h3>
<table class="admin-table">
<thead><tr><th>通道</th><th>交易号</th><th>金额</th><th>状态</th><th>付款时间</th></tr></thead>
<tbody>
<?php foreach ($payments as $p): ?>
<tr><td><?php echo $p['channel'] === 'wechat' ? '微信' : '支付宝'; ?></td><td><?php echo e($p['trade_no']); ?></td><td>¥<?php echo e($p['amount']); ?></td><td><?php echo e($p['status']); ?></td><td><?php echo e($p['paid_at']); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
+37
View File
@@ -0,0 +1,37 @@
<?php /** @var array $orders */ /** @var string $seg */ ?>
<div class="page-head"><div><h1>订单管理</h1><div class="desc">查看客户下单与付款状态(超级管理员 / 管理员可见)</div></div></div>
<div class="admin-card">
<table class="admin-table">
<thead><tr><th>订单号</th><th>商品</th><th>客户</th><th>数量</th><th>金额</th><th>通道</th><th>状态</th><th>下单时间</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($orders as $o): ?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo e($o['product_name']); ?></td>
<td><?php echo e($o['customer_name']); ?><br><span class="muted"><?php echo e($o['phone']); ?></span></td>
<td><?php echo e($o['qty']); ?></td>
<td>¥<?php echo e($o['amount']); ?></td>
<td><?php echo $o['channel'] === 'wechat' ? '微信' : ($o['channel'] === 'alipay' ? '支付宝' : '—'); ?></td>
<td><?php echo $o['status'] === 'paid'
? '<span class="tag-mini" style="background:#dcfce7;color:#15803d">已支付</span>'
: '<span class="tag-mini" style="background:#fef3c7;color:#b45309">待支付</span>'; ?></td>
<td class="muted"><?php echo e($o['created_at']); ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/orders/show/' . $o['id']); ?>">详情</a>
<?php if ($o['status'] !== 'paid'): ?>
<form method="post" action="<?php echo site_url('admin/orders/markPaid/' . $o['id']); ?>" style="display:inline">
<?php echo csrf_field(); ?><button class="btn-primary btn-sm" type="submit" data-confirm="标记为已支付?">标记支付</button>
</form>
<?php endif; ?>
<form method="post" action="<?php echo site_url('admin/orders/delete/' . $o['id']); ?>" style="display:inline">
<?php echo csrf_field(); ?><button class="btn-danger btn-sm" type="submit" data-confirm="确认删除该订单?">删除</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if (empty($orders)): ?><p class="muted" style="padding:20px">暂无订单</p><?php endif; ?>
</div>
+120
View File
@@ -0,0 +1,120 @@
<?php
/** @var array $p */
/** @var array $layout */
/** @var string $mode */
$v = function ($k, $d = '') use ($p) { return $p ? ($p[$k] ?? $d) : $d; };
$isEdit = !empty($p);
$switchUrl = $isEdit ? site_url('admin/pages/switchMode/' . (int)$p['id']) : '';
$initial = json_encode($layout ?: []);
?>
<div class="pb-root">
<div class="pb-topbar">
<div class="pb-top-left">
<a class="btn-ghost" href="<?php echo site_url('admin/pages'); ?>">← 返回</a>
<span class="pb-t">可视化编辑:<?php echo e($p['title']); ?></span>
<span class="mode-badge mode-builder">可视化编辑</span>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<form id="pbForm" method="post" action="<?php echo site_url('admin/pages/update/' . $p['id']); ?>" class="pb-top-form">
<?php echo csrf_field(); ?>
<input type="hidden" name="layout" id="pbLayout">
<input class="pb-title" name="title" value="<?php echo e($p['title']); ?>" placeholder="页面标题">
<button class="btn-primary" type="submit">保存页面</button>
</form>
</div>
<div class="pb-workspace">
<!-- 左侧:素材 + 添加 -->
<aside class="pb-left">
<div class="pb-box">
<h4>素材库</h4>
<label class="pb-upload"> 上传图片
<input type="file" id="pbFile" accept="image/*" hidden>
</label>
<div class="pb-lib" id="pbLib"></div>
</div>
<div class="pb-box">
<h4>添加元素</h4>
<button class="pb-add" id="pbAddText" type="button"> 文字</button>
<button class="pb-add" id="pbAddImg" type="button"> 图片(从素材库)</button>
</div>
<p class="pb-hint">· 拖动元素移动位置<br>· 拖动右下角缩放<br>· 双击文字可直接编辑<br>· 选中后在右侧改样式</p>
</aside>
<!-- 中间:画布 -->
<main class="pb-canvas-wrap">
<div class="pb-stage" id="pbStage"></div>
</main>
<!-- 右侧:属性 -->
<aside class="pb-right">
<div class="pb-box">
<h4>属性</h4>
<div id="pbPropsBody" class="pb-empty">在画布中选择一个元素以编辑属性</div>
</div>
</aside>
</div>
</div>
<style>
.pb-root{display:flex;flex-direction:column}
.pb-topbar{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:14px 18px;background:#fff;border-bottom:1px solid var(--admin-border,#e2e8f0);position:sticky;top:0;z-index:30}
.pb-top-left{display:flex;align-items:center;gap:12px}
.pb-t{font-weight:700;color:#0f172a}
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:6px 12px;font-size:13px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:6px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
.pb-top-form{display:flex;align-items:center;gap:10px}
.pb-title{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;min-width:200px}
.pb-workspace{display:flex;gap:0;min-height:72vh}
.pb-left,.pb-right{width:248px;flex:0 0 248px;background:#f8fafc;border-left:1px solid #e2e8f0;border-right:1px solid #e2e8f0;padding:14px;overflow:auto}
.pb-right{border-left:1px solid #e2e8f0}
.pb-box{margin-bottom:18px}
.pb-box h4{margin:0 0 10px;font-size:13px;color:#475569;letter-spacing:.04em;text-transform:uppercase}
.pb-upload{display:block;text-align:center;background:#0ea5e9;color:#fff;border-radius:10px;padding:10px;cursor:pointer;font-size:14px;font-weight:600;margin-bottom:10px}
.pb-upload:hover{background:#0284c7}
.pb-lib{display:flex;flex-wrap:wrap;gap:8px}
.pb-lib img{width:64px;height:64px;object-fit:cover;border-radius:8px;border:2px solid transparent;cursor:pointer}
.pb-lib img:hover{border-color:#0ea5e9}
.pb-add{display:block;width:100%;margin-bottom:8px;background:#fff;border:1px dashed #94a3b8;color:#334155;border-radius:10px;padding:10px;cursor:pointer;font-size:14px}
.pb-add:hover{border-color:#0ea5e9;color:#0ea5e9}
.pb-hint{font-size:12px;color:#94a3b8;line-height:1.8}
.pb-canvas-wrap{flex:1;overflow:auto;background:#eef2f7;background-image:radial-gradient(#dbe3ec 1px,transparent 1px);background-size:18px 18px;padding:26px;display:flex;justify-content:center;align-items:flex-start}
.pb-stage{position:relative;width:720px;min-height:480px;background:#fff;box-shadow:0 10px 40px rgba(15,23,42,.12);border-radius:6px}
.pb-el{position:absolute;box-sizing:border-box;cursor:move;user-select:none}
.pb-text{width:100%;min-height:1.4em;white-space:pre-wrap;word-break:break-word;padding:2px 4px}
.pb-img{width:100%;display:block;object-fit:cover;border-radius:4px}
.pb-text[contenteditable=true]{user-select:text;cursor:text;outline:none}
.pb-el.sel{outline:2px dashed #0ea5e9;outline-offset:2px}
.pb-handle{position:absolute;right:-7px;bottom:-7px;width:14px;height:14px;background:#0ea5e9;border:2px solid #fff;border-radius:50%;cursor:nwse-resize;display:none}
.pb-el.sel .pb-handle{display:block}
.pb-empty{font-size:13px;color:#94a3b8}
#pbPropsBody label{display:block;font-size:12px;color:#64748b;margin:10px 0 4px}
#pbPropsBody input[type=text],#pbPropsBody input[type=number],#pbPropsBody textarea,#pbPropsBody select{width:100%;border:1px solid #e2e8f0;border-radius:8px;padding:7px 9px;font-size:13px;font-family:inherit}
#pbPropsBody textarea{min-height:64px;resize:vertical}
.pb-del{margin-top:14px;width:100%;background:#fef2f2;color:#dc2626;border:1px solid #fecaca;border-radius:8px;padding:8px;cursor:pointer;font-size:13px}
.pb-del:hover{background:#fee2e2}
@media(max-width:980px){.pb-left,.pb-right{width:180px;flex-basis:180px}}
</style>
<script>
window.__PB_INIT__ = <?php echo $initial; ?>;
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
window.__PB_MODULE__ = 'page';
window.__PB_DELETE__ = '<?php echo site_url('admin/media/delete/'); ?>';
</script>
<script src="<?php echo asset('js/page-builder.js'); ?>"></script>
+103
View File
@@ -0,0 +1,103 @@
<?php
/** @var array $p */
/** @var string $mode */
$v = function ($k, $d = '') use ($p) { return $p ? ($p[$k] ?? $d) : $d; };
$isEdit = !empty($p);
$mode = $mode ?? 'fixed';
$switchUrl = $isEdit ? site_url('admin/pages/switchMode/' . (int)$p['id']) : '';
?>
<div class="page-head">
<div>
<h1>编辑单页:<?php echo e($p['title']); ?><span class="mode-badge mode-fixed">固定版面</span></h1>
<div class="desc">固定版面:页面套用统一模板(标题区 + 正文 + 联系按钮),您只需编辑正文内容。</div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form method="post" action="<?php echo site_url('admin/pages/update/' . $p['id']); ?>" id="fxForm">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="fixed">
<div class="field"><label>标题</label><input name="title" value="<?php echo e($p['title']); ?>"></div>
<div class="field">
<label>正文内容</label>
<div class="fx-toolbar">
<button type="button" data-cmd="bold" title="加粗"><b>B</b></button>
<button type="button" data-cmd="italic" title="斜体"><i>I</i></button>
<button type="button" data-cmd="formatBlock" data-val="H2">H2</button>
<button type="button" data-cmd="formatBlock" data-val="H3">H3</button>
<button type="button" data-cmd="insertUnorderedList" title="无序列表">• 列表</button>
<button type="button" data-cmd="insertOrderedList" title="有序列表">1. 列表</button>
<button type="button" data-cmd="formatBlock" data-val="BLOCKQUOTE" title="引用">引用</button>
<button type="button" id="fxLink" title="插入链接">链接</button>
<button type="button" id="fxImg" title="插入图片">图片</button>
</div>
<div class="fx-editor" id="fxEditor" contenteditable="true"><?php echo $p['content']; ?></div>
<textarea name="content" id="fxContent" hidden><?php echo e($p['content']); ?></textarea>
<p class="fx-hint">直接排版正文即可;切换「可视化编辑」可自由拖拽布局。保存后页面以统一版式展示。</p>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/pages'); ?>">取消</a>
</div>
</form>
</div>
<!-- 图片选择弹层 -->
<div class="fx-modal" id="fxModal" hidden>
<div class="fx-modal-box">
<div class="fx-modal-head"><span>选择图片</span><button type="button" id="fxModalClose">×</button></div>
<div class="fx-modal-body">
<label class="fx-upload"> 上传图片<input type="file" id="fxUp" accept="image/*" hidden></label>
<div class="fx-lib" id="fxLib"></div>
</div>
</div>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
.fx-toolbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.fx-toolbar button{width:auto;min-width:38px;height:34px;padding:0 10px;border:1px solid #e2e8f0;background:#fff;border-radius:8px;cursor:pointer;font-size:14px;color:#334155}
.fx-toolbar button:hover{border-color:#0ea5e9;color:#0ea5e9}
.fx-editor{min-height:320px;border:1px solid #e2e8f0;border-radius:10px;padding:16px 18px;font-size:16px;line-height:1.9;outline:none;background:#fff;overflow:auto}
.fx-editor:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.fx-editor h2{font-size:24px;margin:.6em 0 .4em}
.fx-editor h3{font-size:20px;margin:.6em 0 .4em}
.fx-editor blockquote{margin:.6em 0;padding:8px 14px;border-left:4px solid #0ea5e9;color:#475569;background:#f8fafc}
.fx-editor img{max-width:100%;border-radius:8px;display:block;margin:8px 0}
.fx-editor a{color:#0ea5e9}
.fx-hint{font-size:12px;color:#94a3b8;margin:8px 2px 0}
.fx-modal{position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:80}
.fx-modal[hidden]{display:none}
.fx-modal-box{background:#fff;border-radius:14px;width:420px;max-width:92vw;overflow:hidden}
.fx-modal-head{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #e2e8f0;font-weight:700}
.fx-modal-head button{border:none;background:none;font-size:22px;cursor:pointer;color:#64748b;line-height:1}
.fx-modal-body{padding:16px;max-height:60vh;overflow:auto}
.fx-upload{display:block;text-align:center;background:#0ea5e9;color:#fff;border-radius:10px;padding:10px;cursor:pointer;font-weight:600;margin-bottom:12px}
.fx-lib{display:flex;flex-wrap:wrap;gap:8px}
.fx-lib img{width:72px;height:72px;object-fit:cover;border-radius:8px;cursor:pointer;border:2px solid transparent}
.fx-lib img:hover{border-color:#0ea5e9}
</style>
<script>
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
</script>
<script src="<?php echo asset('js/fixed-editor.js'); ?>"></script>
+23
View File
@@ -0,0 +1,23 @@
<div class="page-head">
<div><h1>单页管理</h1><div class="desc">关于我们 / 服务优势 / 客户案例 / 荣誉资质</div></div>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>标识</th><th>标题</th><th>模式</th><th>更新时间</th><th>操作</th></tr>
<?php foreach ($pages as $p): ?>
<?php $m = empty($p['mode']) ? 'fixed' : $p['mode']; ?>
<tr>
<td class="muted"><?php echo e($p['slug']); ?></td>
<td><b><?php echo e($p['title']); ?></b></td>
<td><span class="mode-badge <?php echo $m === 'builder' ? 'builder' : 'fixed'; ?>"><?php echo $m === 'builder' ? '可视化编辑' : '固定版面'; ?></span></td>
<td class="muted"><?php echo e(format_date($p['updated_at'])); ?></td>
<td><a class="btn-soft btn-sm" href="<?php echo site_url('admin/pages/edit/' . $p['id']); ?>">编辑</a></td>
</tr>
<?php endforeach; ?>
</table>
<style>
.mode-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600}
.mode-badge.fixed{background:#f1f5f9;color:#475569}
.mode-badge.builder{background:#e0f2fe;color:#0369a1}
</style>
</div>
+60
View File
@@ -0,0 +1,60 @@
<?php
/** @var string $module */
/** @var array $layout */
$module = $module ?? 'page';
$initial = json_encode($layout ?: [], JSON_UNESCAPED_UNICODE);
$isProduct = $module === 'product';
?>
<div class="pb-workspace">
<!-- 最左:窄图标栏(仅图标) -->
<aside class="pb-rail" title="工具栏">
<button type="button" class="pb-rail-btn" data-act="material" title="素材库">🖼️</button>
<button type="button" class="pb-rail-btn" data-act="text" title="添加文字">T</button>
<button type="button" class="pb-rail-btn" data-act="image" title="添加图片">🏞️</button>
<button type="button" class="pb-rail-btn" data-act="button" title="添加按钮">🔗</button>
<?php if ($isProduct): ?>
<button type="button" class="pb-rail-btn" data-act="buy" title="购买按钮">🛒</button>
<button type="button" class="pb-rail-btn" data-act="price" title="价格">¥</button>
<button type="button" class="pb-rail-btn" data-act="specs" title="规格参数">▤</button>
<?php endif; ?>
</aside>
<!-- 中间:可编辑画布 -->
<main class="pb-canvas-wrap">
<div class="pb-stage" id="pbStage"></div>
</main>
<!-- 右栏:标签页 预览 / 属性 / 素材 -->
<aside class="pb-side">
<div class="pb-tabs">
<button type="button" class="pb-tab active" data-tab="preview">预览</button>
<button type="button" class="pb-tab" data-tab="props">属性</button>
<button type="button" class="pb-tab" data-tab="material">素材</button>
</div>
<div class="pb-panes">
<div class="pb-pane" id="panePreview">
<div class="pb-preview" id="pbPreviewWrap"><div class="pb-stage pb-front" id="pbPreview"></div></div>
<p class="pb-hint" style="padding:8px 12px;margin:0">实时模拟(只读)。在画布选中元素即跳到「属性」。</p>
</div>
<div class="pb-pane hidden" id="paneProps">
<div id="pbPropsBody" class="pb-empty">在画布中选择一个元素以编辑属性</div>
</div>
<div class="pb-pane hidden" id="paneMaterial">
<label class="pb-upload"> 上传图片
<input type="file" id="pbFile" accept="image/*" hidden>
</label>
<div class="pb-lib" id="pbLib"></div>
<p class="pb-hint">点击图片加入画布;悬停图片点 × 可删除素材。</p>
</div>
</div>
</aside>
</div>
<script>
window.__PB_INIT__ = <?php echo $initial; ?>;
window.__PB_MODULE__ = '<?php echo $module; ?>';
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_DELETE__ = '<?php echo site_url('admin/media/delete/'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
</script>
<script src="<?php echo asset('js/page-builder.js'); ?>"></script>
+17
View File
@@ -0,0 +1,17 @@
<?php /** @var string $error */ /** @var string $ok */ ?>
<div class="page-head">
<div><h1>修改密码</h1><div class="desc">修改当前登录账号(<?php echo e($_SESSION['admin_name'] ?? ''); ?>)的密码</div></div>
</div>
<?php if ($error): ?><div class="alert alert-err"><?php echo e($error); ?></div><?php endif; ?>
<?php if ($ok): ?><div class="alert alert-ok"><?php echo e($ok); ?></div><?php endif; ?>
<div class="admin-card" style="max-width:480px">
<form method="post" action="<?php echo site_url('admin/password'); ?>">
<?php echo csrf_field(); ?>
<div class="field"><label>当前密码</label><input type="password" name="current_password" required></div>
<div class="field"><label>新密码(至少 6 位)</label><input type="password" name="new_password" required></div>
<div class="field"><label>确认新密码</label><input type="password" name="confirm_password" required></div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存修改</button>
</div>
</form>
</div>
+34
View File
@@ -0,0 +1,34 @@
<?php /** @var array $v */ /** @var string $seg */ ?>
<div class="page-head"><div><h1>支付设置</h1><div class="desc">配置支付宝 / 微信支付通道;未配置商户号时默认「演示模式」,整条支付链路可正常走通。</div></div></div>
<div class="admin-card">
<form method="post" action="<?php echo site_url('admin/payments'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>支付开关</label>
<select name="pay_enabled">
<option value="1" <?php echo $v['pay_enabled'] === '1' ? 'selected' : ''; ?>>开启</option>
<option value="0" <?php echo $v['pay_enabled'] === '0' ? 'selected' : ''; ?>>关闭</option>
</select>
</div>
<div class="field"><label>运行模式</label>
<select name="pay_mode">
<option value="demo" <?php echo $v['pay_mode'] === 'demo' ? 'selected' : ''; ?>>演示模式(模拟支付)</option>
<option value="live" <?php echo $v['pay_mode'] === 'live' ? 'selected' : ''; ?>>真实收款(需填下方商户号)</option>
</select>
</div>
</div>
<h3 style="margin:18px 0 8px">支付宝(电脑网站支付)</h3>
<div class="field"><label>AppID</label><input name="pay_alipay_appid" value="<?php echo e($v['pay_alipay_appid']); ?>"></div>
<div class="field"><label>应用私钥 private_key</label><textarea name="pay_alipay_private_key"><?php echo e($v['pay_alipay_private_key']); ?></textarea></div>
<div class="field"><label>支付宝公钥 public_key</label><textarea name="pay_alipay_public_key"><?php echo e($v['pay_alipay_public_key']); ?></textarea></div>
<div class="field"><label>网关地址</label><input name="pay_alipay_gateway" value="<?php echo e($v['pay_alipay_gateway']); ?>"></div>
<h3 style="margin:18px 0 8px">微信支付(NATIVE 扫码)</h3>
<div class="field"><label>商户号 mch_id</label><input name="pay_wechat_mchid" value="<?php echo e($v['pay_wechat_mchid']); ?>"></div>
<div class="field"><label>AppID</label><input name="pay_wechat_appid" value="<?php echo e($v['pay_wechat_appid']); ?>"></div>
<div class="field"><label>API 密钥 key</label><input name="pay_wechat_key" value="<?php echo e($v['pay_wechat_key']); ?>"></div>
<div style="margin-top:8px"><button class="btn-primary" type="submit">保存支付设置</button></div>
</form>
</div>
+93
View File
@@ -0,0 +1,93 @@
<?php
/** @var array $p */
/** @var array $layout */
/** @var string $mode */
$v = function ($k, $d = '') use ($p) { return $p ? ($p[$k] ?? $d) : $d; };
$isEdit = !empty($p);
$switchUrl = $isEdit ? site_url('admin/products/switchMode/' . (int)$p['id']) : '';
$initial = json_encode($layout ?: [], JSON_UNESCAPED_UNICODE);
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑产品' : '新增产品'; ?><span class="mode-badge mode-builder">可视化编辑</span></h1>
<div class="desc">可视化编辑:拖拽文字 / 图片 / 按钮自由排版,可放置「购买 / 价格 / 规格」专用块(自动读取本产品数据)。</div>
</div>
<div class="mode-switch-sel">
<label for="modeSel">编辑模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
</div>
<div class="admin-card">
<form id="pbForm" method="post" action="<?php echo site_url($isEdit ? 'admin/products/update/' . $p['id'] : 'admin/products/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="builder">
<input type="hidden" name="layout" id="pbLayout">
<div class="form-grid">
<div class="field">
<label>所属分类 *</label>
<select name="category_id" required>
<?php foreach ($cats as $c): ?><option value="<?php echo e($c['id']); ?>" <?php echo $v('category_id') == $c['id'] ? 'selected' : ''; ?>><?php echo e($c['name']); ?></option><?php endforeach; ?>
</select>
</div>
<div class="field"><label>产品名称 *</label><input name="name" value="<?php echo e($v('name')); ?>" required></div>
<div class="field"><label>URL 标识(留空按序号生成)</label><input name="slug" value="<?php echo e($v('slug')); ?>" placeholder="留空则自动生成如 12"></div>
<div class="field"><label>价格(元,起)</label><input name="price" type="number" step="0.01" value="<?php echo e($v('price')); ?>"></div>
</div>
<div class="field"><label>一句话简介</label><input name="summary" value="<?php echo e($v('summary')); ?>"></div>
<div class="form-grid">
<div class="field"><label>封面图(上传,可选)</label><input type="file" name="cover" accept="image/*"></div>
<div class="field"><label>或填写图片地址/路径</label><input name="cover_url" value="<?php echo e($v('cover')); ?>" placeholder="assets/uploads/xxx.jpg 或 http(s)://"></div>
</div>
<div class="field">
<label>可视化画布(拖拽排版产品详情)</label>
<?php echo \Core\View::buffer('admin/parts/builder', ['module' => 'product', 'layout' => $layout]); ?>
</div>
<div class="field">
<label>规格参数(每行一项,格式:参数名|参数值)</label>
<textarea name="specs" placeholder="降温方式|水冷循环&#10;续航|6-8 小时"><?php echo e($specText ?? ''); ?></textarea>
</div>
<div class="form-grid">
<div class="field"><label>标签(逗号分隔)</label><input name="tags" value="<?php echo e($v('tags')); ?>"></div>
<div class="field"><label>排序(越小越靠前)</label><input name="sort_order" type="number" value="<?php echo e($v('sort_order', 0)); ?>"></div>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">立即上线</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/products'); ?>">取消</a>
</div>
</form>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
</style>
<script>
window.__PB_INIT__ = <?php echo $initial; ?>;
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
window.__PB_MODULE__ = 'product';
window.__PB_DELETE__ = '<?php echo site_url('admin/media/delete/'); ?>';
</script>
<script src="<?php echo asset('js/page-builder.js'); ?>"></script>
+199
View File
@@ -0,0 +1,199 @@
<?php
/** @var array $p */
/** @var string $mode */
$v = function ($k, $d = '') use ($p) { return $p ? ($p[$k] ?? $d) : $d; };
$isEdit = !empty($p);
$mode = $mode ?? 'fixed';
$switchUrl = $isEdit ? site_url('admin/products/switchMode/' . (int)$p['id']) : '';
$coverVal = $v('cover');
$galleryArr = [];
$g = $v('gallery');
if ($g) { $d = @json_decode($g, true); if (is_array($d)) $galleryArr = $d; }
$desc = $v('description');
?>
<div class="page-head">
<div>
<h1><?php echo $isEdit ? '编辑产品' : '新增产品'; ?><span class="mode-badge mode-fixed">固定版面</span></h1>
<div class="desc">固定版面:填写资料并上传封面 / 图集,图片自适应展示<?php echo $isEdit ? ';可切换为可视化自由排版。' : '。'; ?></div>
</div>
<?php if ($isEdit): ?>
<div class="mode-switch-sel">
<label for="modeSel">切换模式</label>
<select id="modeSel" class="mode-sel" onchange="location.href='<?php echo $switchUrl; ?>?mode='+this.value">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php else: ?>
<div class="mode-switch-sel">
<label for="modeSel">排版方式</label>
<select id="modeSel" class="mode-sel">
<option value="fixed" <?php echo $mode === 'fixed' ? 'selected' : ''; ?>>固定版面</option>
<option value="builder" <?php echo $mode === 'builder' ? 'selected' : ''; ?>>可视化编辑</option>
</select>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<form id="fxForm" method="post" action="<?php echo site_url($isEdit ? 'admin/products/update/' . $p['id'] : 'admin/products/store'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<input type="hidden" name="mode" value="fixed">
<div class="form-grid">
<div class="field">
<label>所属分类 *</label>
<select name="category_id" required>
<?php foreach ($cats as $c): ?><option value="<?php echo e($c['id']); ?>" <?php echo $v('category_id') == $c['id'] ? 'selected' : ''; ?>><?php echo e($c['name']); ?></option><?php endforeach; ?>
</select>
</div>
<div class="field"><label>产品名称 *</label><input name="name" value="<?php echo e($v('name')); ?>" required></div>
<div class="field"><label>URL 标识(留空按序号生成,短而稳定)</label><input name="slug" value="<?php echo e($v('slug')); ?>" placeholder="留空则自动生成如 12"></div>
<div class="field"><label>价格(元,起)</label><input name="price" type="number" step="0.01" value="<?php echo e($v('price')); ?>"></div>
</div>
<div class="field"><label>一句话简介</label><input name="summary" value="<?php echo e($v('summary')); ?>"></div>
<div class="field">
<label>封面图(上传,建议 4:3,将作为列表/详情主图)</label>
<input type="file" name="cover" accept="image/*">
<?php if ($coverVal): ?>
<div class="img-prev"><img src="<?php echo e(site_url($coverVal)); ?>" alt=""><span class="img-prev-path"><?php echo e($coverVal); ?></span></div>
<p class="fx-hint">已上传封面;重新选择文件将替换,留空则保留。</p>
<?php endif; ?>
<input type="text" name="cover_url" value="<?php echo e($coverVal); ?>" placeholder="或填写图片地址/路径 assets/uploads/xxx.jpg 或 http(s)://" style="margin-top:8px">
</div>
<div class="field fx-fixed-only">
<label>图集(可一次选择多张,每张都会单独上传)</label>
<input type="file" name="gallery[]" accept="image/*" multiple>
<?php if (!empty($galleryArr)): ?>
<div class="gallery-prev">
<?php foreach ($galleryArr as $gp): ?><div class="gp-item"><img src="<?php echo e(site_url($gp)); ?>" alt=""><span><?php echo e($gp); ?></span></div><?php endforeach; ?>
</div>
<label class="chk-inline"><input type="checkbox" name="clear_gallery" value="1"> 清空现有图集(重新上传)</label>
<?php endif; ?>
</div>
<div class="field fx-fixed-only">
<label>详细描述(富文本,每个图片均可上传,前台自适应展示)</label>
<div class="fx-toolbar">
<button type="button" data-cmd="bold" title="加粗"><b>B</b></button>
<button type="button" data-cmd="italic" title="斜体"><i>I</i></button>
<button type="button" data-cmd="formatBlock" data-val="H2">H2</button>
<button type="button" data-cmd="formatBlock" data-val="H3">H3</button>
<button type="button" data-cmd="insertUnorderedList" title="无序列表">• 列表</button>
<button type="button" data-cmd="insertOrderedList" title="有序列表">1. 列表</button>
<button type="button" data-cmd="formatBlock" data-val="BLOCKQUOTE" title="引用">引用</button>
<button type="button" id="fxLink" title="插入链接">链接</button>
<button type="button" id="fxImg" title="插入图片">图片</button>
</div>
<div class="fx-editor" id="fxEditor" contenteditable="true"><?php echo $desc; ?></div>
<textarea name="description" id="fxContent" hidden><?php echo e($desc); ?></textarea>
<p class="fx-hint">直接排版正文即可;点击「图片」可从素材库选择或上传新图。保存后前台固定版式展示。</p>
</div>
<div class="field">
<label>规格参数(每行一项,格式:参数名|参数值)</label>
<textarea name="specs" placeholder="降温方式|水冷循环&#10;续航|6-8 小时"><?php echo e($specText ?? ''); ?></textarea>
</div>
<div class="form-grid">
<div class="field"><label>标签(逗号分隔)</label><input name="tags" value="<?php echo e($v('tags')); ?>"></div>
<div class="field"><label>排序(越小越靠前)</label><input name="sort_order" type="number" value="<?php echo e($v('sort_order', 0)); ?>"></div>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" <?php echo $v('status', 1) ? 'checked' : ''; ?> id="st"> <label for="st" style="margin:0">立即上线</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/products'); ?>">取消</a>
</div>
</form>
</div>
<!-- 图片选择弹层 -->
<div class="fx-modal" id="fxModal" hidden>
<div class="fx-modal-box">
<div class="fx-modal-head"><span>选择图片</span><button type="button" id="fxModalClose">×</button></div>
<div class="fx-modal-body">
<label class="fx-upload"> 上传图片<input type="file" id="fxUp" accept="image/*" hidden></label>
<div class="fx-lib" id="fxLib"></div>
</div>
</div>
</div>
<style>
.mode-switch-sel{display:inline-flex;align-items:center;gap:8px}
.mode-switch-sel label{font-size:13px;color:#64748b}
.mode-sel{border:1px solid #e2e8f0;border-radius:10px;padding:8px 12px;font-size:14px;background:#fff;color:#334155;cursor:pointer;min-width:140px}
.mode-sel:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.mode-badge{display:inline-block;margin-left:10px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.7}
.mode-badge.mode-fixed{background:#e0f2fe;color:#0369a1}
.mode-badge.mode-builder{background:#ede9fe;color:#6d28d9}
.img-prev{display:flex;align-items:center;gap:10px;margin-top:10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:8px 10px}
.img-prev img{width:72px;height:54px;object-fit:cover;border-radius:8px;display:block}
.img-prev-path{font-size:12px;color:#94a3b8;word-break:break-all}
.gallery-prev{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px}
.gallery-prev .gp-item{width:104px;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden;background:#fff}
.gallery-prev .gp-item img{width:100%;height:74px;object-fit:cover;display:block}
.gallery-prev .gp-item span{display:block;font-size:11px;color:#94a3b8;padding:4px 6px;word-break:break-all;line-height:1.3}
.chk-inline{display:inline-flex;align-items:center;gap:6px;margin-top:10px;font-size:13px;color:#64748b;cursor:pointer}
.fx-toolbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.fx-toolbar button{width:auto;min-width:38px;height:34px;padding:0 10px;border:1px solid #e2e8f0;background:#fff;border-radius:8px;cursor:pointer;font-size:14px;color:#334155}
.fx-toolbar button:hover{border-color:#0ea5e9;color:#0ea5e9}
.fx-editor{min-height:260px;border:1px solid #e2e8f0;border-radius:10px;padding:16px 18px;font-size:16px;line-height:1.9;outline:none;background:#fff;overflow:auto}
.fx-editor:focus{border-color:#0ea5e9;box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.fx-editor h2{font-size:24px;margin:.6em 0 .4em}
.fx-editor h3{font-size:20px;margin:.6em 0 .4em}
.fx-editor blockquote{margin:.6em 0;padding:8px 14px;border-left:4px solid #0ea5e9;color:#475569;background:#f8fafc}
.fx-editor img{max-width:100%;height:auto;border-radius:8px;display:block;margin:8px 0}
.fx-editor a{color:#0ea5e9}
.fx-hint{font-size:12px;color:#94a3b8;margin:8px 2px 0}
.fx-modal{position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:80}
.fx-modal[hidden]{display:none}
.fx-modal-box{background:#fff;border-radius:14px;width:420px;max-width:92vw;overflow:hidden}
.fx-modal-head{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #e2e8f0;font-weight:700}
.fx-modal-head button{border:none;background:none;font-size:22px;cursor:pointer;color:#64748b;line-height:1}
.fx-modal-body{padding:16px;max-height:60vh;overflow:auto}
.fx-upload{display:block;text-align:center;background:#0ea5e9;color:#fff;border-radius:10px;padding:10px;cursor:pointer;font-weight:600;margin-bottom:12px}
.fx-lib{display:flex;flex-wrap:wrap;gap:8px}
.fx-lib img{width:72px;height:72px;object-fit:cover;border-radius:8px;cursor:pointer;border:2px solid transparent}
.fx-lib img:hover{border-color:#0ea5e9}
</style>
<script>
window.__PB_MEDIA__ = '<?php echo site_url('admin/media'); ?>';
window.__PB_UPLOAD__ = '<?php echo site_url('admin/media/upload'); ?>';
window.__PB_CSRF__ = '<?php echo csrf_token(); ?>';
</script>
<script src="<?php echo asset('js/fixed-editor.js'); ?>"></script>
<script>
// 新增产品时按「排版方式」选择器联动:选可视化编辑则隐藏图集/详细描述,并提示保存后进入可视化编辑器
(function(){
var sel = document.getElementById('modeSel');
if(!sel) return;
var isCreate = <?php echo $isEdit ? 'false' : 'true'; ?>;
var modeField = document.querySelector('#fxForm input[name="mode"]');
var fixedOnly = document.querySelectorAll('.fx-fixed-only');
var note = document.createElement('p');
note.className = 'fx-hint fx-builder-note';
note.style.cssText = 'margin-top:10px;color:#6d28d9;background:#f5f3ff;border:1px solid #ddd6fe;border-radius:8px;padding:8px 12px;display:none';
note.textContent = '已选择「可视化编辑」:图集与详细描述无需填写,保存后将进入可视化编辑器排版产品详情。';
var firstFixed = fixedOnly[0];
if(firstFixed && firstFixed.parentNode){ firstFixed.parentNode.insertBefore(note, firstFixed); }
function apply(){
var m = sel.value;
if(modeField) modeField.value = m;
if(!isCreate) return; // 编辑页选择器仅用于切换页面,不在此隐藏字段
var isBuilder = (m === 'builder');
for(var i=0;i<fixedOnly.length;i++){ fixedOnly[i].style.display = isBuilder ? 'none' : ''; }
note.style.display = isBuilder ? 'block' : 'none';
}
sel.addEventListener('change', apply);
apply();
})();
</script>
+36
View File
@@ -0,0 +1,36 @@
<?php
$cm = new \App\Models\Category();
$cmap = []; foreach ($cm->all() as $c) $cmap[$c['id']] = $c['name'];
?>
<div class="page-head">
<div><h1>产品管理</h1><div class="desc">管理降温服产品与上下架</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/products/create'); ?>">+ 新增产品</a>
</div>
<div class="admin-card">
<table class="admin-table">
<tr><th>封面</th><th>名称</th><th>分类</th><th>价格</th><th>模式</th><th>状态</th><th>操作</th></tr>
<?php foreach ($products as $p): ?>
<?php $pm = empty($p['mode']) ? 'fixed' : $p['mode']; ?>
<tr>
<td><?php if (!empty($p['cover'])): ?><div class="thum" style="background:#fff"><img src="<?php echo e(site_url($p['cover'])); ?>" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:10px"></div><?php else: ?><div class="thum" style="background:<?php echo gradient($p['id']); ?>">❄</div><?php endif; ?></td>
<td><b><?php echo e($p['name']); ?></b><br><span class="muted" style="font-size:12px"><?php echo e($p['slug']); ?></span></td>
<td><?php echo e($cmap[$p['category_id']] ?? '—'); ?></td>
<td>¥<?php echo e($p['price']); ?></td>
<td><span class="mode-badge <?php echo $pm === 'builder' ? 'builder' : 'fixed'; ?>"><?php echo $pm === 'builder' ? '可视化编辑' : '固定版面'; ?></span></td>
<td><?php echo ($p['status'] ?? 1) ? '<span class="tag-mini">已上线</span>' : '<span class="muted">草稿</span>'; ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/products/edit/' . $p['id']); ?>">编辑</a>
<form class="del-form" method="post" action="<?php echo site_url('admin/products/delete/' . $p['id']); ?>"><?php echo csrf_field(); ?><button class="btn-danger btn-sm" type="submit">删除</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
<style>
.mode-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600}
.mode-badge.fixed{background:#f1f5f9;color:#475569}
.mode-badge.builder{background:#e0f2fe;color:#0369a1}
</style>
</div>
+76
View File
@@ -0,0 +1,76 @@
<?php
/**
* 后台:SEO 设置 —— 页面清单
* 每个页面独立编辑,点击卡片进入 admin/seo/edit/{key}。
*/
$pages = $pages ?? [];
$rows = $rows ?? [];
function seo_len(string $s): int { return mb_strlen($s, 'UTF-8'); }
function seo_kwcount(string $s): int {
if ($s === '') return 0;
return count(array_filter(array_map('trim', explode(',', $s)), function ($x) { return $x !== ''; }));
}
function seo_badge(int $n, int $min, string $emptyTxt): string {
if ($n === 0) return '<span class="badge badge-none">' . $emptyTxt . '</span>';
$cls = $n >= $min ? 'badge-ok' : 'badge-warn';
return '<span class="badge ' . $cls . '">' . $n . ($min > 0 ? '(≥' . $min . '' : '') . '</span>';
}
?>
<div class="page-head">
<div>
<h1>SEO 设置</h1>
<p class="muted">逐页维护搜索结果展示信息。点击任一页面进入独立编辑页,带实时字数提示。</p>
</div>
<div class="page-head-actions">
<a class="btn btn-ghost" href="<?= e(site_url('admin/seo/edit/home')) ?>">编辑首页 SEO</a>
<a class="btn btn-ghost" href="<?= e(site_url('sitemap.xml')) ?>" target="_blank" rel="noopener">查看 sitemap.xml</a>
</div>
</div>
<?= flash_html() ?>
<div class="seo-grid">
<?php foreach ($pages as $key => $meta):
$v = $rows[$key] ?? [];
$title = $v['title'] ?? '';
$desc = $v['description'] ?? '';
$tLen = seo_len($title);
$dLen = seo_len($desc);
$kwN = seo_kwcount($v['keywords'] ?? '');
$noindex = !empty($v['noindex']);
?>
<a class="seo-card" href="<?= e(site_url('admin/seo/edit/' . $key)) ?>">
<div class="seo-card-head">
<span class="seo-card-title"><?= e($meta['label']) ?></span>
<code class="mono seo-card-key"><?= e($key) ?></code>
</div>
<div class="seo-card-badges">
<?= seo_badge($tLen, $meta['title_min'], '标题未设置') ?>
<?= seo_badge($dLen, $meta['desc_min'], '描述未设置') ?>
<?= seo_badge($kwN, $meta['kw_min'], '无关键词') ?>
<?php if ($noindex): ?><span class="badge badge-noidx">noindex</span><?php endif; ?>
</div>
<div class="seo-card-preview"><?= e($title ?: '(未设置标题)') ?></div>
<div class="seo-card-desc"><?= e($desc ?: '(未设置描述)') ?></div>
<div class="seo-card-go">编辑此页 </div>
</a>
<?php endforeach; ?>
</div>
<style>
.seo-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
@media (max-width: 820px) { .seo-grid { grid-template-columns: 1fr; } }
.seo-card { display: block; background: #fff; border: 1px solid #e6eaf0; border-radius: 14px; padding: 16px 18px; text-decoration: none; color: inherit; transition: border-color .15s, box-shadow .2s, transform .12s; }
.seo-card:hover { border-color: #7dd3fc; box-shadow: 0 12px 28px -18px rgba(14,165,233,.7); transform: translateY(-2px); }
.seo-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.seo-card-title { font-size: 16px; font-weight: 800; }
.seo-card-key { background: #f1f5f9; color: #64748b; padding: 2px 8px; border-radius: 7px; font-size: 12px; }
.seo-card-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.seo-card-preview { font-size: 14px; font-weight: 700; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.seo-card-desc { font-size: 13px; color: #64748b; margin-top: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.seo-card-go { margin-top: 12px; font-size: 13px; font-weight: 700; color: #0ea5e9; }
.badge-ok { background: #dcfce7; color: #15803d; }
.badge-warn { background: #fef3c7; color: #b45309; }
.badge-none { background: #f1f5f9; color: #94a3b8; }
.badge-noidx { background: #fee2e2; color: #dc2626; }
</style>
+128
View File
@@ -0,0 +1,128 @@
<?php
/**
* 后台:SEO 单页编辑(admin/seo/edit/{key}
* 仅编辑一个页面的 标题/描述/关键词/Open Graph/规范链接/收录开关,带实时字数提示。
*/
$key = $key ?? '';
$meta = $meta ?? [];
$v = $v ?? [];
$ogTypes = ['website' => 'website(普通页)', 'article' => 'article(文章/资讯)', 'product' => 'product(商品)'];
$idp = preg_replace('/[^a-z0-9]/', '', $key);
?>
<div class="page-head">
<div>
<h1>SEO 设置 · <?= e($meta['label'] ?? $key) ?></h1>
<p class="muted">
页面标识 <code class="mono"><?= e($key) ?></code>
<?php if (!empty($meta['note'])): ?>· <b style="color:#b45309"><?= e($meta['note']) ?></b><?php endif; ?>
</p>
</div>
<div class="page-head-actions">
<a class="btn btn-ghost" href="<?= e(site_url('admin/seo')) ?>"> 返回列表</a>
</div>
</div>
<?= flash_html() ?>
<form method="POST" action="<?= e(site_url('admin/seo/edit/' . $key)) ?>" class="seo-edit-form">
<?= csrf_field() ?>
<div class="card">
<div class="card-head"><h3>搜索结果展示(SERP</h3></div>
<div class="form-grid">
<div class="form-group" style="grid-column:1 / -1;">
<label class="label" for="t_<?= $idp ?>">页面标题(Title</label>
<input class="form-control" id="t_<?= $idp ?>" name="title"
value="<?= e($v['title'] ?? '') ?>" data-count data-min="<?= $meta['title_min'] ?? 0 ?>">
<div class="field-hint">
建议 35–60 字符(含品牌名更好);当前 <span class="counter" id="c_t_<?= $idp ?>"></span>
</div>
</div>
<div class="form-group" style="grid-column:1 / -1;">
<label class="label" for="d_<?= $idp ?>">页面描述(Description</label>
<textarea class="form-control" id="d_<?= $idp ?>" name="description" rows="3"
data-count data-min="<?= $meta['desc_min'] ?? 0 ?>"><?= e($v['description'] ?? '') ?></textarea>
<div class="field-hint">
建议 120160 字符;当前 <span class="counter" id="c_d_<?= $idp ?>"></span>
</div>
</div>
<div class="form-group" style="grid-column:1 / -1;">
<label class="label" for="k_<?= $idp ?>">关键词(Keywords,英文逗号分隔)</label>
<input class="form-control" id="k_<?= $idp ?>" name="keywords"
value="<?= e($v['keywords'] ?? '') ?>" data-kw data-min="<?= $meta['kw_min'] ?? 0 ?>">
<div class="field-hint">
建议 <?= $meta['kw_min'] ?? 0 ?>+ 个;当前 <span class="counter" id="c_k_<?= $idp ?>"></span>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-head"><h3>社交分享(Open Graph / Twitter</h3></div>
<div class="form-grid">
<div class="form-group">
<label class="label" for="ot_<?= $idp ?>">分享标题(OG Title</label>
<input class="form-control" id="ot_<?= $idp ?>" name="og_title"
value="<?= e($v['og_title'] ?? '') ?>" placeholder="留空则用页面标题">
</div>
<div class="form-group">
<label class="label" for="oty_<?= $idp ?>">分享类型(OG Type</label>
<select class="form-control" id="oty_<?= $idp ?>" name="og_type">
<?php foreach ($ogTypes as $tv => $tl): ?>
<option value="<?= e($tv) ?>" <?= ($v['og_type'] ?? 'website') === $tv ? 'selected' : '' ?>><?= e($tl) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="grid-column:1 / -1;">
<label class="label" for="od_<?= $idp ?>">分享描述(OG Description</label>
<textarea class="form-control" id="od_<?= $idp ?>" name="og_description" rows="2"
placeholder="留空则用页面描述"><?= e($v['og_description'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label class="label" for="oi_<?= $idp ?>">分享图(OG Image</label>
<input class="form-control" id="oi_<?= $idp ?>" name="og_image"
value="<?= e($v['og_image'] ?? '') ?>" placeholder="留空则用站点默认分享图">
</div>
<div class="form-group">
<label class="label" for="ca_<?= $idp ?>">规范链接(Canonical</label>
<input class="form-control" id="ca_<?= $idp ?>" name="canonical"
value="<?= e($v['canonical'] ?? '') ?>" placeholder="留空则自动取当前 URL">
</div>
<div class="form-group" style="grid-column:1 / -1;">
<label class="checkbox">
<input type="checkbox" name="noindex" value="1" <?= !empty($v['noindex']) ? 'checked' : '' ?>>
<span>禁止搜索引擎收录此页面(输出 noindex 标签)</span>
</label>
</div>
</div>
</div>
<div class="form-actions">
<button class="btn btn-primary" type="submit">保存「<?= e($meta['label'] ?? $key) ?>」SEO 设置</button>
<a class="btn btn-ghost" href="<?= e(site_url('admin/seo')) ?>">返回列表</a>
</div>
</form>
<script>
document.querySelectorAll('[data-count],[data-kw]').forEach(function (el) {
var min = parseInt(el.dataset.min || '0', 10);
var out = document.getElementById('c_' + el.id);
if (!out) return;
function upd() {
var val = el.value;
var n, unit;
if (el.dataset.kw !== undefined) {
var arr = val.split(',').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
n = arr.length; unit = '个关键词';
} else {
n = Array.from(val).length; unit = '字符';
}
out.textContent = n + ' ' + unit + (min > 0 ? '(建议≥' + min + '' : '');
out.style.color = n === 0 ? '#dc2626' : (n >= min ? '#16a34a' : '#d97706');
}
el.addEventListener('input', upd);
upd();
});
</script>
+30
View File
@@ -0,0 +1,30 @@
<div class="page-head">
<div><h1>站点设置</h1><div class="desc">网站名称、联系方式与 SEO 信息</div></div>
</div>
<div class="admin-card">
<form method="post" action="<?php echo site_url('admin/settings'); ?>" enctype="multipart/form-data">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>网站名称</label><input name="site_name" value="<?php echo e($v['site_name']); ?>"></div>
<div class="field"><label>网站标语</label><input name="site_slogan" value="<?php echo e($v['site_slogan']); ?>"></div>
<div class="field"><label>联系电话</label><input name="contact_phone" value="<?php echo e($v['contact_phone']); ?>"></div>
<div class="field"><label>联系邮箱</label><input name="contact_email" value="<?php echo e($v['contact_email']); ?>"></div>
</div>
<div class="field"><label>联系地址</label><input name="contact_address" value="<?php echo e($v['contact_address']); ?>"></div>
<div class="field" style="grid-column:1/-1">
<label>网站 Logo(酷冰甲商标)</label>
<input type="file" name="logo" accept="image/*">
<small style="color:#888;display:block;margin-top:4px">选择图片上传将替换当前 Logo;也可在下方直接填写图片路径或网址(留空则保留现有)。当前:<?php echo e($v['site_logo'] ?: '默认商标'); ?></small>
<input type="text" name="logo_url" value="<?php echo e($v['site_logo']); ?>" placeholder="图片路径或网址,如 assets/img/logo.png 或 https://...">
</div>
<div class="field"><label>备案号(ICP</label><input name="icp" value="<?php echo e($v['icp']); ?>" placeholder="如 苏ICP备XXXXXXXX号"></div>
<div class="field"><label>公安备案号(网安备)</label><input name="gongan" value="<?php echo e($v['gongan'] ?? ''); ?>" placeholder="如 京公网安备11010802012345号"></div>
<hr style="border:none;border-top:1px solid #eef2f7;margin:18px 0">
<div class="field"><label>SEO 标题</label><input name="seo_title" value="<?php echo e($v['seo_title']); ?>"></div>
<div class="field"><label>SEO 关键词</label><input name="seo_keywords" value="<?php echo e($v['seo_keywords']); ?>"></div>
<div class="field"><label>SEO 描述</label><textarea name="seo_description"><?php echo e($v['seo_description']); ?></textarea></div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存设置</button>
</div>
</form>
</div>
+48
View File
@@ -0,0 +1,48 @@
<?php /** @var string $seg */
$phpVer = phpversion();
$sapi = php_sapi_name();
$dbDriver = \Core\Db::driver();
$dbVer = $dbDriver === 'mysql' ? \Core\Db::pdo()->query("SELECT VERSION()")->fetchColumn() : '—';
$tz = date_default_timezone_get();
$storageWritable = is_writable(BASE_PATH . '/storage');
$uploadWritable = is_writable(BASE_PATH . '/public/uploads');
$installed = \Core\Installer::isInstalled();
$info = [
'PHP 版本' => $phpVer,
'运行模式' => $sapi,
'数据库驱动' => $dbDriver === 'mysql' ? 'MySQL' : $dbDriver,
'数据库版本' => $dbVer,
'系统时区' => $tz,
'站点已安装' => $installed ? '是' : '否',
'根目录' => BASE_PATH,
'存储目录可写' => $storageWritable ? '✓ 可写' : '✗ 不可写',
'上传目录可写' => $uploadWritable ? '✓ 可写' : '✗ 不可写',
];
?>
<div class="page-head">
<div><h1>系统设置</h1><div class="desc">查看运行环境与数据库维护入口。</div></div>
</div>
<div class="admin-card">
<h3 class="card-title">运行环境</h3>
<table class="tbl tbl-info">
<tbody>
<?php foreach ($info as $k => $v): ?>
<tr><th><?php echo e($k); ?></th><td class="mono"><?php echo e($v); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="shortcut-grid">
<a class="shortcut-card" href="<?php echo site_url('admin/db'); ?>">
<span class="shortcut-ic"><?php echo admin_icon('database', 26); ?></span>
<span class="shortcut-title">数据库管理</span>
<span class="shortcut-desc">查看并编辑所有表的数据</span>
</a>
<a class="shortcut-card" href="<?php echo site_url('admin/upgrade'); ?>">
<span class="shortcut-ic"><?php echo admin_icon('upload', 26); ?></span>
<span class="shortcut-title">数据库升级</span>
<span class="shortcut-desc">执行升级包并留痕记录</span>
</a>
</div>
+90
View File
@@ -0,0 +1,90 @@
<div class="page-head">
<div><h1>🎨 风格设置</h1><div class="desc">一键设置任意网页风格:配色 / 字体 / 圆角 / 导航 / 明暗 / 自定义 CSS,保存后立即生效</div></div>
</div>
<div class="admin-card">
<form method="post" action="<?php echo site_url('admin/theme'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field">
<label>预设风格(选择后自动填充下方配色)</label>
<select id="preset" name="preset">
<?php foreach ($presets as $key => $p): ?>
<option value="<?php echo e($key); ?>" <?php echo $v['preset'] == $key ? 'selected' : ''; ?>><?php echo e($p['label']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field">
<label>导航栏样式</label>
<select name="header">
<option value="center" <?php echo $v['header'] == 'center' ? 'selected' : ''; ?>>居中Logo</option>
<option value="left" <?php echo $v['header'] == 'left' ? 'selected' : ''; ?>>左对齐</option>
<option value="transparent" <?php echo $v['header'] == 'transparent' ? 'selected' : ''; ?>>透明浮层</option>
</select>
</div>
</div>
<h3 style="margin:8px 0 12px">品牌配色</h3>
<div class="form-grid">
<div class="field"><label>主色 Primary</label><input type="color" id="f_primary" name="primary" value="<?php echo e($v['primary']); ?>"></div>
<div class="field"><label>主色深 Primary-600</label><input type="color" id="f_primary_600" name="primary_600" value="<?php echo e($v['primary_600']); ?>"></div>
<div class="field"><label>辅色 Secondary</label><input type="color" id="f_secondary" name="secondary" value="<?php echo e($v['secondary']); ?>"></div>
<div class="field"><label>强调色 Accent</label><input type="color" id="f_accent" name="accent" value="<?php echo e($v['accent']); ?>"></div>
</div>
<h3 style="margin:8px 0 12px">界面底色(浅色模式)</h3>
<div class="form-grid">
<div class="field"><label>背景 Background</label><input type="color" id="f_bg" name="bg" value="<?php echo e($v['bg']); ?>"></div>
<div class="field"><label>卡片 Surface</label><input type="color" id="f_surface" name="surface" value="<?php echo e($v['surface']); ?>"></div>
<div class="field"><label>正文文字 Text</label><input type="color" id="f_text" name="text" value="<?php echo e($v['text']); ?>"></div>
<div class="field"><label>次要文字 Muted</label><input type="color" id="f_muted" name="muted" value="<?php echo e($v['muted']); ?>"></div>
<div class="field"><label>边框 Border</label><input type="color" id="f_border" name="border" value="<?php echo e($v['border']); ?>"></div>
<div class="field"><label>导航背景(RGBA</label><input id="f_nav_bg" name="nav_bg" value="<?php echo e($v['nav_bg']); ?>"></div>
</div>
<h3 style="margin:8px 0 12px">排版与布局</h3>
<div class="form-grid">
<div class="field">
<label>字体</label>
<select id="f_font" name="font">
<option value="'Noto Sans SC', system-ui, sans-serif" <?php echo strpos($v['font'], 'Noto Sans SC') !== false ? 'selected' : ''; ?>>思源黑体(默认)</option>
<option value="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" <?php echo strpos($v['font'], 'Segoe UI') !== false ? 'selected' : ''; ?>>系统无衬线</option>
<option value="'Microsoft YaHei', system-ui, sans-serif" <?php echo strpos($v['font'], 'YaHei') !== false ? 'selected' : ''; ?>>微软雅黑</option>
<option value="Georgia, 'Times New Roman', serif" <?php echo strpos($v['font'], 'Georgia') !== false ? 'selected' : ''; ?>>衬线 Serif</option>
</select>
</div>
<div class="field"><label>圆角(px</label><input type="number" id="f_radius" name="radius" value="<?php echo e($v['radius']); ?>"></div>
<div class="field"><label>容器宽度(px</label><input type="number" id="f_container" name="container" value="<?php echo e($v['container']); ?>"></div>
<div class="field">
<label>默认明暗</label>
<select id="f_default_mode" name="default_mode">
<option value="light" <?php echo $v['default_mode'] == 'light' ? 'selected' : ''; ?>>浅色</option>
<option value="dark" <?php echo $v['default_mode'] == 'dark' ? 'selected' : ''; ?>>深色</option>
</select>
</div>
</div>
<h3 style="margin:8px 0 12px">自定义 CSS(高级)</h3>
<div class="field">
<label>将追加到主题样式末尾,可覆盖任意样式</label>
<textarea name="custom_css" style="font-family:monospace;min-height:120px" placeholder=".site-header{box-shadow:none;}"><?php echo e($v['custom_css']); ?></textarea>
</div>
<div class="preview-box" id="themePreview">
<div class="pv-title" style="font-size:20px;font-weight:800"></div>
<div class="pv-sub" style="opacity:.9;margin-top:6px"></div>
<div style="margin-top:16px;display:flex;gap:10px">
<span style="background:#fff;color:#0ea5e9;padding:8px 16px;border-radius:10px;font-weight:700">主按钮</span>
<span style="background:rgba(255,255,255,.25);padding:8px 16px;border-radius:10px;font-weight:700">次按钮</span>
</div>
</div>
<div class="form-actions" style="margin-top:18px">
<button class="btn-primary" type="submit">保存并应用风格</button>
<a class="btn-ghost" href="<?php echo site_url(); ?>" target="_blank">查看前台效果</a>
</div>
</form>
</div>
<script>window.__presets__ = <?php echo json_encode($presets, JSON_UNESCAPED_UNICODE); ?>;</script>
+105
View File
@@ -0,0 +1,105 @@
<?php /** @var string $driver @var array $list @var int $pending @var array $history */ ?>
<div class="page-head">
<div>
<h1>数据库升级</h1>
<div class="desc">将升级包(<code>*.sql</code>)放入 <code>install/upgrades/</code> 目录后,系统自动提示可升级;每次升级均留痕记录。</div>
</div>
<a class="btn-soft" href="<?php echo site_url('admin/db'); ?>"><?php echo admin_icon('database', 16); ?> 数据库管理</a>
</div>
<?php if (($driver ?? '') === 'file'): ?>
<div class="admin-card">
<div class="alert alert-warn">当前为文件存储模式,不支持 SQL 升级。请切换到 MySQL 后使用本功能。</div>
</div>
<?php else: ?>
<?php if ($pending > 0): ?>
<div class="alert alert-upgrade">
<div class="alert-upgrade-main">
<span class="alert-upgrade-ic"><?php echo admin_icon('upload', 22); ?></span>
<div>
<b>发现 <?php echo (int)$pending; ?> 个可升级数据库脚本</b>
<div class="muted">执行后将在「升级记录」中留痕,可随时追溯。</div>
</div>
</div>
<a class="btn-primary" href="<?php echo site_url('admin/upgrade/applyall'); ?>"
data-confirm="确认执行全部待升级脚本?建议升级前先备份数据库。">⬆️ 立即全部升级</a>
</div>
<?php else: ?>
<div class="alert alert-ok">✓ 数据库已是最新,没有待升级的脚本。</div>
<?php endif; ?>
<div class="admin-card">
<h3 class="card-title">升级包(install/upgrades/</h3>
<?php if (empty($list)): ?>
<div class="muted" style="padding:12px 0">暂无升级包。将 <code>*.sql</code> 文件放入 <code>install/upgrades/</code> 目录即可在此处升级。</div>
<?php else: ?>
<div class="tbl-wrap">
<table class="tbl">
<thead>
<tr><th>文件名</th><th class="num">大小</th><th>修改时间</th><th>状态</th><th class="num">操作</th></tr>
</thead>
<tbody>
<?php foreach ($list as $it): ?>
<tr>
<td class="mono"><?php echo e($it['name']); ?></td>
<td class="num"><?php echo number_format($it['size']); ?> B</td>
<td class="muted"><?php echo date('Y-m-d H:i', $it['mtime']); ?></td>
<td>
<?php if ($it['state'] === 'done'): ?><span class="tag-mini tag-green">已应用</span>
<?php elseif ($it['state'] === 'changed'): ?><span class="tag-mini tag-amber">内容已变更</span>
<?php else: ?><span class="tag-mini tag-blue">待升级</span><?php endif; ?>
</td>
<td class="num">
<?php if ($it['state'] !== 'done'): ?>
<a class="link-ok" href="<?php echo site_url('admin/upgrade/apply/' . rawurlencode($it['name'])); ?>"
data-confirm="确认执行该升级包?建议先备份数据库。">升级</a>
<?php else: ?><span class="muted">—</span><?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<h3 class="card-title">升级记录(db_upgrades</h3>
<?php if (empty($history)): ?>
<div class="muted" style="padding:12px 0">暂无升级记录。</div>
<?php else: ?>
<div class="tbl-wrap">
<table class="tbl">
<thead>
<tr><th>文件名</th><th>操作人</th><th>执行时间</th><th>文件指纹(MD5)</th></tr>
</thead>
<tbody>
<?php foreach ($history as $h): ?>
<tr>
<td class="mono"><?php echo e($h['file']); ?></td>
<td><?php echo e($h['applied_by'] ?? ''); ?></td>
<td class="muted"><?php echo e($h['applied_at'] ?? ''); ?></td>
<td class="mono muted" title="<?php echo e($h['hash']); ?>"><?php echo e(substr($h['hash'], 0, 12)); ?>…</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<div class="admin-card">
<h3 class="card-title">基础初始化工具</h3>
<p class="muted" style="line-height:1.8">
重新执行基础表结构与种子数据补齐(<code>install/schema.sql</code> / <code>install/seed.php</code>)。
该操作会补齐缺失的新模块表 / 列,并保留已有订单与管理员账号,但不会覆盖客户已编辑的内容数据。
</p>
<form method="post" action="<?php echo site_url('admin/upgrade/init'); ?>">
<?php echo csrf_field(); ?>
<button class="btn-soft" type="submit"
data-confirm="确认执行基础数据升级?">⬆️ 执行基础数据升级</button>
</form>
</div>
<?php endif; ?>
+88
View File
@@ -0,0 +1,88 @@
<?php /** @var array|null $user */ /** @var string $error */ /** @var string $ok */ /** @var array $crmPerms */ /** @var array $psiPerms */ ?>
<div class="page-head">
<div><h1><?php echo $user ? '编辑用户' : '新增用户'; ?></h1></div>
<a class="btn-ghost" href="<?php echo site_url('admin/users'); ?>">← 返回</a>
</div>
<?php if ($error): ?><div class="alert alert-err"><?php echo e($error); ?></div><?php endif; ?>
<?php if ($ok): ?><div class="alert alert-ok"><?php echo $ok; ?></div><?php endif; ?>
<?php
$showCrm = $user ? (($user['crm_role'] ?? 'none') !== 'none') : true;
$showPsi = $user ? (($user['psi_role'] ?? 'none') !== 'none') : true;
$crmPages = ['customers' => '客户管理', 'leads' => '商机线索', 'followups' => '跟进记录'];
$psiPages = ['materials' => '物料管理', 'products' => '成品管理', 'suppliers' => '供应商', 'purchases' => '采购入库', 'sales' => '销售出库', 'stock' => '库存流水'];
?>
<div class="admin-card" style="max-width:640px">
<form method="post" action="<?php echo site_url($user ? 'admin/users/update/' . $user['id'] : 'admin/users/store'); ?>">
<?php echo csrf_field(); ?>
<div class="field">
<label>登录账号</label>
<input name="username" value="<?php echo e($user['username'] ?? ''); ?>" <?php echo $user ? 'readonly' : 'required'; ?>>
<?php if ($user): ?><small class="muted">账号创建后不可修改</small><?php endif; ?>
</div>
<div class="field">
<label>姓名 / 昵称</label>
<input name="name" value="<?php echo e($user['name'] ?? ''); ?>">
</div>
<div class="field">
<label>主角色(后台)</label>
<select name="role">
<option value="super_admin" <?php echo ($user['role'] ?? '') === 'super_admin' ? 'selected' : ''; ?>>超级管理员(全部权限 + 用户管理)</option>
<option value="admin" <?php echo ($user['role'] ?? '') === 'admin' ? 'selected' : ''; ?>>管理员(内容管理)</option>
<option value="user" <?php echo ($user['role'] ?? '') === 'user' ? 'selected' : ''; ?>>用户(受限后台权限)</option>
<option value="none" <?php echo ($user['role'] ?? '') === 'none' ? 'selected' : ''; ?>>无(无后台权限,仅作为 CRM / PSI 子系统账号)</option>
</select>
<small class="muted">「无」用于纯子系统账号:该账号只通过下方 CRM / PSI 角色进入对应系统,不显示任何后台模块,角色分配更清晰。</small>
</div>
<div class="field">
<label>CRM 系统角色</label>
<select name="crm_role">
<option value="none" <?php echo ($user['crm_role'] ?? 'none') === 'none' ? 'selected' : ''; ?>>无(不可进入 CRM</option>
<option value="user" <?php echo ($user['crm_role'] ?? 'none') === 'user' ? 'selected' : ''; ?>>用户(仅查看 CRM</option>
<option value="admin" <?php echo ($user['crm_role'] ?? 'none') === 'admin' ? 'selected' : ''; ?>>管理员(可增删改 CRM</option>
</select>
</div>
<div class="field">
<label>PSI 系统角色</label>
<select name="psi_role">
<option value="none" <?php echo ($user['psi_role'] ?? 'none') === 'none' ? 'selected' : ''; ?>>无(不可进入 PSI</option>
<option value="user" <?php echo ($user['psi_role'] ?? 'none') === 'user' ? 'selected' : ''; ?>>用户(仅查看 PSI</option>
<option value="admin" <?php echo ($user['psi_role'] ?? 'none') === 'admin' ? 'selected' : ''; ?>>管理员(可增删改 PSI</option>
</select>
</div>
<?php if ($showCrm): ?>
<div class="field">
<label>CRM 页面权限(勾选可进入的页面,仪表盘始终可见)</label>
<div class="chk-grid">
<?php foreach ($crmPages as $k => $l): ?>
<label class="chk"><input type="checkbox" name="crm_pages[]" value="<?php echo $k; ?>" <?php echo !empty($crmPerms[$k]) ? 'checked' : ''; ?>> <?php echo $l; ?></label>
<?php endforeach; ?>
</div>
<small class="muted">未勾选的页面将在侧边栏隐藏,且直接访问会被拦截。</small>
</div>
<?php endif; ?>
<?php if ($showPsi): ?>
<div class="field">
<label>PSI 页面权限(勾选可进入的页面,仪表盘始终可见)</label>
<div class="chk-grid">
<?php foreach ($psiPages as $k => $l): ?>
<label class="chk"><input type="checkbox" name="psi_pages[]" value="<?php echo $k; ?>" <?php echo !empty($psiPerms[$k]) ? 'checked' : ''; ?>> <?php echo $l; ?></label>
<?php endforeach; ?>
</div>
<small class="muted">未勾选的页面将在侧边栏隐藏,且直接访问会被拦截。</small>
</div>
<?php endif; ?>
<div class="field">
<label><?php echo $user ? '重置密码(留空则不修改)' : '登录密码'; ?></label>
<input type="password" name="password" <?php echo $user ? '' : 'required'; ?>>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" name="status" value="1" id="st" <?php echo ($user['status'] ?? 1) ? 'checked' : ''; ?>>
<label for="st" style="margin:0">启用该账号</label>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">保存</button>
<a class="btn-ghost" href="<?php echo site_url('admin/users'); ?>">取消</a>
</div>
</form>
</div>
+31
View File
@@ -0,0 +1,31 @@
<?php /** @var array $users */ /** @var string $error */ /** @var string $ok */ ?>
<div class="page-head">
<div><h1>用户管理</h1><div class="desc">管理后台账号与权限:超级管理员 / 管理员 / 用户 / 无(纯子系统账号),支持多人协同编辑</div></div>
<a class="btn-primary" href="<?php echo site_url('admin/users/create'); ?>">+ 新增用户</a>
</div>
<?php if ($ok): ?><div class="alert alert-ok"><?php echo $ok; ?></div><?php endif; ?>
<?php if ($error): ?><div class="alert alert-err"><?php echo e($error); ?></div><?php endif; ?>
<div class="admin-card">
<table class="admin-table">
<tr><th>账号</th><th>姓名</th><th>角色</th><th>状态</th><th>操作</th></tr>
<?php foreach ($users as $u): ?>
<tr>
<td><?php echo e($u['username']); ?></td>
<td><?php echo e($u['name'] ?? ''); ?></td>
<td><span class="role-badge role-<?php echo e($u['role'] ?? 'super_admin'); ?>"><?php echo e(admin_role_label($u['role'] ?? 'super_admin')); ?></span></td>
<td><?php echo (($u['status'] ?? 1) ? '启用' : '停用'); ?></td>
<td>
<div class="row-actions">
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/users/edit/' . $u['id']); ?>">编辑</a>
<a class="btn-ghost btn-sm" href="<?php echo site_url('admin/users/reset/' . $u['id']); ?>" data-confirm="确定重置该用户密码?">重置密码</a>
<?php if (($u['id'] ?? 0) != admin_uid()): ?>
<a class="btn-danger btn-sm" href="<?php echo site_url('admin/users/destroy/' . $u['id']); ?>" data-confirm="确定删除该用户?">删除</a>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
+23
View File
@@ -0,0 +1,23 @@
<section class="page-hero">
<div class="container">
<h1>客户案例</h1>
<p>从需求沟通到成衣交付,为各行业提供定制化降温解决方案</p>
</div>
</section>
<section class="section" style="padding-top:20px">
<div class="container">
<div class="news-grid">
<?php foreach ($cases as $c): ?>
<a class="news-card reveal" href="<?php echo site_url('cases/' . $c['slug']); ?>">
<div class="news-thumb" style="background:<?php echo gradient($c['id']); ?>">🤝</div>
<div class="news-body">
<div class="news-date"><?php echo e(format_date($c['published_at'])); ?> · <?php echo e($c['industry'] ?? ''); ?></div>
<div class="news-title"><?php echo e($c['title']); ?></div>
<div class="news-sum"><?php echo e($c['summary']); ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php if (empty($cases)): ?><p style="color:var(--c-muted)">暂无案例。</p><?php endif; ?>
</div>
</section>
+36
View File
@@ -0,0 +1,36 @@
<?php
$c = $c ?? null;
$layoutArr = [];
if (!empty($c['layout'])) { $d = json_decode($c['layout'], true); if (is_array($d)) $layoutArr = $d; }
// 模式渲染:可视化编辑(builder)且有 layout → 画布;否则固定版式(正文富文本)
$useBuilder = !empty($layoutArr) && ($c['mode'] ?? '') !== 'fixed';
?>
<section class="page-hero">
<div class="container">
<h1><?php echo e($c['title']); ?></h1>
<p><?php echo e(format_date($c['published_at'])); ?> · <?php echo e($c['customer'] ?? ''); ?> · <?php echo e($c['industry'] ?? ''); ?></p>
</div>
</section>
<div class="container">
<nav class="breadcrumb"><a href="<?php echo site_url(); ?>">首页</a> / <a href="<?php echo site_url('cases'); ?>">客户案例</a> / <?php echo e($c['title']); ?></nav>
</div>
<?php if ($useBuilder): ?>
<?php echo \Core\View::buffer('parts/canvas', ['layout' => $layoutArr, 'item' => $c, 'module' => 'case']); ?>
<?php else: ?>
<section class="section" style="padding-top:10px">
<div class="container" style="max-width:780px">
<article class="detail-desc detail-rich" style="font-size:16px"><?php echo $c['content']; ?></article>
</div>
</section>
<?php endif; ?>
<section class="section" style="padding-top:0">
<div class="container" style="max-width:780px">
<div style="display:flex;justify-content:space-between;margin-top:40px;gap:12px;flex-wrap:wrap">
<?php if ($prev): ?><a class="btn btn-ghost" href="<?php echo site_url('cases/' . $prev['slug']); ?>">← <?php echo e(mb_substr($prev['title'],0,14)); ?></a><?php else: ?><span></span><?php endif; ?>
<?php if ($next): ?><a class="btn btn-ghost" href="<?php echo site_url('cases/' . $next['slug']); ?>"><?php echo e(mb_substr($next['title'],0,14)); ?> →</a><?php else: ?><span></span><?php endif; ?>
</div>
<div style="text-align:center;margin-top:30px"><a class="more-link" href="<?php echo site_url('cases'); ?>">返回案例列表</a></div>
</div>
</section>
+60
View File
@@ -0,0 +1,60 @@
<?php
use Core\Theme;
$site = Theme::all();
?>
<section class="page-hero">
<div class="container">
<h1>联系我们</h1>
<p>提交需求,专属顾问 1 对 1 为您设计降温解决方案</p>
</div>
</section>
<section class="section" style="padding-top:28px">
<div class="container">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:start">
<div>
<h3 style="font-size:20px;margin-bottom:16px">联系方式</h3>
<p class="footer-line" style="font-size:16px">📞 电话:<a href="tel:<?php echo e($site['contact_phone']); ?>"><?php echo e($site['contact_phone']); ?></a></p>
<p class="footer-line" style="font-size:16px">✉️ 邮箱:<?php echo e($site['contact_email']); ?></p>
<p class="footer-line" style="font-size:16px">📍 地址:<?php echo e($site['contact_address']); ?></p>
<div class="form-note" style="margin-top:20px">我们支持企业 LOGO 绣字、一人一码量体、柔性化小单定制,10 套起订。</div>
</div>
<div class="form-card">
<?php if ($sent): ?><div class="alert alert-ok">提交成功!我们会尽快与您联系。</div><?php endif; ?>
<?php if ($error): ?><div class="alert alert-err"><?php echo e($error); ?></div><?php endif; ?>
<form method="post" action="<?php echo site_url('contact'); ?>" autocomplete="on">
<?php echo csrf_field(); ?>
<!-- 蜜罐:对真人隐藏,机器人填了即被静默丢弃 -->
<div class="hp-field" aria-hidden="true" style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden">
<label>请勿填写此栏</label>
<input type="text" name="website" tabindex="-1" autocomplete="off">
</div>
<div class="field">
<label>姓名 / 企业 *</label>
<input name="name" required maxlength="40" autocomplete="name" value="<?php echo e($_POST['name'] ?? ''); ?>">
</div>
<div class="field">
<label>联系电话 *</label>
<input name="phone" required maxlength="20" inputmode="tel" pattern="[0-9+\-\s]{5,20}" autocomplete="tel" value="<?php echo e($_POST['phone'] ?? ''); ?>">
</div>
<div class="field">
<label>需求描述 *</label>
<textarea name="message" required maxlength="1000" placeholder="请简述使用场景、人数、期望降温方式等"><?php echo e($_POST['message'] ?? ''); ?></textarea>
</div>
<div class="field">
<label>验证码</label>
<div style="display:flex;align-items:center;gap:12px">
<input name="captcha" required inputmode="text" autocomplete="off" maxlength="4" placeholder="请输入验证码" style="flex:1;text-transform:uppercase">
<img id="captchaImg" src="<?php echo e($captcha['url']); ?>" alt="验证码" title="点击刷新" style="width:130px;height:44px;border:1px solid #d1d5db;border-radius:6px;cursor:pointer;vertical-align:middle">
</div>
</div>
<button class="btn btn-primary magnetic" type="submit" style="width:100%">提交需求</button>
</form>
<script>
document.getElementById('captchaImg').addEventListener('click', function() {
this.src = '<?php echo e($captcha['url']); ?>'.replace(/_t=\w+/, '_t=' + Date.now().toString(16));
});
</script>
</div>
</div>
</div>
</section>
+37
View File
@@ -0,0 +1,37 @@
<?php
/** CRM 客户联系人表单(新增/编辑共用) */
$ct = $contact;
$isEdit = !empty($ct);
$customers = $customers ?? [];
$preId = $preId ?? 0;
$curCustomer = $ct['customer_id'] ?? $preId;
?>
<div class="page-head">
<h1><?php echo $isEdit ? '编辑联系人' : '新增联系人'; ?></h1>
</div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'CRM/contacts/update/' . $ct['id'] : 'CRM/contacts/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>所属客户 *</label>
<select name="customer_id" required>
<option value="">— 请选择客户 —</option>
<?php foreach ($customers as $cu): ?>
<option value="<?php echo $cu['id']; ?>" <?php echo (int)($cu['id']) === (int)$curCustomer ? 'selected' : ''; ?>><?php echo e($cu['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>姓名 *</label><input name="name" value="<?php echo e($ct['name'] ?? ''); ?>" required></div>
<div class="field"><label>职位</label><input name="title" value="<?php echo e($ct['title'] ?? ''); ?>" placeholder="如:采购总监"></div>
<div class="field"><label>电话</label><input name="phone" value="<?php echo e($ct['phone'] ?? ''); ?>"></div>
<div class="field"><label>邮箱</label><input name="email" value="<?php echo e($ct['email'] ?? ''); ?>"></div>
<div class="field"><label>微信</label><input name="wechat" value="<?php echo e($ct['wechat'] ?? ''); ?>"></div>
<div class="field field-check">
<label><input type="checkbox" name="is_primary" value="1" <?php echo !empty($ct['is_primary']) ? 'checked' : ''; ?>> 设为主联系人</label>
</div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="3"><?php echo e($ct['remark'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url($curCustomer ? 'CRM/contacts?customer_id=' . $curCustomer : 'CRM/contacts'); ?>">取消</a>
</form>
</div>
+42
View File
@@ -0,0 +1,42 @@
<?php
/** CRM 客户联系人列表 */
$customer = $customer ?? null;
$title = $customer ? ('『' . e($customer['name']) . '』的联系人') : '全部客户联系人';
$addUrl = $customer ? site_url('CRM/contacts/create?customer_id=' . $customer['id']) : site_url('CRM/contacts/create');
?>
<div class="page-head">
<h1><?php echo e($title); ?></h1>
<a class="btn-primary" href="<?php echo $addUrl; ?>">+ 新增联系人</a>
</div>
<?php if ($customer): ?>
<p class="sub"><a href="<?php echo site_url('CRM/customers'); ?>">← 返回客户列表</a></p>
<?php endif; ?>
<div class="panel">
<table class="tbl">
<thead><tr>
<th>ID</th><?php if (!$customer): ?><th>客户</th><?php endif; ?>
<th>姓名</th><th>职位</th><th>电话</th><th>邮箱</th><th>微信</th><th>主联系人</th><th>操作</th>
</tr></thead>
<tbody>
<?php foreach ($contacts as $ct): ?>
<tr>
<td><?php echo e($ct['id']); ?></td>
<?php if (!$customer): ?><td><?php echo e($ct['customer_name'] ?? '—'); ?></td><?php endif; ?>
<td><?php echo e($ct['name']); ?></td>
<td><?php echo e($ct['title'] ?? ''); ?></td>
<td><?php echo e($ct['phone'] ?? ''); ?></td>
<td><?php echo e($ct['email'] ?? ''); ?></td>
<td><?php echo e($ct['wechat'] ?? ''); ?></td>
<td><?php echo !empty($ct['is_primary']) ? '★ 是' : ''; ?></td>
<td class="row-actions">
<a href="<?php echo site_url('CRM/contacts/edit/' . $ct['id']); ?>">编辑</a>
<a href="<?php echo site_url('CRM/contacts/destroy/' . $ct['id']); ?>" data-confirm="确认删除该联系人?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($contacts)): ?><tr><td colspan="<?php echo $customer ? 8 : 9; ?>" class="muted">暂无联系人,点击右上角新增</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+81
View File
@@ -0,0 +1,81 @@
<?php
/** CRM 客户表单(服装行业) */
$c = $customer;
$isEdit = !empty($c);
$typeOpts = [
'brand' => '品牌方',
'dealer1' => '一级经销商',
'dealer2' => '二级经销商',
'retail' => '直营门店',
'ecom' => '电商',
'export' => '外贸出口',
'oem' => '生产代工',
];
$levelOpts = ['A' => 'A(重点)', 'B' => 'B(普通)', 'C' => 'C(潜在)'];
$industryOpts = ['服装' => '服装', '鞋帽' => '鞋帽', '家纺' => '家纺', '其他' => '其他'];
$statusOpts = [
'lead' => '潜在客户',
'intent' => '意向客户',
'quote' => '报价中',
'cooperating' => '合作中',
'won' => '已成交',
'lost' => '已流失',
'dormant' => '休眠',
];
$curType = $c['type'] ?? 'brand';
$curLevel = $c['level'] ?? 'C';
$curIndustry = $c['industry'] ?? '服装';
$curStatus = $c['status'] ?? 'lead';
?>
<div class="page-head">
<h1><?php echo $isEdit ? '编辑客户' : '新增客户'; ?></h1>
</div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'CRM/customers/update/' . $c['id'] : 'CRM/customers/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>客户名称 *</label><input name="name" value="<?php echo e($c['name'] ?? ''); ?>" required></div>
<div class="field"><label>客户编号</label><input name="customer_no" value="<?php echo e($c['customer_no'] ?? ''); ?>" placeholder="如:C-0001"></div>
<div class="field"><label>公司</label><input name="company" value="<?php echo e($c['company'] ?? ''); ?>"></div>
<div class="field"><label>联系人</label><input name="contact" value="<?php echo e($c['contact'] ?? ''); ?>"></div>
<div class="field"><label>电话</label><input name="phone" value="<?php echo e($c['phone'] ?? ''); ?>"></div>
<div class="field"><label>邮箱</label><input name="email" value="<?php echo e($c['email'] ?? ''); ?>"></div>
<div class="field"><label>国家/地区</label><input name="country" value="<?php echo e($c['country'] ?? ''); ?>" placeholder="如:德国 / 广东"></div>
<div class="field"><label>客户类型</label>
<select name="type">
<?php foreach ($typeOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curType === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>客户等级</label>
<select name="level">
<?php foreach ($levelOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curLevel === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>所属行业</label>
<select name="industry">
<?php foreach ($industryOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curIndustry === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>区域</label><input name="region" value="<?php echo e($c['region'] ?? ''); ?>" placeholder="如:华东/华南/海外"></div>
<div class="field"><label>信用额度</label><input name="credit_limit" type="number" step="0.01" value="<?php echo e($c['credit_limit'] ?? '0'); ?>"></div>
<div class="field"><label>客户状态</label>
<select name="status">
<?php foreach ($statusOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curStatus === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>来源</label><input name="source" value="<?php echo e($c['source'] ?? ''); ?>" placeholder="如:展会/官网/转介绍"></div>
<div class="field"><label>负责人</label><input name="owner" value="<?php echo e($c['owner'] ?? ''); ?>"></div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="3"><?php echo e($c['remark'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url('CRM/customers'); ?>">取消</a>
</form>
</div>
+72
View File
@@ -0,0 +1,72 @@
<?php
/** CRM 客户列表(服装行业:品牌方/经销商分级/区域/信用额度) */
$typeLabel = [
'brand' => '品牌方',
'dealer1' => '一级经销商',
'dealer2' => '二级经销商',
'retail' => '直营门店',
'ecom' => '电商',
'export' => '外贸出口',
'oem' => '生产代工',
];
$levelLabel = ['A' => 'A(重点)', 'B' => 'B(普通)', 'C' => 'C(潜在)'];
$statusLabel = [
'lead' => '潜在客户',
'intent' => '意向客户',
'quote' => '报价中',
'cooperating' => '合作中',
'won' => '已成交',
'lost' => '已流失',
'dormant' => '休眠',
];
$statusClass = [
'lead' => 'st-grey',
'intent' => 'st-blue',
'quote' => 'st-purple',
'cooperating' => 'st-green',
'won' => 'st-gold',
'lost' => 'st-red',
'dormant' => 'st-grey',
];
?>
<div class="page-head">
<h1>客户管理</h1>
<a class="btn-primary" href="<?php echo site_url('CRM/customers/create'); ?>">+ 新增客户</a>
</div>
<div class="panel">
<table class="tbl">
<thead><tr>
<th>ID</th><th>客户名称</th><th>公司</th><th>联系人</th><th>电话</th><th>国家/地区</th>
<th>类型</th><th>等级</th><th>状态</th><th>行业</th><th>区域</th><th>信用额度</th><th>负责人</th><th>操作</th>
</tr></thead>
<tbody>
<?php foreach ($customers as $c): ?>
<tr>
<td><?php echo e($c['id']); ?></td>
<td><?php echo e($c['name']); ?></td>
<td><?php echo e($c['company'] ?? ''); ?></td>
<td><?php echo e($c['contact'] ?? ''); ?></td>
<td><?php echo e($c['phone'] ?? ''); ?></td>
<td><?php echo e($c['country'] ?? ''); ?></td>
<td><?php echo e($typeLabel[$c['type'] ?? ''] ?? $c['type'] ?? ''); ?></td>
<td><?php echo e($levelLabel[$c['level'] ?? ''] ?? $c['level'] ?? ''); ?></td>
<td><span class="badge <?php echo e($statusClass[$c['status'] ?? 'lead'] ?? 'st-grey'); ?>"><?php echo e($statusLabel[$c['status'] ?? 'lead'] ?? '潜在客户'); ?></span></td>
<td><?php echo e($c['industry'] ?? ''); ?></td>
<td><?php echo e($c['region'] ?? ''); ?></td>
<td><?php echo e(number_format((float)($c['credit_limit'] ?? 0), 2)); ?></td>
<td><?php echo e($c['owner'] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('CRM/customers/edit/' . $c['id']); ?>">编辑</a>
<a href="<?php echo site_url('CRM/contacts?customer_id=' . $c['id']); ?>">联系人<?php
$cc = $contactCounts[$c['id']] ?? 0;
if ($cc > 0) echo ' (' . $cc . ')';
?></a>
<a href="<?php echo site_url('CRM/customers/destroy/' . $c['id']); ?>" data-confirm="确认删除?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($customers)): ?><tr><td colspan="14" class="muted">暂无客户,点击右上角新增</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+112
View File
@@ -0,0 +1,112 @@
<?php
/** CRM 仪表盘 */
$stageLabel = ['new' => '新线索', 'contact' => '已联系', 'quote' => '报价中', 'won' => '已成交', 'lost' => '已流失'];
$typeLabel = ['brand' => '品牌方', 'dealer1' => '一级经销商', 'dealer2' => '二级经销商', 'retail' => '直营门店', 'ecom' => '电商', 'export' => '外贸出口', 'oem' => '生产代工'];
$statusLabel = [
'lead' => '潜在客户', 'intent' => '意向客户', 'quote' => '报价中',
'cooperating' => '合作中', 'won' => '已成交', 'lost' => '已流失', 'dormant' => '休眠',
];
$statusClass = [
'lead' => 'st-grey', 'intent' => 'st-blue', 'quote' => 'st-purple',
'cooperating' => 'st-green', 'won' => 'st-gold', 'lost' => 'st-red', 'dormant' => 'st-grey',
];
$recentCustomers = $recentCustomers ?? [];
?>
<div class="page-head">
<h1>CRM 仪表盘</h1>
<p class="sub">客户需求与客户关系总览</p>
</div>
<div class="stat-grid">
<div class="stat-card"><div class="stat-num"><?php echo e($customerTotal); ?></div><div class="stat-label">客户总数</div></div>
<div class="stat-card"><div class="stat-num"><?php echo e($leadTotal); ?></div><div class="stat-label">商机线索</div></div>
<div class="stat-card"><div class="stat-num"><?php echo e($followTotal); ?></div><div class="stat-label">跟进记录</div></div>
<div class="stat-card"><div class="stat-num">¥<?php echo e(number_format($amountTotal, 2)); ?></div><div class="stat-label">商机金额合计</div></div>
</div>
<div class="quick-actions">
<a class="qa-card" href="<?php echo site_url('CRM/customers/create'); ?>">
<span class="qa-ico"></span><span class="qa-t">录入新客户</span><span class="qa-d">新增品牌方/经销商等</span>
</a>
<a class="qa-card" href="<?php echo site_url('CRM/contacts'); ?>">
<span class="qa-ico">👥</span><span class="qa-t">客户联系人</span><span class="qa-d">维护对接人多人对接</span>
</a>
<a class="qa-card" href="<?php echo site_url('CRM/customers'); ?>">
<span class="qa-ico">🔄</span><span class="qa-t">更新客户状态</span><span class="qa-d">在编辑页调整阶段/状态</span>
</a>
<a class="qa-card" href="<?php echo site_url('CRM/leads/create'); ?>">
<span class="qa-ico">💡</span><span class="qa-t">新建商机</span><span class="qa-d">登记线索与金额</span>
</a>
</div>
<div class="two-col">
<div class="panel">
<h3>商机阶段分布</h3>
<?php if (empty($stageCount)): ?><p class="muted">暂无商机数据</p><?php else: ?>
<table class="tbl">
<thead><tr><th>阶段</th><th>数量</th></tr></thead>
<tbody>
<?php foreach ($stageCount as $k => $v): ?>
<tr><td><?php echo e($stageLabel[$k] ?? $k); ?></td><td><?php echo e($v); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="panel">
<h3>客户类型分布</h3>
<?php if (empty($typeCount)): ?><p class="muted">暂无客户数据</p><?php else: ?>
<table class="tbl">
<thead><tr><th>类型</th><th>数量</th></tr></thead>
<tbody>
<?php foreach ($typeCount as $k => $v): ?>
<tr><td><?php echo e($typeLabel[$k] ?? $k); ?></td><td><?php echo e($v); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<div class="panel">
<h3>最近客户</h3>
<?php if (empty($recentCustomers)): ?><p class="muted">暂无客户,点击上方「录入新客户」开始</p><?php else: ?>
<table class="tbl">
<thead><tr><th>ID</th><th>客户名称</th><th>类型</th><th>状态</th><th>负责人</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($recentCustomers as $c): ?>
<tr>
<td><?php echo e($c['id']); ?></td>
<td><?php echo e($c['name']); ?></td>
<td><?php echo e($typeLabel[$c['type'] ?? ''] ?? $c['type'] ?? ''); ?></td>
<td><span class="badge <?php echo e($statusClass[$c['status'] ?? 'lead'] ?? 'st-grey'); ?>"><?php echo e($statusLabel[$c['status'] ?? 'lead'] ?? '潜在客户'); ?></span></td>
<td><?php echo e($c['owner'] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('CRM/customers/edit/' . $c['id']); ?>">编辑</a>
<a href="<?php echo site_url('CRM/contacts?customer_id=' . $c['id']); ?>">联系人</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="panel">
<h3>最近跟进</h3>
<?php if (empty($recent)): ?><p class="muted">暂无跟进记录</p><?php else: ?>
<table class="tbl">
<thead><tr><th>时间</th><th>客户ID</th><th>内容</th><th>下次跟进</th></tr></thead>
<tbody>
<?php foreach ($recent as $r): ?>
<tr>
<td><?php echo e($r['created_at'] ?? ''); ?></td>
<td>#<?php echo e($r['customer_id'] ?? ''); ?></td>
<td><?php echo e(mb_substr($r['content'] ?? '', 0, 40)); ?></td>
<td><?php echo e($r['next_at'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
+36
View File
@@ -0,0 +1,36 @@
<?php
/** CRM 跟进表单 */
$f = $follow;
$isEdit = !empty($f);
$wayOpts = ['tel' => '电话', 'wechat' => '微信', 'visit' => '拜访', 'email' => '邮件', 'other' => '其他'];
$curWay = $f['way'] ?? 'tel';
?>
<div class="page-head"><h1><?php echo $isEdit ? '编辑跟进' : '新增跟进'; ?></h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'CRM/followups/update/' . $f['id'] : 'CRM/followups/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>关联客户 *</label>
<select name="customer_id">
<option value="0">未关联</option>
<?php foreach ($customers as $c): ?>
<option value="<?php echo e($c['id']); ?>" <?php echo ($f['customer_id'] ?? 0) == $c['id'] ? 'selected' : ''; ?>><?php echo e($c['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>跟进方式</label>
<select name="way">
<?php foreach ($wayOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curWay === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>下次跟进</label><input name="next_at" type="date" value="<?php echo e($f['next_at'] ?? ''); ?>"></div>
<div class="field"><label>跟进结果</label><input name="result" value="<?php echo e($f['result'] ?? ''); ?>" placeholder="如:已寄样/待报价"></div>
<div class="field"><label>负责人</label><input name="owner" value="<?php echo e($f['owner'] ?? ''); ?>"></div>
</div>
<div class="field"><label>跟进内容 *</label><textarea name="content" rows="4" required><?php echo e($f['content'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url('CRM/followups'); ?>">取消</a>
</form>
</div>
+32
View File
@@ -0,0 +1,32 @@
<?php
/** CRM 跟进记录列表 */
$wayLabel = ['tel' => '电话', 'wechat' => '微信', 'visit' => '拜访', 'email' => '邮件', 'other' => '其他'];
?>
<div class="page-head">
<h1>跟进记录</h1>
<a class="btn-primary" href="<?php echo site_url('CRM/followups/create'); ?>">+ 新增跟进</a>
</div>
<div class="panel">
<table class="tbl">
<thead><tr><th>ID</th><th>时间</th><th>客户</th><th>方式</th><th>内容</th><th>结果</th><th>下次跟进</th><th>负责人</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($follows as $f): ?>
<tr>
<td><?php echo e($f['id']); ?></td>
<td><?php echo e($f['created_at'] ?? ''); ?></td>
<td><?php echo e($cmap[$f['customer_id']] ?? ('#' . $f['customer_id'])); ?></td>
<td><?php echo e($wayLabel[$f['way'] ?? ''] ?? $f['way'] ?? ''); ?></td>
<td><?php echo e(mb_substr($f['content'] ?? '', 0, 40)); ?></td>
<td><?php echo e($f['result'] ?? ''); ?></td>
<td><?php echo e($f['next_at'] ?? ''); ?></td>
<td><?php echo e($f['owner'] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('CRM/followups/edit/' . $f['id']); ?>">编辑</a>
<a href="<?php echo site_url('CRM/followups/destroy/' . $f['id']); ?>" data-confirm="确认删除?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($follows)): ?><tr><td colspan="9" class="muted">暂无跟进记录</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+46
View File
@@ -0,0 +1,46 @@
<?php
/** CRM 商机表单 */
$l = $lead;
$isEdit = !empty($l);
$stageLabel = ['new' => '新线索', 'contact' => '已联系', 'quote' => '报价中', 'won' => '已成交', 'lost' => '已流失'];
$srcOpts = ['expo' => '展会', 'referral' => '转介绍', 'online' => '线上', 'web' => '官网', 'tel' => '电话', 'other' => '其他'];
$curSrc = $l['source'] ?? 'expo';
?>
<div class="page-head"><h1><?php echo $isEdit ? '编辑商机' : '新增商机'; ?></h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'CRM/leads/update/' . $l['id'] : 'CRM/leads/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>关联客户 *</label>
<select name="customer_id">
<option value="0">未关联</option>
<?php foreach ($customers as $c): ?>
<option value="<?php echo e($c['id']); ?>" <?php echo ($l['customer_id'] ?? 0) == $c['id'] ? 'selected' : ''; ?>><?php echo e($c['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>商机标题 *</label><input name="title" value="<?php echo e($l['title'] ?? ''); ?>" required></div>
<div class="field"><label>金额</label><input name="amount" type="number" step="0.01" value="<?php echo e($l['amount'] ?? '0'); ?>"></div>
<div class="field"><label>阶段</label>
<select name="stage">
<?php foreach ($stageLabel as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo ($l['stage'] ?? 'new') === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>来源渠道</label>
<select name="source">
<?php foreach ($srcOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curSrc === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>成交概率(%)</label><input name="probability" type="number" min="0" max="100" value="<?php echo e($l['probability'] ?? '0'); ?>"></div>
<div class="field"><label>预计成交日期</label><input name="expected_close" type="date" value="<?php echo e($l['expected_close'] ?? ''); ?>"></div>
<div class="field"><label>负责人</label><input name="owner" value="<?php echo e($l['owner'] ?? ''); ?>"></div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="3"><?php echo e($l['remark'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url('CRM/leads'); ?>">取消</a>
</form>
</div>
+34
View File
@@ -0,0 +1,34 @@
<?php
/** CRM 商机线索列表 */
$stageLabel = ['new' => '新线索', 'contact' => '已联系', 'quote' => '报价中', 'won' => '已成交', 'lost' => '已流失'];
$sourceLabel = ['expo' => '展会', 'referral' => '转介绍', 'online' => '线上', 'web' => '官网', 'tel' => '电话', 'other' => '其他'];
?>
<div class="page-head">
<h1>商机线索</h1>
<a class="btn-primary" href="<?php echo site_url('CRM/leads/create'); ?>">+ 新增商机</a>
</div>
<div class="panel">
<table class="tbl">
<thead><tr><th>ID</th><th>标题</th><th>客户</th><th>金额</th><th>阶段</th><th>来源</th><th>成交概率</th><th>预计成交</th><th>负责人</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($leads as $l): ?>
<tr>
<td><?php echo e($l['id']); ?></td>
<td><?php echo e($l['title']); ?></td>
<td><?php echo e($cmap[$l['customer_id']] ?? ('#' . $l['customer_id'])); ?></td>
<td>¥<?php echo e(number_format((float)($l['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($stageLabel[$l['stage'] ?? ''] ?? $l['stage'] ?? ''); ?></td>
<td><?php echo e($sourceLabel[$l['source'] ?? ''] ?? $l['source'] ?? ''); ?></td>
<td><?php echo e((int)($l['probability'] ?? 0)); ?>%</td>
<td><?php echo e($l['expected_close'] ?? ''); ?></td>
<td><?php echo e($l['owner'] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('CRM/leads/edit/' . $l['id']); ?>">编辑</a>
<a href="<?php echo site_url('CRM/leads/destroy/' . $l['id']); ?>" data-confirm="确认删除?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($leads)): ?><tr><td colspan="10" class="muted">暂无商机,点击右上角新增</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+180
View File
@@ -0,0 +1,180 @@
<?php
$banner = $banners[0] ?? ['title' => '科技降温 · 清凉一夏', 'subtitle' => '为高温作业人群定制', 'link' => '/products'];
?>
<section class="hero">
<div class="hero-bg"></div>
<div class="container">
<div>
<p class="eyebrow">❄ SQY COOLING APPAREL</p>
<h1 class="hero-title"><?php echo e($banner['title']); ?><br><span class="hl">体感直降 8-12℃</span></h1>
<p class="hero-sub"><?php echo e($banner['subtitle']); ?>。我们提供水冷循环、相变蓄冷、涡扇风冷三大降温方案,覆盖工业、户外、消防等高温场景。</p>
<div class="hero-cta">
<a class="btn btn-primary magnetic" href="<?php echo site_url('products'); ?>">浏览产品中心</a>
<a class="btn btn-ghost" href="<?php echo site_url('contact'); ?>">获取免费方案</a>
</div>
</div>
<div class="hero-visual">
<div class="hero-orb"></div>
<div class="hero-frost">❄</div>
</div>
<div class="hero-stats">
<?php foreach ($stats as $s): ?>
<div class="stat reveal"><b><?php echo e($s['n']); ?><span style="font-size:16px"><?php echo e($s['u']); ?></span></b><span><?php echo e($s['l']); ?></span></div>
<?php endforeach; ?>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">产品系列</p>
<h2 class="section-title">六大降温服系列,总有一款适合你</h2>
<p class="section-sub">从长时间恒温作业到临时户外移动,覆盖不同降温原理与场景。</p>
</div>
<div class="cat-grid">
<?php foreach ($categories as $c): ?>
<a class="cat-card reveal" href="<?php echo site_url('products?cat=' . $c['id']); ?>">
<div class="cat-icon"><?php echo e($c['icon']); ?></div>
<h3><?php echo e($c['name']); ?></h3>
<p><?php echo e($c['description']); ?></p>
</a>
<?php endforeach; ?>
</div>
</div>
</section>
<section class="section" style="background:var(--c-surface)">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">精品推荐</p>
<h2 class="section-title">热门降温服</h2>
<p class="section-sub">外贸级品质,支持企业 LOGO 绣字与一人一码量体。</p>
</div>
<div class="product-grid">
<?php foreach ($products as $p): ?>
<a class="product-card reveal" href="<?php echo site_url('products/' . $p['slug']); ?>">
<?php if (!empty($p['cover'])): ?>
<div class="product-thumb"><img src="<?php echo e(site_url($p['cover'])); ?>" alt="<?php echo e($p['name']); ?>"></div>
<?php else: ?>
<div class="product-thumb" style="background:<?php echo gradient($p['id']); ?>">❄</div>
<?php endif; ?>
<div class="product-body">
<div class="product-name"><?php echo e($p['name']); ?></div>
<div class="product-sum"><?php echo e($p['summary']); ?></div>
<div class="product-meta">
<div class="product-price">¥<?php echo e($p['price']); ?> <small>起</small></div>
<span class="tag">查看详情 →</span>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<div style="text-align:center"><a class="more-link" href="<?php echo site_url('products'); ?>">查看全部产品 →</a></div>
</div>
</section>
<section class="section">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">为什么选择我们</p>
<h2 class="section-title">20 年服装经验,外贸级品质</h2>
</div>
<div class="adv-grid">
<?php foreach ($advantages as $a): ?>
<div class="adv-card reveal">
<div class="num"><?php echo e($a['n']); ?></div>
<h3><?php echo e($a['t']); ?></h3>
<p><?php echo e($a['d']); ?></p>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<section class="section" style="background:var(--c-surface)">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">交付流程</p>
<h2 class="section-title">28 天快速定制,省心交付</h2>
</div>
<div class="process">
<?php foreach ($process as $s): ?>
<div class="step reveal"><h4><?php echo e($s['t']); ?></h4><p><?php echo e($s['d']); ?></p></div>
<?php endforeach; ?>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">新闻动态</p>
<h2 class="section-title">最新资讯与案例</h2>
</div>
<div class="news-grid">
<?php foreach ($news as $n): ?>
<a class="news-card reveal" href="<?php echo site_url('news/' . $n['slug']); ?>">
<div class="news-thumb" style="background:<?php echo gradient($n['id']); ?>">📰</div>
<div class="news-body">
<div class="news-date"><?php echo e(format_date($n['published_at'])); ?></div>
<div class="news-title"><?php echo e($n['title']); ?></div>
<div class="news-sum"><?php echo e($n['summary']); ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
<div style="text-align:center"><a class="more-link" href="<?php echo site_url('news'); ?>">更多资讯 →</a></div>
</div>
</section>
<section class="section" style="background:var(--c-surface)">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">客户案例</p>
<h2 class="section-title">他们都在用酷冰甲降温服</h2>
<p class="section-sub">覆盖钢铁、消防、化工、环卫等高温行业,提供定制化降温方案。</p>
</div>
<div class="news-grid">
<?php foreach ($cases as $c): ?>
<a class="news-card reveal" href="<?php echo site_url('cases/' . $c['slug']); ?>">
<div class="news-thumb" style="background:<?php echo gradient($c['id']); ?>">🤝</div>
<div class="news-body">
<div class="news-date"><?php echo e($c['industry'] ?? ''); ?> · <?php echo e($c['customer'] ?? ''); ?></div>
<div class="news-title"><?php echo e($c['title']); ?></div>
<div class="news-sum"><?php echo e($c['summary']); ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
<div style="text-align:center"><a class="more-link" href="<?php echo site_url('cases'); ?>">查看全部案例 →</a></div>
</div>
</section>
<section class="section" style="background:var(--c-surface)">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">常见问题</p>
<h2 class="section-title">关于降温服,您可能想了解</h2>
<p class="section-sub">高频疑问一站式解答,定制与选型更省心。</p>
</div>
<div class="faq-list">
<?php foreach ($faqs as $f): ?>
<details class="faq-item reveal">
<summary><?php echo e($f['q']); ?><span class="faq-ico">+</span></summary>
<div class="faq-a"><?php echo e($f['a']); ?></div>
</details>
<?php endforeach; ?>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="cta-banner reveal">
<h2>免费拿样 · 5 天出设计方案</h2>
<p>提交您的需求,专属顾问 1 对 1 为您提供降温服装定制方案,支持企业 LOGO 绣字。</p>
<a class="btn btn-primary magnetic" href="<?php echo site_url('contact'); ?>">立即咨询</a>
</div>
</div>
</section>
+59
View File
@@ -0,0 +1,59 @@
<?php
/** @var string $content */
use Core\Theme;
$seg = $seg ?? '';
if ($seg === '') {
$__p = trim(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/');
$__parts = explode('/', $__p);
$seg = $__parts[1] ?? '';
}
$site = Theme::get('site_name', '酷冰甲 · 降温服');
$nav = admin_nav_items();
?>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>后台管理 · <?php echo e($site); ?></title>
<link rel="stylesheet" href="<?php echo asset('css/admin.css'); ?>">
</head>
<body class="admin">
<?php if (is_admin()): ?>
<aside class="admin-side">
<a class="admin-brand" href="<?php echo site_url('admin'); ?>"><span class="brand-mark"><?php echo admin_icon('snowflake', 20); ?></span> <span class="brand-text"><?php echo e($site); ?></span></a>
<nav class="admin-nav">
<?php foreach ($nav as $n): ?>
<a href="<?php echo site_url($n['url']); ?>" class="<?php echo $seg === $n['k'] ? 'active' : ''; ?>"><span class="nav-ic"><?php echo admin_icon($n['ic'] ?? 'file-text', 20); ?></span><span class="nav-label"><?php echo e($n['label']); ?></span><?php if (!empty($n['badge'])): ?><span class="nav-badge"><?php echo (int)$n['badge']; ?></span><?php endif; ?></a>
<?php endforeach; ?>
</nav>
<div class="admin-side-foot">
<a href="<?php echo site_url(); ?>" target="_blank"><?php echo admin_icon('globe', 18); ?> 查看前台</a>
</div>
</aside>
<div class="admin-body">
<header class="admin-top">
<div class="admin-top-left">
<button class="menu-toggle" aria-label="菜单"><?php echo admin_icon('menu', 22); ?></button>
<a class="top-link" href="<?php echo site_url(); ?>" target="_blank"><?php echo admin_icon('globe', 16); ?> 查看前台</a>
</div>
<div class="admin-user">
<span class="admin-user-name"><span class="admin-avatar"><?php echo admin_icon('user', 16); ?></span> <?php echo e($_SESSION['admin_name'] ?? '管理员'); ?>
<span class="role-badge role-<?php echo e(admin_role()); ?>"><?php echo e(admin_role_label(admin_role())); ?></span>
</span>
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/password'); ?>"><?php echo admin_icon('key', 15); ?> 修改密码</a>
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/logout'); ?>"><?php echo admin_icon('logout', 15); ?> 退出</a>
</div>
</header>
<main class="admin-content">
<?php if (!empty($_SESSION['flash'])): $f = $_SESSION['flash']; unset($_SESSION['flash']); ?>
<div class="alert alert-<?php echo $f['type'] === 'ok' ? 'ok' : 'err'; ?>"><?php echo e($f['msg']); ?></div>
<?php endif; ?>
<?php echo $content; ?></main>
</div>
<?php else: ?>
<div class="admin-login-wrap"><?php echo $content; ?></div>
<?php endif; ?>
<script src="<?php echo asset('js/admin.js'); ?>"></script>
</body>
</html>
+202
View File
@@ -0,0 +1,202 @@
<?php
/** @var string $content */
use Core\Theme;
$site = Theme::all();
$name = $site['site_name'];
$slogan = $site['site_slogan'];
$phone = $site['contact_phone'];
$logo = $site['site_logo'];
$defaultMode = $site['default_mode'];
$headerStyle = $site['header'];
$icp = $site['icp'];
$gongan = $site['gongan'] ?? '';
// ── 页面级 SEO 数据(控制器通过 $this->view() 或 $data 传入)────
$pageSeo = $pageSeo ?? [];
$seoTitle = $pageSeo['title'] ?? $site['seo_title'];
$seoDesc = $pageSeo['description'] ?? $site['seo_description'];
$seoKeywords = $pageSeo['keywords'] ?? $site['seo_keywords'];
$ogImage = $pageSeo['og_image'] ?? (site_url() . 'assets/img/og-default.png');
$ogType = $pageSeo['og_type'] ?? 'website';
$jsonLd = $pageSeo['jsonld'] ?? '';
$breadcrumb = $pageSeo['breadcrumb'] ?? null;
$noindex = $pageSeo['noindex'] ?? false;
$canonical = $pageSeo['canonical'] ?? absolute_url();
// 页面级 title 自动追加站点名后缀(如果没包含)
if (!empty($pageSeo['title']) && mb_strpos($seoTitle, $name) === false) {
$seoTitle = $seoTitle . ' - ' . $name;
}
$current = trim(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/');
$nav = [
['label' => '首页', 'url' => ''],
['label' => '产品中心', 'url' => 'products'],
['label' => '新闻动态', 'url' => 'news'],
['label' => '客户案例', 'url' => 'cases'],
['label' => '关于我们', 'url' => 'page/about'],
['label' => '联系我们', 'url' => 'contact'],
];
$isActive = function ($url) use ($current) {
if ($url === '') return $current === '';
return strpos($current, $url) === 0;
};
?>
<!doctype html>
<html lang="zh-CN" data-theme="<?php echo e($defaultMode); ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo e($seoTitle); ?></title>
<meta name="description" content="<?php echo e($seoDesc); ?>">
<meta name="keywords" content="<?php echo e($seoKeywords); ?>">
<?php if ($noindex): ?><meta name="robots" content="noindex, follow"><?php endif; ?>
<link rel="canonical" href="<?php echo e($canonical); ?>">
<link rel="icon" type="image/png" sizes="32x32" href="<?php echo asset('img/favicon-32.png'); ?>">
<link rel="icon" type="image/png" sizes="16x16" href="<?php echo asset('img/favicon-16.png'); ?>">
<link rel="apple-touch-icon" sizes="180x180" href="<?php echo asset('img/apple-touch-icon.png'); ?>">
<!-- Open Graph (社交分享卡片: 微信/QQ/Facebook/LinkedIn 等) -->
<meta property="og:title" content="<?php echo e($seoTitle); ?>">
<meta property="og:description" content="<?php echo e($seoDesc); ?>">
<meta property="og:image" content="<?php echo e($ogImage); ?>">
<meta property="og:url" content="<?php echo e($canonical); ?>">
<meta property="og:type" content="<?php echo e($ogType); ?>">
<meta property="og:site_name" content="<?php echo e($name); ?>">
<meta property="og:locale" content="zh_CN">
<!-- Twitter Card (Twitter/X 分享预览) -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo e($seoTitle); ?>">
<meta name="twitter:description" content="<?php echo e($seoDesc); ?>">
<meta name="twitter:image" content="<?php echo e($ogImage); ?>">
<!-- ── 结构化数据(JSON-LD Schema.org)──── -->
<!-- Organization - 全局组织信息,所有页面共用 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": <?php echo json_encode($name, JSON_UNESCAPED_UNICODE); ?>,
"url": <?php echo json_encode(site_url(), JSON_UNESCAPED_UNICODE); ?>,
"logo": <?php echo json_encode($logo ?: '', JSON_UNESCAPED_UNICODE); ?>,
"description": <?php echo json_encode($slogan, JSON_UNESCAPED_UNICODE); ?>,
"contactPoint": {
"@type": "ContactPoint",
"telephone": <?php echo json_encode($phone, JSON_UNESCAPED_UNICODE); ?>,
"contactType": "customer service",
"availableLanguage": ["Chinese"]
}
}
</script>
<!-- WebSite - 搜索框结构化数据,可在 SERP 中显示站内搜索框 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": <?php echo json_encode($name, JSON_UNESCAPED_UNICODE); ?>,
"url": <?php echo json_encode(site_url(), JSON_UNESCAPED_UNICODE); ?>
}
</script>
<?php if ($breadcrumb): ?>
<!-- BreadcrumbList - 面包屑导航 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
<?php $bcCount = count($breadcrumb); $bcIdx = 0; foreach ($breadcrumb as $bc): $bcIdx++; ?>
{
"@type": "ListItem",
"position": <?php echo $bcIdx; ?>,
"name": <?php echo json_encode($bc['name'], JSON_UNESCAPED_UNICODE); ?>,
"item": <?php echo json_encode($bc['url'] ?? '', JSON_UNESCAPED_UNICODE); ?>
}<?php if ($bcIdx < $bcCount) echo ','; echo "\n"; ?>
<?php endforeach; ?>
]
}
</script>
<?php endif; ?>
<?php if ($jsonLd): ?>
<?php echo $jsonLd; ?>
<?php endif; ?>
<link rel="stylesheet" href="<?php echo asset('css/theme.css'); ?>">
<link rel="stylesheet" href="<?php echo asset('css/site.css'); ?>">
<script>
(function () {
try {
var t = localStorage.getItem('site_theme');
if (t) document.documentElement.setAttribute('data-theme', t);
} catch (e) {}
})();
</script>
</head>
<body class="header-<?php echo e($headerStyle); ?>">
<header class="site-header" id="siteHeader">
<div class="container nav-inner">
<a class="brand" href="<?php echo site_url(); ?>">
<?php $logoSrc = $logo ? (preg_match('#^https?://|^\/\/#i', (string)$logo) ? $logo : site_url(ltrim($logo, '/'))) : ''; ?>
<?php if ($logoSrc): ?><img src="<?php echo e($logoSrc); ?>" alt="<?php echo e($name); ?>" class="brand-logo"><?php else: ?><span class="brand-mark">❄</span><?php endif; ?>
</a>
<nav class="nav-links" id="navLinks">
<?php foreach ($nav as $n): ?>
<a href="<?php echo site_url($n['url']); ?>" class="<?php echo $isActive($n['url']) ? 'active' : ''; ?>"><?php echo e($n['label']); ?></a>
<?php endforeach; ?>
</nav>
<div class="nav-actions">
<button class="theme-toggle" id="themeToggle" aria-label="切换明暗">🌓</button>
<a class="btn btn-primary magnetic" href="<?php echo site_url('contact'); ?>">免费拿样</a>
<button class="nav-burger" id="navBurger" aria-label="菜单">☰</button>
</div>
</div>
</header>
<main class="site-main">
<?php echo $content; ?>
</main>
<footer class="site-footer">
<div class="container footer-grid">
<div>
<div class="brand">
<?php if ($logoSrc): ?><img src="<?php echo e($logoSrc); ?>" alt="<?php echo e($name); ?>" class="brand-logo"><?php else: ?><span class="brand-mark">❄</span><span class="brand-name"><?php echo e($name); ?></span><?php endif; ?>
</div>
<p class="footer-slogan"><?php echo e($slogan); ?></p>
<p class="footer-line">电话:<a href="tel:<?php echo e($phone); ?>"><?php echo e($phone); ?></a></p>
<p class="footer-line">邮箱:<?php echo e($site['contact_email']); ?></p>
<p class="footer-line">地址:<?php echo e($site['contact_address']); ?></p>
</div>
<div>
<h4>产品系列</h4>
<a href="<?php echo site_url('products'); ?>">水冷循环降温服</a>
<a href="<?php echo site_url('products'); ?>">相变蓄冷凝胶服</a>
<a href="<?php echo site_url('products'); ?>">涡扇风冷降温服</a>
<a href="<?php echo site_url('products'); ?>">工业降温工装</a>
</div>
<div>
<h4>快速链接</h4>
<a href="<?php echo site_url('page/about'); ?>">关于我们</a>
<a href="<?php echo site_url('page/service'); ?>">服务与优势</a>
<a href="<?php echo site_url('cases'); ?>">客户案例</a>
<a href="<?php echo site_url('news'); ?>">新闻动态</a>
<a href="<?php echo site_url('order/query'); ?>">订单查询</a>
</div>
<div>
<h4>关注我们</h4>
<p class="footer-line">扫码或来电,获取专属降温方案</p>
<a class="btn btn-ghost" href="<?php echo site_url('contact'); ?>">立即咨询</a>
</div>
</div>
<div class="footer-bottom container">
<span>© <?php echo date('Y'); ?> <?php echo e($name); ?> · 科技降温服装定制</span>
<?php if ($icp): ?><span><?php echo e($icp); ?></span><?php endif; ?>
<?php if ($gongan): ?><span><a href="https://beian.mps.gov.cn/" target="_blank" rel="noopener"><?php echo e($gongan); ?></a></span><?php endif; ?>
</div>
</footer>
<script src="<?php echo asset('js/main.js'); ?>"></script>
</body>
</html>
+95
View File
@@ -0,0 +1,95 @@
<?php
/**
* 分系统后台布局(CRM / 进销存 PSI 共用)
* 独立框架:左侧导航使用各子系统自身的 $nav,不再复用 admin 全量后台导航,
* 因此 CRM/PSI 内不会出现「管理后台」仪表盘及 products/news 等后台模块。
*/
use Core\Theme;
$sys = $_sys ?? 'crm';
$sysName = $sys === 'psi' ? '进销存 PSI' : '客户管理 CRM';
$nav = \subsys_nav($sys);
$seg = $_seg ?? '';
$site = Theme::get('site_name', '酷冰甲 · 降温服');
$brandIc = $sys === 'psi' ? 'package' : 'handshake';
$isAuthed = is_admin() || subsys_user($sys);
// 子系统导航 key -> 图标(避免逐个控制器改造,统一在此映射)
$icMap = [
'dashboard' => 'dashboard', 'customers' => 'handshake', 'leads' => 'lightbulb',
'followups' => 'phone', 'contacts' => 'users', 'materials' => 'package',
'products' => 'shopping-bag', 'suppliers' => 'truck', 'purchases' => 'inbox',
'sales' => 'cart', 'stock' => 'database', 'orders' => 'receipt', 'users' => 'users', 'admin' => 'shield',
'sales_orders' => 'shopping-bag', 'purchase_orders' => 'file-text', 'outbounds' => 'package', 'reports' => 'bar-chart',
'reminders' => 'bell', 'notifications' => 'settings',
];
?>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo e($sysName); ?> · <?php echo e($site); ?></title>
<link rel="stylesheet" href="<?php echo asset('css/admin.css'); ?>">
<link rel="stylesheet" href="<?php echo asset('css/subsys.css'); ?>">
</head>
<body class="admin subsys subsys-<?php echo e($sys); ?>">
<?php if ($isAuthed): ?>
<aside class="ss-side">
<a class="ss-brand" href="<?php echo site_url($sys); ?>">
<span class="ss-brand-ic"><?php echo admin_icon($brandIc, 22); ?></span>
<span class="ss-brand-text"><b><?php echo e($sysName); ?></b><small><?php echo e($site); ?></small></span>
</a>
<nav class="ss-nav">
<?php foreach ($nav as $it): ?>
<?php $k = $it['k'] ?? ''; ?>
<?php if (in_array($k, subsys_pages($sys), true) && !subsys_page_can($sys, $k)) continue; ?>
<?php $active = ($k === $seg); ?>
<a href="<?php echo site_url($it['url'] ?? '#'); ?>" class="ss-nav-item<?php echo $active ? ' active' : ''; ?>">
<span class="ss-ic"><?php echo admin_icon($icMap[$k] ?? 'file-text', 20); ?></span>
<span class="ss-label"><?php echo e($it['label'] ?? ''); ?></span>
</a>
<?php endforeach; ?>
</nav>
<div class="ss-side-foot">
<a href="<?php echo site_url(); ?>" target="_blank" class="ss-foot-link"><?php echo admin_icon('globe', 18); ?> 查看前台</a>
</div>
</aside>
<div class="ss-body">
<header class="ss-top">
<div class="ss-top-left">
<button class="menu-toggle" type="button" aria-label="菜单"><?php echo admin_icon('menu', 22); ?></button>
<span class="ss-crumb"><?php echo e($sysName); ?></span>
</div>
<?php if ($sys === 'psi'):
$unread = \psi_unread_events(); ?>
<a class="ss-bell" href="<?php echo \Core\App::url('PSI/reminders'); ?>" title="紧急提醒">
<?php echo admin_icon('bell', 20) ?: '🔔'; ?>
<?php if ($unread > 0): ?><span class="ss-badge"><?php echo $unread > 99 ? '99+' : $unread; ?></span><?php endif; ?>
</a>
<?php endif; ?>
<div class="ss-user">
<span class="ss-user-name">
<span class="ss-avatar"><?php echo admin_icon('user', 15); ?></span>
<?php echo e($_SESSION['admin_name'] ?? '操作员'); ?>
<span class="role-badge role-<?php echo e(admin_role()); ?>"><?php echo e(admin_role_label(admin_role())); ?></span>
</span>
<?php if (in_array(admin_role(), ['super_admin', 'admin'], true)): ?>
<a class="btn-soft btn-sm" href="<?php echo site_url('admin'); ?>"><?php echo admin_icon('dashboard', 15); ?> 管理后台</a>
<?php endif; ?>
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/password'); ?>"><?php echo admin_icon('key', 15); ?> 密码</a>
<a class="btn-soft btn-sm" href="<?php echo site_url('admin/logout'); ?>"><?php echo admin_icon('logout', 15); ?> 退出</a>
</div>
</header>
<main class="ss-content">
<?php if (!empty($_SESSION['flash'])): $f = $_SESSION['flash']; unset($_SESSION['flash']); ?>
<div class="alert alert-<?php echo $f['type'] === 'ok' ? 'ok' : 'err'; ?>"><?php echo e($f['msg']); ?></div>
<?php endif; ?>
<?php echo $content; ?>
</main>
</div>
<?php else: ?>
<div class="admin-login-wrap"><?php echo $content; ?></div>
<?php endif; ?>
<script src="<?php echo asset('js/admin.js'); ?>"></script>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
<section class="page-hero">
<div class="container">
<h1>新闻动态</h1>
<p>产品发布、技术科普与行业案例</p>
</div>
</section>
<section class="section" style="padding-top:20px">
<div class="container">
<div class="news-grid">
<?php foreach ($news as $n): ?>
<a class="news-card reveal" href="<?php echo site_url('news/' . $n['slug']); ?>">
<div class="news-thumb" style="background:<?php echo gradient($n['id']); ?>">📰</div>
<div class="news-body">
<div class="news-date"><?php echo e(format_date($n['published_at'])); ?> · <?php echo e($n['author']); ?></div>
<div class="news-title"><?php echo e($n['title']); ?></div>
<div class="news-sum"><?php echo e($n['summary']); ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php if (empty($news)): ?><p style="color:var(--c-muted)">暂无新闻。</p><?php endif; ?>
</div>
</section>
+36
View File
@@ -0,0 +1,36 @@
<?php
$n = $n ?? null;
$layoutArr = [];
if (!empty($n['layout'])) { $d = json_decode($n['layout'], true); if (is_array($d)) $layoutArr = $d; }
// 模式渲染:可视化编辑(builder)且有 layout → 画布;否则固定版式(正文富文本)
$useBuilder = !empty($layoutArr) && ($n['mode'] ?? '') !== 'fixed';
?>
<section class="page-hero">
<div class="container">
<h1><?php echo e($n['title']); ?></h1>
<p><?php echo e(format_date($n['published_at'])); ?> · <?php echo e($n['author']); ?></p>
</div>
</section>
<div class="container">
<nav class="breadcrumb"><a href="<?php echo site_url(); ?>">首页</a> / <a href="<?php echo site_url('news'); ?>">新闻动态</a> / <?php echo e($n['title']); ?></nav>
</div>
<?php if ($useBuilder): ?>
<?php echo \Core\View::buffer('parts/canvas', ['layout' => $layoutArr, 'item' => $n, 'module' => 'news']); ?>
<?php else: ?>
<section class="section" style="padding-top:10px">
<div class="container" style="max-width:780px">
<article class="detail-desc detail-rich" style="font-size:16px"><?php echo $n['content']; ?></article>
</div>
</section>
<?php endif; ?>
<section class="section" style="padding-top:0">
<div class="container" style="max-width:780px">
<div style="display:flex;justify-content:space-between;margin-top:40px;gap:12px;flex-wrap:wrap">
<?php if ($prev): ?><a class="btn btn-ghost" href="<?php echo site_url('news/' . $prev['slug']); ?>">← <?php echo e(mb_substr($prev['title'],0,14)); ?></a><?php else: ?><span></span><?php endif; ?>
<?php if ($next): ?><a class="btn btn-ghost" href="<?php echo site_url('news/' . $next['slug']); ?>"><?php echo e(mb_substr($next['title'],0,14)); ?> →</a><?php else: ?><span></span><?php endif; ?>
</div>
<div style="text-align:center;margin-top:30px"><a class="more-link" href="<?php echo site_url('news'); ?>">返回新闻列表</a></div>
</div>
</section>
+34
View File
@@ -0,0 +1,34 @@
<?php
/** @var array $p */
/** @var string $slug */
/** @var string $err */
use Core\Theme;
?>
<section class="page-hero">
<div class="container"><h1>立即下单</h1><p><?php echo e($p['name']); ?></p></div>
</section>
<div class="container">
<nav class="breadcrumb"><a href="<?php echo site_url(); ?>">首页</a> / <a href="<?php echo site_url('products'); ?>">产品中心</a> / <?php echo e($p['name']); ?></nav>
<div class="form-card">
<?php if ($err): ?><div class="alert alert-err"><?php echo e($err); ?></div><?php endif; ?>
<div style="display:flex;gap:14px;align-items:center;margin-bottom:6px">
<div style="width:60px;height:60px;border-radius:14px;display:grid;place-items:center;color:#fff;font-size:24px;background:<?php echo gradient($p['id']); ?>">❄</div>
<div>
<div style="font-weight:800;font-size:17px"><?php echo e($p['name']); ?></div>
<div style="color:var(--c-muted)">单价 ¥<?php echo e($p['price']); ?> / 套</div>
</div>
</div>
<form method="post" action="<?php echo site_url('order/store'); ?>" style="margin-top:20px">
<?php echo csrf_field(); ?>
<input type="hidden" name="slug" value="<?php echo e($slug); ?>">
<div class="field"><label>姓名 / 单位 *</label><input name="name" required placeholder="如:张三 / 某某公司"></div>
<div class="field"><label>手机号 *</label><input name="phone" required placeholder="用于订单查询与发货联系"></div>
<div class="field"><label>邮箱(选填)</label><input name="email" placeholder="可留空"></div>
<div class="field"><label>数量(套)</label><input name="qty" type="number" min="1" value="1"></div>
<div class="form-actions">
<button class="btn btn-primary magnetic" type="submit">提交订单并支付</button>
<a class="btn btn-ghost" href="<?php echo site_url('products/' . $slug); ?>">返回</a>
</div>
</form>
</div>
</div>
+50
View File
@@ -0,0 +1,50 @@
<?php
/** @var array $order */
/** @var array|null $gw */
/** @var bool $choose */
/** @var bool $paid */
$isPaid = $paid ?? ($order['status'] === 'paid');
?>
<section class="page-hero"><div class="container"><h1>订单支付</h1><p>订单号 <?php echo e($order['order_no']); ?></p></div></section>
<div class="container">
<div class="form-card">
<div style="padding:16px;border-radius:14px;background:var(--c-surface);border:1px solid var(--c-border);margin-bottom:6px">
<div style="font-weight:800;font-size:18px"><?php echo e($order['product_name']); ?></div>
<div style="color:var(--c-muted);font-size:14px">数量 <?php echo e($order['qty']); ?> 套 · 下单人 <?php echo e($order['customer_name']); ?></div>
<div style="font-size:28px;font-weight:900;color:var(--c-primary);margin-top:8px">应付 ¥<?php echo e($order['amount']); ?></div>
</div>
<?php if ($isPaid): ?>
<div class="alert alert-ok">✓ 该订单已支付成功</div>
<a class="btn btn-primary" href="<?php echo site_url('order/success/' . $order['order_no']); ?>">查看支付详情</a>
<?php elseif (!empty($choose)): ?>
<p style="margin:18px 0 10px;font-weight:700">选择支付方式</p>
<form method="post" action="<?php echo site_url('order/pay/' . $order['order_no']); ?>">
<?php echo csrf_field(); ?>
<div class="pay-channels">
<button type="submit" name="channel" value="alipay" class="pay-channel">💰 支付宝</button>
<button type="submit" name="channel" value="wechat" class="pay-channel">💚 微信支付</button>
</div>
</form>
<?php else:
$mode = $gw['mode'] ?? 'demo';
if ($mode === 'redirect'): ?>
<p style="margin:16px 0">正在跳转到支付宝支付…</p>
<script>setTimeout(function(){ window.location.href = <?php echo json_encode($gw['url']); ?>; }, 300);</script>
<a class="btn btn-primary" href="<?php echo e($gw['url']); ?>">如未自动跳转,点击此处支付</a>
<?php elseif ($mode === 'qrcode'): ?>
<p style="margin:16px 0">请使用微信扫码支付:</p>
<div class="pay-qr"><?php echo e($gw['qr']); ?></div>
<?php else: ?>
<div class="alert alert-ok" style="margin-top:16px">演示模式:点击下方按钮完成模拟支付(不影响真实下单流程)</div>
<?php if (!empty($gw['config_missing'])): ?><p style="color:var(--c-muted);font-size:13px;margin-top:6px">* 尚未配置商户号,当前为演示支付;在后台「支付设置」填入商户号并切到真实模式即可真实收款。</p><?php endif; ?>
<div class="pay-demo-box">
<div class="pay-demo-icon"><?php echo ($gw['channel'] ?? 'alipay') === 'wechat' ? '💚' : '💰'; ?></div>
<div style="margin:6px 0">订单号 <b><?php echo e($order['order_no']); ?></b></div>
<a class="btn btn-primary magnetic" href="<?php echo e($gw['simulate']); ?>">模拟支付成功</a>
</div>
<?php endif; ?>
<?php endif; ?>
<p style="margin-top:20px"><a href="<?php echo site_url('order/query'); ?>">查询我的订单</a></p>
</div>
</div>
+49
View File
@@ -0,0 +1,49 @@
<?php
/** @var array $orders 每项: ['order' => array, 'payments' => array] */
/** @var string $no */
/** @var string $name */
/** @var string $phone */
/** @var string $err */
?>
<section class="page-hero"><div class="container"><h1>订单查询</h1><p>凭 客户名 + 手机号 核验身份,即可查询您的订单与付款信息(也可加订单号精确查单笔)</p></div></section>
<div class="container">
<div class="form-card">
<form method="post" action="<?php echo site_url('order/query'); ?>">
<?php echo csrf_field(); ?>
<div class="field"><label>客户名(姓名/单位)*</label><input name="name" value="<?php echo e($name); ?>" placeholder="下单时填写的姓名或单位" required></div>
<div class="field"><label>手机号 *</label><input name="phone" value="<?php echo e($phone); ?>" placeholder="下单时填写的手机号" required></div>
<div class="field"><label>订单号(选填)</label><input name="order_no" value="<?php echo e($no); ?>" placeholder="如留空则列出该客户名下全部订单"></div>
<button class="btn btn-primary" type="submit">查询</button>
</form>
<?php if ($err): ?><div class="alert alert-err" style="margin-top:18px"><?php echo e($err); ?></div><?php endif; ?>
<?php if ($orders): ?>
<div class="alert alert-ok" style="margin-top:18px">✓ 共找到 <?php echo count($orders); ?> 笔订单</div>
<?php foreach ($orders as $item): ?>
<?php $o = $item['order']; $ps = $item['payments']; ?>
<div class="order-card">
<table class="spec-table">
<tr><td>订单号</td><td><?php echo e($o['order_no']); ?></td></tr>
<tr><td>商品</td><td><?php echo e($o['product_name']); ?></td></tr>
<tr><td>数量</td><td><?php echo e($o['qty']); ?> 套</td></tr>
<tr><td>金额</td><td>¥<?php echo e($o['amount']); ?></td></tr>
<tr><td>状态</td><td><?php echo $o['status'] === 'paid' ? '<span class="ok">已支付</span>' : '<span class="pend">待支付</span>'; ?></td></tr>
</table>
<?php if ($o['status'] !== 'paid'): ?>
<a class="btn btn-primary" style="margin-top:14px" href="<?php echo site_url('order/pay/' . $o['order_no']); ?>">去支付</a>
<?php endif; ?>
<?php if ($ps): ?>
<h4 style="margin:18px 0 8px">付款记录</h4>
<table class="admin-table">
<thead><tr><th>通道</th><th>交易流水号</th><th>金额</th><th>付款时间</th></tr></thead>
<tbody>
<?php foreach ($ps as $p): ?>
<tr><td><?php echo $p['channel'] === 'wechat' ? '微信支付' : '支付宝'; ?></td><td><?php echo e($p['trade_no']); ?></td><td>¥<?php echo e($p['amount']); ?></td><td><?php echo e($p['paid_at']); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
+35
View File
@@ -0,0 +1,35 @@
<?php
/** @var array $order */
/** @var array $payments */
$ok = $order['status'] === 'paid';
?>
<section class="page-hero"><div class="container"><h1><?php echo $ok ? '支付成功' : '订单已创建'; ?></h1></div></section>
<div class="container">
<div class="form-card">
<div class="alert alert-<?php echo $ok ? 'ok' : 'err'; ?>">
<?php echo $ok ? '✓ 您的付款已成功,我们会尽快与您联系。' : '订单已创建,请完成支付。'; ?>
</div>
<table class="spec-table">
<tr><td>订单号</td><td><?php echo e($order['order_no']); ?></td></tr>
<tr><td>商品</td><td><?php echo e($order['product_name']); ?></td></tr>
<tr><td>数量</td><td><?php echo e($order['qty']); ?> 套</td></tr>
<tr><td>金额</td><td>¥<?php echo e($order['amount']); ?></td></tr>
<tr><td>状态</td><td><?php echo $ok ? '已支付' : '待支付'; ?></td></tr>
<tr><td>下单人</td><td><?php echo e($order['customer_name']); ?><?php echo e($order['phone']); ?></td></tr>
</table>
<?php if ($ok && $payments): ?>
<h4 style="margin:22px 0 10px">付款记录</h4>
<table class="spec-table">
<?php foreach ($payments as $p): ?>
<tr><td>支付通道</td><td><?php echo $p['channel'] === 'wechat' ? '微信支付' : '支付宝'; ?></td></tr>
<tr><td>交易流水号</td><td><?php echo e($p['trade_no']); ?></td></tr>
<tr><td>付款时间</td><td><?php echo e($p['paid_at']); ?></td></tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<div class="form-actions" style="margin-top:20px">
<a class="btn btn-primary" href="<?php echo site_url('order/query'); ?>">查询我的订单</a>
<a class="btn btn-ghost" href="<?php echo site_url(); ?>">返回首页</a>
</div>
</div>
</div>
+24
View File
@@ -0,0 +1,24 @@
<?php
/** @var array $p */
$layout = [];
if (!empty($p['layout'])) {
$dec = json_decode($p['layout'], true);
if (is_array($dec)) $layout = $dec;
}
// 模式渲染:可视化编辑(builder)且有 layout → 画布;否则固定版式正文。
// 旧页面 mode 为空且含 layout 时仍走画布,保持向后兼容。
$useBuilder = !empty($layout) && ($p['mode'] ?? '') !== 'fixed';
?>
<?php if ($useBuilder): ?>
<?php echo \Core\View::buffer('parts/canvas', ['layout' => $layout, 'item' => $p, 'module' => 'page']); ?>
<?php else: ?>
<section class="page-hero">
<div class="container"><h1><?php echo e($p['title']); ?></h1></div>
</section>
<section class="section" style="padding-top:24px">
<div class="container" style="max-width:860px">
<article class="detail-desc" style="font-size:16px;line-height:1.95"><?php echo $p['content']; ?></article>
<div style="text-align:center;margin-top:36px"><a class="btn btn-primary magnetic" href="<?php echo site_url('contact'); ?>">联系我们</a></div>
</div>
</section>
<?php endif; ?>
+78
View File
@@ -0,0 +1,78 @@
<?php
/** @var array $layout */
/** @var array $item */
/** @var string $module */
if (empty($layout) || !is_array($layout)) return;
$slug = $module === 'product' ? ($item['slug'] ?? '') : '';
$price = $module === 'product' ? ($item['price'] ?? 0) : 0;
$specsArr = [];
if ($module === 'product' && !empty($item['specs'])) {
$d = json_decode($item['specs'], true);
if (is_array($d)) $specsArr = $d;
}
?>
<section class="section" style="padding-top:24px">
<div class="container">
<div style="position:relative" id="pbWrap">
<div class="pb-stage pb-front" id="pbFront" style="width:720px;position:relative;background:var(--c-surface,#fff);border:1px solid var(--c-border,#e2e8f0);margin:0 auto;min-height:420px;border-radius:8px;overflow:hidden;box-shadow:0 10px 30px rgba(15,23,42,.08)">
<?php foreach ($layout as $el):
$pos = 'position:absolute;left:' . (int)($el['x'] ?? 0) . 'px;top:' . (int)($el['y'] ?? 0) . 'px;width:' . (int)($el['w'] ?? 200) . 'px;z-index:' . (int)($el['z'] ?? 1) . ';';
$type = $el['type'] ?? 'text';
$txtColor = (empty($el['color']) || ($el['color'] ?? '') === '#0f172a') ? 'var(--c-text,#0f172a)' : e($el['color']);
if ($type === 'image'):
$ai = ($el['align'] ?? 'left') === 'center' ? 'center' : (($el['align'] ?? 'left') === 'right' ? 'flex-end' : 'flex-start');
$jc = !empty($el['v']) ? 'center' : 'flex-start';
?>
<div class="pb-fe" style="<?php echo $pos; ?>display:flex;flex-direction:column;align-items:<?php echo $ai; ?>;justify-content:<?php echo $jc; ?>">
<img src="<?php echo e($el['src']); ?>" alt="" style="object-fit:cover;border-radius:4px;max-width:100%;width:auto;display:block">
</div>
<?php elseif ($type === 'button'): ?>
<a class="pb-fe" href="<?php echo e($el['href'] ?? '#'); ?>" style="<?php echo $pos; ?>display:inline-block;background:<?php echo e($el['bg'] ?? '#0ea5e9'); ?>;color:<?php echo e($el['tc'] ?? '#fff'); ?>;padding:10px 22px;border-radius:10px;text-decoration:none;font-weight:600;font-size:<?php echo ($el['size'] === 'lg' ? '18px' : ($el['size'] === 'sm' ? '13px' : '15px')); ?>"><?php echo e($el['text'] ?? '按钮'); ?></a>
<?php elseif ($type === 'buy'): ?>
<a class="pb-fe" href="<?php echo site_url('order/checkout/' . $slug); ?>" style="<?php echo $pos; ?>display:inline-block;background:#ef4444;color:#fff;padding:12px 28px;border-radius:10px;text-decoration:none;font-weight:700;font-size:16px">立即购买</a>
<?php elseif ($type === 'price'): ?>
<div class="pb-fe" style="<?php echo $pos; ?>text-align:<?php echo e($el['align'] ?? 'left'); ?>;font-size:28px;font-weight:700;color:<?php echo $txtColor; ?>">¥<?php echo e($price); ?> <small style="font-size:14px;font-weight:400">起/套</small></div>
<?php elseif ($type === 'specs'): ?>
<div class="pb-fe" style="<?php echo $pos; ?>text-align:<?php echo e($el['align'] ?? 'left'); ?>">
<table style="width:100%;border-collapse:collapse;font-size:14px">
<?php foreach ($specsArr as $s): ?><tr><td style="padding:8px 10px;border-bottom:1px solid var(--c-border,#eee);color:var(--c-muted,#64748b)"><?php echo e($s['k'] ?? ''); ?></td><td style="padding:8px 10px;border-bottom:1px solid var(--c-border,#eee);font-weight:600;color:var(--c-text,#0f172a)"><?php echo e($s['v'] ?? ''); ?></td></tr><?php endforeach; ?>
</table>
</div>
<?php else: ?>
<div class="pb-fe" style="<?php echo $pos; ?>font-size:<?php echo (int)($el['fontSize'] ?? 18); ?>px;color:<?php echo $txtColor; ?>;font-weight:<?php echo !empty($el['bold']) ? 700 : 400; ?>;text-align:<?php echo e($el['align'] ?? 'left'); ?>;<?php echo !empty($el['v']) ? 'writing-mode:vertical-rl;' : '' ?>line-height:1.4;white-space:pre-wrap;word-break:break-word"><?php echo e($el['text'] ?? ''); ?></div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div>
</section>
<script>
(function () {
var STAGE_W = 720, stage = document.getElementById('pbFront'), wrap = document.getElementById('pbWrap');
if (!stage || !wrap) return;
function maxBottom() {
var m = 420, kids = stage.children;
for (var i = 0; i < kids.length; i++) {
var k = kids[i];
var y = parseInt(k.style.top, 10) || 0;
var b = y + k.offsetHeight;
if (b > m) m = b;
}
return m;
}
function fit() {
var h = maxBottom();
stage.style.height = h + 'px';
var avail = wrap.clientWidth, scale = Math.min(1, avail / STAGE_W);
stage.style.transform = 'scale(' + scale + ')';
stage.style.transformOrigin = 'top left';
wrap.style.width = (STAGE_W * scale) + 'px';
wrap.style.height = (h * scale) + 'px';
wrap.style.margin = '0 auto';
}
fit();
window.addEventListener('resize', fit);
stage.querySelectorAll('img').forEach(function (im) { im.addEventListener('load', fit); });
})();
</script>
+48
View File
@@ -0,0 +1,48 @@
<?php
$title = $cat ? $cat['name'] : '产品中心';
$sub = $cat ? $cat['description'] : '水冷循环 / 相变蓄冷 / 涡扇风冷,覆盖全场景降温需求';
?>
<section class="page-hero">
<div class="container">
<h1><?php echo e($title); ?></h1>
<p><?php echo e($sub); ?></p>
</div>
</section>
<div class="container">
<nav class="breadcrumb"><a href="<?php echo site_url(); ?>">首页</a> / 产品中心</nav>
</div>
<section class="section" style="padding-top:20px">
<div class="container">
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:34px">
<a class="tag" style="<?php echo !$activeCat ? 'background:var(--c-primary);color:#fff' : '' ?>" href="<?php echo site_url('products'); ?>">全部</a>
<?php foreach ($categories as $c): ?>
<a class="tag" style="<?php echo $activeCat == $c['id'] ? 'background:var(--c-primary);color:#fff' : '' ?>" href="<?php echo site_url('products?cat=' . $c['id']); ?>"><?php echo e($c['name']); ?></a>
<?php endforeach; ?>
</div>
<?php if (empty($products)): ?><p style="color:var(--c-muted)">该分类暂无产品。</p><?php endif; ?>
<div class="product-grid">
<?php foreach ($products as $p): ?>
<div class="product-card reveal">
<a class="product-link" href="<?php echo site_url('products/' . $p['slug']); ?>">
<?php if (!empty($p['cover'])): ?>
<div class="product-thumb"><img src="<?php echo e(site_url($p['cover'])); ?>" alt="<?php echo e($p['name']); ?>"></div>
<?php else: ?>
<div class="product-thumb" style="background:<?php echo gradient($p['id']); ?>">❄</div>
<?php endif; ?>
<div class="product-body">
<div class="product-name"><?php echo e($p['name']); ?></div>
<div class="product-sum"><?php echo e($p['summary']); ?></div>
<div class="product-meta">
<div class="product-price">¥<?php echo e($p['price']); ?> <small>起</small></div>
</div>
</div>
</a>
<div class="product-actions">
<a class="btn btn-primary magnetic" href="<?php echo site_url('order/checkout/' . $p['slug']); ?>">立即购买</a>
<a class="btn btn-ghost" href="<?php echo site_url('products/' . $p['slug']); ?>">查看</a>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
+132
View File
@@ -0,0 +1,132 @@
<?php
$layoutArr = [];
if (!empty($p['layout'])) { $d = json_decode($p['layout'], true); if (is_array($d)) $layoutArr = $d; }
// 模式渲染:可视化编辑(builder)且有 layout → 画布;否则固定版式(封面/图集/描述)。
$useBuilder = !empty($layoutArr) && ($p['mode'] ?? '') !== 'fixed';
// 固定版面素材
$cover = $p['cover'] ?? '';
$galleryArr = [];
if (!empty($p['gallery'])) { $dg = json_decode($p['gallery'], true); if (is_array($dg)) $galleryArr = $dg; }
?>
<section class="page-hero">
<div class="container">
<h1><?php echo e($p['name']); ?></h1>
<p><?php echo e($cat['name'] ?? '降温服'); ?></p>
</div>
</section>
<div class="container">
<nav class="breadcrumb">
<a href="<?php echo site_url(); ?>">首页</a> / <a href="<?php echo site_url('products'); ?>">产品中心</a> /
<?php echo e($p['name']); ?>
</nav>
</div>
<?php if ($useBuilder): ?>
<?php echo \Core\View::buffer('parts/canvas', ['layout' => $layoutArr, 'item' => $p, 'module' => 'product']); ?>
<?php else: ?>
<section class="section detail-sec" style="padding-top:20px">
<div class="container">
<div class="detail-wrap">
<div>
<?php if ($cover): ?>
<div class="detail-thumb"><img class="detail-cover" src="<?php echo e(site_url($cover)); ?>" alt="<?php echo e($p['name']); ?>"></div>
<?php else: ?>
<div class="detail-thumb" style="background:<?php echo gradient($p['id']); ?>">❄</div>
<?php endif; ?>
<div class="gallery">
<?php if (!empty($galleryArr)): ?>
<?php foreach ($galleryArr as $gi): ?><div class="detail-thumb gal"><img src="<?php echo e(site_url($gi)); ?>" alt=""></div><?php endforeach; ?>
<?php else: ?>
<div class="detail-thumb" style="aspect-ratio:1/1;font-size:28px;background:<?php echo gradient($p['id'] + 1); ?>">❄</div>
<div class="detail-thumb" style="aspect-ratio:1/1;font-size:28px;background:<?php echo gradient($p['id'] + 2); ?>">❄</div>
<div class="detail-thumb" style="aspect-ratio:1/1;font-size:28px;background:<?php echo gradient($p['id'] + 3); ?>">❄</div>
<?php endif; ?>
</div>
</div>
<div class="detail-info">
<h1><?php echo e($p['name']); ?></h1>
<div class="detail-price">¥<?php echo e($p['price']); ?> <small style="font-size:14px;color:var(--c-muted)">起 / 套</small></div>
<p class="detail-sum"><?php echo e($p['summary']); ?></p>
<?php if (!empty($specs)): ?>
<table class="spec-table">
<?php foreach ($specs as $s): ?><tr><td><?php echo e($s['k']); ?></td><td><?php echo e($s['v']); ?></td></tr><?php endforeach; ?>
</table>
<?php endif; ?>
<?php if (!empty($p['description'])): ?><div class="detail-desc detail-rich"><?php echo $p['description']; ?></div><?php endif; ?>
<div style="display:flex;gap:12px;margin-top:24px;flex-wrap:wrap">
<a class="btn btn-primary magnetic" href="<?php echo site_url('order/checkout/' . $p['slug']); ?>">立即购买</a>
<a class="btn btn-ghost" href="<?php echo site_url('contact'); ?>">咨询报价</a>
<a class="btn btn-ghost" href="<?php echo site_url('products'); ?>">返回列表</a>
</div>
</div>
</div>
</div>
</section>
<?php endif; ?>
<?php if (!empty($faqs)): ?>
<section class="section" style="padding-top:10px">
<div class="container">
<div class="section-head reveal">
<p class="eyebrow">常见问题</p>
<h2 class="section-title">关于本品,您可能想了解</h2>
</div>
<div class="faq-list">
<?php foreach ($faqs as $f): ?>
<details class="faq-item reveal">
<summary><?php echo e($f['q']); ?><span class="faq-ico">+</span></summary>
<div class="faq-a"><?php echo e($f['a']); ?></div>
</details>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>
<?php if (!empty($related)): ?>
<section class="section" style="padding-top:30px">
<div class="container">
<h3 style="margin:0 0 20px;font-size:22px">相关推荐</h3>
<div class="product-grid">
<?php foreach ($related as $r): ?>
<a class="product-card" href="<?php echo site_url('products/' . $r['slug']); ?>">
<?php if (!empty($r['cover'])): ?>
<div class="product-thumb"><img src="<?php echo e(site_url($r['cover'])); ?>" alt="<?php echo e($r['name']); ?>"></div>
<?php else: ?>
<div class="product-thumb" style="background:<?php echo gradient($r['id']); ?>">❄</div>
<?php endif; ?>
<div class="product-body">
<div class="product-name"><?php echo e($r['name']); ?></div>
<div class="product-sum"><?php echo e($r['summary']); ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>
<!-- 吸底购买栏:始终展示价格与立即购买 -->
<div class="buy-bar">
<div class="container buy-bar-inner">
<div class="buy-bar-info">
<span class="buy-bar-name"><?php echo e($p['name']); ?></span>
<span class="buy-bar-price">¥<?php echo e($p['price']); ?> <small>起 / 套</small></span>
</div>
<div class="buy-bar-actions">
<a class="btn btn-primary magnetic" href="<?php echo site_url('order/checkout/' . $p['slug']); ?>">立即购买</a>
<a class="btn btn-ghost" href="<?php echo site_url('contact'); ?>">咨询报价</a>
</div>
</div>
</div>
<style>
.detail-cover{width:100%;height:100%;object-fit:cover;display:block;border-radius:var(--radius)}
.detail-thumb.gal img{width:100%;height:100%;object-fit:cover;display:block;border-radius:var(--radius)}
.detail-rich img{max-width:100%;height:auto;display:block;border-radius:8px;margin:10px 0}
.detail-rich h2{font-size:24px;margin:.6em 0 .4em}
.detail-rich h3{font-size:20px;margin:.6em 0 .4em}
.detail-rich blockquote{margin:.6em 0;padding:8px 14px;border-left:4px solid var(--c-primary);color:var(--c-muted);background:rgba(14,165,233,.06)}
.detail-rich a{color:var(--c-primary)}
</style>
+23
View File
@@ -0,0 +1,23 @@
<?php
/** PSI 手动调整库存(盘盈/盘亏/报损) */
$isMat = $type === 'material';
$label = $isMat ? '物料' : '成品';
?>
<div class="page-head"><h1>调整库存 — <?php echo e($item['name']); ?></h1></div>
<div class="panel">
<p>当前库存:<b><?php echo e((float)($item['stock'] ?? 0)); ?></b></p>
<form method="post" action="<?php echo site_url('PSI/' . ($isMat ? 'materials' : 'products') . '/adjust/' . $item['id']); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>方向</label>
<select name="direction">
<option value="in">入库 +(盘盈)</option>
<option value="out">出库 -(盘亏/报损)</option>
</select>
</div>
<div class="field"><label>数量 *</label><input name="qty" type="number" step="0.01" required></div>
</div>
<button class="btn-primary" type="submit">确认调整</button>
<a class="btn-soft" href="<?php echo site_url('PSI/' . ($isMat ? 'materials' : 'products')); ?>">取消</a>
</form>
</div>
+144
View File
@@ -0,0 +1,144 @@
<?php
/** PSI 进销存仪表盘 */
?>
<div class="page-head">
<h1>进销存仪表盘</h1>
<p class="sub">服装生产 · 进出口贸易 · 终端销售 一体化库存</p>
</div>
<div class="stat-grid">
<div class="stat-card"><div class="stat-num"><?php echo e($materialTotal); ?></div><div class="stat-label">物料种类</div></div>
<div class="stat-card"><div class="stat-num"><?php echo e($productTotal); ?></div><div class="stat-label">成品种类</div></div>
<div class="stat-card"><div class="stat-num"><?php echo e(number_format($matStock, 0)); ?></div><div class="stat-label">物料库存量</div></div>
<div class="stat-card"><div class="stat-num"><?php echo e(number_format($prodStock, 0)); ?></div><div class="stat-label">成品库存量</div></div>
<div class="stat-card"><div class="stat-num">¥<?php echo e(number_format($purchaseAmt, 0)); ?></div><div class="stat-label">采购总额</div></div>
<div class="stat-card"><div class="stat-num">¥<?php echo e(number_format($salesAmt, 0)); ?></div><div class="stat-label">销售总额</div></div>
</div>
<!-- 订单业务看板 -->
<div class="stat-grid">
<a class="stat-card" href="<?php echo site_url('PSI/reports/soPo?type=sales'); ?>" style="text-decoration:none">
<div class="stat-ic"><?php echo admin_icon('trending-up', 20); ?></div>
<div class="stat-num">¥<?php echo e(number_format($salesAmount, 0)); ?></div><div class="stat-label">订单销售额(累计)</div>
</a>
<a class="stat-card" href="<?php echo site_url('PSI/reports/soPo?type=sales'); ?>" style="text-decoration:none">
<div class="stat-ic"><?php echo admin_icon('trending-up', 20); ?></div>
<div class="stat-num">¥<?php echo e(number_format($salesAmountMonth, 0)); ?></div><div class="stat-label">当月订单销售额</div>
</a>
<a class="stat-card" href="<?php echo site_url('PSI/reports/delivery'); ?>" style="text-decoration:none">
<div class="stat-ic" style="color:#f59e0b"><?php echo admin_icon('alert', 20); ?></div>
<div class="stat-num" style="color:#f59e0b"><?php echo e($undelivered['count']); ?> <small>单 / ¥<?php echo e(number_format($undelivered['amount'], 0)); ?></small></div>
<div class="stat-label">未交订单</div>
</a>
<a class="stat-card" href="<?php echo site_url('PSI/reports/delivery'); ?>" style="text-decoration:none">
<div class="stat-ic" style="color:#10b981"><?php echo admin_icon('check', 20); ?></div>
<div class="stat-num" style="color:#10b981"><?php echo e($monthDeliveries['count']); ?> <small>单 / ¥<?php echo e(number_format($monthDeliveries['amount'], 0)); ?></small></div>
<div class="stat-label">当月交付订单</div>
</a>
</div>
<div class="panel">
<div class="panel-bar"><h3>按业务员(订单销售额)</h3>
<a class="btn-sm btn-ghost" href="<?php echo site_url('PSI/reports/soPo?type=sales'); ?>">查看明细</a></div>
<?php if (empty($bySalesman)): ?><p class="muted">暂无销售订单数据</p><?php else: ?>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>业务员</th><th>订单数</th><th>销售额</th></tr></thead>
<tbody>
<?php foreach ($bySalesman as $r): ?>
<tr><td><?php echo e($r['salesman']); ?></td><td><?php echo e($r['orders']); ?></td>
<td>¥<?php echo e(number_format((float)($r['amount'] ?? 0), 2)); ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<div class="quick-actions">
<a class="qa-card" href="<?php echo site_url('PSI/materials/create'); ?>">
<span class="qa-ico">🧵</span><span class="qa-t">录入物料</span><span class="qa-d">登记面料/辅料/纱线</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/products/create'); ?>">
<span class="qa-ico">👕</span><span class="qa-t">录入成品</span><span class="qa-d">款号/颜色/尺码</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/purchases/create'); ?>">
<span class="qa-ico">📥</span><span class="qa-t">采购入库</span><span class="qa-d">生成入库单与流水</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/sales/create'); ?>">
<span class="qa-ico">📤</span><span class="qa-t">销售出库</span><span class="qa-d">生成出库单与流水</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/sales_orders/create'); ?>">
<span class="qa-ico">📝</span><span class="qa-t">销售订单</span><span class="qa-d">录入并可打印</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/purchase_orders/create'); ?>">
<span class="qa-ico">📋</span><span class="qa-t">采购订单</span><span class="qa-d">录入并收货入库</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/outbounds/create'); ?>">
<span class="qa-ico">🚚</span><span class="qa-t">出库单</span><span class="qa-d">交付并扣库存</span>
</a>
<a class="qa-card" href="<?php echo site_url('PSI/reports'); ?>">
<span class="qa-ico">📊</span><span class="qa-t">报表中心</span><span class="qa-d">订单/交付明细</span>
</a>
</div>
<div class="two-col">
<div class="panel">
<h3>最近采购入库</h3>
<?php if (empty($recentPurchases)): ?><p class="muted">暂无采购单</p><?php else: ?>
<table class="tbl">
<thead><tr><th>单号</th><th>物料/成品</th><th>数量</th><th>金额</th><th>日期</th></tr></thead>
<tbody>
<?php foreach ($recentPurchases as $p): ?>
<tr>
<td><?php echo e($p['no'] ?? $p['id'] ?? ''); ?></td>
<td><?php echo e($p['name'] ?? ''); ?></td>
<td><?php echo e((float)($p['qty'] ?? 0)); ?></td>
<td>¥<?php echo e(number_format((float)($p['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($p['created_at'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="panel">
<h3>最近销售出库</h3>
<?php if (empty($recentSales)): ?><p class="muted">暂无销售单</p><?php else: ?>
<table class="tbl">
<thead><tr><th>单号</th><th>物料/成品</th><th>数量</th><th>金额</th><th>日期</th></tr></thead>
<tbody>
<?php foreach ($recentSales as $s): ?>
<tr>
<td><?php echo e($s['no'] ?? $s['id'] ?? ''); ?></td>
<td><?php echo e($s['name'] ?? ''); ?></td>
<td><?php echo e((float)($s['qty'] ?? 0)); ?></td>
<td>¥<?php echo e(number_format((float)($s['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($s['created_at'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<div class="panel">
<h3>库存预警(低于 20</h3>
<?php if (empty($lowStock)): ?>
<p class="muted">当前库存充足 ✅</p>
<?php else: ?>
<table class="tbl">
<thead><tr><th>名称</th><th>类型</th><th>当前库存</th></tr></thead>
<tbody>
<?php foreach ($lowStock as $x): ?>
<tr>
<td><?php echo e($x['item']['name']); ?></td>
<td><?php echo e($x['type']); ?></td>
<td style="color:#dc2626;font-weight:700"><?php echo e((float)($x['item']['stock'] ?? 0)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
+45
View File
@@ -0,0 +1,45 @@
<?php
/** PSI 物料表单(面辅料) */
$m = $material;
$isEdit = !empty($m);
$catOpts = ['面料' => '面料', '辅料' => '辅料', '纱线' => '纱线', '包装' => '包装', '其他' => '其他'];
$curCat = $m['category'] ?? '面料';
if ($curCat && !isset($catOpts[$curCat])) { $catOpts[$curCat] = $curCat; }
?>
<div class="page-head"><h1><?php echo $isEdit ? '编辑物料' : '新增物料'; ?></h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'PSI/materials/update/' . $m['id'] : 'PSI/materials/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>编码</label><input name="code" value="<?php echo e($m['code'] ?? ''); ?>"></div>
<div class="field"><label>名称 *</label><input name="name" value="<?php echo e($m['name'] ?? ''); ?>" required></div>
<div class="field"><label>分类</label>
<select name="category">
<?php foreach ($catOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curCat === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>规格</label><input name="spec" value="<?php echo e($m['spec'] ?? ''); ?>"></div>
<div class="field"><label>单位</label><input name="unit" value="<?php echo e($m['unit'] ?? '个'); ?>"></div>
<div class="field"><label>颜色</label><input name="color" value="<?php echo e($m['color'] ?? ''); ?>"></div>
<div class="field"><label>成分</label><input name="composition" value="<?php echo e($m['composition'] ?? ''); ?>" placeholder="如:100%棉 / 65%涤35%棉"></div>
<div class="field"><label>克重(g/m²)</label><input name="weight_gsm" type="number" step="0.01" value="<?php echo e($m['weight_gsm'] ?? '0'); ?>"></div>
<div class="field"><label>幅宽(cm)</label><input name="width_cm" type="number" step="0.01" value="<?php echo e($m['width_cm'] ?? '0'); ?>"></div>
<div class="field"><label>缸号/批号</label><input name="batch_no" value="<?php echo e($m['batch_no'] ?? ''); ?>"></div>
<div class="field"><label>初始库存</label><input name="stock" type="number" step="0.01" value="<?php echo e($m['stock'] ?? '0'); ?>"></div>
<div class="field"><label>单价</label><input name="price" type="number" step="0.01" value="<?php echo e($m['price'] ?? '0'); ?>"></div>
<div class="field"><label>默认供应商</label>
<select name="supplier_id">
<option value="0">无</option>
<?php foreach ($suppliers as $s): ?>
<option value="<?php echo e($s['id']); ?>" <?php echo ($m['supplier_id'] ?? 0) == $s['id'] ? 'selected' : ''; ?>><?php echo e($s['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="2"><?php echo e($m['remark'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url('PSI/materials'); ?>">取消</a>
</form>
</div>
+38
View File
@@ -0,0 +1,38 @@
<?php
/** PSI 物料列表(面辅料) */
?>
<div class="page-head">
<h1>物料管理</h1>
<a class="btn-primary" href="<?php echo site_url('PSI/materials/create'); ?>">+ 新增物料</a>
</div>
<div class="panel">
<table class="tbl">
<thead><tr><th>ID</th><th>编码</th><th>名称</th><th>规格</th><th>单位</th><th>分类</th><th>颜色</th><th>成分</th><th>克重(g)</th><th>幅宽(cm)</th><th>缸号</th><th>库存</th><th>单价</th><th>供应商</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($materials as $m): ?>
<tr>
<td><?php echo e($m['id']); ?></td>
<td><?php echo e($m['code'] ?? ''); ?></td>
<td><?php echo e($m['name']); ?></td>
<td><?php echo e($m['spec'] ?? ''); ?></td>
<td><?php echo e($m['unit'] ?? '个'); ?></td>
<td><?php echo e($m['category'] ?? ''); ?></td>
<td><?php echo e($m['color'] ?? ''); ?></td>
<td><?php echo e($m['composition'] ?? ''); ?></td>
<td><?php echo e((float)($m['weight_gsm'] ?? 0)); ?></td>
<td><?php echo e((float)($m['width_cm'] ?? 0)); ?></td>
<td><?php echo e($m['batch_no'] ?? ''); ?></td>
<td><b><?php echo e((float)($m['stock'] ?? 0)); ?></b></td>
<td>¥<?php echo e(number_format((float)($m['price'] ?? 0), 2)); ?></td>
<td><?php echo e($smap[$m['supplier_id']] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/materials/adjust/' . $m['id']); ?>">调整库存</a>
<a href="<?php echo site_url('PSI/materials/edit/' . $m['id']); ?>">编辑</a>
<a href="<?php echo site_url('PSI/materials/destroy/' . $m['id']); ?>" data-confirm="确认删除?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($materials)): ?><tr><td colspan="15" class="muted">暂无物料,点击右上角新增</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+46
View File
@@ -0,0 +1,46 @@
<?php
/** @var array $v */
$checked = fn($k) => !empty($v[$k]) ? 'checked' : '';
?>
<div class="page-head">
<div><h2>通知设置</h2><p class="muted">配置“新订单 / 新事件”的负责人通知渠道(邮件 + 企业微信)。</p></div>
</div>
<form method="post" action="<?php echo \Core\App::url('PSI/notifications/save'); ?>" class="form-card notify-form">
<?php echo csrf_field(); ?>
<div class="form-section">
<label class="check"><input type="checkbox" name="notify_enabled" <?php echo $checked('notify_enabled'); ?>> <b>启用事件通知</b></label>
<small>关闭后仅记录站内紧急事件,不发送邮件 / 微信。</small>
</div>
<fieldset class="form-section">
<legend>邮件通知</legend>
<label class="check"><input type="checkbox" name="notify_email_enabled" <?php echo $checked('notify_email_enabled'); ?>> 启用邮件</label>
<div class="form-row"><label>SMTP 服务器</label><input type="text" name="notify_email_smtp_host" value="<?php echo e($v['notify_email_smtp_host'] ?? ''); ?>" placeholder="如 smtp.exmail.qq.com(留空则使用系统 mail()"></div>
<div class="form-row"><label>端口</label><input type="text" name="notify_email_smtp_port" value="<?php echo e($v['notify_email_smtp_port'] ?? '465'); ?>" placeholder="465=SSL587=STARTTLS"></div>
<div class="form-row"><label>账号</label><input type="text" name="notify_email_smtp_user" value="<?php echo e($v['notify_email_smtp_user'] ?? ''); ?>" placeholder="发信邮箱/账号"></div>
<div class="form-row"><label>密码 / 授权码</label><input type="password" name="notify_email_smtp_pass" value="<?php echo e($v['notify_email_smtp_pass'] ?? ''); ?>" autocomplete="new-password"></div>
<div class="form-row"><label>发件人</label><input type="text" name="notify_email_from" value="<?php echo e($v['notify_email_from'] ?? ''); ?>" placeholder="留空则用上面的账号"></div>
<div class="form-row"><label>负责人邮箱(逗号分隔)</label><input type="text" name="notify_email_to" value="<?php echo e($v['notify_email_to'] ?? ''); ?>" placeholder="a@qq.com, b@qq.com"></div>
</fieldset>
<fieldset class="form-section">
<legend>企业微信通知</legend>
<label class="check"><input type="checkbox" name="notify_wechat_enabled" <?php echo $checked('notify_wechat_enabled'); ?>> 启用企业微信(群机器人)</label>
<div class="form-row"><label>群机器人 Webhook 地址</label><input type="text" name="notify_wechat_webhook" value="<?php echo e($v['notify_wechat_webhook'] ?? ''); ?>" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."></div>
<div class="form-row"><label>被 @ 的手机号/英文 id(逗号分隔)</label><input type="text" name="notify_wechat_mention" value="<?php echo e($v['notify_wechat_mention'] ?? ''); ?>" placeholder="138..., 139...(留空则不@"></div>
<small>提示:在企业微信群中添加「群机器人」即可获得 webhook 地址。</small>
</fieldset>
<fieldset class="form-section">
<legend>低库存预警</legend>
<label class="check"><input type="checkbox" name="notify_lowstock_enabled" <?php echo $checked('notify_lowstock_enabled'); ?>> 库存跌破阈值时通知</label>
<div class="form-row"><label>预警阈值</label><input type="text" name="notify_lowstock_threshold" value="<?php echo e($v['notify_lowstock_threshold'] ?? '20'); ?>" placeholder="如 20"></div>
</fieldset>
<div class="form-actions">
<button class="btn-primary" type="submit">保存设置</button>
<a class="btn-ghost" href="<?php echo \Core\App::url('PSI/reminders'); ?>">查看紧急提醒</a>
</div>
</form>
+56
View File
@@ -0,0 +1,56 @@
<?php
/** PSI 订单详情(合并自后台订单详情) */
/** @var array $o @var array $payments */
$canManage = \subsys_admin('psi');
$channelLabel = function ($c) {
return $c === 'wechat' ? '微信' : ($c === 'alipay' ? '支付宝' : '—');
};
?>
<div class="page-head">
<div><h1>订单详情</h1><div class="desc">订单号 <?php echo e($o['order_no']); ?></div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/orders'); ?>">← 返回列表</a>
</div>
<?php echo flash_html(); ?>
<div class="panel">
<table class="tbl">
<tr><th>商品</th><td><?php echo e($o['product_name']); ?></td></tr>
<tr><th>客户</th><td><?php echo e($o['customer_name']); ?><?php echo e($o['phone']); ?><?php if (!empty($o['email'])): ?>/ <?php echo e($o['email']); ?><?php endif; ?></td></tr>
<tr><th>数量</th><td><?php echo e($o['qty']); ?> 套</td></tr>
<tr><th>金额</th><td>¥<?php echo e(number_format((float)($o['amount'] ?? 0), 2)); ?></td></tr>
<tr><th>支付通道</th><td><?php echo $channelLabel($o['channel']); ?></td></tr>
<tr><th>状态</th><td>
<?php echo $o['status'] === 'paid'
? '<span class="tag-mini" style="background:#dcfce7;color:#15803d">已支付</span>'
: '<span class="tag-mini" style="background:#fef3c7;color:#b45309">待支付</span>'; ?>
<?php if (!empty($o['paid_at'])): ?><span class="muted"><?php echo e($o['paid_at']); ?></span><?php endif; ?>
</td></tr>
<tr><th>网关交易号</th><td><?php echo e($o['gateway_trade_no'] ?? '—'); ?></td></tr>
<tr><th>下单时间</th><td><?php echo e($o['created_at']); ?></td></tr>
</table>
<?php if ($canManage && $o['status'] !== 'paid'): ?>
<form method="post" action="<?php echo site_url('PSI/orders/markPaid/' . $o['id']); ?>" style="margin-top:16px">
<?php echo csrf_field(); ?><button class="btn-primary" type="submit" data-confirm="标记为已支付?">标记为已支付</button>
</form>
<?php endif; ?>
<?php if ($payments): ?>
<h3 style="margin:24px 0 10px">付款记录</h3>
<table class="tbl">
<thead><tr><th>通道</th><th>交易号</th><th>金额</th><th>状态</th><th>付款时间</th></tr></thead>
<tbody>
<?php foreach ($payments as $p): ?>
<tr>
<td><?php echo $p['channel'] === 'wechat' ? '微信' : '支付宝'; ?></td>
<td><?php echo e($p['trade_no']); ?></td>
<td>¥<?php echo e(number_format((float)($p['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($p['status']); ?></td>
<td><?php echo e($p['paid_at']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
+63
View File
@@ -0,0 +1,63 @@
<?php
/** PSI 订单管理(合并自后台订单) */
/** @var array $orders */
$canManage = \subsys_admin('psi');
$statusTag = function ($s) {
return $s === 'paid'
? '<span class="tag-mini" style="background:#dcfce7;color:#15803d">已支付</span>'
: '<span class="tag-mini" style="background:#fef3c7;color:#b45309">待支付</span>';
};
$channelLabel = function ($c) {
return $c === 'wechat' ? '微信' : ($c === 'alipay' ? '支付宝' : '—');
};
?>
<div class="page-head">
<div><h1>订单管理</h1><div class="desc">客户下单与付款处理(合并自后台订单,便于在 PSI 内快速处理)</div></div>
</div>
<?php echo flash_html(); ?>
<div class="panel">
<div class="panel-bar">
<h3>订单列表</h3>
<span class="muted">共 <?php echo count($orders); ?> 笔</span>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr>
<th>订单号</th><th>商品</th><th>客户</th><th>数量</th><th>金额</th>
<th>通道</th><th>状态</th><th>下单时间</th><th>操作</th>
</tr></thead>
<tbody>
<?php foreach ($orders as $o): ?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo e($o['product_name']); ?></td>
<td><?php echo e($o['customer_name']); ?><br><span class="muted"><?php echo e($o['phone']); ?></span></td>
<td><?php echo e($o['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($o['amount'] ?? 0), 2)); ?></td>
<td><?php echo $channelLabel($o['channel']); ?></td>
<td><?php echo $statusTag($o['status']); ?></td>
<td class="muted"><?php echo e($o['created_at']); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/orders/show/' . $o['id']); ?>">详情</a>
<?php if ($canManage && $o['status'] !== 'paid'): ?>
<form method="post" class="inline-form" action="<?php echo site_url('PSI/orders/markPaid/' . $o['id']); ?>" data-confirm="标记为已支付?">
<?php echo csrf_field(); ?><button type="submit" class="link-ok">标记支付</button>
</form>
<?php endif; ?>
<?php if ($canManage): ?>
<form method="post" class="inline-form" action="<?php echo site_url('PSI/orders/destroy/' . $o['id']); ?>" data-confirm="确认删除该订单?">
<?php echo csrf_field(); ?><button type="submit" class="link-danger">删除</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($orders)): ?>
<tr><td colspan="9" class="muted" style="text-align:center;padding:24px">暂无订单</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
+131
View File
@@ -0,0 +1,131 @@
<?php
/** PSI 出库单录入/编辑 */
/** @var array|null $o @var array $items @var array|null $so @var array $soItems @var array $products */
$isEdit = !empty($o);
$prodMap = [];
foreach ($products as $p) { $prodMap[(int)$p['id']] = ['name' => $p['name'], 'spec' => $p['spec'] ?? '', 'unit' => $p['unit'] ?? '', 'price' => (float)($p['price'] ?? 0)]; }
// 行数据来源:编辑=出库明细;新建且带销售订单=销售订单未交明细;否则空行
$rows = [];
if ($isEdit) {
foreach ($items as $it) $rows[] = ['so_item_id' => $it['so_item_id'], 'product_id' => $it['product_id'], 'name' => $it['name'], 'spec' => $it['spec'], 'unit' => $it['unit'], 'qty' => $it['qty'], 'price' => $it['price']];
} elseif ($so) {
foreach ($soItems as $si) { $remain = max(0, (int)($si['qty'] ?? 0) - (int)($si['delivered_qty'] ?? 0)); if ($remain <= 0) continue;
$rows[] = ['so_item_id' => $si['id'], 'product_id' => $si['product_id'], 'name' => $si['name'], 'spec' => $si['spec'], 'unit' => $si['unit'], 'qty' => $remain, 'price' => $si['price']];
}
}
?>
<div class="page-head">
<div><h1><?php echo $isEdit ? '编辑出库单' : '新建出库单'; ?></h1>
<div class="desc"><?php echo $so ? ('关联销售订单:<b>' . e($so['order_no']) . '</b> · 已预填未交明细') : '填写出库明细,保存后自动扣减库存'; ?></div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/outbounds'); ?>">← 返回</a>
</div>
<div class="panel form-card">
<form method="post" action="<?php echo site_url($isEdit ? ('PSI/outbounds/update/' . $o['id']) : 'PSI/outbounds/store'); ?>">
<?php echo csrf_field(); ?>
<input type="hidden" name="so_no" value="<?php echo e($o['so_no'] ?? ($so['order_no'] ?? '')); ?>">
<div class="form-row">
<label>关联销售订单</label>
<input type="text" value="<?php echo e($o['so_no'] ?? ($so['order_no'] ?? '')); ?>" readonly placeholder="可选,留空为独立出库">
</div>
<div class="form-row">
<label>客户 <span class="req">*</span></label>
<input type="text" name="customer" value="<?php echo e($o['customer'] ?? ($so['customer'] ?? '')); ?>" required>
</div>
<div class="form-row">
<label>业务员</label>
<input type="text" name="salesman" value="<?php echo e($o['salesman'] ?? ($so['salesman'] ?? ($_SESSION['admin_name'] ?? ''))); ?>">
</div>
<div class="form-row">
<label>仓库</label>
<input type="text" name="warehouse" value="<?php echo e($o['warehouse'] ?? ''); ?>" placeholder="如:主仓">
</div>
<div class="form-row">
<label>出库日期</label>
<input type="date" name="delivery_date" value="<?php echo e($o['delivery_date'] ?? date('Y-m-d')); ?>">
</div>
<div class="form-row">
<label>备注</label>
<textarea name="remark" rows="2"><?php echo e($o['remark'] ?? ''); ?></textarea>
</div>
<div class="form-row">
<label>出库明细 <span class="req">*</span></label>
<div class="table-wrap">
<table class="tbl" id="itemTbl">
<thead><tr>
<th style="width:150px">关联成品</th><th>名称</th><th>规格</th><th>单位</th>
<th>数量</th><th>单价</th><th>金额</th><th></th>
</tr></thead>
<tbody id="itemBody"></tbody>
</table>
</div>
<button type="button" class="btn" id="addRow" style="margin-top:10px"><?php echo admin_icon('plus', 16); ?> 添加明细</button>
</div>
<div class="form-row" style="margin-bottom:14px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:normal">
<input type="checkbox" name="auto_print" value="1" checked style="width:auto;margin:0"> 保存后打印预览
</label>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">保存并出库</button>
<a class="btn-ghost" href="<?php echo site_url('PSI/outbounds'); ?>">取消</a>
</div>
</form>
</div>
<script>
const PROD = <?php echo json_encode($prodMap, JSON_UNESCAPED_UNICODE); ?>;
let ROW = 0;
function newRow(p){
p = p || {};
const tr = document.createElement('tr');
tr.className='item-row';
tr.innerHTML = `
<td><select class="prod-sel" style="width:100%">
<option value="0">手动输入</option>
${Object.keys(PROD).map(id=>`<option value="${id}">${PROD[id].name}</option>`).join('')}
</select>
<input type="hidden" name="items[${ROW}][so_item_id]" value="${p.so_item_id||0}">
<input type="hidden" name="items[${ROW}][product_id]" class="pid" value="${p.product_id||0}"></td>
<td><input type="text" name="items[${ROW}][name]" value="${p.name||''}" required style="width:100%"></td>
<td><input type="text" name="items[${ROW}][spec]" value="${p.spec||''}" style="width:90px"></td>
<td><input type="text" name="items[${ROW}][unit]" value="${p.unit||''}" style="width:50px"></td>
<td><input type="number" min="1" name="items[${ROW}][qty]" value="${p.qty||1}" class="qty" style="width:64px"></td>
<td><input type="number" step="0.01" min="0" name="items[${ROW}][price]" value="${p.price||0}" class="price" style="width:84px"></td>
<td class="amt" style="white-space:nowrap">¥0.00</td>
<td><button type="button" class="row-del" title="删除">×</button></td>`;
ROW++;
tr.querySelector('.prod-sel').addEventListener('change', e=>{ const id=e.target.value;
if(PROD[id]){ const d=PROD[id];
tr.querySelector('input[name$="[name]"]').value=d.name;
tr.querySelector('input[name$="[spec]"]').value=d.spec;
tr.querySelector('input[name$="[unit]"]').value=d.unit;
tr.querySelector('input[name$="[price]"]').value=d.price; calc(tr);
tr.querySelector('.pid').value=id;
}});
tr.querySelector('.qty').addEventListener('input', ()=>calc(tr));
tr.querySelector('.price').addEventListener('input', ()=>calc(tr));
tr.querySelector('.row-del').addEventListener('click', ()=>tr.remove());
return tr;
}
function calc(tr){ const q=parseFloat(tr.querySelector('.qty').value)||0,p=parseFloat(tr.querySelector('.price').value)||0; tr.querySelector('.amt').textContent='¥'+(q*p).toFixed(2); }
document.getElementById('addRow').addEventListener('click', ()=>document.getElementById('itemBody').appendChild(newRow()));
<?php if (!empty($rows)): ?>
<?php foreach ($rows as $r): ?>
document.getElementById('itemBody').appendChild(newRow({
so_item_id:<?php echo (int)($r['so_item_id'] ?? 0); ?>,
product_id:<?php echo (int)($r['product_id'] ?? 0); ?>,
name:<?php echo json_encode($r['name'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
spec:<?php echo json_encode($r['spec'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
unit:<?php echo json_encode($r['unit'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
qty:<?php echo (int)($r['qty'] ?? 1); ?>,
price:<?php echo (float)($r['price'] ?? 0); ?>
}));
<?php endforeach; ?>
<?php else: ?>
document.getElementById('itemBody').appendChild(newRow());
<?php endif; ?>
</script>
+32
View File
@@ -0,0 +1,32 @@
<?php
/** PSI 出库单打印正文 */
/** @var array $o @var array $items */
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $items));
?>
<h2>出库单</h2>
<div class="sub"><?php echo e($o['order_no']); ?></div>
<div class="meta">
<span><b>关联销售订单:</b><?php echo $o['so_no'] ? e($o['so_no']) : '—'; ?></span>
<span><b>客户:</b><?php echo e($o['customer']); ?></span>
<span><b>业务员:</b><?php echo e($o['salesman']); ?></span>
<span><b>仓库:</b><?php echo e($o['warehouse']); ?></span>
<span><b>出库日期:</b><?php echo e($o['delivery_date'] ?? '—'); ?></span>
</div>
<table>
<thead><tr><th>商品</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th></tr></thead>
<tbody>
<?php foreach ($items as $it): ?>
<tr>
<td><?php echo e($it['name']); ?></td>
<td><?php echo e($it['spec']); ?></td>
<td><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><th colspan="5" style="text-align:right">合计</th><th>¥<?php echo e(number_format($amt, 2)); ?></th></tr></tfoot>
</table>
<?php if ($o['remark']): ?><p style="font-size:13px;color:#374151;margin-top:10px"><b>备注:</b><?php echo e($o['remark']); ?></p><?php endif; ?>
<div class="sign"><span>发货:<?php echo e($o['salesman']); ?></span><span>收货签字:____________</span></div>
+50
View File
@@ -0,0 +1,50 @@
<?php
/** PSI 出库单详情 */
/** @var array $o @var array $items */
$obStatus = ['delivered' => ['label' => '已交付', 'cls' => 'tag-green'], 'partial' => ['label' => '部分交付', 'cls' => 'tag-amber']];
$s = $obStatus[$o['status']] ?? $obStatus['delivered'];
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $items));
?>
<div class="page-head">
<div><h1>出库单详情</h1><div class="desc">出库单号 <?php echo e($o['order_no']); ?></div></div>
<div style="display:flex;gap:10px">
<a class="btn-ghost" href="<?php echo site_url('PSI/outbounds/print/' . $o['id']); ?>" target="_blank"><?php echo admin_icon('printer', 16); ?> 打印</a>
<a class="btn-ghost" href="<?php echo site_url('PSI/outbounds'); ?>">← 返回</a>
</div>
</div>
<div class="panel two-col">
<table class="tbl">
<tr><th>关联销售订单</th><td><?php echo $o['so_no'] ? e($o['so_no']) : '<span class="muted">—</span>'; ?></td></tr>
<tr><th>客户</th><td><?php echo e($o['customer']); ?></td></tr>
<tr><th>业务员</th><td><?php echo e($o['salesman']); ?></td></tr>
<tr><th>仓库</th><td><?php echo e($o['warehouse']); ?></td></tr>
<tr><th>状态</th><td><span class="tag-mini <?php echo $s['cls']; ?>"><?php echo $s['label']; ?></span></td></tr>
<tr><th>出库日期</th><td><?php echo e($o['delivery_date'] ?? '—'); ?></td></tr>
<tr><th>备注</th><td class="muted"><?php echo e($o['remark'] ?: '—'); ?></td></tr>
</table>
</div>
<div class="panel">
<div class="panel-bar"><h3>出库明细</h3><span class="muted">合计 ¥<?php echo e(number_format($amt, 2)); ?></span></div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>商品</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th></tr></thead>
<tbody>
<?php foreach ($items as $it): ?>
<tr>
<td><?php echo e($it['name']); ?></td>
<td class="muted"><?php echo e($it['spec']); ?></td>
<td class="muted"><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="margin-top:16px;display:flex;gap:10px">
<a class="btn-primary" href="<?php echo site_url('PSI/outbounds/edit/' . $o['id']); ?>">编辑</a>
</div>
</div>
+46
View File
@@ -0,0 +1,46 @@
<?php
/** PSI 出库单列表 */
/** @var array $list */
$obStatus = ['delivered' => ['label' => '已交付', 'cls' => 'tag-green'], 'partial' => ['label' => '部分交付', 'cls' => 'tag-amber']];
?>
<div class="page-head">
<div><h1>出库单</h1><div class="desc">按销售订单发货出库(自动扣减库存并回写已交付量,可直接打印)</div></div>
<a class="btn-primary" href="<?php echo site_url('PSI/outbounds/create'); ?>"><?php echo admin_icon('plus', 16); ?> 新建出库单</a>
</div>
<div class="panel">
<div class="table-wrap">
<table class="tbl">
<thead><tr>
<th>出库单号</th><th>关联销售订单</th><th>客户</th><th>业务员</th><th>仓库</th>
<th>金额</th><th>状态</th><th>出库日期</th><th>操作</th>
</tr></thead>
<tbody>
<?php foreach ($list as $o):
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $o['_items'] ?? []));
$s = $obStatus[$o['status']] ?? $obStatus['delivered'];
?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo $o['so_no'] ? e($o['so_no']) : '<span class="muted">—</span>'; ?></td>
<td><?php echo e($o['customer']); ?></td>
<td><?php echo e($o['salesman']); ?></td>
<td class="muted"><?php echo e($o['warehouse']); ?></td>
<td>¥<?php echo e(number_format($amt, 2)); ?></td>
<td><span class="tag-mini <?php echo $s['cls']; ?>"><?php echo $s['label']; ?></span></td>
<td class="muted"><?php echo e($o['delivery_date'] ?? '—'); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/outbounds/show/' . $o['id']); ?>">详情</a>
<a href="<?php echo site_url('PSI/outbounds/print/' . $o['id']); ?>" target="_blank">打印</a>
<a href="<?php echo site_url('PSI/outbounds/edit/' . $o['id']); ?>">编辑</a>
<form method="post" class="inline-form" action="<?php echo site_url('PSI/outbounds/destroy/' . $o['id']); ?>" data-confirm="确认删除该出库单?将回冲库存与已交付量。">
<?php echo csrf_field(); ?><button class="link-danger" type="submit">删除</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($list)): ?><tr><td colspan="9" class="muted" style="text-align:center;padding:24px">暂无出库单</td></tr><?php endif; ?>
</tbody>
</table>
</div>
</div>
+47
View File
@@ -0,0 +1,47 @@
<?php
/** PSI 成品表单(降温服/成衣:款号/颜色/尺码/季节/年份) */
$p = $product;
$isEdit = !empty($p);
$catOpts = ['降温服' => '降温服', '成衣' => '成衣', '配饰' => '配饰', '其他' => '其他'];
$curCat = $p['category'] ?? '降温服';
if ($curCat && !isset($catOpts[$curCat])) { $catOpts[$curCat] = $curCat; }
$seasonOpts = ['春' => '春', '夏' => '夏', '秋' => '秋', '冬' => '冬', '四季' => '四季'];
$curSeason = $p['season'] ?? '';
?>
<div class="page-head"><h1><?php echo $isEdit ? '编辑成品' : '新增成品'; ?></h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'PSI/products/update/' . $p['id'] : 'PSI/products/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>款号 *</label><input name="style_no" value="<?php echo e($p['style_no'] ?? ''); ?>" placeholder="如:KJ-2026-001"></div>
<div class="field"><label>名称 *</label><input name="name" value="<?php echo e($p['name'] ?? ''); ?>" required></div>
<div class="field"><label>编码</label><input name="code" value="<?php echo e($p['code'] ?? ''); ?>"></div>
<div class="field"><label>分类</label>
<select name="category">
<?php foreach ($catOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curCat === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>规格</label><input name="spec" value="<?php echo e($p['spec'] ?? ''); ?>"></div>
<div class="field"><label>单位</label><input name="unit" value="<?php echo e($p['unit'] ?? '件'); ?>"></div>
<div class="field"><label>颜色</label><input name="color" value="<?php echo e($p['color'] ?? ''); ?>"></div>
<div class="field"><label>尺码段</label><input name="size_run" value="<?php echo e($p['size_run'] ?? ''); ?>" placeholder="如:S/M/L/XL"></div>
<div class="field"><label>季节</label>
<select name="season">
<option value="">未设置</option>
<?php foreach ($seasonOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curSeason === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>年份</label><input name="year" value="<?php echo e($p['year'] ?? ''); ?>" placeholder="如:2026"></div>
<div class="field"><label>初始库存</label><input name="stock" type="number" step="0.01" value="<?php echo e($p['stock'] ?? '0'); ?>"></div>
<div class="field"><label>成本</label><input name="cost" type="number" step="0.01" value="<?php echo e($p['cost'] ?? '0'); ?>"></div>
<div class="field"><label>售价</label><input name="price" type="number" step="0.01" value="<?php echo e($p['price'] ?? '0'); ?>"></div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="2"><?php echo e($p['remark'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url('PSI/products'); ?>">取消</a>
</form>
</div>
+38
View File
@@ -0,0 +1,38 @@
<?php
/** PSI 成品列表(降温服/成衣:款号/颜色/尺码/季节) */
?>
<div class="page-head">
<h1>成品管理</h1>
<a class="btn-primary" href="<?php echo site_url('PSI/products/create'); ?>">+ 新增成品</a>
</div>
<div class="panel">
<table class="tbl">
<thead><tr><th>ID</th><th>编码</th><th>款号</th><th>名称</th><th>规格</th><th>单位</th><th>分类</th><th>颜色</th><th>尺码</th><th>季节</th><th>年份</th><th>库存</th><th>成本</th><th>售价</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($products as $p): ?>
<tr>
<td><?php echo e($p['id']); ?></td>
<td><?php echo e($p['code'] ?? ''); ?></td>
<td><b><?php echo e($p['style_no'] ?? ''); ?></b></td>
<td><?php echo e($p['name']); ?></td>
<td><?php echo e($p['spec'] ?? ''); ?></td>
<td><?php echo e($p['unit'] ?? '件'); ?></td>
<td><?php echo e($p['category'] ?? ''); ?></td>
<td><?php echo e($p['color'] ?? ''); ?></td>
<td><?php echo e($p['size_run'] ?? ''); ?></td>
<td><?php echo e($p['season'] ?? ''); ?></td>
<td><?php echo e($p['year'] ?? ''); ?></td>
<td><b><?php echo e((float)($p['stock'] ?? 0)); ?></b></td>
<td>¥<?php echo e(number_format((float)($p['cost'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($p['price'] ?? 0), 2)); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/products/adjust/' . $p['id']); ?>">调整库存</a>
<a href="<?php echo site_url('PSI/products/edit/' . $p['id']); ?>">编辑</a>
<a href="<?php echo site_url('PSI/products/destroy/' . $p['id']); ?>" data-confirm="确认删除?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($products)): ?><tr><td colspan="15" class="muted">暂无成品,点击右上角新增</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+56
View File
@@ -0,0 +1,56 @@
<?php
/** PSI 采购表单(物料/成品统一) */
$mats = isset($GLOBALS['_psi_materials']) ? $GLOBALS['_psi_materials'] : [];
$prods = isset($GLOBALS['_psi_products']) ? $GLOBALS['_psi_products'] : [];
// 通过控制器注入时取不到,这里改为从全局读取;若为空则由控制器提供
?>
<div class="page-head"><h1>新增采购入库</h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url('PSI/purchases/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>供应商</label>
<select name="supplier_id">
<option value="0">未选择</option>
<?php foreach ($suppliers as $s): ?>
<option value="<?php echo e($s['id']); ?>"><?php echo e($s['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>入库类型</label>
<select name="item_type" id="item_type">
<option value="material">物料</option>
<option value="product">成品</option>
</select>
</div>
<div class="field"><label>物料/成品</label>
<select name="item_id" id="item_id">
<optgroup label="物料">
<?php foreach ($materials as $m): ?><option value="<?php echo e($m['id']); ?>"><?php echo e($m['name']); ?></option><?php endforeach; ?>
</optgroup>
<optgroup label="成品">
<?php foreach ($products as $p): ?><option value="<?php echo e($p['id']); ?>"><?php echo e($p['name']); ?></option><?php endforeach; ?>
</optgroup>
</select>
</div>
<div class="field"><label>数量 *</label><input name="qty" type="number" step="0.01" required></div>
<div class="field"><label>单价</label><input name="price" type="number" step="0.01" value="0"></div>
<div class="field"><label>批次/缸号</label><input name="batch_no" placeholder="面辅料缸号/批次"></div>
<div class="field"><label>预计到货</label><input name="expected_at" type="date"></div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="2"></textarea></div>
<button class="btn-primary" type="submit">入库并提交</button>
<a class="btn-soft" href="<?php echo site_url('PSI/purchases'); ?>">取消</a>
</form>
</div>
<script>
// 切换类型时仅保留对应分组的选项
document.getElementById('item_type').addEventListener('change', function(){
var type = this.value; // material|product
var sel = document.getElementById('item_id');
Array.prototype.forEach.call(sel.querySelectorAll('optgroup'), function(og){
og.style.display = (og.label === (type==='product'?'成品':'物料')) ? '' : 'none';
});
sel.selectedIndex = 0;
});
</script>
+132
View File
@@ -0,0 +1,132 @@
<?php
/** PSI 采购订单录入/编辑 */
/** @var array|null $o @var array $items @var array $materials @var array $products */
$isEdit = !empty($o);
$cat = ['material' => [], 'product' => []];
foreach ($materials as $m) { $cat['material'][(int)$m['id']] = ['name' => $m['name'], 'spec' => $m['spec'] ?? '', 'unit' => $m['unit'] ?? '', 'price' => (float)($m['price'] ?? 0)]; }
foreach ($products as $p) { $cat['product'][(int)$p['id']] = ['name' => $p['name'], 'spec' => $p['spec'] ?? '', 'unit' => $p['unit'] ?? '', 'price' => (float)($p['price'] ?? 0)]; }
?>
<div class="page-head">
<div><h1><?php echo $isEdit ? '编辑采购订单' : '新建采购订单'; ?></h1><div class="desc">选择供应商与采购明细(物料 / 成品),收货后自动入库</div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/purchase_orders'); ?>">← 返回</a>
</div>
<div class="panel form-card">
<form method="post" action="<?php echo site_url($isEdit ? ('PSI/purchase_orders/update/' . $o['id']) : 'PSI/purchase_orders/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-row">
<label>供应商 <span class="req">*</span></label>
<input type="text" name="supplier_name" value="<?php echo e($o['supplier_name'] ?? ''); ?>" required placeholder="供应商名称">
</div>
<div class="form-row">
<label>采购员</label>
<input type="text" name="salesman" value="<?php echo e($o['salesman'] ?? ($_SESSION['admin_name'] ?? '')); ?>" placeholder="默认当前登录人">
</div>
<div class="form-row">
<label>期望到货</label>
<input type="date" name="expected_at" value="<?php echo e($o['expected_at'] ?? ''); ?>">
</div>
<div class="form-row">
<label>备注</label>
<textarea name="remark" rows="2" placeholder="选填"><?php echo e($o['remark'] ?? ''); ?></textarea>
</div>
<div class="form-row">
<label>采购明细 <span class="req">*</span></label>
<div class="table-wrap">
<table class="tbl" id="itemTbl">
<thead><tr>
<th style="width:90px">类型</th><th style="width:160px">选择</th><th>名称</th><th>规格</th><th>单位</th>
<th>数量</th><th>单价</th><th>金额</th><th></th>
</tr></thead>
<tbody id="itemBody"></tbody>
</table>
</div>
<button type="button" class="btn" id="addRow" style="margin-top:10px"><?php echo admin_icon('plus', 16); ?> 添加明细</button>
</div>
<div class="form-row" style="margin-bottom:14px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:normal">
<input type="checkbox" name="auto_print" value="1" checked style="width:auto;margin:0"> 保存后打印预览
</label>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">保存</button>
<a class="btn-ghost" href="<?php echo site_url('PSI/purchase_orders'); ?>">取消</a>
</div>
</form>
</div>
<script>
const CAT = <?php echo json_encode($cat, JSON_UNESCAPED_UNICODE); ?>;
let ROW = 0;
function opts(type){
let h = '<option value="0">手动输入</option>';
for (const id in CAT[type]) h += `<option value="${id}">${CAT[type][id].name}</option>`;
return h;
}
function newRow(p){
p = p || {};
const type = p.item_type || 'material';
const tr = document.createElement('tr');
tr.className = 'item-row';
tr.innerHTML = `
<td><select class="itype">
<option value="material" ${type==='material'?'selected':''}>物料</option>
<option value="product" ${type==='product'?'selected':''}>成品</option>
</select>
<input type="hidden" name="items[${ROW}][item_type]" class="itype-h" value="${type}"></td>
<td><select class="isel" style="width:100%">${opts(type)}</select>
<input type="hidden" name="items[${ROW}][item_id]" class="iid" value="${p.item_id||0}"></td>
<td><input type="text" name="items[${ROW}][name]" value="${p.name||''}" required style="width:100%"></td>
<td><input type="text" name="items[${ROW}][spec]" value="${p.spec||''}" style="width:90px"></td>
<td><input type="text" name="items[${ROW}][unit]" value="${p.unit||''}" style="width:50px"></td>
<td><input type="number" min="1" name="items[${ROW}][qty]" value="${p.qty||1}" class="qty" style="width:64px"></td>
<td><input type="number" step="0.01" min="0" name="items[${ROW}][price]" value="${p.price||0}" class="price" style="width:84px"></td>
<td class="amt" style="white-space:nowrap">¥0.00</td>
<td><button type="button" class="row-del" title="删除">×</button></td>`;
ROW++;
const sel = tr.querySelector('.isel');
const typeSel = tr.querySelector('.itype');
sel.value = p.item_id || '0';
typeSel.addEventListener('change', e=>{
const t = e.target.value;
tr.querySelector('.itype-h').value = t;
sel.innerHTML = opts(t); sel.value = '0';
});
sel.addEventListener('change', e=>{
const id = e.target.value;
tr.querySelector('.iid').value = id;
if (CAT[type][id]) { const d=CAT[type][id];
tr.querySelector('input[name$="[name]"]').value=d.name;
tr.querySelector('input[name$="[spec]"]').value=d.spec;
tr.querySelector('input[name$="[unit]"]').value=d.unit;
tr.querySelector('input[name$="[price]"]').value=d.price; calc(tr);
}
});
tr.querySelector('.qty').addEventListener('input', ()=>calc(tr));
tr.querySelector('.price').addEventListener('input', ()=>calc(tr));
tr.querySelector('.row-del').addEventListener('click', ()=>tr.remove());
return tr;
}
function calc(tr){
const q=parseFloat(tr.querySelector('.qty').value)||0, p=parseFloat(tr.querySelector('.price').value)||0;
tr.querySelector('.amt').textContent='¥'+(q*p).toFixed(2);
}
document.getElementById('addRow').addEventListener('click', ()=>document.getElementById('itemBody').appendChild(newRow()));
<?php if (!empty($items)): ?>
<?php foreach ($items as $it): ?>
document.getElementById('itemBody').appendChild(newRow({
item_type:'<?php echo $it['item_type']==='product'?'product':'material'; ?>',
item_id:<?php echo (int)($it['item_id'] ?? 0); ?>,
name:<?php echo json_encode($it['name'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
spec:<?php echo json_encode($it['spec'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
unit:<?php echo json_encode($it['unit'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
qty:<?php echo (int)($it['qty'] ?? 1); ?>,
price:<?php echo (float)($it['price'] ?? 0); ?>
}));
<?php endforeach; ?>
<?php else: ?>
document.getElementById('itemBody').appendChild(newRow());
<?php endif; ?>
</script>
+32
View File
@@ -0,0 +1,32 @@
<?php
/** PSI 采购订单打印正文 */
/** @var array $o @var array $items */
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $items));
?>
<h2>采购订单</h2>
<div class="sub"><?php echo e($o['order_no']); ?></div>
<div class="meta">
<span><b>供应商:</b><?php echo e($o['supplier_name']); ?></span>
<span><b>采购员:</b><?php echo e($o['salesman']); ?></span>
<span><b>期望到货:</b><?php echo e($o['expected_at'] ?? '—'); ?></span>
<span><b>下单:</b><?php echo e($o['created_at']); ?></span>
</div>
<table>
<thead><tr><th>类型</th><th>名称</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th></tr></thead>
<tbody>
<?php foreach ($items as $it): ?>
<tr>
<td><?php echo $it['item_type'] === 'product' ? '成品' : '物料'; ?></td>
<td><?php echo e($it['name']); ?></td>
<td><?php echo e($it['spec']); ?></td>
<td><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><th colspan="6" style="text-align:right">合计</th><th>¥<?php echo e(number_format($amt, 2)); ?></th></tr></tfoot>
</table>
<?php if ($o['remark']): ?><p style="font-size:13px;color:#374151;margin-top:10px"><b>备注:</b><?php echo e($o['remark']); ?></p><?php endif; ?>
<div class="sign"><span>制单:<?php echo e($o['salesman']); ?></span><span>供应商签字:____________</span></div>
+60
View File
@@ -0,0 +1,60 @@
<?php
/** PSI 采购订单详情 */
/** @var array $o @var array $items */
$poStatus = [
'pending' => ['label' => '待处理', 'cls' => 'tag-gray'],
'partial' => ['label' => '部分收货', 'cls' => 'tag-amber'],
'received' => ['label' => '已收货', 'cls' => 'tag-green'],
'closed' => ['label' => '已关闭', 'cls' => 'tag-gray'],
];
$s = $poStatus[$o['status']] ?? $poStatus['pending'];
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $items));
?>
<div class="page-head">
<div><h1>采购订单详情</h1><div class="desc">订单号 <?php echo e($o['order_no']); ?></div></div>
<div style="display:flex;gap:10px">
<a class="btn-ghost" href="<?php echo site_url('PSI/purchase_orders/print/' . $o['id']); ?>" target="_blank"><?php echo admin_icon('printer', 16); ?> 打印</a>
<a class="btn-ghost" href="<?php echo site_url('PSI/purchase_orders'); ?>">← 返回</a>
</div>
</div>
<div class="panel two-col">
<table class="tbl">
<tr><th>供应商</th><td><?php echo e($o['supplier_name']); ?></td></tr>
<tr><th>采购员</th><td><?php echo e($o['salesman']); ?></td></tr>
<tr><th>状态</th><td><span class="tag-mini <?php echo $s['cls']; ?>"><?php echo $s['label']; ?></span></td></tr>
<tr><th>期望到货</th><td><?php echo e($o['expected_at'] ?? '—'); ?></td></tr>
<tr><th>备注</th><td class="muted"><?php echo e($o['remark'] ?: '—'); ?></td></tr>
</table>
</div>
<div class="panel">
<div class="panel-bar"><h3>采购明细</h3><span class="muted">合计 ¥<?php echo e(number_format($amt, 2)); ?></span></div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>类型</th><th>名称</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th><th>已收</th></tr></thead>
<tbody>
<?php foreach ($items as $it): ?>
<tr>
<td><?php echo $it['item_type'] === 'product' ? '成品' : '物料'; ?></td>
<td><?php echo e($it['name']); ?></td>
<td class="muted"><?php echo e($it['spec']); ?></td>
<td class="muted"><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($it['received_qty'] ?? 0); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="margin-top:16px;display:flex;gap:10px">
<?php if ($o['status'] !== 'received'): ?>
<a class="btn-primary" href="<?php echo site_url('PSI/purchase_orders/edit/' . $o['id']); ?>">编辑</a>
<form method="post" action="<?php echo site_url('PSI/purchase_orders/receive/' . $o['id']); ?>" data-confirm="确认收货入库?将增加对应成品库存。">
<?php echo csrf_field(); ?><button class="btn-ok" type="submit">收货入库</button>
</form>
<?php endif; ?>
</div>
</div>
+55
View File
@@ -0,0 +1,55 @@
<?php
/** PSI 采购订单列表 */
/** @var array $orders */
$poStatus = [
'pending' => ['label' => '待处理', 'cls' => 'tag-gray'],
'partial' => ['label' => '部分收货', 'cls' => 'tag-amber'],
'received' => ['label' => '已收货', 'cls' => 'tag-green'],
'closed' => ['label' => '已关闭', 'cls' => 'tag-gray'],
];
?>
<div class="page-head">
<div><h1>采购订单</h1><div class="desc">录入对供应商的采购订单,收货后自动入库并增加库存(可打印)</div></div>
<a class="btn-primary" href="<?php echo site_url('PSI/purchase_orders/create'); ?>"><?php echo admin_icon('plus', 16); ?> 新建采购订单</a>
</div>
<div class="panel">
<div class="table-wrap">
<table class="tbl">
<thead><tr>
<th>订单号</th><th>供应商</th><th>采购员</th><th>明细</th><th>金额</th>
<th>状态</th><th>期望到货</th><th>操作</th>
</tr></thead>
<tbody>
<?php foreach ($orders as $o):
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $o['_items'] ?? []));
$s = $poStatus[$o['status']] ?? $poStatus['pending'];
?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo e($o['supplier_name']); ?></td>
<td><?php echo e($o['salesman']); ?></td>
<td class="muted"><?php echo count($o['_items'] ?? []); ?> 项</td>
<td>¥<?php echo e(number_format($amt, 2)); ?></td>
<td><span class="tag-mini <?php echo $s['cls']; ?>"><?php echo $s['label']; ?></span></td>
<td class="muted"><?php echo e($o['expected_at'] ?? '—'); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/purchase_orders/show/' . $o['id']); ?>">详情</a>
<a href="<?php echo site_url('PSI/purchase_orders/print/' . $o['id']); ?>" target="_blank">打印</a>
<?php if ($o['status'] !== 'received'): ?>
<a href="<?php echo site_url('PSI/purchase_orders/edit/' . $o['id']); ?>">编辑</a>
<form method="post" class="inline-form" action="<?php echo site_url('PSI/purchase_orders/receive/' . $o['id']); ?>" data-confirm="确认收货入库?将增加对应成品库存。">
<?php echo csrf_field(); ?><button class="link-ok" type="submit">收货</button>
</form>
<?php endif; ?>
<form method="post" class="inline-form" action="<?php echo site_url('PSI/purchase_orders/destroy/' . $o['id']); ?>" data-confirm="确认删除该采购订单?">
<?php echo csrf_field(); ?><button class="link-danger" type="submit">删除</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($orders)): ?><tr><td colspan="8" class="muted" style="text-align:center;padding:24px">暂无采购订单</td></tr><?php endif; ?>
</tbody>
</table>
</div>
</div>
+32
View File
@@ -0,0 +1,32 @@
<?php
/** PSI 采购入库列表 */
?>
<div class="page-head">
<h1>采购入库</h1>
<a class="btn-primary" href="<?php echo site_url('PSI/purchases/create'); ?>">+ 新增采购</a>
</div>
<div class="panel">
<p class="muted">提交采购单后系统自动增加对应物料/成品库存,并写入库存流水。</p>
<table class="tbl">
<thead><tr><th>单号</th><th>供应商</th><th>类型</th><th>批次/缸号</th><th>数量</th><th>单价</th><th>金额</th><th>预计到货</th><th>日期</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($purchases as $p): ?>
<tr>
<td><?php echo e($p['order_no']); ?></td>
<td><?php echo e($smap[$p['supplier_id']] ?? ''); ?></td>
<td><?php echo $p['item_type'] === 'product' ? '成品' : '物料'; ?></td>
<td><?php echo e($p['batch_no'] ?? ''); ?></td>
<td><?php echo e((float)($p['qty'] ?? 0)); ?></td>
<td>¥<?php echo e(number_format((float)($p['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($p['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($p['expected_at'] ?? ''); ?></td>
<td><?php echo e($p['created_at'] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/purchases/destroy/' . $p['id']); ?>" data-confirm="删除不会回滚库存,确认?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($purchases)): ?><tr><td colspan="10" class="muted">暂无采购记录</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+26
View File
@@ -0,0 +1,26 @@
<?php
/** @var string $title */
/** @var string $body */
?>
<div class="page-head">
<div><h2>发起紧急提醒</h2><p class="muted">提交后将作为紧急事件通知相关负责人(站内 + 邮件 + 微信,依通知设置)。</p></div>
<div class="head-actions">
<a class="btn-ghost" href="<?php echo \Core\App::url('PSI/reminders'); ?>">返回列表</a>
</div>
</div>
<form method="post" action="<?php echo \Core\App::url('PSI/reminders/create'); ?>" class="form-card">
<?php echo csrf_field(); ?>
<div class="form-row">
<label>提醒标题 <span class="req">*</span></label>
<input type="text" name="title" maxlength="120" required value="<?php echo e($title); ?>" placeholder="例如:A 客户订单需今日发货">
</div>
<div class="form-row">
<label>提醒内容</label>
<textarea name="body" rows="5" placeholder="补充说明,可留空"><?php echo e($body); ?></textarea>
</div>
<div class="form-actions">
<button class="btn-primary" type="submit">发送紧急提醒</button>
<a class="btn-ghost" href="<?php echo \Core\App::url('PSI/reminders'); ?>">取消</a>
</div>
</form>
+73
View File
@@ -0,0 +1,73 @@
<?php
/** @var array $events */
/** @var int $unread */
$typeLabel = [
'sales_order' => '新销售订单',
'purchase_order' => '新采购订单',
'customer_order' => '新客户订单',
'low_stock' => '低库存预警',
'manual' => '手动提醒',
];
?>
<div class="page-head">
<div>
<h2>紧急提醒</h2>
<p class="muted">系统将“新订单 / 新事件”作为紧急事件推送;未读 <b style="color:#dc2626"><?php echo $unread; ?></b> 条。</p>
</div>
<div class="head-actions">
<a class="btn-primary" href="<?php echo \Core\App::url('PSI/reminders/create'); ?>"><?php echo admin_icon('bell', 16); ?> 发起提醒</a>
<?php if ($unread > 0): ?>
<form method="post" action="<?php echo \Core\App::url('PSI/reminders/markAll'); ?>" class="inline-form">
<?php echo csrf_field(); ?>
<button class="btn-ghost" type="submit">全部标为已读</button>
</form>
<?php endif; ?>
</div>
</div>
<?php if (empty($events)): ?>
<div class="empty-box">暂无紧急事件。</div>
<?php else: ?>
<div class="ev-list">
<?php foreach ($events as $ev):
$type = $ev['type'] ?? 'manual';
$channels = json_decode($ev['channels'] ?? '[]', true) ?: [];
$isRead = !empty($ev['_read']);
?>
<div class="ev-card<?php echo $isRead ? ' read' : ''; ?>">
<div class="ev-left">
<span class="ev-dot"></span>
</div>
<div class="ev-main">
<div class="ev-top">
<span class="ev-tag"><?php echo e($typeLabel[$type] ?? $type); ?></span>
<?php if (!$isRead): ?><span class="ev-new">未读</span><?php endif; ?>
<span class="ev-time"><?php echo e($ev['created_at'] ?? ''); ?></span>
</div>
<div class="ev-title"><?php echo e($ev['title'] ?? ''); ?></div>
<div class="ev-body"><?php echo nl2br(e($ev['body'] ?? '')); ?></div>
<div class="ev-foot">
<span class="ev-channels">
<?php foreach ($channels as $c): ?>
<span class="ch ch-<?php echo e($c); ?>"><?php echo e($c === 'inapp' ? '站内' : ($c === 'email' ? '邮件' : ($c === 'wechat' ? '微信' : $c))); ?></span>
<?php endforeach; ?>
</span>
<span class="ev-ops">
<?php if (!empty($ev['url'])): ?>
<a class="link-ok" href="<?php echo \Core\App::url($ev['url']); ?>">查看详情</a>
<?php endif; ?>
<?php if (!$isRead): ?>
<form method="post" action="<?php echo \Core\App::url('PSI/reminders/mark/' . $ev['id']); ?>" class="inline-form">
<?php echo csrf_field(); ?>
<button class="link-ok" type="submit">标为已读</button>
</form>
<?php else: ?>
<span class="muted">已读</span>
<?php endif; ?>
</span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
+59
View File
@@ -0,0 +1,59 @@
<?php
/** PSI 交付明细报表:出库单 + 销售订单交付进度 */
/** @var array $list @var array $progress */
$soStatus = ['pending'=>'待处理','partial'=>'部分交付','delivered'=>'已交付','closed'=>'已关闭'];
?>
<div class="page-head">
<div><h1>交付明细</h1><div class="desc">出库(交付)记录与销售订单交付进度、未交订单</div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/reports'); ?>">← 报表中心</a>
</div>
<div class="panel">
<div class="panel-bar"><h3>销售订单交付进度</h3><span class="muted">应发 / 已发 / 未发</span></div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>销售订单</th><th>客户</th><th>业务员</th><th>状态</th><th>应发</th><th>已发</th><th>未发</th><th>金额</th></tr></thead>
<tbody>
<?php foreach ($progress as $p): ?>
<tr>
<td><?php echo e($p['order_no']); ?></td>
<td><?php echo e($p['customer']); ?></td>
<td><?php echo e($p['salesman']); ?></td>
<td><span class="tag-mini <?php echo $p['status']==='delivered'?'tag-green':($p['status']==='partial'?'tag-amber':'tag-gray'); ?>"><?php echo $soStatus[$p['status']] ?? '待处理'; ?></span></td>
<td><?php echo $p['ordered']; ?></td>
<td><?php echo $p['delivered']; ?></td>
<td><?php echo $p['remain']; ?></td>
<td>¥<?php echo e(number_format($p['amount'], 2)); ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($progress)): ?><tr><td colspan="8" class="muted" style="text-align:center;padding:16px">暂无销售订单交付数据</td></tr><?php endif; ?>
</tbody>
</table>
</div>
</div>
<div class="panel">
<div class="panel-bar"><h3>出库(交付)记录</h3><span class="muted">共 <?php echo count($list); ?> 单</span></div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>出库单号</th><th>关联销售订单</th><th>客户</th><th>业务员</th><th>金额</th><th>出库日期</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($list as $o):
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $o['_items'] ?? []));
?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo $o['so_no'] ? e($o['so_no']) : '<span class="muted">—</span>'; ?></td>
<td><?php echo e($o['customer']); ?></td>
<td><?php echo e($o['salesman']); ?></td>
<td>¥<?php echo e(number_format($amt, 2)); ?></td>
<td class="muted"><?php echo e($o['delivery_date'] ?? '—'); ?></td>
<td class="row-actions"><a href="<?php echo site_url('PSI/outbounds/show/' . $o['id']); ?>">详情</a>
<a href="<?php echo site_url('PSI/outbounds/print/' . $o['id']); ?>" target="_blank">打印</a></td>
</tr>
<?php endforeach; ?>
<?php if (empty($list)): ?><tr><td colspan="7" class="muted" style="text-align:center;padding:16px">暂无出库记录</td></tr><?php endif; ?>
</tbody>
</table>
</div>
</div>
+42
View File
@@ -0,0 +1,42 @@
<?php
/** PSI 采购订单明细报表 */
/** @var array $orders */
$poStatus = ['pending'=>'待处理','partial'=>'部分收货','received'=>'已收货','closed'=>'已关闭'];
?>
<div class="page-head">
<div><h1>采购订单明细</h1><div class="desc">每张采购订单的物料/成品明细(共 <?php echo count($orders); ?> 张)</div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/reports'); ?>">← 报表中心</a>
</div>
<?php foreach ($orders as $o):
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $o['_items'] ?? []));
?>
<div class="panel">
<div class="panel-bar">
<h3><?php echo e($o['order_no']); ?> · <?php echo e($o['supplier_name']); ?>
<span class="tag-mini tag-gray" style="margin-left:6px"><?php echo $poStatus[$o['status']] ?? '待处理'; ?></span></h3>
<span class="muted">采购员 <?php echo e($o['salesman']); ?> · 合计 ¥<?php echo e(number_format($amt, 2)); ?></span>
</div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>类型</th><th>名称</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th><th>已收</th></tr></thead>
<tbody>
<?php foreach (($o['_items'] ?? []) as $it): ?>
<tr>
<td><?php echo $it['item_type'] === 'product' ? '成品' : '物料'; ?></td>
<td><?php echo e($it['name']); ?></td>
<td class="muted"><?php echo e($it['spec']); ?></td>
<td class="muted"><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($it['received_qty'] ?? 0); ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($o['_items'])): ?><tr><td colspan="8" class="muted" style="text-align:center;padding:16px">无明细</td></tr><?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
<?php if (empty($orders)): ?><div class="panel"><div class="muted" style="text-align:center;padding:24px">暂无采购订单</div></div><?php endif; ?>
+66
View File
@@ -0,0 +1,66 @@
<?php
/** PSI 销售/采购订单明细(合并筛选) */
/** @var string $type @var array $so @var array $po */
$soStatus = ['pending'=>'待处理','partial'=>'部分交付','delivered'=>'已交付','closed'=>'已关闭'];
$poStatus = ['pending'=>'待处理','partial'=>'部分收货','received'=>'已收货','closed'=>'已关闭'];
?>
<div class="page-head">
<div><h1>销售 / 采购订单明细</h1><div class="desc">合并查看销售订单与采购订单(含金额)</div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/reports'); ?>">← 报表中心</a>
</div>
<div class="panel">
<div class="panel-bar">
<h3>筛选</h3>
<div class="seg" style="display:flex;gap:8px">
<a class="btn-sm <?php echo $type==='all'?'btn-primary':'btn-ghost'; ?>" href="<?php echo site_url('PSI/reports/soPo?type=all'); ?>">全部</a>
<a class="btn-sm <?php echo $type==='sales'?'btn-primary':'btn-ghost'; ?>" href="<?php echo site_url('PSI/reports/soPo?type=sales'); ?>">仅销售订单</a>
<a class="btn-sm <?php echo $type==='purchase'?'btn-primary':'btn-ghost'; ?>" href="<?php echo site_url('PSI/reports/soPo?type=purchase'); ?>">仅采购订单</a>
</div>
</div>
<?php if ($type !== 'purchase'): ?>
<h4 style="margin:14px 0 8px">销售订单(<?php echo count($so); ?></h4>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>订单号</th><th>客户</th><th>业务员</th><th>渠道</th><th>明细</th><th>金额</th><th>状态</th></tr></thead>
<tbody>
<?php foreach ($so as $o): ?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo e($o['customer']); ?></td>
<td><?php echo e($o['salesman']); ?></td>
<td><?php echo $o['channel']==='export'?'外贸':'内贸'; ?></td>
<td class="muted"><?php echo count($o['_items']??[]); ?> 项</td>
<td>¥<?php echo e(number_format((float)($o['_amt'] ?? 0), 2)); ?></td>
<td><?php echo $soStatus[$o['status']] ?? '待处理'; ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($so)): ?><tr><td colspan="7" class="muted" style="text-align:center;padding:14px">无销售订单</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if ($type !== 'sales'): ?>
<h4 style="margin:18px 0 8px">采购订单(<?php echo count($po); ?></h4>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>订单号</th><th>供应商</th><th>采购员</th><th>明细</th><th>金额</th><th>状态</th></tr></thead>
<tbody>
<?php foreach ($po as $o): ?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo e($o['supplier_name']); ?></td>
<td><?php echo e($o['salesman']); ?></td>
<td class="muted"><?php echo count($o['_items']??[]); ?> 项</td>
<td>¥<?php echo e(number_format((float)($o['_amt'] ?? 0), 2)); ?></td>
<td><?php echo $poStatus[$o['status']] ?? '待处理'; ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($po)): ?><tr><td colspan="6" class="muted" style="text-align:center;padding:14px">无采购订单</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
+25
View File
@@ -0,0 +1,25 @@
<?php
/** PSI 报表中心首页 */
/** @var int $poCount @var int $soCount @var int $obCount */
$cards = [
['label' => '采购订单明细', 'desc' => '按采购订单查看物料/成品明细', 'url' => 'PSI/reports/poDetail', 'icon' => 'file-text', 'n' => $poCount],
['label' => '销售/采购订单明细', 'desc' => '合并查看两类订单与金额', 'url' => 'PSI/reports/soPo', 'icon' => 'clipboard', 'n' => $soCount + $poCount],
['label' => '交付明细', 'desc' => '出库交付进度与未交订单', 'url' => 'PSI/reports/delivery', 'icon' => 'truck', 'n' => $obCount],
];
?>
<div class="page-head">
<div><h1>报表中心</h1><div class="desc">集中查看单据与单据之间的关联性</div></div>
</div>
<div class="stat-grid" style="grid-template-columns:repeat(auto-fill,minmax(280px,1fr))">
<?php foreach ($cards as $c): ?>
<a class="stat-card" href="<?php echo site_url($c['url']); ?>" style="text-decoration:none">
<div class="stat-ic"><?php echo admin_icon($c['icon'], 22); ?></div>
<div class="stat-main">
<div class="stat-num"><?php echo admin_icon($c['icon'], 18) ? '' : ''; ?><?php echo e($c['label']); ?></div>
<div class="stat-sub"><?php echo e($c['desc']); ?></div>
<div class="muted" style="font-size:12px;margin-top:6px">相关单据 <?php echo $c['n']; ?> 条</div>
</div>
</a>
<?php endforeach; ?>
</div>
+56
View File
@@ -0,0 +1,56 @@
<?php
/** PSI 销售出库表单 */
?>
<div class="page-head"><h1>新增销售出库</h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url('PSI/sales/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>客户/买方</label><input name="customer" value="" placeholder="出口填海外客户名"></div>
<div class="field"><label>渠道</label>
<select name="channel">
<option value="domestic">内销(终端/批发)</option>
<option value="export">外贸出口</option>
</select>
</div>
<div class="field"><label>销售区域</label><input name="region" value="" placeholder="如:华东/海外"></div>
<div class="field"><label>出库类型</label>
<select name="item_type" id="item_type2">
<option value="product">成品</option>
<option value="material">物料</option>
</select>
</div>
<div class="field"><label>成品/物料</label>
<select name="item_id" id="item_id2">
<optgroup label="成品">
<?php foreach ($products as $p): ?><option value="<?php echo e($p['id']); ?>"><?php echo e($p['name']); ?>(库存<?php echo e((float)($p['stock'] ?? 0)); ?>)</option><?php endforeach; ?>
</optgroup>
<optgroup label="物料">
<?php foreach ($materials as $m): ?><option value="<?php echo e($m['id']); ?>"><?php echo e($m['name']); ?>(库存<?php echo e((float)($m['stock'] ?? 0)); ?>)</option><?php endforeach; ?>
</optgroup>
</select>
</div>
<div class="field"><label>数量 *</label><input name="qty" type="number" step="0.01" required></div>
<div class="field"><label>单价</label><input name="price" type="number" step="0.01" value="0"></div>
<div class="field"><label>批次/缸号</label><input name="batch_no" placeholder="出库批次/缸号"></div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="2"></textarea></div>
<div class="field" style="margin-bottom:16px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:normal">
<input type="checkbox" name="auto_print" value="1" checked style="width:auto;margin:0"> 保存后打印预览
</label>
</div>
<button class="btn-primary" type="submit">出库并提交</button>
<a class="btn-soft" href="<?php echo site_url('PSI/sales'); ?>">取消</a>
</form>
</div>
<script>
document.getElementById('item_type2').addEventListener('change', function(){
var type = this.value;
var sel = document.getElementById('item_id2');
Array.prototype.forEach.call(sel.querySelectorAll('optgroup'), function(og){
og.style.display = (og.label === (type==='material'?'物料':'成品')) ? '' : 'none';
});
sel.selectedIndex = 0;
});
</script>
+34
View File
@@ -0,0 +1,34 @@
<?php
/** PSI 销售出库列表 */
$chLabel = ['domestic' => '内销', 'export' => '外贸出口'];
?>
<div class="page-head">
<h1>销售出库</h1>
<a class="btn-primary" href="<?php echo site_url('PSI/sales/create'); ?>">+ 新增销售</a>
</div>
<div class="panel">
<p class="muted">支持内销与外贸出口。提交后自动扣减库存,库存不足将被拦截。</p>
<table class="tbl">
<thead><tr><th>单号</th><th>客户</th><th>渠道</th><th>区域</th><th>数量</th><th>单价</th><th>金额</th><th>日期</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($sales as $s): ?>
<tr>
<td><?php echo e($s['order_no']); ?></td>
<td><?php echo e($s['customer'] ?? ''); ?></td>
<td><?php echo e($chLabel[$s['channel'] ?? ''] ?? $s['channel'] ?? ''); ?></td>
<td><?php echo e($s['region'] ?? ''); ?></td>
<td><?php echo e((float)($s['qty'] ?? 0)); ?></td>
<td>¥<?php echo e(number_format((float)($s['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($s['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($s['created_at'] ?? ''); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/sales/show/' . $s['id']); ?>">查看</a>
<a href="<?php echo site_url('PSI/sales/print/' . $s['id']); ?>" target="_blank">打印</a>
<a class="del" href="<?php echo site_url('PSI/sales/destroy/' . $s['id']); ?>" data-confirm="删除不会回滚库存,确认?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($sales)): ?><tr><td colspan="9" class="muted">暂无销售记录</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+125
View File
@@ -0,0 +1,125 @@
<?php
/** PSI 销售订单录入/编辑 */
/** @var array|null $o @var array $items @var array $products */
$isEdit = !empty($o);
$prodMap = [];
foreach ($products as $p) { $prodMap[(int)$p['id']] = ['name' => $p['name'], 'spec' => $p['spec'] ?? '', 'unit' => $p['unit'] ?? '', 'price' => (float)($p['price'] ?? 0)]; }
?>
<div class="page-head">
<div><h1><?php echo $isEdit ? '编辑销售订单' : '新建销售订单'; ?></h1><div class="desc">填写客户与商品明细,保存后即可打印 / 据此出库</div></div>
<a class="btn-ghost" href="<?php echo site_url('PSI/sales_orders'); ?>">← 返回</a>
</div>
<div class="panel form-card">
<form method="post" action="<?php echo site_url($isEdit ? ('PSI/sales_orders/update/' . $o['id']) : 'PSI/sales_orders/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-row">
<label>客户 <span class="req">*</span></label>
<input type="text" name="customer" value="<?php echo e($o['customer'] ?? ''); ?>" required placeholder="客户名称">
</div>
<div class="form-row">
<label>业务员</label>
<input type="text" name="salesman" value="<?php echo e($o['salesman'] ?? ($_SESSION['admin_name'] ?? '')); ?>" placeholder="默认当前登录人">
</div>
<div class="radio-row" style="margin-bottom:16px">
<label class="radio"><input type="radio" name="channel" value="domestic" <?php echo (!empty($o) && $o['channel']==='export')?'':'checked'; ?>> 内贸</label>
<label class="radio"><input type="radio" name="channel" value="export" <?php echo (!empty($o) && $o['channel']==='export')?'checked':''; ?>> 外贸</label>
</div>
<div class="form-row">
<label>地区</label>
<input type="text" name="region" value="<?php echo e($o['region'] ?? ''); ?>" placeholder="如:华东 / 北美">
</div>
<div class="form-row">
<label>交货日期</label>
<input type="date" name="delivery_date" value="<?php echo e($o['delivery_date'] ?? ''); ?>">
</div>
<div class="form-row">
<label>备注</label>
<textarea name="remark" rows="2" placeholder="选填"><?php echo e($o['remark'] ?? ''); ?></textarea>
</div>
<div class="form-row">
<label>商品明细 <span class="req">*</span></label>
<div class="table-wrap">
<table class="tbl" id="itemTbl">
<thead><tr>
<th style="width:160px">关联成品</th><th>名称</th><th>规格</th><th>单位</th>
<th>数量</th><th>单价</th><th>金额</th><th></th>
</tr></thead>
<tbody id="itemBody"></tbody>
</table>
</div>
<button type="button" class="btn" id="addRow" style="margin-top:10px"><?php echo admin_icon('plus', 16); ?> 添加明细</button>
</div>
<div class="form-row" style="margin-bottom:14px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:normal">
<input type="checkbox" name="auto_print" value="1" checked style="width:auto;margin:0"> 保存后打印预览
</label>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">保存</button>
<a class="btn-ghost" href="<?php echo site_url('PSI/sales_orders'); ?>">取消</a>
</div>
</form>
</div>
<script>
const PROD = <?php echo json_encode($prodMap, JSON_UNESCAPED_UNICODE); ?>;
let ROW = 0;
function newRow(p) {
p = p || {};
const tr = document.createElement('tr');
tr.className = 'item-row';
tr.innerHTML = `
<td><select class="prod-sel" style="width:100%">
<option value="0">手动输入</option>
${Object.keys(PROD).map(id=>`<option value="${id}">${PROD[id].name}</option>`).join('')}
</select>
<input type="hidden" name="items[${ROW}][product_id]" class="pid" value="${p.product_id||0}"></td>
<td><input type="text" name="items[${ROW}][name]" value="${p.name||''}" required style="width:100%"></td>
<td><input type="text" name="items[${ROW}][spec]" value="${p.spec||''}" style="width:90px"></td>
<td><input type="text" name="items[${ROW}][unit]" value="${p.unit||''}" style="width:50px"></td>
<td><input type="number" min="1" name="items[${ROW}][qty]" value="${p.qty||1}" class="qty" style="width:64px"></td>
<td><input type="number" step="0.01" min="0" name="items[${ROW}][price]" value="${p.price||0}" class="price" style="width:84px"></td>
<td class="amt" style="white-space:nowrap">¥0.00</td>
<td><button type="button" class="row-del" title="删除">×</button></td>`;
ROW++;
tr.querySelector('.prod-sel').addEventListener('change', e=>{
const id = e.target.value;
if (PROD[id]) {
const d = PROD[id];
tr.querySelector('input[name$="[name]"]').value = d.name;
tr.querySelector('input[name$="[spec]"]').value = d.spec;
tr.querySelector('input[name$="[unit]"]').value = d.unit;
tr.querySelector('input[name$="[price]"]').value = d.price;
tr.querySelector('.pid').value = id;
calc(tr);
}
});
tr.querySelector('.qty').addEventListener('input', ()=>calc(tr));
tr.querySelector('.price').addEventListener('input', ()=>calc(tr));
tr.querySelector('.row-del').addEventListener('click', ()=>tr.remove());
return tr;
}
function calc(tr){
const q = parseFloat(tr.querySelector('.qty').value)||0;
const p = parseFloat(tr.querySelector('.price').value)||0;
tr.querySelector('.amt').textContent = '¥' + (q*p).toFixed(2);
}
document.getElementById('addRow').addEventListener('click', ()=>document.getElementById('itemBody').appendChild(newRow()));
<?php if (!empty($items)): ?>
<?php foreach ($items as $it): ?>
document.getElementById('itemBody').appendChild(newRow({
product_id: <?php echo (int)($it['product_id'] ?? 0); ?>,
name: <?php echo json_encode($it['name'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
spec: <?php echo json_encode($it['spec'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
unit: <?php echo json_encode($it['unit'] ?? '', JSON_UNESCAPED_UNICODE); ?>,
qty: <?php echo (int)($it['qty'] ?? 1); ?>,
price:<?php echo (float)($it['price'] ?? 0); ?>
}));
<?php endforeach; ?>
<?php else: ?>
document.getElementById('itemBody').appendChild(newRow());
<?php endif; ?>
</script>
+33
View File
@@ -0,0 +1,33 @@
<?php
/** PSI 销售订单打印正文(由 psi_print_shell 包裹) */
/** @var array $o @var array $items */
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $items));
$chn = $o['channel'] === 'export' ? '外贸' : '内贸';
?>
<h2>销售订单</h2>
<div class="sub"><?php echo e($o['order_no']); ?></div>
<div class="meta">
<span><b>客户:</b><?php echo e($o['customer']); ?></span>
<span><b>业务员:</b><?php echo e($o['salesman']); ?></span>
<span><b>渠道:</b><?php echo $chn . ($o['region'] ? ' / ' . e($o['region']) : ''); ?></span>
<span><b>交货日期:</b><?php echo e($o['delivery_date'] ?? '—'); ?></span>
<span><b>下单:</b><?php echo e($o['created_at']); ?></span>
</div>
<table>
<thead><tr><th>商品</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th></tr></thead>
<tbody>
<?php foreach ($items as $it): ?>
<tr>
<td><?php echo e($it['name']); ?></td>
<td><?php echo e($it['spec']); ?></td>
<td><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot><tr><th colspan="5" style="text-align:right">合计</th><th>¥<?php echo e(number_format($amt, 2)); ?></th></tr></tfoot>
</table>
<?php if ($o['remark']): ?><p style="font-size:13px;color:#374151;margin-top:10px"><b>备注:</b><?php echo e($o['remark']); ?></p><?php endif; ?>
<div class="sign"><span>制单:<?php echo e($o['salesman']); ?></span><span>客户签字:____________</span></div>
+57
View File
@@ -0,0 +1,57 @@
<?php
/** PSI 销售订单详情 */
/** @var array $o @var array $items */
$soStatus = [
'pending' => ['label' => '待处理', 'cls' => 'tag-gray'],
'partial' => ['label' => '部分交付', 'cls' => 'tag-amber'],
'delivered'=> ['label' => '已交付', 'cls' => 'tag-green'],
'closed' => ['label' => '已关闭', 'cls' => 'tag-gray'],
];
$s = $soStatus[$o['status']] ?? $soStatus['pending'];
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $items));
?>
<div class="page-head">
<div><h1>销售订单详情</h1><div class="desc">订单号 <?php echo e($o['order_no']); ?></div></div>
<div style="display:flex;gap:10px">
<a class="btn-ghost" href="<?php echo site_url('PSI/sales_orders/print/' . $o['id']); ?>" target="_blank"><?php echo admin_icon('printer', 16); ?> 打印</a>
<a class="btn-ghost" href="<?php echo site_url('PSI/sales_orders'); ?>">← 返回</a>
</div>
</div>
<div class="panel two-col">
<table class="tbl">
<tr><th>客户</th><td><?php echo e($o['customer']); ?></td></tr>
<tr><th>业务员</th><td><?php echo e($o['salesman']); ?></td></tr>
<tr><th>渠道</th><td><?php echo $o['channel'] === 'export' ? '外贸' : '内贸'; ?><?php if ($o['region']): ?> · <?php echo e($o['region']); ?><?php endif; ?></td></tr>
<tr><th>状态</th><td><span class="tag-mini <?php echo $s['cls']; ?>"><?php echo $s['label']; ?></span></td></tr>
<tr><th>交货日期</th><td><?php echo e($o['delivery_date'] ?? '—'); ?></td></tr>
<tr><th>备注</th><td class="muted"><?php echo e($o['remark'] ?: '—'); ?></td></tr>
</table>
</div>
<div class="panel">
<div class="panel-bar"><h3>商品明细</h3><span class="muted">合计 ¥<?php echo e(number_format($amt, 2)); ?></span></div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>商品</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th><th>已交付</th><th>未交付</th></tr></thead>
<tbody>
<?php foreach ($items as $it): $remain = max(0, (int)($it['qty'] ?? 0) - (int)($it['delivered_qty'] ?? 0)); ?>
<tr>
<td><?php echo e($it['name']); ?></td>
<td class="muted"><?php echo e($it['spec']); ?></td>
<td class="muted"><?php echo e($it['unit']); ?></td>
<td><?php echo e($it['qty']); ?></td>
<td>¥<?php echo e(number_format((float)($it['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format((float)($it['amount'] ?? 0), 2)); ?></td>
<td><?php echo e($it['delivered_qty'] ?? 0); ?></td>
<td><?php echo $remain; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="margin-top:16px;display:flex;gap:10px">
<a class="btn-primary" href="<?php echo site_url('PSI/sales_orders/edit/' . $o['id']); ?>">编辑</a>
<a class="btn-ghost" href="<?php echo site_url('PSI/outbounds/create?so=' . $o['order_no']); ?>"><?php echo admin_icon('package', 16); ?> 据此出库</a>
</div>
</div>
+52
View File
@@ -0,0 +1,52 @@
<?php
/** PSI 销售订单列表 */
/** @var array $orders */
$soStatus = [
'pending' => ['label' => '待处理', 'cls' => 'tag-gray'],
'partial' => ['label' => '部分交付', 'cls' => 'tag-amber'],
'delivered'=> ['label' => '已交付', 'cls' => 'tag-green'],
'closed' => ['label' => '已关闭', 'cls' => 'tag-gray'],
];
?>
<div class="page-head">
<div><h1>销售订单</h1><div class="desc">录入客户销售订单,可关联出库单进行交付(直接打印)</div></div>
<a class="btn-primary" href="<?php echo site_url('PSI/sales_orders/create'); ?>"><?php echo admin_icon('plus', 16); ?> 新建销售订单</a>
</div>
<div class="panel">
<div class="table-wrap">
<table class="tbl">
<thead><tr>
<th>订单号</th><th>客户</th><th>业务员</th><th>渠道</th><th>明细</th>
<th>金额</th><th>状态</th><th>交货日期</th><th>操作</th>
</tr></thead>
<tbody>
<?php foreach ($orders as $o):
$amt = array_sum(array_map(fn($i) => (float)($i['amount'] ?? 0), $o['_items'] ?? []));
$s = $soStatus[$o['status']] ?? $soStatus['pending'];
?>
<tr>
<td><?php echo e($o['order_no']); ?></td>
<td><?php echo e($o['customer']); ?></td>
<td><?php echo e($o['salesman']); ?></td>
<td><?php echo $o['channel'] === 'export' ? '外贸' : '内贸'; ?></td>
<td class="muted"><?php echo count($o['_items'] ?? []); ?> 项</td>
<td>¥<?php echo e(number_format($amt, 2)); ?></td>
<td><span class="tag-mini <?php echo $s['cls']; ?>"><?php echo $s['label']; ?></span></td>
<td class="muted"><?php echo e($o['delivery_date'] ?? '—'); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/sales_orders/show/' . $o['id']); ?>">详情</a>
<a href="<?php echo site_url('PSI/sales_orders/print/' . $o['id']); ?>" target="_blank">打印</a>
<a href="<?php echo site_url('PSI/sales_orders/edit/' . $o['id']); ?>">编辑</a>
<a href="<?php echo site_url('PSI/outbounds/create?so=' . $o['order_no']); ?>" class="link-ok">出库</a>
<form method="post" class="inline-form" action="<?php echo site_url('PSI/sales_orders/destroy/' . $o['id']); ?>" data-confirm="确认删除该销售订单?">
<?php echo csrf_field(); ?><button class="link-danger" type="submit">删除</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($orders)): ?><tr><td colspan="9" class="muted" style="text-align:center;padding:24px">暂无销售订单</td></tr><?php endif; ?>
</tbody>
</table>
</div>
</div>
+30
View File
@@ -0,0 +1,30 @@
<?php
/** PSI 销售出库打印正文(由 psi_print_shell 包裹) */
/** @var array $s @var array $item */
$chn = ($s['channel'] ?? '') === 'export' ? '外贸出口' : '内销';
$amt = (float)($s['qty'] ?? 0) * (float)($s['price'] ?? 0);
?>
<h2>销售出库单</h2>
<div class="sub"><?php echo e($s['order_no']); ?></div>
<div class="meta">
<span><b>客户:</b><?php echo e($s['customer']); ?></span>
<span><b>渠道:</b><?php echo $chn . ($s['region'] ? ' / ' . e($s['region']) : ''); ?></span>
<span><b>批次/缸号:</b><?php echo e($s['batch_no'] ?: '—'); ?></span>
<span><b>出库日期:</b><?php echo e($s['created_at']); ?></span>
</div>
<table>
<thead><tr><th>物品</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th></tr></thead>
<tbody>
<tr>
<td><?php echo e($item['name'] ?? '—'); ?></td>
<td><?php echo e($item['spec'] ?? ''); ?></td>
<td><?php echo e($item['unit'] ?? ''); ?></td>
<td><?php echo e($s['qty'] ?? 0); ?></td>
<td>¥<?php echo e(number_format((float)($s['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format($amt, 2)); ?></td>
</tr>
</tbody>
<tfoot><tr><th colspan="5" style="text-align:right">合计</th><th>¥<?php echo e(number_format($amt, 2)); ?></th></tr></tfoot>
</table>
<?php if ($s['remark']): ?><p style="font-size:13px;color:#374151;margin-top:10px"><b>备注:</b><?php echo e($s['remark']); ?></p><?php endif; ?>
<div class="sign"><span>发货:____________</span><span>收货签字:____________</span></div>
+43
View File
@@ -0,0 +1,43 @@
<?php
/** PSI 销售出库详情 */
/** @var array $s @var array $item */
$chn = ($s['channel'] ?? '') === 'export' ? '外贸出口' : '内销';
$amt = (float)($s['qty'] ?? 0) * (float)($s['price'] ?? 0);
?>
<div class="page-head">
<div><h1>销售出库详情</h1><div class="desc">单号 <?php echo e($s['order_no']); ?></div></div>
<div style="display:flex;gap:10px">
<a class="btn-ghost" href="<?php echo site_url('PSI/sales/print/' . $s['id']); ?>" target="_blank"><?php echo admin_icon('printer', 16); ?> 打印</a>
<a class="btn-ghost" href="<?php echo site_url('PSI/sales'); ?>">← 返回</a>
</div>
</div>
<div class="panel two-col">
<table class="tbl">
<tr><th>客户</th><td><?php echo e($s['customer'] ?? '—'); ?></td></tr>
<tr><th>渠道</th><td><?php echo $chn; ?></td></tr>
<tr><th>区域</th><td><?php echo e($s['region'] ?: '—'); ?></td></tr>
<tr><th>批次/缸号</th><td><?php echo e($s['batch_no'] ?: '—'); ?></td></tr>
<tr><th>出库日期</th><td><?php echo e($s['created_at'] ?? '—'); ?></td></tr>
<tr><th>备注</th><td class="muted"><?php echo e($s['remark'] ?: '—'); ?></td></tr>
</table>
</div>
<div class="panel">
<div class="panel-bar"><h3>出库明细</h3><span class="muted">合计 ¥<?php echo e(number_format($amt, 2)); ?></span></div>
<div class="table-wrap">
<table class="tbl">
<thead><tr><th>物品</th><th>规格</th><th>单位</th><th>数量</th><th>单价</th><th>金额</th></tr></thead>
<tbody>
<tr>
<td><?php echo e($item['name'] ?? '—'); ?></td>
<td class="muted"><?php echo e($item['spec'] ?? ''); ?></td>
<td class="muted"><?php echo e($item['unit'] ?? ''); ?></td>
<td><?php echo e($s['qty'] ?? 0); ?></td>
<td>¥<?php echo e(number_format((float)($s['price'] ?? 0), 2)); ?></td>
<td>¥<?php echo e(number_format($amt, 2)); ?></td>
</tr>
</tbody>
</table>
</div>
</div>
+29
View File
@@ -0,0 +1,29 @@
<?php
/** PSI 库存流水 */
?>
<div class="page-head">
<h1>库存流水</h1>
<p class="sub">所有采购入库 / 销售出库 / 手动调整 的变动记录</p>
</div>
<div class="panel">
<table class="tbl">
<thead><tr><th>时间</th><th>类型</th><th>名称</th><th>方向</th><th>数量</th><th>批次/缸号</th><th>关联单号</th></tr></thead>
<tbody>
<?php foreach ($moves as $mv): ?>
<tr>
<td><?php echo e($mv['created_at'] ?? ''); ?></td>
<td><?php echo $mv['item_type'] === 'product' ? '成品' : '物料'; ?></td>
<td><?php echo e($nameMap[($mv['item_type'] ?? 'product') . ':' . ($mv['item_id'] ?? 0)] ?? ('#' . ($mv['item_id'] ?? ''))); ?></td>
<td>
<?php if (($mv['direction'] ?? '') === 'in'): ?><span class="tag-in">入库 +</span>
<?php else: ?><span class="tag-out">出库 -</span><?php endif; ?>
</td>
<td><?php echo e((float)($mv['qty'] ?? 0)); ?></td>
<td><?php echo e($mv['batch_no'] ?? ''); ?></td>
<td><?php echo e($mv['ref_no'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($moves)): ?><tr><td colspan="7" class="muted">暂无库存流水</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+42
View File
@@ -0,0 +1,42 @@
<?php
/** PSI 供应商表单 */
$s = $supplier;
$isEdit = !empty($s);
$typeOpts = ['面料商' => '面料商', '辅料商' => '辅料商', '成衣加工厂' => '成衣加工厂', 'OEM' => 'OEM', '物流' => '物流', '其他' => '其他'];
$gradeOpts = ['战略' => '战略', '合格' => '合格', '试用' => '试用', '淘汰' => '淘汰'];
$curType = $s['type'] ?? '';
$curGrade = $s['grade'] ?? '';
?>
<div class="page-head"><h1><?php echo $isEdit ? '编辑供应商' : '新增供应商'; ?></h1></div>
<div class="panel">
<form method="post" action="<?php echo site_url($isEdit ? 'PSI/suppliers/update/' . $s['id'] : 'PSI/suppliers/store'); ?>">
<?php echo csrf_field(); ?>
<div class="form-grid">
<div class="field"><label>名称 *</label><input name="name" value="<?php echo e($s['name'] ?? ''); ?>" required></div>
<div class="field"><label>供应商类型</label>
<select name="type">
<option value="">未设置</option>
<?php foreach ($typeOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curType === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>等级</label>
<select name="grade">
<option value="">未设置</option>
<?php foreach ($gradeOpts as $k => $v): ?>
<option value="<?php echo $k; ?>" <?php echo $curGrade === $k ? 'selected' : ''; ?>><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field"><label>联系人</label><input name="contact" value="<?php echo e($s['contact'] ?? ''); ?>"></div>
<div class="field"><label>电话</label><input name="phone" value="<?php echo e($s['phone'] ?? ''); ?>"></div>
<div class="field"><label>国家/地区</label><input name="country" value="<?php echo e($s['country'] ?? ''); ?>"></div>
<div class="field"><label>交期达成率(%)</label><input name="ontime_rate" type="number" step="0.01" value="<?php echo e($s['ontime_rate'] ?? '0'); ?>"></div>
<div class="field"><label>质检合格率(%)</label><input name="qc_rate" type="number" step="0.01" value="<?php echo e($s['qc_rate'] ?? '0'); ?>"></div>
</div>
<div class="field"><label>备注</label><textarea name="remark" rows="2"><?php echo e($s['remark'] ?? ''); ?></textarea></div>
<button class="btn-primary" type="submit">保存</button>
<a class="btn-soft" href="<?php echo site_url('PSI/suppliers'); ?>">取消</a>
</form>
</div>
+33
View File
@@ -0,0 +1,33 @@
<?php
/** PSI 供应商列表(面料商/辅料商/成衣加工厂/OEM) */
?>
<div class="page-head">
<h1>供应商管理</h1>
<a class="btn-primary" href="<?php echo site_url('PSI/suppliers/create'); ?>">+ 新增供应商</a>
</div>
<div class="panel">
<table class="tbl">
<thead><tr><th>ID</th><th>名称</th><th>类型</th><th>等级</th><th>联系人</th><th>电话</th><th>国家/地区</th><th>交期达成率</th><th>质检合格率</th><th>备注</th><th>操作</th></tr></thead>
<tbody>
<?php foreach ($suppliers as $s): ?>
<tr>
<td><?php echo e($s['id']); ?></td>
<td><?php echo e($s['name']); ?></td>
<td><?php echo e($s['type'] ?? ''); ?></td>
<td><?php echo e($s['grade'] ?? ''); ?></td>
<td><?php echo e($s['contact'] ?? ''); ?></td>
<td><?php echo e($s['phone'] ?? ''); ?></td>
<td><?php echo e($s['country'] ?? ''); ?></td>
<td><?php echo ($s['ontime_rate'] !== '' && (float)$s['ontime_rate'] != 0) ? e($s['ontime_rate']) . '%' : '—'; ?></td>
<td><?php echo ($s['qc_rate'] !== '' && (float)$s['qc_rate'] != 0) ? e($s['qc_rate']) . '%' : '—'; ?></td>
<td><?php echo e(mb_substr($s['remark'] ?? '', 0, 30)); ?></td>
<td class="row-actions">
<a href="<?php echo site_url('PSI/suppliers/edit/' . $s['id']); ?>">编辑</a>
<a href="<?php echo site_url('PSI/suppliers/destroy/' . $s['id']); ?>" data-confirm="确认删除?">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($suppliers)): ?><tr><td colspan="11" class="muted">暂无供应商</td></tr><?php endif; ?>
</tbody>
</table>
</div>
+65
View File
@@ -0,0 +1,65 @@
<?php
/** @var array $user @var string $sys @var string $sysName @var array $pages @var bool $edit */
$labelMap = [];
foreach (\subsys_nav($sys) as $it) { $labelMap[$it['k']] = $it['label']; }
$raw = $user[$sys . '_perms'] ?? '';
$dec = is_string($raw) && $raw !== '' ? @json_decode($raw, true) : [];
$cur = is_array($dec) ? array_keys(array_filter($dec)) : [];
$roleKey = $sys . '_role';
$role = $user[$roleKey] ?? 'user';
$upper = strtoupper($sys);
?>
<div class="page-head">
<h1><?php echo $edit ? '编辑用户' : '新增用户'; ?></h1>
<p class="sub"><?php echo e($sysName); ?></p>
</div>
<?php echo flash_html(); ?>
<div class="panel form-card">
<form method="post" action="<?php echo site_url($upper . '/users/' . ($edit ? 'update/' . $user['id'] : 'store')); ?>">
<?php echo csrf_field(); ?>
<div class="form-row">
<label>用户名 <span class="req">*</span></label>
<input type="text" name="username" value="<?php echo e($user['username'] ?? ''); ?>" <?php echo $edit ? 'readonly' : ''; ?> required>
<?php if ($edit): ?><small class="muted">用户名不可修改</small><?php endif; ?>
</div>
<div class="form-row">
<label>姓名</label>
<input type="text" name="name" value="<?php echo e($user['name'] ?? ''); ?>">
</div>
<div class="form-row">
<label>角色</label>
<div class="radio-row">
<label class="radio"><input type="radio" name="<?php echo e($roleKey); ?>" value="user" <?php echo $role !== 'admin' ? 'checked' : ''; ?>> 普通用户</label>
<label class="radio"><input type="radio" name="<?php echo e($roleKey); ?>" value="admin" <?php echo $role === 'admin' ? 'checked' : ''; ?>> 管理员(可管理本系统用户与权限)</label>
</div>
</div>
<div class="form-row">
<label>页面权限</label>
<div class="perm-grid">
<?php foreach ($pages as $p): ?>
<?php if ($p === 'dashboard') continue; ?>
<label class="check"><input type="checkbox" name="perm_<?php echo e($p); ?>" value="1" <?php echo in_array($p, $cur, true) ? 'checked' : ''; ?>> <?php echo e($labelMap[$p] ?? $p); ?></label>
<?php endforeach; ?>
</div>
<small class="muted">仪表盘对所有用户默认可见;未勾选的页面该用户不可访问(侧栏隐藏且不可直访)。</small>
</div>
<div class="form-row">
<label>密码 <?php if (!$edit): ?><span class="req">*</span><?php endif; ?></label>
<input type="password" name="password" autocomplete="new-password" <?php echo $edit ? '' : 'required'; ?>>
<small class="muted"><?php echo $edit ? '留空则保持原密码' : '请设置登录密码(至少 6 位)'; ?></small>
</div>
<div class="form-row">
<label>状态</label>
<div class="radio-row">
<label class="radio"><input type="radio" name="status" value="1" <?php echo (!empty($user['status'])) ? 'checked' : ''; ?>> 启用</label>
<label class="radio"><input type="radio" name="status" value="0" <?php echo (empty($user['status'])) ? 'checked' : ''; ?>> 停用</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn"><?php echo $edit ? '保存修改' : '创建用户'; ?></button>
<a class="btn-soft" href="<?php echo site_url($upper . '/users'); ?>">取消</a>
</div>
</form>
</div>
+25
View File
@@ -0,0 +1,25 @@
<?php
/** @var array $user @var string $sys @var string $sysName */
$upper = strtoupper($sys);
?>
<div class="page-head">
<h1>重置密码</h1>
<p class="sub"><?php echo e($sysName); ?> · 为用户 <b><?php echo e($user['username']); ?></b> 设置新密码</p>
</div>
<?php echo flash_html(); ?>
<div class="panel form-card">
<form method="post" action="<?php echo site_url($upper . '/users/reset/' . $user['id']); ?>">
<?php echo csrf_field(); ?>
<div class="form-row">
<label>新密码 <span class="req">*</span></label>
<input type="password" name="password" autocomplete="new-password" required>
<small class="muted">至少 6 位</small>
</div>
<div class="form-actions">
<button type="submit" class="btn">确认重置</button>
<a class="btn-soft" href="<?php echo site_url($upper . '/users'); ?>">取消</a>
</div>
</form>
</div>
+72
View File
@@ -0,0 +1,72 @@
<?php
/** @var array $users @var string $sys @var string $sysName @var array $pages */
$labelMap = [];
foreach (\subsys_nav($sys) as $it) { $labelMap[$it['k']] = $it['label']; }
$roleLabels = ['super_admin' => '超级管理员', 'admin' => '管理员', 'user' => '普通用户', 'none' => '无'];
$upper = strtoupper($sys);
?>
<div class="page-head">
<h1>用户管理</h1>
<p class="sub"><?php echo e($sysName); ?> · 管理系统内的用户账号及其页面访问权限</p>
</div>
<?php echo flash_html(); ?>
<div class="panel">
<div class="panel-bar">
<h3>账号列表</h3>
<a class="btn" href="<?php echo site_url($upper . '/users/create'); ?>"><?php echo admin_icon('user-plus', 16); ?> 新增用户</a>
</div>
<div class="table-wrap">
<table class="tbl">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>姓名</th>
<th>角色</th>
<th>页面权限</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<?php
$raw = $u[$sys . '_perms'] ?? '';
$dec = is_string($raw) && $raw !== '' ? @json_decode($raw, true) : [];
$perms = is_array($dec) ? array_keys(array_filter($dec)) : [];
$role = $u[$sys . '_role'] ?? 'user';
?>
<tr>
<td><?php echo (int)$u['id']; ?></td>
<td><?php echo e($u['username']); ?></td>
<td><?php echo e($u['name'] ?? ''); ?></td>
<td><span class="role-badge role-<?php echo e($role); ?>"><?php echo e($roleLabels[$role] ?? $role); ?></span></td>
<td class="perm-cell">
<?php if (empty($perms)): ?>
<span class="muted">仅仪表盘</span>
<?php else: foreach ($perms as $p): ?>
<span class="perm-chip"><?php echo e($labelMap[$p] ?? $p); ?></span>
<?php endforeach; endif; ?>
</td>
<td>
<?php if (!empty($u['status'])): ?><span class="st st-on">启用</span><?php else: ?><span class="st st-off">停用</span><?php endif; ?>
</td>
<td class="row-actions">
<a href="<?php echo site_url($upper . '/users/edit/' . $u['id']); ?>">编辑</a>
<a href="<?php echo site_url($upper . '/users/reset/' . $u['id']); ?>">重置密码</a>
<form method="post" class="inline-form" action="<?php echo site_url($upper . '/users/destroy/' . $u['id']); ?>" data-confirm="确认删除该用户?">
<?php echo csrf_field(); ?>
<button type="submit" class="link-danger">删除</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($users)): ?>
<tr><td colspan="7" class="muted" style="text-align:center;padding:24px">暂无用户</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>