feat: 完善首页布局并添加组件

1. 添加首页轮播图组件 ImageSlider。
2. 添加我的任务组件 MineTask,展示用户任务事项。
3. 调整首页组件结构,优化页面展示效果。
4. 更新 TypeScript 版本至 5.8.3。
5. 将 tsconfig.app.json 中的 module 修改为 ESNext,适配新的模块加载方式。
6. 在文档中强调使用 Vue3 的 `<script setup>` 语法。
7. 添加 Echarts依赖
This commit is contained in:
Huangzhe
2025-05-08 17:04:17 +08:00
parent 4422182108
commit d10ab302d5
12 changed files with 384 additions and 8 deletions

View File

@@ -5,3 +5,4 @@ trigger: always_on
1. always use chinese to response 1. always use chinese to response
2. 尽量使用 element-plus 的组件内容 2. 尽量使用 element-plus 的组件内容
3. 开发的时候尽量少使用 css 内容,能不用尽量不用 3. 开发的时候尽量少使用 css 内容,能不用尽量不用
4. 使用 vue3 的 <script setup> 语法, 拒绝使用以前的语法

View File

@@ -22,6 +22,7 @@
"core-js": "^3.41.0", "core-js": "^3.41.0",
"cos-js-sdk-v5": "^1.8.7", "cos-js-sdk-v5": "^1.8.7",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"echarts": "^5.6.0",
"element-plus": "^2.7.8", "element-plus": "^2.7.8",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -62,7 +63,7 @@
"postcss-pxtorem": "^6.1.0", "postcss-pxtorem": "^6.1.0",
"sass": "^1.85.1", "sass": "^1.85.1",
"sass-loader": "^16.0.5", "sass-loader": "^16.0.5",
"typescript": "~5.4.0", "typescript": "^5.8.3",
"unplugin-auto-import": "^0.18.6", "unplugin-auto-import": "^0.18.6",
"unplugin-vue-components": "^0.27.5", "unplugin-vue-components": "^0.27.5",
"vite": "^6.0.0", "vite": "^6.0.0",

View File

@@ -0,0 +1,48 @@
import { ref } from 'vue';
const option = {
// title: {
// text: 'Referer of a Website',
// subtext: 'Fake Data',
// left: 'center'
// },
// tooltip: {
// trigger: 'item'
// },
// legend: {
// orient: 'vertical',
// left: 'left'
// },
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
export const pieOption = ref<Partial<typeof option>>(option);
// 删除左侧的预览图
export function deleteLegend() {
delete pieOption.value.legend;
}
export function deleteTitle() {
delete pieOption.value.title;
}

6
src/hooks/chart/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
type optsType =
| {
title: boolean;
legend: boolean;
}
| {};

View File

@@ -0,0 +1,64 @@
import { onMounted, ref, type ShallowRef, watch } from 'vue';
import type { ECOption } from '@/utils/echarts';
import { chart } from '@/utils/echarts';
import { deleteLegend, deleteTitle, pieOption } from './data/pie';
type dataOption = Partial<ECOption['data']>;
const pieChart = ref();
/**
* 定义数据集
*/
const series = ref<dataOption[]>([
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]);
/**
* 饼图的 option
*/
const option = ref(pieOption);
function useSetPieChart(
dom: Readonly<ShallowRef<HTMLSpanElement | null>>,
data: any[],
opts: optsType = {}
): void {
for (let item in opts) {
if (item === 'legend') !opts[item] && deleteLegend();
else if (item === 'title') !opts[item] && deleteTitle();
}
// 检测边界范围 dom 和 data 是否存在
onMounted(() => {
console.log(dom);
if (!dom || data.length === 0) return;
// 在 dom 挂载之后,显示饼图
pieChart.value = chart.init(dom.value);
pieChart.value.setOption(option.value, opts);
});
// 如果 data 变动,重新生成图表
watch(series, (value) => {
pieChart.value.setOption(value, opts);
});
}
export { useSetPieChart };

View File

@@ -14,7 +14,6 @@ import '@/style/utils.scss';
import appBridge from '@/assets/js/appBridge'; import appBridge from '@/assets/js/appBridge';
import VConsole from 'vconsole'; import VConsole from 'vconsole';
import './assets/css/main.scss'; import './assets/css/main.scss';
const app = createApp(App); const app = createApp(App);
if (import.meta.env.VITE_APP_ENV !== 'production') { if (import.meta.env.VITE_APP_ENV !== 'production') {

View File

@@ -0,0 +1,54 @@
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import type { ComposeOption } from 'echarts/core';
import * as echarts from 'echarts/core';
import type { PieSeriesOption } from 'echarts/charts';
// 引入 饼状图
import { PieChart } from 'echarts/charts';
// 组件类型的定义后缀都为 ComponentOption
import type {
DatasetComponentOption,
GridComponentOption,
TitleComponentOption,
TooltipComponentOption,
LegendComponentOption
} from 'echarts/components';
// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
DatasetComponent,
GridComponent,
TitleComponent,
TooltipComponent,
TransformComponent,
LegendComponent
} from 'echarts/components';
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 使用 SVG 渲染器
import { SVGRenderer } from 'echarts/renderers';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = ComposeOption<
| PieSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| LegendComponentOption
>;
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LabelLayout,
UniversalTransition,
SVGRenderer,
PieChart,
LegendComponent
]);
export { echarts as chart, type ECOption };

