提交
This commit is contained in:
1 parent
19c5aaa8f5
commit
69fa5b1fa8
38 files changed
+15226
-11921
No files matched your search
@@ -180,20 +180,33 @@
|
||||
|
||||
<div class="pl-plan-stop-products-head">
|
||||
<span class="pl-plan-stop-products-head__title">送货产品</span>
|
||||
<el-button
|
||||
v-if="!readonly"
|
||||
class="btn-ghost"
|
||||
size="small"
|
||||
@click="emit('sync-products', index)"
|
||||
>
|
||||
<el-icon><Refresh /></el-icon>从装货同步
|
||||
</el-button>
|
||||
<div v-if="!readonly" class="pl-plan-stop-products-head__actions">
|
||||
<el-button class="btn-ghost" size="small" @click="emit('sync-products', index)">
|
||||
<el-icon><Refresh /></el-icon>从装货同步
|
||||
</el-button>
|
||||
<span class="pl-plan-stop-products-head__add-wrap" @click="handleSelectProductsClick(index)">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="selectProductsDisabled(delivery)"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>选择产品
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="showSplitHint" class="pl-plan-delivery-section__split-hint">
|
||||
多个送货段,请拆分各段数量
|
||||
</p>
|
||||
<p
|
||||
v-if="missingFromLoadHint(delivery)"
|
||||
class="pl-plan-delivery-section__missing-load-hint"
|
||||
>
|
||||
{{ missingFromLoadHint(delivery) }}
|
||||
</p>
|
||||
<p v-if="!delivery.products.length" class="pl-plan-delivery-section__empty-hint">
|
||||
请先在装货区添加产品,将自动同步到送货段
|
||||
可从装货同步,或点击「选择产品」从合同中添加
|
||||
</p>
|
||||
<PlPlanStopProductTable
|
||||
:products="delivery.products"
|
||||
@@ -210,7 +223,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Bottom, Delete, Refresh, Search, Top } from '@element-plus/icons-vue'
|
||||
import { Bottom, Delete, Plus, Refresh, Search, Top } from '@element-plus/icons-vue'
|
||||
import { fetchCustomerOptions } from '@/api/crm/customer'
|
||||
import { customerStopPrefApi, type CustomerStopPref } from '@/api/pl/index'
|
||||
import PlAddressWithLibrary, {
|
||||
@@ -222,8 +235,10 @@
|
||||
createEmptyDeliveryForm,
|
||||
resolveStopDibangDefaults,
|
||||
sumStopProductsPlanQty,
|
||||
type PlanDeliveryForm
|
||||
type PlanDeliveryForm,
|
||||
type PlanStopProductFormRow
|
||||
} from '@/utils/pl-plan-stop-form'
|
||||
import { findDeliveryProductsMissingFromLoad } from '@/utils/pl-plan-contract-product'
|
||||
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
|
||||
import { pickBxData } from '@/utils/http/bx'
|
||||
|
||||
@@ -237,6 +252,8 @@
|
||||
/** 多送货段时提示拆分数量 */
|
||||
showSplitHint?: boolean
|
||||
excludePlanId?: string
|
||||
/** 装货段产品,用于「送货产品不在装货列表」软提醒 */
|
||||
loadProducts?: PlanStopProductFormRow[]
|
||||
}>(),
|
||||
{
|
||||
tabMode: true,
|
||||
@@ -244,7 +261,8 @@
|
||||
compactProducts: true,
|
||||
qtyUnitLabel: '',
|
||||
showSplitHint: false,
|
||||
excludePlanId: ''
|
||||
excludePlanId: '',
|
||||
loadProducts: () => []
|
||||
}
|
||||
)
|
||||
|
||||
@@ -252,6 +270,7 @@
|
||||
'update:modelValue': [value: PlanDeliveryForm[]]
|
||||
'open-customer-picker': [index: number]
|
||||
'sync-products': [index: number]
|
||||
'select-products': [index: number]
|
||||
'add-delivery': []
|
||||
'active-index-change': [index: number]
|
||||
}>()
|
||||
@@ -267,6 +286,27 @@
|
||||
function deliveryContractId(delivery: PlanDeliveryForm) {
|
||||
return String(delivery.contracts?.[0]?.contractId || '').trim()
|
||||
}
|
||||
|
||||
function selectProductsDisabled(delivery: PlanDeliveryForm) {
|
||||
if (isPlInternalCustomerName(delivery.customerName)) return false
|
||||
return !deliveryContractId(delivery)
|
||||
}
|
||||
|
||||
function handleSelectProductsClick(index: number) {
|
||||
const delivery = props.modelValue[index]
|
||||
if (!delivery) return
|
||||
if (selectProductsDisabled(delivery)) {
|
||||
ElMessage.warning('请先选择送货合同后再选择产品')
|
||||
return
|
||||
}
|
||||
emit('select-products', index)
|
||||
}
|
||||
|
||||
function missingFromLoadHint(delivery: PlanDeliveryForm) {
|
||||
const names = findDeliveryProductsMissingFromLoad(delivery.products, props.loadProducts)
|
||||
if (!names.length) return ''
|
||||
return `注意:产品「${names.join('、')}」不在装货产品列表中,请核对(不影响保存/安排)`
|
||||
}
|
||||
const customerOptionsByIndex = ref<Record<number, CustomerOptionRow[]>>({})
|
||||
const customerOptionsLoading = ref<Record<number, boolean>>({})
|
||||
|
||||
@@ -656,12 +696,29 @@
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pl-plan-stop-products-head__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pl-plan-stop-products-head__add-wrap {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.pl-plan-delivery-section__split-hint {
|
||||
margin: 6px 0 0;
|
||||
font-size: 12px;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.pl-plan-delivery-section__missing-load-hint {
|
||||
margin: 6px 0 0;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.pl-plan-delivery-section__empty-hint {
|
||||
margin: 6px 0 0;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
compact-products
|
||||
:exclude-plan-id="planId || ''"
|
||||
@open-customer-picker="openCustomerSelector('load')"
|
||||
@add-products="productSelectorVisible = true"
|
||||
@add-products="openProductSelector('load')"
|
||||
/>
|
||||
</template>
|
||||
<template #delivery-actions>
|
||||
@@ -232,9 +232,11 @@
|
||||
:qty-unit-label="planQtyUnitLabel"
|
||||
:show-split-hint="showDeliverySplitHint"
|
||||
:exclude-plan-id="planId || ''"
|
||||
:load-products="loadForm.products"
|
||||
@active-index-change="deliveryActiveIndex = $event"
|
||||
@open-customer-picker="openCustomerSelector('delivery', $event)"
|
||||
@sync-products="syncDeliveryProductsFromLoadManual"
|
||||
@select-products="openProductSelector('delivery', $event)"
|
||||
/>
|
||||
</template>
|
||||
</PlPlanStopsSplitShell>
|
||||
@@ -517,18 +519,71 @@
|
||||
const customerOptions = ref<CustomerOptionRow[]>([])
|
||||
const customerOptionsLoading = ref(false)
|
||||
const productSelectorVisible = ref(false)
|
||||
/** 产品选择器目标:装货 或 某一送货段 */
|
||||
const productSelectorTarget = ref<{ side: 'load' | 'delivery'; deliveryIndex: number }>({
|
||||
side: 'load',
|
||||
deliveryIndex: 0
|
||||
})
|
||||
/** 当前装货合同明细中的 CRM productId(非内部客户限制选品) */
|
||||
const loadContractProductIds = ref<string[]>([])
|
||||
/** 装货合同明细拉取序号,避免快速换合同时旧响应覆盖新结果 */
|
||||
let loadContractFetchSeq = 0
|
||||
/** 合同 id → 明细 productId(装/送货共用缓存) */
|
||||
const contractProductIdsCache = ref<Record<string, string[]>>({})
|
||||
/** 送货段合同明细拉取序号(按段) */
|
||||
const deliveryContractFetchSeqByIndex: Record<number, number> = {}
|
||||
/**
|
||||
* 内部客户:undefined → ProductSelector 全量目录
|
||||
* 非内部:始终传 string[](合同无明细时为 [])→ 受限模式,勿与「未传」混淆
|
||||
*/
|
||||
const productSelectorAllowedIds = computed((): string[] | undefined => {
|
||||
if (productSelectorTarget.value.side === 'delivery') {
|
||||
const delivery = deliveryForms.value[productSelectorTarget.value.deliveryIndex]
|
||||
if (!delivery) return []
|
||||
if (isPlInternalCustomerName(delivery.customerName)) return undefined
|
||||
const cid = String(delivery.contracts?.[0]?.contractId || '').trim()
|
||||
return cid ? contractProductIdsCache.value[cid] || [] : []
|
||||
}
|
||||
if (isPlInternalCustomerName(loadForm.value.customerName)) return undefined
|
||||
return loadContractProductIds.value
|
||||
})
|
||||
|
||||
function openProductSelector(side: 'load' | 'delivery', deliveryIndex = 0) {
|
||||
productSelectorTarget.value = { side, deliveryIndex }
|
||||
productSelectorVisible.value = true
|
||||
}
|
||||
|
||||
async function fetchContractProductIds(contractId: string): Promise<string[]> {
|
||||
const id = String(contractId || '').trim()
|
||||
if (!id) return []
|
||||
if (Object.prototype.hasOwnProperty.call(contractProductIdsCache.value, id)) {
|
||||
return contractProductIdsCache.value[id] || []
|
||||
}
|
||||
try {
|
||||
const res = await getContract(id)
|
||||
const detail = pickBxData<{ products?: Array<{ productId?: string | null }> }>(res)
|
||||
const allowedIds = collectContractProductIds(detail?.products || [])
|
||||
contractProductIdsCache.value = { ...contractProductIdsCache.value, [id]: allowedIds }
|
||||
return allowedIds
|
||||
} catch {
|
||||
contractProductIdsCache.value = { ...contractProductIdsCache.value, [id]: [] }
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function filterDeliveryProductsByContract(delivery: PlanDeliveryForm): PlanDeliveryForm {
|
||||
if (isPlInternalCustomerName(delivery.customerName)) return delivery
|
||||
const cid = String(delivery.contracts?.[0]?.contractId || '').trim()
|
||||
if (!cid) return delivery
|
||||
const allowed = contractProductIdsCache.value[cid]
|
||||
// 明细尚未拉到时先不刷,避免误清空;拉完后 watch 会再滤
|
||||
if (allowed === undefined) return delivery
|
||||
return {
|
||||
...delivery,
|
||||
products: filterProductsByAllowedProductIds(delivery.products || [], allowed)
|
||||
}
|
||||
}
|
||||
|
||||
const vehicleViewVisible = ref(false)
|
||||
const vehicleLibraryVisible = ref(false)
|
||||
const carrierLibraryVisible = ref(false)
|
||||
@@ -635,32 +690,42 @@
|
||||
)
|
||||
}
|
||||
|
||||
function applyLoadProductsToDeliveries(options?: {
|
||||
async function ensureDeliveryContractProductCaches() {
|
||||
const tasks = deliveryForms.value.map(async (row) => {
|
||||
if (isPlInternalCustomerName(row.customerName)) return
|
||||
const cid = String(row.contracts?.[0]?.contractId || '').trim()
|
||||
if (cid) await fetchContractProductIds(cid)
|
||||
})
|
||||
await Promise.all(tasks)
|
||||
}
|
||||
|
||||
async function applyLoadProductsToDeliveries(options?: {
|
||||
showHint?: boolean
|
||||
forceQtyFromLoad?: boolean
|
||||
}) {
|
||||
await ensureDeliveryContractProductCaches()
|
||||
const result = syncDeliveriesProductsFromLoad(
|
||||
deliveryForms.value,
|
||||
loadForm.value.products || [],
|
||||
{ forceQtyFromLoad: options?.forceQtyFromLoad }
|
||||
)
|
||||
deliveryForms.value = result.deliveries
|
||||
deliveryForms.value = result.deliveries.map((row) => filterDeliveryProductsByContract(row))
|
||||
if (options?.showHint && result.needSplitHint) {
|
||||
ElMessage.info('多个送货段,请拆分各段数量')
|
||||
}
|
||||
}
|
||||
|
||||
/** 装货产品变更后:镜像计划层产品 + 同步送货段(C1) */
|
||||
function syncFromLoadProducts(options?: { showHint?: boolean }) {
|
||||
async function syncFromLoadProducts(options?: { showHint?: boolean }) {
|
||||
if (suppressStopProductSync.value || detailLoading.value) return
|
||||
mirrorPlanProductsFromLoad()
|
||||
applyLoadProductsToDeliveries(options)
|
||||
await applyLoadProductsToDeliveries(options)
|
||||
}
|
||||
|
||||
function handleAddDelivery() {
|
||||
deliverySectionRef.value?.addDelivery()
|
||||
nextTick(() => {
|
||||
applyLoadProductsToDeliveries({ showHint: true })
|
||||
void applyLoadProductsToDeliveries({ showHint: true })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -671,7 +736,7 @@
|
||||
function handleRemoveDelivery() {
|
||||
deliverySectionRef.value?.removeDelivery(deliveryActiveIndex.value)
|
||||
nextTick(() => {
|
||||
applyLoadProductsToDeliveries()
|
||||
void applyLoadProductsToDeliveries()
|
||||
})
|
||||
}
|
||||
const hasFreightRef = computed(() => formData.hasFreight)
|
||||
@@ -993,7 +1058,7 @@
|
||||
}
|
||||
|
||||
/** 详情加载后:无装货产品时用计划产品回填;再按 C1 同步送货 */
|
||||
function bootstrapStopsFromPlanProducts() {
|
||||
async function bootstrapStopsFromPlanProducts() {
|
||||
if (!loadForm.value.products.length && formData.products.length) {
|
||||
loadForm.value = {
|
||||
...loadForm.value,
|
||||
@@ -1003,11 +1068,11 @@
|
||||
enrichStopFormsDisplayFromPlanProducts()
|
||||
}
|
||||
mirrorPlanProductsFromLoad()
|
||||
applyLoadProductsToDeliveries()
|
||||
await applyLoadProductsToDeliveries()
|
||||
}
|
||||
|
||||
function syncDeliveryProductsFromLoadManual() {
|
||||
applyLoadProductsToDeliveries({ forceQtyFromLoad: deliveryForms.value.length < 2 })
|
||||
async function syncDeliveryProductsFromLoadManual() {
|
||||
await applyLoadProductsToDeliveries({ forceQtyFromLoad: deliveryForms.value.length < 2 })
|
||||
ElMessage.success('已从装货产品同步')
|
||||
}
|
||||
|
||||
@@ -1030,16 +1095,8 @@
|
||||
return
|
||||
}
|
||||
const seq = ++loadContractFetchSeq
|
||||
let allowedIds: string[] = []
|
||||
try {
|
||||
const res = await getContract(id)
|
||||
if (seq !== loadContractFetchSeq) return
|
||||
const detail = pickBxData<{ products?: Array<{ productId?: string | null }> }>(res)
|
||||
allowedIds = collectContractProductIds(detail?.products || [])
|
||||
} catch {
|
||||
if (seq !== loadContractFetchSeq) return
|
||||
allowedIds = []
|
||||
}
|
||||
const allowedIds = await fetchContractProductIds(id)
|
||||
if (seq !== loadContractFetchSeq) return
|
||||
loadContractProductIds.value = allowedIds
|
||||
// 合同 id 变化时过滤装货产品(详情首次灌入同 id / 加载中不刷)
|
||||
if (
|
||||
@@ -1059,6 +1116,40 @@
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() =>
|
||||
deliveryForms.value.map((row) => ({
|
||||
customerName: row.customerName,
|
||||
contractId: String(row.contracts?.[0]?.contractId || '').trim()
|
||||
})),
|
||||
async (curr, prev) => {
|
||||
if (detailLoading.value || suppressStopProductSync.value) return
|
||||
for (let i = 0; i < curr.length; i += 1) {
|
||||
const id = curr[i]?.contractId || ''
|
||||
const prevId = prev?.[i]?.contractId || ''
|
||||
if (!id) continue
|
||||
const seq = (deliveryContractFetchSeqByIndex[i] || 0) + 1
|
||||
deliveryContractFetchSeqByIndex[i] = seq
|
||||
const allowedIds = await fetchContractProductIds(id)
|
||||
if (deliveryContractFetchSeqByIndex[i] !== seq) continue
|
||||
const delivery = deliveryForms.value[i]
|
||||
if (!delivery || isPlInternalCustomerName(delivery.customerName)) continue
|
||||
const contractChanged = !prev || id !== prevId
|
||||
const hasOrphan = (delivery.products || []).some((row) => {
|
||||
const pid = String(row.productId || '').trim()
|
||||
return !!pid && !allowedIds.includes(pid)
|
||||
})
|
||||
// 换合同必滤;同合同仅在有合同外产品时滤(如同步后明细刚拉到)
|
||||
if (!contractChanged && !hasOrphan) continue
|
||||
const nextProducts = filterProductsByAllowedProductIds(delivery.products || [], allowedIds)
|
||||
deliveryForms.value = deliveryForms.value.map((row, idx) =>
|
||||
idx === i ? { ...row, products: nextProducts } : row
|
||||
)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
function openCustomerSelector(side: CustomerSide, deliveryIndex = 0) {
|
||||
customerSelectorSide.value = side
|
||||
customerSelectorDeliveryIndex.value = deliveryIndex
|
||||
@@ -1170,29 +1261,58 @@
|
||||
)
|
||||
|
||||
async function handleProductConfirm(products: any[]) {
|
||||
const nextRows = [...(loadForm.value.products || [])]
|
||||
for (const product of products) {
|
||||
const brandMeta = initLineBrandMetaFromProduct(product as Record<string, unknown>)
|
||||
const row: PlanFormProductRow = {
|
||||
productId: String(product.id || ''),
|
||||
productNo: String(product.productNo || product.id || ''),
|
||||
productName: String(product.productName || ''),
|
||||
model: product.model as string | undefined,
|
||||
categoryName: product.categoryName as string | undefined,
|
||||
unit: String(product.unit || '吨'),
|
||||
quantity: 1,
|
||||
actualQty: 0,
|
||||
packageSpec: '',
|
||||
deliveryMethod: '',
|
||||
isToxic: Number(product.isToxic || 0),
|
||||
isExplosive: Number(product.isExplosive || 0),
|
||||
...brandMeta
|
||||
const target = productSelectorTarget.value
|
||||
const buildRows = async () => {
|
||||
const rows: ReturnType<typeof createStopProductsFromPlanProducts> = []
|
||||
for (const product of products) {
|
||||
const brandMeta = initLineBrandMetaFromProduct(product as Record<string, unknown>)
|
||||
const row: PlanFormProductRow = {
|
||||
productId: String(product.id || ''),
|
||||
productNo: String(product.productNo || product.id || ''),
|
||||
productName: String(product.productName || ''),
|
||||
model: product.model as string | undefined,
|
||||
categoryName: product.categoryName as string | undefined,
|
||||
unit: String(product.unit || '吨'),
|
||||
quantity: 1,
|
||||
actualQty: 0,
|
||||
packageSpec: '',
|
||||
deliveryMethod: '',
|
||||
isToxic: Number(product.isToxic || 0),
|
||||
isExplosive: Number(product.isExplosive || 0),
|
||||
...brandMeta
|
||||
}
|
||||
const [enriched] = await enrichPlanProductLinesAlias(
|
||||
await enrichPlanProductsHazardFromCrm([row])
|
||||
)
|
||||
rows.push(...createStopProductsFromPlanProducts([enriched]))
|
||||
}
|
||||
const [enriched] = await enrichPlanProductLinesAlias(
|
||||
await enrichPlanProductsHazardFromCrm([row])
|
||||
)
|
||||
nextRows.push(...createStopProductsFromPlanProducts([enriched]))
|
||||
return rows
|
||||
}
|
||||
|
||||
if (target.side === 'delivery') {
|
||||
const index = target.deliveryIndex
|
||||
const delivery = deliveryForms.value[index]
|
||||
if (!delivery) return
|
||||
const existingIds = new Set(
|
||||
(delivery.products || []).map((row) => String(row.productId || '').trim()).filter(Boolean)
|
||||
)
|
||||
const added = (await buildRows()).filter((row) => {
|
||||
const pid = String(row.productId || '').trim()
|
||||
if (!pid || existingIds.has(pid)) return false
|
||||
existingIds.add(pid)
|
||||
return true
|
||||
})
|
||||
if (!added.length) {
|
||||
ElMessage.info('所选产品已在送货段中')
|
||||
return
|
||||
}
|
||||
deliveryForms.value = deliveryForms.value.map((row, idx) =>
|
||||
idx === index ? { ...row, products: [...(row.products || []), ...added] } : row
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const nextRows = [...(loadForm.value.products || []), ...(await buildRows())]
|
||||
loadForm.value = { ...loadForm.value, products: nextRows }
|
||||
freightCalc.resetFreightCalc()
|
||||
}
|
||||
@@ -1738,7 +1858,7 @@
|
||||
return (contracts || []).some((item) => !!String(item.contractId || '').trim())
|
||||
}
|
||||
|
||||
/** 非内部客户:装/送货段合同至多 1 份;已选 1 份时校验装货产品归属合同 */
|
||||
/** 非内部客户:装/送货段合同至多 1 份;已选 1 份时校验装/送货产品归属合同 */
|
||||
function validateSingleContractPerStop(): boolean {
|
||||
if (
|
||||
!isPlInternalCustomerName(loadForm.value.customerName) &&
|
||||
@@ -1775,6 +1895,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < deliveryForms.value.length; i += 1) {
|
||||
const row = deliveryForms.value[i]
|
||||
if (isPlInternalCustomerName(row.customerName)) continue
|
||||
if ((row.contracts || []).length !== 1) continue
|
||||
const products = row.products || []
|
||||
if (!products.length) continue
|
||||
const cid = String(row.contracts?.[0]?.contractId || '').trim()
|
||||
const allowedList = cid ? contractProductIdsCache.value[cid] : undefined
|
||||
const allowed = new Set((allowedList || []).map((id) => String(id).trim()).filter(Boolean))
|
||||
if (!allowed.size) {
|
||||
ElMessage.warning(
|
||||
`送货段#${i + 1}所选合同暂无产品明细,或合同明细加载失败,请重选合同后再保存`
|
||||
)
|
||||
return false
|
||||
}
|
||||
const orphan = products.some((p) => !allowed.has(String(p.productId || '').trim()))
|
||||
if (orphan) {
|
||||
ElMessage.warning(`送货段#${i + 1}产品须属于所选合同`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// 菜单样式变量
|
||||
$menu-height: 40px;
|
||||
$menu-icon-size: 16px;
|
||||
$menu-font-size: 13px;
|
||||
$menu-font-size: 15px;
|
||||
$hover-bg-color: var(--mint-row-alt, #f8fafc);
|
||||
$popup-menu-height: 40px;
|
||||
$popup-menu-padding: 8px;
|
||||
@@ -200,7 +200,7 @@ $popup-menu-radius: 6px;
|
||||
}
|
||||
|
||||
.el-sub-menu > .el-sub-menu__title {
|
||||
font-size: 12px !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 700;
|
||||
color: var(--mint-text-muted, #64748b) !important;
|
||||
letter-spacing: 0.02em;
|
||||
|
||||
Reference in new issue
Block a user