263 lines
9.9 KiB
PHP
263 lines
9.9 KiB
PHP
<?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;
|
||
}
|
||
}
|