This commit is contained in:
zpc 2026-02-20 19:12:08 +08:00
parent 07562a377b
commit de7eb8234c
14 changed files with 244 additions and 140 deletions

View File

@ -0,0 +1,12 @@
-- 为测评类型表添加详情横幅图字段
-- 用于小程序测评信息填写页顶部的横幅图展示
IF NOT EXISTS (
SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'assessment_types' AND COLUMN_NAME = 'DetailImageUrl'
)
BEGIN
ALTER TABLE assessment_types
ADD DetailImageUrl nvarchar(500) NULL;
END
GO

View File

@ -36,6 +36,12 @@ public class AssessmentType
[MaxLength(500)]
public string? ImageUrl { get; set; }
/// <summary>
/// 详情横幅图URL
/// </summary>
[MaxLength(500)]
public string? DetailImageUrl { get; set; }
/// <summary>
/// 介绍内容
/// </summary>

View File

@ -25,6 +25,11 @@ public class AssessmentTypeDto
/// </summary>
public string? ImageUrl { get; set; }
/// <summary>
/// 详情横幅图URL
/// </summary>
public string? DetailImageUrl { get; set; }
/// <summary>
/// 介绍内容
/// </summary>

View File

@ -27,6 +27,12 @@ public class CreateAssessmentTypeRequest
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
public string? ImageUrl { get; set; }
/// <summary>
/// 详情横幅图URL
/// </summary>
[MaxLength(500, ErrorMessage = "详情横幅图URL不能超过500个字符")]
public string? DetailImageUrl { get; set; }
/// <summary>
/// 介绍内容
/// </summary>

View File

@ -33,6 +33,12 @@ public class UpdateAssessmentTypeRequest
[MaxLength(500, ErrorMessage = "图片URL不能超过500个字符")]
public string? ImageUrl { get; set; }
/// <summary>
/// 详情横幅图URL
/// </summary>
[MaxLength(500, ErrorMessage = "详情横幅图URL不能超过500个字符")]
public string? DetailImageUrl { get; set; }
/// <summary>
/// 介绍内容
/// </summary>

View File

@ -83,6 +83,7 @@ public class AssessmentService : IAssessmentService
Name = t.Name,
Code = t.Code,
ImageUrl = t.ImageUrl,
DetailImageUrl = t.DetailImageUrl,
IntroContent = t.IntroContent,
Price = t.Price,
QuestionCount = t.QuestionCount,
@ -108,6 +109,7 @@ public class AssessmentService : IAssessmentService
Name = t.Name,
Code = t.Code,
ImageUrl = t.ImageUrl,
DetailImageUrl = t.DetailImageUrl,
IntroContent = t.IntroContent,
Price = t.Price,
QuestionCount = t.QuestionCount,
@ -159,6 +161,7 @@ public class AssessmentService : IAssessmentService
Name = request.Name,
Code = request.Code,
ImageUrl = request.ImageUrl,
DetailImageUrl = request.DetailImageUrl,
IntroContent = request.IntroContent,
Price = request.Price,
QuestionCount = request.QuestionCount,
@ -219,6 +222,7 @@ public class AssessmentService : IAssessmentService
assessmentType.Name = request.Name;
assessmentType.Code = request.Code;
assessmentType.ImageUrl = request.ImageUrl;
assessmentType.DetailImageUrl = request.DetailImageUrl;
assessmentType.IntroContent = request.IntroContent;
assessmentType.Price = request.Price;
assessmentType.QuestionCount = request.QuestionCount;

View File

