531 lines
24 KiB
PHP
531 lines
24 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* ============================================================================
|
||
* TripLog Web 安装向导(独立入口,不加载框架)
|
||
* ============================================================================
|
||
*
|
||
* 流程:
|
||
* 1) 已安装检测 —— config.php 存在且 db.name 非空 → 拒绝再装(config.php 本身就是锁);
|
||
* 2) 环境检查 —— PHP 版本、扩展、目录可写;
|
||
* 3) 表单 —— 网站标题 / 家庭名称 / 数据库 / 高德 / 管理员;
|
||
* 4) 执行 —— 测库连接 → 跑 sql/schema.sql 全量建表 → src/Migrate.php 幂等迁移
|
||
* → 写入基本种子数据(家庭、管理员、节点类型、角色标签、出行方式);
|
||
* 5) 生成 config.php(临时文件 include 验证后 rename)→ 自动删除本文件。
|
||
*
|
||
* 安全设计:
|
||
* * 「已安装」以 config.php 为准,安装器重复访问无害;
|
||
* * 自删失败只提示手动删除,不阻塞使用;
|
||
* * 所有输出经 e() 转义;CSRF token;密码不明文回显;
|
||
* * config.php 写入前先写临时文件并 include 验证,失败即删,绝不留下半残配置。
|
||
*/
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 0) 已安装检测(最先做,不做任何环境假设)
|
||
// ---------------------------------------------------------------------------
|
||
$ROOT = __DIR__;
|
||
$CFG_FILE = $ROOT . '/config.php';
|
||
$installed = false;
|
||
if (is_file($CFG_FILE)) {
|
||
$cfgProbe = @include $CFG_FILE;
|
||
$installed = is_array($cfgProbe) && !empty($cfgProbe['db']['name'] ?? null);
|
||
unset($cfgProbe);
|
||
}
|
||
unset($CFG_FILE);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 1) 会话与工具
|
||
// ---------------------------------------------------------------------------
|
||
session_start();
|
||
if (empty($_SESSION['install_csrf'])) {
|
||
$_SESSION['install_csrf'] = bin2hex(random_bytes(16));
|
||
}
|
||
|
||
/** HTML 转义 */
|
||
function e(string $s): string
|
||
{
|
||
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
|
||
}
|
||
|
||
/** 手机号加密 —— 必须与 src/Controllers/UserController::encrypt() 保持一致 */
|
||
function install_encrypt(string $plain): string
|
||
{
|
||
$key = getenv('APP_ENC_KEY') ?: 'family-trip-default-key-change-me';
|
||
$iv = random_bytes(16);
|
||
$cipher = openssl_encrypt($plain, 'AES-256-CBC', hash('sha256', $key, true), OPENSSL_RAW_DATA, $iv);
|
||
return base64_encode($iv . $cipher);
|
||
}
|
||
|
||
/** 环境检查:返回 [项名, 是否通过, 说明] 列表 */
|
||
function env_checks(string $root): array
|
||
{
|
||
$exts = ['pdo_mysql', 'openssl', 'gd', 'curl', 'mbstring', 'json', 'session'];
|
||
$rows = [];
|
||
$rows[] = ['PHP 版本 ≥ 7.4', version_compare(PHP_VERSION, '7.4.0', '>='), '当前 ' . PHP_VERSION];
|
||
foreach ($exts as $ext) {
|
||
$rows[] = ["扩展 {$ext}", extension_loaded($ext), extension_loaded($ext) ? '已加载' : '未安装'];
|
||
}
|
||
$rows[] = ['站点根目录可写(用于生成 config.php)', is_writable($root), is_writable($root) ? '可写' : '不可写'];
|
||
$rows[] = ['uploads/ 目录可写(用于存放照片)', is_writable($root . '/uploads'), is_writable($root . '/uploads') ? '可写' : '不可写'];
|
||
$rows[] = ['sql/schema.sql 存在', is_file($root . '/sql/schema.sql'), is_file($root . '/sql/schema.sql') ? '存在' : '缺失'];
|
||
return $rows;
|
||
}
|
||
|
||
/** 把 schema.sql 拆成可逐条执行的 SQL(剥离注释行、过滤空段) */
|
||
function schema_statements(string $root): array
|
||
{
|
||
$raw = (string)file_get_contents($root . '/sql/schema.sql');
|
||
$parts = preg_split('/;\s*[\r\n]+/', $raw) ?: [];
|
||
$out = [];
|
||
foreach ($parts as $part) {
|
||
// 去掉每段里以 -- 开头的注释行
|
||
$lines = preg_grep('/^\s*--/', explode("\n", $part), PREG_GREP_INVERT) ?: [];
|
||
$stmt = trim(implode("\n", $lines));
|
||
if ($stmt !== '' && stripos($stmt, 'CREATE TABLE') === 0) {
|
||
$out[] = $stmt . ';';
|
||
}
|
||
}
|
||
return $out;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 2) 已安装:直接拒绝
|
||
// ---------------------------------------------------------------------------
|
||
if ($installed) {
|
||
?><!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>TripLog 安装</title></head>
|
||
<body style="font-family:system-ui,-apple-system,'PingFang SC','Microsoft YaHei',sans-serif;background:#f4f6fa;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;">
|
||
<div style="background:#fff;border-radius:14px;padding:40px 44px;max-width:460px;box-shadow:0 8px 30px rgba(30,50,90,.10);text-align:center;">
|
||
<div style="font-size:44px;">✅</div>
|
||
<h1 style="font-size:20px;margin:12px 0 8px;">TripLog 已安装</h1>
|
||
<p style="color:#6b7686;font-size:14px;line-height:1.7;margin:0 0 20px;">
|
||
检测到站点已完成安装(config.php 已存在)。<br>
|
||
为安全起见,安装向导已停用。<br>
|
||
如果 <code>install.php</code> 仍在站点目录中,请手动删除它。
|
||
</p>
|
||
<a href="index.php" style="display:inline-block;background:#3d6bf5;color:#fff;text-decoration:none;border-radius:8px;padding:10px 28px;font-size:14px;">进入首页</a>
|
||
</div></body></html><?php
|
||
exit;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 3) 安装执行(POST)
|
||
// ---------------------------------------------------------------------------
|
||
$errors = [];
|
||
$values = [
|
||
'site_name' => '',
|
||
'family_name' => '我的家庭',
|
||
'db_host' => '127.0.0.1',
|
||
'db_port' => '3306',
|
||
'db_name' => '',
|
||
'db_user' => '',
|
||
'db_pass' => '',
|
||
'amap_key' => '',
|
||
'amap_sec' => '',
|
||
'amap_nav_src' => 'trip.example.com',
|
||
'admin_name' => '',
|
||
'admin_phone' => '',
|
||
];
|
||
|
||
$done = false;
|
||
$removed = false; // install.php 是否自删成功
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$token = (string)($_POST['_csrf'] ?? '');
|
||
if (!hash_equals($_SESSION['install_csrf'], $token)) {
|
||
$errors[] = '页面已过期,请刷新后重试。';
|
||
} else {
|
||
// ---- 收集(密码类不回填,单独处理)----
|
||
foreach ($values as $k => $_) {
|
||
if (isset($_POST[$k]) && is_string($_POST[$k])) {
|
||
$values[$k] = trim($_POST[$k]);
|
||
}
|
||
}
|
||
$adminPass = (string)($_POST['admin_pass'] ?? '');
|
||
$adminPass2 = (string)($_POST['admin_pass2'] ?? '');
|
||
$dbPass = $values['db_pass'];
|
||
|
||
// ---- 校验 ----
|
||
if ($values['site_name'] === '') {
|
||
$errors[] = '网站标题不能为空';
|
||
} elseif (mb_strlen($values['site_name']) > 50) {
|
||
$errors[] = '网站标题不能超过 50 个字符';
|
||
}
|
||
if ($values['family_name'] === '') {
|
||
$values['family_name'] = '我的家庭';
|
||
} elseif (mb_strlen($values['family_name']) > 30) {
|
||
$errors[] = '家庭名称不能超过 30 个字符';
|
||
}
|
||
if (!preg_match('/^\d{1,5}$/', $values['db_port']) || (int)$values['db_port'] < 1 || (int)$values['db_port'] > 65535) {
|
||
$errors[] = '数据库端口不正确';
|
||
}
|
||
if (!preg_match('/^[A-Za-z0-9_]+$/', $values['db_name'])) {
|
||
$errors[] = '数据库名只能包含字母、数字和下划线';
|
||
}
|
||
if ($values['db_user'] === '') {
|
||
$errors[] = '数据库用户名不能为空';
|
||
}
|
||
if ($values['admin_name'] === '') {
|
||
$errors[] = '管理员姓名不能为空';
|
||
}
|
||
if (!preg_match('/^1[3-9]\d{9}$/', $values['admin_phone'])) {
|
||
$errors[] = '管理员手机号格式不正确(11 位,1 开头)';
|
||
}
|
||
if (strlen($adminPass) < 6) {
|
||
$errors[] = '管理员密码至少 6 位';
|
||
}
|
||
if ($adminPass !== $adminPass2) {
|
||
$errors[] = '两次输入的管理员密码不一致';
|
||
}
|
||
if ($values['amap_nav_src'] === '') {
|
||
$values['amap_nav_src'] = 'trip.example.com';
|
||
}
|
||
|
||
// ---- 执行安装 ----
|
||
if (!$errors) {
|
||
$pdo = null;
|
||
try {
|
||
$dsn = sprintf(
|
||
'mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4',
|
||
$values['db_host'], (int)$values['db_port'], $values['db_name']
|
||
);
|
||
$pdo = new PDO($dsn, $values['db_user'], $dbPass, [
|
||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||
]);
|
||
} catch (PDOException $ex) {
|
||
$code = (int)$ex->getCode();
|
||
$msg = $ex->getMessage();
|
||
if (strpos($msg, '1049') !== false || $code === 1049
|
||
|| strpos($msg, '1044') !== false || $code === 1044) {
|
||
// MySQL 8 对不存在的库报 1049,MariaDB 报 1044(Access denied)——
|
||
// 对用户来说两种情况都是「库没建好或账号不对」,合并成一条可操作的提示
|
||
$errors[] = '数据库不存在,或该用户无权访问「' . $values['db_name'] . '」:请先在 MySQL 里创建名为「'
|
||
. $values['db_name'] . '」的空数据库(CREATE DATABASE ' . $values['db_name']
|
||
. ' DEFAULT CHARACTER SET utf8mb4;)并把它的全部权限授予刚才填写的用户,再回到本页重试。';
|
||
} elseif (strpos($msg, '1045') !== false || $code === 1045) {
|
||
$errors[] = '无法连接数据库主机「' . $values['db_host'] . '」,请确认主机地址与端口。';
|
||
} else {
|
||
$errors[] = '数据库连接失败:' . $msg;
|
||
}
|
||
}
|
||
|
||
if ($pdo) {
|
||
try {
|
||
// ---- 建表(schema.sql 全量 + Migrate 增量,均幂等)----
|
||
$stmts = schema_statements($ROOT);
|
||
if (count($stmts) < 10) {
|
||
throw new RuntimeException('sql/schema.sql 内容异常(仅解析出 ' . count($stmts) . ' 条建表语句)');
|
||
}
|
||
foreach ($stmts as $stmt) {
|
||
$pdo->exec($stmt);
|
||
}
|
||
require $ROOT . '/src/Migrate.php';
|
||
\App\Migrate::run($pdo);
|
||
|
||
// ---- 种子数据(一个事务;均只在空表时插入,重复执行不重复)----
|
||
$pdo->beginTransaction();
|
||
$familyName = $values['family_name'];
|
||
$pdo->prepare('INSERT INTO families (name) VALUES (?)')->execute([$familyName]);
|
||
$familyId = (int)$pdo->lastInsertId();
|
||
|
||
$pdo->prepare('INSERT INTO users
|
||
(family_id, name, phone_encrypted, phone_hash, password_hash, role, status)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)')
|
||
->execute([
|
||
$familyId,
|
||
$values['admin_name'],
|
||
install_encrypt($values['admin_phone']),
|
||
hash('sha256', $values['admin_phone']),
|
||
password_hash($adminPass, PASSWORD_DEFAULT),
|
||
'admin',
|
||
'active',
|
||
]);
|
||
|
||
// 节点类型(内置,不可删除)
|
||
$insType = $pdo->prepare('INSERT IGNORE INTO node_types (family_id, name, icon, is_system) VALUES (?, ?, ?, 1)');
|
||
foreach ([
|
||
['游玩', '🎡'], ['用餐', '🍜'], ['住宿', '🏨'], ['赶路', '🚄'], ['休息', '☕'],
|
||
] as $t) {
|
||
$insType->execute([$familyId, $t[0], $t[1]]);
|
||
}
|
||
|
||
// 角色标签
|
||
$insTag = $pdo->prepare('INSERT IGNORE INTO role_tags (family_id, name, icon) VALUES (?, ?, ?)');
|
||
foreach ([
|
||
['司机', '🚗'], ['摄影担当', '📷'], ['向导', '🗺️'], ['财务', '💰'],
|
||
] as $t) {
|
||
$insTag->execute([$familyId, $t[0], $t[1]]);
|
||
}
|
||
|
||
// 出行方式:不含自驾两种(自驾需先在「车辆管理」里添加车辆,
|
||
// travel_modes.vehicle_id 外键指向车辆,空挂没有意义)
|
||
$insMode = $pdo->prepare('INSERT IGNORE INTO travel_modes (family_id, mode_type) VALUES (?, ?)');
|
||
foreach (['train', 'plane', 'bus', 'subway', 'taxi', 'bike', 'walk', 'ship', 'other'] as $m) {
|
||
$insMode->execute([$familyId, $m]);
|
||
}
|
||
|
||
$pdo->commit();
|
||
|
||
// ---- 生成 config.php(临时文件 → include 验证 → rename)----
|
||
$configArr = [
|
||
'db' => [
|
||
'host' => $values['db_host'],
|
||
'port' => (int)$values['db_port'],
|
||
'name' => $values['db_name'],
|
||
'user' => $values['db_user'],
|
||
'pass' => $dbPass,
|
||
'charset' => 'utf8mb4',
|
||
],
|
||
'app' => [
|
||
'name' => $values['site_name'],
|
||
'timezone' => 'Asia/Shanghai',
|
||
],
|
||
'amap' => [
|
||
'key' => $values['amap_key'],
|
||
'security_js' => $values['amap_sec'],
|
||
'nav_src' => $values['amap_nav_src'],
|
||
],
|
||
];
|
||
$php = "<?php\n"
|
||
. "/**\n"
|
||
. " * 由 TripLog 安装向导生成于 " . date('Y-m-d H:i:s') . "\n"
|
||
. " * 结构与 config.example.php 一致;手工调整后注意保持键完整。\n"
|
||
. " */\n"
|
||
. 'return ' . var_export($configArr, true) . ";\n";
|
||
$tmp = $ROOT . '/config.install-tmp.php';
|
||
if (@file_put_contents($tmp, $php) === false) {
|
||
throw new RuntimeException('无法写入配置文件(站点根目录不可写)。请把 Web 运行账号对该目录授予写权限后重试。');
|
||
}
|
||
$check = @include $tmp;
|
||
if (!is_array($check) || empty($check['db']['name']) || empty($check['app']['name'])) {
|
||
@unlink($tmp);
|
||
throw new RuntimeException('生成的配置文件校验未通过,已中止。请重试安装。');
|
||
}
|
||
if (!@rename($tmp, $ROOT . '/config.php')) {
|
||
@unlink($tmp);
|
||
throw new RuntimeException('配置文件落位失败(无法重命名为 config.php)。请检查站点根目录写权限后重试。');
|
||
}
|
||
|
||
// ---- 自删 install.php(失败不阻塞,config.php 已是锁)----
|
||
$removed = @unlink(__FILE__);
|
||
|
||
$done = true;
|
||
} catch (Throwable $ex) {
|
||
if ($pdo && $pdo->inTransaction()) {
|
||
$pdo->rollBack();
|
||
}
|
||
$errors[] = $ex instanceof PDOException
|
||
? '安装执行失败:' . $ex->getMessage()
|
||
: $ex->getMessage();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 4) 结果页 / 表单页
|
||
// ---------------------------------------------------------------------------
|
||
$checks = env_checks($ROOT);
|
||
$envOk = true;
|
||
foreach ($checks as $c) {
|
||
if (!$c[1]) { $envOk = false; break; }
|
||
}
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="robots" content="noindex,nofollow">
|
||
<title>TripLog 安装向导</title>
|
||
<style>
|
||
* { box-sizing: border-box; }
|
||
body { margin:0; font-family:system-ui,-apple-system,'PingFang SC','Microsoft YaHei',sans-serif;
|
||
background:#f4f6fa; color:#243041; font-size:14px; }
|
||
.wrap { max-width:640px; margin:0 auto; padding:36px 16px 60px; }
|
||
.head { text-align:center; margin-bottom:26px; }
|
||
.head .logo { font-size:0; }
|
||
.head .logo img { width:44px; height:44px; display:inline-block; }
|
||
.head h1 { font-size:22px; margin:8px 0 4px; }
|
||
.head p { color:#6b7686; margin:0; }
|
||
.card { background:#fff; border-radius:14px; padding:24px 26px; margin-bottom:18px;
|
||
box-shadow:0 8px 30px rgba(30,50,90,.10); }
|
||
.card h2 { font-size:15px; margin:0 0 14px; padding-bottom:10px; border-bottom:1px solid #eef1f6; }
|
||
.check-row { display:flex; align-items:center; gap:10px; padding:5px 0; }
|
||
.check-ok { color:#178a50; }
|
||
.check-bad { color:#c6362f; }
|
||
.check-note { color:#98a2b3; font-size:12px; margin-left:auto; }
|
||
.field { margin-bottom:14px; }
|
||
.field label { display:block; font-weight:600; margin-bottom:5px; }
|
||
.field label .req { color:#c6362f; }
|
||
.field input { width:100%; border:1px solid #d8dfe9; border-radius:8px; padding:9px 12px;
|
||
font-size:14px; background:#fbfcfe; }
|
||
.field input:focus { outline:none; border-color:#3d6bf5; background:#fff; }
|
||
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:0 14px; }
|
||
.hint { color:#8a94a6; font-size:12px; margin-top:4px; line-height:1.6; }
|
||
.btn { display:block; width:100%; background:#3d6bf5; color:#fff; border:0; border-radius:9px;
|
||
padding:12px; font-size:15px; font-weight:600; cursor:pointer; }
|
||
.btn:disabled { background:#b9c6dd; cursor:not-allowed; }
|
||
.btn-go { display:inline-block; background:#3d6bf5; color:#fff; text-decoration:none;
|
||
border-radius:8px; padding:11px 34px; font-size:15px; font-weight:600; }
|
||
.errors { background:#fdf1f0; border:1px solid #f3c2be; color:#a1322b; border-radius:9px;
|
||
padding:12px 16px; margin-bottom:18px; line-height:1.8; }
|
||
.okbox { text-align:center; padding:14px 0 4px; }
|
||
.okbox .big { font-size:48px; }
|
||
.okbox h1 { font-size:21px; margin:10px 0 6px; }
|
||
.okbox p { color:#6b7686; line-height:1.8; margin:0 0 22px; }
|
||
code { background:#eef2f8; border-radius:4px; padding:1px 6px; font-size:12.5px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<div class="head">
|
||
<div class="logo"><img src="assets/icons/trip-log.svg" alt="" width="44" height="44"></div>
|
||
<h1>TripLog 安装向导</h1>
|
||
<p>家庭旅行日记 · 首次部署配置</p>
|
||
</div>
|
||
|
||
<?php if ($done): ?>
|
||
<div class="card">
|
||
<div class="okbox">
|
||
<div class="big">🎉</div>
|
||
<h1>安装完成!</h1>
|
||
<p>
|
||
数据表已创建,基本数据已就绪(节点类型、角色标签、出行方式)。<br>
|
||
请使用刚才设置的管理员手机号和密码登录。<br>
|
||
<?php if (!$removed): ?>
|
||
<span style="color:#c6362f;">⚠️ 安装文件 install.php 未能自动删除,请手动删除它。</span><br>
|
||
<?php endif; ?>
|
||
</p>
|
||
<a class="btn-go" href="index.php">开始使用</a>
|
||
</div>
|
||
</div>
|
||
|
||
<?php else: ?>
|
||
|
||
<?php if ($errors): ?>
|
||
<div class="errors">
|
||
<strong>安装未完成,请修正以下问题:</strong><br>
|
||
<?php foreach ($errors as $err): ?>· <?= e($err) ?><br><?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="card">
|
||
<h2>环境检查</h2>
|
||
<?php foreach ($checks as [$label, $ok, $note]): ?>
|
||
<div class="check-row">
|
||
<span class="<?= $ok ? 'check-ok' : 'check-bad' ?>"><?= $ok ? '✓' : '✗' ?></span>
|
||
<span><?= e($label) ?></span>
|
||
<span class="check-note"><?= e($note) ?></span>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<form method="post" autocomplete="off">
|
||
<input type="hidden" name="_csrf" value="<?= e($_SESSION['install_csrf']) ?>">
|
||
|
||
<div class="card">
|
||
<h2>站点信息</h2>
|
||
<div class="field">
|
||
<label>网站标题 <span class="req">*</span></label>
|
||
<input type="text" name="site_name" maxlength="50" required
|
||
placeholder="例如:我们一家的旅行日记"
|
||
value="<?= e($values['site_name']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>家庭名称</label>
|
||
<input type="text" name="family_name" maxlength="30"
|
||
placeholder="我的家庭" value="<?= e($values['family_name']) ?>">
|
||
<div class="hint">所有成员、行程、花销都归属这个家庭;之后还可以在后台调整。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>数据库(MySQL)</h2>
|
||
<div class="hint" style="margin:0 0 12px;">
|
||
请先在 MySQL 中创建一个<strong>空的</strong>数据库,表结构由安装向导自动生成。
|
||
</div>
|
||
<div class="grid2">
|
||
<div class="field">
|
||
<label>主机 <span class="req">*</span></label>
|
||
<input type="text" name="db_host" required value="<?= e($values['db_host']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>端口 <span class="req">*</span></label>
|
||
<input type="text" name="db_port" required value="<?= e($values['db_port']) ?>">
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>数据库名 <span class="req">*</span></label>
|
||
<input type="text" name="db_name" required placeholder="trip"
|
||
value="<?= e($values['db_name']) ?>">
|
||
</div>
|
||
<div class="grid2">
|
||
<div class="field">
|
||
<label>用户名 <span class="req">*</span></label>
|
||
<input type="text" name="db_user" required value="<?= e($values['db_user']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>密码 <span class="req">*</span></label>
|
||
<input type="password" name="db_pass" autocomplete="new-password">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>高德开放平台(可稍后在 config.php 中补充)</h2>
|
||
<div class="hint" style="margin:0 0 12px;">
|
||
用于地图展示与地点搜索。<a href="https://lbs.amap.com/" target="_blank" rel="noopener">lbs.amap.com</a>
|
||
→ 控制台 → 应用管理 → 创建应用(Web端(JS API) + Web服务)后填写。
|
||
</div>
|
||
<div class="field">
|
||
<label>Key(Web端 JS API)</label>
|
||
<input type="text" name="amap_key" value="<?= e($values['amap_key']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>安全密钥(securityJsCode)</label>
|
||
<input type="text" name="amap_sec" value="<?= e($values['amap_sec']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>导航来源标识(src)</label>
|
||
<input type="text" name="amap_nav_src" value="<?= e($values['amap_nav_src']) ?>">
|
||
<div class="hint">仅用于高德侧的调用来源统计,填任意英数字串均可,通常填你的域名。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>管理员账号</h2>
|
||
<div class="field">
|
||
<label>姓名 <span class="req">*</span></label>
|
||
<input type="text" name="admin_name" maxlength="50" required
|
||
value="<?= e($values['admin_name']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>手机号 <span class="req">*</span></label>
|
||
<input type="text" name="admin_phone" maxlength="11" required placeholder="11 位手机号(用于登录)"
|
||
value="<?= e($values['admin_phone']) ?>">
|
||
</div>
|
||
<div class="grid2">
|
||
<div class="field">
|
||
<label>密码 <span class="req">*</span></label>
|
||
<input type="password" name="admin_pass" minlength="6" required autocomplete="new-password">
|
||
</div>
|
||
<div class="field">
|
||
<label>确认密码 <span class="req">*</span></label>
|
||
<input type="password" name="admin_pass2" minlength="6" required autocomplete="new-password">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn" type="submit" <?= $envOk ? '' : 'disabled' ?>>
|
||
<?= $envOk ? '开始安装' : '环境检查未通过,无法安装' ?>
|
||
</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
</body>
|
||
</html>
|