Browse Source

feat(page): add song-ui demo showcase

main
Mohan 1 month ago
parent
commit
a9e4f7e86f
  1. 421
      src/App.vue
  2. 11
      src/main.js

421
src/App.vue

@ -0,0 +1,421 @@
<script setup>
import { ref, provide } from 'vue'
import { useSongTheme } from './composables/useSongTheme'
import { useMessage } from './composables/useMessage'
import {
SongButton, SongCard, SongTag, SongInput, SongNav, SongTable,
SongBadge, SongProgress, SongModal, SongScrim, SongLogo, SongDisclaimer,
SongLoading, SongSeal
} from './components/index'
const theme = useSongTheme()
provide('songTheme', theme)
const message = useMessage()
const navItems = ref([
{ label: '美学', active: true },
{ label: '色彩', active: false },
{ label: '字体', active: false },
{ label: '组件', active: false }
])
const modalOpen = ref(false)
const showMaskLoading = ref(false)
const btnLoading = ref(false)
const name = ref('')
const email = ref('')
const patterns = ['ice', 'blind', 'tortoise', 'vine', 'water', 'cloud']
const patternNames = { ice: '冰裂', blind: '直棂', tortoise: '龟背', vine: '缠枝', water: '水纹', cloud: '云纹' }
const activePattern = ref('ice')
let loadingTimer = null
function simulate() {
btnLoading.value = true
clearTimeout(loadingTimer)
loadingTimer = setTimeout(() => {
btnLoading.value = false
message.success('登记已纳 · 章已钤')
}, 2200)
}
const columns = [
{ key: 'id', title: '序号' },
{ key: 'name', title: '藏品' },
{ key: 'dynasty', title: '窑口' },
{ key: 'glaze', title: '釉色' },
{ key: 'status', title: '状态' },
{ key: 'progress', title: '展线' }
]
const tableData = [
{ id: 'A-011', name: '汝窑天青弦纹三足樽', dynasty: '汝窑', glaze: '天青', status: 'in', progress: 72 },
{ id: 'A-024', name: '龙泉粉青贯耳瓶', dynasty: '龙泉', glaze: '粉青', status: 'review', progress: 46 },
{ id: 'A-033', name: '哥窑冰裂纹胆瓶', dynasty: '哥窑', glaze: '月白', status: 'out', progress: 9 },
{ id: 'A-047', name: '定窑白瓷童子枕', dynasty: '定窑', glaze: '牙白', status: 'ok', progress: 100 }
]
const STATUS = {
in: ['巡展中', 'primary'],
review: ['待审批', 'warning'],
out: ['出库', 'danger'],
ok: ['在库', 'success']
}
</script>
<template>
<div class="demo">
<SongScrim :pattern="activePattern" :opacity="0.5" />
<header class="demo__brand">
<span class="demo__brand-mark"><SongLogo :size="36" /></span>
<span class="demo__brand-flag">
<span class="demo__brand-name display">SongUI</span>
<span class="demo__brand-sub caption">檐下 · 天青至简</span>
</span>
</header>
<SongNav :items="navItems" />
<main class="demo__main">
<!-- G4 空灵展示区 -->
<section class="demo__hero">
<p class="demo__hero-kicker caption">马一角 · 夏半边</p>
<h1 class="demo__hero-title display">
雨过天晴云破处<br />这般颜色做将来
</h1>
<p class="demo__hero-desc">
以减柱造之疏朗汝瓷之天青宋画之留白为骨
以现代极简之功架塑一界安静温润有书卷气的界面语言
</p>
<div class="demo__hero-actions">
<SongButton size="large" type="secondary">观览组件</SongButton>
<SongButton size="large">开始使用</SongButton>
</div>
</section>
<!-- G3 疏朗色卡 -->
<section class="demo__section">
<h2 class="h2">宋韵色谱</h2>
<p class="caption demo__section-desc">语义与色名绑定 · 色相不可换明度可校准契约 §2</p>
<div class="demo__swatches">
<div v-for="s in [
['天青', '#88ADED'], ['月白', '#D6ECF0'], ['竹青', '#789262'],
['缃色', '#C9A227'], ['茶褐', '#A8795B'], ['赭石', '#845A33'],
['胭脂', '#A8493A'], ['墨灰', '#758A99'], ['墨色', '#3A4A52'], ['象牙白', '#F7F4EC']
]" :key="s[0]" class="demo__swatch">
<span class="demo__swatch-block" :style="{ background: s[1] }" />
<span class="demo__swatch-name">{{ s[0] }}</span>
<span class="demo__swatch-hex caption tnum">{{ s[1] }}</span>
</div>
</div>
</section>
<!-- 底纹 · 某种选择 -->
<section class="demo__section">
<h2 class="h2">底纹 · 几何化 SVG</h2>
<p class="caption demo__section-desc">契约 §5单屏主纹样 1 占比受限置于内容之下</p>
<div class="demo__pattern-row">
<button
v-for="p in patterns"
:key="p"
class="demo__pattern-btn"
:class="{ 'demo__pattern-btn--active': p === activePattern }"
@click="activePattern = p"
>{{ patternNames[p] }}</button>
</div>
<SongCard class="demo__pattern-preview">
<SongScrim :pattern="activePattern" />
<span class="demo__pattern-label">{{ patternNames[activePattern] }}</span>
</SongCard>
</section>
<!-- 组件展示 -->
<section class="demo__section">
<h2 class="h2">组件 · 檐下工坊</h2>
<div class="demo__grid">
<SongCard title="钮" class="demo__no-hover">
<div class="demo__row">
<SongButton>主钮</SongButton>
<SongButton type="secondary">次钮</SongButton>
<SongButton type="text">文钮</SongButton>
<SongButton type="danger">汰钮</SongButton>
<SongButton disabled>寸钮</SongButton>
</div>
<div class="demo__row">
<SongButton size="small">小字</SongButton>
<SongButton size="large">大字</SongButton>
<SongBadge type="danger" :count="3" dot>角标</SongBadge>
</div>
</SongCard>
<SongCard title="签">
<div class="demo__row">
<SongTag>默签</SongTag>
<SongTag type="primary">青签</SongTag>
<SongTag type="success">成签</SongTag>
<SongTag type="warning">审签</SongTag>
<SongTag type="danger">汰签</SongTag>
</div>
<div class="demo__row">
<SongProgress type="success" :percent="72" />
</div>
<div class="demo__row">
<SongProgress type="warning" :percent="46" bar=false />
</div>
</SongCard>
<SongCard title="墨痕" class="demo__no-hover">
<div class="demo__form">
<SongInput v-model="name" label="名签" placeholder="请书字号" required />
<SongInput v-model="email" label="款识" placeholder="人之署名(电子邮权)" />
<span class="demo__preview caption">
{{ name || '—' }} · {{ email || '—' }}
</span>
</div>
</SongCard>
</div>
<div class="demo__grid">
<SongCard title="提示 · 雾隐">
<p class="caption demo__row">雾隐淡入墨线 + 小章意象三息自散</p>
<div class="demo__row">
<SongButton size="small" type="secondary" @click="message.success('纳签成功 · 已入展线')"> · </SongButton>
<SongButton size="small" type="secondary" @click="message.warning('待审 · 尚未钤印')"> · </SongButton>
<SongButton size="small" type="secondary" @click="message.danger('此签出库 · 不符陈规')"> · </SongButton>
<SongButton size="small" type="secondary" @click="message.info('雨过天青 · 展线将启')"> · </SongButton>
</div>
</SongCard>
<SongCard title="加载 · 水墨晕染">
<div class="demo__row">
<SongButton size="small" :loading="btnLoading" @click="simulate">如纳 · 待墨</SongButton>
<SongButton size="small" type="secondary" @click="showMaskLoading = true">全屏 · 罩墨</SongButton>
</div>
<SongLoading :text="btnLoading ? '倒墨中' : '墨迹未干'" />
</SongCard>
<SongCard title="章印 · 钤印意象">
<div class="demo__row">
<SongSeal text="檐下" :size="70" />
<SongSeal text="雅集" :size="70" rotate="-1" />
<SongSeal text="安" round :size="70" />
<SongSeal text="观" round :size="70" rotate="-3" />
</div>
<p class="caption">方印田字格 4 · 圆印连珠单字 · SVG 自绘非字库</p>
</SongCard>
</div>
<!-- G1 密集中后台数据区 -->
<SongCard title="展签 · 名录" class="demo__table-card">
<SongTable :columns="columns" :data="tableData">
<template #status="{ row }">
<SongTag :type="STATUS[row.status][1]">{{ STATUS[row.status][0] }}</SongTag>
</template>
<template #progress="{ row }">
<SongProgress :type="row.progress > 80 ? 'success' : 'primary'" :percent="row.progress" />
</template>
</SongTable>
<template #header>
<div class="demo__card-head">
<span class="h3">展签 · 名录</span>
<div class="demo__card-actions">
<SongButton size="small" type="secondary">导入</SongButton>
<SongButton size="small" @click="modalOpen = true">登记</SongButton>
</div>
</div>
</template>
</SongCard>
</section>
</main>
<footer class="demo__foot">
<SongDisclaimer />
<SongDisclaimer text="本演示为 SongUI 研究契约的实现样本(Vue3 + Vite),全部纹样为几何化 SVG,字体遵循开源授权与回退策略。" />
<p class="demo__foot-mark caption tnum">SongUI © 2026 · 檐下 · 契约 v0.1</p>
</footer>
<SongModal v-model="modalOpen" title="登记 · 新签">
<div class="demo__form">
<SongInput label="藏品名" placeholder="如:汝窑天青碗" />
<SongInput label="窑口" placeholder="汝 / 龙泉 / 哥 / 定 / 钧" />
</div>
<template #footer>
<SongButton type="secondary" @click="modalOpen = false"></SongButton>
<SongButton @click="modalOpen = false"></SongButton>
</template>
</SongModal>
<SongLoading mask :visible="showMaskLoading" text="展卷 · 墨色氤氲" @click="showMaskLoading = false" />
</div>
</template>
<style scoped>
.demo {
min-height: 100vh;
position: relative;
}
.demo__brand {
display: flex;
align-items: center;
gap: 14px;
padding: 28px var(--song-space-6) 0;
position: relative;
z-index: 1;
}
.demo__brand-name {
font-size: 24px;
line-height: 1.2;
}
.demo__brand-sub {
display: block;
letter-spacing: 0.3em;
margin-top: 2px;
}
.demo__main {
max-width: 1020px;
margin: 0 auto;
padding: 0 var(--song-space-5);
position: relative;
z-index: 1;
}
.demo__hero {
padding: 72px 0 96px;
max-width: 620px;
}
.demo__hero-kicker {
letter-spacing: 0.5em;
color: var(--song-primary);
}
.demo__hero-title {
margin: 16px 0 20px;
}
.demo__hero-desc {
color: var(--song-text-secondary);
line-height: 2;
}
.demo__hero-actions {
margin-top: 32px;
display: flex;
gap: 14px;
}
.demo__section {
margin-bottom: 64px;
}
.demo__section-desc {
margin: 8px 0 20px;
letter-spacing: 0.08em;
}
.demo__swatches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
gap: 14px;
}
.demo__swatch-block {
display: block;
height: 64px;
border-radius: var(--song-radius-s);
border: 1px solid var(--song-border);
}
.demo__swatch-name {
display: block;
margin-top: 8px;
font-size: 13px;
}
.demo__swatch-hex {
display: block;
}
.demo__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.demo__pattern-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 16px;
}
.demo__pattern-btn {
height: 30px;
padding: 0 16px;
font-family: var(--song-font-serif);
font-size: 13px;
color: var(--song-text-secondary);
background: transparent;
border: 1px solid var(--song-border);
border-radius: var(--song-radius-s);
cursor: pointer;
transition: color var(--song-dur-1), border-color var(--song-dur-1), background var(--song-dur-1);
}
.demo__pattern-btn:hover {
color: var(--song-primary);
border-color: var(--song-primary);
}
.demo__pattern-btn--active {
color: var(--song-primary);
border-color: var(--song-primary);
background: var(--song-primary-bg);
}
.demo__pattern-preview {
position: relative;
height: 180px;
overflow: hidden;
display: grid;
place-items: center;
}
.demo__pattern-label {
position: relative;
z-index: 1;
font-family: var(--song-font-display);
font-size: 30px;
letter-spacing: 0.6em;
color: var(--song-text);
text-shadow: 0 0 24px var(--song-bg-elevated);
}
.demo__row {
margin-bottom: 16px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.demo__row:last-child {
margin-bottom: 0;
}
.demo__form {
display: flex;
flex-direction: column;
gap: 14px;
}
.demo__preview {
letter-spacing: 0.04em;
}
.demo__table-card {
margin-top: 20px;
}
.demo__card-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.demo__card-actions {
display: flex;
gap: 10px;
}
.demo__foot {
border-top: 1px solid var(--song-border);
padding: 32px var(--song-space-6) 40px;
max-width: 1020px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.demo__foot-mark {
margin-top: 20px;
letter-spacing: 0.2em;
}
</style>

11
src/main.js

@ -0,0 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
import { SongComponents } from './components/index'
import { initSongTheme } from './composables/useSongTheme'
import './styles/base.css'
initSongTheme()
createApp(App)
.use(SongComponents)
.mount('#app')
Loading…
Cancel
Save