feat(web): migrate CRM screens to Ant Design Vue with central theme

Adopt ant-design-vue and a shared antdTheme for leads, pipeline, invoices, and shared form controls under strict single-page governance.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-18 19:39:59 +03:30
co-authored by Cursor
parent ab538b8304
commit 4abab55a6b
40 changed files with 7529 additions and 11795 deletions
+69 -102
View File
@@ -1,13 +1,25 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import {
Checkbox as ACheckbox,
Col as ACol,
FormItem as AFormItem,
Input as AInput,
InputNumber as AInputNumber,
Row as ARow,
Select as ASelect,
Spin as ASpin,
Typography,
} from 'ant-design-vue';
import { api } from '@/lib/api';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import { UiField, UiInput, UiSelect } from '@/components/ui';
import type {
CustomFieldDefinitionDto,
CustomFieldEntity,
} from '@mento/shared';
const { Title: ATypographyTitle } = Typography;
const props = defineProps<{
entityType: CustomFieldEntity;
modelValue: Record<string, unknown>;
@@ -46,6 +58,10 @@ function displayValue(key: string) {
return String(v);
}
function selectOptions(field: CustomFieldDefinitionDto) {
return (field.options ?? []).map((opt) => ({ value: opt, label: opt }));
}
watch(
() => props.entityType,
() => {
@@ -57,110 +73,61 @@ onMounted(load);
</script>
<template>
<div v-if="loading" class="muted">بارگذاری فیلدهای سفارشی…</div>
<div v-else-if="fields.length" class="custom-block">
<h3 class="custom-title">فیلدهای سفارشی</h3>
<div class="crm-form-grid-2">
<UiField
v-for="field in fields"
:key="field.id"
:label="field.label"
:required="field.required"
:for-id="`cf-${field.key}`"
>
<UiSelect
v-if="field.fieldType === 'select'"
:id="`cf-${field.key}`"
:model-value="String(displayValue(field.key))"
@update:model-value="setValue(field.key, $event)"
>
<option value="">انتخاب کنید</option>
<option v-for="opt in field.options" :key="opt" :value="opt">
{{ opt }}
</option>
</UiSelect>
<label v-else-if="field.fieldType === 'boolean'" class="cf-check">
<input
<a-spin v-if="loading" />
<template v-else-if="fields.length">
<a-typography-title :level="5" :style="{ marginTop: '16px' }">
فیلدهای سفارشی
</a-typography-title>
<a-row :gutter="[16, 0]">
<a-col v-for="field in fields" :key="field.id" :xs="24" :md="12">
<a-form-item :label="field.label" :required="field.required">
<a-select
v-if="field.fieldType === 'select'"
:id="`cf-${field.key}`"
type="checkbox"
:checked="Boolean(props.modelValue[field.key])"
@change="
setValue(field.key, ($event.target as HTMLInputElement).checked)
"
allow-clear
:value="String(displayValue(field.key)) || undefined"
:options="selectOptions(field)"
placeholder="انتخاب کنید"
style="width: 100%"
@update:value="setValue(field.key, $event ?? '')"
/>
<span>فعال</span>
</label>
<UiInput
v-else-if="field.fieldType === 'number'"
:id="`cf-${field.key}`"
type="number"
:model-value="props.modelValue[field.key] != null ? Number(props.modelValue[field.key]) : ''"
@update:model-value="
setValue(
field.key,
$event === '' || $event == null ? null : Number($event),
)
"
/>
<a-checkbox
v-else-if="field.fieldType === 'boolean'"
:id="`cf-${field.key}`"
:checked="Boolean(props.modelValue[field.key])"
@update:checked="setValue(field.key, $event)"
>
فعال
</a-checkbox>
<JalaliDateInput
v-else-if="field.fieldType === 'date'"
:id="`cf-${field.key}`"
:model-value="String(displayValue(field.key))"
@update:model-value="setValue(field.key, $event || null)"
/>
<a-input-number
v-else-if="field.fieldType === 'number'"
:id="`cf-${field.key}`"
:value="
props.modelValue[field.key] != null
? Number(props.modelValue[field.key])
: undefined
"
style="width: 100%"
@update:value="setValue(field.key, $event == null ? null : Number($event))"
/>
<UiInput
v-else
:id="`cf-${field.key}`"
type="text"
:model-value="String(displayValue(field.key))"
@update:model-value="setValue(field.key, $event)"
/>
</UiField>
</div>
</div>
<JalaliDateInput
v-else-if="field.fieldType === 'date'"
:id="`cf-${field.key}`"
:model-value="String(displayValue(field.key))"
@update:model-value="setValue(field.key, $event || null)"
/>
<a-input
v-else
:id="`cf-${field.key}`"
:value="String(displayValue(field.key))"
@update:value="setValue(field.key, $event)"
/>
</a-form-item>
</a-col>
</a-row>
</template>
</template>
<style scoped>
.custom-block {
display: flex;
flex-direction: column;
gap: var(--sp-3);
padding-top: var(--sp-4);
border-top: 1px dashed var(--line);
width: 100%;
}
.custom-title {
margin: 0;
font-size: var(--fs-sm);
font-weight: var(--fw-semibold);
color: var(--ink-secondary);
}
.muted {
color: var(--muted);
font-size: var(--fs-xs);
}
.cf-check {
display: inline-flex;
align-items: center;
gap: var(--sp-2);
min-height: var(--control-h-md);
font-size: var(--fs-base);
font-weight: var(--fw-medium);
color: var(--ink);
cursor: pointer;
}
.cf-check input[type='checkbox'] {
width: 18px;
height: 18px;
accent-color: var(--accent);
cursor: pointer;
}
</style>