更新模板

This commit is contained in:
2026-01-21 01:37:34 +08:00
parent b7be8c51bf
commit c5c73828bd
83 changed files with 8687 additions and 1235 deletions

230
pages/adoption/detail.uvue Normal file
View File

@@ -0,0 +1,230 @@
<template>
<cl-page>
<cl-topbar title="项目详情" />
<scroll-view scroll-y class="content" v-if="project != null">
<swiper class="banner" autoplay circular indicator-dots>
<swiper-item v-for="(img, index) in projectImages" :key="index">
<image class="banner-image" :src="img" mode="aspectFill" />
</swiper-item>
</swiper>
<view class="project-info">
<text class="project-name">{{ project.name }}</text>
<view class="project-meta">
<view class="meta-item">
<text class="meta-label">周期</text>
<text class="meta-value">{{ project.duration }}天</text>
</view>
<view class="meta-item">
<text class="meta-label">权益</text>
<text class="meta-value">{{ benefits.length }}项</text>
</view>
<view class="meta-item">
<text class="meta-label">已认养</text>
<text class="meta-value">{{ project.adoptedCount }}人</text>
</view>
</view>
</view>
<view class="benefits-section">
<text class="section-title">认养权益</text>
<view class="benefit-list">
<view v-for="(benefit, index) in benefits" :key="index" class="benefit-item">
<cl-icon name="check-circle" :size="36" color="#52c41a" />
<text class="benefit-text">{{ benefit }}</text>
</view>
</view>
</view>
<view class="desc-section">
<text class="section-title">项目介绍</text>
<text class="desc">{{ project.description }}</text>
</view>
<view style="height: 140rpx;"></view>
</scroll-view>
<view class="bottom-bar" v-if="project != null">
<view class="price-info">
<text class="price">¥{{ project.price }}</text>
<text class="original" v-if="project.originalPrice != null">¥{{ project.originalPrice }}</text>
</view>
<cl-button type="primary" round @click="onAdopt">立即认养</cl-button>
</view>
</cl-page>
</template>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue';
import { router, useCool, parseObject } from '@/cool';
const { service } = useCool();
const project = ref<any>(null);
const projectImages = computed(() => {
if (project.value?.images == null) return ['/static/images/farm.png'];
try {
return parseObject<string[]>(project.value.images) ?? ['/static/images/farm.png'];
} catch {
return ['/static/images/farm.png'];
}
});
const benefits = computed(() => {
if (project.value?.benefits == null) return ['新鲜农产品直送', '专属认养证书', '实时生长查看'];
try {
return parseObject<string[]>(project.value.benefits) ?? ['新鲜农产品直送', '专属认养证书', '实时生长查看'];
} catch {
return ['新鲜农产品直送', '专属认养证书', '实时生长查看'];
}
});
async function loadProject() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const projectId = Number(currentPage.options?.id || 0);
if (projectId != 0) {
try {
const res = await service.nongchuang.adoption.detail({ id: projectId });
project.value = res;
} catch (e) {
project.value = {
id: projectId,
name: '有机苹果园认养',
description: '位于山东烟台的优质苹果园,采用有机种植方式',
duration: 180,
price: 299,
adoptedCount: 56
};
}
}
}
function onAdopt() {
uni.showToast({ title: '认养功能开发中', icon: 'none' });
}
onMounted(() => {
loadProject();
});
</script>
<style lang="scss" scoped>
.content {
flex: 1;
}
.banner {
height: 400rpx;
.banner-image {
width: 100%;
height: 100%;
}
}
.project-info {
padding: 30rpx;
background: #fff;
.project-name {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.project-meta {
display: flex;
flex-direction: row;
justify-content: space-around;
margin-top: 30rpx;
padding: 24rpx 0;
background: #f9f9f9;
border-radius: 12rpx;
.meta-item {
text-align: center;
.meta-label {
font-size: 24rpx;
color: #999;
}
.meta-value {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-top: 8rpx;
}
}
}
}
.benefits-section, .desc-section {
margin-top: 20rpx;
padding: 30rpx;
background: #fff;
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
}
.benefit-list {
.benefit-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 0;
font-size: 28rpx;
color: #666;
.benefit-text {
margin-left: 16rpx;
}
}
}
.desc {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
.price-info {
display: flex;
flex-direction: row;
align-items: center;
.price {
font-size: 40rpx;
font-weight: bold;
color: #ff4d4f;
}
.original {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
}
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<cl-page>
<cl-topbar title="兑换礼品卡" />
<view class="gift-card-page">
<view class="card-input-section">
<text class="section-title">礼品卡码</text>
<input class="card-input" v-model="cardCode" type="text" placeholder="请输入礼品卡码" maxlength="20" />
<cl-button type="primary" size="large" round :loading="loading" @click="redeemCard">
立即兑换
</cl-button>
</view>
<view class="tips-section">
<text class="section-title">使用说明</text>
<view class="tip-list">
<view class="tip-item">
<cl-icon name="check-fill" :size="32" color="#52c41a" />
<text class="tip-text">每张礼品卡只能兑换一次</text>
</view>
<view class="tip-item">
<cl-icon name="check-fill" :size="32" color="#52c41a" />
<text class="tip-text">兑换成功后自动激活认养权益</text>
</view>
<view class="tip-item">
<cl-icon name="check-fill" :size="32" color="#52c41a" />
<text class="tip-text">如有问题请联系客服</text>
</view>
</view>
</view>
</view>
</cl-page>
</template>
<script setup lang="uts">
import { ref } from 'vue';
import { router, useCool } from '@/cool';
const { service } = useCool();
const cardCode = ref('');
const loading = ref(false);
async function redeemCard() {
if (cardCode.value.length == 0) {
uni.showToast({ title: '请输入礼品卡码', icon: 'none' });
return;
}
loading.value = true;
try {
const res = await service.nongchuang.adoption.redeemGiftCard({ cardCode: cardCode.value });
uni.showToast({ title: '兑换成功', icon: 'success' });
setTimeout(() => {
router.push({ path: '/pages/adoption/my' });
}, 1500);
} catch (e: any) {
uni.showToast({ title: e.message || '兑换失败', icon: 'none' });
}
loading.value = false;
}
</script>
<style lang="scss" scoped>
.gift-card-page {
padding: 30rpx;
}
.card-input-section {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.card-input {
width: 100%;
padding: 24rpx;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 32rpx;
margin-bottom: 30rpx;
}
}
.tips-section {
margin-top: 30rpx;
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.tip-list {
.tip-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 0;
font-size: 26rpx;
color: #666;
.tip-text {
margin-left: 16rpx;
}
}
}
}
</style>

132
pages/adoption/list.uvue Normal file
View File

@@ -0,0 +1,132 @@
<template>
<cl-page>
<cl-topbar title="认养项目" />
<scroll-view scroll-y class="content" @scrolltolower="loadMore">
<view class="project-list">
<view
v-for="project in projectList"
:key="project.id"
class="project-card"
@click="goDetail(project.id)"
>
<image class="project-image" :src="project.getArray<string>('images') != null && project.getArray<string>('images')!.length > 0 ? project.getArray<string>('images')![0] : '/static/images/farm.png'" mode="aspectFill" />
<view class="project-info">
<text class="project-name">{{ project.name }}</text>
<text class="project-desc">{{ project.description }}</text>
<view class="project-meta">
<text class="duration">周期: {{ project.duration }}天</text>
<text class="count">已认养: {{ project.adoptedCount }}</text>
</view>
<view class="project-footer">
<text class="price">¥{{ project.price }}</text>
<cl-button type="primary" size="small" round>立即认养</cl-button>
</view>
</view>
</view>
</view>
<cl-empty v-if="projectList.length === 0 && loading == false" text="暂无认养项目" />
</scroll-view>
</cl-page>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue';
import { router, useCool } from '@/cool';
const { service } = useCool();
const projectList = ref<any[]>([]);
const loading = ref(false);
async function loadProjects() {
loading.value = true;
try {
const res = await service.nongchuang.adoption.list();
if (res != null) {
const data = res as UTSJSONObject;
const list = data.getArray<any>("list");
projectList.value = list != null ? list : (res as any[]);
}
} catch (e) {
projectList.value = [
{ id: 1, name: '有机苹果园认养', description: '山东烟台优质苹果', duration: 180, adoptedCount: 56, price: 299 },
{ id: 2, name: '生态鸡认养', description: '散养土鸡,绿色健康', duration: 120, adoptedCount: 32, price: 199 }
];
}
loading.value = false;
}
function loadMore() {
// 加载更多
}
function goDetail(id: number) {
router.push({ path: '/pages/adoption/detail', query: { id } });
}
onMounted(() => {
loadProjects();
});
</script>
<style lang="scss" scoped>
.content {
flex: 1;
padding: 20rpx;
}
.project-card {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 20rpx;
.project-image {
width: 100%;
height: 300rpx;
}
.project-info {
padding: 24rpx;
.project-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.project-desc {
font-size: 26rpx;
color: #999;
margin-top: 10rpx;
}
.project-meta {
display: flex;
flex-direction: row;
margin-top: 16rpx;
font-size: 24rpx;
color: #666;
.duration {
margin-right: 30rpx;
}
}
.project-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
.price {
font-size: 36rpx;
font-weight: bold;
color: #ff4d4f;
}
}
}
}
</style>

