diff --git a/server/MiAssessment/scripts/init_business_dict.sql b/server/MiAssessment/scripts/init_business_dict.sql index d1b593d..937c787 100644 --- a/server/MiAssessment/scripts/init_business_dict.sql +++ b/server/MiAssessment/scripts/init_business_dict.sql @@ -212,7 +212,7 @@ BEGIN INSERT INTO [dbo].[dict_items] ([type_id], [label], [value], [css_class], [status], [sort], [created_at]) VALUES (@promotion_position_id, N'首页底部', '1', 'primary', 1, 1, GETDATE()), - (@promotion_position_id, N'团队页面', '2', 'success', 1, 2, GETDATE()); + (@promotion_position_id, N'产品页', '2', 'success', 1, 2, GETDATE()); PRINT N' - promotion_position items inserted'; END diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Controllers/ContentController.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Controllers/ContentController.cs index 7aa5cce..f9d6abf 100644 --- a/server/MiAssessment/src/MiAssessment.Admin.Business/Controllers/ContentController.cs +++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Controllers/ContentController.cs @@ -276,7 +276,7 @@ public class ContentController : BusinessControllerBase // Position 验证 if (request.Position != 1 && request.Position != 2) { - return ValidationError("位置值无效,只能为1(首页底部)或2(团队页)"); + return ValidationError("位置值无效,只能为1(首页底部)或2(产品页)"); } try @@ -317,7 +317,7 @@ public class ContentController : BusinessControllerBase // Position 验证 if (request.Position != 1 && request.Position != 2) { - return ValidationError("位置值无效,只能为1(首页底部)或2(团队页)"); + return ValidationError("位置值无效,只能为1(首页底部)或2(产品页)"); } try diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/ContentService.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/ContentService.cs index 89c6004..3ff309d 100644 --- a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/ContentService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/ContentService.cs @@ -43,7 +43,7 @@ public class ContentService : IContentService private static readonly Dictionary PositionNames = new() { { 1, "首页底部" }, - { 2, "团队页" } + { 2, "产品页" } }; /// @@ -629,7 +629,7 @@ public class ContentService : IContentService { if (position != 1 && position != 2) { - throw new BusinessException(ErrorCodes.ParamError, "位置值必须为1(首页底部)或2(团队页)"); + throw new BusinessException(ErrorCodes.ParamError, "位置值必须为1(首页底部)或2(产品页)"); } } diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/content/banner/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/content/banner/index.vue index f7ec408..7cd724e 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/content/banner/index.vue +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/content/banner/index.vue @@ -218,7 +218,7 @@ + + + + + + + + + + + + { const linkType = Number(state.formData.linkType) switch (linkType) { case LINK_TYPE.INTERNAL: - return '请输入内部页面路径,如:/pages/detail/index' + return '请选择内部页面' case LINK_TYPE.EXTERNAL: return '请输入外部链接地址,如:https://example.com' case LINK_TYPE.MINIPROGRAM: @@ -390,14 +424,57 @@ const linkUrlPlaceholder = computed(() => { } }) +/** 是否为内部页面类型 */ +const isInternalPage = computed(() => { + return Number(state.formData.linkType) === LINK_TYPE.INTERNAL +}) + +/** 静态内部页面选项 */ +const staticPageOptions = [ + { label: '首页', value: '/pages/index/index' }, + { label: '产品页', value: '/pages/team/index' }, + { label: '我的', value: '/pages/mine/index' }, + { label: '往期测评', value: '/pages/assessment/history/index' }, + { label: '我的订单', value: '/pages/order/list/index' }, + { label: '学业规划', value: '/pages/planner/list/index' }, + { label: '邀请新用户', value: '/pages/invite/index' }, + { label: '关于', value: '/pages/about/index' } +] + +/** 产品页标签选项(从宣传图 position=2 动态加载) */ +const productTabOptions = ref<{ label: string; value: string }[]>([]) + +/** 加载产品页标签选项 */ +async function loadProductTabOptions() { + try { + const res = await getPromotionList({ page: 1, pageSize: 100, position: 2, status: 1 }) + if (res.code === 0 && res.data?.list) { + productTabOptions.value = res.data.list.map((item, index) => ({ + label: `产品页 - ${item.title || `标签${index + 1}`}`, + value: `/pages/team/index?tab=${index}` + })) + } + } catch (error) { + console.error('加载产品页标签失败:', error) + } +} + // ============ Form Rules ============ /** 链接地址验证器 */ const linkUrlValidator = (_rule: any, value: string, callback: (error?: Error) => void) => { const linkType = Number(state.formData.linkType) - // 内部页面或外部链接需要 linkUrl - if (linkType === LINK_TYPE.INTERNAL || linkType === LINK_TYPE.EXTERNAL) { + // 内部页面需要选择页面 + if (linkType === LINK_TYPE.INTERNAL) { + if (!value || !value.trim()) { + callback(new Error('请选择内部页面')) + return + } + } + + // 外部链接需要 linkUrl + if (linkType === LINK_TYPE.EXTERNAL) { if (!value || !value.trim()) { callback(new Error('请输入链接地址')) return @@ -438,7 +515,7 @@ const formRules = computed(() => ({ { required: true, message: '请选择跳转类型', trigger: 'change' } ], linkUrl: [ - { validator: linkUrlValidator, trigger: 'blur' } + { validator: linkUrlValidator, trigger: ['blur', 'change'] } ], appId: [ { validator: appIdValidator, trigger: 'blur' } @@ -618,9 +695,14 @@ function handleEdit(row: BannerItem) { function handleLinkTypeChange() { // 切换跳转类型时,清空相关字段并重新验证 + state.formData.linkUrl = '' if (Number(state.formData.linkType) !== LINK_TYPE.MINIPROGRAM) { state.formData.appId = '' } + // 内部页面时加载产品页标签选项 + if (Number(state.formData.linkType) === LINK_TYPE.INTERNAL && productTabOptions.value.length === 0) { + loadProductTabOptions() + } // 触发表单重新验证 nextTick(() => { formRef.value?.validateField(['linkUrl', 'appId']) @@ -718,6 +800,7 @@ function handleDialogClosed() { onMounted(() => { loadBannerList() + loadProductTabOptions() }) diff --git a/server/MiAssessment/src/MiAssessment.Api/wwwroot/css/pages/disclaimer.css b/server/MiAssessment/src/MiAssessment.Api/wwwroot/css/pages/disclaimer.css index d3218d7..af8c47b 100644 --- a/server/MiAssessment/src/MiAssessment.Api/wwwroot/css/pages/disclaimer.css +++ b/server/MiAssessment/src/MiAssessment.Api/wwwroot/css/pages/disclaimer.css @@ -67,3 +67,7 @@ .disc-footer-right { white-space: nowrap; } + +.disc-footer-right { + font-weight: 900; +} diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs index 995d822..5243b64 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/PdfGenerationService.cs @@ -76,6 +76,9 @@ public class PdfGenerationService : IPdfGenerationService throw new InvalidOperationException("ReportSettings:BaseUrl 未配置"); } + // 1.5 检查八大智能同分情况:>=6项同分时不生成PDF + await CheckIntelligenceTiedScoresAsync(recordId); + // 2. 查询启用的页面配置,按 SortOrder 升序 var pageConfigs = await _dbContext.ReportPageConfigs .Where(p => p.Status == 1) @@ -167,6 +170,43 @@ public class PdfGenerationService : IPdfGenerationService recordId, reportUrl); } + /// + /// 检查八大智能(CategoryType=1)是否存在>=6项同分的情况 + /// 如果存在,抛出异常阻止PDF生成 + /// + /// 测评记录ID + private async Task CheckIntelligenceTiedScoresAsync(long recordId) + { + // 查询八大智能(CategoryType=1)的测评结果 + var intelligenceScores = await _dbContext.AssessmentResults + .AsNoTracking() + .Join( + _dbContext.ReportCategories.AsNoTracking().Where(c => !c.IsDeleted && c.CategoryType == 1), + r => r.CategoryId, + c => c.Id, + (r, c) => new { r.RecordId, r.Percentage }) + .Where(x => x.RecordId == recordId) + .Select(x => x.Percentage) + .ToListAsync(); + + if (intelligenceScores.Count == 0) + { + return; + } + + // 按百分比分组,检查是否有任意一个分数出现>=6次 + var maxTiedCount = intelligenceScores + .GroupBy(p => p) + .Max(g => g.Count()); + + if (maxTiedCount >= 6) + { + _logger.LogWarning("八大智能存在 {TiedCount} 项同分,不生成PDF,RecordId: {RecordId}", + maxTiedCount, recordId); + throw new InvalidOperationException($"八大智能存在{maxTiedCount}项同分(>=6项),无法生成有效的测评报告"); + } + } + /// /// 处理单个页面配置,获取图片字节数组 /// diff --git a/uniapp/pages/assessment/info/index.vue b/uniapp/pages/assessment/info/index.vue index eeee8c6..66130a2 100644 --- a/uniapp/pages/assessment/info/index.vue +++ b/uniapp/pages/assessment/info/index.vue @@ -1,951 +1,1003 @@ + // ========== 邀请码输入 ========== + .invite-input { + width: 100%; + height: 88rpx; + background-color: $bg-gray; + border-radius: $border-radius-md; + padding: 0 $spacing-lg; + font-size: $font-size-lg; + color: $text-color; + text-align: center; + letter-spacing: 16rpx; + box-sizing: border-box; + } + \ No newline at end of file diff --git a/uniapp/pages/assessment/questions/index.vue b/uniapp/pages/assessment/questions/index.vue index 7d9437b..d662fe7 100644 --- a/uniapp/pages/assessment/questions/index.vue +++ b/uniapp/pages/assessment/questions/index.vue @@ -362,7 +362,6 @@ onLoad((options) => { height: 100vh; display: flex; flex-direction: column; - background-color: rgba(255, 234, 231, 1); overflow: hidden; } diff --git a/uniapp/pages/team/index.vue b/uniapp/pages/team/index.vue index 7f911aa..07b8d3e 100644 --- a/uniapp/pages/team/index.vue +++ b/uniapp/pages/team/index.vue @@ -7,7 +7,7 @@