You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

294 lines
8.4 KiB

<script setup lang="ts">
import PageTitle from '@/components/PageTitle.vue'
import { ref } from 'vue'
import { usePageLoad } from '@/composables/usePageLoad'
import {
fetchGridMembers,
createGridMember,
updateGridMember,
resetGridMemberPassword,
deleteGridMember,
} from '@/api/admin/grid-members'
import type { GridMemberRow } from '@/api/admin/grid-members'
import { fetchResearchDirectionOptions } from '@/api/admin/research-directions'
import { fetchUniversities } from '@/api/admin/teachers'
import { enabledStatusClass } from '@/utils/admin-list'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const items = ref<GridMemberRow[]>([])
const meta = ref({ current_page: 1, per_page: 20, total: 0 })
const keyword = ref('')
const page = ref(1)
const universityOptions = ref<{ id: number; name: string }[]>([])
const directionOptions = ref<{ id: number; name: string }[]>([])
const dialog = ref(false)
const editing = ref<GridMemberRow | null>(null)
const form = ref({
username: '',
password: '',
real_name: '',
mobile: '',
email: '',
status: 1,
university_ids: [] as number[],
research_direction_ids: [] as number[],
})
async function loadOptions() {
const [uniRes, dirs] = await Promise.all([
fetchUniversities({ page: 1, page_size: 500, simple: 1 }),
fetchResearchDirectionOptions(),
])
universityOptions.value = uniRes.items.map((u) => ({ id: u.id, name: u.name }))
directionOptions.value = dirs.map((d) => ({ id: d.id, name: d.name }))
}
async function load() {
loading.value = true
try {
const res = await fetchGridMembers({
page: page.value,
page_size: meta.value.per_page,
keyword: keyword.value || undefined,
})
items.value = res.items
meta.value = res.meta
} finally {
loading.value = false
}
}
function openCreate() {
editing.value = null
form.value = {
username: '',
password: '',
real_name: '',
mobile: '',
email: '',
status: 1,
university_ids: [],
research_direction_ids: [],
}
dialog.value = true
}
function openEdit(row: GridMemberRow) {
editing.value = row
form.value = {
username: row.username,
password: '',
real_name: row.real_name || '',
mobile: row.mobile || '',
email: row.email || '',
status: row.status,
university_ids: row.universities?.map((u) => u.id) || [],
research_direction_ids: row.research_directions?.map((d) => d.id) || [],
}
dialog.value = true
}
async function save() {
if (!editing.value && !form.value.password) {
ElMessage.warning('请设置初始密码')
return
}
if (!form.value.university_ids.length) {
ElMessage.warning('请至少选择一个负责高校')
return
}
if (!form.value.research_direction_ids.length) {
ElMessage.warning('请至少选择一个研究方向')
return
}
const payload = {
real_name: form.value.real_name || null,
mobile: form.value.mobile || null,
email: form.value.email || null,
status: form.value.status,
university_ids: form.value.university_ids,
research_direction_ids: form.value.research_direction_ids,
}
if (editing.value) {
await updateGridMember(editing.value.id, payload)
if (form.value.password) {
await resetGridMemberPassword(editing.value.id, form.value.password)
}
} else {
await createGridMember({
username: form.value.username,
password: form.value.password,
...payload,
})
}
ElMessage.success('已保存')
dialog.value = false
await load()
}
async function remove(row: GridMemberRow) {
await ElMessageBox.confirm(`确定删除网格员「${row.username}」?`, '提示', { type: 'warning' })
await deleteGridMember(row.id)
ElMessage.success('已删除')
await load()
}
function search() {
page.value = 1
void load()
}
function resetFilters() {
keyword.value = ''
page.value = 1
void load()
}
usePageLoad(async () => {
await loadOptions()
await load()
})
</script>
<template>
<div class="list-page">
<div class="page-header">
<PageTitle />
<el-button type="primary" size="small" class="btn-create" @click="openCreate"></el-button>
</div>
<el-card shadow="never" class="admin-list-card">
<div class="list-filter-bar">
<el-input
v-model="keyword"
placeholder="账号/姓名/手机"
clearable
class="filter-search"
@keyup.enter="search"
/>
<el-button type="primary" @click="search">搜索</el-button>
<el-button @click="resetFilters">重置</el-button>
</div>
<el-table v-loading="loading" :data="items" row-key="id">
<el-table-column prop="username" label="账号" width="130" />
<el-table-column prop="real_name" label="姓名" width="110" />
<el-table-column label="负责高校" min-width="180">
<template #default="{ row }">
<el-tag
v-for="u in row.universities"
:key="u.id"
size="small"
style="margin: 2px 4px 2px 0"
>
{{ u.name }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="研究方向" min-width="180">
<template #default="{ row }">
<el-tag
v-for="d in row.research_directions"
:key="d.id"
size="small"
type="info"
style="margin: 2px 4px 2px 0"
>
{{ d.name }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<span class="status-badge" :class="enabledStatusClass(row.status)">
{{ row.status === 1 ? '启用' : '停用' }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<div class="table-row-actions">
<el-button class="btn-action-primary" @click="openEdit(row)">编辑</el-button>
<el-button class="btn-action-brand" @click="remove(row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="list-pager">
<el-pagination
v-model:current-page="page"
layout="total, prev, pager, next"
:total="meta.total"
:page-size="meta.per_page"
@current-change="load"
/>
</div>
</el-card>
</div>
<el-dialog v-model="dialog" :title="editing ? '编辑网格员' : '新增网格员'" width="560px">
<el-form label-width="100px">
<el-form-item v-if="!editing" label="账号">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item :label="editing ? '重置密码' : '初始密码'">
<el-input v-model="form.password" type="password" show-password :placeholder="editing ? '可留空' : ''" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="form.real_name" />
</el-form-item>
<el-form-item label="手机">
<el-input v-model="form.mobile" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="负责高校">
<el-select
v-model="form.university_ids"
multiple
filterable
style="width: 100%"
placeholder="选择高校"
>
<el-option
v-for="u in universityOptions"
:key="u.id"
:label="u.name"
:value="u.id"
/>
</el-select>
</el-form-item>
<el-form-item label="研究方向">
<el-select
v-model="form.research_direction_ids"
multiple
filterable
style="width: 100%"
placeholder="选择研究方向"
>
<el-option
v-for="d in directionOptions"
:key="d.id"
:label="d.name"
:value="d.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog = false">取消</el-button>
<el-button type="primary" @click="save"></el-button>
</template>
</el-dialog>
</template>