优化问卷分析与界面展示

- 修复了图表数据为空时的渲染问题,添加了数据验证逻辑
- 优化了Analysis组件中的图表展示,增加了条件渲染
- 新增useScreen钩子函数用于响应式布局
- 改进了SurveyItem组件的标题样式,从h1调整为h3
- 优化了表格组件的宽度计算逻辑,提高了不同数据量下的展示效果
- 调整了多个组件的布局和样式,提升用户体验
- 清理了代码中的冗余注释和空白行,提高代码可读性
This commit is contained in:
Huangzhe
2025-05-21 16:12:58 +08:00
parent 0ae0bd0fdf
commit ef7463dc59
16 changed files with 108 additions and 66 deletions

View File

@@ -36,7 +36,9 @@ const index = ref(0);
// 当 keyword 变动的时候,标记脏数据 // 当 keyword 变动的时候,标记脏数据
watch( watch(
() => analysis.value, () => analysis.value,
async () => { async (value) => {
// 排除空数据渲染图标步骤
tableData.value = { tableData.value = {
...analysis.value, ...analysis.value,
option: getTableData(analysis.value) option: getTableData(analysis.value)
@@ -46,6 +48,9 @@ watch(
series.value = formatData(dimension.value ? tableData.value : analysis.value, index.value); series.value = formatData(dimension.value ? tableData.value : analysis.value, index.value);
const pieChart = useTemplateRef<HTMLSpanElement>('pieChart'); const pieChart = useTemplateRef<HTMLSpanElement>('pieChart');
// console.log(`series value `, series.value);
if (!series.value?.data?.length) return;
useSetPieChart(pieChart, series, { title: false, legend: false }); useSetPieChart(pieChart, series, { title: false, legend: false });
}, },
{ immediate: true } { immediate: true }
@@ -71,6 +76,7 @@ const changeChart = (i: number) => {
<!-- 图表部分 --> <!-- 图表部分 -->
<div <div
v-if="series?.data?.length"
class="charts" class="charts"
:style="{ :style="{
display: 'flex', display: 'flex',
@@ -79,7 +85,13 @@ const changeChart = (i: number) => {
margin: '16px 0' margin: '16px 0'
}" }"
> >
<span ref="pieChart" style="width: 100%; height: v-bind(chartHeight + 'px')"></span> <span
ref="pieChart"
:style="{
width: '100%',
height: series?.data?.length ? chartHeight + 'px' : ''
}"
></span>
</div> </div>
</section> </section>
</template> </template>

View File

@@ -8,7 +8,7 @@ const survey = defineModel<object>('survey', { required: true });
<template> <template>
<div> <div>
<h1>{{ survey.project_name }}</h1> <h3>{{ survey.project_name }}</h3>
<el-space spacer="|" direction="horizontal"> <el-space spacer="|" direction="horizontal">
<section class="flex items-center pt-1"> <section class="flex items-center pt-1">
<img :src="people" alt="" style="margin-right: 5px" /> <img :src="people" alt="" style="margin-right: 5px" />
@@ -25,8 +25,8 @@ const survey = defineModel<object>('survey', { required: true });
<style lang="scss" scoped module="item"> <style lang="scss" scoped module="item">
@use '@/assets/css/theme'; @use '@/assets/css/theme';
h1 { h3 {
margin: 17px 0 12px 0; margin: 10px 0 12px 0;
font-weight: 800; font-weight: 800;
line-height: 15px; line-height: 15px;
text-align: left; text-align: left;

View File

@@ -1,3 +1,7 @@
<script setup lang="ts">
const template = defineModel('template', { required: true });
</script>
<template> <template>
<van-cell style="margin-bottom: 10px"> <van-cell style="margin-bottom: 10px">
<template #extra> <template #extra>
@@ -18,6 +22,14 @@
</van-cell> </van-cell>
</template> </template>
<script setup lang="ts"> <style lang="scss" scoped module="item">
const template = defineModel('template', { required: true }); @use '@/assets/css/theme';
</script>
h3 {
margin: 10px 0 12px 0;
font-weight: 800;
line-height: 15px;
text-align: left;
font-style: normal;
}
</style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { RowAlign, type ColumnStyle } from 'element-plus'; import { type ColumnStyle } from 'element-plus';
const data = defineModel<unknown[]>('data', { default: [] }); const data = defineModel<unknown[]>('data', { default: [] });
const props = defineModel<TablePropsType[]>('props'); const props = defineModel<TablePropsType[]>('props');

View File

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

View File

@@ -0,0 +1,25 @@
import { onMounted, onUnmounted, ref } from 'vue';
export function screenLayout() {
// 当前视口的宽度
const width = ref(window.innerWidth);
// 当前视口的高度
const height = ref(window.innerHeight);
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const { width: _width, height: _height } = entry.contentRect;
width.value = _width;
height.value = _height;
});
});
// 当页面挂载的时候开始监听元素
onMounted(() => observer.observe(document.body));
// 当页面卸载的时候停止监听元素
onUnmounted(() => observer.disconnect());
return {
width,
height
};
}

View File

@@ -1,4 +1,5 @@
<script setup> <script setup>
import { fetchSurveys } from '@/hooks/request/useSurvey';
import CreateSurvey from './components/CreateSurvey/Index.vue'; import CreateSurvey from './components/CreateSurvey/Index.vue';
import NewSurvey from './components/NewSurvey/index.vue'; import NewSurvey from './components/NewSurvey/index.vue';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
@@ -14,7 +15,8 @@ import MineTask from '@/views/Home/components/MineTask/Index.vue';
import router from '@/router'; import router from '@/router';
const contentShow = ref(false); const contentShow = ref(false);
// 获取我的问卷数据
const { surveys } = fetchSurveys();
const keyword = ref(''); const keyword = ref('');
onMounted(async () => { onMounted(async () => {
if (appBridge.isInReactNative()) { if (appBridge.isInReactNative()) {
@@ -67,9 +69,8 @@ function handleSearchClick() {
<!--底部新建问卷--> <!--底部新建问卷-->
<NewSurvey /> <NewSurvey />
<mine-task v-if="false" /> <mine-task :surveys="surveys" v-if="surveys?.length > 0" />
<home-recommend class="home_recommend" v-else /> <home-recommend class="home_recommend" v-else />
<navigation /> <navigation />
</div> </div>
</div> </div>

View File

@@ -33,7 +33,7 @@ const props = ref<TablePropsType[]>([
<template> <template>
<van-cell class="home_recommend"> <van-cell class="home_recommend">
<template #extra> <template #extra>
<div style="width: 88vw"> <div style="width: 90vw">
<yl-table :data="data?.surveyTrendDataVOS" :props="props" /> <yl-table :data="data?.surveyTrendDataVOS" :props="props" />
</div> </div>
</template> </template>

View File

@@ -1,26 +1,39 @@
<template>
<el-card shadow="never" :body-style="{ padding: 0 }">
<van-swipe :autoplay="5000" indicator-color="white">
<van-swipe-item v-for="banner in banners">
<el-image :src="banner.banner_address" fit="contain" @click="handleBannerClick(banner)" />
</van-swipe-item>
</van-swipe>
</el-card>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { banners } from '@/views/Home/components/ImageSlider/hooks/useSlider'; import { banners } from '@/views/Home/components/ImageSlider/hooks/useSlider';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { bannerInfo } from '@/views/AD/hooks/useAD'; import { bannerInfo } from '@/views/AD/hooks/useAD';
const router = useRouter();
// const defineBanners = defineModel('banners'); // const defineBanners = defineModel('banners');
// 如果定义了 banner 那么 banners 就不再初始化 // 如果定义了 banner 那么 banners 就不再初始化
// defineBanners.value && updateBanners(defineBanners.value); // defineBanners.value && updateBanners(defineBanners.value);
const borderRadius = defineModel('borderRadius');
function handleBannerClick(banner: any) { function handleBannerClick(banner: any) {
const router = useRouter();
// 把对应的信息给 AD 的 hooks // 把对应的信息给 AD 的 hooks
bannerInfo.value = banner; bannerInfo.value = banner;
router.push({ name: 'ad' }); router.push({ name: 'ad' });
} }
</script> </script>
<template>
<van-swipe :autoplay="5000" indicator-color="white">
<van-swipe-item v-for="banner in banners">
<el-image
class="img"
:style="{ borderRadius: borderRadius + 'px' }"
:src="banner.banner_address"
fit="contain"
@click="handleBannerClick(banner)"
/>
</van-swipe-item>
</van-swipe>
</template>
<style lang="scss" scoped>
@use '@/assets/css/theme' as *;
.img {
border-radius: $card-radius;
}
</style>

View File

@@ -1,9 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { fetchSurveys } from '@/hooks/request/useSurvey';
import QuestionList from './components/QuestionList.vue'; import QuestionList from './components/QuestionList.vue';
import YlSwiper from '@/components/YlSwiper/Index.vue'; import YlSwiper from '@/components/YlSwiper/Index.vue';
const { surveys } = fetchSurveys(); const surveys = defineModel('surveys', { required: true });
</script> </script>
<template> <template>

View File

@@ -1,13 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import YlSwiper from '@/components/YlSwiper/Index.vue';
import { useFetchAnalysis } from '@/hooks/request/useSurvey'; import { useFetchAnalysis } from '@/hooks/request/useSurvey';
import { ref } from 'vue'; import { ref } from 'vue';
import SurveyItem from '@/views/Survey/components/SurveyItem.vue'; import SurveyItem from '@/views/Survey/components/SurveyItem.vue';
import LogicInfo from '@/views/Survey/views/Analysis/components/LogicInfo/Index.vue';
import { useRoute } from 'vue-router';
import Wait from '@/views/Survey/views/Analysis/components/Wait/Index.vue';
import AnalysisInfo from '@/views/Survey/views/Analysis/components/AnalysisInfo/Index.vue'; import AnalysisInfo from '@/views/Survey/views/Analysis/components/AnalysisInfo/Index.vue';
import SearchBar from '@/components/Search/Index.vue';
import { fetchSingleSurvey } from '@/hooks/request/useSurvey'; import { fetchSingleSurvey } from '@/hooks/request/useSurvey';
const survey = defineModel<SurveyItem>('survey'); const survey = defineModel<SurveyItem>('survey');
@@ -16,16 +11,6 @@ const { questionAnalysis } = useFetchAnalysis(survey.value?.sn as string);
const { currentSurvey } = fetchSingleSurvey(survey.value?.sn as string); const { currentSurvey } = fetchSingleSurvey(survey.value?.sn as string);
const disableInsight = ref(true); const disableInsight = ref(true);
const aiInsightsConfig = ref({
visible: false,
message: ''
});
const postAnalysis = (sn: string) => {
aiInsightsConfig.value.visible = true;
};
const height = ref(`200px`);
</script> </script>
<template> <template>
@@ -80,15 +65,6 @@ const height = ref(`200px`);
.question-item-container { .question-item-container {
width: 90vw; width: 90vw;
// display: flex;
// justify-content: center;
// flex-flow: column nowrap;
// align-items: center;
.survey-item {
width: 95%;
}
.analysis-info { .analysis-info {
} }
} }

View File

@@ -93,6 +93,9 @@ watch(keyword, async () => {
// await handleSearch(); // await handleSearch();
// 重置状态 // 重置状态
loading.value = false loading.value = false
// 清空 banners
banners.value = [];
}); });
// 索引变动之后,立刻进行搜索 (没有进行防抖) // 索引变动之后,立刻进行搜索 (没有进行防抖)

View File

@@ -68,7 +68,6 @@ onUnmounted(() => {
@use '@/assets/css/theme'; @use '@/assets/css/theme';
.title { .title {
overflow-wrap: break-word;
color: rgba(0, 0, 0, 1); color: rgba(0, 0, 0, 1);
font-size: 14px; font-size: 14px;
font-family: PingFangSC-Medium; font-family: PingFangSC-Medium;
@@ -76,7 +75,7 @@ onUnmounted(() => {
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
line-height: 20px; line-height: 20px;
margin: 28px 297px 0 2px; margin: 28px 0 0 0;
} }
.search-container { .search-container {
@@ -88,7 +87,7 @@ onUnmounted(() => {
} }
.banner { .banner {
padding: theme.$gap; padding: 0 theme.$gap;
} }
// 搜索结果外部布局 // 搜索结果外部布局

View File

@@ -1,10 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { index, surveys as _surveys, keyword } from '@/views/HomeSearch/Hooks/useSurveySearch';
index,
surveys as _surveys,
lastIndex,
keyword
} from '@/views/HomeSearch/Hooks/useSurveySearch';
import SurveyItem from '@/components/SurveyItem/Index.vue'; import SurveyItem from '@/components/SurveyItem/Index.vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { computed } from 'vue'; import { computed } from 'vue';
@@ -53,5 +48,9 @@ const surveys = computed(() => {
.list-item { .list-item {
margin-top: theme.$gap; margin-top: theme.$gap;
} }
.list-item:first-child {
margin-top: 0;
}
} }
</style> </style>

View File

@@ -48,8 +48,7 @@ const { questionAnalysis } = useFetchAnalysis(sn.value);
:survey="currentSurvey as SurveyItem" :survey="currentSurvey as SurveyItem"
@post-analysis="postAnalysis(sn as string)" @post-analysis="postAnalysis(sn as string)"
/> />
<!-- 弹窗组件 -->
<!-- 弹窗组件 -->
<el-dialog <el-dialog
:show-close="false" :show-close="false"
:close-on-click-modal="false" :close-on-click-modal="false"

View File

@@ -24,7 +24,7 @@
v-if="analysis.head" v-if="analysis.head"
/> />
</section> </section>
<!-- <section v-else> <!-- <section v-else>
<empty-container /> <empty-container />
</section> --> </section> -->
</div> </div>
@@ -38,14 +38,17 @@ import ChartMsg from '@/components/Analysis/Index.vue';
import { getTableData } from './hooks/pieSeries'; import { getTableData } from './hooks/pieSeries';
import YlTable from '@/components/YlTable/Index.vue'; import YlTable from '@/components/YlTable/Index.vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { screenLayout } from '@/hooks/browser/useScreen';
// questionTypeMap 自己去对应 // questionTypeMap 自己去对应
const showChart = ref([1, 2, 5, 106, 9, 10]); const showChart = ref([1, 2, 5, 106, 9, 10]);
// 接受上级传递的 questionAnalysis 数据 // 接受上级传递的 questionAnalysis 数据
const questionAnalysis = defineModel<any[]>('questionAnalysis'); const questionAnalysis = defineModel<any[]>('questionAnalysis');
const { width } = screenLayout();
// 构建表头 // 构建表头
const getTableHeadProps = (values: any[], option: any[]) => { const getTableHeadProps = (values: any[], option: any[]): TablePropsType[] => {
const head = []; const head = [];
if (values && values.length > 0) { if (values && values.length > 0) {
@@ -54,12 +57,13 @@ const getTableHeadProps = (values: any[], option: any[]) => {
head.push({ head.push({
label: item.title, label: item.title,
prop: item.key, prop: item.key,
width: 120 width: values.length < 3 ? width.value / values.length : 120
}); });
} }
}); });
} }
if (option && option.length > 0 && option[0].option) {
if (option.length > 0 && option[0].option) {
head.unshift({ head.unshift({
label: '选项', label: '选项',
prop: 'option', prop: 'option',