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,9 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import {
|
||||
Button as AButton,
|
||||
Checkbox as ACheckbox,
|
||||
Flex as AFlex,
|
||||
Popover as APopover,
|
||||
Typography,
|
||||
} from 'ant-design-vue';
|
||||
import { SettingOutlined } from '@ant-design/icons-vue';
|
||||
import { useTableColumns } from '@/lib/tableColumns';
|
||||
import { toFaDigits } from '@/lib/jalali';
|
||||
|
||||
const { Text: ATypographyText } = Typography;
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
tableKey: string;
|
||||
@@ -14,164 +23,49 @@ const props = withDefaults(
|
||||
);
|
||||
|
||||
const open = ref(false);
|
||||
const root = ref<HTMLElement | null>(null);
|
||||
const cols = useTableColumns(() => props.tableKey);
|
||||
|
||||
const columns = computed(() => cols.columns());
|
||||
const hiddenCount = computed(() => cols.hiddenCount());
|
||||
|
||||
function onDocClick(e: MouseEvent) {
|
||||
if (open.value && root.value && !root.value.contains(e.target as Node)) open.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('click', onDocClick));
|
||||
onBeforeUnmount(() => document.removeEventListener('click', onDocClick));
|
||||
const placement = computed(() =>
|
||||
props.align === 'end' ? 'bottomLeft' : 'bottomRight',
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root" class="tc-pop">
|
||||
<button
|
||||
class="tc-tool"
|
||||
type="button"
|
||||
:class="{ active: hiddenCount > 0 }"
|
||||
:title="label"
|
||||
@click.stop="open = !open"
|
||||
>
|
||||
<UiIcon name="sliders" :size="16" />
|
||||
<span class="tc-label">{{ label }}</span>
|
||||
<em v-if="hiddenCount" class="tc-badge">{{ toFaDigits(hiddenCount) }} مخفی</em>
|
||||
</button>
|
||||
<div v-if="open" class="tc-menu" :class="align" @click.stop>
|
||||
<p class="tc-head">نمایش ستونها</p>
|
||||
<p v-if="!columns.length" class="tc-empty">ستونی برای نمایش وجود ندارد.</p>
|
||||
<label v-for="col in columns" :key="col" class="tc-item">
|
||||
<input type="checkbox" :checked="cols.isVisible(col)" @change="cols.toggle(col)" />
|
||||
<span>{{ col }}</span>
|
||||
</label>
|
||||
<button v-if="hiddenCount" type="button" class="tc-reset" @click="cols.showAll()">
|
||||
نمایش همه ستونها
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<a-popover v-model:open="open" trigger="click" :placement="placement">
|
||||
<template #content>
|
||||
<a-flex
|
||||
vertical
|
||||
:gap="8"
|
||||
style="min-width: 13rem; max-width: calc(100vw - 1.5rem)"
|
||||
>
|
||||
<a-typography-text type="secondary" strong>
|
||||
نمایش ستونها
|
||||
</a-typography-text>
|
||||
<a-typography-text v-if="!columns.length" type="secondary">
|
||||
ستونی برای نمایش وجود ندارد.
|
||||
</a-typography-text>
|
||||
<a-checkbox
|
||||
v-for="col in columns"
|
||||
:key="col"
|
||||
:checked="cols.isVisible(col)"
|
||||
@click.prevent="cols.toggle(col)"
|
||||
>
|
||||
{{ col }}
|
||||
</a-checkbox>
|
||||
<a-button v-if="hiddenCount" type="link" @click="cols.showAll()">
|
||||
نمایش همه ستونها
|
||||
</a-button>
|
||||
</a-flex>
|
||||
</template>
|
||||
<a-button :type="hiddenCount > 0 ? 'primary' : 'default'" :ghost="hiddenCount > 0">
|
||||
<template #icon><SettingOutlined /></template>
|
||||
{{ label }}
|
||||
<template v-if="hiddenCount">
|
||||
· {{ toFaDigits(hiddenCount) }} مخفی
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tc-pop {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tc-tool {
|
||||
min-height: var(--control-h, 2.4rem);
|
||||
height: var(--control-h, 2.4rem);
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
border-radius: var(--radius);
|
||||
padding: 0 0.75rem;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--ink-secondary);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tc-tool:hover {
|
||||
background: var(--surface-subtle);
|
||||
border-color: var(--line-strong);
|
||||
}
|
||||
|
||||
.tc-tool.active {
|
||||
border-color: #5eead4;
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.tc-badge {
|
||||
font-style: normal;
|
||||
font-size: 0.72rem;
|
||||
background: #f0fdfa;
|
||||
color: #0f766e;
|
||||
border-radius: 999px;
|
||||
padding: 0.05rem 0.45rem;
|
||||
}
|
||||
|
||||
.tc-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
inset-inline-start: 0;
|
||||
z-index: 20;
|
||||
background: #fff;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.14);
|
||||
padding: 0.45rem;
|
||||
min-width: 13rem;
|
||||
max-width: calc(100vw - 1.5rem);
|
||||
max-height: min(24rem, 70vh);
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.tc-menu.end {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
.tc-head {
|
||||
margin: 0 0 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: var(--fs-xs, 0.75rem);
|
||||
color: var(--ink-muted, #64748b);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tc-empty {
|
||||
margin: 0;
|
||||
padding: 0.4rem 0.5rem;
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--ink-muted, #64748b);
|
||||
}
|
||||
|
||||
.tc-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.42rem 0.5rem;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
font-size: var(--fs-sm);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tc-item:hover {
|
||||
background: #f0fdfa;
|
||||
}
|
||||
|
||||
.tc-item input {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
accent-color: #0d9488;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tc-reset {
|
||||
margin-top: 0.3rem;
|
||||
border: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
background: none;
|
||||
padding: 0.5rem;
|
||||
font: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
color: #0f766e;
|
||||
cursor: pointer;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.tc-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user