SsCommonProblem.md 12 KB

SsCommonProblem.vue 组件详解

概述

SsCommonProblem.vue 是一个展示常见问题的 Vue 3 组件,采用 Composition API 和 TypeScript 编写。该组件提供了一个常见问题列表,用户可以点击问题快速发送到聊天框,还可以刷新获取新的问题列表。组件采用了响应式设计,在不同设备上都有良好的显示效果。

组件结构

1. 脚本部分 (Script)

1.1 导入声明

import {ref, defineEmits, onMounted} from 'vue'
import {Refresh} from '@element-plus/icons-vue'
import {useGlobalStore} from "@/stores/global";

功能解析

  • 从 Vue 3 导入 ref(创建响应式引用)、defineEmits(定义组件事件)、onMounted(生命周期钩子)
  • 从 Element Plus 导入 Refresh 图标组件
  • 导入全局状态管理 store useGlobalStore

1.2 事件定义

const emit = defineEmits(['finish', 'quickSend', 'loaded'])

功能解析

  • 定义组件可以触发的三种事件:
    • finish:刷新完成时触发
    • quickSend:用户点击问题时触发,用于快速发送问题到聊天框
    • loaded:组件挂载完成时触发

1.3 状态管理

const global = useGlobalStore()
const isRefresh = ref(false);

功能解析

  • global:获取全局状态管理实例,用于访问和修改全局状态
  • isRefresh:响应式布尔值,用于控制刷新按钮的加载状态

1.4 方法定义

const refresh = () => {
  isRefresh.value = true;
  global.loadCommonProblem('', () => {
    isRefresh.value = false;
    emit('finish')
  })
}

const quickSend = (msg: string) => {
  emit('quickSend', msg)
}

功能解析

  • refresh 方法:

    1. 设置刷新状态为 true,显示加载动画
    2. 调用全局 store 的 loadCommonProblem 方法加载常见问题
    3. 加载完成后,设置刷新状态为 false,并触发 finish 事件
  • quickSend 方法:

    • 接收一个问题内容作为参数
    • 触发 quickSend 事件,将问题内容传递给父组件

1.5 组件暴露

defineExpose({
  refresh
});

功能解析

  • 使用 defineExpose 显式暴露 refresh 方法,使父组件可以通过 ref 调用此方法

1.6 生命周期钩子

onMounted(() => {
  emit('loaded', 'footer')
})

功能解析

  • 组件挂载完成后触发 loaded 事件,传递参数 'footer',可能用于标识组件位置

2. 模板部分 (Template)

2.1 根容器

<div class="problem hover-scale">

功能解析

  • 使用 problem 类作为主容器
  • hover-scale 类可能提供悬停时的缩放效果

2.2 内容区域

<div class="problem-content">

功能解析

  • 包含问题的实际内容,设置较高的 z-index 确保在背景图片之上

2.3 头部区域

<div class="header">
  <el-image class="header-logo" src="/images/common_problem.png" fit="contain"/>
  <div class="header-content">
    <div class="header-title">常见问题</div>
    <div class="header-subject">聚焦商贸投资热点,回应企业民生关切</div>
    <el-button class="refresh is-mobile" :icon="Refresh" :loading="isRefresh" @click="refresh">
      换一换
    </el-button>
  </div>
</div>

功能解析

  • 显示常见问题的图标、标题和副标题
  • 包含一个"换一换"按钮,用于刷新问题列表
  • is-mobile 类可能用于移动端样式控制
  • 按钮绑定了 isRefresh 状态控制加载动画

2.4 问题列表

<div class="problem-list">
  <template v-for="(item, index) in global.commonProblem">
    <div class="problem-item ellipsis" @click="quickSend(item.questionContent)">
      {{ index + 1 }}.{{ item.questionContent }}
    </div>
  </template>
</div>

功能解析

  • 使用 v-for 遍历全局状态中的 commonProblem 数组
  • 每个问题显示序号和内容
  • ellipsis 类可能用于文本溢出时显示省略号
  • 点击问题项调用 quickSend 方法,传递问题内容

2.5 背景图片

<el-image class="problem-image is-pc" fit="contain" src="/images/bg01.png"/>
<el-image class="problem-image is-mobile" fit="contain" src="/images/bg011.png"/>

