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
+56 -64
View File
@@ -1,5 +1,12 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { Button as AButton, Input as AInput } from 'ant-design-vue';
import {
CalendarOutlined,
ClockCircleOutlined,
LeftOutlined,
RightOutlined,
} from '@ant-design/icons-vue';
import {
addJalaliMonths,
dateToJalali,
@@ -12,7 +19,6 @@ import {
parseLocalInput,
toFaDigits,
} from '@/lib/jalali';
import UiIcon from '@/components/UiIcon.vue';
const model = defineModel<string>({ default: '' });
const props = withDefaults(
@@ -39,7 +45,7 @@ const WEEKDAYS = ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج'] as const;
const MOBILE_MQ = '(max-width: 720px)';
const open = ref(false);
const triggerRef = ref<HTMLButtonElement | null>(null);
const triggerRef = ref<HTMLElement | null>(null);
const panelRef = ref<HTMLElement | null>(null);
const clockRef = ref<HTMLElement | null>(null);
const hourColRef = ref<HTMLElement | null>(null);
@@ -439,41 +445,28 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="wrap" :class="{ disabled, 'time-only': mode === 'time' }">
<button
<div
ref="triggerRef"
class="wrap"
:class="{ disabled, 'time-only': mode === 'time' }"
style="width: 100%"
>
<a-input
:id="id"
ref="triggerRef"
class="trigger"
:class="{ 'clock-trigger': mode === 'time' }"
type="button"
readonly
:value="display"
:placeholder="emptyLabel"
:disabled="disabled"
:aria-expanded="open"
:aria-required="required || undefined"
style="width: 100%; cursor: pointer"
@click="toggle"
>
<template v-if="mode === 'time'">
<span class="clock-face" aria-hidden="true">
<i
class="hand hour-hand"
:style="{
transform: `rotate(${(draftHour % 12) * 30 + draftMinute * 0.5}deg)`,
}"
/>
<i
class="hand minute-hand"
:style="{ transform: `rotate(${draftMinute * 6}deg)` }"
/>
<i class="hub" />
</span>
<span class="clock-text" :class="{ empty: !display }">
{{ display || emptyLabel }}
</span>
<template #suffix>
<ClockCircleOutlined v-if="triggerIcon === 'clock'" />
<CalendarOutlined v-else />
</template>
<template v-else>
<span :class="{ empty: !display }">{{ display || emptyLabel }}</span>
<UiIcon :name="triggerIcon" :size="18" />
</template>
</button>
</a-input>
<Teleport to="body">
<div
@@ -490,13 +483,13 @@ onBeforeUnmount(() => {
:style="{ top: `${top}px`, left: `${left}px` }"
>
<header v-if="mode !== 'time'" class="nav">
<button type="button" class="nav-btn" aria-label="ماه بعد" @click="goMonth(1)">
<UiIcon name="chevron" :size="16" />
</button>
<a-button type="text" class="nav-btn" aria-label="ماه بعد" @click="goMonth(1)">
<template #icon><RightOutlined /></template>
</a-button>
<strong>{{ monthTitle }}</strong>
<button type="button" class="nav-btn next" aria-label="ماه قبل" @click="goMonth(-1)">
<UiIcon name="chevron" :size="16" />
</button>
<a-button type="text" class="nav-btn next" aria-label="ماه قبل" @click="goMonth(-1)">
<template #icon><LeftOutlined /></template>
</a-button>
</header>
<template v-if="mode !== 'time'">
@@ -504,16 +497,16 @@ onBeforeUnmount(() => {
<span v-for="day in WEEKDAYS" :key="day">{{ day }}</span>
</div>
<div class="grid">
<button
<a-button
v-for="cell in cells"
:key="cell.key"
type="button"
type="text"
class="day"
:class="{ mute: !cell.inMonth, today: cell.isToday, on: cell.isSelected }"
@click="pickDay(cell)"
>
{{ toFaDigits(cell.jd) }}
</button>
</a-button>
</div>
</template>
@@ -521,25 +514,25 @@ onBeforeUnmount(() => {
<template v-if="isMobile">
<p class="md-label">{{ mode === 'time' ? 'انتخاب ساعت' : 'ساعت' }}</p>
<div class="md-digits" dir="ltr">
<button
type="button"
<a-button
type="text"
class="md-chip"
:class="{ on: clockFace === 'hour' }"
aria-label="ساعت"
@click="clockFace = 'hour'"
>
{{ faPad(draftHour) }}
</button>
</a-button>
<span class="md-colon">:</span>
<button
type="button"
<a-button
type="text"
class="md-chip"
:class="{ on: clockFace === 'minute' }"
aria-label="دقیقه"
@click="clockFace = 'minute'"
>
{{ faPad(draftMinute) }}
</button>
</a-button>
</div>
<div
ref="clockRef"
@@ -561,10 +554,10 @@ onBeforeUnmount(() => {
</span>
<i class="md-hub" />
<template v-if="clockFace === 'hour'">
<button
<a-button
v-for="h in hours"
:key="`h-${h}`"
type="button"
type="text"
class="md-tick"
:class="{ on: draftHour === h, inner: h >= 12 }"
:style="hourDialStyle(h)"
@@ -572,13 +565,13 @@ onBeforeUnmount(() => {
@click.stop="setHour(h)"
>
{{ toFaDigits(h) }}
</button>
</a-button>
</template>
<template v-else>
<button
<a-button
v-for="m in clockMinutes"
:key="`m-${m}`"
type="button"
type="text"
class="md-tick"
:class="{ on: draftMinute === m }"
:style="minuteDialStyle(m)"
@@ -586,7 +579,7 @@ onBeforeUnmount(() => {
@click.stop="setMinute(m)"
>
{{ faPad(m) }}
</button>
</a-button>
</template>
</div>
</template>
@@ -596,31 +589,31 @@ onBeforeUnmount(() => {
<div class="col">
<span class="col-label">ساعت</span>
<div ref="hourColRef" class="col-list" role="listbox" aria-label="ساعت">
<button
<a-button
v-for="h in hours"
:key="`col-h-${h}`"
type="button"
type="text"
class="col-item"
:class="{ on: draftHour === h }"
@click="setHour(h)"
>
{{ faPad(h) }}
</button>
</a-button>
</div>
</div>
<div class="col">
<span class="col-label">دقیقه</span>
<div ref="minuteColRef" class="col-list" role="listbox" aria-label="دقیقه">
<button
<a-button
v-for="m in columnMinutes"
:key="`col-m-${m}`"
type="button"
type="text"
class="col-item"
:class="{ on: draftMinute === m }"
@click="setMinute(m)"
>
{{ faPad(m) }}
</button>
</a-button>
</div>
</div>
</div>
@@ -628,18 +621,17 @@ onBeforeUnmount(() => {
</div>
<footer class="foot">
<button type="button" class="link" @click="pickToday">
<a-button type="link" @click="pickToday">
{{ mode === 'time' ? 'الان' : 'امروز' }}
</button>
<button
</a-button>
<a-button
v-if="clearable && !required && model"
type="button"
class="link"
type="link"
@click="clear"
>
پاک کردن
</button>
<button type="button" class="done" @click="hide">تأیید</button>
</a-button>
<a-button type="primary" @click="hide">تأیید</a-button>
</footer>
</div>
</Teleport>