<!--
  前端路径：d:\RuoYI_workspace\fad_oa\ruoyi-ui\src\views\oa\project\dashboard2\index.vue
  后端聚合接口：GET /oa/project/dashboard/{projectId}
-->
<template>
  <div class="app-container dashboard2" v-loading="pageLoading">
    <div class="layout">
      <!-- 左侧 20%：项目列表 -->
      <div class="left">
        <div class="left-search">
          <el-input
            v-model="projectQuery.keyword"
            size="small"
            clearable
            placeholder="名称 / 编号 / 代号"
            class="left-keyword-input"
            prefix-icon="el-icon-search"
            @keyup.enter.native="getProjectList"
          />
          <el-button type="primary" size="small" class="left-search-btn" @click="getProjectList">搜索</el-button>
        </div>

        <div class="project-list" v-loading="projectLoading">
          <div
            v-for="p in projectList"
            :key="p.projectId"
            class="project-item"
            :class="{ active: String(p.projectId) === String(currentProjectId) }"
            @click="handleSelectProject(p)"
            :title="p.projectName"
          >
            <div class="project-name text-ellipsis">{{ p.projectName }}</div>
            <div class="project-meta text-ellipsis">
              <span v-if="p.projectCode" class="code">{{ p.projectCode }}</span>
              <span class="time">{{ formatDate(p.beginTime) }} ~ {{ formatDate(p.finishTime) }}</span>
            </div>
          </div>

          <div v-if="!projectList || projectList.length === 0" class="left-empty">
            <el-empty :image-size="72" description="暂无项目"></el-empty>
          </div>
        </div>

        <div class="left-pager">
          <el-pagination
            small
            background
            layout="total, prev, pager, next"
            :total="projectTotal"
            :current-page.sync="projectQuery.pageNum"
            :page-size.sync="projectQuery.pageSize"
            @current-change="getProjectList"
          />
        </div>
      </div>

      <!-- 右侧 80% -->
      <div class="right">
        <div v-if="!currentProjectId" class="right-empty">
          <el-empty description="请选择项目"></el-empty>
        </div>

        <template v-else>
          <div class="panel task-panel">
            <div class="panel-header task-panel__toolbar">
              <div class="toolbar">
                <el-input
                  v-model="taskQuery.projectCode"
                  size="small"
                  clearable
                  placeholder="项目代号"
                  style="width: 140px"
                />
                <el-input
                  v-model="taskQuery.taskKeyword"
                  size="small"
                  clearable
                  placeholder="任务主题关键词"
                  style="width: 160px"
                  @keyup.enter.native="resetTaskPage"
                />
                <el-button type="primary" size="mini" @click="resetTaskPage">搜索</el-button>
                <el-button size="mini" @click="resetTaskFilters">重置</el-button>
                <el-button size="mini" @click="refreshCurrent">刷新</el-button>
              </div>
            </div>

            <div class="panel-body panel-body-flex">
              <div class="task-table-wrap">
                <vxe-table
                  ref="taskTable"
                  size="mini"
                  class="task-vxe"
                  border
                  stripe
                  show-overflow="tooltip"
                  height="auto"
                  :data="pagedTaskList"
                  :row-config="{ isHover: true }"
                >
                  <vxe-column field="projectCode" title="代号" width="72"></vxe-column>
                  <vxe-column field="taskTitle" title="任务主题" min-width="96"></vxe-column>
                  <vxe-column field="scheduleProgress" title="对应进度" min-width="112">
                    <template #default="{ row }">
                      <div v-if="scheduleProgressUnlinked(row)" class="schedule-progress-wrap">
                        <el-tag type="info" effect="plain" size="mini">未关联进度</el-tag>
                      </div>
                      <div
                        v-else
                        class="schedule-progress-wrap schedule-progress-wrap--linked"
                        :title="scheduleProgressTitle(row)"
                      >
                        <el-tag :type="scheduleStepTagType(row)" size="mini" class="schedule-progress-status-tag">
                          {{ scheduleStepStatusLabel(row) }}
                        </el-tag>
                        <span class="schedule-progress-path">{{ scheduleProgressPath(row) }}</span>
                      </div>
                    </template>
                  </vxe-column>
                  <vxe-column field="taskType" title="工作类型" width="86">
                    <template #default="{ row }">
                      <dict-tag :options="dict.type.sys_work_type" :value="row.taskType" />
                    </template>
                  </vxe-column>
                  <vxe-column field="taskGrade" title="任务级别" width="78">
                    <template #default="{ row }">
                      <dict-tag :options="dict.type.sys_sort_grade" :value="row.taskGrade" />
                    </template>
                  </vxe-column>
                  <vxe-column field="workerNickName" title="执行人" width="76">
                    <template #default="{ row }">{{ row.workerNickName || '-' }}</template>
                  </vxe-column>
                  <vxe-column field="createUserNickName" title="创建人" width="76">
                    <template #default="{ row }">{{ row.createUserNickName || '-' }}</template>
                  </vxe-column>
                  <vxe-column field="state" title="状态" width="88">
                    <template #default="{ row }">
                      <el-tag :type="getStateTagType(row.state)" size="mini">{{ stateText(row.state) }}</el-tag>
                    </template>
                  </vxe-column>
                  <vxe-column field="finishTime" title="预期结束" width="100">
                    <template #default="{ row }">{{ formatDate(row.finishTime) }}</template>
                  </vxe-column>
                </vxe-table>
              </div>

              <div class="task-pager">
                <el-pagination
                  small
                  background
                  layout="total, prev, pager, next"
                  :total="filteredTaskTotal"
                  :current-page.sync="taskQuery.pageNum"
                  :page-size.sync="taskQuery.pageSize"
                  @current-change="noop"
                />
              </div>
            </div>
          </div>

          <!-- 下：进度板块 — Tab1 进度明细表（默认）；Tab2 原思维导图 + 图例 -->
          <div class="panel schedule-panel">
            <el-tabs v-model="scheduleViewTab" class="schedule-panel-tabs">
              <el-tab-pane label="进度明细" name="list">
                <!-- 进度数据：表格自然撑开高度，由 .progress-table-scroll 单独承担纵向/横向滚动 -->
                <div class="progress-table-pane" v-loading="xmindLoading">
                  <el-empty
                    v-if="!stepList || stepList.length === 0"
                    :image-size="72"
                    description="暂无进度步骤数据"
                  />
                  <div v-else class="progress-table-scroll">
                    <el-table
                      :data="scheduleStepsForTable"
                      size="mini"
                      border
                      stripe
                      class="progress-step-el-table"
                    >
                      <el-table-column prop="firstLevelNode" label="一级节点" min-width="112" show-overflow-tooltip />
                      <el-table-column prop="secondLevelNode" label="二级节点" min-width="112" show-overflow-tooltip />
                      <el-table-column prop="stepName" label="步骤名称" min-width="128" show-overflow-tooltip />
                      <el-table-column label="状态" width="92" align="center">
                        <template slot-scope="{ row }">
                          <el-tag :type="stepStatusTagType(row)" size="mini">{{ stepStatusLabel(row) }}</el-tag>
                        </template>
                      </el-table-column>
                      <el-table-column label="计划开始" width="108" align="center">
                        <template slot-scope="{ row }">{{ formatStepPlanStart(row) }}</template>
                      </el-table-column>
                      <el-table-column label="计划结束" width="108" align="center">
                        <template slot-scope="{ row }">{{ formatStepPlanEnd(row) }}</template>
                      </el-table-column>
                      <el-table-column label="负责人" min-width="96" show-overflow-tooltip>
                        <template slot-scope="{ row }">{{ formatStepResponsible(row) }}</template>
                      </el-table-column>
                    </el-table>
                  </div>
                </div>
              </el-tab-pane>
              <el-tab-pane label="进度导图" name="mind">
                <div class="schedule-tab-pane-inner schedule-tab-pane-inner--mind">
                  <div class="schedule-mind">
                    <div class="xmind-wrap" v-loading="xmindLoading">
                      <div v-if="xmindEmpty" class="xmind-empty">
                        <el-empty :image-size="80" description="暂无进度步骤数据"></el-empty>
                      </div>
                      <xmind
                        v-else-if="scheduleViewTab === 'mind'"
                        :list="stepList"
                        height="100%"
                        dashboard-mode
                        @refresh="onXmindRefresh"
                      />
                    </div>
                    <aside v-if="scheduleViewTab === 'mind' && !xmindEmpty && stepList && stepList.length" class="mind-legend-aside">
                      <div class="mind-legend-title">状态图例</div>
                      <div class="mind-legend-row">
                        <i class="lg-dot lg-dot--done" />
                        <span>已完成</span>
                      </div>
                      <div class="mind-legend-row">
                        <i class="lg-dot lg-dot--doing" />
                        <span>进行中 / 待验收</span>
                      </div>
                      <div class="mind-legend-row">
                        <i class="lg-dot lg-dot--todo" />
                        <span>未开始 / 暂停</span>
                      </div>
                      <p class="mind-legend-tip">连线：已完成节点为绿色；可滚轮缩放、拖动画布。</p>
                    </aside>
                  </div>
                </div>
              </el-tab-pane>
            </el-tabs>
          </div>
        </template>
      </div>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
