|
|
@@ -43,6 +43,192 @@ class Material extends Api
|
|
|
return 'jpg';
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据 material_url 删除 public 下对应文件(如 uploads/material/2026-03-25/xxx.png)
|
|
|
+ */
|
|
|
+ protected function unlinkMaterialFileByUrl($materialUrl)
|
|
|
+ {
|
|
|
+ if ($materialUrl === null || $materialUrl === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $rel = str_replace('\\', '/', trim((string) $materialUrl));
|
|
|
+ $rel = ltrim($rel, '/');
|
|
|
+ if ($rel === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $fullPath = rtrim(str_replace('\\', '/', ROOT_PATH), '/') . '/public/' . $rel;
|
|
|
+ if (is_file($fullPath)) {
|
|
|
+ @unlink($fullPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图层所属页码(多页模版):0 起,缺省或非数字按 0
|
|
|
+ */
|
|
|
+ protected function layerPageIndex(array $layer): int
|
|
|
+ {
|
|
|
+ if (!array_key_exists('page_index', $layer)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $v = $layer['page_index'];
|
|
|
+ if ($v === '' || $v === null) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (is_numeric($v)) {
|
|
|
+ return (int) $v;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析 product_template.page_image_urls(JSON 数组)
|
|
|
+ */
|
|
|
+ protected function decodePageImageUrlsField($stored): array
|
|
|
+ {
|
|
|
+ if ($stored === null || $stored === '') {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ if (is_array($stored)) {
|
|
|
+ return $stored;
|
|
|
+ }
|
|
|
+ if (!is_string($stored)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $decoded = json_decode($stored, true);
|
|
|
+ return is_array($decoded) ? $decoded : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除多页预览图(本地 + OSS),用于更新前清理或删模版
|
|
|
+ */
|
|
|
+ protected function deleteStoredPageGalleryImages($pageImageUrlsJsonOrArray): void
|
|
|
+ {
|
|
|
+ $list = is_array($pageImageUrlsJsonOrArray)
|
|
|
+ ? $pageImageUrlsJsonOrArray
|
|
|
+ : $this->decodePageImageUrlsField($pageImageUrlsJsonOrArray);
|
|
|
+ foreach ($list as $p) {
|
|
|
+ if ($p === null || $p === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $this->unlinkMaterialFileByUrl((string) $p);
|
|
|
+ Common::deleteOssObject((string) $p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 preview_images[] 中每页 dataURL 落盘并同步 OSS,返回与下标对齐的路径数组(失败页为空串占位)
|
|
|
+ *
|
|
|
+ * @param array $previewImages 下标 0=第 1 页…
|
|
|
+ * @param string $saveDir public/uploads/template/YYYY-mm-dd/
|
|
|
+ * @param string $dateYmd YYYY-mm-dd
|
|
|
+ * @return array{paths: string[], oss: bool[]}
|
|
|
+ */
|
|
|
+ protected function savePreviewGalleryBase64List(array $previewImages, string $saveDir, string $dateYmd): array
|
|
|
+ {
|
|
|
+ if ($previewImages === []) {
|
|
|
+ return ['paths' => [], 'oss' => []];
|
|
|
+ }
|
|
|
+ $keys = array_keys($previewImages);
|
|
|
+ $maxIdx = $keys === [] ? -1 : max(array_map('intval', $keys));
|
|
|
+ $n = $maxIdx >= 0 ? ($maxIdx + 1) : 0;
|
|
|
+ $paths = array_fill(0, $n, '');
|
|
|
+ $oss = array_fill(0, $n, false);
|
|
|
+
|
|
|
+ foreach ($previewImages as $pageIndex => $base64Data) {
|
|
|
+ $i = (int) $pageIndex;
|
|
|
+ if ($i < 0 || $i >= $n) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!is_string($base64Data) || trim($base64Data) === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!preg_match('/data:image\/(png|jpg|jpeg|webp);base64,(.+)/is', $base64Data, $m)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $imageType = strtolower($m[1]);
|
|
|
+ if ($imageType === 'jpeg') {
|
|
|
+ $imageType = 'jpg';
|
|
|
+ }
|
|
|
+ $b64 = preg_replace('/\s+/', '', $m[2]);
|
|
|
+ $imageData = base64_decode($b64, true);
|
|
|
+ if ($imageData === false || strlen($imageData) < 100) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $fn = 'page_' . $i . '_' . uniqid() . '_' . date('YmdHis') . '.' . $imageType;
|
|
|
+ $full = $saveDir . $fn;
|
|
|
+ if (!file_put_contents($full, $imageData)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $dbPath = '/uploads/template/' . $dateYmd . '/' . $fn;
|
|
|
+ $paths[$i] = $dbPath;
|
|
|
+ $oss[$i] = Common::uploadLocalFileToOss((string) $full, (string) $dbPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ['paths' => $paths, 'oss' => $oss];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 layers 解析最终 material_id 列表(与写入 relation 时逻辑一致,同一 id 可出现多次)
|
|
|
+ */
|
|
|
+ protected function collectMaterialIdsFromLayers($layers, $layerIdToMaterial)
|
|
|
+ {
|
|
|
+ $ids = [];
|
|
|
+ if (empty($layers) || !is_array($layers)) {
|
|
|
+ return $ids;
|
|
|
+ }
|
|
|
+ foreach ($layers as $layer) {
|
|
|
+ $materialId = $layer['material_id'] ?? null;
|
|
|
+ if (isset($layer['id']) && isset($layerIdToMaterial[$layer['id']])) {
|
|
|
+ $materialId = $layerIdToMaterial[$layer['id']]['id'];
|
|
|
+ }
|
|
|
+ if ($materialId !== null && $materialId !== '') {
|
|
|
+ $ids[] = (int) $materialId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $ids;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按图层引用次数增加 template_material.count
|
|
|
+ */
|
|
|
+ protected function incrementMaterialUseCountsFromIds(array $materialIds)
|
|
|
+ {
|
|
|
+ if (empty($materialIds)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $counts = array_count_values($materialIds);
|
|
|
+ foreach ($counts as $mid => $cnt) {
|
|
|
+ if ($mid > 0 && $cnt > 0) {
|
|
|
+ Db::name('template_material')->where('id', $mid)->setInc('count', $cnt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 某模版旧关联中各 material_id 出现几次,count 减几次(删除关联前调用)
|
|
|
+ */
|
|
|
+ protected function decrementMaterialUseCountsByTemplateId($templateId)
|
|
|
+ {
|
|
|
+ $rows = Db::name('template_material_relation')->where('template_id', $templateId)->column('material_id');
|
|
|
+ if (empty($rows)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $ids = [];
|
|
|
+ foreach ($rows as $mid) {
|
|
|
+ if ($mid !== null && $mid !== '' && (int) $mid > 0) {
|
|
|
+ $ids[] = (int) $mid;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (empty($ids)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $counts = array_count_values($ids);
|
|
|
+ foreach ($counts as $mid => $cnt) {
|
|
|
+ Db::name('template_material')->where('id', $mid)->setDec('count', $cnt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 新增素材图片上传
|
|
|
*
|
|
|
@@ -69,6 +255,7 @@ class Material extends Api
|
|
|
|
|
|
$uploaded = [];
|
|
|
|
|
|
+ // 兼容单图(img)与多图(img[]),ThinkPHP 会返回对象或数组
|
|
|
$files = $this->request->file('img');
|
|
|
if (!empty($files)) {
|
|
|
$fileList = is_array($files) ? $files : [$files];
|
|
|
@@ -80,13 +267,18 @@ class Material extends Api
|
|
|
if (!$file || !$file->isValid()) {
|
|
|
continue;
|
|
|
}
|
|
|
+ // 先落本地(后续可用于备份/排障),再尝试同步 OSS
|
|
|
$saveFileName = uniqid() . '_' . date('YmdHis') . '.' . $this->resolveUploadedImageExt($file);
|
|
|
$info = $file->move($materialSavePath, $saveFileName);
|
|
|
if (!$info) {
|
|
|
continue;
|
|
|
}
|
|
|
$savedName = $info->getFilename();
|
|
|
- $materialUrl = '/uploads/material/' . $dateDir . '/' . $savedName;
|
|
|
+ $fullLocalPath = $materialSavePath . $savedName;
|
|
|
+ $materialUrl = 'uploads/material/' . $dateDir . '/' . $savedName;
|
|
|
+ // OSS 失败不阻断主流程(本地已保存)
|
|
|
+ Common::uploadLocalFileToOss((string)$fullLocalPath, (string)$materialUrl);
|
|
|
+
|
|
|
$materialRecord = [
|
|
|
'sys_id' => $sysId,
|
|
|
'Category_id' => $categoryId,
|
|
|
@@ -95,10 +287,12 @@ class Material extends Api
|
|
|
'create_time' => date('Y-m-d H:i:s'),
|
|
|
'count' => 1
|
|
|
];
|
|
|
+
|
|
|
$materialId = Db::name('template_material')->insertGetId($materialRecord);
|
|
|
$uploaded[] = ['id' => $materialId, 'material_url' => $materialUrl];
|
|
|
}
|
|
|
} else {
|
|
|
+ // 兼容旧版 base64 传参:uploaded_materials=[{data, material_name}, ...]
|
|
|
$materials = $params['uploaded_materials'] ?? [];
|
|
|
if (empty($materials) || !is_array($materials)) {
|
|
|
return json(['code' => 1, 'msg' => '请上传至少一张素材图片(img/img[] 或 uploaded_materials)']);
|
|
|
@@ -120,7 +314,10 @@ class Material extends Api
|
|
|
if (!file_put_contents($fullPath, $imageData)) {
|
|
|
continue;
|
|
|
}
|
|
|
- $materialUrl = '/uploads/material/' . $dateDir . '/' . $fileName;
|
|
|
+ $materialUrl = 'uploads/material/' . $dateDir . '/' . $fileName;
|
|
|
+ // base64 分支同样尝试同步 OSS
|
|
|
+ Common::uploadLocalFileToOss((string)$fullPath, (string)$materialUrl);
|
|
|
+
|
|
|
$materialRecord = [
|
|
|
'sys_id' => $sysId,
|
|
|
'Category_id' => $categoryId,
|
|
|
@@ -142,24 +339,35 @@ class Material extends Api
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 素材图片删除(软删除)
|
|
|
+ * 素材图片删除:物理删库 + 删除 public 下对应图片文件
|
|
|
*/
|
|
|
public function materialDelete()
|
|
|
{
|
|
|
$params = $this->request->param();
|
|
|
- $record['mod_rq'] = date('Y-m-d H:i:s');
|
|
|
- $res = Db::name('template_material')->where('id', $params['id'])->update($record);
|
|
|
- if (!$res) {
|
|
|
+ if (empty($params['id'])) {
|
|
|
+ return json(['code' => 1, 'msg' => 'id 不能为空', 'data' => '']);
|
|
|
+ }
|
|
|
+ $id = intval($params['id']);
|
|
|
+ $row = Db::name('template_material')->where('id', $id)->find();
|
|
|
+ if (!$row) {
|
|
|
+ return json(['code' => 1, 'msg' => '记录不存在', 'data' => '']);
|
|
|
+ }
|
|
|
+ $list = Db::name('template_material_relation')->where('material_id', $id)->select();
|
|
|
+ if ($list) {
|
|
|
return json([
|
|
|
'code' => 1,
|
|
|
- 'msg' => '删除失败',
|
|
|
+ 'msg' => '当前素材已被模版使用,不可删除',
|
|
|
'data' => ''
|
|
|
]);
|
|
|
}
|
|
|
- return json([
|
|
|
- 'code' => 0,
|
|
|
- 'msg' => '删除成功'
|
|
|
- ]);
|
|
|
+ $this->unlinkMaterialFileByUrl($row['material_url'] ?? '');
|
|
|
+ // 同步删除 OSS 对象(如未配置 OSS 或删除失败,不阻断数据库删除)
|
|
|
+ Common::deleteOssObject((string)($row['material_url'] ?? ''));
|
|
|
+ $res = Db::name('template_material')->where('id', $id)->delete();
|
|
|
+ if (!$res) {
|
|
|
+ return json(['code' => 1, 'msg' => '删除失败', 'data' => '']);
|
|
|
+ }
|
|
|
+ return json(['code' => 0, 'msg' => '删除成功']);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -173,9 +381,9 @@ class Material extends Api
|
|
|
return json(['code' => 1, 'msg' => 'id 不能为空']);
|
|
|
}
|
|
|
$id = intval($params['id']);
|
|
|
- $row = Db::name('template_material')->where('id', $id)->whereNull('mod_rq')->find();
|
|
|
+ $row = Db::name('template_material')->where('id', $id)->find();
|
|
|
if (!$row) {
|
|
|
- return json(['code' => 1, 'msg' => '记录不存在或已删除']);
|
|
|
+ return json(['code' => 1, 'msg' => '记录不存在']);
|
|
|
}
|
|
|
|
|
|
$update = ['update_time' => date('Y-m-d H:i:s')];
|
|
|
@@ -205,27 +413,32 @@ class Material extends Api
|
|
|
public function Material_List(){
|
|
|
$params = $this->request->param();
|
|
|
$page = max(1, intval($params['page'] ?? 1));
|
|
|
- $pageSize = min(500, max(1, intval($params['pageSize'] ?? 100)));
|
|
|
+ $pageSize = min(500, max(1, intval($params['pageSize'] ?? 30)));
|
|
|
|
|
|
$where = [];
|
|
|
if (!empty($params['search'])) {
|
|
|
// 使用更安全的查询方式,material_name 与 category_name 任一匹配即可
|
|
|
$search = trim($params['search']);
|
|
|
- $where['a.material_name|b.category_name'] = ['like', '%' . $search . '%'];
|
|
|
+ $where['a.material_name|b.category_name|a.material_url'] = ['like', '%' . $search . '%'];
|
|
|
}
|
|
|
if (!empty($params['Category_id'])) {
|
|
|
$where['a.Category_id'] = ['like', '%' . $params['Category_id'] . '%'];
|
|
|
}
|
|
|
|
|
|
$query = Db::name('template_material')->alias('a')
|
|
|
- ->field('a.id,a.sys_id,a.Category_id,b.category_name,a.material_name,a.material_url')
|
|
|
+ ->field('a.id,a.sys_id,a.Category_id,b.category_name,a.material_name,a.material_url,a.count')
|
|
|
->join('template_material_category b', 'a.Category_id = b.id AND b.mod_rq IS NULL', 'LEFT')
|
|
|
->where($where)
|
|
|
->whereNull('a.mod_rq')
|
|
|
->order('a.id desc');
|
|
|
-
|
|
|
$total = (clone $query)->count();
|
|
|
$data = $query->page($page, $pageSize)->select();
|
|
|
+ foreach ($data as &$item) {
|
|
|
+ if (!empty($item['material_url'])) {
|
|
|
+ $item['material_url'] = Common::ossFullUrl((string)$item['material_url']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($item);
|
|
|
|
|
|
return json([
|
|
|
'code' => 0,
|
|
|
@@ -242,11 +455,31 @@ class Material extends Api
|
|
|
public function Template_Material_Relation(){
|
|
|
$params = $this->request->param();
|
|
|
$res = Db::name('template_material_relation')->alias('a')
|
|
|
- ->field('a.*, b.material_url,c.canvasWidth,c.canvasHeight,c.size')
|
|
|
+ ->field('a.*,c.chinese_description,c.page_image_urls, b.material_url,c.canvasWidth,c.canvasHeight,c.size')
|
|
|
->join('template_material b', 'a.material_id = b.id', 'left')
|
|
|
->join('product_template c', 'a.template_id = c.id', 'left')
|
|
|
->where('a.template_id',$params['id'])->select();
|
|
|
-
|
|
|
+ foreach ($res as &$item) {
|
|
|
+ if (!empty($item['material_url'])) {
|
|
|
+ $item['material_url'] = Common::ossFullUrl((string)$item['material_url']);
|
|
|
+ }
|
|
|
+ if (array_key_exists('chinese_description', $item)) {
|
|
|
+ $item['chinese_description'] = Common::decodeChineseDescriptionForApi($item['chinese_description']);
|
|
|
+ }
|
|
|
+ if (array_key_exists('page_image_urls', $item) && $item['page_image_urls'] !== null && $item['page_image_urls'] !== '') {
|
|
|
+ $arr = json_decode((string) $item['page_image_urls'], true);
|
|
|
+ if (is_array($arr)) {
|
|
|
+ foreach ($arr as &$pu) {
|
|
|
+ if ($pu !== null && $pu !== '') {
|
|
|
+ $pu = Common::ossFullUrl((string) $pu);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($pu);
|
|
|
+ $item['page_image_urls'] = $arr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($item);
|
|
|
// 处理null值,转换为空字符串
|
|
|
if($res){
|
|
|
foreach($res as &$item){
|
|
|
@@ -282,6 +515,7 @@ class Material extends Api
|
|
|
// echo "<pre>";die;
|
|
|
// 处理 uploaded_materials:保存素材图片到 uploads/material/ 并写入 template_material 表
|
|
|
$layerIdToMaterial = []; // layer_id => ['id'=>material_id, 'url'=>material_url]
|
|
|
+ $ossSync = ['configured' => Common::isOssEnabled(), 'materials' => [], 'template_main' => null, 'template_thumb' => null];
|
|
|
if (!empty($params['uploaded_materials'])) {
|
|
|
$materialSavePath = str_replace('\\', '/', ROOT_PATH . 'public/uploads/material/' . date('Y-m-d') . '/');
|
|
|
if (!is_dir($materialSavePath)) {
|
|
|
@@ -304,20 +538,28 @@ class Material extends Api
|
|
|
continue;
|
|
|
}
|
|
|
$materialUrl = 'uploads/material/' . date('Y-m-d') . '/' . $fileName;
|
|
|
+ // uploaded_materials 分支:素材图先落本地,再尝试同步到 OSS(失败不影响新增模版)
|
|
|
+ $ossSync['materials'][] = [
|
|
|
+ 'objectKey' => $materialUrl,
|
|
|
+ 'ok' => Common::uploadLocalFileToOss((string)$fullPath, (string)$materialUrl),
|
|
|
+ ];
|
|
|
$materialRecord = [
|
|
|
'sys_id' => $params['sys_id'] ?? '',
|
|
|
'material_url' => $materialUrl,
|
|
|
'type' => $item['type'] ?? '',
|
|
|
+ 'Category_id' => $item['Category_id'] ?? '',
|
|
|
+ 'chinese_description' => $item['chinese_description'] ?? '',
|
|
|
+ 'material_name' => $item['material_name'] ?? '',
|
|
|
'create_time' => date('Y-m-d H:i:s'),
|
|
|
'count' => 1
|
|
|
];
|
|
|
+
|
|
|
$materialId = Db::name('template_material')->insertGetId($materialRecord);
|
|
|
if ($materialId && isset($item['layer_id'])) {
|
|
|
$layerIdToMaterial[$item['layer_id']] = ['id' => $materialId, 'url' => $materialUrl];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
$save_path = ROOT_PATH . 'public' . '/' . 'uploads' . '/' . 'template' .'/'. date('Y-m-d') . '/';
|
|
|
// 移除ROOT_PATH中可能存在的反斜杠,确保统一使用正斜杠
|
|
|
$save_path = str_replace('\\', '/', $save_path);
|
|
|
@@ -350,20 +592,44 @@ class Material extends Api
|
|
|
}
|
|
|
// 生成数据库存储路径(使用正斜杠格式)
|
|
|
$db_img_path = '/uploads/template/'. date('Y-m-d') .'/' . $file_name;
|
|
|
+ // 预览图(模板原图)同步 OSS,保持本地/云端路径一致(失败见返回 oss_sync 与 runtime/log)
|
|
|
+ $ossSync['template_main'] = Common::uploadLocalFileToOss((string)$full_file_path, (string)$db_img_path);
|
|
|
|
|
|
// 生成缩略图
|
|
|
$thumbnail_path = $this->generateThumbnail($full_file_path, $save_path, $file_name);
|
|
|
$db_thumbnail_path = '/uploads/template/'.date('Y-m-d') .'/' . $thumbnail_path;
|
|
|
+ $fullThumbnailPath = $save_path . $thumbnail_path;
|
|
|
+ // 缩略图文件存在时再同步 OSS,避免空文件名导致无效上传
|
|
|
+ if (!empty($thumbnail_path) && is_file($fullThumbnailPath)) {
|
|
|
+ $ossSync['template_thumb'] = Common::uploadLocalFileToOss((string)$fullThumbnailPath, (string)$db_thumbnail_path);
|
|
|
+ } else {
|
|
|
+ $ossSync['template_thumb'] = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $dateYmd = date('Y-m-d');
|
|
|
+ $newGalleryPathsForRollback = []; // 多页预览路径;插入失败时用于回滚删除
|
|
|
+ // 多页预览图:preview_images 与页下标一致,落盘 + OSS,JSON 存入 page_image_urls(TEXT,需建表字段)
|
|
|
+ if (!empty($params['preview_images']) && is_array($params['preview_images'])) {
|
|
|
+ $gal = $this->savePreviewGalleryBase64List($params['preview_images'], $save_path, $dateYmd);
|
|
|
+ $newGalleryPathsForRollback = $gal['paths'];
|
|
|
+ $ossSync['page_images'] = $gal['oss'];
|
|
|
+ }
|
|
|
|
|
|
//新增到模版表(product_template)
|
|
|
$record['toexamine'] = '审核通过';
|
|
|
|
|
|
$record['sys_id'] = $params['sys_id'];
|
|
|
+ // 多页提示词:chinese_description 为数组时下标 0=第 1 页…,入库 JSON;缺省存 []
|
|
|
+ $record['chinese_description'] = Common::encodeChineseDescriptionForDb($params['chinese_description'] ?? []);
|
|
|
+ $record['template_name'] = isset($params['template_name']) ? (string) $params['template_name'] : '';
|
|
|
$record['canvasWidth'] = $params['canvasWidth'];
|
|
|
$record['canvasHeight'] = $params['canvasHeight'];
|
|
|
$record['size'] = $params['canvasRatio'];
|
|
|
$record['template_image_url'] = $db_img_path;//原图
|
|
|
$record['thumbnail_image'] = $db_thumbnail_path;//缩略图
|
|
|
+ if (!empty($params['preview_images']) && is_array($params['preview_images'])) {
|
|
|
+ $record['page_image_urls'] = json_encode($newGalleryPathsForRollback, JSON_UNESCAPED_UNICODE);
|
|
|
+ }
|
|
|
|
|
|
$record['sys_rq'] = date('Y-m-d');
|
|
|
$record['create_time'] = date('Y-m-d H:i:s');
|
|
|
@@ -376,6 +642,7 @@ class Material extends Api
|
|
|
if (file_exists($full_file_path)) {
|
|
|
unlink($full_file_path);
|
|
|
}
|
|
|
+ $this->deleteStoredPageGalleryImages($newGalleryPathsForRollback);
|
|
|
return '数据库插入失败';
|
|
|
}
|
|
|
|
|
|
@@ -423,7 +690,7 @@ class Material extends Api
|
|
|
'fill_mode' => $layer['fill_mode'] ?? $layer['fillMode'] ?? '',//填充模式 solid/none
|
|
|
'fill_color' => $layer['fill_color'] ?? $layer['fillColor'] ?? '',//填充色
|
|
|
'stroke_color' => $layer['stroke_color'] ?? $layer['strokeColor'] ?? '',//描边色
|
|
|
- 'page_index' => $layer['page_index'] ?? $layer['page_index'] ?? '',//画布分页排序
|
|
|
+ 'page_index' => $this->layerPageIndex($layer),// 多页:0=第 1 页画布
|
|
|
'stroke_width' => isset($layer['stroke_width']) ? floatval($layer['stroke_width']) : (isset($layer['strokeWidth']) ? floatval($layer['strokeWidth']) : 0),//描边宽度
|
|
|
'create_time' => date('Y-m-d H:i:s')
|
|
|
|
|
|
@@ -432,6 +699,11 @@ class Material extends Api
|
|
|
// 插入关联记录
|
|
|
Db::name('template_material_relation')->insert($relationData);
|
|
|
}
|
|
|
+ $this->incrementMaterialUseCountsFromIds($this->collectMaterialIdsFromLayers($layers, $layerIdToMaterial));
|
|
|
+ }
|
|
|
+ $pageUrlsOut = [];
|
|
|
+ foreach ($newGalleryPathsForRollback as $pu) {
|
|
|
+ $pageUrlsOut[] = ($pu === '' || $pu === null) ? '' : Common::ossFullUrl((string) $pu);
|
|
|
}
|
|
|
return json([
|
|
|
'code' => 0,
|
|
|
@@ -439,7 +711,10 @@ class Material extends Api
|
|
|
'data' => '',
|
|
|
'template_id' => $templateId,
|
|
|
'template_image_url' => $db_img_path,
|
|
|
- 'template_image' => $db_thumbnail_path
|
|
|
+ 'template_image' => $db_thumbnail_path,
|
|
|
+ 'page_image_urls' => $pageUrlsOut,
|
|
|
+ // OSS 是否开启、各文件是否上传成功(任一为 false 时查 runtime/log 中 [OSS uploadLocalFileToOss])
|
|
|
+ 'oss_sync' => $ossSync,
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
@@ -472,6 +747,9 @@ class Material extends Api
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
+ $oldGalleryPathsToDeleteAfterDbOk = [];
|
|
|
+ $newGalleryPathsRollbackOnFail = [];
|
|
|
+
|
|
|
// 处理 uploaded_materials:修改时会有新的素材图上传,保存到 uploads/material/ 并写入 template_material 表(参考新增模版)
|
|
|
$layerIdToMaterial = [];
|
|
|
if (!empty($params['uploaded_materials'])) {
|
|
|
@@ -496,12 +774,17 @@ class Material extends Api
|
|
|
continue;
|
|
|
}
|
|
|
$materialUrl = 'uploads/material/' . date('Y-m-d') . '/' . $fileName;
|
|
|
+ // 修改模版时新增素材:先本地保存,再同步 OSS(失败不阻塞更新)
|
|
|
+ Common::uploadLocalFileToOss((string)$fullPath, (string)$materialUrl);
|
|
|
$materialRecord = [
|
|
|
'sys_id' => $params['sys_id'] ?? '',
|
|
|
'material_url' => $materialUrl,
|
|
|
'type' => $item['type'] ?? '',
|
|
|
+ 'Category_id' => $item['Category_id'] ?? '',
|
|
|
+ 'chinese_description' => $item['chinese_description'] ?? '',
|
|
|
+ 'material_name' => $item['material_name'] ?? '',
|
|
|
'create_time' => date('Y-m-d H:i:s'),
|
|
|
- 'count' => 1
|
|
|
+ 'count' => 0
|
|
|
];
|
|
|
$materialId = Db::name('template_material')->insertGetId($materialRecord);
|
|
|
if ($materialId && isset($item['layer_id'])) {
|
|
|
@@ -559,10 +842,17 @@ class Material extends Api
|
|
|
}
|
|
|
// 生成数据库存储路径(使用正斜杠格式)
|
|
|
$db_img_path = '/uploads/template/'. date('Y-m-d') .'/' . $file_name;
|
|
|
+ // 修改模版预览图:同步 OSS,便于前端统一使用云端地址
|
|
|
+ Common::uploadLocalFileToOss((string)$full_file_path, (string)$db_img_path);
|
|
|
|
|
|
// 生成缩略图
|
|
|
$thumbnail_path = $this->generateThumbnail($full_file_path, $save_path, $file_name);
|
|
|
$db_thumbnail_path = '/uploads/template/'.date('Y-m-d') .'/' . $thumbnail_path;
|
|
|
+ $fullThumbnailPath = $save_path . $thumbnail_path;
|
|
|
+ // 修改模版缩略图:存在则同步 OSS
|
|
|
+ if (!empty($thumbnail_path) && is_file($fullThumbnailPath)) {
|
|
|
+ Common::uploadLocalFileToOss((string)$fullThumbnailPath, (string)$db_thumbnail_path);
|
|
|
+ }
|
|
|
|
|
|
// 删除旧图片
|
|
|
if (!empty($template['template_image_url'])) {
|
|
|
@@ -588,14 +878,31 @@ class Material extends Api
|
|
|
$record['thumbnail_image'] = $db_thumbnail_path;//缩略图
|
|
|
}
|
|
|
|
|
|
+ if (array_key_exists('preview_images', $params) && is_array($params['preview_images'])) {
|
|
|
+ $oldGalleryPathsToDeleteAfterDbOk = $this->decodePageImageUrlsField($template['page_image_urls'] ?? null);
|
|
|
+ $saveGal = str_replace('\\', '/', ROOT_PATH . 'public/uploads/template/' . date('Y-m-d') . '/');
|
|
|
+ if (!is_dir($saveGal)) {
|
|
|
+ mkdir($saveGal, 0755, true);
|
|
|
+ }
|
|
|
+ $gal = $this->savePreviewGalleryBase64List($params['preview_images'], $saveGal, date('Y-m-d'));
|
|
|
+ $newGalleryPathsRollbackOnFail = $gal['paths'];
|
|
|
+ $record['page_image_urls'] = json_encode($gal['paths'], JSON_UNESCAPED_UNICODE);
|
|
|
+ }
|
|
|
+
|
|
|
$record['sys_rq'] = date('Y-m-d');
|
|
|
- $record['template_name'] = $params['template_name'];
|
|
|
+ if (array_key_exists('chinese_description', $params)) {
|
|
|
+ $record['chinese_description'] = Common::encodeChineseDescriptionForDb($params['chinese_description']);
|
|
|
+ }
|
|
|
+ if (array_key_exists('template_name', $params)) {
|
|
|
+ $record['template_name'] = (string) $params['template_name'];
|
|
|
+ }
|
|
|
$record['update_time'] = date('Y-m-d H:i:s');
|
|
|
|
|
|
// 更新模板记录
|
|
|
$res = Db::name('product_template')->where('id', $templateId)->update($record);
|
|
|
|
|
|
if (!$res) {
|
|
|
+ $this->deleteStoredPageGalleryImages($newGalleryPathsRollbackOnFail);
|
|
|
return json([
|
|
|
'code' => 1,
|
|
|
'msg' => '数据库更新失败',
|
|
|
@@ -603,8 +910,19 @@ class Material extends Api
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
+ if ($oldGalleryPathsToDeleteAfterDbOk !== []) {
|
|
|
+ foreach ($oldGalleryPathsToDeleteAfterDbOk as $p) {
|
|
|
+ if ($p === null || $p === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $this->unlinkMaterialFileByUrl((string) $p);
|
|
|
+ Common::deleteOssObject((string) $p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 处理layers数据,更新模版-素材表(template_material_relation)
|
|
|
if (!empty($params['layers'])) {
|
|
|
+ $this->decrementMaterialUseCountsByTemplateId($templateId);
|
|
|
// 删除旧的关联记录
|
|
|
Db::name('template_material_relation')->where('template_id', $templateId)->delete();
|
|
|
|
|
|
@@ -650,7 +968,7 @@ class Material extends Api
|
|
|
'fill_mode' => $layer['fill_mode'] ?? $layer['fillMode'] ?? '',//填充模式 solid/none
|
|
|
'fill_color' => $layer['fill_color'] ?? $layer['fillColor'] ?? '',//填充色
|
|
|
'stroke_color' => $layer['stroke_color'] ?? $layer['strokeColor'] ?? '',//描边色
|
|
|
- 'page_index' => $layer['page_index'] ?? $layer['page_index'] ?? '',//画布分页排序
|
|
|
+ 'page_index' => $this->layerPageIndex($layer),// 多页:0=第 1 页画布
|
|
|
'stroke_width' => isset($layer['stroke_width']) ? floatval($layer['stroke_width']) : (isset($layer['strokeWidth']) ? floatval($layer['strokeWidth']) : 0),//描边宽度
|
|
|
'create_time' => date('Y-m-d H:i:s')
|
|
|
|
|
|
@@ -658,14 +976,25 @@ class Material extends Api
|
|
|
// 插入关联记录
|
|
|
Db::name('template_material_relation')->insert($relationData);
|
|
|
}
|
|
|
+ $this->incrementMaterialUseCountsFromIds($this->collectMaterialIdsFromLayers($layers, $layerIdToMaterial));
|
|
|
}
|
|
|
+ $pageGalleryOut = array_key_exists('preview_images', $params)
|
|
|
+ ? $newGalleryPathsRollbackOnFail
|
|
|
+ : $this->decodePageImageUrlsField($template['page_image_urls'] ?? null);
|
|
|
+ foreach ($pageGalleryOut as &$gp) {
|
|
|
+ if ($gp !== null && $gp !== '') {
|
|
|
+ $gp = Common::ossFullUrl((string) $gp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($gp);
|
|
|
return json([
|
|
|
'code' => 0,
|
|
|
'msg' => '修改成功',
|
|
|
'data' => '',
|
|
|
'template_id' => $templateId,
|
|
|
'template_image_url' => $db_img_path,
|
|
|
- 'template_image' => $db_thumbnail_path
|
|
|
+ 'template_image' => $db_thumbnail_path,
|
|
|
+ 'page_image_urls' => $pageGalleryOut,
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
@@ -802,8 +1131,33 @@ class Material extends Api
|
|
|
*/
|
|
|
public function Template_Material_Delete(){
|
|
|
$params = $this->request->param();
|
|
|
- $record['mod_rq'] = date('Y-m-d H:i:s');
|
|
|
- $res = Db::name('product_template')->where('id', $params['template_id'])->update($record);
|
|
|
+ if (empty($params['template_id']) || !is_numeric($params['template_id'])) {
|
|
|
+ return json([
|
|
|
+ 'code' => 1,
|
|
|
+ 'msg' => 'template_id 参数错误',
|
|
|
+ 'data' => ''
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ $templateId = intval($params['template_id']);
|
|
|
+ $template = Db::name('product_template')->where('id', $templateId)->find();
|
|
|
+ if (!$template) {
|
|
|
+ return json([
|
|
|
+ 'code' => 1,
|
|
|
+ 'msg' => '模版不存在',
|
|
|
+ 'data' => ''
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除本地文件
|
|
|
+ $this->unlinkMaterialFileByUrl($template['template_image_url'] ?? '');
|
|
|
+ $this->unlinkMaterialFileByUrl($template['thumbnail_image'] ?? '');
|
|
|
+ $this->deleteStoredPageGalleryImages($template['page_image_urls'] ?? '');
|
|
|
+ // 删除 OSS 文件(失败不阻断主流程)
|
|
|
+ Common::deleteOssObject((string)($template['template_image_url'] ?? ''));
|
|
|
+ Common::deleteOssObject((string)($template['thumbnail_image'] ?? ''));
|
|
|
+
|
|
|
+ // 删除模板记录(物理删除)
|
|
|
+ $res = Db::name('product_template')->where('id', $templateId)->delete();
|
|
|
if (!$res) {
|
|
|
return json([
|
|
|
'code' => 1,
|