HomePage.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <?php
  2. namespace app\common\library\ps;
  3. use think\Db;
  4. /**
  5. * 工厂端首页模块。后台可增删改,前台按权重渲染。
  6. */
  7. class HomePage
  8. {
  9. public static function typeList()
  10. {
  11. return [
  12. 'hero' => '头图介绍',
  13. 'stats' => '宣传数据',
  14. 'steps' => '操作流程',
  15. 'features' => '功能卡片',
  16. 'cta' => '底部行动',
  17. 'custom' => '自定义图文',
  18. ];
  19. }
  20. /**
  21. * @return array
  22. */
  23. public static function published()
  24. {
  25. try {
  26. $rows = Db::name('ps_home_block')->where('status', 'normal')->order('weigh desc,id asc')->select();
  27. } catch (\Exception $e) {
  28. return self::defaults();
  29. }
  30. $out = [];
  31. foreach ($rows as $row) {
  32. $out[] = self::formatRow($row);
  33. }
  34. return $out;
  35. }
  36. /**
  37. * @param array $row
  38. * @return array
  39. */
  40. public static function formatRow(array $row)
  41. {
  42. $content = [];
  43. if (!empty($row['content'])) {
  44. $decoded = json_decode($row['content'], true);
  45. if (is_array($decoded)) {
  46. $content = $decoded;
  47. }
  48. }
  49. return [
  50. 'id' => (int)$row['id'],
  51. 'type' => (string)$row['type'],
  52. 'name' => (string)$row['name'],
  53. 'kicker' => (string)$row['kicker'],
  54. 'title' => (string)$row['title'],
  55. 'subtitle' => (string)$row['subtitle'],
  56. 'weigh' => (int)$row['weigh'],
  57. 'content' => $content,
  58. ];
  59. }
  60. /**
  61. * @return array
  62. */
  63. public static function defaults()
  64. {
  65. $now = time();
  66. $rows = [
  67. [
  68. 'id' => 0,
  69. 'type' => 'hero',
  70. 'name' => '头图',
  71. 'kicker' => '',
  72. 'title' => '智印流程单',
  73. 'subtitle' => '把工单数据,准确地打在已印好的纸上',
  74. 'weigh' => 100,
  75. 'content' => [
  76. 'eyebrow' => '给车间用的智能套打',
  77. 'brand' => '智印流程单',
  78. 'lead' => '文件导入就能识别产品与工序,选好模板、填好数量,即可批量套打。模板可自制,也可在商城里选用,每次打印都留得下记录。',
  79. 'btn_primary_text' => '开始套打',
  80. 'btn_primary_path' => '/workorders/edit',
  81. 'btn_secondary_text' => '了解流程',
  82. 'btn_secondary_path' => '#home-flow',
  83. 'panel_title' => '今天的车间',
  84. 'notes' => [
  85. '导入文件,AI 帮你认出编号和工序',
  86. '数量只在套打时填写,一版一张',
  87. '按原纸格子对齐,字打在该打的地方',
  88. ],
  89. ],
  90. ],
  91. [
  92. 'id' => 0,
  93. 'type' => 'stats',
  94. 'name' => '宣传数据',
  95. 'kicker' => '',
  96. 'title' => '',
  97. 'subtitle' => '',
  98. 'weigh' => 90,
  99. 'content' => [
  100. 'items' => [
  101. ['label' => '入驻厂家', 'key' => 'factory_count', 'value' => ''],
  102. ['label' => '今日打印量', 'key' => 'print_today', 'value' => ''],
  103. ['label' => '累计套打张数', 'key' => 'print_total', 'value' => ''],
  104. ['label' => '可用模板', 'key' => 'template_count', 'value' => ''],
  105. ],
  106. ],
  107. ],
  108. [
  109. 'id' => 0,
  110. 'type' => 'steps',
  111. 'name' => '操作流程',
  112. 'kicker' => '怎么用',
  113. 'title' => '四步走完,就能出单',
  114. 'subtitle' => '从建档到打印,车间按这个顺序做就行,不用先学一堆设置。',
  115. 'weigh' => 80,
  116. 'content' => [
  117. 'items' => [
  118. ['title' => '建档或导入', 'desc' => '手工新建产品 / 工单,或用文件导入让 AI 识别编号、名称和工序。'],
  119. ['title' => '准备模板', 'desc' => '在本厂模板中选用已识版样式,也可到模板商城补充官方或市场模板。'],
  120. ['title' => '套打出单', 'desc' => '填写本次总数量和每版数量,预览拆张后确认打印,只印字段不印底图。'],
  121. ['title' => '记录与分析', 'desc' => '在打印日志里查看流水和补打记录,首页看板同步展示打印量与工厂数据。'],
  122. ],
  123. ],
  124. ],
  125. [
  126. 'id' => 0,
  127. 'type' => 'features',
  128. 'name' => '功能介绍',
  129. 'kicker' => '能做什么',
  130. 'title' => '日常出单用得到的能力',
  131. 'subtitle' => '识别、套打、模板、商城、日志和分析都在这一处,点卡片就能进去。',
  132. 'weigh' => 70,
  133. 'content' => [
  134. 'items' => [
  135. ['tag' => '识别', 'title' => '文件导入 AI 识别', 'desc' => '上传 Excel、PDF 或图片,自动提取编号、名称与工序,核对后写入本厂档案。', 'path' => '/workorders'],
  136. ['tag' => '套打', 'title' => '批量打印', 'desc' => '按总数量和每版数量自动拆张,一次确认即可连续套打多张流程单。', 'path' => '/templates'],
  137. ['tag' => '模板', 'title' => '自定义模板', 'desc' => '上传车间流程单识版,格子坐标与字段对齐后,按原纸套打不跑偏。', 'path' => '/templates'],
  138. ['tag' => '商城', 'title' => '模板商城', 'desc' => '选用官方或市场模板,单次或买断授权,快速补齐本厂可套打样式。', 'path' => '/market'],
  139. ['tag' => '日志', 'title' => '打印日志', 'desc' => '每次套打留流水号、模板与张数,便于车间对账、补打和追溯。', 'path' => '/print/history'],
  140. ['tag' => '分析', 'title' => '数据智能分析', 'desc' => '汇总入驻工厂、日打印量、本厂工单与套打次数,看清车间打印节奏。', 'path' => '/print/history'],
  141. ],
  142. ],
  143. ],
  144. [
  145. 'id' => 0,
  146. 'type' => 'cta',
  147. 'name' => '底部行动',
  148. 'kicker' => '',
  149. 'title' => '准备好就开打',
  150. 'subtitle' => '选一张本厂模板,填上本次数量,流程单就能按原纸套出来。',
  151. 'weigh' => 60,
  152. 'content' => [
  153. 'btn_primary_text' => '选择模板套打',
  154. 'btn_primary_path' => '/templates',
  155. 'btn_secondary_text' => '进入工单',
  156. 'btn_secondary_path' => '/workorders',
  157. ],
  158. ],
  159. ];
  160. foreach ($rows as &$row) {
  161. $row['createtime'] = $now;
  162. $row['updatetime'] = $now;
  163. $row['status'] = 'normal';
  164. }
  165. unset($row);
  166. return $rows;
  167. }
  168. /**
  169. * @param array $params
  170. * @return array
  171. */
  172. public static function normalize(array $params)
  173. {
  174. $type = isset($params['type']) ? (string)$params['type'] : 'custom';
  175. if (!isset(self::typeList()[$type])) {
  176. $type = 'custom';
  177. }
  178. $params['type'] = $type;
  179. $params['name'] = mb_substr(trim((string)($params['name'] ?? '')), 0, 64);
  180. $params['kicker'] = mb_substr(trim((string)($params['kicker'] ?? '')), 0, 64);
  181. $params['title'] = mb_substr(trim((string)($params['title'] ?? '')), 0, 120);
  182. $params['subtitle'] = mb_substr(trim((string)($params['subtitle'] ?? '')), 0, 255);
  183. $params['weigh'] = (int)($params['weigh'] ?? 0);
  184. $params['status'] = ($params['status'] ?? '') === 'hidden' ? 'hidden' : 'normal';
  185. $raw = $params['content_json'] ?? $params['content'] ?? [];
  186. if (is_string($raw)) {
  187. $decoded = json_decode($raw, true);
  188. $raw = is_array($decoded) ? $decoded : [];
  189. }
  190. if (!is_array($raw)) {
  191. $raw = [];
  192. }
  193. $params['content'] = json_encode(self::cleanContent($type, $raw), JSON_UNESCAPED_UNICODE);
  194. unset($params['content_json'], $params['items'], $params['notes']);
  195. return $params;
  196. }
  197. /**
  198. * @param string $type
  199. * @param array $raw
  200. * @return array
  201. */
  202. public static function cleanContent($type, array $raw)
  203. {
  204. $str = function ($key, $max = 255) use ($raw) {
  205. return mb_substr(trim((string)($raw[$key] ?? '')), 0, $max);
  206. };
  207. if ($type === 'hero') {
  208. $notes = $raw['notes'] ?? [];
  209. if (is_string($notes)) {
  210. $notes = preg_split('/\r\n|\n/', $notes);
  211. }
  212. $cleanNotes = [];
  213. foreach ((array)$notes as $note) {
  214. $note = trim((string)$note);
  215. if ($note !== '') {
  216. $cleanNotes[] = mb_substr($note, 0, 120);
  217. }
  218. }
  219. return [
  220. 'eyebrow' => $str('eyebrow', 40),
  221. 'brand' => $str('brand', 40),
  222. 'lead' => $str('lead', 500),
  223. 'btn_primary_text' => $str('btn_primary_text', 20),
  224. 'btn_primary_path' => $str('btn_primary_path', 120),
  225. 'btn_secondary_text' => $str('btn_secondary_text', 20),
  226. 'btn_secondary_path' => $str('btn_secondary_path', 120),
  227. 'panel_title' => $str('panel_title', 40),
  228. 'notes' => $cleanNotes,
  229. ];
  230. }
  231. if ($type === 'stats' || $type === 'steps' || $type === 'features') {
  232. $items = [];
  233. foreach ((array)($raw['items'] ?? []) as $item) {
  234. if (!is_array($item)) {
  235. continue;
  236. }
  237. if ($type === 'stats') {
  238. $label = mb_substr(trim((string)($item['label'] ?? '')), 0, 40);
  239. if ($label === '') {
  240. continue;
  241. }
  242. $items[] = [
  243. 'label' => $label,
  244. 'key' => mb_substr(trim((string)($item['key'] ?? '')), 0, 32),
  245. 'value' => mb_substr(trim((string)($item['value'] ?? '')), 0, 32),
  246. ];
  247. } elseif ($type === 'steps') {
  248. $title = mb_substr(trim((string)($item['title'] ?? '')), 0, 40);
  249. if ($title === '') {
  250. continue;
  251. }
  252. $items[] = [
  253. 'title' => $title,
  254. 'desc' => mb_substr(trim((string)($item['desc'] ?? '')), 0, 200),
  255. ];
  256. } else {
  257. $title = mb_substr(trim((string)($item['title'] ?? '')), 0, 40);
  258. if ($title === '') {
  259. continue;
  260. }
  261. $items[] = [
  262. 'tag' => mb_substr(trim((string)($item['tag'] ?? '')), 0, 12),
  263. 'title' => $title,
  264. 'desc' => mb_substr(trim((string)($item['desc'] ?? '')), 0, 200),
  265. 'path' => mb_substr(trim((string)($item['path'] ?? '')), 0, 120),
  266. ];
  267. }
  268. }
  269. return ['items' => $items];
  270. }
  271. if ($type === 'cta') {
  272. return [
  273. 'btn_primary_text' => $str('btn_primary_text', 20),
  274. 'btn_primary_path' => $str('btn_primary_path', 120),
  275. 'btn_secondary_text' => $str('btn_secondary_text', 20),
  276. 'btn_secondary_path' => $str('btn_secondary_path', 120),
  277. ];
  278. }
  279. return [
  280. 'body' => $str('body', 2000),
  281. 'btn_text' => $str('btn_text', 20),
  282. 'btn_path' => $str('btn_path', 120),
  283. ];
  284. }
  285. }