feat(content): 首页更多区域模块化配置
All checks were successful
continuous-integration/drone/push Build is passing

- home_navigations 表新增 Position 和 ActionType 字段
- 小程序 API 支持按 position 筛选导航列表
- 首页拆分专业测评和更多区域,动态渲染+QR弹窗
- 后台管理支持 Position/ActionType 配置和筛选
- ActionType=1 时 LinkUrl 必填验证
- 状态简化为 0=禁用/1=启用
This commit is contained in:
zpc 2026-03-25 11:26:43 +08:00
parent 0ee0870198
commit 9d4f9a0722
17 changed files with 376 additions and 120 deletions

View File

@ -0,0 +1,22 @@
-- ============================================================
-- 迁移脚本home_navigations 表新增 Position 和 ActionType 列
-- 功能:首页「更多」区域模块化配置
-- ============================================================
-- 1. 新增列
ALTER TABLE home_navigations ADD Position INT NOT NULL DEFAULT 1;
ALTER TABLE home_navigations ADD ActionType INT NOT NULL DEFAULT 1;
-- 2. 现有记录设置为专业测评区域 + 跳转页面
UPDATE home_navigations SET Position = 1, ActionType = 1 WHERE IsDeleted = 0;
-- 3. 将原 Status=0 的"即将上线"记录改为 ActionType=3, Status=1
UPDATE home_navigations SET ActionType = 3, Status = 1 WHERE Status = 0 AND IsDeleted = 0;
-- 注意Status=2 的记录也改为 ActionType=3
UPDATE home_navigations SET ActionType = 3, Status = 1 WHERE Status = 2 AND IsDeleted = 0;
-- 4. 插入「更多」区域初始数据
INSERT INTO home_navigations (Name, ImageUrl, LinkUrl, Sort, Status, Position, ActionType, CreateTime, UpdateTime, IsDeleted)
VALUES
(N'学习方案', NULL, NULL, 2, 1, 2, 3, GETDATE(), GETDATE(), 0),
(N'详细咨询', NULL, NULL, 1, 1, 2, 2, GETDATE(), GETDATE(), 0);

View File

@ -440,6 +440,12 @@ public class ContentController : BusinessControllerBase
return ValidationError("导航名称不能为空");
}
// ActionType=1跳转页面时 LinkUrl 必填
if (request.ActionType == 1 && string.IsNullOrWhiteSpace(request.LinkUrl))
{
return Error(ErrorCodes.ParamError, "跳转链接不能为空");
}
try
{
var id = await _contentService.CreateNavigationAsync(request);
@ -474,6 +480,12 @@ public class ContentController : BusinessControllerBase
return ValidationError("导航名称不能为空");
}
// ActionType=1跳转页面时 LinkUrl 必填
if (request.ActionType == 1 && string.IsNullOrWhiteSpace(request.LinkUrl))
{
return Error(ErrorCodes.ParamError, "跳转链接不能为空");
}
try
{
var result = await _contentService.UpdateNavigationAsync(request);
@ -532,9 +544,9 @@ public class ContentController : BusinessControllerBase
return ValidationError("导航ID无效");
}
if (request.Status < 0 || request.Status > 2)
if (request.Status != 0 && request.Status != 1)
{
return ValidationError("状态值无效只能为0下线、1上线或2即将上线");
return Error(ErrorCodes.ParamError, "状态值无效");
}
try

View File

@ -41,7 +41,17 @@ public class HomeNavigation
public int Sort { get; set; }
/// <summary>
/// 状态0即将上线 1已上线
/// 区域标识1=专业测评区域2=更多区域
/// </summary>
public int Position { get; set; } = 1;
/// <summary>
/// 动作类型1=跳转页面2=弹客服二维码3=即将上线
/// </summary>
public int ActionType { get; set; } = 1;
/// <summary>
/// 状态0=禁用1=启用
/// </summary>
public int Status { get; set; } = 1;

View File

