文件
huaqiapp/server/application/admin/library/FormBuilder.php
linda 56f5f60aa5 feat(admin): 后台登录页品牌化 + 26 个业务模块与前端对齐 + 数据同步打通
一、登录页重构(暖色调品牌版)
- 左右分栏卡片:44% 品牌面板(花红渐变 + 光斑 + 内联 SVG 花瓣 Logo + 花期字标/口号)
  + 56% 表单面板(52px 输入框、内嵌矢量图标、聚焦暖色光晕、渐变主按钮)
- 与前台同一套设计令牌(#E8604C / #F58C7C / #FFF9F5 / #3E2C27),响应式适配
- 完整保留 #login-form/#pd-form-username/#profile-img/#errtips/token/验证码/保持登录等结构,后台登录 JS 零改动

二、后台业务模块(26 个,与前端 M1–M9 一一对应)
- 新增通用 Manage 控制器(列表/搜索/排序/新增/编辑/软删除/批量/列元数据)
- 新增 ManageConfig(26 张表的字段标签/类型/枚举/搜索配置)与 FormBuilder(表单控件渲染)
- 新增通用视图 manage/{index,add,edit,style}.html + public/assets/js/backend/manage.js
- 新增 26 个薄控制器(各 8 行,仅声明 )
- db/05_admin_menu.sql:花期运营菜单树 + 6 个通用操作权限,含站点名品牌化

三、品牌一致性
- site.php 站点名改为「花期运营中心」,顶栏 Logo 增加矢量花瓣
- 业务页暖色表头、渐变按钮、圆角输入、品牌色开关

四、验证(全绿)
- 后台自检 31 项:登录页品牌、框架菜单、26 模块页面 200 + 列表数据、后台编辑写库、
  后台改动 → 前台 API 同步读取、数据还原
- 前台接口 82 项断言、契约 84 接口、Vue 20/20、SCSS 19/19、无 emoji 均通过
- 修复 5 个真实问题:方法签名不兼容致 500、菜单缓存、SQL 字符集乱码、
  snake_case URL 与权限规则命名、会话文件属主导致的自检 500
2026-11-06 11:10:19 +08:00

105 行
4.4 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\admin\library;
/**
* 通用表单渲染器(运营后台)
*
* 根据 ManageConfig 中的字段定义渲染表单控件,保持所有业务模块表单风格一致。
*/
class FormBuilder
{
/**
* @param array $fields 字段定义(name => def)
* @param array $row 当前数据
* @param array $users 用户下拉数据
* @return string
*/
public static function render($fields, $row = [], $users = [])
{
$html = '';
foreach ($fields as $name => $def) {
if (isset($def['readonly']) && $def['readonly']) {
continue;
}
if (isset($def['form']) && $def['form'] === false) {
continue;
}
$html .= self::group($name, $def, $row, $users);
}
return $html;
}
protected static function group($name, $def, $row, $users)
{
$label = htmlspecialchars($def['label'] ?? $name);
$type = $def['type'] ?? 'text';
$value = array_key_exists($name, $row) ? $row[$name] : '';
$control = self::control($name, $type, $def, $value, $users);
$hint = isset($def['hint']) ? '<span class="help-block hq-hint">' . htmlspecialchars($def['hint']) . '</span>' : '';
return '<div class="form-group hq-form-group">'
. '<label class="control-label col-xs-12 col-sm-2">' . $label . '</label>'
. '<div class="col-xs-12 col-sm-8">' . $control . $hint . '</div>'
. '</div>';
}
protected static function control($name, $type, $def, $value, $users)
{
$n = htmlspecialchars($name);
$v = is_string($value) ? htmlspecialchars($value) : $value;
switch ($type) {
case 'textarea':
return '<textarea class="form-control hq-input" rows="4" name="' . $n . '" placeholder="' . htmlspecialchars($def['label'] ?? '') . '">'
. htmlspecialchars((string)$value) . '</textarea>';
case 'select':
$options = $def['options'] ?? [];
if (is_string($options)) {
$options = ManageConfig::$enums[$options] ?? [];
}
$html = '<select class="form-control hq-input" name="' . $n . '">';
if (!isset($def['required']) || !$def['required']) {
$html .= '<option value="">请选择</option>';
}
foreach ($options as $key => $text) {
$selected = ((string)$value === (string)$key) ? ' selected' : '';
$html .= '<option value="' . htmlspecialchars((string)$key) . '"' . $selected . '>' . htmlspecialchars($text) . '</option>';
}
return $html . '</select>';
case 'bool':
$checked = $value ? ' checked' : '';
return '<div class="hq-switch-wrap"><input type="hidden" name="' . $n . '" value="0"/>'
. '<label class="hq-switch"><input type="checkbox" name="' . $n . '" value="1"' . $checked . '/><span></span></label></div>';
case 'number':
return '<input type="number" step="1" class="form-control hq-input" name="' . $n . '" value="' . $v . '"/>';
case 'decimal':
return '<input type="number" step="0.01" class="form-control hq-input" name="' . $n . '" value="' . $v . '"/>';
case 'date':
return '<input type="date" class="form-control hq-input" name="' . $n . '" value="' . $v . '"/>';
case 'datetime':
return '<input type="datetime-local" class="form-control hq-input" name="' . $n . '" value="' . $v . '"/>';
case 'user':
$html = '<select class="form-control hq-input" name="' . $n . '">';
if (!$users) {
$html .= '<option value="">暂无用户</option>';
}
foreach ($users as $uid => $text) {
$selected = ((string)$value === (string)$uid) ? ' selected' : '';
$html .= '<option value="' . htmlspecialchars((string)$uid) . '"' . $selected . '>' . htmlspecialchars($text) . '</option>';
}
return $html . '</select>';
default:
return '<input type="text" class="form-control hq-input" name="' . $n . '" value="' . $v . '" placeholder="' . htmlspecialchars($def['label'] ?? '') . '"/>';
}
}
}