435 lines
13 KiB
Vue
435 lines
13 KiB
Vue
<template>
|
||
<div class="scholar-db-container">
|
||
<div class="crumbs">
|
||
<el-breadcrumb separator="/">
|
||
<el-breadcrumb-item> <i class="el-icon-user"></i> Scholar Database </el-breadcrumb-item>
|
||
</el-breadcrumb>
|
||
</div>
|
||
<div class="toolbar">
|
||
<div class="filters">
|
||
<el-form :inline="true" :model="query" size="small">
|
||
<el-form-item label="">
|
||
<el-cascader
|
||
ref="cascader"
|
||
@change="handleChange(index)"
|
||
v-model="major_id"
|
||
:placeholder="'Please select field'"
|
||
:options="options"
|
||
:props="getProps()"
|
||
style="width: 260px"
|
||
></el-cascader>
|
||
</el-form-item>
|
||
<el-form-item label="">
|
||
<el-input v-model="query.keyword" placeholder="Name / Email / Affiliation" clearable style="width: 260px" />
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-search" :loading="loading" @click="handleSearch"> Search </el-button>
|
||
<el-button @click="handleReset">Reset</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<div class="actions">
|
||
<el-button type="success" icon="el-icon-download" @click="handleExport" :loading="exportLoading">
|
||
Download Excel
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-card class="table-card" shadow="never">
|
||
<div class="source-tag">Data sourced from PubMed</div>
|
||
<el-table :data="list" border stripe v-loading="loading" header-row-class-name="dark-table-header">
|
||
<el-table-column prop="name" label="Base Information" min-width="220">
|
||
<template slot-scope="scope">
|
||
<div>
|
||
<p class="info-row">
|
||
<span class="label">Name: </span><span class="value bold">{{ scope.row.name }}</span>
|
||
</p>
|
||
<p class="info-row">
|
||
<span class="label">Email: </span><span class="value link">{{ scope.row.email }}</span>
|
||
</p>
|
||
<p class="info-row" style="margin-top: 10px; font-size: 12px">
|
||
<span class="label">Acquisition Time:</span>
|
||
<span class="value time">{{ scope.row.ctime_text ? scope.row.ctime_text : '-' }}</span>
|
||
</p>
|
||
|
||
<span class="custom-tag">{{ scope.row.state_text }}</span>
|
||
</div>
|
||
</template></el-table-column
|
||
>
|
||
|
||
<el-table-column prop="affiliation" label="Affiliation" min-width="260" />
|
||
<el-table-column prop="fieldDisplay" label="Research areas" min-width="200">
|
||
<template slot-scope="scope">
|
||
<div v-for="(field, index) in scope.row.fields" :key="index">
|
||
<span
|
||
><span style="color: #006699">{{ index + 1 }}.</span> {{ field.field }}</span
|
||
>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="pagination">
|
||
<el-pagination
|
||
background
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:current-page="query.pageIndex"
|
||
:page-size="query.pageSize"
|
||
:page-sizes="[10, 20, 50]"
|
||
:total="total"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handlePageChange"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import Common from '@/components/common/common';
|
||
export default {
|
||
name: 'scholarCrawlers',
|
||
data() {
|
||
return {
|
||
mediaUrl: Common.mediaUrl,
|
||
major_id: [],
|
||
majors: [],
|
||
query: {
|
||
major_id: null,
|
||
keyword: '',
|
||
|
||
pageIndex: 1,
|
||
pageSize: 10
|
||
},
|
||
list: [],
|
||
total: 0,
|
||
loading: false,
|
||
exportLoading: false
|
||
};
|
||
},
|
||
created() {
|
||
this.loadFields();
|
||
this.fetchList();
|
||
},
|
||
methods: {
|
||
loadFields() {
|
||
this.$api.post('api/Major/getMajorList', {}).then((res) => {
|
||
const transformData = (data) => {
|
||
return data.map((item) => {
|
||
const transformedItem = {
|
||
...item,
|
||
value: item.major_id,
|
||
label: `${item.major_title}`
|
||
};
|
||
|
||
// 如果存在 children,递归处理
|
||
if (item.children && item.children.length > 0) {
|
||
transformedItem.children = transformData(item.children);
|
||
}
|
||
|
||
return transformedItem;
|
||
});
|
||
};
|
||
|
||
// 执行递归,获取选项数据
|
||
const data = transformData(res.data.majors.find((item) => item.major_id == 1).children);
|
||
this.options = [...data]; // 将选项数据赋给 options
|
||
});
|
||
},
|
||
handleChange(i) {
|
||
|
||
this.$nextTick(() => {
|
||
this.$refs[`cascader`].dropDownVisible = false;
|
||
this.query.major_id = this.major_id[this.major_id.length - 1];
|
||
this.query.pageIndex = 1;
|
||
this.fetchList();
|
||
|
||
this.$forceUpdate();
|
||
});
|
||
},
|
||
getProps() {
|
||
return {
|
||
value: 'value',
|
||
label: 'label',
|
||
children: 'children',
|
||
checkStrictly: true, // 允许任意选择一级
|
||
expandTrigger: 'hover' // 使用 hover 触发展开
|
||
};
|
||
},
|
||
async fetchMajors() {
|
||
try {
|
||
const res = await this.$api.post('api/Ucenter/getMajor', { major_id: 1 });
|
||
if (res && res.code === 0 && res.data && res.data.major) {
|
||
this.majors = res.data.major.children || [];
|
||
}
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
},
|
||
async fetchList() {
|
||
this.loading = true;
|
||
try {
|
||
const params = {
|
||
major_id: this.query.major_id,
|
||
keyword: this.query.keyword,
|
||
|
||
pageIndex: this.query.pageIndex,
|
||
pageSize: this.query.pageSize
|
||
};
|
||
const res = await this.$api.post('api/expert_manage/getList', params);
|
||
if (res && res.code === 0 && res.data) {
|
||
const rawList = res.data.list || [];
|
||
// 后端已提供单个 field 字段,直接用于列表展示
|
||
|
||
this.list = rawList.map((item) => {
|
||
// 1. 获取后端返回的原始 field 数组(看截图应该是 item.field)
|
||
const fieldArray = item.fields || [];
|
||
|
||
// 2. 提取出数组中每个对象里的 'field' 属性字符串
|
||
// 如果只需要展示文本,可以用 join 连起来
|
||
const fieldNames = fieldArray.map((f) => f.field).join(', ');
|
||
|
||
return {
|
||
...item,
|
||
fieldDisplay: fieldNames
|
||
};
|
||
});
|
||
this.total = res.data.total || 0;
|
||
} else {
|
||
this.list = [];
|
||
this.total = 0;
|
||
}
|
||
} catch (e) {
|
||
this.list = [];
|
||
this.total = 0;
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
handleSearch() {
|
||
this.query.pageIndex = 1;
|
||
this.fetchList();
|
||
},
|
||
handleReset() {
|
||
this.major_id = [];
|
||
this.query = {
|
||
major_id: null,
|
||
keyword: '',
|
||
|
||
pageIndex: 1,
|
||
pageSize: 10
|
||
};
|
||
this.fetchList();
|
||
},
|
||
handleSizeChange(size) {
|
||
this.query.pageSize = size;
|
||
this.query.pageIndex = 1;
|
||
this.fetchList();
|
||
},
|
||
handlePageChange(page) {
|
||
this.query.pageIndex = page;
|
||
this.fetchList();
|
||
},
|
||
async handleExport() {
|
||
// 导出前必须至少选择领域或填写关键字
|
||
if (!this.query.major_id && !this.query.keyword) {
|
||
this.$message.warning('Please select a research area or enter a keyword before exporting.');
|
||
return;
|
||
}
|
||
this.exportLoading = true;
|
||
try {
|
||
const params = {
|
||
major_id: this.query.major_id,
|
||
keyword: this.query.keyword
|
||
};
|
||
const res = await this.$api.post('api/expert_manage/exportExcel', params);
|
||
if (res && res.code === 0 && res.data && res.data.file_url) {
|
||
window.open(this.mediaUrl + res.data.file_url, '_blank');
|
||
} else {
|
||
this.$message.error(res.msg || 'Export failed');
|
||
}
|
||
} catch (e) {
|
||
this.$message.error(e.msg || 'Export failed');
|
||
} finally {
|
||
this.exportLoading = false;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.scholar-db-container {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 15px;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.title i {
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.filters {
|
||
flex: 1;
|
||
margin: 0 20px;
|
||
margin-left: 0;
|
||
}
|
||
|
||
.actions {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.table-card {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 15px;
|
||
text-align: right;
|
||
}
|
||
|
||
/deep/ .dark-table-header th {
|
||
background-color: #f5f7fa;
|
||
font-weight: 600;
|
||
}
|
||
/deep/ .el-form-item--mini.el-form-item,
|
||
.el-form-item--small.el-form-item {
|
||
margin-bottom: 0;
|
||
}
|
||
.custom-tag {
|
||
/* 基础布局 */
|
||
display: inline-block;
|
||
position: absolute;
|
||
right: 6px;
|
||
top: 0px;
|
||
|
||
/* 颜色与背景 */
|
||
|
||
color: #ce4f15; /* 深灰色文字 */
|
||
|
||
/* 形状 */
|
||
/* font-weight: bold; */
|
||
|
||
/* 字体 */
|
||
font-size: 12px;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
}
|
||
/* 每一行的布局 */
|
||
.info-row {
|
||
margin-bottom: 4px;
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
line-height: 1.2;
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
|
||
/* 左侧 Label 样式 */
|
||
.label {
|
||
color: #999; /* 浅灰色 */
|
||
margin-right: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 右侧 Value 样式 */
|
||
.value {
|
||
color: #333;
|
||
}
|
||
.value.bold {
|
||
font-weight: bold;
|
||
font-size: 15px;
|
||
}
|
||
.value.link {
|
||
color: #0066a1;
|
||
} /* 邮箱颜色 */
|
||
.value.time {
|
||
color: #888;
|
||
} /* 邮箱颜色 */
|
||
.value.italic {
|
||
font-style: italic;
|
||
color: #666;
|
||
}
|
||
.blue-text {
|
||
color: #0066a1;
|
||
}
|
||
|
||
/* H-指数特有颜色 */
|
||
.h-score {
|
||
font-weight: bold;
|
||
margin-right: 15px;
|
||
}
|
||
.green {
|
||
color: #28a745;
|
||
}
|
||
.red {
|
||
color: #dc3545;
|
||
}
|
||
|
||
/* 星星评分 */
|
||
.stars {
|
||
color: #ffb400;
|
||
font-size: 16px;
|
||
}
|
||
.stars .half {
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 状态标签 (参考你之前的要求) */
|
||
.status-tag {
|
||
background-color: #fffbe6; /* 浅黄背景 */
|
||
color: #d48806; /* 橙黄色文字 */
|
||
border: 1px solid #ffe58f;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
.source-tag {
|
||
/* 布局 */
|
||
float: right;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 6px 16px;
|
||
margin-top: -10px;
|
||
margin-bottom: 10px;
|
||
|
||
/* 颜色与背景 */
|
||
background-color: #f0f7ff; /* 极浅蓝背景 */
|
||
color: #0077cc; /* 品牌蓝文字 */
|
||
border: 1px solid #e1f0ff; /* 浅蓝色边框 */
|
||
|
||
/* 形状与阴影 */
|
||
border-radius: 20px;
|
||
box-shadow: 0 2px 6px rgba(0, 119, 204, 0.1); /* 淡淡的蓝色投影 */
|
||
|
||
/* 字体 */
|
||
font-size: 11px;
|
||
font-weight: bold;
|
||
|
||
letter-spacing: 0.5px; /* 字间距增加 */
|
||
}
|
||
|
||
/* 前置小圆点 */
|
||
.source-tag::before {
|
||
content: '';
|
||
width: 8px;
|
||
height: 8px;
|
||
background-color: #0095ff; /* 亮蓝色圆点 */
|
||
border-radius: 50%;
|
||
margin-right: 10px;
|
||
}
|
||
</style>
|