import Xmind from '@/views/oa/project/pace/components/xmind.vue'

export default {
  name: 'OaProjectDashboard2',
  components: { Xmind },
  dicts: ['sys_work_type', 'sys_sort_grade'],
  data () {
    return {
      pageLoading: false,
      projectLoading: false,
      xmindLoading: false,
      /** 进度板块：默认进度明细表，切换为进度导图（原思维导图） */
      scheduleViewTab: 'list'
    }
  },
  computed: {
    ...mapState('oaProjectDashboard2', [
      'currentProjectId',
      'projectQuery',
      'projectList',
      'projectTotal',
      'taskQuery',
      'taskList',
      'stepList'
    ]),
    filteredTaskList () {
      const list = this.taskList || []
      const code = (this.taskQuery.projectCode || '').trim().toLowerCase()
      const kw = (this.taskQuery.taskKeyword || '').trim().toLowerCase()
      return list.filter(r => {
        const okCode = !code || String(r.projectCode || '').toLowerCase().indexOf(code) !== -1
        const title = String(r.taskTitle || '').toLowerCase()
        const okKw = !kw || title.indexOf(kw) !== -1
        return okCode && okKw
      })
    },
    filteredTaskTotal () {
      return (this.filteredTaskList || []).length
    },
    pagedTaskList () {
      const pageNum = Number(this.taskQuery.pageNum || 1)
      const pageSize = Number(this.taskQuery.pageSize || 10)
      const start = (pageNum - 1) * pageSize
      return (this.filteredTaskList || []).slice(start, start + pageSize)
    },
    xmindEmpty () {
      return !this.stepList || this.stepList.length === 0
    },
    /** 进度明细表：按步骤序号排序，便于阅读 */
    scheduleStepsForTable () {
      const list = this.stepList || []
      return [...list].sort((a, b) => {
        const oa = Number(a.stepOrder != null ? a.stepOrder : 0)
        const ob = Number(b.stepOrder != null ? b.stepOrder : 0)
        return oa - ob
      })
    }
  },
  created () {
    this.getProjectList()
  },
  methods: {
    async getProjectList () {
      this.projectLoading = true
      try {
        await this.$store.dispatch('oaProjectDashboard2/fetchProjectList', {
          pageNum: this.projectQuery.pageNum,
          pageSize: this.projectQuery.pageSize,
          keyword: this.projectQuery.keyword
        })
      } catch (e) {
        this.$message.error('项目列表加载失败，请稍后重试')
      } finally {
        this.projectLoading = false
      }
    },

    async handleSelectProject (p) {
      const projectId = p && p.projectId
      if (!projectId) return
      this.scheduleViewTab = 'list'
      this.pageLoading = true
      this.xmindLoading = true
      try {
        this.$store.commit('oaProjectDashboard2/SET_TASK_QUERY', { pageNum: 1 })
        await this.$store.dispatch('oaProjectDashboard2/selectProject', projectId)
      } catch (e) {
        this.$message.error((e && e.message) || '加载项目数据失败，请稍后重试')
      } finally {
        this.pageLoading = false
        this.xmindLoading = false
      }
    },

    resetTaskPage () {
      this.$store.commit('oaProjectDashboard2/SET_TASK_QUERY', { pageNum: 1 })
    },

    resetTaskFilters () {
      this.$store.commit('oaProjectDashboard2/SET_TASK_QUERY', {
        pageNum: 1,
        pageSize: 10,
        projectCode: '',
        taskKeyword: ''
      })
    },

    async refreshCurrent () {
      if (!this.currentProjectId) return
      this.pageLoading = true
      this.xmindLoading = true
      try {
        await this.$store.dispatch('oaProjectDashboard2/selectProject', this.currentProjectId)
      } catch (e) {
        this.$message.error('刷新失败，请稍后重试')
      } finally {
        this.pageLoading = false
        this.xmindLoading = false
      }
    },

    /** 计划开始：库表 plan_start 可能为空，与进度页一致回退 start_time */
    formatStepPlanStart (row) {
      const raw = row && (row.planStart != null && row.planStart !== '' ? row.planStart : row.startTime)
      return this.formatDateFlexible(raw)
    },

    formatStepPlanEnd (row) {
      const raw = row && (row.planEnd != null && row.planEnd !== '' ? row.planEnd : row.endTime)
      return this.formatDateFlexible(raw)
    },

    formatDateFlexible (val) {
      if (val == null || val === '') return '-'
      if (Array.isArray(val) && val.length >= 3) {
        const y = val[0]
        const mo = String(val[1]).padStart(2, '0')
        const d = String(val[2]).padStart(2, '0')
        return `${y}-${mo}-${d}`
      }
      if (typeof val === 'string') {
        const s = val.trim()
        if (!s) return '-'
        if (s.length >= 10 && s[4] === '-' && s[7] === '-') return s.substring(0, 10)
        const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
        return m ? m[1] : s
      }
      if (val instanceof Date && !isNaN(val.getTime())) {
        const y = val.getFullYear()
        const mo = String(val.getMonth() + 1).padStart(2, '0')
        const d = String(val.getDate()).padStart(2, '0')
        return `${y}-${mo}-${d}`
      }
      return '-'
    },

    /** 负责人：业务侧常用 node_header，兼容 header */
    formatStepResponsible (row) {
      if (!row) return '-'
      const h = row.header != null && String(row.header).trim() !== '' ? String(row.header).trim() : ''
      if (h) return h
      const n = row.nodeHeader != null && String(row.nodeHeader).trim() !== '' ? String(row.nodeHeader).trim() : ''
      return n || '-'
    },

    async onXmindRefresh () {
      await this.refreshCurrent()
    },

    noop () {},

    /** 无 track_id 或无步骤名称 → 未关联（后端联表 oa_project_schedule_step，use_flag=1） */
    scheduleProgressUnlinked (row) {
      if (!row || row.trackId == null || row.trackId === '') return true
      const n = row.scheduleStepName
      return n == null || String(n).trim() === ''
    },
    scheduleStepTagType (row) {
      const s = row && row.scheduleStatus
      if (s === null || s === undefined) return 'info'
      const v = Number(s)
      if (v === 2) return 'success'
      if (v === 3) return 'warning'
      if (v === 1) return 'primary'
      if (v === 0) return 'info'
      return 'info'
    },
    scheduleStepStatusLabel (row) {
      const s = row && row.scheduleStatus
      if (s === null || s === undefined) return '—'
      const v = Number(s)
      if (v === 2) return '已完成'
      if (v === 1) return '进行中'
      if (v === 3) return '暂停'
      if (v === 0) return '未开始'
      return '—'
    },
    scheduleProgressPath (row) {
      if (!row) return ''
      const n = row.scheduleStepName
      return (n != null && String(n).trim() !== '') ? String(n).trim() : ''
    },
    scheduleProgressTitle (row) {
      return `${this.scheduleStepStatusLabel(row)} ${this.scheduleProgressPath(row)}`.trim()
    },

    /** oa_project_schedule_step.status：与任务表「对应进度」标签一致 */
    stepStatusLabel (row) {
      return this.scheduleStepStatusLabel({ scheduleStatus: row && row.status })
    },
    stepStatusTagType (row) {
      return this.scheduleStepTagType({ scheduleStatus: row && row.status })
    },

    formatDate (val) {
      if (!val) return '-'
      const s = String(val)
      return s.length >= 10 ? s.substring(0, 10) : s
    },

    stateText (val) {
      if (val === null || val === undefined || val === '') return '-'
      const v = Number(val)
      if (v === 2) return '执行完成'
      if (v === 1) return '待验收'
      if (v === 15) return '延期申请中'
      if (v === 0) return '进行中'
      return '其他'
    },
    getStateTagType (val) {
      if (val === null || val === undefined || val === '') return 'info'
      const v = Number(val)
      if (v === 2) return 'success'
      if (v === 1) return 'warning'
      if (v === 15) return 'warning'
      if (v === 0) return 'info'
      return 'info'
    }
  }
}
</script>