功能解析

  • 提供两个背景图片,分别用于 PC 端和移动端
  • 使用 is-pcis-mobile 类进行响应式控制

3. 样式部分 (Style)

3.1 主容器样式

.problem {
  border-radius: 1.25rem;
  position: relative;
  overflow: hidden;
  color: #ffffff;
  background: linear-gradient(180deg, #5A92F8, #2943D6);
  box-shadow: 0 0.25rem 1.25rem 0 rgba(52, 149, 239, 0.4);
  min-height: 16rem;
}

功能解析

  • 圆角边框设计
  • 相对定位,为内部绝对定位元素提供参考
  • 隐藏溢出内容
  • 白色文字
  • 蓝色渐变背景
  • 蓝色阴影效果
  • 最小高度设置

3.2 内容区域样式

.problem-content {
  position: relative;
  z-index: 2;
  padding: 1.5rem 1.25rem;
}

功能解析

  • 相对定位,设置 z-index 确保在背景图片之上
  • 内边距设置

3.3 头部样式

.header {
  display: flex;
  align-items: center;
  margin-bottom: 2.45rem;

  .header-logo {
    width: 2.13rem;
    height: 2.13rem;
    margin-right: 0.5rem;
  }

  .header-content {
    width: calc(100% - 2.65rem);
    position: relative;

    .header-title {
      font-size: 1rem;
      font-weight: 600;
      margin-bottom: 0.3rem;
      line-height: 0.88rem;
      cursor: pointer;
    }

    .header-subject {
      font-size: 0.8rem;
      font-weight: 400;
      line-height: 0.88rem;
      cursor: pointer;
    }

    .refresh {
      position: absolute;
      right: 0;
      top: 0;
      padding: 0;
      line-height: 0.75rem;
      font-size: 0.75rem;
      height: 0.75rem;
      color: #FFEF7C;
      background-color: transparent;
      border: 0;
    }
  }
}

功能解析

  • 使用 Flexbox 布局,垂直居中对齐
  • 图标固定尺寸和右边距
  • 内容区域宽度计算(总宽度减去图标宽度和边距)
  • 标题和副标题的字体样式设置
  • 刷新按钮绝对定位到右上角,透明背景和黄色文字

3.4 问题列表样式

.problem-list {
  .problem-item {
    font-size: 0.8rem;
    line-height: 0.8rem;
    margin-bottom: 0.65rem;
    cursor: pointer;
    padding-bottom: 0.1rem;

    &:last-child {
      margin-bottom: 0;
    }

    &:hover {
      text-decoration: underline;
    }
  }
}

功能解析

  • 问题项的字体大小和行高设置
  • 下边距设置,最后一项无下边距
  • 鼠标指针样式
  • 悬停时添加下划线效果

3.5 背景图片样式

.problem-image {
  position: absolute;
  bottom: 0;
  right: 1rem;
  width: 18rem;
  height: 10rem;
  z-index: 1;
  background-size: contain;
}

功能解析

  • 绝对定位到右下角
  • 固定尺寸
  • z-index 设置为 1,确保在内容下方

3.6 响应式设计

@media screen and (max-width: 750px) {
  .problem {
    .problem-content {
      .header {
        margin-bottom: 1.5rem;
      }
    }

    .problem-image {
      width: 7.94rem;
      height: 7.81rem;
      bottom: 1rem;
      right: 0.75rem;
    }
  }
}

功能解析

  • 屏幕宽度小于 750px 时应用移动端样式
  • 减少头部下边距
  • 调整背景图片尺寸和位置

数据流与状态管理

1. 全局状态

组件通过 useGlobalStore 访问全局状态,主要使用:

  • global.commonProblem:存储常见问题列表
  • global.loadCommonProblem:加载常见问题的方法

2. 数据类型

根据 api.d.ts 中的类型定义,常见问题项的数据结构为:

interface commonProblemItem {
  questionContent: string,  // 问题内容
  remark: string | null,     // 备注
}

3. 数据流向

  1. 组件挂载时,通过 global.loadCommonProblem 加载常见问题
  2. 问题数据存储在全局状态中
  3. 组件通过 global.commonProblem 访问并显示问题列表
  4. 用户点击问题时,通过 quickSend 事件将问题内容传递给父组件
  5. 用户点击刷新时,重新加载问题列表

组件通信

1. 事件发射

组件向父组件发射三种事件:

  1. finish:刷新完成时
  2. quickSend(msg):用户点击问题时,传递问题内容
  3. loaded:组件挂载完成时

2. 方法暴露

组件通过 defineExpose 暴露 refresh 方法,使父组件可以主动触发刷新:

// 父组件中可以这样调用
const commonProblemRef = ref()
commonProblemRef.value.refresh()

设计模式与最佳实践

1. Composition API

组件使用 Vue 3 的 Composition API,具有以下优势:

  • 逻辑组织更灵活,相关代码可以组合在一起
  • 更好的 TypeScript 支持
  • 更容易进行逻辑复用

2. 状态管理

使用 Pinia 进行全局状态管理:

  • 集中管理常见问题数据
  • 提供统一的数据加载方法
  • 支持回调函数处理加载完成后的逻辑

3. 响应式设计

采用媒体查询实现响应式设计:

  • 针对不同屏幕尺寸提供不同样式
  • 移动端优化显示效果
  • 使用相对单位(rem)确保在不同设备上的一致性

4. 组件设计原则

  • 单一职责:组件专注于常见问题的展示和交互
  • 可复用性:通过 props 和 events 实现与父组件的通信
  • 可测试性:暴露必要的方法便于测试

5. 用户体验优化

  • 加载状态反馈:刷新时显示加载动画
  • 视觉反馈:悬停效果、点击效果
  • 响应式布局:适应不同设备

扩展建议

1. 添加搜索功能

可以添加搜索框,允许用户搜索特定问题:

<template>
  <div class="search-box">
    <el-input v-model="searchKeyword" placeholder="搜索问题" @input="handleSearch"/>
  </div>
  <!-- 其他内容 -->
</template>

<script setup lang="ts">
const searchKeyword = ref('')

const handleSearch = () => {
  global.loadCommonProblem(searchKeyword.value)
}
</script>

2. 添加分类筛选

可以添加问题分类,允许用户按类别筛选问题:

<template>
  <div class="category-tabs">
    <el-tabs v-model="activeCategory" @tab-click="handleCategoryChange">
      <el-tab-pane label="全部" name="all"/>
      <el-tab-pane label="政策咨询" name="policy"/>
      <el-tab-pane label="办事指南" name="guide"/>
    </el-tabs>
  </div>
  <!-- 其他内容 -->
</template>

3. 添加问题详情弹窗

点击问题时,可以显示弹窗展示问题的详细内容:

<template>
  <!-- 其他内容 -->
  <el-dialog v-model="dialogVisible" title="问题详情">
    <div class="question-detail">
      <h3>{{ selectedQuestion?.questionContent }}</h3>
      <p>{{ selectedQuestion?.remark }}</p>
    </div>
  </el-dialog>
</template>

<script setup lang="ts">
const dialogVisible = ref(false)
const selectedQuestion = ref<API.commonProblemItem | null>(null)

const showQuestionDetail = (item: API.commonProblemItem) => {
  selectedQuestion.value = item
  dialogVisible.value = true
}
</script>

4. 添加问题收藏功能

允许用户收藏常用问题:

<template>
  <div class="problem-item" @click="quickSend(item.questionContent)">
    {{ index + 1 }}.{{ item.questionContent }}
    <el-icon class="favorite-icon" :class="{ 'is-favorite': isFavorite(item.id) }" @click.stop="toggleFavorite(item)">
      <StarFilled v-if="isFavorite(item.id)" />
      <Star v-else />
    </el-icon>
  </div>
</template>

总结

SsCommonProblem.vue 是一个设计良好的 Vue 3 组件,展示了现代前端开发的多个最佳实践:

  1. Composition API:使用 Vue 3 的最新特性组织代码逻辑
  2. TypeScript:提供类型安全和更好的开发体验
  3. 状态管理:使用 Pinia 进行集中式状态管理
  4. 响应式设计:适配不同设备的显示需求
  5. 组件通信:通过事件和方法暴露实现与父组件的交互
  6. 用户体验:提供加载状态、视觉反馈等细节优化

该组件不仅功能完整,而且代码结构清晰,易于维护和扩展,是学习 Vue 3 组件开发的良好示例。