2 changed files with 432 additions and 0 deletions
-
421src/App.vue
-
11src/main.js
@ -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> |
|||
@ -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') |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue