增加 运营开支
This commit is contained in:
@@ -68,6 +68,8 @@ export interface FinanceDailyItem {
|
||||
withdrawal_paid_count: number
|
||||
manual_paid_amount_cent: number
|
||||
manual_paid_count: number
|
||||
operating_expense_amount_cent: number
|
||||
operating_expense_count: number
|
||||
disbursement_paid_amount_cent: number
|
||||
disbursement_paid_count: number
|
||||
}
|
||||
@@ -107,6 +109,11 @@ export interface FinanceDisbursementSummary {
|
||||
manual_paid_count: number
|
||||
}
|
||||
|
||||
export interface OperatingExpenseSummary {
|
||||
amount_cent: number
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface FinanceDisbursementListSummary {
|
||||
record_count: number
|
||||
paid_amount_cent: number
|
||||
@@ -207,9 +214,61 @@ export interface FinanceDashboard {
|
||||
pickup_summary: FinancePickupSummary
|
||||
mohong_summary: FinanceMohongSummary
|
||||
disbursement_summary: FinanceDisbursementSummary
|
||||
operating_expense_summary: OperatingExpenseSummary
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export interface OperatingExpense {
|
||||
id: number
|
||||
expense_no: string
|
||||
category: string
|
||||
payee_name: string
|
||||
amount_cent: number
|
||||
occurred_at: string
|
||||
remark: string
|
||||
voucher_url: string
|
||||
status: 'paid' | 'voided'
|
||||
created_by: number
|
||||
created_by_name: string
|
||||
voided_by?: number
|
||||
voided_by_name: string
|
||||
voided_at?: string
|
||||
void_reason: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface OperatingExpenseQuery extends FinanceDateQuery {
|
||||
status?: string
|
||||
category?: string
|
||||
keyword?: string
|
||||
date_type?: 'created' | 'occurred'
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export interface OperatingExpenseListSummary {
|
||||
record_count: number
|
||||
paid_amount_cent: number
|
||||
paid_count: number
|
||||
}
|
||||
|
||||
export interface OperatingExpenseList {
|
||||
items: OperatingExpense[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
summary: OperatingExpenseListSummary
|
||||
}
|
||||
|
||||
export interface CreateOperatingExpensePayload {
|
||||
category: string
|
||||
payee_name: string
|
||||
amount_cent: number
|
||||
occurred_at: string
|
||||
remark: string
|
||||
voucher_url?: string
|
||||
}
|
||||
|
||||
export interface FinanceDetail {
|
||||
order_id: number
|
||||
order_no: string
|
||||
@@ -309,6 +368,10 @@ export async function fetchFinanceDashboard(query: FinanceDateQuery = {}) {
|
||||
manual_paid_amount_cent: 0,
|
||||
manual_paid_count: 0,
|
||||
},
|
||||
operating_expense_summary: data.data?.operating_expense_summary ?? {
|
||||
amount_cent: 0,
|
||||
count: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +413,37 @@ export async function fetchFinanceDisbursements(query: FinanceDisbursementQuery
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchOperatingExpenses(query: OperatingExpenseQuery = {}) {
|
||||
const { data } = await apiClient.get<ApiResponse<OperatingExpenseList>>(
|
||||
'/admin/finance/operating-expenses',
|
||||
{ params: cleanParams(query) }
|
||||
)
|
||||
const result = data.data
|
||||
return {
|
||||
items: Array.isArray(result?.items) ? result.items : [],
|
||||
total: Number(result?.total ?? 0),
|
||||
page: Number(result?.page ?? query.page ?? 1),
|
||||
page_size: Number(result?.page_size ?? query.page_size ?? 20),
|
||||
summary: result?.summary ?? { record_count: 0, paid_amount_cent: 0, paid_count: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
export async function createOperatingExpense(payload: CreateOperatingExpensePayload) {
|
||||
const { data } = await apiClient.post<ApiResponse<OperatingExpense>>(
|
||||
'/admin/finance/operating-expenses',
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function voidOperatingExpense(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<OperatingExpense>>(
|
||||
`/admin/finance/operating-expenses/${id}/void`,
|
||||
{ reason }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function createManualDisbursement(payload: CreateManualDisbursementPayload) {
|
||||
const { data } = await apiClient.post<ApiResponse<ManualDisbursement>>(
|
||||
'/admin/finance/manual-disbursements',
|
||||
|
||||
@@ -204,6 +204,11 @@ function diffType(value: number) {
|
||||
<strong>{{ moneyCent(dashboard.disbursement_summary.manual_paid_amount_cent) }}</strong>
|
||||
<small>{{ dashboard.disbursement_summary.manual_paid_count }} 笔有效记账</small>
|
||||
</div>
|
||||
<div class="metric-card operating-expense-card">
|
||||
<span>运营开支</span>
|
||||
<strong>{{ moneyCent(dashboard.operating_expense_summary.amount_cent) }}</strong>
|
||||
<small>{{ dashboard.operating_expense_summary.count }} 笔有效记账</small>
|
||||
</div>
|
||||
<div class="metric-card pending-card">
|
||||
<span>当前待出款</span>
|
||||
<strong>{{ moneyCent(dashboard.disbursement_summary.pending_payment_amount_cent) }}</strong>
|
||||
@@ -286,6 +291,9 @@ function diffType(value: number) {
|
||||
<el-table-column label="实际出款" width="130">
|
||||
<template #default="{ row }">{{ moneyCent(row.disbursement_paid_amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运营开支" width="130">
|
||||
<template #default="{ row }">{{ moneyCent(row.operating_expense_amount_cent) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<p v-if="dashboard" class="generated-at">
|
||||
@@ -331,6 +339,10 @@ function diffType(value: number) {
|
||||
border-left: 3px solid #0f766e;
|
||||
}
|
||||
|
||||
.operating-expense-card {
|
||||
border-left: 3px solid #b45309;
|
||||
}
|
||||
|
||||
.pending-card {
|
||||
border-left: 3px solid #d97706;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
<script setup lang="ts">
|
||||
import { CircleClose, Plus, Search, UploadFilled, View } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import {
|
||||
createOperatingExpense,
|
||||
fetchOperatingExpenses,
|
||||
voidOperatingExpense,
|
||||
type OperatingExpense,
|
||||
type OperatingExpenseListSummary,
|
||||
} from '@/features/admin/api/adminFinance'
|
||||
import AuthImage from '@/shared/components/business/AuthImage.vue'
|
||||
import { uploadAdminFile } from '@/shared/api/files'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
import { formatCentWithSymbol, yuanToCent } from '@/shared/utils/money'
|
||||
import { formatDateTime, formatInputDate } from '@/shared/utils/time'
|
||||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||||
|
||||
const adminSession = useAdminSessionStore()
|
||||
const loading = ref(false)
|
||||
const createVisible = ref(false)
|
||||
const createSaving = ref(false)
|
||||
const voucherUploading = ref(false)
|
||||
const actionID = ref(0)
|
||||
const selectedExpense = ref<OperatingExpense | null>(null)
|
||||
const items = ref<OperatingExpense[]>([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const currentPageSize = ref(20)
|
||||
const summary = ref<OperatingExpenseListSummary>(emptySummary())
|
||||
const canManage = computed(() => adminSession.hasPermission('finance:operating_expense'))
|
||||
const filters = reactive({
|
||||
date_type: 'created' as 'created' | 'occurred',
|
||||
start_date: defaultStartDate(),
|
||||
end_date: formatInputDate(new Date()),
|
||||
status: '',
|
||||
category: '',
|
||||
keyword: '',
|
||||
})
|
||||
const createForm = reactive({
|
||||
category: '',
|
||||
payee_name: '',
|
||||
amount_yuan: 0,
|
||||
occurred_at: new Date(),
|
||||
remark: '',
|
||||
voucher_url: '',
|
||||
})
|
||||
|
||||
onMounted(loadItems)
|
||||
|
||||
async function loadItems() {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await fetchOperatingExpenses({
|
||||
...filters,
|
||||
page: currentPage.value,
|
||||
page_size: currentPageSize.value,
|
||||
})
|
||||
items.value = result.items
|
||||
total.value = result.total
|
||||
summary.value = result.summary
|
||||
} catch (error) {
|
||||
items.value = []
|
||||
total.value = 0
|
||||
summary.value = emptySummary()
|
||||
ElMessage.error(readError(error, '运营开支记录加载失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function search() {
|
||||
currentPage.value = 1
|
||||
void loadItems()
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filters.date_type = 'created'
|
||||
filters.start_date = defaultStartDate()
|
||||
filters.end_date = formatInputDate(new Date())
|
||||
filters.status = ''
|
||||
filters.category = ''
|
||||
filters.keyword = ''
|
||||
currentPage.value = 1
|
||||
void loadItems()
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
createForm.category = ''
|
||||
createForm.payee_name = ''
|
||||
createForm.amount_yuan = 0
|
||||
createForm.occurred_at = new Date()
|
||||
createForm.remark = ''
|
||||
createForm.voucher_url = ''
|
||||
createVisible.value = true
|
||||
}
|
||||
|
||||
async function uploadVoucher(options: { file: File }) {
|
||||
if (!options.file.type.startsWith('image/')) {
|
||||
ElMessage.warning('开支凭证仅支持图片')
|
||||
return
|
||||
}
|
||||
voucherUploading.value = true
|
||||
try {
|
||||
const uploaded = await uploadAdminFile(options.file, 'operating-expense')
|
||||
createForm.voucher_url = uploaded.url
|
||||
ElMessage.success('凭证已上传')
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '凭证上传失败'))
|
||||
} finally {
|
||||
voucherUploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitExpense() {
|
||||
const amountCent = yuanToCent(createForm.amount_yuan)
|
||||
if (
|
||||
!createForm.category.trim() ||
|
||||
!createForm.payee_name.trim() ||
|
||||
amountCent <= 0 ||
|
||||
!createForm.occurred_at ||
|
||||
!createForm.remark.trim()
|
||||
) {
|
||||
ElMessage.warning('请填写完整的开支信息')
|
||||
return
|
||||
}
|
||||
createSaving.value = true
|
||||
try {
|
||||
await createOperatingExpense({
|
||||
category: createForm.category.trim(),
|
||||
payee_name: createForm.payee_name.trim(),
|
||||
amount_cent: amountCent,
|
||||
occurred_at: createForm.occurred_at.toISOString(),
|
||||
remark: createForm.remark.trim(),
|
||||
voucher_url: createForm.voucher_url || undefined,
|
||||
})
|
||||
createVisible.value = false
|
||||
ElMessage.success('运营开支已记账')
|
||||
await loadItems()
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '运营开支记账失败'))
|
||||
} finally {
|
||||
createSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVoid(item: OperatingExpense) {
|
||||
if (actionID.value) return
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt(
|
||||
`确认作废开支单 ${item.expense_no}?作废后将从运营开支统计中排除。`,
|
||||
'作废运营开支',
|
||||
{
|
||||
confirmButtonText: '确认作废',
|
||||
cancelButtonText: '取消',
|
||||
inputPlaceholder: '请输入作废原因',
|
||||
inputValidator: value => !!value.trim() || '请输入作废原因',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
actionID.value = item.id
|
||||
await voidOperatingExpense(item.id, value.trim())
|
||||
selectedExpense.value = null
|
||||
ElMessage.success('运营开支已作废')
|
||||
await loadItems()
|
||||
} catch (error) {
|
||||
if (error === 'cancel' || error === 'close') return
|
||||
ElMessage.error(readError(error, '运营开支作废失败'))
|
||||
} finally {
|
||||
actionID.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
function emptySummary(): OperatingExpenseListSummary {
|
||||
return { record_count: 0, paid_amount_cent: 0, paid_count: 0 }
|
||||
}
|
||||
|
||||
function defaultStartDate() {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() - 29)
|
||||
return formatInputDate(date)
|
||||
}
|
||||
|
||||
function money(value: number) {
|
||||
return formatCentWithSymbol(Number(value || 0))
|
||||
}
|
||||
|
||||
function statusLabel(status: string) {
|
||||
return status === 'paid' ? '已记账' : '已作废'
|
||||
}
|
||||
|
||||
function statusType(status: string) {
|
||||
return status === 'paid' ? 'success' : 'danger'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page operating-expenses-page">
|
||||
<div class="page-header-row">
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Operating Expenses</p>
|
||||
<h1>运营开支</h1>
|
||||
<p>独立记录日常运营费用,并在财务仪表盘按发生日期汇总。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button v-if="canManage" type="success" :icon="Plus" @click="openCreate">
|
||||
新增运营开支
|
||||
</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
<el-button type="primary" :icon="Search" :loading="loading" @click="search">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-grid operating-metrics">
|
||||
<div class="metric-card">
|
||||
<span>筛选记录</span>
|
||||
<strong>{{ summary.record_count }} 笔</strong>
|
||||
<small>当前筛选条件</small>
|
||||
</div>
|
||||
<div class="metric-card operating-metric">
|
||||
<span>有效运营开支</span>
|
||||
<strong>{{ money(summary.paid_amount_cent) }}</strong>
|
||||
<small>{{ summary.paid_count }} 笔已记账开支</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form class="table-panel operating-filters" label-position="top">
|
||||
<el-form-item label="日期类型">
|
||||
<el-select v-model="filters.date_type" class="full-control">
|
||||
<el-option label="录入日期" value="created" />
|
||||
<el-option label="开支日期" value="occurred" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="filters.start_date" class="full-control" type="date" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="filters.end_date" class="full-control" type="date" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filters.status" clearable placeholder="全部状态" class="full-control">
|
||||
<el-option label="已记账" value="paid" />
|
||||
<el-option label="已作废" value="voided" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开支类别">
|
||||
<el-input v-model="filters.category" clearable placeholder="按类别精确筛选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="filters.keyword" clearable placeholder="单号、收款方或用途" @keyup.enter="search" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" class="table-panel" :data="items">
|
||||
<el-table-column label="开支单号" min-width="190">
|
||||
<template #default="{ row }">{{ row.expense_no }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="category" label="开支类别" min-width="130" />
|
||||
<el-table-column label="收款方" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<strong>{{ row.payee_name }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开支金额" min-width="135" align="right">
|
||||
<template #default="{ row }"><strong>{{ money(row.amount_cent) }}</strong></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开支时间" min-width="175">
|
||||
<template #default="{ row }">{{ formatDateTime(row.occurred_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="105" align="center">
|
||||
<template #default="{ row }"><el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录入人" min-width="120">
|
||||
<template #default="{ row }">{{ row.created_by_name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.remark }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="145" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" :icon="View" @click="selectedExpense = row">详情</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'paid' && canManage"
|
||||
link
|
||||
type="danger"
|
||||
:icon="CircleClose"
|
||||
:loading="actionID === row.id"
|
||||
@click="handleVoid(row)"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<AdminTablePagination
|
||||
v-if="total > 0"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="currentPageSize"
|
||||
:total="total"
|
||||
:loading="loading"
|
||||
@page-change="loadItems"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="createVisible" title="新增运营开支" width="560px" :close-on-click-modal="false">
|
||||
<el-form class="expense-form" label-position="top" @submit.prevent="submitExpense">
|
||||
<el-form-item label="开支类别" required>
|
||||
<el-input v-model="createForm.category" maxlength="50" placeholder="例如:推广投放、办公采购" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收款方" required>
|
||||
<el-input v-model="createForm.payee_name" maxlength="100" placeholder="姓名或单位名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开支金额" required>
|
||||
<el-input-number v-model="createForm.amount_yuan" class="full-control" :min="0.01" :precision="2" :step="100" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开支时间" required>
|
||||
<el-date-picker v-model="createForm.occurred_at" class="full-control" type="datetime" placeholder="选择开支时间" />
|
||||
</el-form-item>
|
||||
<el-form-item class="expense-form-wide" label="开支用途" required>
|
||||
<el-input v-model="createForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="填写开支用途" />
|
||||
</el-form-item>
|
||||
<el-form-item class="expense-form-wide" label="开支凭证(选填)">
|
||||
<div v-if="createForm.voucher_url" class="voucher-preview">
|
||||
<AuthImage :source="createForm.voucher_url" :admin="true" fit="cover" :preview-src-list="[createForm.voucher_url]" :image-style="{ width: '104px', height: '104px', borderRadius: '6px' }" />
|
||||
<el-button class="voucher-remove" circle plain type="danger" :icon="CircleClose" title="移除凭证" @click="createForm.voucher_url = ''" />
|
||||
</div>
|
||||
<el-upload v-else :show-file-list="false" :http-request="uploadVoucher" accept="image/jpeg,image/png,image/webp">
|
||||
<el-button :icon="UploadFilled" :loading="voucherUploading">上传凭证</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="createSaving" @click="createVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createSaving" :disabled="voucherUploading" @click="submitExpense">确认记账</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :model-value="!!selectedExpense" title="运营开支详情" width="620px" @update:model-value="selectedExpense = null">
|
||||
<el-descriptions v-if="selectedExpense" :column="2" border>
|
||||
<el-descriptions-item label="开支单号">{{ selectedExpense.expense_no }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态"><el-tag :type="statusType(selectedExpense.status)">{{ statusLabel(selectedExpense.status) }}</el-tag></el-descriptions-item>
|
||||
<el-descriptions-item label="开支类别">{{ selectedExpense.category }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收款方">{{ selectedExpense.payee_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开支金额"><strong>{{ money(selectedExpense.amount_cent) }}</strong></el-descriptions-item>
|
||||
<el-descriptions-item label="开支时间">{{ formatDateTime(selectedExpense.occurred_at) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="录入人">{{ selectedExpense.created_by_name || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="录入时间">{{ formatDateTime(selectedExpense.created_at) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开支用途" :span="2">{{ selectedExpense.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="selectedExpense.voucher_url" label="开支凭证" :span="2">
|
||||
<AuthImage :source="selectedExpense.voucher_url" :admin="true" fit="cover" :preview-src-list="[selectedExpense.voucher_url]" :image-style="{ width: '140px', height: '140px', borderRadius: '6px' }" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="selectedExpense.status === 'voided'" label="作废信息" :span="2">
|
||||
{{ selectedExpense.voided_by_name || '-' }} · {{ formatDateTime(selectedExpense.voided_at) }} · {{ selectedExpense.void_reason || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="selectedExpense = null">关闭</el-button>
|
||||
<el-button v-if="selectedExpense?.status === 'paid' && canManage" type="danger" :icon="CircleClose" :loading="actionID === selectedExpense.id" @click="handleVoid(selectedExpense)">作废记录</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.operating-metrics {
|
||||
grid-template-columns: repeat(2, minmax(170px, 1fr));
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.operating-metric {
|
||||
border-left: 3px solid #b45309;
|
||||
}
|
||||
|
||||
.operating-filters {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(130px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.expense-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0 16px;
|
||||
}
|
||||
|
||||
.expense-form-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.voucher-preview {
|
||||
position: relative;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
}
|
||||
|
||||
.voucher-remove {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.operating-filters {
|
||||
grid-template-columns: repeat(3, minmax(140px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user