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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user