135
pages/adoption/my.uvue Normal file
View File

@@ -0,0 +1,135 @@
<template>
<cl-page>
<cl-topbar title="我的认养" />
<scroll-view scroll-y class="content">
<view class="adoption-list">
<view v-for="item in adoptionList" :key="item.id" class="adoption-card" @click="goDetail(item.id)">
<image class="adoption-image" :src="item.projectImage != null ? item.projectImage : '/static/images/farm.png'" mode="aspectFill" />
<view class="adoption-info">
<text class="adoption-name">{{ item.projectName }}</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: item.progress + '%' }"></view>
</view>
<view class="adoption-meta">
<text class="date">{{ item.startTime }} - {{ item.endTime }}</text>
<text class="status" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</text>
</view>
</view>
</view>
</view>
<cl-empty v-if="adoptionList.length === 0" text="暂无认养记录" />
</scroll-view>
</cl-page>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue';
import { router, useCool } from '@/cool';
const { service } = useCool();
const statusBarHeight = ref(0);
const adoptionList = ref<any[]>([]);
function getStatusClass(status: number) {
return { active: status === 1, completed: status === 2, cancelled: status === 3 };
}
function getStatusText(status: number): string {
const texts: Record<number, string> = { 1: '进行中', 2: '已完成', 3: '已取消' };
const text = texts[status];
return text != null ? text : '未知';
}
async function loadAdoptions() {
try {
const res = await service.nongchuang.adoption.myProjects();
if (res != null) {
const data = res as UTSJSONObject;
const list = data.getArray<any>("list");
adoptionList.value = list != null ? list : (res as any[]);
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' });
adoptionList.value = [];
}
}
function goDetail(id: number) {
router.push({ path: '/pages/adoption/detail', query: { id } });
}
onMounted(() => {
const info = uni.getWindowInfo();
statusBarHeight.value = info.statusBarHeight + 10;
loadAdoptions();
});
</script>
<style lang="scss" scoped>
.content {
flex: 1;
padding: 20rpx;
}
.adoption-card {
display: flex;
flex-direction: row;
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
.adoption-image {
width: 160rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 20rpx;
}
.adoption-info {
flex: 1;
.adoption-name {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.progress-bar {
height: 8rpx;
background: #f0f0f0;
border-radius: 4rpx;
margin: 16rpx 0;
overflow: hidden;
.progress-fill {
height: 100%;
background: #52c41a;
border-radius: 4rpx;
}
}
.adoption-meta {
display: flex;
flex-direction: row;
justify-content: space-between;
.date {
font-size: 24rpx;
color: #999;
}
.status {
font-size: 24rpx;
&.active { color: #52c41a; }
&.completed { color: #8c8c8c; }
&.cancelled { color: #ff4d4f; }
}
}
}
}
</style>