<style scoped>
.dashboard2 {
  font-size: 13px;
  color: #606266;
}

.layout {
  display: flex;
  gap: 8px;
  align-items: stretch;
  height: calc(100vh - 120px);
  min-height: 0;
  max-height: calc(100vh - 120px);
}

.left {
  width: 20%;
  flex: 0 0 20%;
  min-width: 220px;
  max-width: 300px;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: 10px;
  box-sizing: border-box;
  background: #fafbfc;
}

.left-search {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.left-keyword-input {
  flex: 1;
  min-width: 0;
}

.left-search-btn {
  flex-shrink: 0;
  padding-left: 14px;
  padding-right: 14px;
}

.project-list {
  flex: 1;
  overflow: auto;
  min-height: 0;
}

.project-item {
  padding: 6px 8px;
  cursor: pointer;
  border-bottom: 1px solid #eef0f3;
  border-radius: 4px;
  margin-bottom: 2px;
  min-height: 42px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  transition: background 0.15s ease;
}

.project-item:hover {
  background: #f0f2f5;
}

.project-item.active {
  background: #ecf5ff;
  border-left: 3px solid #409eff;
  padding-left: 5px;
  box-shadow: inset 0 0 0 1px rgba(64, 158, 255, 0.15);
}

.project-name {
  font-weight: 500;
  font-size: 12px;
  line-height: 1.35;
  color: #303133;
}

.project-meta {
  font-size: 11px;
  color: #909399;
  line-height: 1.35;
  margin-top: 3px;
}

.project-meta .code {
  margin-right: 8px;
  color: #409eff;
}

.left-pager {
  padding-top: 8px;
  margin-top: 4px;
  border-top: 1px solid #ebeef5;
}

.left-pager :deep(.el-pagination) {
  justify-content: center;
}

.left-empty {
  padding: 10px;
}

.right {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 100%;
}

.right-empty {
  border: 1px dashed #dcdfe6;
  border-radius: 4px;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.panel {
  border: 1px solid #ebeef5;
  border-radius: 4px;
  overflow: hidden;
  background: #fff;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.panel-header {
  padding: 8px 12px;
  border-bottom: 1px solid #ebeef5;
}

/* 原「任务列表」标题条去掉后，工具栏区沿用顶栏灰底，与下方表格白底区分不变 */
.task-panel__toolbar {
  background: #fafafa;
}

.panel-body {
  padding: 8px 12px;
}

.panel-body-flex {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}

.task-panel {
  flex: 4 1 0%;
  min-height: 0;
}

.schedule-panel {
  flex: 6 1 0%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
}

.schedule-panel-tabs {
  flex: 1 1 0%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  
}

/* Element Tabs 根节点参与纵向 flex，才能把剩余高度交给内容区 */
.schedule-panel-tabs :deep(.el-tabs) {
  flex: 1 1 0%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.schedule-panel-tabs :deep(.el-tabs__header) {
  margin-bottom: 0;
  padding: 0 12px;
  flex-shrink: 0;
}

.schedule-panel-tabs :deep(.el-tabs__content) {
  flex: 1 1 0%;
  min-height: 0;
  overflow: hidden;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* 当前激活的 pane 占满内容区高度，子元素才能算出可滚动区域 */
.schedule-panel-tabs :deep(.el-tab-pane) {
  flex: 1 1 0%;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.schedule-tab-pane-inner {
  flex: 1 1 0%;
  min-height: 0;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
}

.schedule-tab-pane-inner--mind {
  min-height: 280px;
  overflow: hidden;
}

/* 进度明细 Tab：占满下方板块；内部仅滚动区参与滚动 */
.progress-table-pane {
  flex: 1 1 0%;
  min-height: 0;
  width: 100%;
  height:300px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 8px 12px 10px;
  
}

.progress-table-scroll {
  flex: 1 1 0%;
  min-height: 0;
  min-width: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

.progress-step-el-table {
  width: 100%;
}

.schedule-mind {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  flex: 1;
  min-height: 0;
}

.task-table-wrap {
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}

.task-table-wrap :deep(.vxe-table) {
  font-size: 12px;
  width: 100%;
}

.task-table-wrap :deep(.vxe-table--body-wrapper),
.task-table-wrap :deep(.vxe-table--header-wrapper) {
  overflow-x: hidden;
}

.schedule-progress-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  line-height: 1.35;
}

.schedule-progress-wrap--linked {
  min-width: 0;
}

.schedule-progress-status-tag {
  flex-shrink: 0;
}

.schedule-progress-path {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: #606266;
}

.task-pager {
  margin-top: 8px;
  display: flex;
  justify-content: flex-end;
}

.xmind-wrap {
  flex: 1;
  min-width: 0;
  position: relative;
  padding: 0;
  display: flex;
  flex-direction: column;
  min-height: 320px;
}

.xmind-wrap :deep(.xmind-box--dashboard) {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.xmind-wrap :deep(.xmind-container) {
  flex: 1;
  min-height: 300px !important;
}

.mind-legend-aside {
  flex: 0 0 132px;
  padding: 10px 12px;
  border-left: 1px solid #ebeef5;
  background: #fafafa;
  font-size: 12px;
  color: #606266;
  line-height: 1.5;
}

.mind-legend-title {
  font-weight: 600;
  color: #303133;
  margin-bottom: 10px;
  font-size: 13px;
}

.mind-legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.lg-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.lg-dot--done {
  background: #67c23a;
}

.lg-dot--doing {
  background: #e6a23c;
}

.lg-dot--todo {
  background: #909399;
}

.mind-legend-tip {
  margin: 12px 0 0;
  font-size: 11px;
  color: #909399;
  line-height: 1.45;
}

.xmind-empty {
  height: 100%;
  min-height: 240px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed #ebeef5;
  border-radius: 4px;
}

.text-ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>