View File

@@ -8,6 +8,9 @@ import utils from '@/assets/js/common';
import { getUserInfo } from '@/api/common/index.js'; import { getUserInfo } from '@/api/common/index.js';
import { showFailToast } from 'vant'; import { showFailToast } from 'vant';
import appBridge from '@/assets/js/appBridge'; import appBridge from '@/assets/js/appBridge';
import ImageSlider from './components/ImageSlider/Index.vue';
import MineTask from '@/views/Home/components/MineTask/Index.vue';
const contentShow = ref(false); const contentShow = ref(false);
onMounted(async () => { onMounted(async () => {
@@ -42,12 +45,16 @@ onMounted(async () => {
<div v-if="contentShow" class="container-home"> <div v-if="contentShow" class="container-home">
<div class="container-body"> <div class="container-body">
<create-survey :createdNewPage="false" /> <create-survey :createdNewPage="false" />
<!-- 首页轮播图 -->
<image-slider />
<!-- 最新问卷 --> <!-- 最新问卷 -->
<last-survey /> <!-- <last-survey />-->
<!-- 模板市场 --> <!-- 模板市场 -->
<Market /> <!-- <Market />-->
<!--底部新建问卷--> <!--底部新建问卷-->
<NewSurvey></NewSurvey> <NewSurvey />
<mine-task />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,43 @@
<template>
<el-card shadow="never">
<el-carousel height="160px" :interval="1000" arrow="never">
<el-carousel-item v-for="item in sliderItems" :key="item.id">
<el-row :gutter="20" justify="space-between">
<el-col :span="16">
<el-row >
<el-col :span="4">
<el-icon :size="24" color="#409EFF"><DataAnalysis /></el-icon>
</el-col>
<el-col :span="20">
<h3>{{ item.title }}</h3>
<p>{{ item.subtitle }}</p>
</el-col>
</el-row>
</el-col>
</el-row>
</el-carousel-item>
</el-carousel>
</el-card>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DataAnalysis } from '@element-plus/icons-vue';
const sliderItems = ref<SliderItem[]>([
{
id: 1,
title: '伊调研功能上新',
subtitle: '从问卷投放到数据报分析一站搞定!',
image: '/src/assets/slider/slider1.png'
},
{
id: 2,
title: '数据分析功能',
subtitle: '强大的数据分析能力,助您洞察市场趋势',
image: '/src/assets/slider/slider2.png'
}
]);
</script>

View File

@@ -0,0 +1,6 @@
interface SliderItem {
id: number;
title: string;
subtitle: string;
image: string;
}

View File

@@ -0,0 +1,146 @@
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { showToast } from 'vant';
import { useSetPieChart } from '@/hooks/chart/usePieChart';
// 饼图 dom 结构
const pieChart = useTemplateRef<HTMLSpanElement>('pieChart');
useSetPieChart(pieChart, [1], { title: false, ledge: false });
// 任务数据
const taskData = ref({
title: '问卷A',
progress: 100,
deadline: '2025-03-31至04-01',
creator: '张三',
creationMethod: '移动端',
creationTime: '2025-03-04',
responseCount: 10,
responseRate: '10%',
submissionRate: '10%',
status: '草稿中'
});
// 图表数据
const chartData = ref([
{ name: '选项1', value: 66.77, color: '#F56C6C' },
{ name: '选项2', value: 33.33, color: '#F7BA2A' },
{ name: '选项3', value: 0, color: '#67C23A' },
{ name: '选项4', value: 0, color: '#409EFF' }
]);
// 导航按钮点击事件
const handlePrev = () => {
showToast('点击了上一个问题');
};
const handleNext = () => {
showToast('点击了下一个问题');
};
</script>
<template>
<van-card class="task-card">
<!-- 状态标签 -->
<template #tags>
<el-tag type="success" effect="plain">{{ taskData.status }}</el-tag>
</template>
<!-- 标题部分 -->
<template #title>
<van-space>
<span style="font-size: 18px; font-weight: bold">{{ taskData.title }}</span>
<span style="color: #409eff">{{ taskData.progress }}%</span>
<span style="color: #606266; font-size: 14px">| {{ taskData.deadline }}</span>
</van-space>
</template>
<!-- 创建者信息 -->
<template #desc>
<el-space spacer="|">
<span>{{ taskData.creator }}</span>
<span>{{ taskData.creationMethod }}</span>
<span>创建时间:{{ taskData.creationTime }}</span>
</el-space>
</template>
<template #price>
<!-- 空的价格区域 -->
</template>
<!-- 卡片内容 -->
<template #bottom>
<van-space direction="vertical" fill style="width: 100%; padding-top: 16px">
<!-- 任务统计 -->
<van-grid :column-num="3">
<van-grid-item>
<van-space direction="vertical" fill>
<span style="color: #67c23a; font-size: 18px; font-weight: bold">{{
taskData.responseCount
}}</span>
<span style="font-size: 14px; color: #909399">回收数量</span>
</van-space>
</van-grid-item>
<van-grid-item>
<van-space direction="vertical" fill>
<span style="color: #67c23a; font-size: 18px; font-weight: bold">{{
taskData.responseRate
}}</span>
<span style="font-size: 14px; color: #909399">回收数量进度</span>
</van-space>
</van-grid-item>
<van-grid-item>
<van-space direction="vertical" fill>
<span style="color: #67c23a; font-size: 18px; font-weight: bold">{{
taskData.submissionRate
}}</span>
<span style="font-size: 14px; color: #909399">投放时间进度</span>
</van-space>
</van-grid-item>
</van-grid>
<!-- 问题部分 -->
<van-divider />
<p>1. 能描述您为自己或家人购买这款产品A的可能性吗单选</p>
<!-- 图表部分 -->
<div style="display: flex; justify-content: center; margin: 16px 0">
<span ref="pieChart" style="width: 100%; height: 300px"></span>
</div>
<!-- 选项列表 -->
<van-cell-group inset>
<van-cell v-for="(item, index) in chartData" :key="index">
<template #title>
<van-tag :color="item.value > 0 ? item.color : '#dcdfe6'" plain>
{{ item.name }}
</van-tag>
</template>
<template #value>
<span>{{ item.value.toFixed(2) }}%</span>
</template>
</van-cell>
</van-cell-group>
</van-space>
<!-- 导航按钮 左右按钮-->
<!-- <div style="position: fixed; left: 16px; top: 50%; transform: translateY(-50%)">-->
<!-- <van-button round icon="arrow-left" @click="handlePrev" />-->
<!-- </div>-->
<!-- <div style="position: fixed; right: 16px; top: 50%; transform: translateY(-50%)">-->
<!-- <van-button round icon="arrow" @click="handleNext" />-->
<!-- </div>-->
</template>
</van-card>
</template>
<style lang="scss" scoped>
.task-card {
//margin: 16px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
</style>

View File

@@ -15,7 +15,8 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"allowJs": true, "allowJs": true,
"baseUrl": ".", "baseUrl": ".",
"module": "commonjs", "module": "ESNext",
// "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,