diff --git a/app/admin/controller/v1/SiteConfig.php b/app/admin/controller/v1/SiteConfig.php index c1ca70e2..165bc36f 100644 --- a/app/admin/controller/v1/SiteConfig.php +++ b/app/admin/controller/v1/SiteConfig.php @@ -141,6 +141,44 @@ class SiteConfig return $ret; } + // 根据分组获取配置 + public function getByGroupUniqueLabel($unique_label) + { + $configs = SysConfigModel::alias('c') + ->field([ + 'c.id', + 'c.title', + 'c.name', + 'c.value', + ]) + ->join(SysConfigGroupModel::getTable(). ' g', 'g.id = c.group_id') + ->where('g.language_id', '=', request()->lang_id) + ->where('g.unique_label', '=', $unique_label) + ->where('g.status', '=', 1) + ->order(['c.sort' => 'asc', 'c.id' => 'desc']) + ->select() + ->each(function ($item) { + // 修改字段为null的输出为空字符串 + $keys = array_keys($item->toArray()); + foreach ($keys as $key) { + if (is_null($item[$key])) { + $item[$key] = ''; + } + } + return $item; + }) + ->toArray(); + if (!empty($configs)) { + $configs_map = []; + foreach ($configs as $cfg) { + $configs_map[$cfg['name']] = $cfg; + } + return $configs_map; + } + + return []; + } + // 更新配置 public function update() { diff --git a/app/admin/controller/v1/System.php b/app/admin/controller/v1/System.php index d53350ce..bd8c2ef2 100644 --- a/app/admin/controller/v1/System.php +++ b/app/admin/controller/v1/System.php @@ -8,7 +8,6 @@ use app\admin\model\v1\ArticleModel; use app\admin\model\v1\ProductCategoryModel; use app\admin\model\v1\ProductModel; use think\facade\Db; -use think\facade\Route; class System { @@ -147,7 +146,11 @@ class System 'name' => '产品分类', 'link_to' => 'product_category', 'data' => array_to_tree(array_map(function($item) { - $item['url'] = (string)url('/index/product/category/' . $item['id']); + if ($item['pid'] == 0) { + $item['url'] = (string)url('/index/product/category/'. $item['id']); + } else { + $item['url'] = (string)url('/index/product/subcategory/' . $item['id']); + } return $item; }, $product_category), 0, 'pid', false, false) ], @@ -155,6 +158,11 @@ class System 'name' => '产品', 'link_to' => 'product', 'data' => array_to_tree($product_category, 0, 'pid', false, false) + ], + [ + 'name' => '其他内页', + 'link_to' => 'system_page', + 'data' => self::getSystemOtherPages() ] ]; @@ -220,6 +228,124 @@ class System return $data->toArray(); } + // 获取系统其他内页 + static private function getSystemOtherPages() + { + return [ + [ + 'id' => 1, + 'name' => '首页', + 'url' => (string)url('/index/index/index') + ], + [ + 'id' => 2, + 'name' => '新品上市', + 'url' => (string)url('/index/product/newpro') + ], + [ + 'id' => 3, + 'name' => '附件下载', + 'url' => (string)url('/index/attachment/index') + ], + [ + 'id' => 4, + 'name' => '问答中心', + 'url' => (string)url('/index/faq/index') + ], + [ + 'id' => 5, + 'name' => '关于我们', + 'url' => '', + 'children' => [ + [ + 'id' => 51, + 'name' => '品牌介绍', + 'url' => (string)url('/index/aboutus/introduction') + ], + [ + 'id' => 52, + 'name' => '品牌故事', + 'url' => (string)url('/index/aboutus/story') + ], + [ + 'id' => 53, + 'name' => '品牌历程', + 'url' => (string)url('/index/aboutus/mileage') + ], + [ + 'id' => 54, + 'name' => '文化介绍', + 'url' => (string)url('/index/aboutus/culture') + ], + [ + 'id' => 55, + 'name' => '售后政策', + 'url' => (string)url('/index/aboutus/policy') + ] + ] + ], + [ + 'id' => 6, + 'name' => '联系我们', + 'url' => '', + 'children' => [ + [ + 'id' => 61, + 'name' => '联系我们', + 'url' => (string)url('/index/contactus/index') + ], + [ + 'id' => 62, + 'name' => '留言联系我们', + 'url' => (string)url('/index/contactus/message') + ], + [ + 'id' => 63, + 'name' => '留言成为分销商', + 'url' => (string)url('/index/contactus/distributor') + ], + [ + 'id' => 64, + 'name' => '留言批量购买', + 'url' => (string)url('/index/contactus/bulkbuy') + ] + ] + ], + [ + 'id' => 7, + 'name' => 'NAS专题', + 'url' => '', + 'children' => [ + [ + 'id' => 71, + 'name' => '首页', + 'url' => (string)url('/index/topic/nas/index') + ], + [ + 'id' => 72, + 'name' => '产品体验', + 'url' => (string)url('/index/topic/nas/product') + ], + [ + 'id' => 73, + 'name' => '客户合作', + 'url' => (string)url('/index/topic/nas/cooperation') + ], + [ + 'id' => 74, + 'name' => '帮助中心', + 'url' => (string)url('/index/topic/nas/help') + ], + [ + 'id' => 75, + 'name' => '软件下载', + 'url' => (string)url('/index/topic/nas/download') + ] + ] + ] + ]; + } + // 根据系统页面url获取回显数据项 static public function getEchoDataBySystemPageUrl($link_to, $link) { @@ -235,21 +361,29 @@ class System parse_str($url, $params); } - if (empty($params['id'])) return []; - switch ($link_to) { case 'article_category': + if (empty($params['id'])) return []; $data = ArticleCategoryModel::field(['id', 'name'])->bypk($params['id'])->find(); break; case 'article': + if (empty($params['id'])) return []; $data = ArticleModel::field(['id', 'title' => 'name'])->bypk($params['id'])->find(); break; case 'product_category': + if (empty($params['id'])) return []; $data = ProductCategoryModel::field(['id', 'name'])->bypk($params['id'])->find(); break; case 'product': + if (empty($params['id'])) return []; $data = ProductModel::field(['id', 'name'])->bypk($params['id'])->find(); break; + case 'system_page': + $data = self::filterSystemOtherPage(self::getSystemOtherPages(), function($item) use ($params, $link) { + if (empty($params['id'])) return $item['url'] == $link; + return $item['id'] == $params['id']; + }); + break; default: return []; break; @@ -262,4 +396,22 @@ class System 'link' => $link ]; } + + // 根据条件过滤结果 + static private function filterSystemOtherPage(array $data, callable $callback): array + { + foreach ($data as $it) { + if ($callback($it)) { + return $it; + } + if (isset($it['children'])) { + $child = self::filterSystemOtherPage($it['children'], $callback); + if (!empty($child)) { + return $child; + } + } + } + + return []; + } } \ No newline at end of file diff --git a/app/admin/controller/v1/Upload.php b/app/admin/controller/v1/Upload.php index e9d58ec8..c8ba0a66 100644 --- a/app/admin/controller/v1/Upload.php +++ b/app/admin/controller/v1/Upload.php @@ -7,6 +7,7 @@ use app\admin\model\v1\SysImageUploadRecordModel; use app\admin\model\v1\SysVideoUploadRecordModel; use app\admin\model\v1\SysAttachmentUploadRecordModel; use Intervention\Image\ImageManager; +use Intervention\Image\Typography\FontFactory; use think\facade\Filesystem; /** @@ -44,9 +45,75 @@ class Upload $image_model = SysImageUploadRecordModel::md5($filemd5)->find(); if (is_null($image_model)) { $filename = Filesystem::disk('image')->putFile($param['module'], $file); - // 生成缩略图 - $image_manager = new ImageManager(new \Intervention\Image\Drivers\Imagick\Driver()); + // 处理图片 + $image_manager = ImageManager::imagick(); $image = $image_manager->read('.' . $storage . '/' . $filename); + + // 水印 + list( + 'enabled' => $enabled, + 'type' => $type, + 'text_options' => $text_options, + 'image_options' => $image_options + ) = $this->getWatermarkOptions(); + if ($enabled) { + // 图片水印 + if ($type == 'IMAGE' && $image_options['image'] != '') { + // 读取水印图片 + $watermark_image = $image_manager->read(public_path() . $image_options['image']); + // 缩放水印图片 + $watermark_image->scale($image_options['width'], $image_options['height']); + // 绘制水印图片 + $image->place( + $watermark_image, + $image_options['position'], + $image_options['offset_x'], + $image_options['offset_y'], + $image_options['opacity'] + ); + } + // 文字水印 + else if ($type == 'TEXT' && $text_options['txt'] != '') { + // 原图宽度 + $origin_width = $image->width(); + // 原图高度 + $origin_height = $image->height(); + + $font_factory = new FontFactory(function(FontFactory $font) use($text_options) { + // 设置字体 + $font->filename(public_path() . $text_options['font']); + // 设置字体大小 + $font->size($text_options['size']); + // 设置字体颜色及透明度 + $opacity = $text_options['opacity'] > 0 ? dechex((int)ceil(255 * ($text_options['opacity'] / 100))) : '00'; + $font->color($text_options['color'] . $opacity); + $font->align('left'); + $font->valign('top'); + }); + // 文字尺寸 + $font_rect = $image->driver()->fontProcessor()->boxSize($text_options['txt'], $font_factory()); + // 计算偏移量 + list($offset_x, $offset_y) = $this->scaleTxtOffsetXYByPosition( + $text_options['position'], + $text_options['offset_x'], + $text_options['offset_y'], + $origin_width, + $origin_height, + $font_rect->width(), + $font_rect->height() + ); + // 绘制文字 + $image->text( + $text_options['txt'], + $offset_x, + $offset_y, + $font_factory() + ); + } + $image->save('.'. $storage. '/'. $filename); + } + + // 缩略图 $image->scale(200, 200); $idx = strrpos($filename, '.'); $thumb_filename = mb_substr($filename, 0, $idx) . '_thumb.' . mb_substr($filename, $idx + 1); @@ -79,6 +146,90 @@ class Upload return error('上传失败'); } + /** + * 获取水印配置 + * + * @return array + */ + private function getWatermarkOptions(): array + { + $config_model = new \app\admin\controller\v1\SiteConfig; + $watermark_config = $config_model->getByGroupUniqueLabel('watermark'); + + $opacity = data_get($watermark_config, 'watermark_opacity.value', 100); + if ($opacity == '') { + $opacity = 100; + } + return [ + 'enabled' => data_get($watermark_config, 'watermark_enabled.value', 0) == 1, + 'type' => data_get($watermark_config, 'watermark_type.value', ''), + 'text_options' => [ + 'txt' => data_get($watermark_config, 'watermark_text_value.value', ''), + 'font' => data_get($watermark_config, 'watermark_text_font.value', ''), + 'size' => (float)data_get($watermark_config, 'watermark_text_size.value', 12)?:12, + 'color' => data_get($watermark_config, 'watermark_text_color.value', '#000000')?:'#000000', + 'position' => data_get($watermark_config, 'watermark_position.value', 'top-left')?:'top-left', + 'offset_x' => (int)data_get($watermark_config, 'watermark_offset_x.value', 0), + 'offset_y' => (int)data_get($watermark_config, 'watermark_offset_y.value', 0), + 'opacity' => (int)$opacity, + ], + 'image_options' => [ + 'image' => data_get($watermark_config, 'watermark_image_value.value', ''), + 'width' => (int)data_get($watermark_config, 'watermark_image_width.value')?:null, + 'height' => (int)data_get($watermark_config, 'watermark_image_height.value')?:null, + 'position' => data_get($watermark_config, 'watermark_position.value', 'top-left')?:'top-left', + 'offset_x' => (int)data_get($watermark_config, 'watermark_offset_x.value', 0), + 'offset_y' => (int)data_get($watermark_config, 'watermark_offset_y.value', 0), + 'opacity' => (int)$opacity, + ] + ]; + } + /** + * 计算文体水印偏移量 + * + * @param string $position + * @param integer $offset_x + * @param integer $offset_y + * @param integer $image_width + * @param integer $image_height + * @param integer $txt_width + * @param integer $txt_height + * @return array + */ + private function scaleTxtOffsetXYByPosition(string $position, int $offset_x, int $offset_y, int $image_width, int $image_height, int $txt_width, int $txt_height) + { + switch ($position) { + case 'top-left': + // top-left:左上角 + return [$offset_x, $offset_y]; + case 'top-right': + // top-right:右上角 + return [(int)($image_width-$txt_width-$offset_x), $offset_y]; + case 'top': + // top:上 - 水平居中 + return [(int)(($image_width-$txt_width+$offset_x)/2), $offset_y]; + case 'left': + // left:左 - 垂直居中 + return [$offset_x, (int)(($image_height-$txt_height)/2+$offset_y)]; + case 'center': + // center:水平垂直居中 + return [(int)(($image_width-$txt_width)/2+$offset_x), (int)(($image_height-$txt_height)/2+$offset_y)]; + case 'right': + // right:右 - 垂直居中 + return [(int)($image_width-$txt_width-$offset_x), (int)(($image_height-$txt_height)/2+$offset_y)]; + case'bottom': + // bottom:下 - 水平居中 + return [(int)(($image_width-$txt_width+$offset_x)/2), (int)($image_height-$txt_height-$offset_y)]; + case'bottom-left': + // bottom-left:左下角 + return [$offset_x, (int)($image_height-$txt_height-$offset_y)]; + case'bottom-right': + // bottom-right:右下角 + return [(int)($image_width-$txt_width-$offset_x), (int)($image_height-$txt_height-$offset_y)]; + default: + throw new \InvalidArgumentException('Invalid position'); + } + } /** * 上传视频 @@ -152,7 +303,7 @@ class Upload try { $max_size = strtobytes(env('ADMIN_API.MAX_ATTACHMENT_SIZE', '100mb')); $validate = validate([ - 'attachment' => "fileSize:$max_size|fileExt:biz,bz,bz2,gz,tgz,zip,rar,7z,doc,docx,xls,xlsx,csv,ppt,pptx,pdf,txt,jpg,jpeg,png" + 'attachment' => "fileSize:$max_size|fileExt:biz,bz,bz2,gz,tgz,zip,rar,7z,doc,docx,xls,xlsx,csv,ppt,pptx,pdf,txt,jpg,jpeg,png,ttf" ]); if (!$validate->check(['attachment' => $file])) { return error($validate->getError()); diff --git a/app/admin/controller/v1/Video.php b/app/admin/controller/v1/Video.php index 70296b40..6ec0586b 100644 --- a/app/admin/controller/v1/Video.php +++ b/app/admin/controller/v1/Video.php @@ -50,7 +50,8 @@ class Video 'page' => $params['page'], ]) ->bindAttr('category', ['category_name']) - ->hidden(['category', 'category_id']); + ->hidden(['category', 'category_id']) + ?->each(fn($item) => $item->image = thumb($item->image)); return success('获取成功', $videos); } diff --git a/app/admin/controller/v1/VideoTrash.php b/app/admin/controller/v1/VideoTrash.php index 48b6c833..3d811dec 100644 --- a/app/admin/controller/v1/VideoTrash.php +++ b/app/admin/controller/v1/VideoTrash.php @@ -47,7 +47,8 @@ class VideoTrash 'page' => $params['page'], ]) ->bindAttr('category', ['category_name']) - ->hidden(['category_id', 'category']); + ->hidden(['category_id', 'category']) + ?->each(fn($item) => $item->image = thumb($item->image)); return success('获取成功', $videos); } diff --git a/app/admin/validate/v1/NavigationItemValidate.php b/app/admin/validate/v1/NavigationItemValidate.php index 94deb29b..70bdae9c 100644 --- a/app/admin/validate/v1/NavigationItemValidate.php +++ b/app/admin/validate/v1/NavigationItemValidate.php @@ -21,7 +21,7 @@ class NavigationItemValidate extends Validate 'pid' => 'integer|different:id|checkPidNotBeChildren', 'name' => 'require|max:64', 'icon' => 'max:64', - 'link_to' => 'require|max:64|in:article,article_category,product,product_category,custom', + 'link_to' => 'require|max:64|in:article,article_category,product,product_category,system_page,custom', 'link' => 'max:255', 'sort' => 'integer', 'blank' => 'in:0,1', @@ -47,7 +47,7 @@ class NavigationItemValidate extends Validate 'icon.max' => '图标最多不能超过64个字符', 'link_to.require' => '链接类型不能为空', 'link_to.max' => '链接类型最多不能超过64个字符', - 'link_to.in' => '链接类型必须是article,goods_category,goods,custom中之一', + 'link_to.in' => '链接类型必须是article,article_category,product_category,product,system_page,custom中之一', 'link.max' => '链接最多不能超过255个字符', 'sort.integer' => '排序必须为整数', 'blank.in' => '是否新窗口打开只能是0或1', diff --git a/app/admin/validate/v1/SysBannerItemValidate.php b/app/admin/validate/v1/SysBannerItemValidate.php index d02b30e7..39b8d285 100644 --- a/app/admin/validate/v1/SysBannerItemValidate.php +++ b/app/admin/validate/v1/SysBannerItemValidate.php @@ -23,7 +23,7 @@ class SysBannerItemValidate extends Validate 'type' => 'in:image,video', 'image' => 'max:255', 'video' => 'max:255', - 'link_to' => 'requireIf:type,image|max:64|in:article,article_category,product,product_category,custom', + 'link_to' => 'requireIf:type,image|max:64|in:article,article_category,product,product_category,system_page,custom', 'link' => 'max:255', 'sort' => 'integer', 'status' => 'in:-1,1' @@ -50,7 +50,7 @@ class SysBannerItemValidate extends Validate 'video.max' => '视频地址最多不能超过255个字符', 'link_to.requireIf' => '链接类型不能为空', 'link_to.max' => '链接类型最多不能超过64个字符', - 'link_to.in' => '链接类型必须是article,article_category,product,product_category,custom中之一', + 'link_to.in' => '链接类型必须是article,article_category,product,product_category,system_page,custom中之一', 'link.max' => '链接最多不能超过255个字符', 'sort.integer' => '排序值必须是整数', 'status.in' => '状态必须是-1或1' diff --git a/app/index/controller/TopicNas.php b/app/index/controller/TopicNas.php index e5bccc4d..70e15097 100644 --- a/app/index/controller/TopicNas.php +++ b/app/index/controller/TopicNas.php @@ -111,7 +111,9 @@ class TopicNas extends Common $trial_instructions = []; // 获取banner数据 $banners = SysBannerModel::with(['items' => function($query) { - $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at'])->enabled(true); + $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at']) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->enabled(true); }]) ->atPlatform(request()->from) ->uniqueLabel([ @@ -150,7 +152,9 @@ class TopicNas extends Common $cooperation_cotacts = []; // 获取banner数据 $banners = SysBannerModel::with(['items' => function($query) { - $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at'])->enabled(true); + $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at']) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->enabled(true); }]) ->atPlatform(request()->from) ->uniqueLabel([ @@ -189,7 +193,9 @@ class TopicNas extends Common // 获取文章分类及文章数据 $parent = ArticleCategoryModel::uniqueLabel('CATEGORY_681182e0a4529')->language($this->lang_id)->value('id'); $article_categorys = ArticleCategoryModel::with(['article' => function($query) { - $query->field(['id', 'title', 'category_id'])->limit(3); + $query->field(['id', 'title', 'category_id']) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->limit(3); }]) ->field([ 'id', @@ -205,7 +211,9 @@ class TopicNas extends Common $contacts = []; // 获取banner数据 $banners = SysBannerModel::with(['items' => function ($query) { - $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at'])->enabled(true); + $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at']) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->enabled(true); }]) ->atPlatform(request()->from) ->uniqueLabel(['BANNER_6819754be2dc6']) @@ -238,7 +246,7 @@ class TopicNas extends Common // 获取文章分类及文章数据 $parent = ArticleCategoryModel::uniqueLabel('CATEGORY_681182e0a4529')->language($this->lang_id)->value('id'); $article_categorys = ArticleCategoryModel::with(['article' => function ($query) { - $query->field(['id', 'title', 'category_id']); + $query->field(['id', 'title', 'category_id'])->order(['sort' => 'asc', 'id' => 'desc']); }]) ->field([ 'id', @@ -261,6 +269,25 @@ class TopicNas extends Common { $keywords = request()->post('keywords'); // 根据关键词查询文章 + $parent = ArticleCategoryModel::uniqueLabel('CATEGORY_681182e0a4529') + ->language($this->lang_id) + ->value('id'); + + // 获取帮且中心分类子分类 + $table_name = (new ArticleCategoryModel)->getTable(); + $categorys = \think\facade\Db::query(preg_replace( + '/\s+/u', + ' ', + "WITH RECURSIVE article_tree_by AS ( + SELECT a.id, a.pid FROM $table_name a WHERE a.id = {$parent} + UNION ALL + SELECT k.id, k.pid FROM $table_name k INNER JOIN article_tree_by t ON t.id = k.pid + ) + SELECT id FROM article_tree_by WHERE id <> {$parent}" + )); + if (empty($categorys)) return success('success', []); + + // 获取文章数据 $articles = ArticleModel::field([ 'id', 'title' @@ -269,6 +296,7 @@ class TopicNas extends Common 'title' => $keywords??null ]) ->language($this->lang_id) + ->where('category_id', 'IN', array_column($categorys, 'id')) ->select(); return success('success', $articles->toArray()); @@ -282,7 +310,9 @@ class TopicNas extends Common $data = []; // 获取banner数据 $banners = SysBannerModel::with(['items' => function($query) { - $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at'])->enabled(true); + $query->withoutField(['sort', 'created_at', 'updated_at', 'deleted_at']) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->enabled(true); }]) ->atPlatform(request()->from) ->uniqueLabel([ diff --git a/app/index/view/pc/contact_us/distributor.html b/app/index/view/pc/contact_us/distributor.html index c6893a16..d735dab0 100644 --- a/app/index/view/pc/contact_us/distributor.html +++ b/app/index/view/pc/contact_us/distributor.html @@ -61,7 +61,7 @@
{$topic.title}
-{$topic.title}
-{:lang_i18n('明星产品/热点产品')}
-
-
- {$topic.title}
+{$topic.title}
+{:lang_i18n('明星产品/热点产品')}
+
+
+ {:lang_i18n('ORICO 技术')}
- {:lang_i18n('强大功能、简单使用')} - -
-
-
-
- {:lang_i18n('回答您最关心的问题')}
-{:lang_i18n('客服团队的工作时间:周一到周五,早9点到晚6点 平均应答时间:24小时内')}
-{$faq.answer|raw}
-{:lang_i18n('ORICO 技术')}
+ {:lang_i18n('强大功能、简单使用')} + +
+
+
+
+ {:lang_i18n('回答您最关心的问题')}
+{:lang_i18n('客服团队的工作时间:周一到周五,早9点到晚6点 平均应答时间:24小时内')}
+{$faq.answer|raw}
+