Files
Lucky_Wheel/apps/admin/src/views/managers/AdminFormDrawer.vue
T

390 lines
12 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import {
ApartmentOutlined,
KeyOutlined,
SafetyCertificateOutlined,
SaveOutlined,
UserOutlined,
} from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { apiClient } from '../../services/api/client';
const props = defineProps<{
open: boolean;
adminId?: string | null;
}>();
const emit = defineEmits<{
(e: 'update:open', val: boolean): void;
(e: 'saved'): void;
}>();
const loading = ref(false);
const submitting = ref(false);
const rolesList = ref<any[]>([]);
const workspacesList = ref<any[]>([]);
const campaignsList = ref<any[]>([]);
const formState = reactive({
name: '',
email: '',
mobile: '',
password: '',
status: 'ACTIVE',
isSuperAdmin: false,
roleIds: [] as string[],
workspaceId: '',
scopeType: 'ALL',
specificCampaignIds: [] as string[],
});
const isEdit = computed(() => !!props.adminId);
async function loadMetadata() {
try {
const [rolesRes, wsRes, campRes] = await Promise.all([
apiClient.get('/admin/roles'),
apiClient.get('/admin/workspaces'),
apiClient.get('/admin/campaigns', { params: { pageSize: 100 } }),
]);
rolesList.value = Array.isArray(rolesRes.data) ? rolesRes.data : rolesRes.data?.data || [];
workspacesList.value = Array.isArray(wsRes.data) ? wsRes.data : wsRes.data?.data || [];
campaignsList.value = Array.isArray(campRes.data) ? campRes.data : campRes.data?.data || [];
} catch (err) {
// Handled
}
}
async function loadAdminData(id: string) {
loading.value = true;
try {
const res = await apiClient.get(`/admin/managers/${id}`);
const a = res.data;
formState.name = a.name || '';
formState.email = a.email || '';
formState.mobile = a.mobile || '';
formState.password = '';
formState.status = a.status || 'ACTIVE';
formState.isSuperAdmin = !!a.isSuperAdmin;
formState.roleIds = a.roles ? a.roles.map((r: any) => r.role?.id || r.roleId) : [];
if (a.workspaceAssignments && a.workspaceAssignments.length > 0) {
const wa = a.workspaceAssignments[0];
formState.workspaceId = wa.workspaceId || wa.workspace?.id || '';
formState.scopeType = wa.scopeType || 'ALL';
formState.specificCampaignIds = Array.isArray(wa.specificCampaignIds) ? wa.specificCampaignIds : [];
} else {
formState.workspaceId = workspacesList.value[0]?.id || '';
formState.scopeType = 'ALL';
formState.specificCampaignIds = [];
}
} finally {
loading.value = false;
}
}
function resetForm() {
formState.name = '';
formState.email = '';
formState.mobile = '';
formState.password = '';
formState.status = 'ACTIVE';
formState.isSuperAdmin = false;
formState.roleIds = [];
formState.workspaceId = workspacesList.value[0]?.id || '';
formState.scopeType = 'ALL';
formState.specificCampaignIds = [];
}
watch(
() => props.open,
(val) => {
if (val) {
loadMetadata();
if (props.adminId) {
loadAdminData(props.adminId);
} else {
resetForm();
}
}
},
);
async function onSubmit() {
if (!formState.name.trim()) {
message.error('لطفاً نام و نام خانوادگی مدیر را وارد کنید.');
return;
}
if (!formState.email.trim() || !formState.email.includes('@')) {
message.error('لطفاً یک آدرس ایمیل معتبر وارد کنید.');
return;
}
if (!formState.mobile.trim() || !/^09\d{9}$/.test(formState.mobile.trim())) {
message.error('شماره موبایل باید ۱۱ رقمی بوده و با ۰۹ شروع شود.');
return;
}
if (!isEdit.value && (!formState.password || formState.password.length < 6)) {
message.error('کلمه عبور باید حداقل ۶ کاراکتر باشد.');
return;
}
submitting.value = true;
try {
const payload: any = {
name: formState.name.trim(),
email: formState.email.trim().toLowerCase(),
mobile: formState.mobile.trim(),
status: formState.status,
isSuperAdmin: formState.isSuperAdmin,
roleIds: formState.roleIds,
};
if (formState.password) {
payload.password = formState.password;
}
if (formState.workspaceId) {
payload.workspaceAssignments = [
{
workspaceId: formState.workspaceId,
scopeType: formState.scopeType,
specificCampaignIds:
formState.scopeType === 'CUSTOM_CAMPAIGNS' ? formState.specificCampaignIds : undefined,
},
];
}
if (isEdit.value && props.adminId) {
await apiClient.patch(`/admin/managers/${props.adminId}`, payload);
message.success('مشخصات مدیر با موفقیت به‌روزرسانی شد.');
} else {
await apiClient.post('/admin/managers', payload);
message.success('مدیر جدید با موفقیت ایجاد شد.');
}
emit('saved');
emit('update:open', false);
} finally {
submitting.value = false;
}
}
</script>
<template>
<a-drawer
:open="open"
:title="isEdit ? 'ویرایش اطلاعات مدیر' : 'افزودن مدیر جدید به سامانه'"
:width="540"
placement="right"
:maskClosable="false"
rootClassName="admin-form-drawer-rtl"
@close="emit('update:open', false)"
>
<a-spin :spinning="loading">
<a-form layout="vertical">
<a-tabs defaultActiveKey="1" class="admin-form-tabs">
<!-- TAB 1: اطلاعات فردی و ورود -->
<a-tab-pane key="1">
<template #tab>
<span><UserOutlined /> اطلاعات هویتی</span>
</template>
<a-form-item label="نام و نام خانوادگی" required>
<a-input
v-model:value="formState.name"
placeholder="مثال: علی رضایی"
size="large"
/>
</a-form-item>
<a-form-item label="شماره موبایل سازمانی" required>
<a-input
v-model:value="formState.mobile"
placeholder="09xxxxxxxxx"
dir="ltr"
size="large"
maxLength="11"
/>
</a-form-item>
<a-form-item label="ایمیل سازمانی (نام کاربری ورود)" required>
<a-input
v-model:value="formState.email"
placeholder="admin@example.com"
dir="ltr"
size="large"
/>
</a-form-item>
<a-form-item
:label="isEdit ? 'کلمه عبور جدید (در صورت نیاز به تغییر)' : 'کلمه عبور ورود'"
:required="!isEdit"
>
<a-input-password
v-model:value="formState.password"
placeholder="حداقل ۶ کاراکتر"
size="large"
dir="ltr"
/>
</a-form-item>
<a-form-item label="وضعیت حساب کاربری">
<a-radio-group v-model:value="formState.status" buttonStyle="solid">
<a-radio-button value="ACTIVE">فعال</a-radio-button>
<a-radio-button value="DISABLED">غیرفعال / مسدود</a-radio-button>
</a-radio-group>
</a-form-item>
</a-tab-pane>
<!-- TAB 2: نقش‌ها و اختیارات -->
<a-tab-pane key="2">
<template #tab>
<span><SafetyCertificateOutlined /> نقش‌ها و اختیارات</span>
</template>
<div class="super-admin-banner">
<a-checkbox v-model:checked="formState.isSuperAdmin">
<strong style="color: #7c3aed; font-size: 15px">مدیر ارشد سامانه (Super Admin)</strong>
</a-checkbox>
<p class="banner-sub">
مدیر ارشد دسترسی کامل و بی‌قیدوشرط به تمامی بخش‌ها، ایجاد مدیران و تنظیمات کلی سامانه دارد.
</p>
</div>
<a-divider style="margin: 16px 0" />
<a-form-item label="نقش‌های سازمانی (می‌توانید چند نقش همزمان انتخاب کنید)">
<div v-if="rolesList.length === 0" style="color: #94a3b8">
در حال بارگذاری نقش‌ها...
</div>
<a-checkbox-group
v-else
v-model:value="formState.roleIds"
style="width: 100%"
:disabled="formState.isSuperAdmin"
>
<a-row :gutter="[12, 12]">
<a-col :span="24" v-for="r in rolesList" :key="r.id">
<div class="role-item-card" :class="{ 'is-selected': formState.roleIds.includes(r.id) }">
<a-checkbox :value="r.id">
<span class="role-name">{{ r.name }}</span>
<a-tag v-if="r.isSystem" color="blue" size="small" style="margin-right: 6px">سیستمی</a-tag>
</a-checkbox>
<div class="role-desc">{{ r.description || 'بدون توضیح' }}</div>
</div>
</a-col>
</a-row>
</a-checkbox-group>
</a-form-item>
</a-tab-pane>
<!-- TAB 3: فضاهای کاری و اسکوپ دسترسی -->
<a-tab-pane key="3">
<template #tab>
<span><ApartmentOutlined /> قلمرو و فضای کاری</span>
</template>
<a-form-item label="فضای کاری مجاز">
<a-select
v-model:value="formState.workspaceId"
placeholder="انتخاب فضای کاری"
size="large"
style="width: 100%"
>
<a-select-option v-for="ws in workspacesList" :key="ws.id" :value="ws.id">
{{ ws.name }} ({{ ws.slug }})
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="قلمرو دسترسی کمپین‌ها (Scope)">
<a-radio-group v-model:value="formState.scopeType" style="margin-bottom: 12px">
<a-radio value="ALL">دسترسی به تمامی کمپین‌های این فضای کاری</a-radio>
<a-radio value="CUSTOM_CAMPAIGNS">محدود به کمپین‌های خاص</a-radio>
</a-radio-group>
<div v-if="formState.scopeType === 'CUSTOM_CAMPAIGNS'" style="margin-top: 8px">
<a-select
v-model:value="formState.specificCampaignIds"
mode="multiple"
placeholder="کمپین‌های مجاز را انتخاب کنید"
size="large"
style="width: 100%"
>
<a-select-option v-for="c in campaignsList" :key="c.id" :value="c.id">
{{ c.title }}
</a-select-option>
</a-select>
</div>
</a-form-item>
</a-tab-pane>
</a-tabs>
</a-form>
</a-spin>
<template #footer>
<div style="display: flex; justify-content: flex-end; gap: 8px">
<a-button @click="emit('update:open', false)">انصراف</a-button>
<a-button type="primary" :loading="submitting" @click="onSubmit">
<template #icon><SaveOutlined /></template>
{{ isEdit ? 'ذخیره تغییرات' : 'ثبت مدیر' }}
</a-button>
</div>
</template>
</a-drawer>
</template>
<style scoped>
.super-admin-banner {
background: #f5f3ff;
border: 1px solid #ddd6fe;
border-radius: 8px;
padding: 12px 16px;
}
.banner-sub {
margin: 6px 0 0 24px;
font-size: 12px;
color: #6b7280;
line-height: 1.6;
}
.role-item-card {
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
transition: all 0.2s ease;
background: #fff;
}
.role-item-card.is-selected {
border-color: #3b82f6;
background: #eff6ff;
}
.role-name {
font-weight: 600;
font-size: 14px;
color: #1e293b;
}
.role-desc {
margin-right: 24px;
font-size: 12px;
color: #64748b;
margin-top: 2px;
}
:deep(.ant-drawer-content) {
direction: rtl;
text-align: right;
}
:deep(.ant-drawer-header) {
direction: rtl;
text-align: right;
}
:deep(.ant-drawer-title) {
text-align: right;
}
:deep(.ant-drawer-close) {
margin-right: 0;
margin-left: var(--ant-margin-xs, 8px);
}
</style>