Files
lgp-admin-plus-api/app/Service/business/WxTemplateService.php
LQ bcf54c2727 初始化
缺陷:主题配色需要优化整体的同风格
2026-08-14 23:21:21 +08:00

263 lines
9.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service\business;
use App\BaseApp\BaseService;
use App\Models\business\WxTemplateModel;
use Illuminate\Support\Facades\DB;
/**
* 小程序装修模板管理(后台)
*/
class WxTemplateService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->model = WxTemplateModel::class;
$this->selectField = [
'id', 'name', 'code', 'preview', 'style_tag', 'is_default', 'status',
'app_code', 'version', 'sort', 'created_at', 'updated_at',
];
$this->queryField = [
'name' => 'like',
'code' => 'like',
'style_tag' => '=',
'app_code' => '=',
'status' => '=',
];
$this->orderBy = ['name' => 'sort', 'sort' => 'asc'];
}
public function list(): array
{
$result = $this->getPageList();
// 列表不回完整 tokens体积大但卡片预览需要色板与布局摘要
$ids = array_values(array_filter(array_map(
static fn ($row) => (int) ($row['id'] ?? 0),
$result['items'] ?? []
)));
if ($ids === []) {
return $result;
}
$extras = WxTemplateModel::whereIn('id', $ids)
->get(['id', 'tokens', 'layout'])
->keyBy('id');
foreach ($result['items'] as &$item) {
$extra = $extras[(int) $item['id']] ?? null;
$tokens = is_array($extra?->tokens) ? $extra->tokens : [];
$layout = is_array($extra?->layout) ? $extra->layout : [];
$color = is_array($tokens['color'] ?? null) ? $tokens['color'] : [];
$home = is_array($layout['home'] ?? null) ? $layout['home'] : [];
$item['swatch'] = [
'primary' => (string) ($color['primary'] ?? '#B08D57'),
'accent' => (string) ($color['accent'] ?? ($color['primary'] ?? '#B08D57')),
'bg' => (string) ($color['bg'] ?? '#FAFAF9'),
'surface' => (string) ($color['surface'] ?? '#FFFFFF'),
'text' => (string) ($color['text'] ?? '#18181B'),
'border' => (string) ($color['border'] ?? '#E4E4E7'),
];
$item['layout_hint'] = [
'hero' => (string) ($home['hero'] ?? 'banner'),
'category' => (string) ($home['category'] ?? 'grid'),
'product' => (string) ($home['product'] ?? 'grid'),
];
}
unset($item);
return $result;
}
public function option(): mixed
{
$this->optionField = ['id', 'name', 'code', 'style_tag'];
return $this->getOption();
}
public function detail($id): mixed
{
// 详情是单条记录,没有 list 的字段裁剪压力;
// tokens/layout 必须回显(编辑弹窗回填要用),不能用默认 selectField它把这两个字段排除了
$saved = $this->selectField;
$this->selectField = ['*'];
try {
return $this->getDetail($id);
} finally {
$this->selectField = $saved;
}
}
public function create($params): mixed
{
$params = $this->normalize($params);
return $this->insert($params);
}
public function update($id, $params): mixed
{
$params = $this->normalize($params, (int) $id);
return $this->save($id, $params);
}
public function delete($ids): mixed
{
// 默认模板被删掉小程序就没样式可用了,必须先改默认再删
$hasDefault = WxTemplateModel::whereIn('id', (array) $ids)->where('is_default', 1)->exists();
if ($hasDefault) {
$this->utils->errorThrow('默认模板不能删除,请先把其他模板设为默认');
}
return $this->del($ids);
}
public function status($id, $status): mixed
{
return $this->save($id, ['status' => (int) $status]);
}
/**
* 设为默认(同一 app_code 下只能有一个默认)
* 同时把 code 写入 nl_wx_app.template_code保证小程序 current() 优先命中
*/
public function setDefault(int $id): bool
{
$template = WxTemplateModel::where('id', $id)->where('deleted_at', 0)->first();
if (empty($template)) {
$this->utils->errorThrow('模板不存在');
}
DB::connection('business')->transaction(function () use ($template, $id) {
WxTemplateModel::where('app_code', $template['app_code'])
->where('id', '!=', $id)
->update(['is_default' => 0, 'updated_at' => time()]);
WxTemplateModel::where('id', $id)->update([
'is_default' => 1,
'status' => 0,
'updated_at' => time(),
]);
});
// 系统库 nl_wx_app空 app_code 的全局模板同步到全部启用应用;有品牌则只同步该品牌
$appQuery = \App\Models\WxAppModel::where('deleted_at', 0)->where('status', 0);
$appCode = trim((string) ($template['app_code'] ?? ''));
if ($appCode !== '') {
$appQuery->where('code', $appCode);
}
$appQuery->update([
'template_code' => (string) $template['code'],
'updated_at' => time(),
]);
return true;
}
/**
* 导出:给出可直接再导入的 JSON
*/
public function export(array $ids): array
{
$rows = WxTemplateModel::whereIn('id', $ids)->where('deleted_at', 0)->get([
'name', 'code', 'preview', 'style_tag', 'tokens', 'layout', 'app_code', 'version',
]);
return [
'version' => 1,
'exported_at' => date('Y-m-d H:i:s'),
'templates' => $rows->toArray(),
];
}
/**
* 导入:整包校验通过才落库
*
* 单条不合法就整包拒绝,不做「部分成功」——一半新一半旧的模板库更难排查。
*/
public function import(array $payload, bool $overwrite = false): array
{
$templates = $payload['templates'] ?? $payload;
if (!is_array($templates) || empty($templates)) {
$this->utils->errorThrow('导入内容为空');
}
$schema = WxTemplateSchemaService::getInstance();
$rows = [];
foreach ($templates as $index => $item) {
$code = trim((string) ($item['code'] ?? ''));
$name = trim((string) ($item['name'] ?? ''));
if ($code === '' || $name === '') {
$this->utils->errorThrow('第 ' . ($index + 1) . ' 个模板缺少 code 或 name');
}
$rows[] = [
'code' => $code,
'name' => $name,
'preview' => (string) ($item['preview'] ?? ''),
'style_tag' => (string) ($item['style_tag'] ?? ''),
'app_code' => (string) ($item['app_code'] ?? ''),
'tokens' => json_encode($schema->sanitizeTokens($item['tokens'] ?? [], true), JSON_UNESCAPED_UNICODE),
'layout' => json_encode($schema->sanitizeLayout($item['layout'] ?? [], true), JSON_UNESCAPED_UNICODE),
'version' => max(1, (int) ($item['version'] ?? 1)),
];
}
$inserted = 0;
$updated = 0;
$skipped = 0;
DB::connection('business')->transaction(function () use ($rows, $overwrite, &$inserted, &$updated, &$skipped) {
foreach ($rows as $row) {
$exists = WxTemplateModel::where('code', $row['code'])
->where('app_code', $row['app_code'])
->first();
if (!empty($exists)) {
if (!$overwrite) {
$skipped++;
continue;
}
$row['version'] = (int) $exists['version'] + 1;
$row['updated_at'] = time();
WxTemplateModel::where('id', $exists['id'])->update($row);
$updated++;
continue;
}
$row['created_at'] = time();
WxTemplateModel::insert($row);
$inserted++;
}
});
return ['inserted' => $inserted, 'updated' => $updated, 'skipped' => $skipped];
}
/**
* 用内置预设初始化模板库20 套)
*/
public function initPresets(bool $overwrite = false): array
{
$presets = WxTemplatePresetService::getInstance()->all();
$result = $this->import(['templates' => $presets], $overwrite);
// 一套默认都没有的话,把第一套轻奢设为默认
if (!WxTemplateModel::where('deleted_at', 0)->where('is_default', 1)->exists()) {
$first = WxTemplateModel::where('deleted_at', 0)->orderBy('sort', 'asc')->first();
if (!empty($first)) {
$this->setDefault((int) $first['id']);
}
}
return $result;
}
/**
* 入库前清洗code 在同一品牌下唯一
*/
private function normalize(array $params, int $excludeId = 0): array
{
$schema = WxTemplateSchemaService::getInstance();
if (array_key_exists('tokens', $params)) {
$params['tokens'] = json_encode($schema->sanitizeTokens($params['tokens'], true), JSON_UNESCAPED_UNICODE);
}
if (array_key_exists('layout', $params)) {
$params['layout'] = json_encode($schema->sanitizeLayout($params['layout'], true), JSON_UNESCAPED_UNICODE);
}
if (!empty($params['code'])) {
$duplicate = WxTemplateModel::where('code', $params['code'])
->where('app_code', (string) ($params['app_code'] ?? ''))
->when($excludeId > 0, fn ($query) => $query->where('id', '!=', $excludeId))
->exists();
if ($duplicate) {
$this->utils->errorThrow('模板标识已存在');
}
}
return $params;
}
}