feat(user): about software page, enamad badge, and deal sheet UI polish

Add public about-software flow, login-only enamad, and deal buy/sell UI refinements for the store user app.
This commit is contained in:
2026-08-24 19:39:07 +03:30
parent bb4bcdf600
commit e87ba066db
11 changed files with 367 additions and 47 deletions
@@ -0,0 +1,27 @@
export function getDefaultAboutSoftwareText(projectName: string) {
const name = projectName.trim() || 'این نرم‌افزار'
return `${name} بستری امن و آنلاین برای خرید و فروش طلا فراهم کرده تا کاربران بتوانند سرمایه‌های خود را به‌سادگی مدیریت کنند.\n\nهدف ما ارائه تجربه‌ای سریع، شفاف و کاربرپسند در معاملات طلا است.`
}
function looksLikeHtml(value: string) {
return /<\/?[a-z][\s\S]*>/i.test(value)
}
export type AboutSoftwareBody =
| { kind: 'html'; html: string }
| { kind: 'text'; text: string }
/** اولویت با تنظیم about_software؛ در صورت خالی بودن، متن پیش‌فرض */
export function resolveAboutSoftwareBody(
raw: unknown,
projectName: string,
): AboutSoftwareBody {
const value = String(raw ?? '').trim()
if (!value) {
return { kind: 'text', text: getDefaultAboutSoftwareText(projectName) }
}
if (looksLikeHtml(value)) {
return { kind: 'html', html: value }
}
return { kind: 'text', text: value }
}
+163
View File
@@ -0,0 +1,163 @@
<template>
<div class="about-software-page-root">
<template v-if="isDesktop">
<div class="dabout">
<div class="dabout__container">
<DesktopPageTopbar eyebrow="اطلاعات" title="درباره نرم‌افزار" show-back />
<div class="dabout-layout">
<section class="dabout-surface dabout-main about-software-card">
<div class="dabout-main__intro">
<div class="dabout-main__logo">
<BasicIcon icon="vuesax-linear:info-circle" size="28" />
</div>
<div>
<h2 class="dabout-main__project">{{ projectName }}</h2>
<p class="dabout-main__tagline">معرفی نرم‌افزار و خدمات</p>
</div>
</div>
<div class="about-software-scroll">
<div
v-if="body.kind === 'html'"
class="about-software-body about-software-html"
v-html="body.html"
/>
<div v-else class="about-software-body whitespace-pre-line">
{{ body.text }}
</div>
<div class="about-software-enamad">
<EnamadBadge size="lg" />
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<template v-else>
<MobilePageHeader title="درباره نرم‌افزار" show-back />
<div class="mpage-content about-software-mobile pb-[calc(var(--app-bottom-chrome-height)+1rem)]">
<div class="about-software-card about-software-mobile__card">
<div class="about-software-scroll">
<div
v-if="body.kind === 'html'"
class="about-software-body about-software-html"
v-html="body.html"
/>
<div v-else class="about-software-body whitespace-pre-line">
{{ body.text }}
</div>
<div class="about-software-enamad">
<EnamadBadge size="lg" />
</div>
</div>
</div>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted } from 'vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import EnamadBadge from '@/components/EnamadBadge.vue'
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { getConfig } from '@/config/app-config'
import { useSettingsStore } from '@/store/settingsStore'
import { resolveAboutSoftwareBody } from './aboutSoftwareContent'
const isDesktop = useDesktopBreakpoint()
const settingsStore = useSettingsStore()
const projectName = computed(() => getConfig('name_fa', 'کشکول گلد') || '')
const body = computed(() => {
void settingsStore.settings
return resolveAboutSoftwareBody(
settingsStore.getSettingByKey('about_software'),
String(projectName.value),
)
})
onMounted(() => {
void settingsStore.fetchSettings()
})
</script>
<style scoped>
.about-software-page-root {
min-height: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.about-software-mobile {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.about-software-mobile__card {
flex: 1 1 0;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid rgba(15, 23, 42, 0.12);
border-radius: 0.75rem;
background: #fff;
}
.about-software-card {
padding: 0.5rem 0.625rem;
}
.about-software-scroll {
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
}
.about-software-body {
font-size: 0.75rem;
line-height: 1.7;
text-align: justify;
text-justify: inter-word;
}
.about-software-html :deep(p) {
margin: 0;
font-size: 0.75rem;
line-height: 1.7;
text-align: justify;
text-justify: inter-word;
}
.about-software-html :deep(p + p) {
margin-top: 0.65rem;
}
.about-software-html :deep(ul) {
margin: 0.5rem 0 0;
padding-right: 1.1rem;
list-style: disc;
font-size: 0.75rem;
text-align: justify;
}
.about-software-enamad {
display: flex;
justify-content: center;
flex-shrink: 0;
margin-top: 1rem;
padding-top: 0.75rem;
}
</style>