@ -32,7 +32,17 @@ public class CreateHomeNavigationRequest
public int Sort { get; set; }
/// <summary>
/// 状态0即将上线 1已上线
/// 区域标识1=专业测评区域2=更多区域默认1
/// </summary>
public int Position { get; set; } = 1;
/// <summary>
/// 动作类型1=跳转页面2=弹客服二维码3=即将上线默认1
/// </summary>
public int ActionType { get; set; } = 1;
/// <summary>
/// 状态0=禁用1=启用
/// </summary>
public int Status { get; set; } = 1;
}

View File

@ -31,7 +31,27 @@ public class HomeNavigationDto
public int Sort { get; set; }
/// <summary>
/// 状态0即将上线 1已上线
/// 区域标识
/// </summary>
public int Position { get; set; }
/// <summary>
/// 区域名称(专业测评区域/更多区域)
/// </summary>
public string PositionName { get; set; } = null!;
/// <summary>
/// 动作类型
/// </summary>
public int ActionType { get; set; }
/// <summary>
/// 动作类型名称(跳转页面/弹客服二维码/即将上线)
/// </summary>
public string ActionTypeName { get; set; } = null!;
/// <summary>
/// 状态0=禁用1=启用
/// </summary>
public int Status { get; set; }

View File

@ -16,4 +16,9 @@ public class HomeNavigationQueryRequest : PagedRequest
/// 状态筛选
/// </summary>
public int? Status { get; set; }
/// <summary>
/// 区域标识筛选
/// </summary>
public int? Position { get; set; }
}

View File

@ -38,7 +38,17 @@ public class UpdateHomeNavigationRequest
public int Sort { get; set; }
/// <summary>
/// 状态0即将上线 1已上线
/// 区域标识1=专业测评区域2=更多区域
/// </summary>
public int Position { get; set; }
/// <summary>
/// 动作类型1=跳转页面2=弹客服二维码3=即将上线
/// </summary>
public int ActionType { get; set; }
/// <summary>
/// 状态0=禁用1=启用
/// </summary>
public int Status { get; set; }
}

View File