@ -21,6 +21,8 @@ export interface AssessmentTypeItem {
code: string
/** 图片URL */
imageUrl: string
/** 详情横幅图URL */
detailImageUrl: string
/** 介绍内容(富文本) */
introContent: string
/** 价格 */
@ -59,6 +61,8 @@ export interface CreateAssessmentTypeRequest {
code: string
/** 图片URL */
imageUrl?: string
/** 详情横幅图URL */
detailImageUrl?: string
/** 介绍内容(富文本) */
introContent?: string
/** 价格 */

View File

@ -212,6 +212,15 @@
/>
</el-form-item>
<el-form-item label="详情横幅图" prop="detailImageUrl">
<ImageUpload
v-model="state.formData.detailImageUrl"
placeholder="点击上传详情页横幅图"
tip="建议尺寸750x400支持 jpg、png、gif 格式"
:max-size="10"
/>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input-number
v-model="state.formData.price"
@ -302,6 +311,7 @@ interface AssessmentTypeFormData {
name: string
code: string
imageUrl: string
detailImageUrl: string
introContent: string
price: number
sort: number
@ -384,6 +394,7 @@ function getDefaultFormData(): AssessmentTypeFormData {
name: '',
code: '',
imageUrl: '',
detailImageUrl: '',
introContent: '',
price: 0,
sort: 0,
@ -497,6 +508,7 @@ function handleEdit(row: AssessmentTypeItem) {
name: row.name,
code: row.code,
imageUrl: row.imageUrl || '',
detailImageUrl: row.detailImageUrl || '',
introContent: row.introContent || '',
price: row.price,
sort: row.sort,
@ -570,6 +582,7 @@ async function handleSubmit() {
name: formData.name,
code: formData.code,
imageUrl: formData.imageUrl || undefined,
detailImageUrl: formData.detailImageUrl || undefined,
introContent: formData.introContent || undefined,
price: formData.price,
sort: formData.sort,

View File

@ -48,6 +48,7 @@ public class AssessmentService : IAssessmentService
.Where(a => a.Id == typeId && !a.IsDeleted)
.Select(a => new AssessmentIntroDto
{
ImageUrl = a.DetailImageUrl,
Content = a.IntroContent ?? string.Empty,
ContentType = "html",
Price = a.Price

View File

@ -1124,6 +1124,9 @@ public partial class MiAssessmentDbContext : DbContext
entity.Property(e => e.ImageUrl)
.HasMaxLength(500)
.HasComment("入口图片URL");
entity.Property(e => e.DetailImageUrl)
.HasMaxLength(500)
.HasComment("详情横幅图URL");
entity.Property(e => e.IntroContent)
.HasComment("介绍内容");
entity.Property(e => e.Price)

View File

@ -36,6 +36,12 @@ public class AssessmentType
[MaxLength(500)]
public string? ImageUrl { get; set; }
/// <summary>
/// 详情横幅图URL
/// </summary>
[MaxLength(500)]
public string? DetailImageUrl { get; set; }
/// <summary>
/// 介绍内容
/// </summary>

View File

@ -5,6 +5,11 @@ namespace MiAssessment.Model.Models.Assessment;
/// </summary>
public class AssessmentIntroDto
{
/// <summary>
/// 详情横幅图URL
/// </summary>
public string? ImageUrl { get; set; }
/// <summary>
/// 介绍内容
/// </summary>

View File

@ -316,7 +316,7 @@ onLoad((options) => {
<template>
<view class="assessment-info-page">
<!-- 导航栏 -->
<Navbar title="测评信息" :showBack="true" />
<Navbar :title="introData.title || '多元智能测评'" :showBack="true" />
<!-- 页面内容 -->
<view class="page-content">
@ -336,19 +336,26 @@ onLoad((options) => {
<!-- 表单区域 -->
<view class="form-section">
<!-- 表单提示 -->
<view class="form-header">
<text class="form-header-text">正式测评前请先填写您的基本信息</text>
</view>
<!-- 姓名 -->
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
<text>姓名</text>
</view>
<input
class="form-input"
type="text"
placeholder="请输入姓名"
v-model="formData.name"
maxlength="20"
/>
<view class="form-input-wrap">
<input
class="form-input"
type="text"
placeholder="请输入"
v-model="formData.name"
maxlength="20"
/>
</view>
</view>
<!-- 手机号 -->
@ -357,13 +364,15 @@ onLoad((options) => {
<text class="required">*</text>
<text>手机号</text>
</view>
<input
class="form-input"
type="number"
placeholder="请输入手机号"
v-model="formData.phone"
maxlength="11"
/>
<view class="form-input-wrap">
<input
class="form-input"
type="number"
placeholder="请输入"
v-model="formData.phone"
maxlength="11"
/>
</view>
</view>
<!-- 性别 -->
@ -379,7 +388,7 @@ onLoad((options) => {
>
<view class="form-picker">
<text :class="{ 'placeholder': !formData.gender }">
{{ formData.gender || '请选择性别' }}
{{ formData.gender || '请选择' }}
</text>
<view class="picker-arrow"></view>
</view>
@ -399,7 +408,7 @@ onLoad((options) => {
>
<view class="form-picker">
<text :class="{ 'placeholder': !formData.age }">
{{ formData.age || '请选择年龄' }}
{{ formData.age || '请选择' }}
</text>
<view class="picker-arrow"></view>
</view>
@ -419,15 +428,15 @@ onLoad((options) => {
>
<view class="form-picker">
<text :class="{ 'placeholder': !formData.educationStage }">
{{ formData.educationStage || '请选择学业阶段' }}
{{ formData.educationStage || '请选择' }}
</text>
<view class="picker-arrow"></view>
</view>
</picker>
</view>
<!-- 省市区 -->
<view class="form-item">
<!-- 所在城市 -->
<view class="form-item form-item-last">
<view class="form-label">
<text class="required">*</text>
<text>所在城市</text>
@ -436,11 +445,25 @@ onLoad((options) => {
mode="region"
@change="onRegionChange"
>
<view class="form-picker">
<text :class="{ 'placeholder': !formData.province }">
{{ formData.province ? `${formData.province} ${formData.city} ${formData.district}` : '请选择省市区' }}
</text>
<view class="picker-arrow"></view>
<view class="form-region-picker">
<view class="region-item">
<text :class="{ 'placeholder': !formData.province }">
{{ formData.province || '省' }}
</text>
<view class="picker-arrow"></view>
</view>
<view class="region-item">
<text :class="{ 'placeholder': !formData.city }">
{{ formData.city || '市' }}
</text>
<view class="picker-arrow"></view>
</view>
<view class="region-item">
<text :class="{ 'placeholder': !formData.district }">
{{ formData.district || '区/县' }}
</text>
<view class="picker-arrow"></view>
</view>
</view>
</picker>
</view>
@ -448,22 +471,24 @@ onLoad((options) => {
<!-- 底部按钮区域 -->
<view class="bottom-section">
<!-- 支付测评按钮 -->
<view
class="btn-pay"
:class="{ 'btn-disabled': !isFormComplete }"
@click="handlePayAssessment"
>
<text>支付{{ introData.price || 0 }} 开始测评</text>
</view>
<!-- 邀请码测评按钮 -->
<view
class="btn-invite"
:class="{ 'btn-disabled': !isFormComplete }"
@click="openInvitePopup"
>
<text>邀请码免费测评</text>
<view class="bottom-btn-group">
<!-- 邀请码测评按钮 -->
<view
class="btn-invite"
:class="{ 'btn-disabled': !isFormComplete }"
@click="openInvitePopup"
>
<text>邀请码免费测评</text>
</view>
<!-- 支付测评按钮 -->
<view
class="btn-pay"
:class="{ 'btn-disabled': !isFormComplete }"
@click="handlePayAssessment"
>
<text>支付{{ introData.price || 0 }}元开始测评</text>
</view>
</view>
</view>
</view>
@ -513,14 +538,11 @@ onLoad((options) => {
}
.page-content {
padding: $spacing-lg;
padding-bottom: 200rpx;
}
//
.intro-section {
background-color: $bg-white;
border-radius: $border-radius-lg;
overflow: hidden;
margin-bottom: $spacing-lg;
@ -531,6 +553,7 @@ onLoad((options) => {
.intro-text {
padding: $spacing-lg;
background-color: $bg-white;
.intro-title {
font-size: $font-size-xl;
@ -551,25 +574,35 @@ onLoad((options) => {
.form-section {
background-color: $bg-white;
border-radius: $border-radius-lg;
margin: 0 $spacing-lg;
padding: 0 $spacing-lg;
}
//
.form-header {
padding: $spacing-xl 0 $spacing-md;
text-align: center;
.form-header-text {
font-size: $font-size-sm;
color: $text-secondary;
}
}
.form-item {
display: flex;
align-items: center;
padding: $spacing-lg 0;
border-bottom: 1rpx solid $border-light;
&:last-child {
&.form-item-last {
border-bottom: none;
}
}
.form-label {
width: 160rpx;
flex-shrink: 0;
font-size: $font-size-md;
color: $text-color;
font-weight: $font-weight-medium;
margin-bottom: $spacing-sm;
.required {
color: $error-color;
@ -577,9 +610,12 @@ onLoad((options) => {
}
}
.form-input-wrap {
margin-top: $spacing-xs;
}
.form-input {
flex: 1;
height: 48rpx;
height: 56rpx;
font-size: $font-size-md;
color: $text-color;
@ -589,11 +625,11 @@ onLoad((options) => {
}
.form-picker {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
height: 48rpx;
height: 56rpx;
margin-top: $spacing-xs;
font-size: $font-size-md;
color: $text-color;
@ -611,6 +647,39 @@ onLoad((options) => {
}
}
//
.form-region-picker {
display: flex;
align-items: center;
gap: $spacing-md;
margin-top: $spacing-xs;
.region-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 64rpx;
background-color: $bg-gray;
border-radius: $border-radius-sm;
font-size: $font-size-md;
color: $text-color;
.placeholder {
color: $text-placeholder;
}
.picker-arrow {
width: 12rpx;
height: 12rpx;
border-right: 3rpx solid $text-placeholder;
border-bottom: 3rpx solid $text-placeholder;
transform: rotate(45deg);
margin-left: $spacing-xs;
}
}
}
//
.bottom-section {
position: fixed;
@ -623,19 +692,25 @@ onLoad((options) => {
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
}
.btn-pay {
.bottom-btn-group {
display: flex;
gap: $spacing-md;
}
.btn-invite {
flex: 1;
height: 88rpx;
background: linear-gradient(135deg, #FF6B6B 0%, #FF5252 100%);
background-color: $bg-white;
border: 2rpx solid #FF5252;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: $spacing-md;
text {
font-size: $font-size-lg;
font-size: $font-size-md;
font-weight: $font-weight-medium;
color: $text-white;
color: #FF5252;
}
&:active {
@ -643,19 +718,19 @@ onLoad((options) => {
}
}
.btn-invite {
.btn-pay {
flex: 1;
height: 88rpx;
background-color: $bg-white;
border: 2rpx solid $primary-color;
background: linear-gradient(135deg, #FF6B6B 0%, #FF5252 100%);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: $font-size-lg;
font-size: $font-size-md;
font-weight: $font-weight-medium;
color: $primary-color;
color: $text-white;
}
&:active {

View File

@ -73,36 +73,24 @@ function handleBack() {
}
/**
* 处理点击参与
* 处理点击按钮
*/
function handleParticipate() {
function handleButtonClick() {
if (!businessDetail.value) return
const detail = businessDetail.value
//
if (detail.linkType === 'assessment') {
//
if (detail.buttonLink) {
//
uni.navigateTo({
url: `/pages/assessment/info/index?typeId=${detail.linkId || ''}&typeName=${encodeURIComponent(detail.name || '')}`
})
} else if (detail.linkType === 'planner') {
//
uni.navigateTo({
url: '/pages/planner/list/index'
})
} else if (detail.linkUrl) {
//
uni.navigateTo({
url: detail.linkUrl,
url: detail.buttonLink,
fail: () => {
uni.switchTab({
url: detail.linkUrl
url: detail.buttonLink
})
}
})
} else {
//
uni.showToast({
title: '功能开发中',
icon: 'none'
@ -130,7 +118,7 @@ onLoad((options) => {
</view>
<!-- 标题 -->
<view class="navbar-title-wrap">
<text class="navbar-title">{{ businessDetail?.name || '业务详情' }}</text>
<text class="navbar-title">{{ businessDetail?.title || '业务详情' }}</text>
</view>
<!-- 右侧占位 -->
<view class="navbar-right"></view>
@ -158,17 +146,12 @@ onLoad((options) => {
class="detail-image"
@error="handleImageError"
/>
<!-- 多张图片支持 -->
<template v-if="businessDetail.images && businessDetail.images.length > 0">
<image
v-for="(img, index) in businessDetail.images"
:key="index"
:src="img.imageUrl || img"
mode="widthFix"
class="detail-image"
@error="handleImageError"
/>
</template>
<!-- 底部按钮悬浮在图片底部 -->
<view v-if="businessDetail.showButton" class="image-bottom-action">
<view class="action-btn" @click="handleButtonClick">
<text class="btn-text">{{ businessDetail.buttonText || '点击参与' }}</text>
</view>
</view>
</view>
</template>
@ -183,14 +166,8 @@ onLoad((options) => {
</view>
</view>
<!-- 底部按钮区域 -->
<view v-if="businessDetail && !pageLoading" class="bottom-action">
<view class="action-btn" @click="handleParticipate">
<text class="btn-text">点击参与</text>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
</view>
</template>
@ -200,7 +177,6 @@ onLoad((options) => {
.business-detail-page {
min-height: 100vh;
background-color: $bg-white;
padding-bottom: 160rpx; //
}
//
@ -277,6 +253,7 @@ onLoad((options) => {
//
.detail-image-wrap {
width: 100%;
position: relative;
.detail-image {
width: 100%;
@ -284,59 +261,40 @@ onLoad((options) => {
}
}
//
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: $spacing-lg;
}
.empty-text {
font-size: $font-size-md;
color: $text-placeholder;
}
}
//
.bottom-action {
position: fixed;
//
.image-bottom-action {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background-color: $bg-white;
padding: $spacing-md $spacing-lg;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
z-index: 100;
bottom: 80rpx;
display: flex;
justify-content: center;
.action-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
border-radius: 48rpx;
min-width: 320rpx;
height: 88rpx;
background-color: $bg-white;
border-radius: $border-radius-round;
display: flex;
align-items: center;
justify-content: center;
padding: 0 60rpx;
box-shadow: $shadow-md;
&:active {
opacity: 0.9;
}
.btn-text {
font-size: 34rpx;
font-size: 32rpx;
font-weight: $font-weight-medium;
color: $text-white;
color: $text-color;
}
}
.safe-area-bottom {
height: env(safe-area-inset-bottom);
}
}
//
.safe-area-bottom {
height: env(safe-area-inset-bottom);
}
</style>