yanghao преди 1 месец
родител
ревизия
44ed696870
променени са 1 файла, в които са добавени 300 реда и са изтрити 12 реда
  1. 300 12
      src/views/expert/list/index.vue

+ 300 - 12
src/views/expert/list/index.vue

@@ -1,14 +1,53 @@
 <template>
   <div class="expert-step-table">
-    <!-- <header class="table-intro">
-      <div class="intro-copy">
-        <h1>专家梯队</h1>
+    <el-card>
+      <div class="search-layout">
+        <section class="filter-panel">
+          <div class="filter-fields">
+            <el-input v-model="searchQuery" placeholder="姓名 / 专业 / 业绩" clearable />
+
+            <el-select v-model="selectedEducation" placeholder="选择学历" clearable>
+              <el-option
+                v-for="option in educationOptions"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+              />
+            </el-select>
+
+            <el-select v-model="selectedTeam" placeholder="选择梯队" clearable>
+              <el-option
+                v-for="option in teamOptions"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+              />
+            </el-select>
+          </div>
+        </section>
+
+        <aside class="word-cloud-panel">
+          <div class="word-cloud">
+            <button
+              v-for="word in wordCloudWords"
+              :key="word.text"
+              type="button"
+              class="cloud-word"
+              :style="{
+                fontSize: `${word.size}px`,
+                color: word.color,
+                transform: `rotate(${word.rotate}deg)`
+              }"
+              @click="handleWordClick(word.text)"
+            >
+              {{ word.text }}
+            </button>
+          </div>
+        </aside>
       </div>
-    </header> -->
 
-    <el-card>
       <div
-        v-for="(row, rowIndex) in tableData"
+        v-for="(row, rowIndex) in filteredTableData"
         :key="row.teamName"
         class="ladder-row"
         :style="{ width: row.tableWidth, '--row-bg': row.backgroundColor }"
@@ -21,7 +60,7 @@
         <table class="content-table" :aria-label="row.teamName">
           <colgroup>
             <col
-              v-for="(_, index) in row.columnWidths"
+              v-for="(_, index) in row.columnWidths || []"
               :key="index"
               :style="{ width: `${getColumnPercent(row, index)}%` }"
             />
@@ -30,7 +69,7 @@
           <tbody>
             <tr>
               <td
-                v-for="(cell, index) in row.cells"
+                v-for="(cell, index) in row.cells || []"
                 :key="index"
                 :class="['step-cell', cell.tone === 'danger' ? 'notice-cell' : '']"
               >
