|
@@ -1,14 +1,53 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="expert-step-table">
|
|
<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>
|
|
</div>
|
|
|
- </header> -->
|
|
|
|
|
|
|
|
|
|
- <el-card>
|
|
|
|
|
<div
|
|
<div
|
|
|
- v-for="(row, rowIndex) in tableData"
|
|
|
|
|
|
|
+ v-for="(row, rowIndex) in filteredTableData"
|
|
|
:key="row.teamName"
|
|
:key="row.teamName"
|
|
|
class="ladder-row"
|
|
class="ladder-row"
|
|
|
:style="{ width: row.tableWidth, '--row-bg': row.backgroundColor }"
|
|
:style="{ width: row.tableWidth, '--row-bg': row.backgroundColor }"
|
|
@@ -21,7 +60,7 @@
|
|
|
<table class="content-table" :aria-label="row.teamName">
|
|
<table class="content-table" :aria-label="row.teamName">
|
|
|
<colgroup>
|
|
<colgroup>
|
|
|
<col
|
|
<col
|
|
|
- v-for="(_, index) in row.columnWidths"
|
|
|
|
|
|
|
+ v-for="(_, index) in row.columnWidths || []"
|
|
|
:key="index"
|
|
:key="index"
|
|
|
:style="{ width: `${getColumnPercent(row, index)}%` }"
|
|
:style="{ width: `${getColumnPercent(row, index)}%` }"
|
|
|
/>
|
|
/>
|
|
@@ -30,7 +69,7 @@
|
|
|
<tbody>
|
|
<tbody>
|
|
|
<tr>
|
|
<tr>
|
|
|
<td
|
|
<td
|
|
|
- v-for="(cell, index) in row.cells"
|
|
|
|
|
|
|
+ v-for="(cell, index) in row.cells || []"
|
|
|
:key="index"
|
|
:key="index"
|
|
|
:class="['step-cell', cell.tone === 'danger' ? 'notice-cell' : '']"
|
|
:class="['step-cell', cell.tone === 'danger' ? 'notice-cell' : '']"
|
|
|
>
|
|
>
|
|
@@ -67,7 +106,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref } from 'vue'
|
|
|
|
|
|
|
+import { computed, ref } from 'vue'
|
|
|
|
|
|
|
|
type CellItem =
|
|
type CellItem =
|
|
|
| {
|
|
| {
|
|
@@ -82,6 +121,8 @@ type CellItem =
|
|
|
|
|
|
|
|
type TableRow = {
|
|
type TableRow = {
|
|
|
teamName: string
|
|
teamName: string
|
|
|
|
|
+ education: string
|
|
|
|
|
+ searchText: string
|
|
|
tableWidth: string
|
|
tableWidth: string
|
|
|
backgroundColor: string
|
|
backgroundColor: string
|
|
|
columnWidths: number[]
|
|
columnWidths: number[]
|
|
@@ -91,6 +132,8 @@ type TableRow = {
|
|
|
const tableData: TableRow[] = [
|
|
const tableData: TableRow[] = [
|
|
|
{
|
|
{
|
|
|
teamName: '第一梯队',
|
|
teamName: '第一梯队',
|
|
|
|
|
+ education: '博士',
|
|
|
|
|
+ searchText: '朱桂林 软件工程 50项成果',
|
|
|
tableWidth: '64%',
|
|
tableWidth: '64%',
|
|
|
backgroundColor: '#eef6ff',
|
|
backgroundColor: '#eef6ff',
|
|
|
columnWidths: [1.5, 1.5, 1.5, 1.5, 1.5],
|
|
columnWidths: [1.5, 1.5, 1.5, 1.5, 1.5],
|
|
@@ -122,6 +165,8 @@ const tableData: TableRow[] = [
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
teamName: '第二梯队',
|
|
teamName: '第二梯队',
|
|
|
|
|
+ education: '硕士',
|
|
|
|
|
+ searchText: '孙乐臻 金融风险 30项业绩',
|
|
|
tableWidth: '75%',
|
|
tableWidth: '75%',
|
|
|
backgroundColor: '#f2faef',
|
|
backgroundColor: '#f2faef',
|
|
|
columnWidths: [1.8, 1.8, 1.8, 1.8, 1.8],
|
|
columnWidths: [1.8, 1.8, 1.8, 1.8, 1.8],
|
|
@@ -135,6 +180,8 @@ const tableData: TableRow[] = [
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
teamName: '第三梯队',
|
|
teamName: '第三梯队',
|
|
|
|
|
+ education: '本科',
|
|
|
|
|
+ searchText: '王军 互联网产品 20项成果',
|
|
|
tableWidth: '100%',
|
|
tableWidth: '100%',
|
|
|
backgroundColor: '#fff8e8',
|
|
backgroundColor: '#fff8e8',
|
|
|
columnWidths: [2, 2, 2, 2, 2],
|
|
columnWidths: [2, 2, 2, 2, 2],
|
|
@@ -167,17 +214,77 @@ const tableData: TableRow[] = [
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
const selectedMember = ref('')
|
|
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 cellBookmarks = ['技术', '经营', '商务', '安全', '法务']
|
|
|
|
|
|
|
|
const getColumnPercent = (row: TableRow, index: number) => {
|
|
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)
|
|
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) => {
|
|
const handleMemberClick = (member: string) => {
|
|
|
selectedMember.value = member
|
|
selectedMember.value = member
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+const handleWordClick = (word: string) => {
|
|
|
|
|
+ searchQuery.value = word
|
|
|
|
|
+}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|
|
@@ -188,7 +295,7 @@ const handleMemberClick = (member: string) => {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
max-width: none;
|
|
max-width: none;
|
|
|
margin: 0 auto;
|
|
margin: 0 auto;
|
|
|
- padding: 34px;
|
|
|
|
|
|
|
+ // padding: 34px;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
@@ -199,6 +306,179 @@ const handleMemberClick = (member: string) => {
|
|
|
width: 100%;
|
|
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 {
|
|
.table-intro {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: flex-end;
|
|
align-items: flex-end;
|
|
@@ -488,6 +768,14 @@ const handleMemberClick = (member: string) => {
|
|
|
gap: 18px;
|
|
gap: 18px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .search-layout {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .word-cloud {
|
|
|
|
|
+ min-height: 112px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.intro-meta {
|
|
.intro-meta {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|