diff --git a/app/BaseApp/BaseController.php b/app/BaseApp/BaseController.php index 1e091bc6..26f16be3 100755 --- a/app/BaseApp/BaseController.php +++ b/app/BaseApp/BaseController.php @@ -141,13 +141,19 @@ class BaseController $result = []; foreach ($checkFields as $fieldName) { $checkValue = $data[$fieldName] ?? ''; - if (is_array($checkValue) && !empty($checkValue)) { - $result[$fieldName] = $checkValue; + if (is_array($checkValue)) { + // 数组值单独处理:非空才收;空数组按「跳过」(与 GF 版一致), + // 且绝不对数组做 strval,避免触发 "Array to string conversion" 警告 + if (!empty($checkValue)) { + $result[$fieldName] = $checkValue; + } } else { if (mb_strlen(strval($checkValue)) <= 0 && !in_array($fieldName, $this->notRequest)) { $requiredFields[] = $fieldName; } else { - if (!empty($checkValue) || $checkValue === 0) { + // 用字符串长度判断是否有值,避免 !empty 把合法的 0 / "0" 误当空丢弃 + // (HTTP 参数皆为字符串,status=0 会传成 "0",原写法会导致状态无法写入/改回启用) + if (mb_strlen(strval($checkValue)) > 0) { $result[$fieldName] = $checkValue; } } diff --git a/app/Http/Controllers/core/CodeGenerationController.php b/app/Http/Controllers/core/CodeGenerationController.php index 83e30d62..0c324346 100644 --- a/app/Http/Controllers/core/CodeGenerationController.php +++ b/app/Http/Controllers/core/CodeGenerationController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\core; use App\BaseApp\BaseController; use App\Service\common\ai\AiCodeGenService; use App\Service\common\ai\AiRequirementPromptService; +use App\Service\common\JWTService; use App\Service\core\CodeGenerationService; use Illuminate\Http\JsonResponse; @@ -15,6 +16,11 @@ class CodeGenerationController extends BaseController public function __construct() { parent::__construct(); + // 代码生成会写后端源码、改路由、执行建表 DDL,属超管工具,必须登录后使用。 + // 本控制器的 CodeGenerationService 未继承 BaseService,故在此显式校验登录态 + // (Bearer JWT + Redis 会话),口径与 BaseService 构造中的鉴权一致; + // autoRouteRegister 用 ReflectionClass 读方法、不实例化控制器,故不会在启动时误触发鉴权 + JWTService::getInstance()->getToken()->getUserInfo(); $this->service = CodeGenerationService::getInstance(); } diff --git a/app/Service/AdminService.php b/app/Service/AdminService.php index 4beb08d6..6cf45d4b 100644 --- a/app/Service/AdminService.php +++ b/app/Service/AdminService.php @@ -96,7 +96,17 @@ class AdminService extends BaseService { $params['ip_table'] = json_encode([]); $params['password'] = password_hash($params['password'], PASSWORD_DEFAULT); - $existsUser = $this->model::where('phone', $params['phone'])->whereOr('email', $params['email'])->exists(); + // 手机号或邮箱任一重复都拒绝,且必须排除软删记录(deleted_at=0)。 + // 原写法 whereOr('email',...) 是 Laravel 动态 where 的空操作(等于只按 phone 判断且不排软删), + // 这里用闭包 orWhere 正确表达「phone OR email」;email 为空时不参与判重,避免误伤空邮箱账号 + $email = $params['email'] ?? ''; + $existsUser = $this->model::where('deleted_at', 0) + ->where(function ($q) use ($params, $email) { + $q->where('phone', $params['phone']); + if ($email !== '') { + $q->orWhere('email', $email); + } + })->exists(); if ($existsUser) { $this->utils->errorThrow('账号或邮箱已存在'); } diff --git a/app/Service/LoginService.php b/app/Service/LoginService.php index 248c50d2..ec7074b7 100644 --- a/app/Service/LoginService.php +++ b/app/Service/LoginService.php @@ -30,7 +30,8 @@ class LoginService extends BaseNotAuthService $ua = UserAgentService::getInstance(); $equipment = $ua->parseEquipment(); $browser = $ua->parseBrowser(); - $userModel = AdminModel::with(['role:id,name,value'])->where('phone', $phone)->first(); + // 必须排除软删账号(deleted_at=0),否则被删管理员仍能用原密码登录 + $userModel = AdminModel::with(['role:id,name,value'])->where('phone', $phone)->where('deleted_at', 0)->first(); if (empty($userModel)) { $this->writeLoginLog(0, (string) $phone, '', 1, '用户或密码错误', $equipment, $browser); UtilsService::getInstance()->errorThrow('用户或密码错误!'); @@ -111,7 +112,7 @@ class LoginService extends BaseNotAuthService */ public function register($phone, $password, $email, $code): array { - $userModel = AdminModel::where('phone', $phone)->first(); + $userModel = AdminModel::where('phone', $phone)->where('deleted_at', 0)->first(); if ($userModel) { UtilsService::getInstance()->errorThrow('账号已被占用!'); } diff --git a/app/Service/RoleService.php b/app/Service/RoleService.php index 2dd53e4d..a9a8201d 100755 --- a/app/Service/RoleService.php +++ b/app/Service/RoleService.php @@ -153,7 +153,10 @@ class RoleService extends BaseService */ public function delete($id): mixed { - if (in_array($id, [1, 2])) { + // BaseController::delete 传入的是 ids 数组,直接 in_array($id,[1,2]) 会因「数组与标量比较恒 false」绕过保护, + // 这里统一归一为数组后用 array_intersect 判断是否命中内置角色(1超管 / 2默认) + $ids = is_array($id) ? $id : [$id]; + if (array_intersect(array_map('intval', $ids), [1, 2])) { $this->utils->errorThrow('管理员角色禁止删除'); } return $this->del($id); diff --git a/app/template/uniapp/PAGES_JSON_SNIPPET.md b/app/template/uniapp/PAGES_JSON_SNIPPET.md index c75da55f..0dce2269 100644 --- a/app/template/uniapp/PAGES_JSON_SNIPPET.md +++ b/app/template/uniapp/PAGES_JSON_SNIPPET.md @@ -33,3 +33,4 @@ - 兼容微信小程序 + App + H5 - 交互:列表 → 详情 → 全屏表单;**禁止**底部抽屉做主 CRUD - 复用 `AppCardList` / `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm` +- 列表页统一:`AppPageAtmosphere`(氛围底)+ `AppSkeleton`(首屏骨架)+ `AppFab`(新增悬浮按钮) diff --git a/app/template/uniapp/pages/detail.vue b/app/template/uniapp/pages/detail.vue index 1b2e125b..9cfdb32a 100644 --- a/app/template/uniapp/pages/detail.vue +++ b/app/template/uniapp/pages/detail.vue @@ -9,6 +9,7 @@ import { deleteupperCamelCase, getupperCamelCaseInfo } from '../api' import AppBottomBar from '@/components/AppBottomBar.vue' import AppDetailHero from '@/components/AppDetailHero.vue' import AppInfoGroup, { type AppInfoRow } from '@/components/AppInfoGroup.vue' +import AppPageAtmosphere from '@/components/AppPageAtmosphere.vue' const id = ref(0) const info = ref>({}) @@ -45,14 +46,17 @@ function onDelete() { diff --git a/app/template/uniapp/pages/list.vue b/app/template/uniapp/pages/list.vue index 585dd8d5..2540f6a1 100644 --- a/app/template/uniapp/pages/list.vue +++ b/app/template/uniapp/pages/list.vue @@ -9,8 +9,12 @@ import { onShow } from '@dcloudio/uni-app' import { getupperCamelCaseList } from '../api' import AppCardList, { type AppCardItem } from '@/components/AppCardList.vue' import AppEmpty from '@/components/AppEmpty.vue' +import AppFab from '@/components/AppFab.vue' import AppFilterBar from '@/components/AppFilterBar.vue' +import AppLoading from '@/components/AppLoading.vue' +import AppPageAtmosphere from '@/components/AppPageAtmosphere.vue' import AppSearchBar from '@/components/AppSearchBar.vue' +import AppSkeleton from '@/components/AppSkeleton.vue' import { usePullRefresh } from '@/composables/usePullRefresh' const list = ref([]) @@ -82,26 +86,30 @@ function goCreate() { diff --git a/config/nl.php b/config/nl.php index bb275d92..982769b3 100755 --- a/config/nl.php +++ b/config/nl.php @@ -44,6 +44,8 @@ return [ 'phone' => 'nl_phone_', 'login_out_key' => 'nl_login_out_', 'menu_key' => 'nl_menu_', + // 公众号 stable access_token 缓存前缀(key = 前缀 + appid,TTL 随微信 expires_in) + 'wechat_token' => 'nl_wechat_at_', ], /* * api白名单 @@ -141,6 +143,10 @@ return [ 'ai-config/key-list', 'ai-config/platform-option', 'ai-config/generation-list', + 'wechat-account/list', + 'wechat-account/option', + 'wechat-article/list', + 'wechat-article/version-list', ] ], 'app' => [ diff --git a/routes/api.php b/routes/api.php index 348f1073..2612daa0 100644 --- a/routes/api.php +++ b/routes/api.php @@ -36,6 +36,9 @@ Route::group([], function () { 'ai-config' => \App\Http\Controllers\Api\AiConfigController::class, // AI 配置(平台/模型/密钥) 'oss-config' => \App\Http\Controllers\Api\OssConfigController::class, // OSS 存储配置 'api-endpoint' => \App\Http\Controllers\Api\ApiEndpointController::class, // 接口注册表 + 'wechat-account' => \App\Http\Controllers\Api\WechatAccountController::class, // 公众号账号配置 + 'wechat-article' => \App\Http\Controllers\Api\WechatArticleController::class, // 公众号图文创作 + 'wechat-theme' => \App\Http\Controllers\Api\WechatThemeController::class, // 自定义排版主题(我的模板) // 需要登录的路由生成地址 ]); });