feat: add miniapp reference assets and primitives

This commit is contained in:
Codex
2026-09-14 06:38:44 +08:00
parent 311f251609
commit df47543501
37 changed files with 215 additions and 50 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
<view class="ui-empty">
<view class="ui-empty__illustration ui-empty__illustration--{{illustration}}">
<view class="ui-empty__icon"></view>
<image class="ui-empty__icon" src="/assets/icons/ui/empty.png" mode="aspectFit" />
</view>
<text class="ui-empty__title">{{title}}</text>
<text wx:if="{{description}}" class="ui-empty__desc">{{description}}</text>
+3 -23
View File
@@ -16,29 +16,9 @@
margin-bottom: var(--spacing-4);
}
.ui-empty__icon {
width: 60rpx;
height: 60rpx;
background: var(--color-text-disabled);
opacity: 0.5;
border-radius: 50%;
}
.ui-empty__illustration--search .ui-empty__icon {
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
}
.ui-empty__illustration--heart .ui-empty__icon {
clip-path: polygon(50% 100%, 0 38%, 18% 18%, 50% 38%, 82% 18%, 100% 38%);
border-radius: 0;
}
.ui-empty__illustration--activity .ui-empty__icon {
border-radius: var(--radius-sm);
width: 80rpx;
height: 80rpx;
}
.ui-empty__illustration--message .ui-empty__icon {
border-radius: var(--radius-md);
width: 80rpx;
height: 60rpx;
width: 72rpx;
height: 72rpx;
opacity: 0.72;
}
.ui-empty__title {
font-size: var(--font-size-lg);
+11 -1
View File
@@ -1 +1,11 @@
Component({properties:{title:{type:String,value:'加载失败'},description:{type:String,value:'请稍后重试'}},methods:{onRetry(){this.triggerEvent('retry')}}})
Component({
properties: {
title: { type: String, value: '加载失败' },
description: { type: String, value: '请稍后重试' },
},
methods: {
onRetry() {
this.triggerEvent('retry')
},
},
})
+6 -1
View File
@@ -1 +1,6 @@
<view class="error-state"><view class="error-icon">!</view><text class="error-title">{{title}}</text><text class="error-desc">{{description}}</text><button class="retry-btn" bindtap="onRetry">重新加载</button></view>
<view class="error-state">
<image class="error-icon" src="/assets/icons/ui/warning.png" mode="aspectFit" />
<text class="error-title">{{title}}</text>
<text class="error-desc">{{description}}</text>
<button class="retry-btn" bindtap="onRetry">重新加载</button>
</view>
+45 -1
View File
@@ -1 +1,45 @@
.error-state{padding:64rpx 32rpx;text-align:center;background:var(--color-bg-card);border-radius:var(--radius-lg)}.error-icon{width:72rpx;height:72rpx;margin:0 auto 20rpx;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--color-danger-light);color:var(--color-danger);font-weight:700}.error-title,.error-desc{display:block}.error-title{font-size:30rpx;font-weight:600}.error-desc{margin-top:8rpx;font-size:24rpx;color:var(--color-text-tertiary)}.retry-btn{width:220rpx;height:80rpx;line-height:80rpx;margin-top:24rpx;border:0;border-radius:var(--radius-full);background:var(--color-primary);color:var(--color-text-inverse);font-size:26rpx}.retry-btn::after{border:0}
.error-state {
padding: 64rpx 32rpx;
text-align: center;
background: var(--color-bg-card);
border-radius: var(--radius-lg);
}
.error-icon {
display: block;
width: 72rpx;
height: 72rpx;
margin: 0 auto 20rpx;
}
.error-title,
.error-desc {
display: block;
}
.error-title {
font-size: 30rpx;
font-weight: 600;
}
.error-desc {
margin-top: 8rpx;
color: var(--color-text-tertiary);
font-size: 24rpx;
}
.retry-btn {
width: 220rpx;
height: 80rpx;
margin-top: 24rpx;
border: 0;
border-radius: var(--radius-full);
background: var(--color-primary);
color: var(--color-text-inverse);
font-size: 26rpx;
line-height: 80rpx;
}
.retry-btn::after {
border: 0;
}
+1 -1
View File
@@ -1,7 +1,7 @@
<view class="ui-loading {{inline ? 'ui-loading--inline' : 'ui-loading--block'}}">
<block wx:if="{{mode === 'spinner'}}">
<view class="ui-loading__spinner">
<view class="ui-loading__spinner-ring"></view>
<image class="ui-loading__spinner-icon" src="/assets/icons/ui/loading.png" mode="aspectFit" />
</view>
<text wx:if="{{text}}" class="ui-loading__text">{{text}}</text>
</block>
+1 -4
View File
@@ -16,12 +16,9 @@
align-items: center;
justify-content: center;
}
.ui-loading__spinner-ring {
.ui-loading__spinner-icon {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
border: 4rpx solid var(--color-divider);
border-top-color: var(--color-primary);
animation: ui-loading-spin 0.8s linear infinite;
}
@keyframes ui-loading-spin {
+1 -1
View File
@@ -1,7 +1,7 @@
<view class="ui-nav ui-nav--{{bg}}" style="{{containerStyle}}">
<view class="ui-nav__bar">
<view wx:if="{{showBack}}" class="ui-nav__back" bindtap="onBack">
<view class="ui-nav__back-arrow"></view>
<image class="ui-nav__back-icon" src="/assets/icons/ui/back.png" mode="aspectFit" />
</view>
<view wx:else class="ui-nav__placeholder"></view>
<view class="ui-nav__title">{{title}}</view>
+3 -6
View File
@@ -28,12 +28,9 @@
justify-content: center;
flex-shrink: 0;
}
.ui-nav__back-arrow {
width: 20rpx;
height: 20rpx;
border-left: 4rpx solid var(--color-text-primary);
border-bottom: 4rpx solid var(--color-text-primary);
transform: rotate(45deg);
.ui-nav__back-icon {
width: 36rpx;
height: 36rpx;
}
.ui-nav__title {
flex: 1;
+1 -1
View File
@@ -5,6 +5,6 @@
</view>
<view wx:if="{{action}}" class="ui-section__action" bindtap="onAction">
<text>{{action}}</text>
<text class="ui-section__arrow">›</text>
<image class="ui-section__arrow" src="/assets/icons/ui/chevron-right.png" mode="aspectFit" />
</view>
</view>
+2 -2
View File
@@ -26,6 +26,6 @@
}
.ui-section__arrow {
margin-left: 4rpx;
font-size: var(--font-size-md);
line-height: 1;
width: 24rpx;
height: 24rpx;
}
+10
View File
@@ -0,0 +1,10 @@
Component({
options: { addGlobalClass: true },
properties: {
eyebrow: { type: String, value: '' },
title: { type: String, value: '' },
subtitle: { type: String, value: '' },
imageSrc: { type: String, value: '' },
tone: { type: String, value: 'rose' },
},
})
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
@@ -0,0 +1,9 @@
<view class="service-hero service-hero--{{tone}}">
<image class="service-hero__image" src="{{imageSrc}}" mode="aspectFill" />
<view class="service-hero__veil"></view>
<view class="service-hero__content">
<text wx:if="{{eyebrow}}" class="service-hero__eyebrow">{{eyebrow}}</text>
<text class="service-hero__title">{{title}}</text>
<text wx:if="{{subtitle}}" class="service-hero__subtitle">{{subtitle}}</text>
</view>
</view>
@@ -0,0 +1,63 @@
.service-hero {
position: relative;
height: 300rpx;
overflow: hidden;
border-radius: 0 0 32rpx 32rpx;
background: #fff4f7;
}
.service-hero__image,
.service-hero__veil {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.service-hero__veil {
background: linear-gradient(90deg, rgba(255, 250, 252, 0.98) 0%, rgba(255, 250, 252, 0.82) 38%, rgba(255, 250, 252, 0.08) 70%);
}
.service-hero--night .service-hero__veil {
background: linear-gradient(90deg, rgba(18, 24, 83, 0.92) 0%, rgba(34, 31, 111, 0.64) 42%, rgba(34, 31, 111, 0.08) 72%);
}
.service-hero__content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
justify-content: center;
width: 54%;
height: 100%;
padding: 0 36rpx;
box-sizing: border-box;
}
.service-hero__eyebrow {
margin-bottom: 10rpx;
color: var(--color-primary);
font-size: 22rpx;
font-weight: 600;
letter-spacing: 2rpx;
}
.service-hero__title {
color: #24305e;
font-size: 40rpx;
font-weight: 700;
line-height: 1.28;
}
.service-hero__subtitle {
margin-top: 14rpx;
color: #737c96;
font-size: 24rpx;
line-height: 1.55;
}
.service-hero--night .service-hero__eyebrow,
.service-hero--night .service-hero__title,
.service-hero--night .service-hero__subtitle {
color: #ffffff;
}
+8 -8
View File
@@ -6,14 +6,14 @@ const { authStore, appStore } = require('../../stores')
const api = require('../../utils/api')
const ENTRY_VISUALS = {
find_object: { tone: 'rose', iconAsset: '/assets/tabbar/discover-active.png' },
matchmaker: { tone: 'purple', iconAsset: '/assets/brand/logo-mark.png' },
activities: { tone: 'blue', iconAsset: '/assets/tabbar/activity-active.png' },
counseling: { tone: 'pink', iconAsset: '/assets/tabbar/message-active.png' },
classroom: { tone: 'amber', iconAsset: '/assets/covers/classroom-default.jpg' },
premarital: { tone: 'indigo', iconAsset: '/assets/tabbar/home-active.png' },
anti_fraud: { tone: 'red', iconAsset: '/assets/covers/anti-fraud-default.jpg' },
service_station: { tone: 'green', iconAsset: '/assets/covers/station-default.jpg' },
find_object: { tone: 'rose', iconAsset: '/assets/icons/home/find-object.png' },
matchmaker: { tone: 'purple', iconAsset: '/assets/icons/home/matchmaker.png' },
activities: { tone: 'blue', iconAsset: '/assets/icons/home/activity.png' },
counseling: { tone: 'pink', iconAsset: '/assets/icons/home/counseling.png' },
classroom: { tone: 'amber', iconAsset: '/assets/icons/home/classroom.png' },
premarital: { tone: 'indigo', iconAsset: '/assets/icons/home/premarital.png' },
anti_fraud: { tone: 'red', iconAsset: '/assets/icons/home/anti-fraud.png' },
service_station: { tone: 'green', iconAsset: '/assets/icons/home/service-station.png' },
}
const QUICK_ENTRIES = [
+46
View File
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict'
import { existsSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { createHash } from 'node:crypto'
const root = resolve(import.meta.dirname, '..')
const assets = [
@@ -16,10 +17,55 @@ const assets = [
'miniapp/assets/covers/station-default.jpg',
]
const miniappHeroAssets = [
'miniapp/assets/brand/home-hero-v2.jpg',
'miniapp/assets/service/matchmaker-hero.jpg',
'miniapp/assets/service/counseling-hero.jpg',
'miniapp/assets/service/premarital-hero.jpg',
'miniapp/assets/service/anti-fraud-hero.jpg',
'miniapp/assets/service/station-hero.jpg',
]
const homeIconAssets = [
'find-object.png',
'matchmaker.png',
'activity.png',
'counseling.png',
'classroom.png',
'premarital.png',
'anti-fraud.png',
'service-station.png',
].map((name) => `miniapp/assets/icons/home/${name}`)
const uiIconAssets = [
'back.png',
'chevron-right.png',
'warning.png',
'empty.png',
'loading.png',
'search.png',
].map((name) => `miniapp/assets/icons/ui/${name}`)
for (const file of assets) {
const path = resolve(root, file)
assert.ok(existsSync(path), `缺少品牌资产 ${file}`)
assert.ok(readFileSync(path).length > 4096, `品牌资产为空或过小 ${file}`)
}
for (const file of miniappHeroAssets) {
const path = resolve(root, file)
assert.ok(existsSync(path), `缺少小程序 Hero 素材 ${file}`)
assert.ok(readFileSync(path).length > 16 * 1024, `小程序 Hero 素材为空或过小 ${file}`)
}
const homeIconHashes = new Set()
for (const file of [...homeIconAssets, ...uiIconAssets]) {
const path = resolve(root, file)
assert.ok(existsSync(path), `缺少小程序图标 ${file}`)
const bytes = readFileSync(path)
assert.ok(bytes.length > 256, `小程序图标为空或过小 ${file}`)
if (homeIconAssets.includes(file)) homeIconHashes.add(createHash('sha256').update(bytes).digest('hex'))
}
assert.equal(homeIconHashes.size, homeIconAssets.length, '首页 8 个入口不得复用同一图标')
console.log('Brand asset contracts passed')