From d113dcd9267b8f209353a54a5629095c9b22db0a Mon Sep 17 00:00:00 2001 From: Kerwin Date: Fri, 21 Nov 2025 09:30:33 +0800 Subject: [PATCH] update: add site_url config --- .gitignore | 9 ++++- db/converter/system_config_converter.go | 10 +++++ db/dto/system_config.go | 6 +++ db/entity/system_config_constants.go | 6 +++ db/repo/system_config_repository.go | 2 + web/pages/admin/resources.vue | 18 ++++----- web/pages/admin/seo.vue | 53 +++++++++++++++++++++++-- web/pages/admin/site-config.vue | 16 ++++++++ web/pages/index.vue | 31 +++++++++++---- web/pages/r/[key].vue | 3 -- 10 files changed, 130 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 2b394bd..8474de2 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,11 @@ dist/ .dockerignore # Air live reload -tmp/ \ No newline at end of file +tmp/ + +# +data/ +.claude/ +main +output.zip +CLAUDE.md \ No newline at end of file diff --git a/db/converter/system_config_converter.go b/db/converter/system_config_converter.go index 5a5debd..399600a 100644 --- a/db/converter/system_config_converter.go +++ b/db/converter/system_config_converter.go @@ -112,6 +112,8 @@ func SystemConfigToResponse(configs []entity.SystemConfig) *dto.SystemConfigResp response.TelegramQrImage = config.Value case entity.ConfigKeyQrCodeStyle: response.QrCodeStyle = config.Value + case entity.ConfigKeyWebsiteURL: + response.SiteURL = config.Value } } @@ -271,6 +273,10 @@ func RequestToSystemConfig(req *dto.SystemConfigRequest) []entity.SystemConfig { configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyQrCodeStyle, Value: *req.QrCodeStyle, Type: entity.ConfigTypeString}) updatedKeys = append(updatedKeys, entity.ConfigKeyQrCodeStyle) } + if req.SiteURL != nil { + configs = append(configs, entity.SystemConfig{Key: entity.ConfigKeyWebsiteURL, Value: *req.SiteURL, Type: entity.ConfigTypeString}) + updatedKeys = append(updatedKeys, entity.ConfigKeyWebsiteURL) + } // 记录更新的配置项 if len(updatedKeys) > 0 { @@ -298,6 +304,7 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte entity.ConfigResponseFieldMaintenanceMode: false, entity.ConfigResponseFieldEnableRegister: true, // 默认开启注册功能 entity.ConfigResponseFieldThirdPartyStatsCode: "", + entity.ConfigResponseFieldWebsiteURL: "", } // 将键值对转换为map,过滤掉敏感配置 @@ -353,6 +360,8 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte response["telegram_qr_image"] = config.Value case entity.ConfigKeyQrCodeStyle: response["qr_code_style"] = config.Value + case entity.ConfigKeyWebsiteURL: + response[entity.ConfigResponseFieldWebsiteURL] = config.Value case entity.ConfigKeyAutoProcessReadyResources: if val, err := strconv.ParseBool(config.Value); err == nil { response["auto_process_ready_resources"] = val @@ -420,5 +429,6 @@ func getDefaultConfigResponse() *dto.SystemConfigResponse { WechatSearchImage: entity.ConfigDefaultWechatSearchImage, TelegramQrImage: entity.ConfigDefaultTelegramQrImage, QrCodeStyle: entity.ConfigDefaultQrCodeStyle, + SiteURL: entity.ConfigDefaultWebsiteURL, } } diff --git a/db/dto/system_config.go b/db/dto/system_config.go index 8e9b411..d414a23 100644 --- a/db/dto/system_config.go +++ b/db/dto/system_config.go @@ -50,6 +50,9 @@ type SystemConfigRequest struct { WechatSearchImage *string `json:"wechat_search_image,omitempty"` TelegramQrImage *string `json:"telegram_qr_image,omitempty"` QrCodeStyle *string `json:"qr_code_style,omitempty"` + + // 网站URL配置 + SiteURL *string `json:"site_url,omitempty"` } // SystemConfigResponse 系统配置响应 @@ -106,6 +109,9 @@ type SystemConfigResponse struct { WechatSearchImage string `json:"wechat_search_image"` TelegramQrImage string `json:"telegram_qr_image"` QrCodeStyle string `json:"qr_code_style"` + + // 网站URL配置 + SiteURL string `json:"site_url"` } // SystemConfigItem 单个配置项 diff --git a/db/entity/system_config_constants.go b/db/entity/system_config_constants.go index 4a8566a..e1c6876 100644 --- a/db/entity/system_config_constants.go +++ b/db/entity/system_config_constants.go @@ -170,6 +170,9 @@ const ( ConfigResponseFieldWechatSearchImage = "wechat_search_image" ConfigResponseFieldTelegramQrImage = "telegram_qr_image" ConfigResponseFieldQrCodeStyle = "qr_code_style" + + // 网站URL配置字段 + ConfigResponseFieldWebsiteURL = "site_url" ) // ConfigDefaultValue 配置默认值常量 @@ -245,4 +248,7 @@ const ( ConfigDefaultWechatSearchImage = "" ConfigDefaultTelegramQrImage = "" ConfigDefaultQrCodeStyle = "Plain" + + // 网站URL配置默认值 + ConfigDefaultWebsiteURL = "" ) diff --git a/db/repo/system_config_repository.go b/db/repo/system_config_repository.go index 781e16b..b67d366 100644 --- a/db/repo/system_config_repository.go +++ b/db/repo/system_config_repository.go @@ -143,6 +143,7 @@ func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() ([]entity.SystemConfig {Key: entity.ConfigKeyWechatSearchImage, Value: entity.ConfigDefaultWechatSearchImage, Type: entity.ConfigTypeString}, {Key: entity.ConfigKeyTelegramQrImage, Value: entity.ConfigDefaultTelegramQrImage, Type: entity.ConfigTypeString}, {Key: entity.ConfigKeyQrCodeStyle, Value: entity.ConfigDefaultQrCodeStyle, Type: entity.ConfigTypeString}, + {Key: entity.ConfigKeyWebsiteURL, Value: entity.ConfigDefaultWebsiteURL, Type: entity.ConfigTypeString}, } createStart := utils.GetCurrentTime() @@ -189,6 +190,7 @@ func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() ([]entity.SystemConfig entity.ConfigKeyEnableFloatButtons: {Key: entity.ConfigKeyEnableFloatButtons, Value: entity.ConfigDefaultEnableFloatButtons, Type: entity.ConfigTypeBool}, entity.ConfigKeyWechatSearchImage: {Key: entity.ConfigKeyWechatSearchImage, Value: entity.ConfigDefaultWechatSearchImage, Type: entity.ConfigTypeString}, entity.ConfigKeyTelegramQrImage: {Key: entity.ConfigKeyTelegramQrImage, Value: entity.ConfigDefaultTelegramQrImage, Type: entity.ConfigTypeString}, + entity.ConfigKeyWebsiteURL: {Key: entity.ConfigKeyWebsiteURL, Value: entity.ConfigDefaultWebsiteURL, Type: entity.ConfigTypeString}, } // 检查现有配置中是否有缺失的配置项 diff --git a/web/pages/admin/resources.vue b/web/pages/admin/resources.vue index f85b2d8..8aff38b 100644 --- a/web/pages/admin/resources.vue +++ b/web/pages/admin/resources.vue @@ -431,7 +431,7 @@ const getCategoryName = (categoryId: number) => { // 获取平台名称 const getPlatformName = (platformId: number) => { - console.log('platformId', platformId, platformsData.value) + // console.log('platformId', platformId, platformsData.value) const platform = (platformsData.value as any)?.find((plat: any) => plat.id === platformId) return platform?.remark || platform?.name || '未知平台' } @@ -449,22 +449,22 @@ const fetchData = async () => { // 添加分类筛选 if (selectedCategory.value) { params.category_id = selectedCategory.value - console.log('添加分类筛选:', selectedCategory.value) + // console.log('添加分类筛选:', selectedCategory.value) } // 添加平台筛选 if (selectedPlatform.value) { params.pan_id = selectedPlatform.value - console.log('添加平台筛选:', selectedPlatform.value) + // console.log('添加平台筛选:', selectedPlatform.value) } - console.log('请求参数:', params) - console.log('pageSize:', pageSize.value) - console.log('selectedCategory:', selectedCategory.value) - console.log('selectedPlatform:', selectedPlatform.value) + // console.log('请求参数:', params) + // console.log('pageSize:', pageSize.value) + // console.log('selectedCategory:', selectedCategory.value) + // console.log('selectedPlatform:', selectedPlatform.value) const response = await resourceApi.getResources(params) as any - console.log('API响应:', response) - console.log('返回的资源数量:', response?.data?.length || 0) + // console.log('API响应:', response) + // console.log('返回的资源数量:', response?.data?.length || 0) if (response && response.data) { // 处理嵌套的data结构:{data: {data: [...], total: ...}} diff --git a/web/pages/admin/seo.vue b/web/pages/admin/seo.vue index 1a34af6..8a6bd61 100644 --- a/web/pages/admin/seo.vue +++ b/web/pages/admin/seo.vue @@ -285,7 +285,7 @@
-
+

