Files
iqudo-top1/web/src/views/MembersView.vue
2026-08-18 16:27:02 +08:00

34 lines
6.0 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, onMounted, reactive, ref } from 'vue'
import { EditOutlined, PlusOutlined, TeamOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { api, apiMessage } from '@/api/client'
import { useAuthStore } from '@/stores/auth'
import type { Member, Role } from '@/types'
const auth=useAuthStore();const loading=ref(false);const open=ref(false);const editing=ref<Member|null>(null);const items=ref<Member[]>([]);const roles=ref<Role[]>([]);const saving=ref(false)
const form=reactive({username:'',display_name:'',password:'',role_code:'operator'})
const editForm=reactive({role_code:'operator',status:'active'})
const roleOptions=computed(()=>roles.value.map(role=>({value:role.code,label:role.name})))
const columns=[{title:'成员',key:'member'},{title:'角色',dataIndex:'role_name',width:150},{title:'状态',dataIndex:'status',width:110},{title:'',key:'action',width:80}]
async function load(){loading.value=true;try{const data=await api.get<{items:Member[];roles:Role[]}>('/members');items.value=data.items||[];roles.value=data.roles||[]}catch(error){message.error(apiMessage(error))}finally{loading.value=false}}
async function create(){saving.value=true;try{await api.post('/members',form);message.success('成员已创建');open.value=false;Object.assign(form,{username:'',display_name:'',password:'',role_code:'operator'});await load()}catch(error){message.error(apiMessage(error))}finally{saving.value=false}}
function edit(item:Member){editing.value=item;Object.assign(editForm,{role_code:item.role_code,status:item.status})}
async function update(){if(!editing.value)return;saving.value=true;try{await api.put(`/members/${editing.value.id}`,editForm);message.success('成员权限已更新');editing.value=null;await load()}catch(error){message.error(apiMessage(error))}finally{saving.value=false}}
onMounted(load)
</script>
<template>
<div class="page-shell members-page">
<div class="page-heading"><div><h1>企业成员</h1><p>分配场景维护SOP 编辑和一线执行职责权限变更会立即生效</p></div><a-button type="primary" @click="open=true"><PlusOutlined />添加成员</a-button></div>
<section class="member-summary"><TeamOutlined /><div><b>{{ items.filter(item=>item.status==='active').length }}</b><span>在用账号</span></div><p>管理员拥有全部权限维护者配置场景契约知识和 SOP执行者使用可用 SOP 和自己的记录</p></section>
<section class="surface"><a-table row-key="id" :loading="loading" :data-source="items" :columns="columns" :pagination="false"><template #bodyCell="{column,record}"><template v-if="column.key==='member'"><div class="member-cell"><span>{{ record.display_name.slice(0,1) }}</span><div><b>{{ record.display_name }}</b><small>{{ record.username }}</small></div></div></template><template v-else-if="column.dataIndex==='role_name'"><a-tag>{{ record.role_name }}</a-tag></template><template v-else-if="column.dataIndex==='status'"><a-tag :color="record.status==='active'?'green':'default'">{{ record.status==='active'?'使用中':'已停用' }}</a-tag></template><template v-else-if="column.key==='action'"><a-button v-if="record.user_id!==auth.user?.user_id" type="text" aria-label="编辑成员权限" @click="edit(record)"><EditOutlined /></a-button></template></template></a-table></section>
<a-drawer v-model:open="open" title="添加企业成员" width="480"><a-form layout="vertical" :model="form" @finish="create"><div class="form-grid"><a-form-item label="登录用户名" name="username" :rules="[{required:true,message:'请输入用户名'}]"><a-input v-model:value="form.username" /></a-form-item><a-form-item label="显示名称" name="display_name" :rules="[{required:true,message:'请输入显示名称'}]"><a-input v-model:value="form.display_name" /></a-form-item></div><a-form-item label="初始密码" name="password" :rules="[{required:true,min:6,message:'密码至少 6 位'}]"><a-input-password v-model:value="form.password" /></a-form-item><a-form-item label="角色" name="role_code" :rules="[{required:true}]"><a-select v-model:value="form.role_code" :options="roleOptions" /></a-form-item><a-button type="primary" html-type="submit" block :loading="saving">创建成员</a-button></a-form></a-drawer>
<a-modal :open="Boolean(editing)" title="调整成员权限" ok-text="保存" :confirm-loading="saving" @ok="update" @cancel="editing=null"><div v-if="editing" class="editing-name"><b>{{ editing.display_name }}</b><span>{{ editing.username }}</span></div><a-form layout="vertical"><a-form-item label="角色"><a-select v-model:value="editForm.role_code" :options="roleOptions" /></a-form-item><a-form-item label="账号状态"><a-segmented v-model:value="editForm.status" :options="[{value:'active',label:'使用中'},{value:'disabled',label:'停用'}]" /></a-form-item></a-form></a-modal>
</div>
</template>
<style scoped>
.members-page{max-width:1180px}.member-summary{min-height:86px;display:grid;grid-template-columns:38px 90px 1fr;align-items:center;gap:14px;margin-bottom:16px;padding:16px 20px;color:white;background:#202825;border-radius:6px}.member-summary>span{font-size:25px;color:#6bc5a7}.member-summary>div{display:flex;flex-direction:column}.member-summary b{font-family:"Noto Serif SC",serif;font-size:24px}.member-summary div span{color:#a9b8b2;font-size:11px}.member-summary p{margin:0;color:#b6c3be;line-height:1.7}.member-cell{display:flex;align-items:center;gap:11px}.member-cell>span{width:34px;height:34px;display:grid;place-items:center;color:#17664f;background:#e5f2ed;border-radius:4px;font-weight:700}.member-cell>div{display:flex;flex-direction:column}.member-cell small{margin-top:3px;color:var(--muted)}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.editing-name{display:flex;flex-direction:column;margin-bottom:18px;padding:12px 14px;background:#f4f7f5;border-left:3px solid var(--green)}.editing-name span{margin-top:4px;color:var(--muted);font-size:12px}@media(max-width:650px){.member-summary{grid-template-columns:34px 1fr}.member-summary p{grid-column:1/3}.form-grid{grid-template-columns:1fr;gap:0}}
</style>