Files
vdi-cloud/console/src/views/CommonPeople.vue
T

64 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { api, type User, type Desktop, type DesktopAction } from '../api'
import { usePlatform } from '../stores/platform'
import { useAuth } from '../stores/auth'
import { useAction } from '../composables/useAction'
import StatusTag from '../components/StatusTag.vue'
const data = usePlatform(), auth = useAuth(), { busy, run, confirm } = useAction()
const search = ref(''), page = ref(1), creating = ref(false), editing = ref(false), reviewing = ref(false), person = ref<User | null>(null)
const profileVisible = ref(false), profileId = ref(''), profileUsername = ref('')
const profile = reactive({ displayName: '', enabled: true })
const form = reactive({ username: '', displayName: '' }), selected = ref<string[]>([]), action = ref<'grant' | 'revoke'>('grant')
const filtered = computed(() => data.users.filter(u => `${u.displayName} ${u.username}`.toLowerCase().includes(search.value.toLowerCase())))
const rows = computed(() => filtered.value.slice((page.value - 1) * 10, page.value * 10))
const desktopName = (id: string) => data.desktops.find(d => d.id === id)?.name || '已失效的桌面'
const choices = computed(() => data.desktops.filter(d => action.value === 'grant' ? !person.value?.desktopIds.includes(d.id) : person.value?.desktopIds.includes(d.id)))
const desktopSearch = ref(''), desktopPage = ref(1)
const desktops = computed(() => data.desktops.filter(d => d.name.toLowerCase().includes(desktopSearch.value.toLowerCase())))
const desktopRows = computed(() => desktops.value.slice((desktopPage.value - 1) * 10, desktopPage.value * 10))
const pending = (id: string) => data.tasks.some(t => t.targetId === id && ['queued', 'running', 'timeout'].includes(t.status))
async function power(desktop: Desktop, operation: DesktopAction) {
if (!auth.canOperate || pending(desktop.id) || busy.value || data.error) return
const label = { start: '开机', shutdown: '正常关机', reboot: '重启桌面' }[operation]
if (!await confirm(`${label} · ${desktop.name}`, operation === 'start' ? '将提交开机任务,连接服务仍需稍后就绪。' : '会中断该桌面上的使用,请先通知使用人保存文件。')) return
if (pending(desktop.id) || data.error) return
await run(() => api.desktopAction(desktop.id, operation), '操作已提交,请在操作记录中查看实际结果')
}
function open(user: User, operation: 'grant' | 'revoke') { person.value = { ...user, desktopIds: [...user.desktopIds] }; action.value = operation; selected.value = []; reviewing.value = false; editing.value = true }
async function create() {
if (!auth.canAdmin || !form.username.trim() || !form.displayName.trim()) return
if (await run(() => api.createUser({ username: form.username.trim(), displayName: form.displayName.trim() }), '员工已添加,请继续分配桌面')) { creating.value = false; form.username = ''; form.displayName = '' }
}
function editProfile(user: User) {
profileId.value = user.id
profileUsername.value = user.username
Object.assign(profile, { displayName: user.displayName, enabled: user.enabled })
profileVisible.value = true
}
async function saveProfile() {
if (!auth.canAdmin || !profile.displayName.trim() || busy.value) return
const existing = data.users.find(user => user.id === profileId.value)
if (existing?.enabled !== profile.enabled && !await confirm(profile.enabled ? '启用员工' : '停用员工', '状态变更会作废该员工未消费的连接票据。停用会阻止后续连接,但保留桌面授权和已有串流。')) return
if (await run(() => api.updateUser(profileId.value, { displayName: profile.displayName.trim(), enabled: profile.enabled }), '员工资料已保存')) profileVisible.value = false
}
async function save() {
if (!auth.canAdmin || !person.value || !selected.value.length || !reviewing.value || busy.value) return
const userId = person.value.id, desktopIds = [...selected.value], operation = action.value
if (await run(() => api.batchBindings({ userIds: [userId], desktopIds, action: operation }), operation === 'grant' ? '桌面授权已分配' : '指定桌面授权已撤销')) editing.value = false
}
</script>
<template>
<div class="page-heading"><div><h1>员工与桌面</h1><p class="subtext">给员工分配已有桌面,其他授权自动保留</p></div><el-button v-if="auth.canAdmin" type="primary" @click="creating = true">添加员工</el-button></div>
<section class="panel"><div class="toolbar"><el-input v-model="search" aria-label="查找员工" placeholder="输入姓名或账号" clearable @input="page = 1" /></div><el-table :data="rows" empty-text="暂无员工,请先添加员工"><el-table-column label="员工" min-width="150"><template #default="{ row }">{{ row.displayName }}<div class="secondary">{{ row.username }} · {{ row.enabled ? '已启用' : '已停用' }}</div></template></el-table-column><el-table-column label="已分配桌面" min-width="210"><template #default="{ row }"><div v-for="id in row.desktopIds" :key="id">{{ desktopName(id) }} <StatusTag v-if="data.desktops.find(d => d.id === id)" :value="data.desktops.find(d => d.id === id)!.powerState" /></div><span v-if="!row.desktopIds.length" class="secondary">尚未分配</span></template></el-table-column><el-table-column label="使用设备" min-width="145"><template #default="{ row }">{{ data.terminals.find(t => t.id === row.terminalId)?.name || '尚未关联设备' }}</template></el-table-column><el-table-column v-if="auth.canAdmin" label="操作" min-width="270"><template #default="{ row }"><div class="row-actions"><el-button link type="primary" @click="editProfile(row)">编辑资料</el-button><el-button link type="primary" @click="open(row, 'grant')">分配桌面</el-button><el-button link :disabled="!row.desktopIds.length" @click="open(row, 'revoke')">撤销授权</el-button></div></template></el-table-column></el-table><el-pagination v-model:current-page="page" :total="filtered.length" :page-size="10" layout="prev, pager, next" /></section>
<section class="panel simple-section"><h2>桌面日常操作</h2><div class="toolbar"><el-input v-model="desktopSearch" aria-label="查找桌面" placeholder="桌面名称" clearable @input="desktopPage = 1" /></div><el-table :data="desktopRows" empty-text="暂无已有桌面,请由运维纳管"><el-table-column prop="name" label="桌面" min-width="150" /><el-table-column label="使用人" min-width="150"><template #default="{ row }">{{ data.users.filter(u => u.desktopIds.includes(row.id)).map(u => u.displayName).join('、') || '尚未分配' }}</template></el-table-column><el-table-column label="状态" min-width="180"><template #default="{ row }"><StatusTag :value="row.powerState" /><div class="secondary">{{ row.setupRequired ? '等待初始化' : row.serviceState === 'ready' ? '连接服务就绪' : '连接服务尚未确认就绪' }}</div><el-tag v-if="pending(row.id)" type="warning" size="small">任务未完成,请先核对</el-tag></template></el-table-column><el-table-column v-if="auth.canOperate" label="操作" min-width="200"><template #default="{ row }"><el-button v-if="row.powerState === 'stopped'" link type="primary" :disabled="busy || pending(row.id) || !!data.error" @click="power(row, 'start')">开机</el-button><template v-if="row.powerState === 'running'"><el-button link :disabled="busy || pending(row.id) || !!data.error" @click="power(row, 'shutdown')">正常关机</el-button><el-button link :disabled="busy || pending(row.id) || !!data.error" @click="power(row, 'reboot')">重启桌面</el-button></template></template></el-table-column></el-table><el-pagination v-model:current-page="desktopPage" :total="desktops.length" :page-size="10" layout="prev, pager, next" /></section>
<p class="subtext">分配表示允许后续连接,不会新建桌面或自动开机。关联设备、批量导入和批量授权等集中管理功能可在高级版中完成。</p>
<el-dialog v-model="creating" title="添加员工" width="460px" :close-on-click-modal="false" :show-close="!busy" :close-on-press-escape="!busy"><el-form label-position="top" :disabled="busy" @submit.prevent="create"><el-form-item label="员工姓名" required><el-input v-model="form.displayName" aria-label="员工姓名" maxlength="80" /></el-form-item><el-form-item label="员工账号" required><el-input v-model="form.username" aria-label="员工账号" maxlength="80" /></el-form-item><p class="subtext">此账号用于关联盒子与桌面授权,桌面系统密码单独管理。</p></el-form><template #footer><el-button :disabled="busy" @click="creating = false">取消</el-button><el-button type="primary" :loading="busy" :disabled="!form.displayName.trim() || !form.username.trim()" @click="create">添加员工</el-button></template></el-dialog>
<el-dialog v-model="profileVisible" title="编辑员工资料" width="480px" :close-on-click-modal="false" :show-close="!busy" :close-on-press-escape="!busy"><el-form label-position="top" :disabled="busy" @submit.prevent="saveProfile"><el-form-item label="员工账号(固定)"><el-input :model-value="profileUsername" disabled readonly /></el-form-item><el-form-item label="姓名" required><el-input v-model="profile.displayName" aria-label="编辑员工姓名" maxlength="80" /></el-form-item><el-form-item label="员工状态"><el-switch v-model="profile.enabled" active-text="启用" inactive-text="停用" aria-label="编辑员工启用状态" /></el-form-item><p class="subtext">员工账号用于授权关系,创建后不能修改;停用会阻止后续连接,但保留已有桌面授权。</p></el-form><template #footer><el-button :disabled="busy" @click="profileVisible = false">取消</el-button><el-button type="primary" :loading="busy" :disabled="!profile.displayName.trim()" @click="saveProfile">保存员工资料</el-button></template></el-dialog>
<el-dialog v-model="editing" :title="action === 'grant' ? '分配已有桌面' : '撤销指定授权'" width="560px" :close-on-click-modal="false" :show-close="!busy" :close-on-press-escape="!busy"><template v-if="person"><h3>{{ person.displayName }} · {{ person.username }}</h3><p v-if="!person.enabled" class="subtext">员工目前已停用,保存授权后仍需管理员启用才能连接。</p><template v-if="!reviewing"><p class="subtext">选择要{{ action === 'grant' ? '分配' : '撤销' }}的桌面:</p><el-checkbox-group v-model="selected" class="simple-checks"><el-checkbox v-for="d in choices" :key="d.id" :value="d.id">{{ d.name }} · {{ d.os }}</el-checkbox></el-checkbox-group><el-empty v-if="!choices.length" description="没有符合条件的桌面" :image-size="64" /></template><div v-else class="simple-plan"><h3>确认变更方案</h3><p>为 {{ person.displayName }} {{ action === 'grant' ? '追加分配' : '撤销以下' }} {{ selected.length }} 台桌面的使用权限:</p><ul><li v-for="id in selected" :key="id">{{ desktopName(id) }}</li></ul><p>其他桌面授权、员工状态和设备关联保持原值。已有串流不会被强制断开。</p></div></template><template #footer><el-button :disabled="busy" @click="reviewing ? reviewing = false : editing = false">{{ reviewing ? '返回修改' : '取消' }}</el-button><el-button v-if="!reviewing" type="primary" :disabled="!selected.length" @click="reviewing = true">查看方案</el-button><el-button v-else type="primary" :loading="busy" @click="save">确认并保存授权</el-button></template></el-dialog>
</template>
<style scoped>
.row-actions{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
</style>