Files
danyang-match/admin-web/src/modules/operator/MatchmakerTeamManagementView.vue
T
WorkBuddy 377429995c
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Waiting to run
CI/CD Pipeline / Backend Build & Test (push) Waiting to run
CI/CD Pipeline / Admin Web Build (push) Waiting to run
CI/CD Pipeline / Mini Program Compile Check (push) Waiting to run
CI/CD Pipeline / Migration Validation (push) Blocked by required conditions
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Waiting to run
CI/CD Pipeline / E2E (live stack) (push) Blocked by required conditions
release: freeze V1.2 demo acceptance 2026-10-25
2026-10-25 14:29:29 +08:00

111 lines
7.3 KiB
Vue

<template>
<div class="page-container">
<el-card shadow="never">
<template #header>
<div class="header-row">
<span>红娘团队管理</span>
<div class="header-actions">
<el-select v-model="status" clearable placeholder="全部状态" style="width:120px" @change="load">
<el-option label="正常" value="normal" /><el-option label="停用" value="suspended" />
</el-select>
<el-input v-model="keyword" clearable placeholder="姓名或用户名" style="width:180px" @keyup.enter="load" />
<el-button type="primary" @click="openCreate">新增红娘</el-button>
</div>
</div>
</template>
<el-alert v-if="error" type="error" :closable="false" :title="error" />
<el-table v-loading="loading" :data="items" border stripe empty-text="暂无红娘团队成员">
<el-table-column prop="username" label="用户名" width="150" />
<el-table-column prop="name" label="姓名" width="120" />
<el-table-column label="角色" width="130"><template #default="{ row }">{{ roleName(row.roles?.[0]) }}</template></el-table-column>
<el-table-column label="数据范围" width="110"><template #default="{ row }">{{ scopeName(row.scope) }}</template></el-table-column>
<el-table-column label="所属组织" min-width="180"><template #default="{ row }">{{ orgMap[row.organizationId] || '未绑定' }}</template></el-table-column>
<el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.status === 'normal' ? 'success' : 'danger'">{{ row.status === 'normal' ? '正常' : '停用' }}</el-tag></template></el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="openEdit(row)">编辑</el-button>
<el-button size="small" @click="resetPassword(row)">重置密码</el-button>
<el-button size="small" :type="row.status === 'normal' ? 'warning' : 'success'" @click="toggleStatus(row)">{{ row.status === 'normal' ? '停用' : '启用' }}</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="total" v-model:current-page="page" style="margin-top:16px;text-align:right" layout="total, prev, pager, next" :page-size="pageSize" :total="total" @current-change="load" />
</el-card>
<el-dialog v-model="dialogVisible" :title="editing ? '编辑红娘团队成员' : '新增红娘团队成员'" width="520px">
<el-form label-width="92px">
<el-form-item label="姓名"><el-input v-model="form.name" /></el-form-item>
<el-form-item label="角色"><el-radio-group v-model="form.roleCode"><el-radio value="matchmaker">普通红娘</el-radio><el-radio value="matchmaker_leader">红娘组长</el-radio></el-radio-group></el-form-item>
<el-form-item label="所属组织" required><el-select v-model="form.organizationId" clearable style="width:100%"><el-option v-for="org in organizations" :key="org.id" :label="org.name" :value="org.id" /></el-select></el-form-item>
<el-form-item v-if="!editing" label="初始密码"><el-input v-model="form.password" type="password" show-password /></el-form-item>
<el-alert type="info" :closable="false" :title="`数据范围:${scopeName(form.roleCode === 'matchmaker_leader' ? 'TEAM' : 'SELF')}`" />
</el-form>
<template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { get, post, put } from '@/api/client'
const loading = ref(false)
const saving = ref(false)
const error = ref('')
const items = ref<any[]>([])
const organizations = ref<any[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = 20
const keyword = ref('')
const status = ref('')
const dialogVisible = ref(false)
const editing = ref<any | null>(null)
const form = ref({ name: '', roleCode: 'matchmaker', organizationId: '', password: '' })
const orgMap = computed(() => Object.fromEntries(organizations.value.map((org) => [org.id, org.name])))
function roleName(role: string) { return role === 'matchmaker_leader' ? '红娘组长' : '普通红娘' }
function scopeName(scope: string) { return scope === 'TEAM' ? '团队范围' : scope === 'ALL' ? '全部范围' : '本人范围' }
async function load() {
loading.value = true; error.value = ''
try {
const res: any = await get('/operator/matchmaker-team/members', { page: page.value - 1, pageSize, keyword: keyword.value || undefined, status: status.value || undefined })
items.value = res.data?.items || []; total.value = res.data?.total || 0
} catch (e: any) { error.value = e?.response?.data?.message || '红娘团队数据加载失败' } finally { loading.value = false }
}
async function loadOrganizations() {
const res: any = await get('/operator/operators/organizations')
organizations.value = res.data || []
}
function openCreate() { editing.value = null; form.value = { name: '', roleCode: 'matchmaker', organizationId: '', password: '' }; dialogVisible.value = true }
function openEdit(row: any) { editing.value = row; form.value = { name: row.name, roleCode: row.roles?.[0] || 'matchmaker', organizationId: row.organizationId || '', password: '' }; dialogVisible.value = true }
async function save() {
if (!form.value.name.trim()) return ElMessage.warning('请填写红娘姓名')
if (form.value.roleCode === 'matchmaker_leader' && !form.value.organizationId) return ElMessage.warning('红娘组长必须选择所属组织')
if (!editing.value && form.value.password.length < 8) return ElMessage.warning('初始密码至少8位')
saving.value = true
try {
const payload: any = { name: form.value.name, roleCode: form.value.roleCode, organizationId: form.value.organizationId || null }
if (!editing.value) Object.assign(payload, { password: form.value.password })
const res: any = editing.value ? await put(`/operator/matchmaker-team/members/${editing.value.id}`, payload) : await post('/operator/matchmaker-team/members', payload)
if (!res.success) throw new Error(res.message || '保存失败')
dialogVisible.value = false; ElMessage.success('保存成功'); await load()
} catch (e: any) { ElMessage.error(e?.response?.data?.message || e.message || '保存失败') } finally { saving.value = false }
}
async function toggleStatus(row: any) {
await put(`/operator/matchmaker-team/members/${row.id}/${row.status === 'normal' ? 'disable' : 'enable'}`)
ElMessage.success('状态已更新'); await load()
}
async function resetPassword(row: any) {
const value = await ElMessageBox.prompt('请输入新密码(至少8位)', '重置密码', { inputType: 'password', inputValidator: (v) => v.length >= 8 || '密码至少8位' })
await put(`/operator/matchmaker-team/members/${row.id}/reset-password`, { newPassword: value.value })
ElMessage.success('密码已重置')
}
onMounted(async () => { await loadOrganizations(); await load() })
</script>
<style scoped>
.header-row,.header-actions{display:flex;align-items:center}.header-row{justify-content:space-between}.header-actions{gap:8px}
</style>