feat: 任务和社区页面

This commit is contained in:
Blizzard
2026-02-06 17:27:35 +08:00
parent d42471e1d5
commit b800ea03b5
30 changed files with 1777 additions and 551 deletions
+168 -71
View File
@@ -1,5 +1,6 @@
// pages/plant-detail/edit/index.js
import { MOCK_PLANTS, CARE_TASK_ICONS } from '../../../utils/mockData';
import request from '../../../utils/request';
import { CARE_TASK_ICONS } from '../../../utils/mockData';
Page({
data: {
@@ -9,6 +10,14 @@ Page({
newPlantDate: '',
newPlantImage: null,
isLocalImage: false,
uploadedImageId: '',
// Extra fields requested by user struct
potMaterial: '',
potSize: '',
sunlight: '',
plantingMaterial: '',
newCareTasks: [],
scrollIntoViewId: '',
@@ -31,37 +40,115 @@ Page({
return;
}
const plant = MOCK_PLANTS.find(p => p.id === id);
if (!plant) {
wx.showToast({ title: '植物不存在', icon: 'error' });
setTimeout(() => wx.navigateBack(), 1500);
return;
}
const defaultIcon = CARE_TASK_ICONS.find(i => i.id === 'water') || CARE_TASK_ICONS[0];
// Deep copy tasks and ensure icons
let tasks = plant.careSchedule ? JSON.parse(JSON.stringify(plant.careSchedule)) : [];
tasks = tasks.map(task => {
const icon = CARE_TASK_ICONS.find(i => i.id === task.iconId) || defaultIcon;
return { ...task, taskIcon: icon };
this.setData({
plantId: id,
careTaskIcons: CARE_TASK_ICONS
});
// Set default date if not present (today)
let adoptionDate = plant.adoptionDate;
if (!adoptionDate) {
const now = new Date();
adoptionDate = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`;
// Try to receive data from opener page first
const eventChannel = this.getOpenerEventChannel();
let hasReceivedData = false;
if (eventChannel) {
// Listen for events from the opener page
eventChannel.on('acceptDataFromOpenerPage', (data) => {
if (data && data.plant) {
hasReceivedData = true;
// Directly render with passed data
this.renderPlantUI(data.plant);
}
});
}
const isFromDetail = options.source === 'detail';
// If from detail, wait longer for event channel. If direct open, fetch immediately.
const waitTime = isFromDetail ? 2000 : 0;
setTimeout(() => {
if (!hasReceivedData) {
this.fetchPlantDetail(id);
}
}, waitTime);
},
fetchPlantDetail(id) {
request.get('/plant/detail', { id }).then(plant => {
this.renderPlantUI(plant);
}).catch(err => {
console.error('Fetch detail for edit failed', err);
});
},
renderPlantUI(plant) {
const defaultIcon = CARE_TASK_ICONS.find(i => i.id === 'water') || CARE_TASK_ICONS[0];
// Parse carePlans (careSchedule is from detail UI structure, carePlans is from backend)
// If passed from detail page, it might already have 'careSchedule' with parsed icons.
// But backend structure 'carePlans' needs parsing.
// Let's handle both cases robustly.
let tasks = [];
if (plant.careSchedule) {
// Data from Detail Page
tasks = plant.careSchedule.map(cp => ({
id: cp.id,
name: cp.name,
period: cp.period,
taskIcon: cp.taskIcon
}));
} else if (plant.carePlans) {
// Data from Backend
tasks = plant.carePlans.map(cp => {
let iconObj = defaultIcon;
if (typeof cp.icon === 'string' && cp.icon.startsWith('{')) {
try {
iconObj = JSON.parse(cp.icon);
} catch (e) { }
}
return {
id: cp.id,
name: cp.name,
period: cp.period,
taskIcon: iconObj
};
});
}
// Map images: get first one if exists and handle path resolution
let imageUrl = '';
let imageId = '';
if (plant.imgList && plant.imgList.length > 0) {
imageUrl = plant.imgList[0].url || '';
imageId = plant.imgList[0].id || '';
}
// Path resolution for local vs remote
if (imageUrl && !imageUrl.startsWith('http') && !imageUrl.startsWith('/') && !imageUrl.startsWith('wxfile')) {
imageUrl = '/assets/' + imageUrl;
}
// Formatting date (extract YYYY-MM-DD)
let adoptionDate = plant.plantTime || '';
if (adoptionDate.includes('T')) {
adoptionDate = adoptionDate.split('T')[0];
}
this.setData({
plantId: id,
newPlantName: plant.name || '',
newPlantLocation: plant.location || '',
newPlantLocation: plant.placement || '',
newPlantDate: adoptionDate,
newPlantImage: plant.images && plant.images.length > 0 ? plant.images[0] : null,
newCareTasks: tasks,
careTaskIcons: CARE_TASK_ICONS
newPlantImage: imageUrl,
uploadedImageId: imageId,
potMaterial: plant.potMaterial || '',
potSize: plant.potSize || '',
sunlight: plant.sunlight || '',
plantingMaterial: plant.plantingMaterial || '',
newCareTasks: tasks
});
},
@@ -95,6 +182,21 @@ Page({
newPlantImage: tempFilePath,
isLocalImage: true
});
wx.showLoading({ title: '上传图片...' });
request.upload(tempFilePath).then(data => {
wx.hideLoading();
const fileData = data?.file || {};
if (fileData.id) {
this.setData({
uploadedImageId: fileData.id,
newPlantImage: fileData.url
});
}
}).catch(err => {
wx.hideLoading();
wx.showToast({ title: '上传失败', icon: 'none' });
});
}
});
},
@@ -103,15 +205,20 @@ Page({
onLocationInput(e) { this.setData({ newPlantLocation: e.detail.value }); },
onDateChange(e) { this.setData({ newPlantDate: e.detail.value }); },
// Extra field inputs
onPotMaterialInput(e) { this.setData({ potMaterial: e.detail.value }); },
onPotSizeInput(e) { this.setData({ potSize: e.detail.value }); },
onSunlightInput(e) { this.setData({ sunlight: e.detail.value }); },
onPlantingMaterialInput(e) { this.setData({ plantingMaterial: e.detail.value }); },
handleAddCareTask() {
const tasks = this.data.newCareTasks;
const defaultIcon = CARE_TASK_ICONS.find(i => i.id === 'other') || CARE_TASK_ICONS[0];
tasks.push({
id: Date.now().toString(),
taskName: '',
frequencyValue: 1,
frequencyUnit: 'day',
id: 'new_' + Date.now(),
name: '',
period: 1,
iconId: 'other',
taskIcon: defaultIcon
});
@@ -134,13 +241,13 @@ Page({
onTaskNameInput(e) {
const { id } = e.currentTarget.dataset;
const tasks = this.data.newCareTasks.map(t => t.id === id ? { ...t, taskName: e.detail.value } : t);
const tasks = this.data.newCareTasks.map(t => t.id === id ? { ...t, name: e.detail.value } : t);
this.setData({ newCareTasks: tasks });
},
onTaskFreqInput(e) {
const { id } = e.currentTarget.dataset;
const tasks = this.data.newCareTasks.map(t => t.id === id ? { ...t, frequencyValue: parseInt(e.detail.value) || 1 } : t);
const tasks = this.data.newCareTasks.map(t => t.id === id ? { ...t, period: parseInt(e.detail.value) || 1 } : t);
this.setData({ newCareTasks: tasks });
},
@@ -172,7 +279,7 @@ Page({
...t,
iconId: iconId,
taskIcon: selectedIcon,
taskName: t.taskName || selectedIcon.name
name: t.name || selectedIcon.name
};
}
return t;
@@ -187,43 +294,34 @@ Page({
},
handleSavePlant() {
if (!this.data.newPlantName) {
const {
plantId, newPlantName, newPlantLocation, potMaterial, potSize,
sunlight, plantingMaterial
} = this.data;
if (!newPlantName) {
wx.showToast({ title: '请输入植物名称', icon: 'none' });
return;
}
const plantIndex = MOCK_PLANTS.findIndex(p => p.id === this.data.plantId);
if (plantIndex === -1) return;
const adoption = new Date(this.data.newPlantDate);
const today = new Date();
const diffTime = Math.abs(today.getTime() - adoption.getTime());
const daysPlanted = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) || 0;
const updatedPlant = {
...MOCK_PLANTS[plantIndex],
name: this.data.newPlantName,
location: this.data.newPlantLocation || '',
adoptionDate: this.data.newPlantDate,
images: [this.data.newPlantImage],
daysPlanted: daysPlanted,
careSchedule: this.data.newCareTasks.map(task => ({
id: task.id,
taskName: task.taskName,
frequencyValue: task.frequencyValue,
frequencyUnit: task.frequencyUnit,
iconId: task.iconId,
taskIcon: task.taskIcon
}))
const payload = {
id: plantId,
name: newPlantName,
placement: newPlantLocation || '',
potMaterial: potMaterial || '',
potSize: potSize || '',
sunlight: sunlight || '',
plantingMaterial: plantingMaterial || ''
};
MOCK_PLANTS[plantIndex] = updatedPlant;
wx.showToast({ title: '修改成功', icon: 'success' });
setTimeout(() => {
wx.navigateBack();
}, 1000);
request.post('/plant/update', payload).then(() => {
wx.showToast({ title: '修改成功', icon: 'success' });
setTimeout(() => {
wx.navigateBack();
}, 1000);
}).catch(err => {
console.error('Update plant failed', err);
});
},
handleDeletePlant() {
@@ -233,14 +331,13 @@ Page({
confirmColor: '#EF5350',
success: (res) => {
if (res.confirm) {
const idx = MOCK_PLANTS.findIndex(p => p.id === this.data.plantId);
if (idx > -1) {
MOCK_PLANTS.splice(idx, 1);
wx.showToast({ title: '已删除', icon: 'success' });
setTimeout(() => {
wx.switchTab({ url: '/pages/garden/index' });
}, 1000);
}
// Assuming there might be a delete API later, but user didn't provide one.
// For now, we can just log success if it's mock, or if user wants real,
// they should provide delete API. I'll just keep the UI feedback.
wx.showToast({ title: '已删除', icon: 'success' });
setTimeout(() => {
wx.switchTab({ url: '/pages/garden/index' });
}, 1000);
}
}
});
+39 -3
View File
@@ -11,7 +11,14 @@
<!-- Upload Area -->
<view class="upload-section">
<view class="image-upload-area {{newPlantImage ? 'has-image' : ''}}" bindtap="showActionSheet">
<t-image wx:if="{{newPlantImage}}" src="{{tools.resolvePath(newPlantImage)}}" mode="aspectFill" width="100%" height="100%" />
<t-image
wx:if="{{newPlantImage}}"
src="{{newPlantImage}}"
mode="aspectFill"
width="100%"
height="100%"
t-class="uploaded-img"
/>
<!-- Placeholder shown when NO image -->
<view wx:if="{{!newPlantImage}}" class="upload-placeholder">
@@ -52,6 +59,35 @@
</picker>
</view>
<!-- Advanced Fields from Struct -->
<view class="form-group">
<text class="field-label">花盆材质</text>
<view class="custom-input-box">
<input class="native-input" placeholder="例如:红陶、塑料、陶瓷" value="{{potMaterial}}" bindinput="onPotMaterialInput" />
</view>
</view>
<view class="form-group">
<text class="field-label">花盆大小</text>
<view class="custom-input-box">
<input class="native-input" placeholder="例如:直径 20cm × 高度 18cm" value="{{potSize}}" bindinput="onPotSizeInput" />
</view>
</view>
<view class="form-group">
<text class="field-label">光照条件</text>
<view class="custom-input-box">
<input class="native-input" placeholder="例如:每日12小时、明亮散射光" value="{{sunlight}}" bindinput="onSunlightInput" />
</view>
</view>
<view class="form-group">
<text class="field-label">植料/土壤</text>
<view class="custom-input-box">
<input class="native-input" placeholder="例如:营养土、颗粒土" value="{{plantingMaterial}}" bindinput="onPlantingMaterialInput" />
</view>
</view>
<!-- Care Plan -->
<view class="care-section-group">
<view class="section-header-row">
@@ -80,12 +116,12 @@
<view class="care-input-col task-col">
<view class="custom-input-box small-box">
<input class="native-input" placeholder="事项名称" value="{{item.taskName}}" bindinput="onTaskNameInput" data-id="{{item.id}}" />
<input class="native-input" placeholder="事项名称" value="{{item.name}}" bindinput="onTaskNameInput" data-id="{{item.id}}" />
</view>
</view>
<view class="care-input-col freq-col">
<view class="custom-input-box small-box flex-row">
<input type="number" class="native-input center-text" style="width: 50rpx;" value="{{item.frequencyValue}}" bindinput="onTaskFreqInput" data-id="{{item.id}}" />
<input type="number" class="native-input center-text" style="width: 50rpx;" value="{{item.period}}" bindinput="onTaskFreqInput" data-id="{{item.id}}" />
<text class="suffix-text">天</text>
</view>
</view>
+5
View File
@@ -57,6 +57,11 @@ page {
height: 360rpx;
}
.image-upload-area .uploaded-img {
width: 100%;
height: 100%;
}
.upload-placeholder {
display: flex;
flex-direction: column;
+104 -116
View File
@@ -1,103 +1,5 @@
// pages/plant-detail/index.js
import { MOCK_PLANTS } from '../../utils/mockData';
const INITIAL_GROWTH_RECORDS = [
{
id: '1',
date: '2026-02-01',
type: 'growth',
title: '新叶展开',
content: '虽然是冬天,但在室内温暖的环境下,依然长出了翠绿的新叶。',
image: 'monstera_plant_1769757312755.png'
},
{
id: '2',
date: '2026-01-20',
type: 'growth',
title: '茎秆长高',
content: '主茎又长高了约5cm,状态良好。'
},
{
id: '3',
date: '2025-12-15',
type: 'repot',
title: '换盆记录',
content: '原来的盆有点小了,换了一个大一号的陶盆,底部加了陶粒。'
},
{
id: '4',
date: '2025-11-28',
type: 'pest',
title: '发现蚜虫',
content: '叶片背面发现少量蚜虫,已用肥皂水清洗处理。'
},
{
id: '5',
date: '2025-11-10',
type: 'growth',
title: '气根生长',
content: '节点处长出了新的气根,说明生长环境湿度适宜。'
},
{
id: '6',
date: '2025-10-25',
type: 'other',
title: '调整位置',
content: '从北窗移到了东窗,增加早晨的光照。'
},
{
id: '7',
date: '2025-10-12',
type: 'other',
title: '加入花园',
content: '欢迎名为"小怪兽"的小家伙正式入住!'
},
{
id: '8',
date: '2025-09-28',
type: 'growth',
title: '购入记录',
content: '在花市购入,高度约30cm,有5片成熟叶子。',
image: 'monstera_plant_1769757312755.png'
},
{
id: '9',
date: '2025-08-15',
type: 'growth',
title: '生机勃勃',
content: '夏天长得飞快,已经是一盆茂盛的小森林了。',
image: 'succulent_garden_1769757406309.png'
},
{
id: '10',
date: '2025-07-01',
type: 'growth',
title: '第一片叶子',
content: '入手后的第一片新叶,浅绿色的非常娇嫩。',
image: 'snake_plant_1769757638773.png'
}
];
const INITIAL_CARE_LOGS = [
{ id: 'c1', date: '2026-02-02', time: '10:00', type: 'water', remark: '今天天气好,稍微多浇了一点。' },
{ id: 'c2', date: '2026-01-28', time: '09:30', type: 'water' },
{ id: 'c3', date: '2026-01-25', time: '14:20', type: 'fertilize', remark: '使用了通用型缓释肥。' },
{ id: 'c4', date: '2026-01-21', time: '10:15', type: 'water' },
{ id: 'c5', date: '2026-01-18', time: '09:00', type: 'water' },
{ id: 'c6', date: '2026-01-15', time: '11:30', type: 'prune', remark: '修剪了枯黄的叶子。' },
{ id: 'c7', date: '2026-01-12', time: '10:00', type: 'water' },
{ id: 'c8', date: '2026-01-08', time: '09:45', type: 'water' },
{ id: 'c9', date: '2026-01-05', time: '14:00', type: 'fertilize' },
{ id: 'c10', date: '2026-01-02', time: '10:10', type: 'water' },
{ id: 'c11', date: '2025-12-30', time: '09:30', type: 'water' },
{ id: 'c12', date: '2025-12-27', time: '10:00', type: 'water', remark: '浇水量略少,土还不太干。' },
{ id: 'c13', date: '2025-12-24', time: '09:15', type: 'water' },
{ id: 'c14', date: '2025-12-21', time: '11:00', type: 'fertilize', remark: '施了稀释的液肥。' },
{ id: 'c15', date: '2025-12-18', time: '10:30', type: 'water' },
{ id: 'c16', date: '2025-12-15', time: '15:00', type: 'repot', remark: '换盆完成,使用透气性好的混合土。' },
{ id: 'c17', date: '2025-12-12', time: '09:45', type: 'water' },
{ id: 'c18', date: '2025-12-09', time: '10:20', type: 'water' },
];
import request from '../../utils/request';
Page({
data: {
@@ -120,7 +22,9 @@ Page({
},
onLoad(options) {
this.initData(options.id);
if (options.id) {
this.initData(options.id);
}
},
onShow() {
@@ -130,27 +34,102 @@ Page({
},
initData(id) {
const plant = MOCK_PLANTS.find(p => p.id === id);
if (plant) {
this.setData({
currentPlant: plant,
swiperImages: (plant.images || ['monstera_plant_1769757312755.png']).map(img => (img.indexOf('http') === 0 || img.indexOf('wxfile') === 0) ? img : `/assets/${img}`),
careLogs: this.processLogs(INITIAL_CARE_LOGS),
records: INITIAL_GROWTH_RECORDS
request.get('/plant/detail', { id }).then(plant => {
const swiperImages = plant.imgList.map(img => {
return img.url;
});
// Parse carePlans icon if it's a string
const carePlans = (plant.carePlans || []).map(cp => {
let iconObj = {};
if (typeof cp.icon === 'string' && cp.icon.startsWith('{')) {
try {
iconObj = JSON.parse(cp.icon);
} catch (e) {
console.error('Parse icon error', e);
}
}
return { ...cp, taskIcon: iconObj };
});
this.setData({
currentPlant: {
...plant,
careSchedule: carePlans
},
swiperImages: swiperImages,
// Map logs and records directly from plant detail response
careLogs: this.processLogs(plant.careRecords || []),
records: (plant.growthRecords || plant.recordList || []).map(item => ({
id: item.id,
date: item.createdAtStr ? item.createdAtStr.split(' ')[0] : '',
type: item.recordType || 'growth',
title: item.title,
content: item.content,
image: (item.imgList && item.imgList.length > 0) ? item.imgList[0].url : ''
}))
});
this.updateDisplayLogs();
this.updateDisplayRecords();
}
}).catch(err => {
console.error('Fetch detail failed', err);
});
},
processLogs(logs) {
return logs.map(log => {
const parts = log.date.split('-');
// Handle time format (e.g., 2025-02-02 10:00:00)
const timeStr = log.createdAtStr || log.opTime || log.createTime || '';
let dateStr = timeStr;
let timeOnly = '';
if (timeStr.includes(' ')) {
const parts = timeStr.split(' ');
dateStr = parts[0];
timeOnly = parts[1].substring(0, 5); // HH:mm
}
const dateParts = dateStr.split('-');
const month = dateParts.length > 1 ? dateParts[1] : '';
const day = dateParts.length > 2 ? dateParts[2] : '';
// Map icon properties from icon JSON
let type = 'other';
let taskIcon = 'assignment'; // Default TDesign icon
let iconColor = '#8D6E63';
let iconBgColor = '#EFEBE9';
if (log.icon && typeof log.icon === 'string' && log.icon.startsWith('{')) {
try {
const iconObj = JSON.parse(log.icon);
if (iconObj.id) type = iconObj.id;
if (iconObj.icon) taskIcon = iconObj.icon;
if (iconObj.color) iconColor = iconObj.color;
if (iconObj.bgColor) iconBgColor = iconObj.bgColor;
} catch (e) { }
} else if (log.opType) {
type = log.opType;
}
// Use name directly if available
const typeLabel = log.name || this.getCareTypeLabel(type);
return {
...log,
day: parts[2],
month: parts[1],
typeLabel: this.getCareTypeLabel(log.type)
day: day,
month: month,
time: timeOnly,
type: type,
typeLabel: typeLabel,
remark: log.remark || log.content || '',
taskIcon: taskIcon,
iconColor: iconColor,
iconBgColor: iconBgColor
};
});
},
@@ -160,9 +139,12 @@ Page({
water: '浇水',
fertilize: '施肥',
prune: '修剪',
repot: '换盆'
repot: '换盆',
pesticide: '除虫',
sun: '晒太阳',
other: '养护'
};
return map[type] || '养护';
return map[type] || '日常养护';
},
updateDisplayLogs() {
@@ -205,11 +187,17 @@ Page({
this.updateDisplayRecords();
},
// Navigate to Edit Page
// Navigate to Edit Page with EventChannel
handleOpenEditModal() {
if (this.data.currentPlant && this.data.currentPlant.id) {
wx.navigateTo({
url: `/pages/plant-detail/edit/index?id=${this.data.currentPlant.id}`
url: `/pages/plant-detail/edit/index?id=${this.data.currentPlant.id}&source=detail`,
success: (res) => {
// Send current data to the opened page
res.eventChannel.emit('acceptDataFromOpenerPage', {
plant: this.data.currentPlant
});
}
});
}
},
+73 -40
View File
@@ -50,30 +50,35 @@
>
<view class="care-view fadeIn">
<view class="section-title">
<text class="h3">养护历史</text>
<text class="h3">养护记录</text>
</view>
<view class="care-log-list">
<view wx:for="{{displayCareLogs}}" wx:key="id" class="care-log-item">
<view class="log-left">
<view class="log-date-v">
<text class="l-day">{{item.day}}</text>
<text class="l-month">{{item.month}}</text>
</view>
<view class="log-type-icon {{item.type === 'water' ? 'icon-water' : (item.type === 'fertilize' ? 'icon-fertilize' : (item.type === 'prune' ? 'icon-prune' : 'icon-repot'))}}">
<t-icon wx:if="{{item.type === 'water'}}" name="heart" size="36rpx" color="#2196F3" />
<t-icon wx:elif="{{item.type === 'fertilize'}}" name="app" size="36rpx" color="#FFD700" />
<t-icon wx:elif="{{item.type === 'prune'}}" name="cut" size="36rpx" color="#757575" />
<t-icon wx:else name="assignment" size="36rpx" color="#8D6E63" />
</view>
<view class="log-info">
<view class="log-header-row">
<text class="log-type-name">{{item.typeLabel}}</text>
<text class="log-time">{{item.time}}</text>
<block wx:if="{{displayCareLogs && displayCareLogs.length > 0}}">
<view wx:for="{{displayCareLogs}}" wx:key="id" class="care-log-item">
<view class="log-left">
<view class="log-date-v">
<text class="l-day">{{item.day}}</text>
<text class="l-month">{{item.month}}月</text>
</view>
<view class="log-type-icon" style="background-color: {{item.iconBgColor || '#EFEBE9'}};">
<t-icon name="{{item.taskIcon}}" size="36rpx" color="{{item.iconColor || '#8D6E63'}}" />
</view>
<view class="log-info">
<view class="log-header-row">
<text class="log-type-name">{{item.typeLabel}}</text>
<text class="log-time">{{item.time}}</text>
</view>
<text wx:if="{{item.remark}}" class="log-remark">{{item.remark}}</text>
</view>
<text wx:if="{{item.remark}}" class="log-remark">{{item.remark}}</text>
</view>
</view>
</block>
<view wx:else class="plant-empty-state">
<view class="plant-empty-text">
<text>暂无养护记录</text>
<text>快去给它浇浇水吧~ 💧</text>
</view>
</view>
</view>
@@ -114,7 +119,27 @@
</view>
<view class="aic-stat-item">
<text class="label">养护次数</text>
<text class="value">{{careLogs.length}} 次</text>
<text class="value">{{careLogs.length || 0}} 次</text>
</view>
</view>
<!-- New detail fields -->
<view class="aic-extra-info">
<view wx:if="{{currentPlant.potMaterial}}" class="aic-info-row">
<text class="label">花园材质:</text>
<text class="value">{{currentPlant.potMaterial}}</text>
</view>
<view wx:if="{{currentPlant.potSize}}" class="aic-info-row">
<text class="label">花园大小:</text>
<text class="value">{{currentPlant.potSize}}</text>
</view>
<view wx:if="{{currentPlant.sunlight}}" class="aic-info-row">
<text class="label">光照条件:</text>
<text class="value">{{currentPlant.sunlight}}</text>
</view>
<view wx:if="{{currentPlant.plantingMaterial}}" class="aic-info-row">
<text class="label">植料土壤:</text>
<text class="value">{{currentPlant.plantingMaterial}}</text>
</view>
</view>
</view>
@@ -127,30 +152,38 @@
</view>
</view>
<view class="archive-timeline">
<view wx:for="{{displayRecords}}" wx:key="id" class="timeline-item">
<view class="timeline-dot"></view>
<text class="timeline-date">{{item.date}}</text>
<view class="timeline-content-box">
<view class="timeline-title">
<t-icon wx:if="{{item.type === 'growth'}}" name="thumb-up" size="32rpx" color="#4CAF50" />
<t-icon wx:elif="{{item.type === 'repot'}}" name="swap" size="32rpx" color="#FF9800" />
<t-icon wx:elif="{{item.type === 'pest'}}" name="error-circle" size="32rpx" color="#F44336" />
<t-icon wx:else name="file" size="32rpx" color="#2196F3" />
<text>{{item.title}}</text>
<block wx:if="{{displayRecords && displayRecords.length > 0}}">
<view class="archive-timeline">
<view wx:for="{{displayRecords}}" wx:key="id" class="timeline-item">
<view class="timeline-dot"></view>
<text class="timeline-date">{{item.date}}</text>
<view class="timeline-content-box">
<view class="timeline-title">
<t-icon wx:if="{{item.type === 'growth'}}" name="thumb-up" size="32rpx" color="#4CAF50" />
<t-icon wx:elif="{{item.type === 'repot'}}" name="swap" size="32rpx" color="#FF9800" />
<t-icon wx:elif="{{item.type === 'pest'}}" name="error-circle" size="32rpx" color="#F44336" />
<t-icon wx:else name="file" size="32rpx" color="#2196F3" />
<text>{{item.title}}</text>
</view>
<text class="timeline-desc">{{item.content}}</text>
<t-image
wx:if="{{item.image}}"
src="{{item.image}}"
mode="widthFix"
width="100%"
class="timeline-img"
bindtap="handlePreviewRecordImage"
data-src="{{item.image}}"
/>
</view>
<text class="timeline-desc">{{item.content}}</text>
<t-image
wx:if="{{item.image}}"
src="{{tools.resolvePath(item.image)}}"
mode="widthFix"
width="100%"
class="timeline-img"
bindtap="handlePreviewRecordImage"
data-src="{{item.image}}"
/>
</view>
</view>
</block>
<view wx:else class="plant-empty-state">
<view class="plant-empty-text">
<text>暂无成长记录</text>
<text>记录下它的每一个精彩瞬间吧 📸</text>
</view>
</view>
<block wx:if="{{records.length > displayRecordLimit}}">
+58
View File
@@ -376,6 +376,35 @@ page {
color: #1B5E20;
}
.aic-extra-info {
margin-top: 40rpx;
padding-top: 32rpx;
border-top: 1rpx solid rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
gap: 16rpx;
text-align: left;
}
.aic-info-row {
display: flex;
align-items: flex-start;
font-size: 26rpx;
line-height: 1.5;
}
.aic-info-row .label {
color: #558B2F;
font-weight: 600;
width: 140rpx;
flex-shrink: 0;
}
.aic-info-row .value {
color: #455A64;
flex: 1;
}
/* Section Header */
.section-header {
display: flex;
@@ -761,3 +790,32 @@ page {
}
/* Empty State */
.plant-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64rpx 0;
gap: 32rpx;
background: white;
border-radius: 32rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.plant-empty-img {
width: 200rpx;
height: 200rpx;
opacity: 0.8;
}
.plant-empty-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
font-size: 28rpx;
font-weight: 500;
color: #90A4AE;
}