ADD: Standalone transaction creation page with sidebar menu entry
- Add CreateWarehouseTransaction.vue — full-page form to register IN/OUT transactions with item/warehouse dropdowns (searchable), quantity input, reason presets, date picker, live stock preview showing current balance for the selected item+warehouse combo, and input validation with inline error messages - Add /warehouse-items/create-transaction route - Add 'ثبت تراکنش' as first item in 'مدیریت انبار' sidebar group
This commit is contained in:
@@ -25,6 +25,7 @@ const model = ref([
|
||||
{
|
||||
label: 'مدیریت انبار',
|
||||
items: [
|
||||
{ label: 'ثبت تراکنش', icon: 'pi pi-fw pi-plus-circle', to: '/warehouse-items/create-transaction' },
|
||||
{ label: 'اقلام انبار', icon: 'pi pi-fw pi-box', to: '/warehouse-items/list' },
|
||||
{ label: 'تراکنشهای انبار', icon: 'pi pi-fw pi-history', to: '/warehouse-items/transactions' },
|
||||
{ label: 'مکانهای انبار', icon: 'pi pi-fw pi-map', to: '/warehouse-items/warehouses' }
|
||||
|
||||
@@ -100,6 +100,11 @@ const router = createRouter({
|
||||
name: 'warehouse-item-stock',
|
||||
component: () => import('@/views/pages/WarehouseItems/WarehouseItemStockView.vue')
|
||||
},
|
||||
{
|
||||
path: '/warehouse-items/create-transaction',
|
||||
name: 'create-warehouse-transaction',
|
||||
component: () => import('@/views/pages/WarehouseItems/CreateWarehouseTransaction.vue')
|
||||
},
|
||||
{
|
||||
path: '/warehouse-items/transactions',
|
||||
name: 'warehouse-item-transactions',
|
||||
|
||||
@@ -0,0 +1,572 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { WarehouseItemService } from '@/service/WarehouseItemService';
|
||||
import { WarehouseService } from '@/service/WarehouseService';
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
const items = ref([]);
|
||||
const warehouses = ref([]);
|
||||
const submitting = ref(false);
|
||||
const submitted = ref(false);
|
||||
|
||||
const form = ref({
|
||||
itemId: '',
|
||||
warehouseId: '',
|
||||
type: 'IN',
|
||||
quantity: null,
|
||||
reason: '',
|
||||
note: '',
|
||||
date: '',
|
||||
});
|
||||
|
||||
// Stock preview for the selected item+warehouse combo
|
||||
const stockPreview = ref(null);
|
||||
const stockLoading = ref(false);
|
||||
|
||||
const IN_REASONS = [
|
||||
'خرید از تامینکننده',
|
||||
'برگشت از کارگاه',
|
||||
'انتقال از انبار دیگر',
|
||||
'اهدا / دریافت رایگان',
|
||||
'موجودی اولیه',
|
||||
'سایر',
|
||||
];
|
||||
|
||||
const OUT_REASONS = [
|
||||
'مصرف در پروژه',
|
||||
'انتقال به انبار دیگر',
|
||||
'فروش / واگذاری',
|
||||
'ضایعات',
|
||||
'مفقودی',
|
||||
'سایر',
|
||||
];
|
||||
|
||||
const reasons = computed(() => form.value.type === 'IN' ? IN_REASONS : OUT_REASONS);
|
||||
|
||||
const selectedItem = computed(() => items.value.find(i => i._id === form.value.itemId));
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadItems(), loadWarehouses()]);
|
||||
});
|
||||
|
||||
const loadItems = async () => {
|
||||
try {
|
||||
const res = await WarehouseItemService.listItems({ limit: 500, isActive: 'true' });
|
||||
items.value = res.items || [];
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در بارگذاری اقلام', life: 3000 });
|
||||
}
|
||||
};
|
||||
|
||||
const loadWarehouses = async () => {
|
||||
try {
|
||||
warehouses.value = await WarehouseService.listWarehouses({ isActive: 'true' });
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در بارگذاری انبارها', life: 3000 });
|
||||
}
|
||||
};
|
||||
|
||||
// Load current stock when both item and warehouse are selected
|
||||
const onItemOrWarehouseChange = async () => {
|
||||
if (!form.value.itemId || !form.value.warehouseId) {
|
||||
stockPreview.value = null;
|
||||
return;
|
||||
}
|
||||
stockLoading.value = true;
|
||||
try {
|
||||
const data = await WarehouseItemService.getStockByItem(form.value.itemId);
|
||||
const warehouseStock = data.stocks.find(s => s.warehouse?._id === form.value.warehouseId);
|
||||
stockPreview.value = {
|
||||
quantity: warehouseStock ? warehouseStock.quantity : 0,
|
||||
unit: data.unit,
|
||||
unitEn: data.unitEn,
|
||||
};
|
||||
} catch (e) {
|
||||
stockPreview.value = null;
|
||||
} finally {
|
||||
stockLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onTypeChange = () => {
|
||||
form.value.reason = '';
|
||||
};
|
||||
|
||||
const isValid = computed(() => {
|
||||
return form.value.itemId && form.value.warehouseId && form.value.quantity > 0 && form.value.reason;
|
||||
});
|
||||
|
||||
const submitTransaction = async () => {
|
||||
submitted.value = true;
|
||||
if (!isValid.value) {
|
||||
toast.add({ severity: 'warn', summary: 'توجه', detail: 'لطفاً تمام فیلدهای ضروری را پر کنید', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
await WarehouseItemService.createTransaction({
|
||||
itemId: form.value.itemId,
|
||||
warehouseId: form.value.warehouseId,
|
||||
type: form.value.type,
|
||||
quantity: form.value.quantity,
|
||||
reason: form.value.reason,
|
||||
note: form.value.note,
|
||||
date: form.value.date || undefined,
|
||||
});
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'موفق',
|
||||
detail: form.value.type === 'IN' ? 'موجودی با موفقیت به انبار افزوده شد' : 'موجودی با موفقیت از انبار کسر شد',
|
||||
life: 4000,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
form.value = { itemId: '', warehouseId: '', type: 'IN', quantity: null, reason: '', note: '', date: '' };
|
||||
submitted.value = false;
|
||||
stockPreview.value = null;
|
||||
} catch (e) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'خطا',
|
||||
detail: e.response?.data?.message || 'عملیات ناموفق بود',
|
||||
life: 5000,
|
||||
});
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const goToStockView = () => {
|
||||
if (form.value.itemId) {
|
||||
router.push(`/warehouse-items/stock/${form.value.itemId}`);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="create-transaction-page">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<div class="header-icon" :class="form.type === 'IN' ? 'icon-in' : 'icon-out'">
|
||||
<i :class="form.type === 'IN' ? 'pi pi-arrow-circle-down' : 'pi pi-arrow-circle-up'"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="page-title">ثبت تراکنش انبار</h1>
|
||||
<p class="page-subtitle">ورود یا خروج اقلام از انبار</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<Button
|
||||
icon="pi pi-history"
|
||||
label="تاریخچه تراکنشها"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="router.push('/warehouse-items/transactions')"
|
||||
/>
|
||||
<Button
|
||||
v-if="form.itemId"
|
||||
icon="pi pi-chart-bar"
|
||||
label="مشاهده موجودی قلم"
|
||||
severity="info"
|
||||
outlined
|
||||
@click="goToStockView"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-layout">
|
||||
|
||||
<!-- Main form card -->
|
||||
<div class="card form-card">
|
||||
|
||||
<!-- Type selector -->
|
||||
<div class="type-selector">
|
||||
<button
|
||||
id="type-in"
|
||||
class="type-card"
|
||||
:class="{ active: form.type === 'IN' }"
|
||||
type="button"
|
||||
@click="form.type = 'IN'; onTypeChange()"
|
||||
>
|
||||
<div class="type-icon in-icon"><i class="pi pi-arrow-down"></i></div>
|
||||
<div class="type-text">
|
||||
<span class="type-label">ورود به انبار</span>
|
||||
<span class="type-desc">افزودن موجودی</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
id="type-out"
|
||||
class="type-card"
|
||||
:class="{ active: form.type === 'OUT' }"
|
||||
type="button"
|
||||
@click="form.type = 'OUT'; onTypeChange()"
|
||||
>
|
||||
<div class="type-icon out-icon"><i class="pi pi-arrow-up"></i></div>
|
||||
<div class="type-text">
|
||||
<span class="type-label">خروج از انبار</span>
|
||||
<span class="type-desc">کسر موجودی</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<!-- Item & Warehouse row -->
|
||||
<div class="fields-grid">
|
||||
<div class="form-field" :class="{ 'field-error': submitted && !form.itemId }">
|
||||
<label for="item-select">قلم انبار <span class="required">*</span></label>
|
||||
<Select
|
||||
id="item-select"
|
||||
v-model="form.itemId"
|
||||
:options="items"
|
||||
optionLabel="nameFa"
|
||||
optionValue="_id"
|
||||
placeholder="انتخاب قلم..."
|
||||
class="w-full"
|
||||
filter
|
||||
filterPlaceholder="جستجو..."
|
||||
@change="onItemOrWarehouseChange"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="item-option">
|
||||
<span class="option-fa">{{ option.nameFa }}</span>
|
||||
<span class="option-en">{{ option.nameEn }}</span>
|
||||
<span class="option-unit">{{ option.unitFa }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
<small class="error-msg" v-if="submitted && !form.itemId">قلم انبار ضروری است</small>
|
||||
</div>
|
||||
|
||||
<div class="form-field" :class="{ 'field-error': submitted && !form.warehouseId }">
|
||||
<label for="warehouse-select">انبار <span class="required">*</span></label>
|
||||
<Select
|
||||
id="warehouse-select"
|
||||
v-model="form.warehouseId"
|
||||
:options="warehouses"
|
||||
optionLabel="name"
|
||||
optionValue="_id"
|
||||
placeholder="انتخاب انبار..."
|
||||
class="w-full"
|
||||
@change="onItemOrWarehouseChange"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<div class="warehouse-option">
|
||||
<span>{{ option.name }}</span>
|
||||
<span v-if="option.location?.address" class="option-addr">{{ option.location.address }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
<small class="error-msg" v-if="submitted && !form.warehouseId">انبار ضروری است</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stock preview banner -->
|
||||
<Transition name="fade">
|
||||
<div v-if="stockPreview !== null" class="stock-preview" :class="{ 'stock-zero': stockPreview.quantity <= 0 }">
|
||||
<i class="pi pi-database"></i>
|
||||
<span>
|
||||
موجودی فعلی در این انبار:
|
||||
<strong>{{ stockPreview.quantity.toLocaleString('fa-IR') }} {{ stockPreview.unit }}</strong>
|
||||
<span class="unit-en-small">({{ stockPreview.unitEn }})</span>
|
||||
</span>
|
||||
<span v-if="stockPreview.quantity <= 0 && form.type === 'OUT'" class="stock-alert">
|
||||
<i class="pi pi-exclamation-triangle"></i> موجودی صفر است
|
||||
</span>
|
||||
<ProgressSpinner v-if="stockLoading" style="width:20px;height:20px" strokeWidth="5" />
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Quantity & Date row -->
|
||||
<div class="fields-grid">
|
||||
<div class="form-field" :class="{ 'field-error': submitted && !form.quantity }">
|
||||
<label for="quantity-input">
|
||||
مقدار
|
||||
<span v-if="selectedItem" class="unit-hint">({{ selectedItem.unitFa }} / {{ selectedItem.unitEn }})</span>
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<InputNumber
|
||||
id="quantity-input"
|
||||
v-model="form.quantity"
|
||||
:min="0.001"
|
||||
:minFractionDigits="0"
|
||||
:maxFractionDigits="3"
|
||||
class="w-full"
|
||||
placeholder="مقدار را وارد کنید"
|
||||
:pt="{ input: { dir: 'ltr', style: 'font-size:1.15rem; font-weight:600;' } }"
|
||||
/>
|
||||
<small class="error-msg" v-if="submitted && !form.quantity">مقدار ضروری است</small>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="date-input">تاریخ تراکنش <span class="optional">(اختیاری — پیشفرض: اکنون)</span></label>
|
||||
<InputText id="date-input" v-model="form.date" type="datetime-local" class="w-full" dir="ltr" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reason -->
|
||||
<div class="form-field" :class="{ 'field-error': submitted && !form.reason }">
|
||||
<label>دلیل تراکنش <span class="required">*</span></label>
|
||||
<div class="reason-chips">
|
||||
<button
|
||||
v-for="r in reasons"
|
||||
:key="r"
|
||||
class="reason-chip"
|
||||
:class="{ active: form.reason === r }"
|
||||
type="button"
|
||||
@click="form.reason = r"
|
||||
>{{ r }}</button>
|
||||
</div>
|
||||
<InputText
|
||||
v-model="form.reason"
|
||||
placeholder="یا دلیل سفارشی وارد کنید..."
|
||||
class="w-full mt-2"
|
||||
:class="{ 'p-invalid': submitted && !form.reason }"
|
||||
/>
|
||||
<small class="error-msg" v-if="submitted && !form.reason">دلیل تراکنش ضروری است</small>
|
||||
</div>
|
||||
|
||||
<!-- Note -->
|
||||
<div class="form-field">
|
||||
<label for="note-input">یادداشت <span class="optional">(اختیاری)</span></label>
|
||||
<Textarea id="note-input" v-model="form.note" rows="2" class="w-full" autoResize placeholder="توضیحات تکمیلی..." />
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="submit-row">
|
||||
<Button
|
||||
label="انصراف و پاککردن فرم"
|
||||
icon="pi pi-times"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="() => { form = { itemId: '', warehouseId: '', type: 'IN', quantity: null, reason: '', note: '', date: '' }; submitted = false; stockPreview = null; }"
|
||||
/>
|
||||
<Button
|
||||
:label="form.type === 'IN' ? 'ثبت ورود به انبار' : 'ثبت خروج از انبار'"
|
||||
:icon="form.type === 'IN' ? 'pi pi-plus-circle' : 'pi pi-minus-circle'"
|
||||
:severity="form.type === 'IN' ? 'success' : 'danger'"
|
||||
:loading="submitting"
|
||||
size="large"
|
||||
class="submit-btn"
|
||||
@click="submitTransaction"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side info card -->
|
||||
<div class="side-panel">
|
||||
<div class="card info-card">
|
||||
<h4 class="info-title"><i class="pi pi-info-circle"></i> راهنما</h4>
|
||||
<ul class="info-list">
|
||||
<li>
|
||||
<i class="pi pi-arrow-down in-dot"></i>
|
||||
<span><strong>ورود:</strong> کالایی وارد انبار میشود (خرید، برگشت کالا، انتقال از جای دیگر)</span>
|
||||
</li>
|
||||
<li>
|
||||
<i class="pi pi-arrow-up out-dot"></i>
|
||||
<span><strong>خروج:</strong> کالایی از انبار برداشت میشود (مصرف، فروش، ضایعات)</span>
|
||||
</li>
|
||||
<li>
|
||||
<i class="pi pi-lock"></i>
|
||||
<span>اگر موجودی کافی نباشد، تراکنش خروج ثبت نمیشود</span>
|
||||
</li>
|
||||
<li>
|
||||
<i class="pi pi-user"></i>
|
||||
<span>کاربر انجامدهنده به صورت خودکار ثبت میشود</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card quick-links-card" v-if="form.itemId">
|
||||
<h4 class="info-title"><i class="pi pi-link"></i> دسترسی سریع</h4>
|
||||
<Button
|
||||
icon="pi pi-chart-bar"
|
||||
label="موجودی این قلم در همه انبارها"
|
||||
class="w-full mb-2"
|
||||
severity="info"
|
||||
outlined
|
||||
@click="goToStockView"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-box"
|
||||
label="لیست همه اقلام"
|
||||
class="w-full"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="router.push('/warehouse-items/list')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.create-transaction-page { padding: 1.5rem; direction: rtl; }
|
||||
|
||||
/* Header */
|
||||
.page-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem;
|
||||
}
|
||||
.header-left { display: flex; align-items: center; gap: 1rem; }
|
||||
.header-icon {
|
||||
width: 56px; height: 56px; border-radius: 16px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.6rem; color: #fff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.icon-in {
|
||||
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||
box-shadow: 0 4px 15px rgba(34,197,94,0.35);
|
||||
}
|
||||
.icon-out {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
box-shadow: 0 4px 15px rgba(239,68,68,0.35);
|
||||
}
|
||||
.page-title { margin: 0; font-size: 1.5rem; font-weight: 700; }
|
||||
.page-subtitle { margin: 0; font-size: 0.875rem; color: var(--text-color-secondary); }
|
||||
.header-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||
|
||||
/* Layout */
|
||||
.form-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 1.5rem;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.form-layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* Form card */
|
||||
.form-card { border-radius: 16px; display: flex; flex-direction: column; gap: 1.25rem; }
|
||||
|
||||
/* Type selector */
|
||||
.type-selector { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
.type-card {
|
||||
border: 2px solid var(--surface-border);
|
||||
background: var(--surface-50);
|
||||
border-radius: 14px;
|
||||
padding: 1.25rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
transition: all 0.25s;
|
||||
text-align: right;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.type-card:hover { border-color: var(--primary-color); transform: translateY(-1px); }
|
||||
.type-card.active { transform: translateY(-2px); box-shadow: 0 6px 20px -5px currentColor; }
|
||||
.type-card:nth-child(1).active { border-color: #22c55e; background: rgba(34,197,94,0.07); box-shadow: 0 6px 20px -5px rgba(34,197,94,0.4); }
|
||||
.type-card:nth-child(2).active { border-color: #ef4444; background: rgba(239,68,68,0.07); box-shadow: 0 6px 20px -5px rgba(239,68,68,0.4); }
|
||||
|
||||
.type-icon {
|
||||
width: 44px; height: 44px; border-radius: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.3rem; color: #fff; flex-shrink: 0;
|
||||
}
|
||||
.in-icon { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
||||
.out-icon { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
||||
.type-text { display: flex; flex-direction: column; gap: 2px; }
|
||||
.type-label { font-weight: 700; font-size: 1rem; }
|
||||
.type-desc { font-size: 0.8rem; color: var(--text-color-secondary); }
|
||||
|
||||
/* Fields */
|
||||
.fields-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
@media (max-width: 600px) { .fields-grid { grid-template-columns: 1fr; } }
|
||||
.form-field { display: flex; flex-direction: column; gap: 0.4rem; }
|
||||
.form-field label { font-weight: 600; font-size: 0.9rem; }
|
||||
.required { color: #ef4444; }
|
||||
.optional { font-size: 0.75rem; color: var(--text-color-secondary); font-weight: 400; }
|
||||
.unit-hint { font-size: 0.8rem; color: #6366f1; font-weight: 600; margin-right: 0.25rem; }
|
||||
.error-msg { color: #ef4444; font-size: 0.8rem; }
|
||||
.field-error label { color: #ef4444; }
|
||||
|
||||
/* Stock preview */
|
||||
.stock-preview {
|
||||
display: flex; align-items: center; gap: 0.75rem;
|
||||
background: rgba(99,102,241,0.08);
|
||||
border: 1px solid rgba(99,102,241,0.25);
|
||||
border-radius: 10px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.stock-preview.stock-zero {
|
||||
background: rgba(239,68,68,0.08);
|
||||
border-color: rgba(239,68,68,0.3);
|
||||
color: #dc2626;
|
||||
}
|
||||
.stock-alert { margin-right: auto; font-size: 0.8rem; font-weight: 600; color: #ef4444; }
|
||||
.unit-en-small { font-size: 0.8rem; color: var(--text-color-secondary); margin-right: 0.25rem; }
|
||||
|
||||
/* Reason chips */
|
||||
.reason-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
||||
.reason-chip {
|
||||
border: 1.5px solid var(--surface-border);
|
||||
background: var(--surface-50);
|
||||
border-radius: 20px;
|
||||
padding: 5px 14px;
|
||||
font-size: 0.82rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.reason-chip:hover { border-color: var(--primary-color); color: var(--primary-color); }
|
||||
.reason-chip.active {
|
||||
background: rgba(99,102,241,0.1);
|
||||
border-color: #6366f1;
|
||||
color: #6366f1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Submit row */
|
||||
.submit-row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding-top: 0.5rem; gap: 1rem;
|
||||
}
|
||||
.submit-btn { flex: 0 0 auto; }
|
||||
|
||||
/* Dropdown option templates */
|
||||
.item-option { display: flex; align-items: center; gap: 0.5rem; }
|
||||
.option-fa { font-weight: 600; }
|
||||
.option-en { font-size: 0.8rem; color: var(--text-color-secondary); direction: ltr; }
|
||||
.option-unit {
|
||||
margin-right: auto;
|
||||
font-size: 0.75rem;
|
||||
background: rgba(99,102,241,0.1);
|
||||
color: #6366f1;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.warehouse-option { display: flex; flex-direction: column; }
|
||||
.option-addr { font-size: 0.75rem; color: var(--text-color-secondary); }
|
||||
|
||||
/* Side panel */
|
||||
.side-panel { display: flex; flex-direction: column; gap: 1rem; }
|
||||
.info-card, .quick-links-card { border-radius: 14px; }
|
||||
.info-title {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
display: flex; align-items: center; gap: 0.5rem;
|
||||
}
|
||||
.info-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.info-list li { display: flex; align-items: flex-start; gap: 0.6rem; font-size: 0.85rem; line-height: 1.5; }
|
||||
.in-dot { color: #22c55e; margin-top: 2px; }
|
||||
.out-dot { color: #ef4444; margin-top: 2px; }
|
||||
|
||||
/* Transition */
|
||||
.fade-enter-active, .fade-leave-active { transition: all 0.25s ease; }
|
||||
.fade-enter-from { opacity: 0; transform: translateY(-6px); }
|
||||
.fade-leave-to { opacity: 0; transform: translateY(-6px); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user