64 lines
11 KiB
Vue
64 lines
11 KiB
Vue
<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>
|