Compare commits
2 Commits
4f6cc77646
...
b1866a8cdd
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b1866a8cdd | ||
![]() |
d6a59d9f37 |
@ -0,0 +1,88 @@
|
|||||||
|
package com.ruoyi.base.controller;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.base.vo.KeywordVO;
|
||||||
|
import com.ruoyi.busi.service.GoogleKeywordService;
|
||||||
|
import com.ruoyi.common.config.GoogleConfig;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.base.domain.BaseSeo;
|
||||||
|
import com.ruoyi.base.service.IBaseSeoService;
|
||||||
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
|
||||||
|
import static com.ruoyi.constant.StrConstants.COUNTRY;
|
||||||
|
import static com.ruoyi.constant.StrConstants.LANGUAGE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEO用到的国家和语言Controller
|
||||||
|
*
|
||||||
|
* @author vinjor-m
|
||||||
|
* @date 2025-08-04
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/base/seo")
|
||||||
|
public class BaseSeoController extends BaseController {
|
||||||
|
@Autowired
|
||||||
|
private GoogleConfig googleConfig;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSeoService baseSeoService;
|
||||||
|
@Autowired
|
||||||
|
private GoogleKeywordService googleKeywordService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询SEO用到的国家和语言列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public AjaxResult list() {
|
||||||
|
LambdaQueryWrapper<BaseSeo> queryWrapper = new LambdaQueryWrapper<BaseSeo>().orderByAsc(BaseSeo::getSort);
|
||||||
|
List<BaseSeo> list = baseSeoService.list(queryWrapper);
|
||||||
|
List<BaseSeo> languageList = list.stream().filter(item -> LANGUAGE.equals(item.getDataType())).collect(Collectors.toList());
|
||||||
|
List<BaseSeo> countryList = list.stream().filter(item -> COUNTRY.equals(item.getDataType())).collect(Collectors.toList());
|
||||||
|
languageList.forEach(item->item.setTitle(item.getTitle()+"("+item.getCn()+")"));
|
||||||
|
countryList.forEach(item->item.setTitle(item.getTitle()+"("+item.getCn()+")"));
|
||||||
|
Map<String, Object> rtnMap = new HashMap<>();
|
||||||
|
rtnMap.put("language", languageList);
|
||||||
|
rtnMap.put("country", countryList);
|
||||||
|
return success(rtnMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取google关键词
|
||||||
|
*
|
||||||
|
* @param language 语言
|
||||||
|
* @param country 国家
|
||||||
|
* @param title 关键词
|
||||||
|
* @param url 产品预览网址
|
||||||
|
* @return com.ruoyi.common.core.domain.AjaxResult
|
||||||
|
* @author vinjor-M
|
||||||
|
* @date 17:51 2025/8/4
|
||||||
|
**/
|
||||||
|
@GetMapping("/getKeywords")
|
||||||
|
public AjaxResult getKeywords(String language, String country, String title, String url) {
|
||||||
|
Map<String,Object> rtnMap = new HashMap<>();
|
||||||
|
rtnMap.put("searchDown",googleConfig.getSearchDown());
|
||||||
|
rtnMap.put("dataList",googleKeywordService.getKeywords(language, country, title, url));
|
||||||
|
return success(rtnMap);
|
||||||
|
}
|
||||||
|
}
|
@ -465,12 +465,6 @@ public class WebController extends BaseController {
|
|||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/test")
|
|
||||||
public R<?> test() {
|
|
||||||
googleKeywordService.test();
|
|
||||||
return R.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导航栏接口--所有分类
|
* 导航栏接口--所有分类
|
||||||
* @author vinjor-M
|
* @author vinjor-M
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.ruoyi.base.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
import com.ruoyi.common.core.domain.DlBaseEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEO用到的国家和语言对象 dl_base_seo
|
||||||
|
*
|
||||||
|
* @author vinjor-m
|
||||||
|
* @date 2025-08-04
|
||||||
|
*/
|
||||||
|
@TableName("dl_base_seo")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BaseSeo extends DlBaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 主键 */
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 数据类型(language-语言|country-国家) */
|
||||||
|
@Excel(name = "数据类型", readConverterExp = "l=anguage-语言|country-国家")
|
||||||
|
private String dataType;
|
||||||
|
|
||||||
|
/** 排序 */
|
||||||
|
@Excel(name = "排序")
|
||||||
|
private Long sort;
|
||||||
|
|
||||||
|
/** 标题 */
|
||||||
|
@Excel(name = "标题")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/** 值 */
|
||||||
|
@Excel(name = "值")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/** 描述 */
|
||||||
|
@Excel(name = "描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/** 图片 */
|
||||||
|
@Excel(name = "图片")
|
||||||
|
private String pic;
|
||||||
|
|
||||||
|
/** 中文 */
|
||||||
|
@Excel(name = "中文")
|
||||||
|
private String cn;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.ruoyi.base.mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.base.domain.BaseSeo;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEO用到的国家和语言Mapper接口
|
||||||
|
*
|
||||||
|
* @author vinjor-m
|
||||||
|
* @date 2025-08-04
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface BaseSeoMapper extends BaseMapper<BaseSeo>
|
||||||
|
{
|
||||||
|
IPage<BaseSeo> queryListPage(@Param("entity") BaseSeo entity, Page<BaseSeo> page);
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.base.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.ruoyi.base.domain.BaseSeo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEO用到的国家和语言Service接口
|
||||||
|
*
|
||||||
|
* @author vinjor-m
|
||||||
|
* @date 2025-08-04
|
||||||
|
*/
|
||||||
|
public interface IBaseSeoService extends IService<BaseSeo>
|
||||||
|
{
|
||||||
|
IPage<BaseSeo> queryListPage(BaseSeo pageReqVO, Page<BaseSeo> page);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.ruoyi.base.service.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.ruoyi.base.mapper.BaseSeoMapper;
|
||||||
|
import com.ruoyi.base.domain.BaseSeo;
|
||||||
|
import com.ruoyi.base.service.IBaseSeoService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEO用到的国家和语言Service业务层处理
|
||||||
|
*
|
||||||
|
* @author vinjor-m
|
||||||
|
* @date 2025-08-04
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class BaseSeoServiceImpl extends ServiceImpl<BaseSeoMapper,BaseSeo> implements IBaseSeoService
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private BaseSeoMapper baseSeoMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<BaseSeo> queryListPage(BaseSeo pageReqVO, Page<BaseSeo> page) {
|
||||||
|
return baseSeoMapper.queryListPage(pageReqVO, page);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.ruoyi.base.vo;
|
||||||
|
|
||||||
|
import com.ruoyi.base.domain.BasePics;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 谷歌关键词规划师VO
|
||||||
|
* @author vinjor-M
|
||||||
|
* @date 17:53 2025/8/4
|
||||||
|
**/
|
||||||
|
@Data
|
||||||
|
public class KeywordVO {
|
||||||
|
/**
|
||||||
|
* 关键词
|
||||||
|
**/
|
||||||
|
private String title;
|
||||||
|
/**
|
||||||
|
* 月搜索量
|
||||||
|
**/
|
||||||
|
private Long searchMonth;
|
||||||
|
/**
|
||||||
|
* 竞争程度
|
||||||
|
**/
|
||||||
|
private String competition;
|
||||||
|
}
|
@ -1,18 +1,14 @@
|
|||||||
package com.ruoyi.busi.service;
|
package com.ruoyi.busi.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.google.ads.googleads.lib.GoogleAdsClient;
|
import com.google.ads.googleads.lib.GoogleAdsClient;
|
||||||
import com.google.ads.googleads.v20.common.Keyword;
|
|
||||||
import com.google.ads.googleads.v20.common.KeywordAnnotations;
|
|
||||||
import com.google.ads.googleads.v20.common.KeywordConcept;
|
|
||||||
import com.google.ads.googleads.v20.enums.KeywordPlanKeywordAnnotationEnum;
|
|
||||||
import com.google.ads.googleads.v20.enums.KeywordPlanNetworkEnum;
|
import com.google.ads.googleads.v20.enums.KeywordPlanNetworkEnum;
|
||||||
import com.google.ads.googleads.v20.enums.KeywordPlanKeywordAnnotationEnum.KeywordPlanKeywordAnnotation;
|
|
||||||
import com.google.ads.googleads.v20.services.*;
|
import com.google.ads.googleads.v20.services.*;
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
import com.google.auth.oauth2.ServiceAccountCredentials;
|
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||||
|
import com.ruoyi.base.vo.KeywordVO;
|
||||||
import com.ruoyi.common.config.GoogleConfig;
|
import com.ruoyi.common.config.GoogleConfig;
|
||||||
import com.ruoyi.common.config.OssConfig;
|
import com.ruoyi.constant.CompetitionEnum;
|
||||||
import lombok.var;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -21,8 +17,6 @@ import org.springframework.util.ResourceUtils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -30,7 +24,19 @@ public class GoogleKeywordService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private GoogleConfig googleConfig;
|
private GoogleConfig googleConfig;
|
||||||
|
|
||||||
public void test() {
|
/**
|
||||||
|
* 获取谷歌关键词规划师规划的关键词
|
||||||
|
*
|
||||||
|
* @param language 语言编码
|
||||||
|
* @param country 国家编码,多个英文逗号隔开
|
||||||
|
* @param title 关键词,多个英文逗号分隔
|
||||||
|
* @param url url
|
||||||
|
* @return java.util.List<com.ruoyi.base.vo.KeywordVO>
|
||||||
|
* @author vinjor-M
|
||||||
|
* @date 17:55 2025/8/4
|
||||||
|
**/
|
||||||
|
public List<KeywordVO> getKeywords(String language, String country, String title, String url) {
|
||||||
|
List<KeywordVO> keywordList = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
// 1. 检查文件是否存在
|
// 1. 检查文件是否存在
|
||||||
File file = ResourceUtils.getFile("file:" + googleConfig.getJsonPath());
|
File file = ResourceUtils.getFile("file:" + googleConfig.getJsonPath());
|
||||||
@ -49,31 +55,42 @@ public class GoogleKeywordService {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 2. 设置参数
|
// 2. 设置参数
|
||||||
List<String> seedKeywords = Arrays.asList("TRUCK");
|
List<String> seedKeywords = Arrays.asList(title.split(StrUtil.COMMA));
|
||||||
// 英语
|
// 语言
|
||||||
String language = "languageConstants/1000";
|
language = "languageConstants/"+language;
|
||||||
// 美国
|
// 国家/地区
|
||||||
String geoTarget = "geoTargetConstants/2840";
|
List<String> geoTargetList = new ArrayList<>();
|
||||||
|
for (String item:country.split(StrUtil.COMMA)){
|
||||||
|
geoTargetList.add("geoTargetConstants/"+item);
|
||||||
|
}
|
||||||
// 3. 获取关键词提示
|
// 3. 获取关键词提示
|
||||||
List<GenerateKeywordIdeaResult> keywordIdeas = generateKeywordIdeas(
|
List<GenerateKeywordIdeaResult> keywordIdeas = generateKeywordIdeas(
|
||||||
googleAdsClient, googleConfig.getCustomerId(), seedKeywords, language, geoTarget);
|
googleAdsClient, googleConfig.getCustomerId(), seedKeywords, language, geoTargetList,url);
|
||||||
|
|
||||||
// 4. 输出结果
|
// 4. 输出结果
|
||||||
for (GenerateKeywordIdeaResult idea : keywordIdeas) {
|
for (GenerateKeywordIdeaResult idea : keywordIdeas) {
|
||||||
// 获取关键词文本
|
// 获取关键词文本
|
||||||
String text = idea.getText();
|
String text = idea.getText();
|
||||||
|
KeywordVO keywordVO = new KeywordVO();
|
||||||
|
keywordVO.setTitle(text);
|
||||||
// 获取关键词指标(可选)
|
// 获取关键词指标(可选)
|
||||||
if (idea.hasKeywordIdeaMetrics()) {
|
if (idea.hasKeywordIdeaMetrics()) {
|
||||||
long avgMonthlySearches = idea.getKeywordIdeaMetrics().getAvgMonthlySearches();
|
long avgMonthlySearches = idea.getKeywordIdeaMetrics().getAvgMonthlySearches();
|
||||||
String competition = String.valueOf(idea.getKeywordIdeaMetrics().getCompetition());
|
String competition = String.valueOf(idea.getKeywordIdeaMetrics().getCompetition());
|
||||||
System.out.println("找到的关键词文本:" + text+";月搜索量:" + avgMonthlySearches+";竞争程度:" + competition);
|
if(avgMonthlySearches>=googleConfig.getSearchDown()){
|
||||||
|
//大于要求的最低月搜索量,返回
|
||||||
|
keywordVO.setSearchMonth(avgMonthlySearches);
|
||||||
|
keywordVO.setCompetition(CompetitionEnum.getTitleByCode(competition).getTitle());
|
||||||
|
keywordList.add(keywordVO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.printf("初始化 Google Ads 客户端失败: %s%n", e.getMessage());
|
System.err.printf("初始化 Google Ads 客户端失败: %s%n", e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
// 倒序排列
|
||||||
|
keywordList.sort((o1, o2) -> Long.compare(o2.getSearchMonth(), o1.getSearchMonth()));
|
||||||
|
return keywordList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,7 +100,8 @@ public class GoogleKeywordService {
|
|||||||
* @param customerId 客户ID
|
* @param customerId 客户ID
|
||||||
* @param seedKeywords 种子关键词列表
|
* @param seedKeywords 种子关键词列表
|
||||||
* @param language 语言常量 (如 "languageConstants/1000" 表示英语)
|
* @param language 语言常量 (如 "languageConstants/1000" 表示英语)
|
||||||
* @param geoTarget 地理位置目标常量 (如 "geoTargetConstants/2840" 表示美国)
|
* @param geoTargets 地理位置目标常量 (如 "geoTargetConstants/2840" 表示美国)
|
||||||
|
* @param url 网址
|
||||||
* @return 关键词提示列表
|
* @return 关键词提示列表
|
||||||
* @throws IOException 如果API调用失败
|
* @throws IOException 如果API调用失败
|
||||||
*/
|
*/
|
||||||
@ -92,24 +110,36 @@ public class GoogleKeywordService {
|
|||||||
long customerId,
|
long customerId,
|
||||||
List<String> seedKeywords,
|
List<String> seedKeywords,
|
||||||
String language,
|
String language,
|
||||||
String geoTarget) throws IOException {
|
List<String> geoTargets,String url) throws IOException {
|
||||||
|
|
||||||
List<GenerateKeywordIdeaResult> allKeywordIdeas = new ArrayList<>();
|
List<GenerateKeywordIdeaResult> allKeywordIdeas = new ArrayList<>();
|
||||||
|
//分页token
|
||||||
String nextPageToken = null;
|
String nextPageToken = null;
|
||||||
|
//组装关键词请求体
|
||||||
KeywordAndUrlSeed seed = KeywordAndUrlSeed.newBuilder()
|
KeywordAndUrlSeed urlSeed = null;
|
||||||
.addAllKeywords(seedKeywords)
|
KeywordSeed seed = null;
|
||||||
.setUrl("https://www.cdtruck.com/truck/dump-truck/china-sinotruk-howo-19m3-6x4-cheap-336hp-10.html") // 提供相关 URL
|
if(StringUtils.isNotEmpty(url)){
|
||||||
.build();
|
urlSeed = KeywordAndUrlSeed.newBuilder()
|
||||||
|
.addAllKeywords(seedKeywords)
|
||||||
|
.setUrl(url)
|
||||||
|
.build();
|
||||||
|
}else{
|
||||||
|
seed = KeywordSeed.newBuilder()
|
||||||
|
.addAllKeywords(seedKeywords)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
try (KeywordPlanIdeaServiceClient client =
|
try (KeywordPlanIdeaServiceClient client =
|
||||||
googleAdsClient.getLatestVersion().createKeywordPlanIdeaServiceClient()) {
|
googleAdsClient.getLatestVersion().createKeywordPlanIdeaServiceClient()) {
|
||||||
GenerateKeywordIdeasRequest.Builder requestBuilder = GenerateKeywordIdeasRequest.newBuilder()
|
GenerateKeywordIdeasRequest.Builder requestBuilder = GenerateKeywordIdeasRequest.newBuilder()
|
||||||
.setCustomerId(Long.toString(customerId))
|
.setCustomerId(Long.toString(customerId))
|
||||||
.setLanguage(String.valueOf(1000))
|
.setLanguage(language)
|
||||||
.addAllGeoTargetConstants(Collections.singletonList(geoTarget))
|
.addAllGeoTargetConstants(geoTargets)
|
||||||
.setKeywordPlanNetwork(KeywordPlanNetworkEnum.KeywordPlanNetwork.GOOGLE_SEARCH_AND_PARTNERS)
|
.setKeywordPlanNetwork(KeywordPlanNetworkEnum.KeywordPlanNetwork.GOOGLE_SEARCH_AND_PARTNERS);
|
||||||
.setKeywordAndUrlSeed(seed);
|
if(StringUtils.isNotEmpty(url)){
|
||||||
|
requestBuilder.setKeywordAndUrlSeed(urlSeed);
|
||||||
|
}else{
|
||||||
|
requestBuilder.setKeywordSeed(seed);
|
||||||
|
}
|
||||||
// 仅在非首次请求时设置 pageToken
|
// 仅在非首次请求时设置 pageToken
|
||||||
if (nextPageToken != null) {
|
if (nextPageToken != null) {
|
||||||
requestBuilder.setPageToken(nextPageToken);
|
requestBuilder.setPageToken(nextPageToken);
|
||||||
@ -118,10 +148,9 @@ public class GoogleKeywordService {
|
|||||||
GenerateKeywordIdeaResponse response = client.generateKeywordIdeasCallable().call(request);
|
GenerateKeywordIdeaResponse response = client.generateKeywordIdeasCallable().call(request);
|
||||||
allKeywordIdeas.addAll(response.getResultsList());
|
allKeywordIdeas.addAll(response.getResultsList());
|
||||||
nextPageToken = response.getNextPageToken();
|
nextPageToken = response.getNextPageToken();
|
||||||
System.out.println("分页token:"+nextPageToken);
|
|
||||||
}
|
}
|
||||||
} while (StringUtils.isNotEmpty(nextPageToken));
|
} while (StringUtils.isNotEmpty(nextPageToken));
|
||||||
System.out.println("关键词总数:"+allKeywordIdeas.size());
|
System.out.println("关键词总数:" + allKeywordIdeas.size());
|
||||||
return allKeywordIdeas;
|
return allKeywordIdeas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
package com.ruoyi.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 竞争程度枚举值
|
||||||
|
*
|
||||||
|
* @author vinjor-m
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum CompetitionEnum {
|
||||||
|
/**
|
||||||
|
*UNSPECIFIED-未知
|
||||||
|
*/
|
||||||
|
UNSPECIFIED("UNSPECIFIED","未知"),
|
||||||
|
/**
|
||||||
|
*UNKNOWN-未知
|
||||||
|
*/
|
||||||
|
UNKNOWN("UNKNOWN","未知"),
|
||||||
|
/**
|
||||||
|
*LOW-低
|
||||||
|
*/
|
||||||
|
LOW("LOW","低"),
|
||||||
|
/**
|
||||||
|
*MEDIUM-中
|
||||||
|
*/
|
||||||
|
MEDIUM("MEDIUM","中"),
|
||||||
|
/**
|
||||||
|
*HIGH-高
|
||||||
|
*/
|
||||||
|
HIGH("HIGH","高");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* code
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
/**
|
||||||
|
* 根据code返回对应的枚举
|
||||||
|
* @author vinjor-M
|
||||||
|
* @date 14:23 2024/10/16
|
||||||
|
* @param code code
|
||||||
|
**/
|
||||||
|
public static CompetitionEnum getTitleByCode(String code) {
|
||||||
|
for (CompetitionEnum thisEnum : CompetitionEnum.values()) {
|
||||||
|
if (thisEnum.getCode().equalsIgnoreCase(code)) {
|
||||||
|
// 找到对应的枚举
|
||||||
|
return thisEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("无效的code:" + code);
|
||||||
|
}
|
||||||
|
}
|
@ -27,4 +27,13 @@ public class StrConstants {
|
|||||||
* 取权重最高的前N个词
|
* 取权重最高的前N个词
|
||||||
*/
|
*/
|
||||||
public static final int TOP_N = 3;
|
public static final int TOP_N = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言
|
||||||
|
*/
|
||||||
|
public static final String LANGUAGE = "language";
|
||||||
|
/**
|
||||||
|
* 国家
|
||||||
|
*/
|
||||||
|
public static final String COUNTRY = "country";
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,12 @@ aliyun:
|
|||||||
#google ads配置
|
#google ads配置
|
||||||
google:
|
google:
|
||||||
ads:
|
ads:
|
||||||
customer-id: 4577179829
|
#经理账号id
|
||||||
developer-token: qwFA3dGfOGqEhSEHlCMorA
|
customer-id: 3052342727
|
||||||
json-path: /www/wwwroot/nuxt/test.json
|
#开发者令牌
|
||||||
|
developer-token: 7e_-FWCPRjWc16zr8uaUDg
|
||||||
|
#服务账号秘钥文件路径
|
||||||
|
json-path: /www/wwwroot/nuxt/chengda.json
|
||||||
|
#月搜索量下限
|
||||||
|
search-down: 500
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"type": "service_account",
|
"type": "service_account",
|
||||||
"project_id": "chengda-466307",
|
"project_id": "chengda-467707",
|
||||||
"private_key_id": "c4f38952fb0922ff625a85d364543f415f93d03d",
|
"private_key_id": "a9a538af8b2e1e3a41f8bc6265200a432450dd00",
|
||||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCRIuvHsOaOnXv5\nlLl3R0fLrWjd8fk6tDv1bIJFpC2xfFem2rYaG1IFJVfnGgCAL7P4j6tVn0j684dL\n2wb4BRJjyK4EDYBLkugc3BWNnpt6z80f8+U39/QtDwRBJqQGvRD4LGhm5dDrAWhf\ngeiCKVC+3TGDGDwkt4JjuZQ6/GSrvX9aSaLyT5PlpYSvxH2tetb5rY6LdeAR9wS1\n0dtFD0jIxYHm96vWpe1LLhtw+28thxYeR7VFOZf6HzJLDDOt4hYbHoo+DAKaPIyC\n6+CepAcE13WWn7ooPGjh8JAGPqs2JpqR6ujFrvYXAto3UvpXSV0W6h1gRv9AD3SW\n33Te4KYzAgMBAAECggEAEu74zkCsGx+9TBZUSZSxb4csPoPyDO/1QHOK3RXpZhWA\nA8LVbbtxrD0uZfYU6aQPeNYZNl7EsQQy+rjUhGd4+i50URAH1BQSlq05XJO72b4h\nFtGE7hO5NWWXmv40+LISdCWq6v2BDx9MY+U5FT3ZjESj0GPJeMq9xk+v4DAL3AF2\nqsXsL/GhSLTyIWufIaxwUPFfumVVqjH3r9B1YqY+0Z//E/2I9W3NOSQ83JzI1osw\nQ5ybQdQ9zQptlr/kh9sb+vtgJ4+7h6RQ6Xcyy6OsW4Kg6Ho7eRTBDIytv9o7Ef+h\nVYwV6OjonAcWcbfCY2TGEO2DDJJbkZZwRby2WE5jkQKBgQDMQt/XfWCI2EVPNwpn\nl6gFY7vWcXSO+dkEloFgIpbxiJTmZTRmWPnpqqEaxUNo2KCxyuUoOCC7B+D6AdRI\nBZfxaFtQe+f71oocnWbjNtzLvW8cnZouHI1pT+HXWO84aq7fh4lkYhXsEZkhu2Ce\nV1nGjSVboe/OJ+Z9oCNVQxtlsQKBgQC15idlkFZbCR3zBDmc9LRDC1iZXyhBlSva\nT30e1M7sOO8lN2Uw825XaP4CKE70jOxL5eLkSvXALWOdeVZ79iclAoz+iE8FsLPf\nbUOWAEuUtsgyyrmkcKU/ddvtvEwsBFqZ0CxxqOCDXj+uXoGifyFLigkl3w5wvY0s\njpe1wipvIwKBgQCUvvpc5XugC8ZlSlK0X5dG3XsTTamw2Lc2BRgP1wCOwYSVRwvi\noFbV16DcatyNBHv5HSTFpiIHsVQfG6foDtK4ROOCd8jW90O6VNFxEym04K2CbC6z\n96zvDPIMrUH/lojkVMIzrM4EDEi0bMyOYlQJKA4VbZbBTQMnZq90Tpsr8QKBgQCr\nd/k9aZGuIWsFEb+JsLdY2BI+ChC1ufvrwLDO5obk8UqmR5DxUxh597QyrnK3XzzE\n00FOOUduUJst8BrRohoGbmAg9LehQpBdFu/2L/MPjjosfyP+2l079EtM0QrxF22c\nvzuWLT7vN2JKajZDyxnEzquO8rT3HAg/r29d3FoKBwKBgQCM4BNOTBT10gCPKQBx\nApULMInlweLr393rc17cJHSSLmL0z26oIfpJYVf+AbV4sLKE9HaCmwxTiR/frt9f\nEhAaOsWfprl/CFC3kcF/Vndey2v9Nlwdzl7//8c0O8ITlqt+OpK3giOUquMWuG8i\nFU9AwpseRhEp5uHZ5e1wKgsEsA==\n-----END PRIVATE KEY-----\n",
|
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaoBGTyINWYh5o\nFSmaGJ33q8wRIevL+GC6oqywzdyvk7in5MKpkGtKnx4yy8Cy0TSHp5yAsoXtblWV\n1TrrV7hia8IizB6QxkZQbcwwPz12shH7HN65m4lOdhsEjsSPZogYIwvDDtkXsAyl\nB+ALfYb+DZpX2aTST+1fvtTc+3PdrfxXBmBqmwJeTaQOVBk10BW+vDL9Y8TxuvHS\nYWbix/PJDQEpJFcQI/1shgiyeNkvBQJAxUHgb58EncukAD3F1W7wLLEJtE+5cnGU\nCH1KNHt+ymmZ/TJ1hdf/BvSjtHTgTYSM/ViEQZ7aTj1RZ1xueflSt2Pzqq84GMP3\nOJaCgcObAgMBAAECggEAC6v3h7NEDro7V1n5mPMwwWhLcCOiW/Q0wQHUjUgM14gM\nEUhYVVpcbvaR3f25XwYhs/e3/ZL8Pw7sEPJWjztA5NUDg4Lq77y3xAx1tFKhvhr0\n9oKYYWRXOf/6TB7GwZhYv5GtqpytUHdujOqKQ/PGw9idBPKd2W5FGcwa309v0JTa\ndhiaUAxi19QJNwqxDHn665EBfAHqZE76h0TR2fXiQ8XfH0T7EQuTuSI4sAD1MYaS\nU/0YnEi9eulCVJNpD5CDVFjL1C5Oa/LkMTo/SubaAd7pTY9Uo3LOrykikRBozrMc\nisObanEZDyi+y435ZMfH4ikywJq7cN4Xnlis06nBcQKBgQDTv0JaX0ze8v6fiwaZ\nrbHvEehAyRvGGaY0/mxDYM9Ioq80aIAG5/Ogh3WGDyrynz4zCu1mqjRqterzpY+H\nSP6fZzhTi7lUHGVa4QUb4QlSkDWV0BED8we4AzXnBOHmto+aMwm1bi0O3qWcbWDk\nOacLIoAOhNKOn4IAzaHWAkgH6QKBgQC68LfXb9EEGafeHIjz2o08EhqHSoMs1kAp\nvu39vh3qdMIlSXWrmGXvQS4EJyBMyq24DLufUbEMRaf2yvbCpl0NRzo3dUeXXQWh\nLBT/9erKF10IjEHd8t4PUoE4JbtuLUPUAKBCx5pbtCcAMi1wWm+kcx9uLdKw/sPC\nKBKuRJTA4wKBgA4yYpaAkTzZeezke1rOZIGs56+ATFZp3dTrwgJ6eStbjPeskfjk\nkFcQ/fYxCiYeOyNc9jHN85m7/La9QPRHFptFZmdjlNdo1/rR32/RFLjuZklXG+zx\n3HCf9ns4vpSjZNln1pzNeEPo9Z2ZT8e9fc+nToKqsRtS4np/Tvx0RrQRAoGAB/Pd\nnkifd7Evtv83xEfz4i5S+pZs5idnK193+GdyFltJNxTc8KPXEkqPjpWrhhjJMx83\njBuJkKAV+SXBQZ6aLsps65cNqJ7IcobRjeIETvyXdJmi47JFSBmWbbENPC6oyAgO\nyUh8hJIxZoy76h+uyCwtlh5U7DqqUUQrJ0a7nUkCgYBoRunRKHNRycFlMFW2rLf+\n+ayC7fIPvLiKzYg2xAHIG/ykTGmtxrqKiVQ909FyVN7k9Rbyg9ZVZdkCqG/Br7iS\nVOF7xD1SCma3pdupRP4GIQSxVZVvUo48xzXNUFWXg4bjzMEgIErG12zL286blVPi\nxMz4TKijJq2YR67kdAEzDw==\n-----END PRIVATE KEY-----\n",
|
||||||
"client_email": "chengdatest@chengda-466307.iam.gserviceaccount.com",
|
"client_email": "shandongchengda@chengda-467707.iam.gserviceaccount.com",
|
||||||
"client_id": "108513526311014104962",
|
"client_id": "107299671168418329002",
|
||||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
"token_uri": "https://oauth2.googleapis.com/token",
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/chengdatest%40chengda-466307.iam.gserviceaccount.com",
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/shandongchengda%40chengda-467707.iam.gserviceaccount.com",
|
||||||
"universe_domain": "googleapis.com"
|
"universe_domain": "googleapis.com"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.base.mapper.BaseSeoMapper">
|
||||||
|
|
||||||
|
<resultMap type="BaseSeo" id="BaseSeoResult">
|
||||||
|
<result property="id" column="id" />
|
||||||
|
<result property="dataType" column="data_type" />
|
||||||
|
<result property="sort" column="sort" />
|
||||||
|
<result property="title" column="title" />
|
||||||
|
<result property="content" column="content" />
|
||||||
|
<result property="description" column="description" />
|
||||||
|
<result property="pic" column="pic" />
|
||||||
|
<result property="cn" column="cn" />
|
||||||
|
<result property="creator" column="creator" />
|
||||||
|
<result property="createTime" column="create_time" />
|
||||||
|
<result property="updater" column="updater" />
|
||||||
|
<result property="updateTime" column="update_time" />
|
||||||
|
<result property="delFlag" column="del_flag" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectBaseSeoVo">
|
||||||
|
select id, data_type, sort, title, content, description, pic, cn, creator, create_time, updater, update_time, del_flag from dl_base_seo
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="queryListPage" parameterType="BaseSeo" resultMap="BaseSeoResult">
|
||||||
|
<include refid="selectBaseSeoVo"/>
|
||||||
|
<where>
|
||||||
|
<if test="entity.dataType != null and entity.dataType != ''"> and data_type = #{entity.dataType}</if>
|
||||||
|
<if test="entity.sort != null "> and sort = #{entity.sort}</if>
|
||||||
|
<if test="entity.title != null and entity.title != ''"> and title = #{entity.title}</if>
|
||||||
|
<if test="entity.content != null and entity.content != ''"> and content = #{entity.content}</if>
|
||||||
|
<if test="entity.description != null and entity.description != ''"> and description = #{entity.description}</if>
|
||||||
|
<if test="entity.pic != null and entity.pic != ''"> and pic = #{entity.pic}</if>
|
||||||
|
<if test="entity.cn != null and entity.cn != ''"> and cn = #{entity.cn}</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
</mapper>
|
@ -15,6 +15,7 @@ public class GoogleConfig {
|
|||||||
private Long customerId;
|
private Long customerId;
|
||||||
private String developerToken;
|
private String developerToken;
|
||||||
private String jsonPath;
|
private String jsonPath;
|
||||||
|
private Long searchDown;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ VUE_APP_TITLE = 成事达管理平台
|
|||||||
ENV = 'development'
|
ENV = 'development'
|
||||||
|
|
||||||
# 成事达管理平台/开发环境
|
# 成事达管理平台/开发环境
|
||||||
VUE_APP_BASE_API = '/dev-api'
|
VUE_APP_BASE_API = 'http://127.0.0.1:8099'
|
||||||
|
|
||||||
# 路由懒加载
|
# 路由懒加载
|
||||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||||
|
19
dl_vue/src/api/base/seo.js
Normal file
19
dl_vue/src/api/base/seo.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询SEO用到的国家和语言列表
|
||||||
|
export function listSeo(query) {
|
||||||
|
return request({
|
||||||
|
url: '/base/seo/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询关键词规划
|
||||||
|
export function getKeywords(query) {
|
||||||
|
return request({
|
||||||
|
url: '/base/seo/getKeywords',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
@ -6,99 +6,182 @@
|
|||||||
<el-button type="success" @click="saveTmp">暂 存</el-button>
|
<el-button type="success" @click="saveTmp">暂 存</el-button>
|
||||||
<el-button type="primary" @click="submitForm">发 布</el-button>
|
<el-button type="primary" @click="submitForm">发 布</el-button>
|
||||||
<el-button type="warning" @click="checkContent">相似度检测</el-button>
|
<el-button type="warning" @click="checkContent">相似度检测</el-button>
|
||||||
|
<el-button @click="toggleSideBar">{{ showKeywords ? '关闭' : '打开' }}关键词推荐</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-divider></el-divider>
|
<el-divider></el-divider>
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="150px">
|
<!-- 表单区域-->
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="8">
|
<el-col v-if="showKeywords" :span="8">
|
||||||
<el-form-item label="产品名称" prop="title">
|
<el-form :model="formSeo" ref="queryForm" :rules="rulesSeo" size="small" :inline="true" label-width="110px">
|
||||||
<div class="dl-flex-column">
|
<el-form-item label="基准词" prop="title">
|
||||||
<el-input v-model="form.title" placeholder="请输入产品名称"/>
|
|
||||||
<div class="dl-add-catg" style="width: 180px" @click="goCatgView">Google关键词获取</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="所属分类" prop="catgId">
|
|
||||||
<div class="dl-flex-column">
|
|
||||||
<treeselect style="width: 200px" v-model="form.catgId" :options="catgOptions" :normalizer="normalizer"
|
|
||||||
:noResultsText="'暂无数据'" placeholder="请选择产品分类"
|
|
||||||
/>
|
|
||||||
<div class="dl-add-catg" @click="goCatgView">添加产品分类</div>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="排序" prop="sort">
|
|
||||||
<el-input v-model="form.sort" type="number" placeholder="请输入排序"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="页面title" prop="prodTitle">
|
|
||||||
<el-input v-model="form.prodTitle" type="textarea" placeholder="请输入页面title"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="页面keyword" prop="prodKeyword">
|
|
||||||
<el-input v-model="form.prodKeyword" type="textarea" placeholder="请输入页面keyword"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="页面description" prop="prodDescription">
|
|
||||||
<el-input v-model="form.prodDescription" type="textarea" placeholder="请输入页面description"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="产品主图" prop="mainPic">
|
|
||||||
<el-tag v-if="!form.mainPic" style="cursor: pointer" @click="choosePic('mainPic',1)">图片库选择</el-tag>
|
|
||||||
<image-upload @uploadedImg="uploadedImg" v-model="form.mainPic" :limit="1"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16">
|
|
||||||
<el-form-item label="产品图库" prop="pics">
|
|
||||||
<el-tag v-if="canPicsNum>0" style="cursor: pointer" @click="choosePic('pics',canPicsNum)">图片库选择</el-tag>
|
|
||||||
<image-upload @uploadedImg="uploadedImg" @input="listenerPicNum" v-model="form.pics" :limit="picsNum"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="18">
|
|
||||||
<el-form-item label="产品简介" prop="description">
|
|
||||||
<div class="dl-flex-column">
|
|
||||||
<el-input style="width: 80%" ref="descriptionInput" v-model="form.description" type="textarea"
|
|
||||||
placeholder="请输入内容" @blur="handleBlur"
|
|
||||||
/>
|
|
||||||
<el-tag style="cursor: pointer;margin-left: 10px" @click="insertBr">插入换行符</el-tag>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-form-item label="首页显示" prop="ifReco">
|
|
||||||
<template v-slot:label>
|
<template v-slot:label>
|
||||||
<span>首页显示</span>
|
<span>基准词</span>
|
||||||
<el-tooltip class="item" effect="dark" content="开启后将在首页优先显示"
|
<el-tooltip class="item" effect="dark" content="请输入您的产品名称,尽量不要过于具体或泛化" placement="bottom">
|
||||||
placement="bottom"
|
<i class="el-icon-question"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<div class="dl-flex-column">
|
||||||
|
<el-input style="width: 150px" v-model="formSeo.title" placeholder="请输入基准词"/>
|
||||||
|
<div class="dl-add-catg" @click="useProdName">使用产品名称</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="语言" prop="language">
|
||||||
|
<el-select style="width: 150px" v-model="formSeo.language" filterable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in languageArray"
|
||||||
|
:key="item.content"
|
||||||
|
:label="item.title"
|
||||||
|
:value="item.content"
|
||||||
>
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结合产品内容" prop="needUrl">
|
||||||
|
<template v-slot:label>
|
||||||
|
<span>结合产品内容</span>
|
||||||
|
<el-tooltip class="item" effect="dark" content="请确保已经录入产品内容并保存!" placement="bottom">
|
||||||
<i class="el-icon-question"></i>
|
<i class="el-icon-question"></i>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="form.ifReco"
|
v-model="formSeo.needUrl"
|
||||||
>
|
>
|
||||||
</el-switch>
|
</el-switch>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-form-item label="国家/地区" prop="country">
|
||||||
</el-row>
|
<el-select v-model="formSeo.country" multiple filterable placeholder="请选择">
|
||||||
<el-form-item label="产品内容">
|
<el-option
|
||||||
<editor v-model="form.content" :min-height="192"/>
|
v-for="item in countryArray"
|
||||||
</el-form-item>
|
:key="item.content"
|
||||||
</el-form>
|
:label="item.title"
|
||||||
|
:value="item.content"
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-tag type="warning" v-if="keywordsList.length>0">共抓取到{{keywordsList.length}}个关键词,月搜索量低于{{searchDown}}的关键词已为您自动忽略</el-tag>
|
||||||
|
<el-table v-loading="loading" :data="keywordsList" max-height="500">
|
||||||
|
<el-table-column type="index" width="60" label="序号" align="center"></el-table-column>
|
||||||
|
<el-table-column label="关键词" align="center" prop="title"/>
|
||||||
|
<el-table-column label="月搜索量" align="center" prop="searchMonth" width="100">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<countTo :startVal="0" :endVal="scope.row.searchMonth" :duration="1000"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="竞争程度" align="center" prop="competition" width="80"/>
|
||||||
|
<el-table-column width="60" label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)">
|
||||||
|
<el-button size="mini" type="text">使用</el-button>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item command="handleTitle">添加至title</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="handleKeyword">添加至keyword</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="handleDescription">添加至description</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="showKeywords?16:24">
|
||||||
|
<el-form ref="form" :model="form" :rules="rules" label-width="150px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="产品名称" prop="title">
|
||||||
|
<div class="dl-flex-column">
|
||||||
|
<el-input v-model="form.title" placeholder="请输入产品名称"/>
|
||||||
|
<div class="dl-add-catg" style="width: 180px" @click="goCatgView">Google关键词获取</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="所属分类" prop="catgId">
|
||||||
|
<div class="dl-flex-column">
|
||||||
|
<treeselect style="width: 200px" v-model="form.catgId" :options="catgOptions" :normalizer="normalizer"
|
||||||
|
:noResultsText="'暂无数据'" placeholder="请选择产品分类"
|
||||||
|
/>
|
||||||
|
<div class="dl-add-catg" @click="goCatgView">添加产品分类</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input v-model="form.sort" type="number" placeholder="请输入排序"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="页面title" prop="prodTitle">
|
||||||
|
<el-input v-model="form.prodTitle" type="textarea" placeholder="请输入页面title"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="页面keyword" prop="prodKeyword">
|
||||||
|
<el-input v-model="form.prodKeyword" type="textarea" placeholder="请输入页面keyword"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="页面description" prop="prodDescription">
|
||||||
|
<el-input v-model="form.prodDescription" type="textarea" placeholder="请输入页面description"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="产品主图" prop="mainPic">
|
||||||
|
<el-tag v-if="!form.mainPic" style="cursor: pointer" @click="choosePic('mainPic',1)">图片库选择</el-tag>
|
||||||
|
<image-upload @uploadedImg="uploadedImg" v-model="form.mainPic" :limit="1"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-form-item label="产品图库" prop="pics">
|
||||||
|
<el-tag v-if="canPicsNum>0" style="cursor: pointer" @click="choosePic('pics',canPicsNum)">图片库选择</el-tag>
|
||||||
|
<image-upload @uploadedImg="uploadedImg" @input="listenerPicNum" v-model="form.pics" :limit="picsNum"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="18">
|
||||||
|
<el-form-item label="产品简介" prop="description">
|
||||||
|
<div class="dl-flex-column">
|
||||||
|
<el-input style="width: 80%" ref="descriptionInput" v-model="form.description" type="textarea"
|
||||||
|
placeholder="请输入内容" @blur="handleBlur"
|
||||||
|
/>
|
||||||
|
<el-tag style="cursor: pointer;margin-left: 10px" @click="insertBr">插入换行符</el-tag>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="首页显示" prop="ifReco">
|
||||||
|
<template v-slot:label>
|
||||||
|
<span>首页显示</span>
|
||||||
|
<el-tooltip class="item" effect="dark" content="开启后将在首页优先显示"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<i class="el-icon-question"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<el-switch
|
||||||
|
v-model="form.ifReco"
|
||||||
|
>
|
||||||
|
</el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="产品内容">
|
||||||
|
<editor v-model="form.content" :min-height="192"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
<el-divider></el-divider>
|
<el-divider></el-divider>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
@ -115,7 +198,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import countTo from 'vue-count-to';
|
||||||
import { listCategory } from '@/api/busi/category'
|
import { listCategory } from '@/api/busi/category'
|
||||||
|
import { listSeo ,getKeywords} from '@/api/base/seo'
|
||||||
|
import Hamburger from '@/components/Hamburger'
|
||||||
import {
|
import {
|
||||||
listProdNew,
|
listProdNew,
|
||||||
getProdNew,
|
getProdNew,
|
||||||
@ -133,7 +219,7 @@ import SimilarityProduct from './similarityProduct'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'prodForm',
|
name: 'prodForm',
|
||||||
components: { SimilarityProduct, Treeselect, selectPic },
|
components: { SimilarityProduct, Treeselect, selectPic, Hamburger , countTo},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
//光标位置--简介
|
//光标位置--简介
|
||||||
@ -171,6 +257,22 @@ export default {
|
|||||||
//当前上传的所有图片
|
//当前上传的所有图片
|
||||||
fileList: []
|
fileList: []
|
||||||
},
|
},
|
||||||
|
formSeo: {
|
||||||
|
title: '',
|
||||||
|
needUrl: true,
|
||||||
|
language: '',
|
||||||
|
country:"",
|
||||||
|
},
|
||||||
|
languageArray: [],
|
||||||
|
countryArray: [],
|
||||||
|
//是否显示关键词提示区域
|
||||||
|
showKeywords: true,
|
||||||
|
// 遮罩层
|
||||||
|
loading: false,
|
||||||
|
// 关键词列表数据
|
||||||
|
keywordsList: [],
|
||||||
|
//月搜索量最低下限
|
||||||
|
searchDown:null,
|
||||||
//相似产品
|
//相似产品
|
||||||
similarityList: [],
|
similarityList: [],
|
||||||
// 表单校验
|
// 表单校验
|
||||||
@ -190,11 +292,23 @@ export default {
|
|||||||
description: [
|
description: [
|
||||||
{ required: true, message: '请输入产品简介', trigger: 'blur' }
|
{ required: true, message: '请输入产品简介', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
rulesSeo:{
|
||||||
|
title: [
|
||||||
|
{ required: true, message: '请输入基准词', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
language: [
|
||||||
|
{ required: true, message: '请选择语言', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
country: [
|
||||||
|
{ required: true, message: '请选择国家', trigger: 'blur' }
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initCatg()
|
this.initCatg()
|
||||||
|
this.initSeo()
|
||||||
if (this.$route.query.id) {
|
if (this.$route.query.id) {
|
||||||
if (this.$route.query.type) {
|
if (this.$route.query.type) {
|
||||||
//添加类似
|
//添加类似
|
||||||
@ -208,6 +322,64 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
useProdName(){
|
||||||
|
this.formSeo.title = this.form.title
|
||||||
|
},
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
handleQuery() {
|
||||||
|
this.$refs['queryForm'].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
this.loading=true
|
||||||
|
let query = JSON.parse(JSON.stringify(this.formSeo))
|
||||||
|
let countryStr = query.country.join()
|
||||||
|
query.country = countryStr
|
||||||
|
if(query.needUrl){
|
||||||
|
query.url="https://www.cdtruck.com/truck/dump-truck/china-sinotruk-howo-19m3-6x4-cheap-336hp-10.html"
|
||||||
|
}
|
||||||
|
getKeywords(query).then(response => {
|
||||||
|
this.keywordsList = response.data.dataList
|
||||||
|
this.searchDown = response.data.searchDown
|
||||||
|
this.loading=false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
resetQuery() {
|
||||||
|
this.resetForm("queryForm");
|
||||||
|
this.handleQuery();
|
||||||
|
},
|
||||||
|
toggleSideBar() {
|
||||||
|
this.showKeywords = !this.showKeywords
|
||||||
|
},
|
||||||
|
// 更多操作触发
|
||||||
|
handleCommand(command, row) {
|
||||||
|
switch (command) {
|
||||||
|
case 'handleTitle':
|
||||||
|
if(this.form.prodTitle){
|
||||||
|
this.form.prodTitle+=","+row.title
|
||||||
|
}else{
|
||||||
|
this.form.prodTitle=row.title
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'handleKeyword':
|
||||||
|
if(this.form.prodKeyword){
|
||||||
|
this.form.prodKeyword+=","+row.title
|
||||||
|
}else{
|
||||||
|
this.form.prodKeyword=row.title
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'handleDescription':
|
||||||
|
if(this.form.prodDescription){
|
||||||
|
this.form.prodDescription+=","+row.title
|
||||||
|
}else{
|
||||||
|
this.form.prodDescription=row.title
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
goCatgView() {
|
goCatgView() {
|
||||||
this.$router.push({ path: '/busi/category' })
|
this.$router.push({ path: '/busi/category' })
|
||||||
},
|
},
|
||||||
@ -220,6 +392,12 @@ export default {
|
|||||||
this.catgOptions = response.data
|
this.catgOptions = response.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
initSeo(){
|
||||||
|
listSeo({ }).then(response => {
|
||||||
|
this.languageArray = response.data.language
|
||||||
|
this.countryArray = response.data.country
|
||||||
|
})
|
||||||
|
},
|
||||||
/** 转换部门数据结构 */
|
/** 转换部门数据结构 */
|
||||||
normalizer(node) {
|
normalizer(node) {
|
||||||
if (node.children && !node.children.length) {
|
if (node.children && !node.children.length) {
|
||||||
@ -363,10 +541,24 @@ export default {
|
|||||||
width: 110px;
|
width: 110px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
|
line-height: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dl-tooltip-text {
|
.dl-tooltip-text {
|
||||||
color: #495060;
|
color: #495060;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hamburger-container {
|
||||||
|
line-height: 46px;
|
||||||
|
height: 100%;
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background .3s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-container:hover {
|
||||||
|
background: rgba(0, 0, 0, .025)
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user