|
|
|
|
@ -9,7 +9,9 @@ import TeacherPaperDialog from '@/components/TeacherPaperDialog.vue'
|
|
|
|
|
import { fetchAdminUsers } from '@/api/admin/users'
|
|
|
|
|
import type { DictItemBrief } from '@/api/admin/teachers'
|
|
|
|
|
import {
|
|
|
|
|
approveTeacher,
|
|
|
|
|
batchUpdateTeacherStar,
|
|
|
|
|
batchApproveTeachers,
|
|
|
|
|
createTeacher,
|
|
|
|
|
createTeacherFollowRecord,
|
|
|
|
|
createUniversity,
|
|
|
|
|
@ -18,6 +20,7 @@ import {
|
|
|
|
|
fetchTeachersList,
|
|
|
|
|
fetchTeacherStats,
|
|
|
|
|
fetchUniversities,
|
|
|
|
|
rejectTeacher,
|
|
|
|
|
type FollowRecordRow,
|
|
|
|
|
type TeacherRow,
|
|
|
|
|
type TeacherStats,
|
|
|
|
|
@ -25,7 +28,7 @@ import {
|
|
|
|
|
import { followRuleHint, previewNextFollowDate } from '@/utils/teacherFollowRule'
|
|
|
|
|
import { starDisplay, sourceTagType, statusTagType, urgencyTagType } from '@/utils/teacherStar'
|
|
|
|
|
import { useAuthStore } from '@/stores/auth'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
|
|
|
|
const auth = useAuthStore()
|
|
|
|
|
const isGridMember = computed(() => auth.isGridMember)
|
|
|
|
|
@ -44,7 +47,8 @@ const detailTeacherId = ref<number | null>(null)
|
|
|
|
|
const items = ref<TeacherRow[]>([])
|
|
|
|
|
const meta = ref({ current_page: 1, per_page: 20, total: 0 })
|
|
|
|
|
const page = ref(1)
|
|
|
|
|
const stats = ref<TeacherStats>({ month_pending: 0, month_followed: 0, overdue: 0, partners: 0 })
|
|
|
|
|
const stats = ref<TeacherStats>({ month_pending: 0, month_followed: 0, overdue: 0, partners: 0, library_pending: 0 })
|
|
|
|
|
const libraryTab = ref<'active' | 'pending' | 'rejected'>('active')
|
|
|
|
|
|
|
|
|
|
const keyword = ref('')
|
|
|
|
|
const filterSource = ref<number | ''>('')
|
|
|
|
|
@ -196,6 +200,7 @@ async function load() {
|
|
|
|
|
if (filterUniversity.value !== '') params.university_id = filterUniversity.value
|
|
|
|
|
if (filterDirection.value !== '') params.research_direction_id = filterDirection.value
|
|
|
|
|
if (statBucket.value) params.stat_bucket = statBucket.value
|
|
|
|
|
params.library_status = libraryTab.value
|
|
|
|
|
const res = await fetchTeachersList(params)
|
|
|
|
|
items.value = res.items
|
|
|
|
|
meta.value = res.meta
|
|
|
|
|
@ -225,16 +230,56 @@ function searchTeachers() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pickStat(bucket: string) {
|
|
|
|
|
if (libraryTab.value !== 'active') {
|
|
|
|
|
libraryTab.value = 'active'
|
|
|
|
|
}
|
|
|
|
|
statBucket.value = statBucket.value === bucket ? '' : bucket
|
|
|
|
|
page.value = 1
|
|
|
|
|
load()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function switchLibraryTab(tab: 'active' | 'pending' | 'rejected') {
|
|
|
|
|
libraryTab.value = tab
|
|
|
|
|
statBucket.value = ''
|
|
|
|
|
page.value = 1
|
|
|
|
|
selectedRows.value = []
|
|
|
|
|
load()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function approveRow(row: TeacherRow) {
|
|
|
|
|
await approveTeacher(row.id)
|
|
|
|
|
ElMessage.success('已确认入库')
|
|
|
|
|
await Promise.all([load(), loadStats()])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function rejectRow(row: TeacherRow) {
|
|
|
|
|
await ElMessageBox.confirm(`确定驳回「${row.name}」?驳回后记录会保留。`, '确认驳回', {
|
|
|
|
|
type: 'warning',
|
|
|
|
|
})
|
|
|
|
|
await rejectTeacher(row.id)
|
|
|
|
|
ElMessage.success('已驳回')
|
|
|
|
|
await Promise.all([load(), loadStats()])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function batchApproveSelected() {
|
|
|
|
|
if (!selectedIds.value.length) {
|
|
|
|
|
ElMessage.warning('请先选择老师')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const res = await batchApproveTeachers(selectedIds.value)
|
|
|
|
|
ElMessage.success(`已确认入库 ${res.updated} 位老师`)
|
|
|
|
|
await Promise.all([load(), loadStats()])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function goDetail(row: TeacherRow) {
|
|
|
|
|
detailTeacherId.value = row.id
|
|
|
|
|
detailVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onDetailSaved() {
|
|
|
|
|
await Promise.all([load(), loadStats()])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshDirectionOptions() {
|
|
|
|
|
const filters = await fetchTeacherFilterOptions()
|
|
|
|
|
directionOptions.value = filters.research_directions
|
|
|
|
|
@ -473,6 +518,14 @@ usePageLoad(async () => {
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
<PageTitle />
|
|
|
|
|
<div class="page-header-actions">
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="libraryTab === 'pending' && selectedIds.length"
|
|
|
|
|
type="success"
|
|
|
|
|
size="small"
|
|
|
|
|
@click="batchApproveSelected"
|
|
|
|
|
>
|
|
|
|
|
批量确认入库
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button class="btn-action-secondary" size="small" @click="openBatch">批量改星</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="!isGridMember"
|
|
|
|
|
@ -487,6 +540,15 @@ usePageLoad(async () => {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="talent-stat-grid">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="talent-stat-card"
|
|
|
|
|
:class="{ 'is-active': libraryTab === 'pending' }"
|
|
|
|
|
@click="switchLibraryTab('pending')"
|
|
|
|
|
>
|
|
|
|
|
<div class="talent-stat-label">待确认入库</div>
|
|
|
|
|
<div class="talent-stat-value is-warning">{{ stats.library_pending ?? 0 }}</div>
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="talent-stat-card"
|
|
|
|
|
@ -526,6 +588,13 @@ usePageLoad(async () => {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-card shadow="never" class="admin-list-card teachers-list-card">
|
|
|
|
|
<div class="library-tabs">
|
|
|
|
|
<el-radio-group v-model="libraryTab" @change="switchLibraryTab(libraryTab)">
|
|
|
|
|
<el-radio-button value="active">正式老师</el-radio-button>
|
|
|
|
|
<el-radio-button value="pending">待确认</el-radio-button>
|
|
|
|
|
<el-radio-button value="rejected">已驳回</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="list-filter-bar">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
@ -631,24 +700,37 @@ usePageLoad(async () => {
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="176" fixed="right">
|
|
|
|
|
<el-table-column label="操作" width="220" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div class="table-row-actions teachers-table-actions">
|
|
|
|
|
<el-button size="small" class="btn-action-info teachers-table-btn" @click="openFollow(row)">
|
|
|
|
|
跟进
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="small" class="btn-action-primary teachers-table-btn" @click="openPaper(row)">
|
|
|
|
|
论文
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="row.is_partner || row.status_item?.value === 'partner'"
|
|
|
|
|
type="primary"
|
|
|
|
|
size="small"
|
|
|
|
|
class="teachers-primary-btn teachers-table-btn"
|
|
|
|
|
@click="openDemand(row)"
|
|
|
|
|
>
|
|
|
|
|
需求
|
|
|
|
|
</el-button>
|
|
|
|
|
<template v-if="row.library_status === 'pending'">
|
|
|
|
|
<el-button size="small" type="success" class="teachers-table-btn" @click="approveRow(row)">
|
|
|
|
|
确认入库
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="small" type="danger" class="teachers-table-btn" @click="rejectRow(row)">
|
|
|
|
|
驳回
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="small" class="btn-action-primary teachers-table-btn" @click="openPaper(row)">
|
|
|
|
|
论文
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="libraryTab === 'active'">
|
|
|
|
|
<el-button size="small" class="btn-action-info teachers-table-btn" @click="openFollow(row)">
|
|
|
|
|
跟进
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="small" class="btn-action-primary teachers-table-btn" @click="openPaper(row)">
|
|
|
|
|
论文
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="row.is_partner || row.status_item?.value === 'partner'"
|
|
|
|
|
type="primary"
|
|
|
|
|
size="small"
|
|
|
|
|
class="teachers-primary-btn teachers-table-btn"
|
|
|
|
|
@click="openDemand(row)"
|
|
|
|
|
>
|
|
|
|
|
需求
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
<el-button size="small" class="btn-action-primary teachers-table-btn" @click="goDetail(row)">
|
|
|
|
|
编辑
|
|
|
|
|
</el-button>
|
|
|
|
|
@ -1021,7 +1103,11 @@ usePageLoad(async () => {
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<TeacherDetailDialog v-model="detailVisible" :teacher-id="detailTeacherId" @saved="load" />
|
|
|
|
|
<TeacherDetailDialog
|
|
|
|
|
v-model="detailVisible"
|
|
|
|
|
:teacher-id="detailTeacherId"
|
|
|
|
|
@saved="onDetailSaved"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
@ -1094,6 +1180,14 @@ usePageLoad(async () => {
|
|
|
|
|
.talent-stat-value.is-success {
|
|
|
|
|
color: var(--el-color-success);
|
|
|
|
|
}
|
|
|
|
|
.talent-stat-value.is-warning {
|
|
|
|
|
color: #d97706;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.library-tabs {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filter-source {
|
|
|
|
|
width: 132px;
|
|
|
|
|
}
|
|
|
|
|
|