优化部分代码,增加微信公众号模板
This commit is contained in:
@@ -141,13 +141,19 @@ class BaseController
|
|||||||
$result = [];
|
$result = [];
|
||||||
foreach ($checkFields as $fieldName) {
|
foreach ($checkFields as $fieldName) {
|
||||||
$checkValue = $data[$fieldName] ?? '';
|
$checkValue = $data[$fieldName] ?? '';
|
||||||
if (is_array($checkValue) && !empty($checkValue)) {
|
if (is_array($checkValue)) {
|
||||||
$result[$fieldName] = $checkValue;
|
// 数组值单独处理:非空才收;空数组按「跳过」(与 GF 版一致),
|
||||||
|
// 且绝不对数组做 strval,避免触发 "Array to string conversion" 警告
|
||||||
|
if (!empty($checkValue)) {
|
||||||
|
$result[$fieldName] = $checkValue;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mb_strlen(strval($checkValue)) <= 0 && !in_array($fieldName, $this->notRequest)) {
|
if (mb_strlen(strval($checkValue)) <= 0 && !in_array($fieldName, $this->notRequest)) {
|
||||||
$requiredFields[] = $fieldName;
|
$requiredFields[] = $fieldName;
|
||||||
} else {
|
} else {
|
||||||
if (!empty($checkValue) || $checkValue === 0) {
|
// 用字符串长度判断是否有值,避免 !empty 把合法的 0 / "0" 误当空丢弃
|
||||||
|
// (HTTP 参数皆为字符串,status=0 会传成 "0",原写法会导致状态无法写入/改回启用)
|
||||||
|
if (mb_strlen(strval($checkValue)) > 0) {
|
||||||
$result[$fieldName] = $checkValue;
|
$result[$fieldName] = $checkValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\core;
|
|||||||
use App\BaseApp\BaseController;
|
use App\BaseApp\BaseController;
|
||||||
use App\Service\common\ai\AiCodeGenService;
|
use App\Service\common\ai\AiCodeGenService;
|
||||||
use App\Service\common\ai\AiRequirementPromptService;
|
use App\Service\common\ai\AiRequirementPromptService;
|
||||||
|
use App\Service\common\JWTService;
|
||||||
use App\Service\core\CodeGenerationService;
|
use App\Service\core\CodeGenerationService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
@@ -15,6 +16,11 @@ class CodeGenerationController extends BaseController
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
// 代码生成会写后端源码、改路由、执行建表 DDL,属超管工具,必须登录后使用。
|
||||||
|
// 本控制器的 CodeGenerationService 未继承 BaseService,故在此显式校验登录态
|
||||||
|
// (Bearer JWT + Redis 会话),口径与 BaseService 构造中的鉴权一致;
|
||||||
|
// autoRouteRegister 用 ReflectionClass 读方法、不实例化控制器,故不会在启动时误触发鉴权
|
||||||
|
JWTService::getInstance()->getToken()->getUserInfo();
|
||||||
$this->service = CodeGenerationService::getInstance();
|
$this->service = CodeGenerationService::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,17 @@ class AdminService extends BaseService
|
|||||||
{
|
{
|
||||||
$params['ip_table'] = json_encode([]);
|
$params['ip_table'] = json_encode([]);
|
||||||
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
|
$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) {
|
if ($existsUser) {
|
||||||
$this->utils->errorThrow('账号或邮箱已存在');
|
$this->utils->errorThrow('账号或邮箱已存在');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ class LoginService extends BaseNotAuthService
|
|||||||
$ua = UserAgentService::getInstance();
|
$ua = UserAgentService::getInstance();
|
||||||
$equipment = $ua->parseEquipment();
|
$equipment = $ua->parseEquipment();
|
||||||
$browser = $ua->parseBrowser();
|
$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)) {
|
if (empty($userModel)) {
|
||||||
$this->writeLoginLog(0, (string) $phone, '', 1, '用户或密码错误', $equipment, $browser);
|
$this->writeLoginLog(0, (string) $phone, '', 1, '用户或密码错误', $equipment, $browser);
|
||||||
UtilsService::getInstance()->errorThrow('用户或密码错误!');
|
UtilsService::getInstance()->errorThrow('用户或密码错误!');
|
||||||
@@ -111,7 +112,7 @@ class LoginService extends BaseNotAuthService
|
|||||||
*/
|
*/
|
||||||
public function register($phone, $password, $email, $code): array
|
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) {
|
if ($userModel) {
|
||||||
UtilsService::getInstance()->errorThrow('账号已被占用!');
|
UtilsService::getInstance()->errorThrow('账号已被占用!');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,10 @@ class RoleService extends BaseService
|
|||||||
*/
|
*/
|
||||||
public function delete($id): mixed
|
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('管理员角色禁止删除');
|
$this->utils->errorThrow('管理员角色禁止删除');
|
||||||
}
|
}
|
||||||
return $this->del($id);
|
return $this->del($id);
|
||||||
|
|||||||
@@ -33,3 +33,4 @@
|
|||||||
- 兼容微信小程序 + App + H5
|
- 兼容微信小程序 + App + H5
|
||||||
- 交互:列表 → 详情 → 全屏表单;**禁止**底部抽屉做主 CRUD
|
- 交互:列表 → 详情 → 全屏表单;**禁止**底部抽屉做主 CRUD
|
||||||
- 复用 `AppCardList` / `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm`
|
- 复用 `AppCardList` / `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm`
|
||||||
|
- 列表页统一:`AppPageAtmosphere`(氛围底)+ `AppSkeleton`(首屏骨架)+ `AppFab`(新增悬浮按钮)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { deleteupperCamelCase, getupperCamelCaseInfo } from '../api'
|
|||||||
import AppBottomBar from '@/components/AppBottomBar.vue'
|
import AppBottomBar from '@/components/AppBottomBar.vue'
|
||||||
import AppDetailHero from '@/components/AppDetailHero.vue'
|
import AppDetailHero from '@/components/AppDetailHero.vue'
|
||||||
import AppInfoGroup, { type AppInfoRow } from '@/components/AppInfoGroup.vue'
|
import AppInfoGroup, { type AppInfoRow } from '@/components/AppInfoGroup.vue'
|
||||||
|
import AppPageAtmosphere from '@/components/AppPageAtmosphere.vue'
|
||||||
|
|
||||||
const id = ref(0)
|
const id = ref(0)
|
||||||
const info = ref<Record<string, any>>({})
|
const info = ref<Record<string, any>>({})
|
||||||
@@ -45,14 +46,17 @@ function onDelete() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="nl-page">
|
<!-- 氛围壳提供主题作用域 + 背景色斑,玻璃卡的模糊才有内容可透 -->
|
||||||
<AppDetailHero
|
<AppPageAtmosphere>
|
||||||
:title="title"
|
<view class="nl-page">
|
||||||
:subtitle="String(info.UNI_SUBTITLE_FIELD || '')"
|
<AppDetailHero
|
||||||
:status-text="Number(info.status) === 0 ? '正常' : '禁用'"
|
:title="title"
|
||||||
:status-type="Number(info.status) === 0 ? 'success' : 'danger'"
|
:subtitle="String(info.UNI_SUBTITLE_FIELD || '')"
|
||||||
/>
|
:status-text="Number(info.status) === 0 ? '正常' : '禁用'"
|
||||||
<AppInfoGroup title="详细信息" :items="rows" />
|
:status-type="Number(info.status) === 0 ? 'success' : 'danger'"
|
||||||
<AppBottomBar primary-text="编辑" danger-text="删除" @primary="goEdit" @danger="onDelete" />
|
/>
|
||||||
</view>
|
<AppInfoGroup title="详细信息" :items="rows" />
|
||||||
|
<AppBottomBar primary-text="编辑" danger-text="删除" @primary="goEdit" @danger="onDelete" />
|
||||||
|
</view>
|
||||||
|
</AppPageAtmosphere>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import { onShow } from '@dcloudio/uni-app'
|
|||||||
import { getupperCamelCaseList } from '../api'
|
import { getupperCamelCaseList } from '../api'
|
||||||
import AppCardList, { type AppCardItem } from '@/components/AppCardList.vue'
|
import AppCardList, { type AppCardItem } from '@/components/AppCardList.vue'
|
||||||
import AppEmpty from '@/components/AppEmpty.vue'
|
import AppEmpty from '@/components/AppEmpty.vue'
|
||||||
|
import AppFab from '@/components/AppFab.vue'
|
||||||
import AppFilterBar from '@/components/AppFilterBar.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 AppSearchBar from '@/components/AppSearchBar.vue'
|
||||||
|
import AppSkeleton from '@/components/AppSkeleton.vue'
|
||||||
import { usePullRefresh } from '@/composables/usePullRefresh'
|
import { usePullRefresh } from '@/composables/usePullRefresh'
|
||||||
|
|
||||||
const list = ref<any[]>([])
|
const list = ref<any[]>([])
|
||||||
@@ -82,26 +86,30 @@ function goCreate() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="nl-page page">
|
<AppPageAtmosphere tone="cool">
|
||||||
<AppSearchBar
|
<view class="nl-page page">
|
||||||
v-model="keyword"
|
<AppSearchBar
|
||||||
placeholder="搜索模板名称"
|
v-model="keyword"
|
||||||
@search="(v) => { keyword = v; reload() }"
|
placeholder="搜索模板名称"
|
||||||
@clear="() => { keyword = ''; reload() }"
|
@search="(v) => { keyword = v; reload() }"
|
||||||
/>
|
@clear="() => { keyword = ''; reload() }"
|
||||||
<AppFilterBar :selected-count="selectedCount" @reset="onFilterReset" @confirm="onFilterConfirm">
|
/>
|
||||||
<view class="filter-row">
|
<AppFilterBar :selected-count="selectedCount" @reset="onFilterReset" @confirm="onFilterConfirm">
|
||||||
<text>关键词</text>
|
<view class="filter-row">
|
||||||
<input v-model="draftKeyword" class="filter-input" placeholder="关键词" />
|
<text>关键词</text>
|
||||||
|
<input v-model="draftKeyword" class="filter-input" placeholder="关键词" />
|
||||||
|
</view>
|
||||||
|
</AppFilterBar>
|
||||||
|
<AppSkeleton v-if="loading && !cards.length" :rows="5" />
|
||||||
|
<AppCardList v-else-if="cards.length" :list="cards" @select="goDetail" />
|
||||||
|
<AppEmpty v-else text="暂无数据" />
|
||||||
|
<AppLoading v-if="loading && cards.length" text="加载更多…" />
|
||||||
|
<view v-else-if="cards.length" class="more" @tap="loadMore">
|
||||||
|
{{ finished ? '没有更多了' : '加载更多' }}
|
||||||
</view>
|
</view>
|
||||||
</AppFilterBar>
|
<AppFab @tap="goCreate" />
|
||||||
<AppCardList v-if="cards.length" :list="cards" @select="goDetail" />
|
|
||||||
<AppEmpty v-else text="暂无数据" />
|
|
||||||
<view v-if="cards.length" class="more" @tap="loadMore">
|
|
||||||
{{ finished ? '没有更多了' : loading ? '加载中…' : '加载更多' }}
|
|
||||||
</view>
|
</view>
|
||||||
<view class="fab" @tap="goCreate">新增</view>
|
</AppPageAtmosphere>
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -111,10 +119,4 @@ function goCreate() {
|
|||||||
}
|
}
|
||||||
.filter-input { flex: 1; text-align: right; margin-left: 24rpx; }
|
.filter-input { flex: 1; text-align: right; margin-left: 24rpx; }
|
||||||
.more { text-align: center; color: $nl-color-text-muted; padding: 16rpx; font-size: 24rpx; }
|
.more { text-align: center; color: $nl-color-text-muted; padding: 16rpx; font-size: 24rpx; }
|
||||||
.fab {
|
|
||||||
position: fixed; right: 32rpx; bottom: calc(48rpx + env(safe-area-inset-bottom));
|
|
||||||
min-width: 120rpx; height: 88rpx; padding: 0 36rpx; border-radius: 999rpx;
|
|
||||||
background: $nl-color-primary; color: #fff; display: flex; align-items: center; justify-content: center;
|
|
||||||
font-weight: 700; box-shadow: 0 16rpx 40rpx rgba(255, 107, 0, 0.35);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ return [
|
|||||||
'phone' => 'nl_phone_',
|
'phone' => 'nl_phone_',
|
||||||
'login_out_key' => 'nl_login_out_',
|
'login_out_key' => 'nl_login_out_',
|
||||||
'menu_key' => 'nl_menu_',
|
'menu_key' => 'nl_menu_',
|
||||||
|
// 公众号 stable access_token 缓存前缀(key = 前缀 + appid,TTL 随微信 expires_in)
|
||||||
|
'wechat_token' => 'nl_wechat_at_',
|
||||||
],
|
],
|
||||||
/*
|
/*
|
||||||
* api白名单
|
* api白名单
|
||||||
@@ -141,6 +143,10 @@ return [
|
|||||||
'ai-config/key-list',
|
'ai-config/key-list',
|
||||||
'ai-config/platform-option',
|
'ai-config/platform-option',
|
||||||
'ai-config/generation-list',
|
'ai-config/generation-list',
|
||||||
|
'wechat-account/list',
|
||||||
|
'wechat-account/option',
|
||||||
|
'wechat-article/list',
|
||||||
|
'wechat-article/version-list',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'app' => [
|
'app' => [
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ Route::group([], function () {
|
|||||||
'ai-config' => \App\Http\Controllers\Api\AiConfigController::class, // AI 配置(平台/模型/密钥)
|
'ai-config' => \App\Http\Controllers\Api\AiConfigController::class, // AI 配置(平台/模型/密钥)
|
||||||
'oss-config' => \App\Http\Controllers\Api\OssConfigController::class, // OSS 存储配置
|
'oss-config' => \App\Http\Controllers\Api\OssConfigController::class, // OSS 存储配置
|
||||||
'api-endpoint' => \App\Http\Controllers\Api\ApiEndpointController::class, // 接口注册表
|
'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, // 自定义排版主题(我的模板)
|
||||||
// 需要登录的路由生成地址
|
// 需要登录的路由生成地址
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user