Files

263 lines
9.9 KiB
PHP
Raw Permalink Normal View History

<?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;
}
}