feat: 新增首页推荐组件及相关功能优化
- 新增首页推荐组件 HomeRecommend,用于展示趋势数据 - 新增推荐相关 API 接口 recommend.ts 及类型定义 - 优化 YlTable 组件,添加类型定义文件,支持 emptyText 和 width 属性 - 修改 Home 页面,添加首页推荐组件的展示 - 优化 Survey 页面代码结构,修复加载逻辑 - 添加响应类型定义,规范 API 返回数据格式
This commit is contained in:
@@ -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>
|
|
||||||
|
|||||||
6
src/components/YlTable/types/table.d.ts
vendored
Normal file
6
src/components/YlTable/types/table.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
type TablePropsType = {
|
||||||
|
prop: string;
|
||||||
|
label: string;
|
||||||
|
width?: string;
|
||||||
|
};
|
||||||
|
|
||||||
35
src/hooks/request/recommend.ts
Normal file
35
src/hooks/request/recommend.ts
Normal 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
38
src/hooks/request/types/recommend.d.ts
vendored
Normal 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
9
src/hooks/request/types/response.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { HomeRecommend } from "./recommend";
|
||||||
|
|
||||||
|
export interface CDMResponse<T>{
|
||||||
|
code: number;
|
||||||
|
data: T;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type HomeRecommendResponse = CDMResponse<HomeRecommend>;
|
||||||
@@ -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>
|
||||||
|
|||||||
51
src/views/Home/components/HomeRecommend/Index.vue
Normal file
51
src/views/Home/components/HomeRecommend/Index.vue
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user