@ -564,13 +564,31 @@ public class ContentService : IContentService
}
/// <summary>
/// 导航状态名称映射
/// 导航状态名称映射0=禁用1=启用
/// </summary>
private static readonly Dictionary<int, string> NavigationStatusNames = new()
{
{ 0, "下线" },
{ 1, "上线" },
{ 2, "即将上线" }
{ 0, "禁用" },
{ 1, "启用" }
};
/// <summary>
/// 导航区域标识名称映射
/// </summary>
private static readonly Dictionary<int, string> NavigationPositionNames = new()
{
{ 1, "专业测评区域" },
{ 2, "更多区域" }
};
/// <summary>
/// 导航动作类型名称映射
/// </summary>
private static readonly Dictionary<int, string> ActionTypeNames = new()
{
{ 1, "跳转页面" },
{ 2, "弹客服二维码" },
{ 3, "即将上线" }
};
/// <summary>
@ -583,6 +601,26 @@ public class ContentService : IContentService
return NavigationStatusNames.TryGetValue(status, out var name) ? name : "未知";
}
/// <summary>
/// 获取导航区域名称
/// </summary>
/// <param name="position">区域标识</param>
/// <returns>区域名称</returns>
private static string GetNavigationPositionName(int position)
{
return NavigationPositionNames.TryGetValue(position, out var name) ? name : "未知";
}
/// <summary>
/// 获取导航动作类型名称
/// </summary>
/// <param name="actionType">动作类型</param>
/// <returns>动作类型名称</returns>
private static string GetActionTypeName(int actionType)
{
return ActionTypeNames.TryGetValue(actionType, out var name) ? name : "未知";
}
/// <summary>
/// 验证 Position 值
/// </summary>
@ -616,6 +654,11 @@ public class ContentService : IContentService
query = query.Where(n => n.Status == request.Status.Value);
}
if (request.Position.HasValue)
{
query = query.Where(n => n.Position == request.Position.Value);
}
var total = await query.CountAsync();
var items = await query
@ -630,15 +673,19 @@ public class ContentService : IContentService
ImageUrl = n.ImageUrl,
LinkUrl = n.LinkUrl,
Sort = n.Sort,
Position = n.Position,
ActionType = n.ActionType,
Status = n.Status,
CreateTime = n.CreateTime
})
.ToListAsync();
// 在内存中映射状态名称(避免 EF Core LINQ 翻译问题)
// 在内存中映射名称(避免 EF Core LINQ 翻译问题)
foreach (var item in items)
{
item.StatusName = GetNavigationStatusName(item.Status);
item.PositionName = GetNavigationPositionName(item.Position);
item.ActionTypeName = GetActionTypeName(item.ActionType);
}
return PagedResult<HomeNavigationDto>.Create(items, total, request.Page, request.PageSize);
@ -657,6 +704,8 @@ public class ContentService : IContentService
ImageUrl = n.ImageUrl,
LinkUrl = n.LinkUrl,
Sort = n.Sort,
Position = n.Position,
ActionType = n.ActionType,
Status = n.Status,
CreateTime = n.CreateTime
})
@ -667,8 +716,10 @@ public class ContentService : IContentService
throw new BusinessException(ErrorCodes.NavigationNotFound, "首页导航不存在");
}
// 在内存中映射状态名称
// 在内存中映射名称
nav.StatusName = GetNavigationStatusName(nav.Status);
nav.PositionName = GetNavigationPositionName(nav.Position);
nav.ActionTypeName = GetActionTypeName(nav.ActionType);
return nav;
}
@ -681,12 +732,20 @@ public class ContentService : IContentService
throw new BusinessException(ErrorCodes.ParamError, "导航名称不能为空");
}
// ActionType=1跳转页面时 LinkUrl 必填
if (request.ActionType == 1 && string.IsNullOrWhiteSpace(request.LinkUrl))
{
throw new BusinessException(ErrorCodes.ParamError, "跳转链接不能为空");
}
var entity = new HomeNavigation
{
Name = request.Name,
ImageUrl = request.ImageUrl,
LinkUrl = request.LinkUrl,
Sort = request.Sort,
Position = request.Position,
ActionType = request.ActionType,
Status = request.Status,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
@ -717,10 +776,18 @@ public class ContentService : IContentService
throw new BusinessException(ErrorCodes.ParamError, "导航名称不能为空");
}
// ActionType=1跳转页面时 LinkUrl 必填
if (request.ActionType == 1 && string.IsNullOrWhiteSpace(request.LinkUrl))
{
throw new BusinessException(ErrorCodes.ParamError, "跳转链接不能为空");
}
entity.Name = request.Name;
entity.ImageUrl = request.ImageUrl;
entity.LinkUrl = request.LinkUrl;
entity.Sort = request.Sort;
entity.Position = request.Position;
entity.ActionType = request.ActionType;
entity.Status = request.Status;
entity.UpdateTime = DateTime.Now;

View File