自动生成功能

@@ -302,6 +302,29 @@

+ + +
+
+
+
当前站点URL
+

+ Sitemap生成时将使用此URL作为基础域名 +

+
+
+
+ {{ systemConfig.site_url }} +
+
+ 未配置 +
+ + 前往配置 + +
+
+
@@ -678,12 +701,20 @@ const generateSitemap = async () => { generateStatus.value = '正在启动生成任务...' try { + // 使用已经加载的系统配置 + const siteUrl = systemConfig.value?.site_url || '' + if (!siteUrl) { + message.warning('请先在站点配置中设置站点URL,然后再生成Sitemap') + isGenerating.value = false + return + } + const sitemapApi = useSitemapApi() - const response = await sitemapApi.generateSitemap() + const response = await sitemapApi.generateSitemap({ site_url: siteUrl }) if (response) { generateStatus.value = response.message || '生成任务已启动' - message.success('Sitemap生成任务已启动') + message.success(`Sitemap生成任务已启动,使用站点URL: ${siteUrl}`) // 更新统计信息 sitemapStats.value.total_resources = response.total_resources || 0 sitemapStats.value.total_pages = response.total_pages || 0 @@ -715,8 +746,24 @@ const viewSitemap = () => { window.open('/sitemap.xml', '_blank') } +// 获取系统配置 +const systemConfig = ref(null) + +// 加载系统配置 +const loadSystemConfig = async () => { + try { + const { useSystemConfigStore } = await import('~/stores/systemConfig') + const systemConfigStore = useSystemConfigStore() + await systemConfigStore.initConfig(true, true) + systemConfig.value = systemConfigStore.config + } catch (error) { + console.error('获取系统配置失败:', error) + } +} + // 初始化 onMounted(async () => { + await loadSystemConfig() loadLinkList() await loadSitemapConfig() await refreshSitemapStatus() diff --git a/web/pages/admin/site-config.vue b/web/pages/admin/site-config.vue index e3d24bc..2d63f1d 100644 --- a/web/pages/admin/site-config.vue +++ b/web/pages/admin/site-config.vue @@ -34,6 +34,18 @@ require-mark-placement="right-hanging" >
+ +
+
+ + 网站的基础URL,用于生成sitemap等,请包含协议名(如:https://example.com) +
+ +
+
@@ -298,6 +310,7 @@ interface Announcement { // 配置表单数据类型 interface SiteConfigForm { + site_url: string site_title: string site_description: string keywords: string @@ -362,6 +375,7 @@ const { }, // 字段映射:前端字段名 -> 后端字段名 fieldMapping: { + site_url: 'site_url', site_title: 'site_title', site_description: 'site_description', keywords: 'keywords', @@ -383,6 +397,7 @@ const { // 配置表单数据 const configForm = ref({ + site_url: '', site_title: '', site_description: '', keywords: '', @@ -424,6 +439,7 @@ const fetchConfig = async () => { if (response) { const configData = { + site_url: response.site_url || '', site_title: response.site_title || '', site_description: response.site_description || '', keywords: response.keywords || '', diff --git a/web/pages/index.vue b/web/pages/index.vue index b6c9407..f83acd1 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -608,24 +608,39 @@ watch(systemConfigError, (error) => { // 从 SSR 数据中获取值 const safeResources = computed(() => { const data = resourcesData.value as any - // console.log('原始API数据结构:', JSON.stringify(data, null, 2)) + let resources: any[] = [] // 处理嵌套的data结构:{data: {data: [...], total: ...}} if (data?.data?.data && Array.isArray(data.data.data)) { - const resources = data.data.data + resources = data.data.data console.log('第一层嵌套资源:', resources) - return resources } // 处理直接的data结构:{data: [...], total: ...} - if (data?.data && Array.isArray(data.data)) { - const resources = data.data + else if (data?.data && Array.isArray(data.data)) { + resources = data.data // console.log('第二层嵌套资源:', resources) - return resources } // 处理直接的数组结构 - if (Array.isArray(data)) { + else if (Array.isArray(data)) { + resources = data // console.log('直接数组结构:', data) - return data + } + + // 根据 key 字段去重 + if (resources.length > 0) { + const keyMap = new Map() + const deduplicatedResources: any[] = [] + + for (const resource of resources) { + const key = resource.key + if (!keyMap.has(key)) { + keyMap.set(key, true) + deduplicatedResources.push(resource) + } + } + + // console.log(`去重前: ${resources.length} 个资源, 去重后: ${deduplicatedResources.length} 个资源`) + return deduplicatedResources } // console.log('未匹配到任何数据结构') diff --git a/web/pages/r/[key].vue b/web/pages/r/[key].vue index 8842c1f..34abf39 100644 --- a/web/pages/r/[key].vue +++ b/web/pages/r/[key].vue @@ -203,9 +203,6 @@ ? (detectionMethods[resource.id] === 'unsupported' ? '不支持检测' : (detectionResults[resource.id] ? '有效' : '无效')) : '不支持检测' }} - - 已转存 -