feat: 新增首页推荐组件及相关功能优化

- 新增首页推荐组件 HomeRecommend,用于展示趋势数据
- 新增推荐相关 API 接口 recommend.ts 及类型定义
- 优化 YlTable 组件,添加类型定义文件,支持 emptyText 和 width 属性
- 修改 Home 页面,添加首页推荐组件的展示
- 优化 Survey 页面代码结构,修复加载逻辑
- 添加响应类型定义,规范 API 返回数据格式
This commit is contained in:
Huangzhe
2025-05-21 14:38:29 +08:00
parent e21770134c
commit 0ae0bd0fdf
8 changed files with 305 additions and 161 deletions

View File

@@ -1,65 +1,63 @@
<script setup lang="ts"> <script setup lang="ts">
import { RowAlign, type ColumnStyle } from 'element-plus'; import { RowAlign, type ColumnStyle } from 'element-plus';
const data = defineModel<unknown[]>('data', { default: [] }); const data = defineModel<unknown[]>('data', { default: [] });
const props = defineModel< const props = defineModel<TablePropsType[]>('props');
{
prop: any; // 是否只显示单行
label: any; const singleLine = defineModel<boolean>('singleLine', { default: false });
width: any; const rowStyle = defineModel<ColumnStyle<any>>('rowStyle', {
}[] default: {}
>('props'); });
// 显示表格的高度, 默认的高度是 200px
// 是否只显示单行 const tableHeight = defineModel<string | number>('height', { default: '300px' });
const singleLine = defineModel<boolean>('singleLine', { default: false });
const rowStyle = defineModel<ColumnStyle<any>>('rowStyle', { const headerStyle = defineModel<ColumnStyle<any>>('headerStyle', {
default: {} default: {
}); background: 'red'
// 显示表格的高度, 默认的高度是 200px }
const tableHeight = defineModel<string | number>('height', { default: '300px' }); });
const rounded = defineModel('rounded', { default: true });
const headerStyle = defineModel<ColumnStyle<any>>('headerStyle', { const stripeColor = defineModel('stripeColor', { default: 'red' });
default: { // 空数据时的提示文本
background: 'red' const emptyText = defineModel('emptyText', { default: '暂无数据' });
}
}); // (data: { row: any; rowIndex: number; }) => string
const rounded = defineModel('rounded', { default: true }); function setStripeColor(rowData: { row: any; rowIndex: number }): string {
const stripeColor = defineModel('stripeColor', { default: 'red' }); const { rowIndex } = rowData;
return rowIndex % 2 === 0 ? 'even-row' : 'odd-row';
// (data: { row: any; rowIndex: number; }) => string }
function setStripeColor(rowData: { row: any; rowIndex: number }): string { </script>
const { rowIndex } = rowData;
return rowIndex % 2 === 0 ? 'even-row' : 'odd-row'; <template>
} <div :style="{ borderRadius: rounded ? '10px' : '0', overflow: 'hidden' }" style="height: 100%">
</script> <el-table
:max-height="tableHeight"
<template> :header-cell-style="{ background: '#f2f8ee' }"
<div :style="{ borderRadius: rounded ? '10px' : '0', overflow: 'hidden' }"> :row-class-name="setStripeColor"
<el-table :data="data"
:max-height="tableHeight" :empty-text="emptyText"
:header-cell-style="{ background: '#f2f8ee' }" style="width: 100%"
:row-class-name="setStripeColor" >
:data="data" <el-table-column
style="width: 100%" v-for="item in props"
> :width="item.width"
<el-table-column :prop="item.prop"
v-for="item in props" :label="item.label"
:prop="item.prop" show-overflow-tooltip
:label="item.label" />
show-overflow-tooltip </el-table>
/> </div>
</el-table> </template>
</div>
</template> <style lang="scss" scoped module="table">
:deep(.even-row) {
<style lang="scss" scoped module="table"> background-color: white;
:deep(.even-row) { }
background-color: white; :deep(.odd-row) {
} background-color: #fafbfa;
:deep(.odd-row) { }
background-color: #fafbfa; :deep(.el-table__header-wrapper) {
} border-radius: 8px 8px 0 0;
:deep(.el-table__header-wrapper) { }
border-radius: 8px 8px 0 0; </style>
}
</style>

View File

@@ -0,0 +1,6 @@
type TablePropsType = {
prop: string;
label: string;
width?: string;
};

View File

@@ -0,0 +1,35 @@
import request from '@/utils/request';
import type { HomeRecommendResponse } from './types/response';
import { ref } from 'vue';
import type { AxiosResponse } from 'axios';
type RecommendParams = {
page: string;
per_page: string;
sort: string;
};
/**
* 首页推荐内容
* @param params 可选参数
* @returns 推荐结果
*/
export function recommend(params: Partial<RecommendParams>) {
const code = ref();
const message = ref();
const data = ref<HomeRecommendResponse['data']>();
request<HomeRecommendResponse>({
url: `/console/survey/trend/list`,
params
}).then((res) => {
code.value = res.data.code;
message.value = res.data.message;
data.value = res.data.data;
});
return {
code,
message,
data
};
}

38
src/hooks/request/types/recommend.d.ts vendored Normal file
View File

@@ -0,0 +1,38 @@
export interface HomeRecommend {
meta: Meta;
surveyTrendDataVOS: SurveyTrendDataVO[];
// [property: string]: any;
}
export interface Meta {
currentPage: number;
from: number;
lastPage: number;
perPage: number;
total: number;
[property: string]: any;
}
export interface SurveyTrendDataVO {
/**
* 声量增长环比
*/
growthRingRatio: number | number;
/**
* 主键
*/
id: number;
/**
* 排名
*/
rank: number;
/**
* 销量增长环比
*/
salesGrowthRingRatio: number;
/**
* 趋势名称
*/
trendName: string;
[property: string]: any;
}

9
src/hooks/request/types/response.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import { HomeRecommend } from "./recommend";
export interface CDMResponse<T>{
code: number;
data: T;
message: string;
}
type HomeRecommendResponse = CDMResponse<HomeRecommend>;

View File

@@ -9,6 +9,7 @@ import appBridge from '@/assets/js/appBridge';
import ImageSlider from './components/ImageSlider/Index.vue'; import ImageSlider from './components/ImageSlider/Index.vue';
import SearchBar from '@/components/Search/Index.vue'; import SearchBar from '@/components/Search/Index.vue';
import Navigation from '@/components/Navigation/Index.vue'; import Navigation from '@/components/Navigation/Index.vue';
import HomeRecommend from './components/HomeRecommend/Index.vue';
import MineTask from '@/views/Home/components/MineTask/Index.vue'; import MineTask from '@/views/Home/components/MineTask/Index.vue';
import router from '@/router'; import router from '@/router';
@@ -66,7 +67,8 @@ function handleSearchClick() {
<!--底部新建问卷--> <!--底部新建问卷-->
<NewSurvey /> <NewSurvey />
<mine-task /> <mine-task v-if="false" />
<home-recommend class="home_recommend" v-else />
<navigation /> <navigation />
</div> </div>
@@ -90,4 +92,8 @@ function handleSearchClick() {
overflow: hidden; overflow: hidden;
border-radius: theme.$card-radius; border-radius: theme.$card-radius;
} }
.home_recommend {
margin: theme.$gap 0;
}
</style> </style>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import { recommend } from '@/hooks/request/recommend';
import { ref } from 'vue';
import YlTable from '@/components/YlTable/Index.vue';
// 外部获取的数据
const { data } = recommend({});
const props = ref<TablePropsType[]>([
{
prop: 'rank',
label: '排名',
width: '55'
},
{
prop: 'trend_name',
label: '趋势名称',
width: '85'
},
{
prop: 'growth_ring_ratio',
label: '声量增长环比',
width: '113'
},
{
prop: 'sales_growth_ring_ratio',
label: '销量增长环比',
width: '113'
}
]);
</script>
<template>
<van-cell class="home_recommend">
<template #extra>
<div style="width: 88vw">
<yl-table :data="data?.surveyTrendDataVOS" :props="props" />
</div>
</template>
</van-cell>
</template>
<style lang="scss" scoped>
@use '@/assets/css/theme' as *;
.home_recommend {
border-radius: $card-radius;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -1,95 +1,96 @@
<template> <template>
<div class="survey-search"> <div class="survey-search">
<nav-search v-model:value="searchValue" @search="blurs" /> <nav-search v-model:value="searchValue" @search="blurs" />
<!-- <nav-search <!-- <nav-search
placeholder="请输入关键词" placeholder="请输入关键词"
v-model:value="searchValue" v-model:value="searchValue"
@click="() => $router.push({ name: 'search' })" @click="() => $router.push({ name: 'search' })"
/> --> /> -->
</div> </div>
<div class="new-survey-container"> <div class="new-survey-container">
<div style="margin-bottom: 80px"> <div style="margin-bottom: 80px">
<van-list <van-list
v-model:loading="loading" v-model:loading="loading"
:finished="finished" :finished="finished"
finished-text="没有更多了" finished-text="没有更多了"
@load="onLoad" @load="onLoad"
> >
<div v-for="item in survey" v-if="survey.length > 0" :key="item" class="new-survey_item"> <div v-for="item in survey" v-if="survey.length > 0" :key="item" class="new-survey_item">
<survey-item :survey="item" :is-analysis="true" :disable-action-button="false" /> <survey-item :survey="item" :is-analysis="true" :disable-action-button="false" />
</div> </div>
<empty-container v-else /> <empty-container v-else />
</van-list> </van-list>
</div> </div>
<NewSurvey v-if="survey.length > 0" /> <NewSurvey v-if="survey.length > 0" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import NavSearch from '@/components/Search/Index.vue'; import NavSearch from '@/components/Search/Index.vue';
import NewSurvey from '@/views/Home/components/NewSurvey/index.vue'; import NewSurvey from '@/views/Home/components/NewSurvey/index.vue';
import EmptyContainer from '@/views/Survey/components/EmptyContainer.vue'; import EmptyContainer from '@/views/Survey/components/EmptyContainer.vue';
import SurveyItem from '@/views/Survey/components/SurveyItem.vue'; import SurveyItem from '@/views/Survey/components/SurveyItem.vue';
import { import {
form, form,
fetchSurveys, fetchSurveys,
loading, loading,
finished, finished,
survey, survey,
searchValue searchValue
} from '@/views/Survey/hooks/useSurveyData'; } from '@/views/Survey/hooks/useSurveyData';
const blurs = () => { const blurs = () => {
form.value.page = 1; form.value.page = 1;
form.value.project_name = searchValue.value; form.value.project_name = searchValue.value;
survey.value = []; survey.value = [];
fetchSurveys(); fetchSurveys();
}; };
const onLoad = () => {
// 异步更新数据 const onLoad = () => {
setTimeout(() => { // 异步更新数据
form.value.page = form.value.page + 1; setTimeout(() => {
fetchSurveys(); form.value.page = form.value.page + 1;
}, 500); fetchSurveys();
}; }, 500);
};
onMounted(() => {
// fetchSurveys(); onMounted(() => {
}); // fetchSurveys();
</script> });
</script>
<style scoped lang="scss">
@use '@/assets/css/theme'; <style scoped lang="scss">
@import '@/assets/css/base'; @use '@/assets/css/theme';
@import '@/assets/css/main'; @import '@/assets/css/base';
@import '@/assets/css/main';
.el-dropdown-menu__item:not(.is-disabled):focus,
.el-dropdown-menu__item:not(.is-disabled):hover { .el-dropdown-menu__item:not(.is-disabled):focus,
background-color: #000; .el-dropdown-menu__item:not(.is-disabled):hover {
} background-color: #000;
}
.survey-search {
@extend %search-gradient; .survey-search {
padding: 10px 10px; @extend %search-gradient;
position: sticky; padding: 10px 10px;
top: 0; position: sticky;
z-index: 1000; top: 0;
background-color: theme.$nav-header-color; z-index: 1000;
} background-color: theme.$nav-header-color;
}
.new-survey-container {
margin-top: 10px; .new-survey-container {
margin-top: 10px;
.new-survey_item {
margin: 0 10px 10px; .new-survey_item {
padding: 15px; margin: 0 10px 10px;
border-radius: 16px; padding: 15px;
background-color: white; border-radius: 16px;
} background-color: white;
}
.new-survey_item + .new-survey_item {
margin: 0 10px 10px; .new-survey_item + .new-survey_item {
} margin: 0 10px 10px;
} }
</style> }
</style>