@@ -67,7 +106,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { computed, ref } from 'vue'
 
 type CellItem =
   | {
@@ -82,6 +121,8 @@ type CellItem =
 
 type TableRow = {
   teamName: string
+  education: string
+  searchText: string
   tableWidth: string
   backgroundColor: string
   columnWidths: number[]
@@ -91,6 +132,8 @@ type TableRow = {
 const tableData: TableRow[] = [
   {
     teamName: '第一梯队',
+    education: '博士',
+    searchText: '朱桂林 软件工程 50项成果',
     tableWidth: '64%',
     backgroundColor: '#eef6ff',
     columnWidths: [1.5, 1.5, 1.5, 1.5, 1.5],
@@ -122,6 +165,8 @@ const tableData: TableRow[] = [
   },
   {
     teamName: '第二梯队',
+    education: '硕士',
+    searchText: '孙乐臻 金融风险 30项业绩',
     tableWidth: '75%',
     backgroundColor: '#f2faef',
     columnWidths: [1.8, 1.8, 1.8, 1.8, 1.8],
@@ -135,6 +180,8 @@ const tableData: TableRow[] = [
   },
   {
     teamName: '第三梯队',
+    education: '本科',
+    searchText: '王军 互联网产品 20项成果',
     tableWidth: '100%',
     backgroundColor: '#fff8e8',
     columnWidths: [2, 2, 2, 2, 2],
@@ -167,17 +214,77 @@ const tableData: TableRow[] = [
 ]
 
 const selectedMember = ref('')
+const searchQuery = ref('')
+const selectedEducation = ref('')
+const selectedTeam = ref('')
+
+const wordCloudWords = [
+  { text: '软件工程', size: 28, rotate: -4, color: '#315b73' },
+  { text: '金融风险', size: 22, rotate: 3, color: '#a15e3f' },
+  { text: '互联网产品', size: 18, rotate: -2, color: '#607d70' },
+  { text: '项目管理', size: 16, rotate: 5, color: '#6d7480' },
+  { text: '数字化转型', size: 24, rotate: 1, color: '#406b82' },
+  { text: '战略规划', size: 15, rotate: -5, color: '#9a755d' },
+  { text: '数据治理', size: 19, rotate: 2, color: '#72876f' },
+  { text: '风险控制', size: 14, rotate: -1, color: '#78818a' }
+]
+
+const educationOptions = [
+  { label: '全部', value: '' },
+  { label: '博士', value: '博士' },
+  { label: '硕士', value: '硕士' },
+  { label: '本科', value: '本科' },
+  { label: '专科', value: '专科' }
+]
+
+const teamOptions = [
+  { label: '全部', value: '' },
+  { label: '第一梯队', value: '第一梯队' },
+  { label: '第二梯队', value: '第二梯队' },
+  { label: '第三梯队', value: '第三梯队' }
+]
+
 const cellBookmarks = ['技术', '经营', '商务', '安全', '法务']
 
 const getColumnPercent = (row: TableRow, index: number) => {
+  if (!row.columnWidths || row.columnWidths.length === 0) {
+    return '0'
+  }
   const total = row.columnWidths.reduce((sum, width) => sum + width, 0)
-
-  return ((row.columnWidths[index] / total) * 100).toFixed(4)
+  const value = row.columnWidths[index] ?? 0
+  if (total === 0) {
+    return '0'
+  }
+  return ((value / total) * 100).toFixed(4)
 }
 
+const filteredTableData = computed(() => {
+  return tableData.filter((row) => {
+    const query = searchQuery.value.trim().toLowerCase()
+    const nameMatch = row.searchText.toLowerCase().includes(query)
+    const teamMatch = row.teamName.toLowerCase().includes(query)
+    const educationMatch = selectedEducation.value
+      ? row.education === selectedEducation.value
+      : true
+    const selectedTeamMatch = selectedTeam.value ? row.teamName === selectedTeam.value : true
+
+    if (query && !nameMatch && !teamMatch) {
+      return false
+    }
+    if (!educationMatch || !selectedTeamMatch) {
+      return false
+    }
+    return true
+  })
+})
+
 const handleMemberClick = (member: string) => {
   selectedMember.value = member
 }
+
+const handleWordClick = (word: string) => {
+  searchQuery.value = word
+}
 </script>
 
 <style scoped lang="scss">
@@ -188,7 +295,7 @@ const handleMemberClick = (member: string) => {
   width: 100%;
   max-width: none;
   margin: 0 auto;
-  padding: 34px;
+  // padding: 34px;
   overflow: hidden;
   box-sizing: border-box;
 
@@ -199,6 +306,179 @@ const handleMemberClick = (member: string) => {
   width: 100%;
 }
 
+.search-layout {
+  display: grid;
+  grid-template-columns: minmax(220px, 0.34fr) minmax(0, 1fr);
+  gap: 18px;
+  margin-bottom: 26px;
+}
+
+.filter-panel,
+.word-cloud-panel {
+  min-width: 0;
+  padding: 22px;
+  border: 1px solid rgba(28, 44, 62, 0.1);
+  border-radius: 12px;
+  box-sizing: border-box;
+}
+
+.filter-panel {
+  background: linear-gradient(160deg, rgba(237, 245, 249, 0.95), rgba(247, 249, 248, 0.9)), #f4f7f8;
+}
+
+.word-cloud-panel {
+  position: relative;
+  overflow: hidden;
+  background: radial-gradient(circle at 84% 20%, rgba(177, 205, 210, 0.3), transparent 32%), #f8f7f2;
+}
+
+.word-cloud-panel::after {
+  position: absolute;
+  right: -70px;
+  bottom: -90px;
+  width: 220px;
+  height: 220px;
+  border: 1px solid rgba(161, 94, 63, 0.16);
+  border-radius: 50%;
+  content: '';
+  box-shadow:
+    0 0 0 20px rgba(161, 94, 63, 0.04),
+    0 0 0 42px rgba(161, 94, 63, 0.03);
+  pointer-events: none;
+}
+
+.panel-kicker {
+  margin-bottom: 8px;
+  color: #8a98a5;
+  font-family: 'Courier New', monospace;
+  font-size: 10px;
+  font-weight: 700;
+  letter-spacing: 0.14em;
+}
+
+.filter-panel h2,
+.word-cloud-panel h2 {
+  margin: 0;
+  color: var(--ink);
+  font-size: 20px;
+  font-weight: 700;
+  letter-spacing: -0.03em;
+}
+
+.panel-description {
+  margin: 8px 0 18px;
+  color: var(--muted-ink);
+  font-size: 12px;
+  line-height: 1.65;
+}
+
+.filter-fields {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.filter-fields :deep(.el-input__wrapper),
+.filter-fields :deep(.el-select__wrapper) {
+  min-height: 38px;
+  border-radius: 7px;
+  background: rgba(255, 255, 255, 0.78);
+  box-shadow: 0 0 0 1px rgba(28, 44, 62, 0.09) inset;
+}
+
+.filter-fields :deep(.el-input__wrapper.is-focus),
+.filter-fields :deep(.el-select__wrapper.is-focused) {
+  box-shadow: 0 0 0 1px #467da4 inset;
+}
+
+.filter-status {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 18px;
+  padding-top: 14px;
+  border-top: 1px dashed rgba(28, 44, 62, 0.15);
+  color: #718092;
+  font-size: 11px;
+}
+
+.status-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: #6f9878;
+  box-shadow: 0 0 0 4px rgba(111, 152, 120, 0.13);
+}
+
+.cloud-heading {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 20px;
+}
+
+.cloud-hint {
+  padding-top: 18px;
+  color: #9aa6b3;
+  font-size: 11px;
+}
+
+.word-cloud {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  min-height: 132px;
+  align-items: center;
+  justify-content: center;
+  gap: 10px 16px;
+  padding: 22px 8px 4px;
+  flex-wrap: wrap;
+}
+
+.cloud-word {
+  border: 0;
+  padding: 0;
+  background: transparent;
+  cursor: pointer;
+  font-family: Georgia, 'Times New Roman', serif;
+  font-weight: 600;
+  letter-spacing: -0.03em;
+  transition:
+    transform 180ms ease,
+    opacity 180ms ease,
+    text-shadow 180ms ease;
+}
+
+.cloud-word:hover {
+  opacity: 0.72;
+  text-shadow: 0 5px 12px rgba(49, 91, 115, 0.18);
+}
+
+.cloud-word:focus-visible {
+  outline: 2px solid #a15e3f;
+  outline-offset: 5px;
+}
+
+.table-divider {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin: 4px 2px 14px;
+  color: var(--ink);
+  font-size: 14px;
+  font-weight: 700;
+}
+
+.result-count {
+  color: #9aa6b3;
+  font-family: 'Courier New', monospace;
+  font-size: 10px;
+  letter-spacing: 0.1em;
+  text-transform: uppercase;
+}
+
 .table-intro {
   display: flex;
   align-items: flex-end;
@@ -488,6 +768,14 @@ const handleMemberClick = (member: string) => {
     gap: 18px;
   }
 
+  .search-layout {
+    grid-template-columns: 1fr;
+  }
+
+  .word-cloud {
+    min-height: 112px;
+  }
+
   .intro-meta {
     width: 100%;
     justify-content: space-between;