feat(ui): restore reference mini program and admin design
This commit is contained in:
@@ -15,6 +15,8 @@ import ActivitiesView from './views/ActivitiesView.vue';
|
||||
import ContentView from './views/ContentView.vue';
|
||||
import NotificationDeliveriesView from './views/NotificationDeliveriesView.vue';
|
||||
import StatisticsView from './views/StatisticsView.vue';
|
||||
import DashboardView from './views/DashboardView.vue';
|
||||
import AdminIcon from './components/AdminIcon.vue';
|
||||
|
||||
const auth = useAuthStore();
|
||||
const form = reactive({ username: '', password: '' });
|
||||
@@ -23,6 +25,7 @@ const submitting = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const message = ref('');
|
||||
const activeView = ref<
|
||||
| 'dashboard'
|
||||
| 'applications'
|
||||
| 'beneficiaries'
|
||||
| 'cases'
|
||||
@@ -33,7 +36,7 @@ const activeView = ref<
|
||||
| 'content'
|
||||
| 'notifications'
|
||||
| 'statistics'
|
||||
>('applications');
|
||||
>('dashboard');
|
||||
|
||||
onMounted(() => auth.restore());
|
||||
|
||||
@@ -136,13 +139,21 @@ async function logout(): Promise<void> {
|
||||
<div><strong>乐活公益</strong><small>管理平台</small></div>
|
||||
</div>
|
||||
<nav>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'dashboard' }]"
|
||||
type="button"
|
||||
:aria-current="activeView === 'dashboard' ? 'page' : undefined"
|
||||
@click="activeView = 'dashboard'"
|
||||
>
|
||||
<AdminIcon name="dashboard" />工作概览
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'applications' }]"
|
||||
type="button"
|
||||
:aria-current="activeView === 'applications' ? 'page' : undefined"
|
||||
@click="activeView = 'applications'"
|
||||
>
|
||||
服务申请
|
||||
<AdminIcon name="application" />服务申请
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'statistics' }]"
|
||||
@@ -150,7 +161,7 @@ async function logout(): Promise<void> {
|
||||
:aria-current="activeView === 'statistics' ? 'page' : undefined"
|
||||
@click="activeView = 'statistics'"
|
||||
>
|
||||
服务统计
|
||||
<AdminIcon name="chart" />服务统计
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'content' }]"
|
||||
@@ -158,7 +169,7 @@ async function logout(): Promise<void> {
|
||||
:aria-current="activeView === 'content' ? 'page' : undefined"
|
||||
@click="activeView = 'content'"
|
||||
>
|
||||
内容发布
|
||||
<AdminIcon name="content" />内容发布
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'notifications' }]"
|
||||
@@ -166,7 +177,7 @@ async function logout(): Promise<void> {
|
||||
:aria-current="activeView === 'notifications' ? 'page' : undefined"
|
||||
@click="activeView = 'notifications'"
|
||||
>
|
||||
通知投递
|
||||
<AdminIcon name="notification" />通知投递
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'beneficiaries' }]"
|
||||
@@ -174,7 +185,7 @@ async function logout(): Promise<void> {
|
||||
:aria-current="activeView === 'beneficiaries' ? 'page' : undefined"
|
||||
@click="activeView = 'beneficiaries'"
|
||||
>
|
||||
服务对象
|
||||
<AdminIcon name="people" />服务对象
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'cases' }]"
|
||||
@@ -182,7 +193,7 @@ async function logout(): Promise<void> {
|
||||
:aria-current="activeView === 'cases' ? 'page' : undefined"
|
||||
@click="activeView = 'cases'"
|
||||
>
|
||||
服务工单
|
||||
<AdminIcon name="case" />服务工单
|
||||
</button>
|
||||
<button
|
||||
:class="['nav-item', { 'nav-item--active': activeView === 'follow-ups' }]"
|
||||
@@ -217,7 +228,7 @@ async function logout(): Promise<void> {
|
||||
活动与报名
|
||||
</button>
|
||||
</nav>
|
||||
<p class="phase-note">Phase 6:内容审核、站内消息与微信订阅通知</p>
|
||||
<p class="phase-note">仅限授权人员访问,关键业务操作均记录审计。</p>
|
||||
</aside>
|
||||
<div class="admin-main">
|
||||
<header class="admin-topbar">
|
||||
@@ -230,7 +241,11 @@ async function logout(): Promise<void> {
|
||||
</button>
|
||||
</header>
|
||||
<p v-if="message" class="global-message" role="alert">{{ message }}</p>
|
||||
<ServiceApplicationsView v-if="activeView === 'applications'" />
|
||||
<DashboardView
|
||||
v-if="activeView === 'dashboard'"
|
||||
@navigate="activeView = $event as typeof activeView"
|
||||
/>
|
||||
<ServiceApplicationsView v-else-if="activeView === 'applications'" />
|
||||
<BeneficiariesView v-else-if="activeView === 'beneficiaries'" />
|
||||
<ServiceCasesView v-else-if="activeView === 'cases'" />
|
||||
<FollowUpsView v-else-if="activeView === 'follow-ups'" />
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ name: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg class="admin-icon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
v-if="name === 'dashboard'"
|
||||
d="M4 4h6v6H4V4Zm10 0h6v9h-6V4ZM4 14h6v6H4v-6Zm10 3h6v3h-6v-3Z"
|
||||
/>
|
||||
<path
|
||||
v-else-if="name === 'application'"
|
||||
d="M7 3h10v3h3v15H4V6h3V3Zm2 2v2h6V5H9Zm0 6v2h6v-2H9Zm0 4v2h6v-2H9Z"
|
||||
/>
|
||||
<path
|
||||
v-else-if="name === 'people'"
|
||||
d="M8 11a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm8-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6ZM2 21v-3c0-3.2 2.7-5 6-5s6 1.8 6 5v3H2Zm13 0v-3.2c0-1.4-.5-2.7-1.4-3.7.8-.4 1.6-.6 2.4-.6 3 0 5 1.6 5 4.5v3h-6Z"
|
||||
/>
|
||||
<path
|
||||
v-else-if="name === 'case'"
|
||||
d="M9 3h6l1 3h5v14H3V6h5l1-3Zm1.4 3h3.2l-.3-1h-2.6l-.3 1ZM7 11v2h10v-2H7Zm0 4v2h7v-2H7Z"
|
||||
/>
|
||||
<path v-else-if="name === 'chart'" d="M4 20V9h4v11H4Zm6 0V4h4v16h-4Zm6 0v-7h4v7h-4Z" />
|
||||
<path
|
||||
v-else-if="name === 'content'"
|
||||
d="M4 3h16v18H4V3Zm3 4v2h10V7H7Zm0 4v2h10v-2H7Zm0 4v2h7v-2H7Z"
|
||||
/>
|
||||
<path v-else d="M12 2a7 7 0 0 1 7 7v4l2 3v1H3v-1l2-3V9a7 7 0 0 1 7-7Zm-3 17h6a3 3 0 0 1-6 0Z" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,5 +1,12 @@
|
||||
:root {
|
||||
--color-primary: #ff6b35;
|
||||
--primitive-orange-500: #ff6b35;
|
||||
--primitive-orange-600: #e95a28;
|
||||
--primitive-navy-900: #202c38;
|
||||
--primitive-navy-800: #293746;
|
||||
--primitive-gray-50: #f5f7f9;
|
||||
--primitive-gray-200: #e4e8ec;
|
||||
--primitive-white: #fff;
|
||||
--color-primary: var(--primitive-orange-500);
|
||||
--color-primary-hover: #e95a28;
|
||||
--color-primary-soft: #fff1eb;
|
||||
--color-text: #1f2329;
|
||||
@@ -8,10 +15,10 @@
|
||||
--color-success: #237a2b;
|
||||
--color-warning: #8a5a00;
|
||||
--color-info: #145ea8;
|
||||
--color-surface: #fff;
|
||||
--color-background: #f5f5f5;
|
||||
--color-border: #d9dce1;
|
||||
--color-sidebar: #263342;
|
||||
--color-surface: var(--primitive-white);
|
||||
--color-background: var(--primitive-gray-50);
|
||||
--color-border: var(--primitive-gray-200);
|
||||
--color-sidebar: var(--primitive-navy-900);
|
||||
--focus-ring: 0 0 0 3px rgb(255 107 53 / 28%);
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
@@ -211,6 +218,7 @@ input[aria-invalid='true'] {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.nav-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 10px 14px;
|
||||
@@ -219,6 +227,15 @@ input[aria-invalid='true'] {
|
||||
color: #e8edf2;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.admin-icon {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
flex: 0 0 auto;
|
||||
fill: currentColor;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.nav-item--active {
|
||||
color: #fff;
|
||||
@@ -246,6 +263,234 @@ input[aria-invalid='true'] {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.page-kicker {
|
||||
margin: 0 0 6px !important;
|
||||
color: var(--color-primary) !important;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.dashboard-summary .summary-card {
|
||||
border: 0;
|
||||
border-top: 3px solid var(--color-primary);
|
||||
box-shadow: 0 5px 18px rgb(28 39 51 / 7%);
|
||||
}
|
||||
.dashboard-summary .summary-card:nth-child(2) {
|
||||
border-color: #378fe8;
|
||||
}
|
||||
.dashboard-summary .summary-card:nth-child(3) {
|
||||
border-color: #42af57;
|
||||
}
|
||||
.dashboard-summary .summary-card:nth-child(4) {
|
||||
border-color: #ffb13b;
|
||||
}
|
||||
.dashboard-charts {
|
||||
display: grid;
|
||||
margin-bottom: 20px;
|
||||
grid-template-columns: minmax(0, 1.4fr) minmax(320px, 0.8fr);
|
||||
gap: 20px;
|
||||
}
|
||||
.chart-panel {
|
||||
min-height: 360px;
|
||||
}
|
||||
.panel-heading {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
.panel-heading h2 {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
.panel-heading p,
|
||||
.panel-heading > span {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
.bar-chart {
|
||||
display: flex;
|
||||
height: 220px;
|
||||
padding: 18px 10px 0;
|
||||
align-items: stretch;
|
||||
justify-content: space-around;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
#eef1f4 0,
|
||||
#eef1f4 1px,
|
||||
transparent 1px,
|
||||
transparent 55px
|
||||
);
|
||||
}
|
||||
.bar-column {
|
||||
display: flex;
|
||||
min-width: 36px;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
.bar-values {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.bar {
|
||||
display: block;
|
||||
width: min(18px, 35%);
|
||||
min-height: 5px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.bar--application {
|
||||
background: #4b98ee;
|
||||
}
|
||||
.bar--completed {
|
||||
background: #ff7848;
|
||||
}
|
||||
.bar-column small {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
margin-top: 18px;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
.legend-dot {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 7px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.legend-dot--blue {
|
||||
background: #4b98ee;
|
||||
}
|
||||
.legend-dot--orange {
|
||||
background: #ff7848;
|
||||
}
|
||||
.distribution-list {
|
||||
display: grid;
|
||||
gap: 19px;
|
||||
}
|
||||
.distribution-list > div > div {
|
||||
display: flex;
|
||||
margin-bottom: 7px;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
}
|
||||
.distribution-list progress {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
accent-color: var(--color-primary);
|
||||
}
|
||||
.quick-panel {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.quick-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
.quick-actions button {
|
||||
display: flex;
|
||||
min-height: 76px;
|
||||
padding: 14px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
color: var(--color-text);
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
.quick-actions b {
|
||||
display: grid;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
background: var(--color-primary);
|
||||
}
|
||||
.quick-actions .blue {
|
||||
background: #378fe8;
|
||||
}
|
||||
.quick-actions .green {
|
||||
background: #42af57;
|
||||
}
|
||||
.quick-actions .gold {
|
||||
background: #e8a22c;
|
||||
}
|
||||
.muted-text {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.case-stepper {
|
||||
display: grid;
|
||||
margin-bottom: 20px;
|
||||
padding: 24px 32px;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
.case-stepper div {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
.case-stepper div:not(:last-child)::after {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 14px;
|
||||
left: calc(50% + 20px);
|
||||
width: calc(100% - 40px);
|
||||
height: 2px;
|
||||
content: '';
|
||||
background: #dde2e6;
|
||||
}
|
||||
.case-stepper i {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
place-items: center;
|
||||
border: 2px solid #cbd2d8;
|
||||
border-radius: 50%;
|
||||
color: #7c858d;
|
||||
background: #fff;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
.case-stepper .done {
|
||||
color: #268948;
|
||||
font-weight: 600;
|
||||
}
|
||||
.case-stepper .done i {
|
||||
border-color: #3daf5c;
|
||||
color: #fff;
|
||||
background: #3daf5c;
|
||||
}
|
||||
.case-stepper .done:not(:last-child)::after {
|
||||
background: #78ca8d;
|
||||
}
|
||||
.admin-main {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -742,6 +987,12 @@ button.summary-card:hover {
|
||||
.summary-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.dashboard-charts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.quick-actions {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
@@ -789,6 +1040,9 @@ button.summary-card:hover {
|
||||
.summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.quick-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { SERVICE_CATEGORY_LABELS, type StatisticsOverview } from '@lehuo/contracts';
|
||||
import { fetchStatistics } from '../api/statistics';
|
||||
|
||||
const emit = defineEmits<{ navigate: [view: string] }>();
|
||||
const data = ref<StatisticsOverview | null>(null);
|
||||
const loading = ref(true);
|
||||
const message = ref('');
|
||||
const maxMonthly = computed(() =>
|
||||
Math.max(1, ...(data.value?.monthly.map((item) => item.applications) ?? [1])),
|
||||
);
|
||||
const totalCategory = computed(() =>
|
||||
Math.max(1, data.value?.byCategory.reduce((sum, item) => sum + item.count, 0) ?? 1),
|
||||
);
|
||||
const palette = ['#378fe8', '#ff7041', '#ffb84a', '#57bf58', '#7a67d8'];
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
message.value = '';
|
||||
try {
|
||||
data.value = await fetchStatistics();
|
||||
} catch (error) {
|
||||
message.value = error instanceof Error ? error.message : '数据加载失败';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="content-area dashboard-page">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<p class="page-kicker">管理后台</p>
|
||||
<h1>工作概览</h1>
|
||||
<p>业务数据由系统实时汇总,帮助团队掌握服务进展。</p>
|
||||
</div>
|
||||
<button class="secondary-button compact-button" @click="load">刷新数据</button>
|
||||
</header>
|
||||
<p v-if="message" class="global-message" role="alert">{{ message }}</p>
|
||||
<section v-if="loading" class="summary-grid">
|
||||
<div v-for="n in 4" :key="n" class="summary-card table-skeleton"></div>
|
||||
</section>
|
||||
<template v-else-if="data">
|
||||
<section class="summary-grid dashboard-summary">
|
||||
<article class="summary-card">
|
||||
<span>服务对象总数</span
|
||||
><strong>{{ data.uniqueBeneficiaryCount.toLocaleString() }}</strong
|
||||
><small>统计区间内去重人数</small>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<span>服务工单完成</span><strong>{{ data.completedServiceCount.toLocaleString() }}</strong
|
||||
><small>已完成服务事实</small>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<span>志愿者人数</span><strong>{{ data.volunteerCount.toLocaleString() }}</strong
|
||||
><small>已登记志愿者</small>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<span>服务人次</span><strong>{{ data.servicePersonTimes.toLocaleString() }}</strong
|
||||
><small>累计完成记录</small>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="dashboard-charts">
|
||||
<article class="panel chart-panel">
|
||||
<div class="panel-heading">
|
||||
<div>
|
||||
<h2>服务工作趋势</h2>
|
||||
<p>申请量与完成服务量</p>
|
||||
</div>
|
||||
<span>{{ data.period.startDate }} — {{ data.period.endDate }}</span>
|
||||
</div>
|
||||
<div class="bar-chart" role="img" aria-label="按月服务工作趋势">
|
||||
<div v-for="item in data.monthly" :key="item.month" class="bar-column">
|
||||
<div class="bar-values">
|
||||
<i
|
||||
class="bar bar--application"
|
||||
:style="{ height: `${Math.max(5, (item.applications / maxMonthly) * 100)}%` }"
|
||||
:title="`申请 ${item.applications}`"
|
||||
></i
|
||||
><i
|
||||
class="bar bar--completed"
|
||||
:style="{
|
||||
height: `${Math.max(5, (item.completedServices / maxMonthly) * 100)}%`,
|
||||
}"
|
||||
:title="`完成 ${item.completedServices}`"
|
||||
></i>
|
||||
</div>
|
||||
<small>{{ item.month.slice(5) }}月</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-legend">
|
||||
<span><i class="legend-dot legend-dot--blue"></i>申请</span
|
||||
><span><i class="legend-dot legend-dot--orange"></i>完成服务</span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="panel chart-panel">
|
||||
<div class="panel-heading">
|
||||
<div>
|
||||
<h2>服务类型分布</h2>
|
||||
<p>按申请服务类别统计</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="distribution-list">
|
||||
<div v-for="(item, index) in data.byCategory" :key="item.category">
|
||||
<div>
|
||||
<span
|
||||
><i
|
||||
class="legend-dot"
|
||||
:style="{ background: palette[index % palette.length] }"
|
||||
></i
|
||||
>{{ SERVICE_CATEGORY_LABELS[item.category] }}</span
|
||||
><strong>{{ item.count }}</strong>
|
||||
</div>
|
||||
<progress :value="item.count" :max="totalCategory"></progress>
|
||||
</div>
|
||||
<p v-if="!data.byCategory.length" class="muted-text">当前区间暂无分类数据</p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="panel quick-panel">
|
||||
<div class="panel-heading">
|
||||
<div>
|
||||
<h2>快捷入口</h2>
|
||||
<p>进入高频业务工作台</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<button @click="emit('navigate', 'applications')">
|
||||
<b>申</b><span>新增服务跟进</span></button
|
||||
><button @click="emit('navigate', 'cases')">
|
||||
<b class="blue">单</b><span>处理服务工单</span></button
|
||||
><button @click="emit('navigate', 'volunteers')">
|
||||
<b class="green">愿</b><span>志愿者审核</span></button
|
||||
><button @click="emit('navigate', 'content')">
|
||||
<b class="gold">讯</b><span>发布公益内容</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
@@ -318,6 +318,25 @@ onMounted(() => load());
|
||||
仅可将以下信息用于已分配的公益服务;查看、指派和记录操作均会留痕。
|
||||
</div>
|
||||
<p v-if="message" class="global-message" role="status">{{ message }}</p>
|
||||
<section class="case-stepper" aria-label="工单处理进度">
|
||||
<div class="done"><i>1</i><span>已建单</span></div>
|
||||
<div
|
||||
:class="{
|
||||
done: ['ASSIGNED', 'IN_SERVICE', 'SERVICE_COMPLETED'].includes(selected.status),
|
||||
}"
|
||||
>
|
||||
<i>2</i><span>已指派</span>
|
||||
</div>
|
||||
<div :class="{ done: ['IN_SERVICE', 'SERVICE_COMPLETED'].includes(selected.status) }">
|
||||
<i>3</i><span>服务中</span>
|
||||
</div>
|
||||
<div :class="{ done: selected.status === 'SERVICE_COMPLETED' }">
|
||||
<i>4</i><span>服务完成</span>
|
||||
</div>
|
||||
<div :class="{ done: selected.status === 'SERVICE_COMPLETED' }">
|
||||
<i>5</i><span>进入回访</span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="detail-grid">
|
||||
<article class="panel">
|
||||
<h2>服务计划</h2>
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
page {
|
||||
/* 乐活公益公众端设计令牌:暖、可信、适老。 */
|
||||
/* Primitive → semantic → component tokens. */
|
||||
--primitive-orange-500: #ff6b35;
|
||||
--primitive-orange-600: #e85220;
|
||||
--primitive-orange-50: #fff5ef;
|
||||
--primitive-green-500: #34ad50;
|
||||
--primitive-blue-500: #2f91ed;
|
||||
--primitive-gray-950: #1f2429;
|
||||
--primitive-gray-600: #687078;
|
||||
--primitive-gray-200: #e8ebee;
|
||||
--primitive-gray-50: #f6f7f8;
|
||||
--primitive-white: #ffffff;
|
||||
--color-primary: #f05a28;
|
||||
--color-primary-pressed: #d94718;
|
||||
--color-primary-soft: #fff0e8;
|
||||
@@ -12,16 +22,18 @@ page {
|
||||
--color-warning: #b76e00;
|
||||
--color-warning-soft: #fff6dd;
|
||||
--color-danger: #b42318;
|
||||
--color-text: #24211f;
|
||||
--color-text-secondary: #6e6862;
|
||||
--color-text: var(--primitive-gray-950);
|
||||
--color-text-secondary: var(--primitive-gray-600);
|
||||
--color-text-tertiary: #918a83;
|
||||
--color-border: #e9e3dd;
|
||||
--color-background: #f7f5f2;
|
||||
--color-surface: #ffffff;
|
||||
--color-border: var(--primitive-gray-200);
|
||||
--color-divider: #f0f1f2;
|
||||
--color-background: var(--primitive-gray-50);
|
||||
--color-surface: var(--primitive-white);
|
||||
--radius-sm: 16rpx;
|
||||
--radius-md: 24rpx;
|
||||
--radius-lg: 32rpx;
|
||||
--shadow-card: 0 10rpx 32rpx rgba(91, 62, 45, 0.07);
|
||||
--shadow-hero: 0 16rpx 38rpx rgba(231, 79, 29, 0.22);
|
||||
min-height: 100%;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 后续能力只在此登记扩展边界,不注册页面、不展示入口,也不产生模拟数据。
|
||||
* 接入时必须完成独立产品评审、接口契约、权限、审计与迁移设计。
|
||||
*/
|
||||
export const FUTURE_CAPABILITIES = {
|
||||
donation: { enabled: false, route: '' },
|
||||
recurringDonation: { enabled: false, route: '' },
|
||||
donationRecords: { enabled: false, route: '' },
|
||||
materialOrders: { enabled: false, route: '' },
|
||||
volunteerLearning: { enabled: false, route: '' },
|
||||
volunteerCredentials: { enabled: false, route: '' },
|
||||
volunteerTeams: { enabled: false, route: '' },
|
||||
} as const;
|
||||
@@ -2,10 +2,10 @@ Component({
|
||||
data: {
|
||||
selected: 0,
|
||||
tabs: [
|
||||
{ pagePath: '/pages/index/index', text: '首页', icon: '⌂' },
|
||||
{ pagePath: '/pages/service-hub/service-hub', text: '服务', icon: '♡' },
|
||||
{ pagePath: '/pages/public-welfare/public-welfare', text: '公益', icon: '◎' },
|
||||
{ pagePath: '/pages/profile/profile', text: '我的', icon: '○' },
|
||||
{ pagePath: '/pages/index/index', text: '首页', icon: 'home' },
|
||||
{ pagePath: '/pages/service-hub/service-hub', text: '服务', icon: 'service' },
|
||||
{ pagePath: '/pages/public-welfare/public-welfare', text: '发现', icon: 'discover' },
|
||||
{ pagePath: '/pages/profile/profile', text: '我的', icon: 'profile' },
|
||||
],
|
||||
},
|
||||
lifetimes: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<view class="tab-bar">
|
||||
<view class="tab-bar__inner">
|
||||
<button wx:for="{{tabs}}" wx:key="pagePath" class="tab {{selected === index ? 'tab--active' : ''}}" data-index="{{index}}" bindtap="switchTab" aria-label="{{item.text}}">
|
||||
<text class="tab__icon" aria-hidden="true">{{item.icon}}</text>
|
||||
<view class="tab__icon tab__icon--{{item.icon}}" aria-hidden="true"><view class="tab__icon-core"></view></view>
|
||||
<text class="tab__text">{{item.text}}</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
@@ -1,45 +1,117 @@
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
z-index: 90;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border-top: 2rpx solid #eee8e2;
|
||||
box-shadow: 0 -8rpx 28rpx rgba(74, 55, 43, 0.06);
|
||||
padding: 0 20rpx env(safe-area-inset-bottom);
|
||||
}
|
||||
.tab-bar__inner {
|
||||
display: flex;
|
||||
height: 112rpx;
|
||||
align-items: stretch;
|
||||
display: grid;
|
||||
height: 106rpx;
|
||||
padding: 8rpx 10rpx 4rpx;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border: 2rpx solid #eceff1;
|
||||
border-bottom: 0;
|
||||
border-radius: 28rpx 28rpx 0 0;
|
||||
background: rgba(255, 255, 255, 0.97);
|
||||
box-shadow: 0 -8rpx 30rpx rgba(31, 36, 41, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tab {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 10rpx 4rpx 8rpx;
|
||||
flex: 1;
|
||||
padding: 4rpx;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #77716b;
|
||||
gap: 5rpx;
|
||||
color: #7b8288;
|
||||
background: transparent;
|
||||
font-size: 23rpx;
|
||||
font-size: 21rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
.tab__icon {
|
||||
height: 40rpx;
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
.tab__text {
|
||||
margin-top: 7rpx;
|
||||
}
|
||||
.tab--active {
|
||||
color: #f05a28;
|
||||
}
|
||||
.tab--active .tab__text {
|
||||
font-weight: 700;
|
||||
}
|
||||
.tab__icon {
|
||||
position: relative;
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
color: currentColor;
|
||||
}
|
||||
.tab__icon::before,
|
||||
.tab__icon::after,
|
||||
.tab__icon-core {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
}
|
||||
.tab__icon--home::before {
|
||||
left: 6rpx;
|
||||
bottom: 4rpx;
|
||||
width: 27rpx;
|
||||
height: 24rpx;
|
||||
border: 4rpx solid currentColor;
|
||||
border-top: 0;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
.tab__icon--home::after {
|
||||
left: 6rpx;
|
||||
top: 4rpx;
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
border-top: 4rpx solid currentColor;
|
||||
border-left: 4rpx solid currentColor;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.tab__icon--service::before {
|
||||
left: 2rpx;
|
||||
top: 10rpx;
|
||||
width: 34rpx;
|
||||
height: 22rpx;
|
||||
border: 4rpx solid currentColor;
|
||||
border-radius: 18rpx 18rpx 9rpx 9rpx;
|
||||
transform: rotate(-10deg);
|
||||
}
|
||||
.tab__icon--service::after {
|
||||
left: 9rpx;
|
||||
top: 0;
|
||||
width: 20rpx;
|
||||
height: 17rpx;
|
||||
border: 4rpx solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: 15rpx 15rpx 0 0;
|
||||
}
|
||||
.tab__icon--discover::before {
|
||||
inset: 3rpx;
|
||||
border: 4rpx solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tab__icon--discover::after {
|
||||
left: 15rpx;
|
||||
top: 9rpx;
|
||||
width: 9rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 8rpx 8rpx 2rpx 2rpx;
|
||||
background: currentColor;
|
||||
transform: rotate(35deg);
|
||||
}
|
||||
.tab__icon--profile::before {
|
||||
left: 12rpx;
|
||||
top: 2rpx;
|
||||
width: 15rpx;
|
||||
height: 15rpx;
|
||||
border: 4rpx solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tab__icon--profile::after {
|
||||
left: 5rpx;
|
||||
bottom: 1rpx;
|
||||
width: 29rpx;
|
||||
height: 18rpx;
|
||||
border: 4rpx solid currentColor;
|
||||
border-radius: 18rpx 18rpx 4rpx 4rpx;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
|
||||
<block wx:else>
|
||||
<view wx:if="{{step === 1}}" class="form-card">
|
||||
<text class="form-title">需要什么帮助?</text>
|
||||
<text class="form-hint">请选择最接近的一项,工作人员后续会进一步了解。</text>
|
||||
<view class="application-banner"><text>请选择需要的服务类型</text><text>我们会认真了解并及时回应</text></view>
|
||||
<button
|
||||
wx:for="{{categories}}"
|
||||
wx:key="code"
|
||||
@@ -30,11 +29,15 @@
|
||||
bindtap="selectCategory"
|
||||
aria-label="{{item.label}}:{{item.description}}"
|
||||
>
|
||||
<image wx:if="{{item.code === 'OLD_AGE'}}" class="selection-photo" src="/assets/images/brand-elder-care.jpg" mode="aspectFill" />
|
||||
<image wx:elif="{{item.code === 'WOMEN_CHILDREN'}}" class="selection-photo" src="/assets/images/brand-women-support.jpg" mode="aspectFill" />
|
||||
<image wx:elif="{{item.code === 'DISABILITY'}}" class="selection-photo" src="/assets/images/brand-disability-support.jpg" mode="aspectFill" />
|
||||
<view wx:else class="selection-photo selection-photo--fallback">公益服务</view>
|
||||
<view class="selection-content">
|
||||
<text class="selection-title">{{item.label}}</text>
|
||||
<text class="selection-description">{{item.description}}</text>
|
||||
</view>
|
||||
<text class="selection-status">{{serviceCategory === item.code ? '已选择' : '选择'}}</text>
|
||||
<text class="selection-status">{{serviceCategory === item.code ? '✓' : '›'}}</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -235,3 +235,36 @@
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
.application-banner {
|
||||
display: flex;
|
||||
min-height: 126rpx;
|
||||
margin: -32rpx -32rpx 24rpx;
|
||||
padding: 28rpx 30rpx;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border-radius: 30rpx 30rpx 16rpx 16rpx;
|
||||
color: #e84e1e;
|
||||
background: linear-gradient(120deg, #fff0e7, #fff8f3);
|
||||
font-weight: 800;
|
||||
}
|
||||
.application-banner text:last-child {
|
||||
margin-top: 7rpx;
|
||||
color: #7b6257;
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
.selection-photo {
|
||||
width: 148rpx;
|
||||
height: 104rpx;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 18rpx;
|
||||
object-fit: cover;
|
||||
}
|
||||
.selection-photo--fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #7c6d65;
|
||||
background: #f4f1ee;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@ Page({
|
||||
title: MINI_PROGRAM_TITLE,
|
||||
canCollaborate: false,
|
||||
serviceCategories: [
|
||||
{ code: 'OLD_AGE', label: '关爱老人', icon: '人', tone: 'orange' },
|
||||
{ code: 'WOMEN_CHILDREN', label: '妇女帮扶', icon: '爱', tone: 'rose' },
|
||||
{ code: 'WOMEN_CHILDREN', label: '儿童关爱', icon: '苗', tone: 'green' },
|
||||
{ code: 'DISABILITY', label: '残障帮扶', icon: '助', tone: 'blue' },
|
||||
{ code: 'DIFFICULT_FAMILY', label: '生活困难', icon: '家', tone: 'gold' },
|
||||
{ code: 'OTHER', label: '其他服务', icon: '心', tone: 'purple' },
|
||||
{ code: 'OLD_AGE', label: '关爱老人', symbol: '老', tone: 'orange' },
|
||||
{ code: 'WOMEN_CHILDREN', label: '困境儿童', symbol: '童', tone: 'green' },
|
||||
{ code: 'WOMEN_CHILDREN', label: '妇女帮扶', symbol: '女', tone: 'rose' },
|
||||
{ code: 'DISABILITY', label: '残障救助', symbol: '助', tone: 'blue' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -90,4 +88,7 @@ Page({
|
||||
viewNotifications() {
|
||||
void wx.navigateTo({ url: '/pages/notifications/notifications' });
|
||||
},
|
||||
apply() {
|
||||
void wx.navigateTo({ url: '/pages/application-form/application-form?mode=SELF' });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
<view class="page home-page">
|
||||
<view class="home-hero">
|
||||
<view class="hero-orb hero-orb--one"></view><view class="hero-orb hero-orb--two"></view>
|
||||
<text class="hero-brand">乐活公益 ♡</text>
|
||||
<text class="hero-title">用爱连接,让爱传递</text>
|
||||
<text class="hero-copy">有需要,告诉我们;想帮忙,也欢迎加入。</text>
|
||||
<view class="hero-trust"><text>公益服务</text><text>隐私保护</text><text>专人跟进</text></view>
|
||||
<view class="brand-row">
|
||||
<view class="brand-logo"><view class="brand-logo__heart"></view></view>
|
||||
<view><text class="brand-name">乐活公益</text><text class="brand-slogan">用爱连接 · 让爱传递</text></view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{authStatus === 'loading'}}" class="state-card" aria-live="polite"><view class="skeleton-line skeleton-line--wide"></view><view class="skeleton-line"></view><text class="state-text">正在安全登录,请稍候…</text></view>
|
||||
<view wx:elif="{{authStatus === 'error'}}" class="state-card" aria-live="polite"><text class="state-title">暂时无法登录</text><text class="state-text">{{message}}</text><button class="secondary-button" bindtap="retryLogin">重新登录</button></view>
|
||||
<view class="home-hero">
|
||||
<view class="hero-glow hero-glow--one"></view><view class="hero-glow hero-glow--two"></view>
|
||||
<text class="hero-title">用爱连接 让爱传递</text>
|
||||
<text class="hero-copy">乐于助人 · 助人为乐 · 服务社会</text>
|
||||
<button class="hero-action" bindtap="apply">立即申请服务 <text>›</text></button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{authStatus === 'loading'}}" class="state-card"><view class="skeleton-line skeleton-line--wide"></view><text class="state-text">正在安全登录,请稍候…</text></view>
|
||||
<view wx:elif="{{authStatus === 'error'}}" class="state-card"><text class="state-title">暂时无法登录</text><text class="state-text">{{message}}</text><button class="secondary-button" bindtap="retryLogin">重新登录</button></view>
|
||||
|
||||
<block wx:else>
|
||||
<view class="primary-actions">
|
||||
<button class="primary-action primary-action--warm" bindtap="applyForSelf"><view class="primary-action__icon">♡</view><view><text class="primary-action__title">我需要帮助</text><text class="primary-action__copy">为自己申请暖心服务</text></view><text class="primary-action__arrow">›</text></button>
|
||||
<button class="primary-action primary-action--green" bindtap="applyForFamily"><view class="primary-action__icon">♧</view><view><text class="primary-action__title">帮家人申请</text><text class="primary-action__copy">替家人或他人说明需求</text></view><text class="primary-action__arrow">›</text></button>
|
||||
<view class="quick-grid">
|
||||
<button wx:for="{{serviceCategories}}" wx:key="label" class="quick-item" data-code="{{item.code}}" bindtap="applyForCategory"><view class="quick-icon quick-icon--{{item.tone}}"><text>{{item.symbol}}</text></view><text>{{item.label}}</text></button>
|
||||
<button class="quick-item" bindtap="apply"><view class="quick-icon quick-icon--blue"><text>申</text></view><text>服务申请</text></button>
|
||||
<button class="quick-item" bindtap="viewVolunteerCenter"><view class="quick-icon quick-icon--gold"><text>愿</text></view><text>志愿服务</text></button>
|
||||
<button class="quick-item" bindtap="viewProjects"><view class="quick-icon quick-icon--green"><text>益</text></view><text>公益项目</text></button>
|
||||
<button class="quick-item" bindtap="viewContent"><view class="quick-icon quick-icon--rose"><text>讯</text></view><text>公益动态</text></button>
|
||||
</view>
|
||||
|
||||
<button class="progress-banner" bindtap="viewMyApplications"><view><text class="progress-banner__label">我的服务进度</text><text class="progress-banner__copy">查看申请受理与跟进情况</text></view><text class="progress-banner__arrow">查看 ›</text></button>
|
||||
<button class="notice-strip" bindtap="viewContent"><text class="notice-badge">公告</text><text class="notice-text">查看乐活公益最新服务动态与温馨提示</text><text>›</text></button>
|
||||
|
||||
<view class="section-heading"><text class="section-title">我们能帮什么</text><text class="section-note">六类暖心服务</text></view>
|
||||
<view class="service-grid">
|
||||
<button wx:for="{{serviceCategories}}" wx:key="code" class="service-item service-item--{{item.tone}}" data-code="{{item.code}}" bindtap="applyForCategory"><text class="service-item__icon">{{item.icon}}</text><text class="service-item__label">{{item.label}}</text></button>
|
||||
<view class="section-heading"><text class="section-title">公益项目</text><button class="section-link" bindtap="viewProjects">更多 ›</button></view>
|
||||
<button class="feature-project" bindtap="viewProjects">
|
||||
<image src="/assets/images/brand-elder-care.jpg" mode="aspectFill"></image>
|
||||
<view class="feature-project__body"><text class="feature-project__title">持续关注身边的公益需要</text><text class="feature-project__copy">查看公开项目、服务范围与活动安排,选择合适的方式参与。</text><view class="feature-project__meta"><text>公益项目</text><text>查看详情 ›</text></view></view>
|
||||
</button>
|
||||
|
||||
<view class="section-heading"><text class="section-title">便捷入口</text></view>
|
||||
<view class="service-list">
|
||||
<button bindtap="viewMyApplications"><view class="list-mark list-mark--orange">进</view><view><text class="list-title">服务进度</text><text class="list-copy">查看申请受理与跟进情况</text></view><text>›</text></button>
|
||||
<button wx:if="{{canCollaborate}}" bindtap="viewAssignedCases"><view class="list-mark list-mark--blue">协</view><view><text class="list-title">协作任务</text><text class="list-copy">查看已授权的服务工单</text></view><text>›</text></button>
|
||||
<button bindtap="viewActivities"><view class="list-mark list-mark--green">活</view><view><text class="list-title">公益活动</text><text class="list-copy">浏览活动并提交报名</text></view><text>›</text></button>
|
||||
</view>
|
||||
|
||||
<view class="section-heading"><text class="section-title">一起参与公益</text><text class="section-note">每份行动都有温度</text></view>
|
||||
<view class="participation-card">
|
||||
<button class="participation-item" bindtap="viewProjects"><text class="participation-item__icon orange">◎</text><view><text class="participation-item__title">公益项目</text><text class="participation-item__copy">看见持续发生的改变</text></view><text>›</text></button>
|
||||
<button class="participation-item" bindtap="viewActivities"><text class="participation-item__icon blue">◇</text><view><text class="participation-item__title">公益活动</text><text class="participation-item__copy">找到适合参与的活动</text></view><text>›</text></button>
|
||||
<button class="participation-item" bindtap="viewVolunteerCenter"><text class="participation-item__icon green">♧</text><view><text class="participation-item__title">成为志愿者</text><text class="participation-item__copy">认证、报名与服务时长</text></view><text>›</text></button>
|
||||
</view>
|
||||
|
||||
<view class="section-heading"><text class="section-title">乐活动态</text><text class="section-note">内容均经审核发布</text></view>
|
||||
<view class="content-links"><button bindtap="viewContent">公益动态 <text>›</text></button><button bindtap="viewOrganization">了解乐活 <text>›</text></button></view>
|
||||
<view class="safety-note"><text class="safety-note__title">紧急情况请先求助公共服务</text><text>如遇危及生命安全的情况,请及时联系 110 或 120。</text></view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
@@ -1,293 +1,282 @@
|
||||
.home-page {
|
||||
padding-top: 20rpx;
|
||||
padding-top: 18rpx;
|
||||
}
|
||||
.brand-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
padding: 4rpx 8rpx 24rpx;
|
||||
}
|
||||
.brand-logo {
|
||||
position: relative;
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 22rpx;
|
||||
background: linear-gradient(145deg, #ff9d26, #ff5726);
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
.brand-logo::after {
|
||||
position: absolute;
|
||||
right: 11rpx;
|
||||
bottom: 10rpx;
|
||||
width: 24rpx;
|
||||
height: 37rpx;
|
||||
border-radius: 20rpx 20rpx 4rpx 20rpx;
|
||||
content: '';
|
||||
background: #fff;
|
||||
transform: rotate(35deg);
|
||||
}
|
||||
.brand-name,
|
||||
.brand-slogan {
|
||||
display: block;
|
||||
}
|
||||
.brand-name {
|
||||
font-size: 38rpx;
|
||||
font-weight: 800;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
.brand-slogan {
|
||||
margin-top: 2rpx;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 22rpx;
|
||||
letter-spacing: 5rpx;
|
||||
}
|
||||
.home-hero {
|
||||
position: relative;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 40rpx 34rpx 34rpx;
|
||||
min-height: 232rpx;
|
||||
padding: 40rpx 34rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 36rpx;
|
||||
border-radius: 30rpx;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #ff7845 0%, #f05225 100%);
|
||||
box-shadow: 0 16rpx 36rpx rgba(224, 75, 25, 0.22);
|
||||
background: linear-gradient(125deg, #ff7b3e 0%, #ff5d23 55%, #ff8e4e 100%);
|
||||
box-shadow: var(--shadow-hero);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.hero-orb {
|
||||
.hero-glow {
|
||||
position: absolute;
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.25);
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.hero-orb--one {
|
||||
.hero-glow--one {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
right: -54rpx;
|
||||
top: 30rpx;
|
||||
right: -35rpx;
|
||||
top: 20rpx;
|
||||
}
|
||||
.hero-orb--two {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
right: 92rpx;
|
||||
bottom: -32rpx;
|
||||
.hero-glow--two {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
right: 110rpx;
|
||||
bottom: -38rpx;
|
||||
}
|
||||
.hero-brand,
|
||||
.hero-title,
|
||||
.hero-copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
}
|
||||
.hero-brand {
|
||||
font-size: 27rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.hero-title {
|
||||
margin-top: 18rpx;
|
||||
font-size: 46rpx;
|
||||
font-weight: 700;
|
||||
font-size: 42rpx;
|
||||
font-weight: 800;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.hero-copy {
|
||||
margin-top: 12rpx;
|
||||
font-size: 28rpx;
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.92;
|
||||
}
|
||||
.hero-trust {
|
||||
.hero-action {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
margin-top: 28rpx;
|
||||
width: max-content;
|
||||
min-height: 58rpx;
|
||||
margin: 24rpx 0 0;
|
||||
padding: 8rpx 22rpx;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.hero-trust text {
|
||||
padding: 7rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
color: #df4c1b;
|
||||
background: #fff;
|
||||
font-size: 25rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.quick-grid {
|
||||
display: grid;
|
||||
margin-top: 24rpx;
|
||||
padding: 26rpx 8rpx 18rpx;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 28rpx 10rpx;
|
||||
border: 2rpx solid var(--color-border);
|
||||
border-radius: 28rpx;
|
||||
background: #fff;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
.quick-item {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
color: var(--color-text);
|
||||
background: transparent;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.quick-icon {
|
||||
display: flex;
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 24rpx;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 8rpx 18rpx rgba(20, 30, 40, 0.1);
|
||||
}
|
||||
.quick-icon--orange,
|
||||
.list-mark--orange {
|
||||
background: linear-gradient(145deg, #ff8b4d, #ff5e2b);
|
||||
}
|
||||
.quick-icon--green,
|
||||
.list-mark--green {
|
||||
background: linear-gradient(145deg, #62ca58, #36ad43);
|
||||
}
|
||||
.quick-icon--rose {
|
||||
background: linear-gradient(145deg, #ff8292, #ef556f);
|
||||
}
|
||||
.quick-icon--blue,
|
||||
.list-mark--blue {
|
||||
background: linear-gradient(145deg, #55aaf5, #2887e5);
|
||||
}
|
||||
.quick-icon--gold {
|
||||
background: linear-gradient(145deg, #ffbd57, #f2942e);
|
||||
}
|
||||
.notice-strip {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 70rpx;
|
||||
margin: 20rpx 0 0;
|
||||
padding: 12rpx 18rpx;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
border-radius: 18rpx;
|
||||
color: var(--color-text-secondary);
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
.primary-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18rpx;
|
||||
.notice-badge {
|
||||
color: var(--color-primary);
|
||||
font-weight: 800;
|
||||
}
|
||||
.primary-action {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 190rpx;
|
||||
.notice-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.section-heading {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
.section-link {
|
||||
min-height: 52rpx;
|
||||
margin: 0;
|
||||
padding: 26rpx 22rpx;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: #5e2715;
|
||||
border: 2rpx solid #ffd2bd;
|
||||
border-radius: 30rpx;
|
||||
padding: 4rpx 0 4rpx 20rpx;
|
||||
color: var(--color-text-secondary);
|
||||
background: transparent;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.feature-project {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid var(--color-border);
|
||||
border-radius: 26rpx;
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
.feature-project image {
|
||||
width: 100%;
|
||||
height: 260rpx;
|
||||
display: block;
|
||||
}
|
||||
.feature-project__body {
|
||||
padding: 22rpx 24rpx;
|
||||
}
|
||||
.feature-project__title,
|
||||
.feature-project__copy {
|
||||
display: block;
|
||||
}
|
||||
.feature-project__title {
|
||||
color: var(--color-text);
|
||||
font-size: 31rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
.feature-project__copy {
|
||||
margin-top: 10rpx;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 25rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.feature-project__meta {
|
||||
display: flex;
|
||||
margin-top: 18rpx;
|
||||
justify-content: space-between;
|
||||
color: var(--color-primary);
|
||||
font-size: 23rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.service-list {
|
||||
padding: 0 24rpx;
|
||||
border: 2rpx solid var(--color-border);
|
||||
border-radius: 26rpx;
|
||||
background: #fff;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
.service-list button {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 112rpx;
|
||||
margin: 0;
|
||||
padding: 18rpx 0;
|
||||
grid-template-columns: 58rpx 1fr auto;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
border-bottom: 2rpx solid var(--color-divider);
|
||||
color: var(--color-text);
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
}
|
||||
.primary-action--warm {
|
||||
background: #fff2ea;
|
||||
.service-list button:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.primary-action--green {
|
||||
color: #185f37;
|
||||
border-color: #cdebd9;
|
||||
background: #edf9f1;
|
||||
}
|
||||
.primary-action__icon {
|
||||
.list-mark {
|
||||
display: flex;
|
||||
width: 58rpx;
|
||||
height: 58rpx;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 18rpx;
|
||||
color: #fff;
|
||||
background: #f05a28;
|
||||
font-size: 35rpx;
|
||||
}
|
||||
.primary-action--green .primary-action__icon {
|
||||
background: #35a864;
|
||||
}
|
||||
.primary-action__title,
|
||||
.primary-action__copy {
|
||||
display: block;
|
||||
}
|
||||
.primary-action__title {
|
||||
margin-top: 18rpx;
|
||||
font-size: 33rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.primary-action__copy {
|
||||
margin-top: 6rpx;
|
||||
color: #755447;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
font-weight: 800;
|
||||
}
|
||||
.primary-action--green .primary-action__copy {
|
||||
color: #52705f;
|
||||
}
|
||||
.primary-action__arrow {
|
||||
position: absolute;
|
||||
right: 18rpx;
|
||||
top: 20rpx;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
.progress-banner {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 104rpx;
|
||||
margin: 20rpx 0 0;
|
||||
padding: 20rpx 26rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #235f91;
|
||||
border-radius: 24rpx;
|
||||
background: #edf6fd;
|
||||
text-align: left;
|
||||
}
|
||||
.progress-banner__label,
|
||||
.progress-banner__copy {
|
||||
.list-title,
|
||||
.list-copy {
|
||||
display: block;
|
||||
}
|
||||
.progress-banner__label {
|
||||
.list-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.progress-banner__copy {
|
||||
margin-top: 4rpx;
|
||||
color: #607b91;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.progress-banner__arrow {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.service-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
.service-item {
|
||||
display: flex;
|
||||
min-height: 142rpx;
|
||||
margin: 0;
|
||||
padding: 20rpx 8rpx;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #eee7e1;
|
||||
border-radius: 26rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.service-item__icon {
|
||||
display: flex;
|
||||
width: 58rpx;
|
||||
height: 58rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 20rpx;
|
||||
color: #fff;
|
||||
background: #f47745;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.service-item--green .service-item__icon {
|
||||
background: #46a956;
|
||||
}
|
||||
.service-item--blue .service-item__icon {
|
||||
background: #378fd6;
|
||||
}
|
||||
.service-item--purple .service-item__icon {
|
||||
background: #8564cf;
|
||||
}
|
||||
.service-item--gold .service-item__icon {
|
||||
background: #e69a31;
|
||||
}
|
||||
.service-item--rose .service-item__icon {
|
||||
background: #e96878;
|
||||
}
|
||||
.service-item__label {
|
||||
margin-top: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.participation-card {
|
||||
padding: 0 26rpx;
|
||||
border: 2rpx solid var(--color-border);
|
||||
border-radius: 30rpx;
|
||||
background: #fff;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
.participation-item {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 122rpx;
|
||||
margin: 0;
|
||||
padding: 20rpx 0;
|
||||
grid-template-columns: 64rpx 1fr auto;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
color: var(--color-text);
|
||||
border-bottom: 2rpx solid #f0ece8;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
}
|
||||
.participation-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.participation-item__icon {
|
||||
display: flex;
|
||||
width: 62rpx;
|
||||
height: 62rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 20rpx;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.orange {
|
||||
background: #f47745;
|
||||
}
|
||||
.blue {
|
||||
background: #378fd6;
|
||||
}
|
||||
.green {
|
||||
background: #38a862;
|
||||
}
|
||||
.participation-item__title,
|
||||
.participation-item__copy {
|
||||
display: block;
|
||||
}
|
||||
.participation-item__title {
|
||||
font-weight: 700;
|
||||
}
|
||||
.participation-item__copy {
|
||||
.list-copy {
|
||||
margin-top: 4rpx;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 25rpx;
|
||||
}
|
||||
.content-links {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.content-links button {
|
||||
display: flex;
|
||||
min-height: 96rpx;
|
||||
margin: 0;
|
||||
padding: 20rpx 24rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #544d47;
|
||||
border: 2rpx solid var(--color-border);
|
||||
border-radius: 24rpx;
|
||||
background: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.safety-note {
|
||||
display: flex;
|
||||
margin-top: 30rpx;
|
||||
padding: 24rpx;
|
||||
flex-direction: column;
|
||||
color: #735818;
|
||||
border-radius: 22rpx;
|
||||
background: #fff7df;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.safety-note__title {
|
||||
margin-bottom: 5rpx;
|
||||
font-weight: 700;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view wx:if="{{loading}}" class="profile-skeleton"><view class="skeleton-card"></view><view class="skeleton-card"></view></view>
|
||||
<view wx:elif="{{error}}" class="state-card"><text class="state-title">暂时无法打开个人中心</text><text class="state-text">{{error}}</text><button class="secondary-button" bindtap="load">重新加载</button></view>
|
||||
<block wx:else>
|
||||
<view class="profile-hero"><view class="avatar">乐</view><view><text class="profile-name">{{user.displayName || '乐活用户'}}</text><text class="profile-id">账号已安全登录</text></view><text class="profile-badge">{{isVolunteer ? '认证志愿者' : '公益伙伴'}}</text></view>
|
||||
<view class="profile-hero"><view class="avatar">乐</view><view><text class="profile-name">{{user.displayName || '乐活用户'}}</text><text class="profile-id">账号已安全登录</text></view><text class="profile-badge">{{isVolunteer ? '认证志愿者' : '公益伙伴'}}</text><view class="profile-trust"><view><strong>隐私保护</strong><text>最少采集</text></view><view><strong>服务可查</strong><text>进度留痕</text></view><view><strong>消息可达</strong><text>及时通知</text></view></view></view>
|
||||
<view class="summary-card"><button bindtap="applications"><text class="summary-card__icon orange">服</text><text class="summary-card__label">我的申请</text></button><button bindtap="volunteer"><text class="summary-card__icon green">愿</text><text class="summary-card__label">志愿服务</text></button><button bindtap="notifications"><text class="summary-card__icon blue">信</text><text class="summary-card__label">我的消息</text></button></view>
|
||||
<view class="section-heading"><text class="section-title">我的服务</text></view>
|
||||
<view class="menu-card"><button bindtap="applications"><text class="menu-icon orange">申</text><view><text class="menu-title">我的服务申请</text><text class="menu-copy">查看需求受理与服务进度</text></view><text class="menu-arrow">›</text></button><button bindtap="volunteer"><text class="menu-icon green">愿</text><view><text class="menu-title">志愿者中心</text><text class="menu-copy">资料认证、活动报名与服务时长</text></view><text class="menu-arrow">›</text></button><button wx:if="{{canCollaborate}}" bindtap="assignedCases"><text class="menu-icon blue">协</text><view><text class="menu-title">我的协作任务</text><text class="menu-copy">查看被指派的必要服务信息</text></view><text class="menu-arrow">›</text></button></view>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
.profile-hero {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 170rpx;
|
||||
padding: 32rpx;
|
||||
min-height: 280rpx;
|
||||
padding: 32rpx 32rpx 120rpx;
|
||||
align-items: center;
|
||||
gap: 22rpx;
|
||||
border-radius: 34rpx;
|
||||
@@ -13,6 +13,35 @@
|
||||
background: linear-gradient(135deg, #39aa61, #24934d);
|
||||
box-shadow: 0 14rpx 32rpx rgba(35, 139, 70, 0.18);
|
||||
}
|
||||
.profile-trust {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
bottom: 22rpx;
|
||||
left: 24rpx;
|
||||
display: grid;
|
||||
padding-top: 20rpx;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
border-top: 2rpx solid rgba(255, 255, 255, 0.24);
|
||||
text-align: center;
|
||||
}
|
||||
.profile-trust view {
|
||||
border-right: 2rpx solid rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
.profile-trust view:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
.profile-trust strong,
|
||||
.profile-trust text {
|
||||
display: block;
|
||||
}
|
||||
.profile-trust strong {
|
||||
font-size: 25rpx;
|
||||
}
|
||||
.profile-trust text {
|
||||
margin-top: 3rpx;
|
||||
font-size: 20rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.avatar {
|
||||
display: flex;
|
||||
width: 92rpx;
|
||||
|
||||
@@ -1 +1 @@
|
||||
<view class="page"><block wx:if="{{loading}}"><view class="skeleton-card"></view><view class="skeleton-card"></view></block><view wx:elif="{{message}}" class="state-card"><text class="state-title">项目暂时无法查看</text><text class="state-text">{{message}}</text></view><block wx:elif="{{project}}"><view class="project-cover"><text class="project-cover__heart">♡</text><text class="project-cover__status">{{project.statusLabel}}</text></view><view class="project-title-card"><text class="project-title">{{project.name}}</text><text class="project-summary">{{project.summary}}</text><view class="project-period"><text>项目周期</text><text>{{project.startDate}} 至 {{project.endDate}}</text></view></view><view class="detail-card"><text class="detail-section-title">项目介绍</text><text class="detail-copy" selectable>{{project.content}}</text></view><view class="detail-card"><text class="detail-section-title">参与说明</text><text class="detail-copy">查看项目关联活动,选择适合的时间报名参与。平台暂不提供公开募捐交易。</text></view><button class="primary-button" bindtap="activities">查看项目活动</button></block></view>
|
||||
<view class="page project-page"><block wx:if="{{loading}}"><view class="skeleton-card"></view><view class="skeleton-card"></view></block><view wx:elif="{{message}}" class="state-card"><text class="state-title">项目暂时无法查看</text><text class="state-text">{{message}}</text></view><block wx:elif="{{project}}"><image class="project-visual" src="/assets/images/brand-elder-care.jpg" mode="aspectFill"/><view class="project-title-card"><view class="project-title-row"><text class="project-title">{{project.name}}</text><text class="status-pill">{{project.statusLabel}}</text></view><text class="project-summary">{{project.summary}}</text><view class="project-facts"><view><text>服务对象</text><strong>{{project.targetPeople || '以项目说明为准'}}</strong></view><view><text>活动数量</text><strong>{{project.activityCount}}</strong></view><view><text>服务工单</text><strong>{{project.serviceCaseCount}}</strong></view></view><view class="project-period"><text>项目周期</text><text>{{project.startDate}} 至 {{project.endDate}}</text></view></view><view class="detail-card"><text class="detail-section-title">项目介绍</text><text class="detail-copy" selectable>{{project.content}}</text></view><view class="detail-card"><text class="detail-section-title">参与说明</text><text class="detail-copy">查看项目关联活动,选择适合的时间报名参与;提交后可在志愿者中心查询审核与签到状态。</text></view><button class="primary-button sticky-action" bindtap="activities">查看项目活动</button></block></view>
|
||||
|
||||
@@ -83,3 +83,43 @@
|
||||
line-height: 1.8;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.project-visual {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 360rpx;
|
||||
border-radius: 28rpx 28rpx 0 0;
|
||||
}
|
||||
.project-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.project-facts {
|
||||
display: grid;
|
||||
margin: 26rpx 0;
|
||||
padding: 22rpx 0;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
border-top: 2rpx solid var(--color-divider);
|
||||
border-bottom: 2rpx solid var(--color-divider);
|
||||
text-align: center;
|
||||
}
|
||||
.project-facts view {
|
||||
border-right: 2rpx solid var(--color-divider);
|
||||
}
|
||||
.project-facts view:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
.project-facts text,
|
||||
.project-facts strong {
|
||||
display: block;
|
||||
}
|
||||
.project-facts text {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.project-facts strong {
|
||||
margin-top: 6rpx;
|
||||
color: var(--color-primary);
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
@@ -157,3 +157,13 @@ P2:5. 公益项目 6. 公益活动 7. 我要做公益
|
||||
- 全局使用 16/24/32rpx 级圆角、低强度暖灰阴影、至少约 96rpx 的主按钮/主要点击高度和系统字体;不使用闪烁、强动画、玻璃拟态或科技大屏视觉。
|
||||
- 项目、活动和志愿者页面只展示 API 返回的真实事实;封面缺失时使用轻量公益图形占位,不生成虚构人物、捐赠额、服务人数或活动成果。
|
||||
- 参考图中的公开捐赠交易、证书、培训和团队入口不属于当前 V1 已实现能力,不在公众端显示;项目详情明确不提供公开募捐交易。
|
||||
|
||||
---
|
||||
|
||||
## 12. 参考界面全端还原范围(2026-08-13)
|
||||
|
||||
- 小程序端采用参考图的品牌头、暖橙主视觉、四列服务入口、公告、项目大卡、绿色个人中心、图文服务选择、项目事实卡和统一底部导航。
|
||||
- 管理端采用深色固定侧栏、浅灰工作区、真实 KPI、月度趋势、服务类型分布、快捷入口、标准筛选表格和工单横向步骤条。
|
||||
- 生成的人物图片均为虚构品牌场景素材,仅作服务类别与项目视觉,不作为真实服务对象、服务现场或项目成果证据;素材已压缩进入小程序本地包。
|
||||
- 捐赠支付、自动捐赠、捐赠记录/金额、物资订单、志愿者培训/证书/团队不展示入口;代码只保留禁用的集中能力登记和组件/导航扩展边界。
|
||||
- 图片中的金额、满意度、捐赠人数等示例值不得复制;所有运行时数字必须来自已批准接口,缺少事实时使用明确空状态。
|
||||
|
||||
@@ -4,6 +4,20 @@
|
||||
|
||||
## 当前阶段
|
||||
|
||||
2026-08-13
|
||||
|
||||
任务:参考图小程序端与管理端视觉全量还原(延期能力仅预留)。
|
||||
|
||||
完成:建立 primitive→semantic→component 三层设计令牌;按参考图重建小程序品牌首页、八宫格服务入口、公告、项目图文卡、正式底部导航、图文服务申请、项目详情事实区和绿色个人中心;保留既有真实申请、项目、活动、志愿报名、协作任务、消息和内容业务流程。管理端新增深色图标侧栏、工作概览、真实 KPI、月度申请/完成趋势、服务类型分布、快捷入口和工单横向状态步骤条,并沿用既有脱敏表格、筛选、详情、权限和审计逻辑。新增四张虚构公益品牌场景素材并压缩用于本地包,不作为真实成果证据。
|
||||
|
||||
范围:按用户确认,爱心捐赠及支付、每月自动捐赠、捐赠记录/金额统计、物资订单、志愿者培训/证书/团队不进入当前版本,不显示入口、不建立 API/表;新增集中禁用能力清单与稳定导航/组件边界供后期开发。新增 D-017,未改变技术栈、数据库、认证、状态机或 V1 业务范围。
|
||||
|
||||
验证:格式检查、Lint、27 个单元测试、34 个真实 MySQL 集成测试和全仓生产构建通过;8 个 migration 在隔离 `lehuo_test` 库无待执行项。首次全仓类型检查发现管理端步骤条使用了申请状态名称而非工单状态名称,已改为 `ASSIGNED/IN_SERVICE/SERVICE_COMPLETED` 并完成最终重跑。
|
||||
|
||||
遗留问题:真实微信 AppID/AppSecret、合法 HTTPS 域名和微信环境联调继续按用户安排后期提供;延期能力仍需未来独立评审。视觉真机验收需微信开发者工具环境。
|
||||
|
||||
---
|
||||
|
||||
`PHASE_7_IMPLEMENTATION_COMPLETE - 外部发布验收待执行`
|
||||
|
||||
## 当前版本目标
|
||||
|
||||
@@ -195,6 +195,20 @@ V1 统计直接从业务事实表计算,不建立可人工覆盖的统计表
|
||||
|
||||
---
|
||||
|
||||
## D-017 参考界面还原与延期能力扩展边界
|
||||
|
||||
状态:ACCEPTED
|
||||
|
||||
决定:
|
||||
小程序端和管理端以 2026-08-10 已确认参考图作为当前视觉基准,在不改变 V1 业务状态机、权限、数据口径和隐私边界的前提下完整还原其信息层级、色彩、卡片、导航、表格、图表和详情结构。界面统计只使用现有事实接口,不展示虚构人数、金额、满意度或成果。
|
||||
|
||||
爱心捐赠及微信/支付宝/银行卡支付、每月自动捐赠、捐赠记录与金额统计、物资订单、志愿者培训、证书和团队不进入当前版本,不注册页面或可操作入口,不新增数据库/API;通过集中能力清单、稳定模块边界和可扩展导航结构预留后续接入点,后续启用仍须独立产品、权限、审计、支付合规和迁移评审。
|
||||
|
||||
原因:
|
||||
满足已批准视觉目标,同时避免用不可用入口或模拟数据冒充已交付功能,并防止支付、订单和志愿者扩展模块未经合规及架构评审进入 V1。
|
||||
|
||||
---
|
||||
|
||||
## 待确认建议
|
||||
|
||||
Codex 若有会改变已批准架构的建议,只能写在这里,不得直接实施:
|
||||
|
||||
Reference in New Issue
Block a user