feat(web): adopt vuesax icons and shared skeleton loading
Replace the inline SVG icon set with the vuesax linear pack, add skeleton primitives for loading states, and keep page scroll inside the app shell. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,26 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
Alert as AAlert,
|
||||
Breadcrumb as ABreadcrumb,
|
||||
BreadcrumbItem as ABreadcrumbItem,
|
||||
Button as AButton,
|
||||
Card as ACard,
|
||||
Col as ACol,
|
||||
Divider as ADivider,
|
||||
Flex as AFlex,
|
||||
Form as AForm,
|
||||
FormItem as AFormItem,
|
||||
Input as AInput,
|
||||
Modal,
|
||||
Row as ARow,
|
||||
Space as ASpace,
|
||||
Textarea as ATextarea,
|
||||
Typography,
|
||||
} from 'ant-design-vue';
|
||||
import { CheckOutlined, DeleteOutlined } from '@/icons/ant-bridge';
|
||||
import { CrmSkeleton, CrmSkeletonCard } from '@/components/skeleton';
|
||||
import { api } from '@/lib/api';
|
||||
import type { CompanyDto, CreateCompanyRequest } from '@mento/shared';
|
||||
import CustomFieldsSection from '@/components/CustomFieldsSection.vue';
|
||||
@@ -10,15 +30,8 @@ import RelatedTasksPanel from '@/components/routines/RelatedTasksPanel.vue';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import CustomerGroupPicker from '@/components/CustomerGroupPicker.vue';
|
||||
import { formatLocalDateInput, parseLocalInput } from '@/lib/jalali';
|
||||
import {
|
||||
UiButton,
|
||||
UiCard,
|
||||
UiField,
|
||||
UiInput,
|
||||
UiPageHeader,
|
||||
UiTextarea,
|
||||
} from '@/components/ui';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
|
||||
const { Title: ATypographyTitle, Text: ATypographyText } = Typography;
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -181,13 +194,22 @@ async function submit() {
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!id.value || !confirm('این شرکت حذف شود؟ افراد متصل بدون شرکت میمانند.')) return;
|
||||
try {
|
||||
await api.delete(`/companies/${id.value}`);
|
||||
await router.push({ name: 'contacts', query: { tab: 'companies' } });
|
||||
} catch {
|
||||
error.value = 'حذف ناموفق بود';
|
||||
}
|
||||
if (!id.value) return;
|
||||
Modal.confirm({
|
||||
title: 'حذف شرکت',
|
||||
content: 'این شرکت حذف شود؟ افراد متصل بدون شرکت میمانند.',
|
||||
okText: 'حذف',
|
||||
okType: 'danger',
|
||||
cancelText: 'انصراف',
|
||||
async onOk() {
|
||||
try {
|
||||
await api.delete(`/companies/${id.value}`);
|
||||
await router.push({ name: 'contacts', query: { tab: 'companies' } });
|
||||
} catch {
|
||||
error.value = 'حذف ناموفق بود';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
@@ -202,232 +224,256 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="crm-page company-form-page" :class="{ embedded }" dir="rtl">
|
||||
<UiPageHeader
|
||||
<a-flex vertical :gap="16" dir="rtl">
|
||||
<a-flex
|
||||
v-if="!embedded"
|
||||
:title="pageTitle"
|
||||
description="ثبت و ویرایش مشخصات حقوقی، اطلاعات تماس و فیلدهای سفارشی شرکت"
|
||||
justify="space-between"
|
||||
align="center"
|
||||
wrap="wrap"
|
||||
:gap="12"
|
||||
>
|
||||
<template #breadcrumb>
|
||||
<RouterLink :to="{ name: 'contacts', query: { tab: 'companies' } }" class="breadcrumb-link">
|
||||
شرکتها
|
||||
</RouterLink>
|
||||
<span class="breadcrumb-separator">/</span>
|
||||
<span>{{ pageTitle }}</span>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<UiButton :loading="saving" @click="submit">
|
||||
<template #icon>
|
||||
<UiIcon name="check-plain" />
|
||||
</template>
|
||||
<a-flex vertical :gap="4">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
<RouterLink :to="{ name: 'contacts', query: { tab: 'companies' } }">
|
||||
شرکتها
|
||||
</RouterLink>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>{{ pageTitle }}</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
<a-typography-title :level="3" :style="{ margin: 0 }">
|
||||
{{ pageTitle }}
|
||||
</a-typography-title>
|
||||
<a-typography-text type="secondary">
|
||||
ثبت و ویرایش مشخصات حقوقی، اطلاعات تماس و فیلدهای سفارشی شرکت
|
||||
</a-typography-text>
|
||||
</a-flex>
|
||||
<a-space :size="8" wrap>
|
||||
<a-button type="primary" :loading="saving" @click="submit">
|
||||
<template #icon><CheckOutlined /></template>
|
||||
ذخیره
|
||||
</UiButton>
|
||||
<UiButton variant="ghost" :disabled="saving" @click="cancel">
|
||||
انصراف
|
||||
</UiButton>
|
||||
<UiButton v-if="!isNew" variant="danger" :disabled="saving" @click="remove">
|
||||
<template #icon>
|
||||
<UiIcon name="trash" />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button :disabled="saving" @click="cancel">انصراف</a-button>
|
||||
<a-button
|
||||
v-if="!isNew"
|
||||
danger
|
||||
:disabled="saving"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
حذف
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiPageHeader>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-flex>
|
||||
|
||||
<div v-if="error" class="crm-alert crm-alert-danger">{{ error }}</div>
|
||||
<a-alert v-if="error" type="error" show-icon :message="error" />
|
||||
|
||||
<form class="crm-form" @submit.prevent="submit">
|
||||
<UiCard title="اطلاعات اصلی شرکت" class="section-card">
|
||||
<div class="crm-form-grid-2">
|
||||
<UiField label="نام شرکت" required class="col-span-2">
|
||||
<UiInput
|
||||
id="name"
|
||||
v-model="name"
|
||||
required
|
||||
minlength="2"
|
||||
placeholder="مثال: دادهپردازی سپهر"
|
||||
/>
|
||||
</UiField>
|
||||
<CrmSkeleton :loading="loading">
|
||||
<CrmSkeletonCard :rows="8">
|
||||
<a-form layout="vertical" @finish="submit">
|
||||
<a-card :bordered="false">
|
||||
<a-divider orientation="right">اطلاعات اصلی شرکت</a-divider>
|
||||
<a-row :gutter="[16, 0]">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="نام شرکت" required>
|
||||
<a-input
|
||||
id="name"
|
||||
v-model:value="name"
|
||||
:minlength="2"
|
||||
placeholder="مثال: دادهپردازی سپهر"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="کد شرکت">
|
||||
<a-input id="code" v-model:value="code" placeholder="کد داخلی" dir="ltr" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="کد دیدار شرکت">
|
||||
<a-input
|
||||
id="sourceRef"
|
||||
v-model:value="sourceRef"
|
||||
placeholder="مثال: 5277"
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="گروههای شرکت">
|
||||
<CustomerGroupPicker v-model="groupIds" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="تاریخ ثبت شرکت">
|
||||
<JalaliDateInput id="registeredAt" v-model="registeredAt" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="برچسبها">
|
||||
<a-input
|
||||
id="company-tags"
|
||||
v-model:value="tagsRaw"
|
||||
placeholder="شرکتی، VIP (با کاما جدا کنید)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="توضیحات شرکت">
|
||||
<a-textarea
|
||||
id="notes"
|
||||
v-model:value="notes"
|
||||
:rows="4"
|
||||
placeholder="توضیحات مهم، حوزههای همکاری..."
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<UiField label="کد شرکت">
|
||||
<UiInput id="code" v-model="code" placeholder="کد داخلی" dir="ltr" />
|
||||
</UiField>
|
||||
<a-card :bordered="false" :style="{ marginTop: '16px' }">
|
||||
<a-divider orientation="right">اطلاعات تماس و آدرس</a-divider>
|
||||
<a-row :gutter="[16, 0]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="تلفن شرکت">
|
||||
<a-input
|
||||
id="phone"
|
||||
v-model:value="phone"
|
||||
type="tel"
|
||||
placeholder="۰۲۱..."
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="تلفن های دیگر شرکت">
|
||||
<a-input
|
||||
id="otherPhones"
|
||||
v-model:value="otherPhones"
|
||||
placeholder="با کاما جدا کنید"
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="ایمیل شرکت">
|
||||
<a-input
|
||||
id="email"
|
||||
v-model:value="email"
|
||||
type="email"
|
||||
placeholder="info@company.ir"
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="ایمیل های دیگر شرکت">
|
||||
<a-input
|
||||
id="otherEmails"
|
||||
v-model:value="otherEmails"
|
||||
placeholder="با کاما جدا کنید"
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="شماره فکس">
|
||||
<a-input id="fax" v-model:value="fax" type="tel" dir="ltr" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="سایت های شرکت">
|
||||
<a-input
|
||||
id="website"
|
||||
v-model:value="website"
|
||||
placeholder="https://..."
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="استان">
|
||||
<a-input id="province" v-model:value="province" placeholder="مثال: تهران" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="شهرستان">
|
||||
<a-input id="city" v-model:value="city" placeholder="مثال: تهران" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="کد پستی">
|
||||
<a-input id="postalCode" v-model:value="postalCode" dir="ltr" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="آدرس">
|
||||
<a-input
|
||||
id="address"
|
||||
v-model:value="address"
|
||||
placeholder="نشانی، طبقه، پلاک..."
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<UiField label="کد دیدار شرکت">
|
||||
<UiInput id="sourceRef" v-model="sourceRef" placeholder="مثال: 5277" dir="ltr" />
|
||||
</UiField>
|
||||
<a-card :bordered="false" :style="{ marginTop: '16px' }">
|
||||
<a-divider orientation="right">اطلاعات حقوقی و ثبتی</a-divider>
|
||||
<a-row :gutter="[16, 0]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="شناسه ملی">
|
||||
<a-input
|
||||
id="nationalId"
|
||||
v-model:value="nationalId"
|
||||
placeholder="شناسه ملی ۱۱ رقمی"
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="شماره اقتصادی">
|
||||
<a-input id="economicCode" v-model:value="economicCode" dir="ltr" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="شماره ثبت">
|
||||
<a-input
|
||||
id="registrationNumber"
|
||||
v-model:value="registrationNumber"
|
||||
dir="ltr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<UiField label="گروههای شرکت" class="col-span-2">
|
||||
<CustomerGroupPicker v-model="groupIds" />
|
||||
</UiField>
|
||||
<CustomFieldsSection entity-type="company" v-model="customFields" />
|
||||
<RelatedTasksPanel
|
||||
v-if="!isNew && id"
|
||||
:entity="{ type: 'company', id, label: name }"
|
||||
/>
|
||||
<EntityTimeline v-if="!isNew && id" entity-type="company" :entity-id="id" />
|
||||
|
||||
<UiField label="تاریخ ثبت شرکت">
|
||||
<JalaliDateInput id="registeredAt" v-model="registeredAt" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="برچسبها" class="col-span-2">
|
||||
<UiInput
|
||||
id="company-tags"
|
||||
v-model="tagsRaw"
|
||||
placeholder="شرکتی، VIP (با کاما جدا کنید)"
|
||||
/>
|
||||
</UiField>
|
||||
|
||||
<UiField label="توضیحات شرکت" class="col-span-2">
|
||||
<UiTextarea
|
||||
id="notes"
|
||||
v-model="notes"
|
||||
:rows="4"
|
||||
placeholder="توضیحات مهم، حوزههای همکاری..."
|
||||
/>
|
||||
</UiField>
|
||||
</div>
|
||||
</UiCard>
|
||||
|
||||
<UiCard title="اطلاعات تماس و آدرس" class="section-card">
|
||||
<div class="crm-form-grid-2">
|
||||
<UiField label="تلفن شرکت">
|
||||
<UiInput id="phone" v-model="phone" type="tel" placeholder="۰۲۱..." dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="تلفن های دیگر شرکت">
|
||||
<UiInput id="otherPhones" v-model="otherPhones" placeholder="با کاما جدا کنید" dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="ایمیل شرکت">
|
||||
<UiInput
|
||||
id="email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="info@company.ir"
|
||||
dir="ltr"
|
||||
/>
|
||||
</UiField>
|
||||
|
||||
<UiField label="ایمیل های دیگر شرکت">
|
||||
<UiInput id="otherEmails" v-model="otherEmails" placeholder="با کاما جدا کنید" dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="شماره فکس">
|
||||
<UiInput id="fax" v-model="fax" type="tel" dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="سایت های شرکت">
|
||||
<UiInput id="website" v-model="website" placeholder="https://..." dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="استان">
|
||||
<UiInput id="province" v-model="province" placeholder="مثال: تهران" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="شهرستان">
|
||||
<UiInput id="city" v-model="city" placeholder="مثال: تهران" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="کد پستی">
|
||||
<UiInput id="postalCode" v-model="postalCode" dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="آدرس" class="col-span-2">
|
||||
<UiInput id="address" v-model="address" placeholder="نشانی، طبقه، پلاک..." />
|
||||
</UiField>
|
||||
</div>
|
||||
</UiCard>
|
||||
|
||||
<UiCard title="اطلاعات حقوقی و ثبتی" class="section-card">
|
||||
<div class="crm-form-grid-2">
|
||||
<UiField label="شناسه ملی">
|
||||
<UiInput id="nationalId" v-model="nationalId" placeholder="شناسه ملی ۱۱ رقمی" dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="شماره اقتصادی">
|
||||
<UiInput id="economicCode" v-model="economicCode" dir="ltr" />
|
||||
</UiField>
|
||||
|
||||
<UiField label="شماره ثبت">
|
||||
<UiInput id="registrationNumber" v-model="registrationNumber" dir="ltr" />
|
||||
</UiField>
|
||||
</div>
|
||||
</UiCard>
|
||||
|
||||
<CustomFieldsSection entity-type="company" v-model="customFields" />
|
||||
<RelatedTasksPanel v-if="!isNew && id" :entity="{ type: 'company', id, label: name }" />
|
||||
<EntityTimeline v-if="!isNew && id" entity-type="company" :entity-id="id" />
|
||||
|
||||
<div class="form-bottom-actions">
|
||||
<UiButton type="submit" :loading="saving">
|
||||
<template #icon>
|
||||
<UiIcon name="check-plain" />
|
||||
</template>
|
||||
ذخیره اطلاعات
|
||||
</UiButton>
|
||||
<UiButton variant="ghost" type="button" :disabled="saving" @click="cancel">
|
||||
انصراف
|
||||
</UiButton>
|
||||
<UiButton v-if="!isNew" variant="danger" type="button" :disabled="saving" @click="remove">
|
||||
حذف شرکت
|
||||
</UiButton>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<a-flex justify="flex-end" :style="{ marginTop: '16px' }">
|
||||
<a-space :size="8" wrap>
|
||||
<a-button type="primary" html-type="submit" :loading="saving">
|
||||
<template #icon><CheckOutlined /></template>
|
||||
ذخیره اطلاعات
|
||||
</a-button>
|
||||
<a-button :disabled="saving" @click="cancel">انصراف</a-button>
|
||||
<a-button
|
||||
v-if="!isNew"
|
||||
danger
|
||||
:disabled="saving"
|
||||
@click="remove"
|
||||
>
|
||||
حذف شرکت
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-flex>
|
||||
</a-form>
|
||||
</CrmSkeletonCard>
|
||||
</CrmSkeleton>
|
||||
</a-flex>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.company-form-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-6);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.company-form-page.embedded {
|
||||
max-width: none;
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
|
||||
.embedded-actions {
|
||||
display: flex;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.breadcrumb-link {
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb-link:hover {
|
||||
color: var(--color-brand-600);
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
margin: 0 var(--sp-2);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.section-card {
|
||||
padding: var(--sp-6);
|
||||
}
|
||||
|
||||
.col-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.form-bottom-actions {
|
||||
display: flex;
|
||||
gap: var(--sp-3);
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--sp-4);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.col-span-2 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.form-bottom-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user