Applies to both user-app and user-app-v2. Logic and structure are shared; each app keeps its own look. Appearance settings - New "تنظیمات ظاهری" page (/settings/appearance), opened from the dashboard and available without login. It offers a light/dark/system theme and a "fast & light" motion switch. - appearanceStore saves the choice to localStorage. A small inline boot script in index.html applies it before first paint so there is no white flash. The "system" option follows OS changes live. - New ToggleSwitch component. Dark mode - Theme LESS tokens now compile to CSS custom properties. Light/dark values come from a generated virtual:palette.css (themeConfig/ appearance.ts, with colors.json + new colors.dark.json). - New LESS alpha() plugin replaces fade() on theme tokens. - New semantic tokens: page, onInk, heroSurface, hairlineStrong, photoMat, photoStage. - The dark palette is layered (page < card < panel < border) so surfaces separate without shadows. - Hard-coded colors were replaced by tokens according to their role. - antd inputs/text get dark overrides; vant uses its own .van-theme-dark. - Light mode looks the same as before. Reduced motion - With html.motion-reduced, transitions are capped at 120ms, keyframe loops stop, and backdrop blurs and permanent will-change are dropped. - Spinners and the toast progress bar (which closes the toast) are exempt via .keep-motion. - Carousel autoplay, the v1 Lottie and the product morph honor the setting. Product detail - Opening a product is now a container-transform morph from the tapped card (ProductMorph). It replaces the bottom Drawer and uses composited transform/opacity only. - The detail view is seeded with the card's data, so it no longer opens blank and jumps. Filters and sheets - Filters are now a full-width bottom sheet. Selections stay a draft until applied, so the result list no longer re-renders while dragging the slider. - DropDown collapse animates grid-template-rows instead of a capped max-height. - v2 IslandSheet is restyled to the v2 design language (API unchanged). Fixes - filtersStore.reset() shallow-copied shared arrays, so clearing the filters never cleared the selected brands.
199 lines
5.2 KiB
Vue
199 lines
5.2 KiB
Vue
<!--
|
|
One document of "جزئیات حساب".
|
|
|
|
Ported from henza_pwa/src/views/report/components/AccountingDetail.vue,
|
|
restyled into this app's design language. The LOGIC is the reference's and is
|
|
identical in user-app; only the styling differs.
|
|
|
|
Kept from the reference: شماره سند + تاریخ, وزن خالص, عیار, نوع سند,
|
|
شماره انگ, نام آزمایشگاه, قیمت, and the طلا in/out pair normalized to 750
|
|
(AccountingDetail.vue:6-85).
|
|
|
|
Rows the reference showed that the target has no equivalent for — نام کارساخته
|
|
(nO_kar), جنس فلز (jens_felez) and the two extra percentage lines — are
|
|
OMITTED rather than rendered blank or zero. They came from an external
|
|
accounting system the target does not have; the API returns null for them and
|
|
this component skips a row whose value is null, so the card never asserts
|
|
something it does not know. Recorded as REP-01.
|
|
|
|
Rows the target CAN fill and the reference could not are added at the end
|
|
(bank/account/destination), since those live on our own ledger entry.
|
|
-->
|
|
<template>
|
|
<article class="doc">
|
|
<header class="doc__head">
|
|
<span><span class="doc__muted">شماره سند:</span> {{ item.sh_factor }}</span>
|
|
<span><span class="doc__muted">تاریخ:</span> {{ jdate(item.tarikh) }}</span>
|
|
</header>
|
|
|
|
<dl class="doc__rows">
|
|
<div class="doc__row">
|
|
<dt>نوع سند</dt>
|
|
<dd>{{ item.no }}</dd>
|
|
</div>
|
|
<div class="doc__row">
|
|
<dt>وزن خالص</dt>
|
|
<dd dir="ltr">{{ item.vazn }}</dd>
|
|
</div>
|
|
<div v-if="item.ayar !== null" class="doc__row">
|
|
<dt>عیار</dt>
|
|
<dd dir="ltr">{{ item.ayar }}</dd>
|
|
</div>
|
|
<div v-if="item.sh_sharti" class="doc__row">
|
|
<dt>شماره انگ</dt>
|
|
<dd dir="ltr">{{ item.sh_sharti }}</dd>
|
|
</div>
|
|
<div v-if="item.name_az" class="doc__row">
|
|
<dt>نام آزمایشگاه</dt>
|
|
<dd>{{ item.name_az }}</dd>
|
|
</div>
|
|
<div v-if="item.Darsad_Talayee" class="doc__row">
|
|
<dt>اجرت (گرم)</dt>
|
|
<dd dir="ltr">{{ item.Darsad_Talayee }}</dd>
|
|
</div>
|
|
<div v-if="item.fi" class="doc__row">
|
|
<dt>قیمت هر گرم</dt>
|
|
<dd dir="ltr">{{ rial(item.fi) }}</dd>
|
|
</div>
|
|
<div v-if="item.fiF" class="doc__row">
|
|
<dt>مبلغ سند</dt>
|
|
<dd dir="ltr">{{ rial(item.fiF) }}</dd>
|
|
</div>
|
|
<div v-if="item.bankName" class="doc__row">
|
|
<dt>بانک</dt>
|
|
<dd>{{ item.bankName }}</dd>
|
|
</div>
|
|
<div v-if="item.accountName" class="doc__row">
|
|
<dt>نام حساب</dt>
|
|
<dd>{{ item.accountName }}</dd>
|
|
</div>
|
|
<div v-if="item.transferTo" class="doc__row">
|
|
<dt>مقصد حواله</dt>
|
|
<dd>{{ item.transferTo }}</dd>
|
|
</div>
|
|
<div v-if="item.receivedAt" class="doc__row">
|
|
<dt>تاریخ دریافت</dt>
|
|
<dd>{{ jdate(item.receivedAt) }}</dd>
|
|
</div>
|
|
<div v-if="item.note" class="doc__row">
|
|
<dt>توضیحات</dt>
|
|
<dd>{{ item.note }}</dd>
|
|
</div>
|
|
</dl>
|
|
|
|
<div class="doc__gold">
|
|
<span>طلا</span>
|
|
<div class="doc__stack">
|
|
<span dir="ltr" class="is-in">{{ item.tabdil750_v }}</span>
|
|
<span dir="ltr" class="is-out">{{ item.tabdil750_kh }}</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { jdate, price } from '../../utils/filters'
|
|
import type { ReportDetailRow } from '../../api/client'
|
|
|
|
defineProps<{ item: ReportDetailRow }>()
|
|
|
|
/** Same rule as the statement: group the whole part only. */
|
|
function rial(value: string): string {
|
|
const n = parseFloat(value)
|
|
if (!Number.isFinite(n)) return value
|
|
const whole = Math.trunc(Math.abs(n)).toString()
|
|
return (n < 0 ? '-' : '') + price(whole)
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
/*
|
|
v2 styling. The template and script above are IDENTICAL to user-app's — only
|
|
this block differs. v2 draws the document as a flat, hairline-separated block
|
|
rather than an outlined card.
|
|
*/
|
|
.doc {
|
|
padding: 18px 2px;
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: 1px solid @hairline;
|
|
}
|
|
|
|
.doc:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.doc__head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
margin-bottom: 10px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.doc__muted {
|
|
color: @inkMuted;
|
|
font-weight: 500;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.doc__rows {
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.doc__row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 9px 0;
|
|
border-bottom: 1px dashed @hairlineSoft;
|
|
}
|
|
|
|
.doc__row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.doc__row dt {
|
|
font-size: 12.5px;
|
|
color: @inkMuted;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.doc__row dd {
|
|
margin: 0;
|
|
font-size: 13.5px;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
}
|
|
|
|
.doc__gold {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
border-top: 1px dashed @hairline;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.doc__stack {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 2px;
|
|
}
|
|
|
|
.is-in {
|
|
color: @green;
|
|
}
|
|
|
|
.is-out {
|
|
color: @red;
|
|
}
|
|
</style>
|