@ -308,10 +308,18 @@ export interface NavigationItem {
linkUrl: string
/** 排序值 */
sort: number
/** 状态 (0: 即将上线, 1: 已上线) */
/** 状态 (0: 禁用, 1: 启用) */
status: number
/** 状态名称 */
statusName: string
/** 区域标识 (1: 专业测评区域, 2: 更多区域) */
position: number
/** 区域名称 */
positionName: string
/** 动作类型 (1: 跳转页面, 2: 弹客服二维码, 3: 即将上线) */
actionType: number
/** 动作类型名称 */
actionTypeName: string
/** 创建时间 */
createTime: string
}
@ -324,6 +332,8 @@ export interface NavigationQuery extends PagedRequest {
name?: string
/** 状态筛选 */
status?: number
/** 区域标识筛选 */
position?: number
}
/**
@ -340,6 +350,10 @@ export interface CreateNavigationRequest {
sort: number
/** 状态 */
status: number
/** 区域标识 (1: 专业测评区域, 2: 更多区域) */
position: number
/** 动作类型 (1: 跳转页面, 2: 弹客服二维码, 3: 即将上线) */
actionType: number
}
/**

View File

@ -29,9 +29,14 @@
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="上线" :value="1" />
<el-option label="下线" :value="0" />
<el-option label="即将上线" :value="2" />
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="区域">
<el-select v-model="queryParams.position" placeholder="请选择区域" clearable>
<el-option label="专业测评区域" :value="1" />
<el-option label="更多区域" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
@ -74,6 +79,12 @@
<!-- 名称 -->
<el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip />
<!-- 区域 -->
<el-table-column prop="positionName" label="区域" width="120" align="center" />
<!-- 动作类型 -->
<el-table-column prop="actionTypeName" label="动作类型" width="120" align="center" />
<!-- 跳转链接 -->
<el-table-column prop="linkUrl" label="跳转链接" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
@ -95,9 +106,8 @@
</el-tag>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="1" :disabled="row.status === 1">上线</el-dropdown-item>
<el-dropdown-item :command="0" :disabled="row.status === 0">下线</el-dropdown-item>
<el-dropdown-item :command="2" :disabled="row.status === 2">即将上线</el-dropdown-item>
<el-dropdown-item :command="1" :disabled="row.status === 1">启用</el-dropdown-item>
<el-dropdown-item :command="0" :disabled="row.status === 0">禁用</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -181,7 +191,22 @@
/>
</el-form-item>
<el-form-item label="跳转链接" prop="linkUrl">
<el-form-item label="区域" prop="position" required>
<el-select v-model="state.formData.position" placeholder="请选择区域">
<el-option label="专业测评区域" :value="1" />
<el-option label="更多区域" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="动作类型" prop="actionType" required>
<el-select v-model="state.formData.actionType" placeholder="请选择动作类型">
<el-option label="跳转页面" :value="1" />
<el-option label="弹客服二维码" :value="2" />
<el-option label="即将上线" :value="3" />
</el-select>
</el-form-item>
<el-form-item v-if="state.formData.actionType === 1" label="跳转链接" prop="linkUrl">
<el-input
v-model="state.formData.linkUrl"
placeholder="请输入跳转链接,如:/pages/assessment/info/index"
@ -200,9 +225,8 @@
<el-form-item label="状态" prop="status" required>
<el-select v-model="state.formData.status" placeholder="请选择状态">
<el-option label="上线" :value="1" />
<el-option label="下线" :value="0" />
<el-option label="即将上线" :value="2" />
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-form>
@ -246,6 +270,8 @@ interface NavigationFormData {
imageUrl: string
linkUrl: string
sort: number
position: number
actionType: number
status: number
}
@ -290,6 +316,15 @@ const formRules: FormRules = {
name: [
{ required: true, message: '请输入导航名称', trigger: 'blur' }
],
position: [
{ required: true, message: '请选择区域', trigger: 'change' }
],
actionType: [
{ required: true, message: '请选择动作类型', trigger: 'change' }
],
linkUrl: [
{ required: true, message: '请输入跳转链接', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' }
]
@ -303,19 +338,21 @@ function getDefaultFormData(): NavigationFormData {
imageUrl: '',
linkUrl: '',
sort: 0,
position: 1,
actionType: 1,
status: 1
}
}
/** 状态文本映射 */
function statusLabel(status: number): string {
const map: Record<number, string> = { 0: '下线', 1: '上线', 2: '即将上线' }
const map: Record<number, string> = { 0: '禁用', 1: '启用' }
return map[status] ?? '未知'
}
/** 状态标签颜色映射 */
function statusTagType(status: number): '' | 'success' | 'info' | 'warning' | 'danger' {
const map: Record<number, '' | 'success' | 'info' | 'warning' | 'danger'> = { 0: 'info', 1: 'success', 2: 'warning' }
const map: Record<number, '' | 'success' | 'info' | 'warning' | 'danger'> = { 0: 'danger', 1: 'success' }
return map[status] ?? 'info'
}
@ -332,6 +369,9 @@ async function loadList() {
if (queryParams.status !== undefined && queryParams.status !== '') {
params.status = Number(queryParams.status)
}
if (queryParams.position !== undefined && queryParams.position !== '') {
params.position = Number(queryParams.position)
}
const res = await getNavigationList(params)
if (res.code === 0) {
@ -358,6 +398,7 @@ function handleSearch() {
function handleReset() {
queryParams.name = ''
queryParams.status = undefined
queryParams.position = undefined
queryParams.page = 1
loadList()
}
@ -389,6 +430,8 @@ function handleEdit(row: NavigationItem) {
imageUrl: row.imageUrl || '',
linkUrl: row.linkUrl || '',
sort: row.sort,
position: row.position,
actionType: row.actionType,
status: row.status
}
state.dialogVisible = true
@ -446,8 +489,10 @@ async function handleSubmit() {
const requestData: CreateNavigationRequest | UpdateNavigationRequest = {
name: formData.name,
imageUrl: formData.imageUrl || undefined,
linkUrl: formData.linkUrl || undefined,
linkUrl: formData.actionType === 1 ? (formData.linkUrl || undefined) : undefined,
sort: formData.sort,
position: formData.position,
actionType: formData.actionType,
status: formData.status
}

View File

@ -89,19 +89,22 @@ public class HomeController : ControllerBase
/// 获取首页导航入口列表
/// </summary>
/// <remarks>
/// GET /api/home/getNavigationList
/// GET /api/home/getNavigationList?position=1
///
/// 返回所有启用状态的首页导航入口按Sort降序排列
/// 返回启用状态的首页导航入口按Sort降序排列
/// 支持按 position 筛选区域1=专业测评2=更多),不传返回全部
/// 不需要用户登录认证
/// Requirements: 2.1, 2.2
/// </remarks>
/// <param name="position">区域标识可选1=专业测评区域2=更多区域</param>
/// <returns>导航入口列表</returns>
[HttpGet("getNavigationList")]
[ProducesResponseType(typeof(ApiResponse<List<HomeNavigationDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<List<HomeNavigationDto>>> GetNavigationList()
public async Task<ApiResponse<List<HomeNavigationDto>>> GetNavigationList([FromQuery] int? position)
{
try
{
var navigations = await _homeService.GetNavigationListAsync();
var navigations = await _homeService.GetNavigationListAsync(position);
return ApiResponse<List<HomeNavigationDto>>.Success(navigations);
}
catch (Exception ex)

View File

@ -29,11 +29,8 @@ public interface IHomeService
/// <summary>
/// 获取首页导航入口列表
/// </summary>
/// <remarks>
/// 返回所有启用状态的首页导航入口按Sort降序排列
/// </remarks>
/// <returns>导航入口列表</returns>
Task<List<HomeNavigationDto>> GetNavigationListAsync();
/// <param name="position">区域标识(可选),不传返回全部</param>
Task<List<HomeNavigationDto>> GetNavigationListAsync(int? position = null);
/// <summary>
/// 获取宣传图列表

View File

@ -76,13 +76,20 @@ public class HomeService : IHomeService
}
/// <inheritdoc />
public async Task<List<HomeNavigationDto>> GetNavigationListAsync()
public async Task<List<HomeNavigationDto>> GetNavigationListAsync(int? position = null)
{
_logger.LogDebug("获取首页导航入口列表");
_logger.LogDebug("获取首页导航入口列表, position={Position}", position);
var navigations = await _dbContext.HomeNavigations
var query = _dbContext.HomeNavigations
.AsNoTracking()
.Where(n => n.Status != 0 && !n.IsDeleted)
.Where(n => !n.IsDeleted && n.Status == 1);
if (position.HasValue)
{
query = query.Where(n => n.Position == position.Value);
}
var navigations = await query
.OrderByDescending(n => n.Sort)
.Select(n => new HomeNavigationDto
{
@ -90,7 +97,9 @@ public class HomeService : IHomeService
Name = n.Name,
ImageUrl = n.ImageUrl ?? string.Empty,
LinkUrl = n.LinkUrl,
Status = n.Status
Status = n.Status,
Position = n.Position,
ActionType = n.ActionType
})
.ToListAsync();

View File

@ -41,7 +41,17 @@ public class HomeNavigation
public int Sort { get; set; }
/// <summary>
/// 状态0即将上线 1已上线
/// 区域标识1=专业测评区域2=更多区域
/// </summary>
public int Position { get; set; } = 1;
/// <summary>
/// 动作类型1=跳转页面2=弹客服二维码3=即将上线
/// </summary>
public int ActionType { get; set; } = 1;
/// <summary>
/// 状态0=禁用1=启用
/// </summary>
public int Status { get; set; } = 1;

View File

@ -29,4 +29,14 @@ public class HomeNavigationDto
/// 状态0即将上线 1已上线
/// </summary>
public int Status { get; set; }
/// <summary>
/// 区域标识
/// </summary>
public int Position { get; set; }
/// <summary>
/// 动作类型
/// </summary>
public int ActionType { get; set; }
}

View File

@ -22,10 +22,12 @@ export function getAssessmentList() {
/**
* 获取首页导航入口列表
* @param {Object} [params] - 查询参数
* @param {number} [params.position] - 区域标识
* @returns {Promise<Object>}
*/
export function getNavigationList() {
return get('/home/getNavigationList')
export function getNavigationList(params) {
return get('/home/getNavigationList', params)
}
/**

View File

@ -44,7 +44,7 @@
</view>
<!-- 专业测评入口 -->
<view class="section-card" v-if="navigationList.length > 0">
<view class="section-card" v-if="assessmentList.length > 0">
<view class="section-header">
<view class="section-indicator"></view>
<text class="section-title">专业测评</text>
@ -52,21 +52,19 @@
<view class="assessment-grid">
<view
class="assessment-card"
v-for="(item, index) in navigationList"
v-for="(item, index) in assessmentList"
:key="index"
:class="'assessment-card--' + index"
@click="handleNavigationClick(item)"
@click="handleCardClick(item)"
>
<!-- 即将上线标签 -->
<view v-if="item.status === 2" class="coming-soon-tag">
<view v-if="item.actionType === 3" class="coming-soon-tag">
<text>即将上线</text>
</view>
<image
:src="item.imageUrl"
mode="aspectFit"
class="assessment-icon"
mode="aspectFill"
class="assessment-image"
/>
<text class="assessment-name">{{ item.name }}</text>
</view>
</view>
</view>
@ -83,24 +81,22 @@
</view>
<!-- 更多 -->
<view class="section-card">
<view class="section-card" v-if="moreList.length > 0">
<view class="section-header">
<view class="section-indicator"></view>
<text class="section-title">更多</text>
</view>
<view class="more-grid">
<view class="more-card more-card--plan" @click="goToStudyPlan">
<text class="more-card__title">学习方案</text>
<view
class="more-card"
v-for="(item, index) in moreList"
:key="index"
:class="{ 'more-card--full': moreList.length % 2 === 1 && index === moreList.length - 1 }"
@click="handleCardClick(item)"
>
<text class="more-card__title">{{ item.name }}</text>
<image
src="/static/icons/study-plan.png"
mode="aspectFit"
class="more-card__icon"
/>
</view>
<view class="more-card more-card--consult" @click="goToConsult">
<text class="more-card__title">详细咨询</text>
<image
src="/static/icons/consult.png"
:src="item.imageUrl"
mode="aspectFit"
class="more-card__icon"
/>
@ -116,6 +112,15 @@
<!-- 底部安全间距 -->
<view class="safe-bottom"></view>
</scroll-view>
<!-- 客服二维码弹窗 -->
<Popup
:visible="showQrPopup"
:imageUrl="qrcodeUrl"
title="扫码咨询"
:content="qrcodeUrl ? '' : '暂未配置客服二维码'"
@close="showQrPopup = false"
/>
</view>
</template>
@ -124,7 +129,9 @@ import { ref, onMounted } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useNavbar } from '@/composables/useNavbar.js'
import { getBannerList, getNavigationList } from '@/api/home.js'
import { getContactInfo } from '@/api/system.js'
import Loading from '@/components/Loading/index.vue'
import Popup from '@/components/Popup/index.vue'
const userStore = useUserStore()
const { statusBarHeight, navbarHeight, totalNavbarHeight } = useNavbar()
@ -133,7 +140,10 @@ const { statusBarHeight, navbarHeight, totalNavbarHeight } = useNavbar()
const pageLoading = ref(true)
const isRefreshing = ref(false)
const bannerList = ref([])
const navigationList = ref([])
const assessmentList = ref([])
const moreList = ref([])
const qrcodeUrl = ref('')
const showQrPopup = ref(false)
/**
* 加载Banner数据
@ -150,16 +160,44 @@ async function loadBannerList() {
}
/**
* 加载首页导航入口数据
* 加载专业测评区域数据position=1
*/
async function loadNavigationList() {
async function loadAssessmentList() {
try {
const res = await getNavigationList()
const res = await getNavigationList({ position: 1 })
if (res && res.code === 0 && res.data) {
navigationList.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
assessmentList.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
}
} catch (error) {
console.error('加载导航入口失败:', error)
console.error('加载专业测评数据失败:', error)
}
}
/**
* 加载更多区域数据position=2
*/
async function loadMoreList() {
try {
const res = await getNavigationList({ position: 2 })
if (res && res.code === 0 && res.data) {
moreList.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
}
} catch (error) {
console.error('加载更多区域数据失败:', error)
}
}
/**
* 加载客服二维码URL
*/
async function loadContactInfo() {
try {
const res = await getContactInfo()
if (res && res.code === 0 && res.data) {
qrcodeUrl.value = res.data.qrcodeUrl || ''
}
} catch (error) {
console.error('加载客服二维码失败:', error)
}
}
@ -172,7 +210,9 @@ async function initPageData() {
userStore.restoreFromStorage()
await Promise.all([
loadBannerList(),
loadNavigationList()
loadAssessmentList(),
loadMoreList(),
loadContactInfo()
])
} finally {
pageLoading.value = false
@ -199,13 +239,18 @@ function handleBannerClick(item) {
}
/**
* 处理导航入口点击
* 处理卡片点击根据 ActionType 分发
*/
function handleNavigationClick(item) {
if (item.status === 2) {
uni.showToast({ title: '该功能即将上线', icon: 'none', duration: 2000 })
function handleCardClick(item) {
if (item.actionType === 3) {
uni.showToast({ title: '即将上线', icon: 'none', duration: 2000 })
return
}
if (item.actionType === 2) {
showQrPopup.value = true
return
}
// actionType === 1:
if (!item.linkUrl) return
uni.navigateTo({
url: item.linkUrl,
@ -222,20 +267,6 @@ function goToPlanner() {
uni.navigateTo({ url: '/pages/planner/list/index' })
}
/**
* 跳转学习方案业务详情页
*/
function goToStudyPlan() {
uni.navigateTo({ url: '/pages/business/detail/index?type=study-plan' })
}
/**
* 跳转详细咨询业务详情页
*/
function goToConsult() {
uni.navigateTo({ url: '/pages/business/detail/index?type=consult' })
}
/**
* 下拉刷新
*/
@ -349,24 +380,10 @@ onMounted(() => {
.assessment-card {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 240rpx;
border-radius: $border-radius-xl;
overflow: hidden;
// - /
&--0 {
background: linear-gradient(135deg, #FF7B6B, #FF5B5B);
}
// - /
&--1 {
background: linear-gradient(135deg, #FFD080, #FFB347);
}
.coming-soon-tag {
position: absolute;
top: 0;
@ -382,16 +399,9 @@ onMounted(() => {
}
}
.assessment-icon {
width: 100rpx;
height: 100rpx;
margin-bottom: $spacing-sm;
}
.assessment-name {
font-size: $font-size-md;
font-weight: $font-weight-bold;
color: $text-white;
.assessment-image {
width: 100%;
height: 100%;
}
}
@ -412,14 +422,15 @@ onMounted(() => {
}
}
// -
// - 2
.more-grid {
display: flex;
flex-wrap: wrap;
gap: $spacing-md;
}
.more-card {
flex: 1;
width: calc(50% - #{$spacing-md} / 2);
display: flex;
align-items: center;
justify-content: space-between;
@ -427,13 +438,12 @@ onMounted(() => {
padding: 0 $spacing-lg;
border-radius: $border-radius-xl;
overflow: hidden;
background-color: $bg-gray;
box-sizing: border-box;
&--plan {
background: linear-gradient(135deg, #FFF5EB, #FFE8D5);
}
&--consult {
background: linear-gradient(135deg, #FFE8EC, #FFD5DC);
//
&--full {
width: 100%;
}
&__title {