init: initial commit

This commit is contained in:
Blizzard
2026-02-04 14:02:31 +08:00
commit 6ceda92e9d
2234 changed files with 38231 additions and 0 deletions
+273
View File
@@ -0,0 +1,273 @@
// pages/community/create/index.js
import { MOCK_POSTS } from '../../../utils/mockData';
Page({
data: {
content: '',
images: [],
canPublish: false,
autoFocus: true,
location: '',
selectedTopics: [],
suggestedTopics: ['植物养护', '多肉日记', '绿植分享', '花卉美照', '阳台花园', '新手入门'],
hasDraft: false,
showImageSheet: false,
imageSheetItems: [
{ label: '拍照', value: 'camera' },
{ label: '从相册选择', value: 'album' }
]
},
onLoad() {
// Check for saved draft
this.loadDraft();
},
onUnload() {
// Save draft if there's content
this.saveDraft();
},
loadDraft() {
try {
const draft = wx.getStorageSync('post_draft');
if (draft && (draft.content || draft.images.length > 0)) {
this.setData({
content: draft.content || '',
images: draft.images || [],
selectedTopics: draft.selectedTopics || [],
canPublish: draft.content && draft.content.trim().length > 0,
hasDraft: true
});
}
} catch (e) {
console.log('No draft found');
}
},
saveDraft() {
if (this.data.content || this.data.images.length > 0) {
try {
wx.setStorageSync('post_draft', {
content: this.data.content,
images: this.data.images,
selectedTopics: this.data.selectedTopics
});
} catch (e) {
console.log('Failed to save draft');
}
}
},
clearDraft() {
try {
wx.removeStorageSync('post_draft');
} catch (e) {
console.log('Failed to clear draft');
}
},
onContentInput(e) {
const content = e.detail.value;
this.setData({
content,
canPublish: content.trim().length > 0,
hasDraft: false
});
},
showImageSourceSheet() {
this.setData({ showImageSheet: true });
},
hideImageSheet() {
this.setData({ showImageSheet: false });
},
onImageSheetSelect(e) {
const { value } = e.detail.selected;
this.setData({ showImageSheet: false });
if (value === 'camera') {
this.takePhoto();
} else {
this.chooseImage();
}
},
chooseImage() {
const remaining = 9 - this.data.images.length;
if (remaining <= 0) {
wx.showToast({ title: '最多9张图片', icon: 'none' });
return;
}
wx.chooseMedia({
count: remaining,
mediaType: ['image'],
sourceType: ['album'],
success: (res) => {
const newImages = res.tempFiles.map(f => f.tempFilePath);
this.setData({
images: [...this.data.images, ...newImages],
hasDraft: false
});
}
});
},
takePhoto() {
if (this.data.images.length >= 9) {
wx.showToast({ title: '最多9张图片', icon: 'none' });
return;
}
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['camera'],
camera: 'back',
success: (res) => {
const newImage = res.tempFiles[0].tempFilePath;
this.setData({
images: [...this.data.images, newImage],
hasDraft: false
});
}
});
},
removeImage(e) {
const index = e.currentTarget.dataset.index;
const images = [...this.data.images];
images.splice(index, 1);
this.setData({ images });
},
showImageMenu(e) {
const index = e.currentTarget.dataset.index;
wx.showActionSheet({
itemList: ['设为封面', '删除'],
success: (res) => {
if (res.tapIndex === 0) {
// Move to first position
const images = [...this.data.images];
const [img] = images.splice(index, 1);
images.unshift(img);
this.setData({ images });
wx.showToast({ title: '已设为封面', icon: 'success' });
} else if (res.tapIndex === 1) {
this.removeImage({ currentTarget: { dataset: { index } } });
}
}
});
},
chooseLocation() {
wx.chooseLocation({
success: (res) => {
this.setData({ location: res.name || res.address });
},
fail: () => {
// User cancelled or no permission
}
});
},
toggleTopic(e) {
const topic = e.currentTarget.dataset.topic;
const hashtag = `#${topic} `;
let { content, selectedTopics } = this.data;
if (selectedTopics.includes(topic)) {
// Remove topic and hashtag from content
selectedTopics = selectedTopics.filter(t => t !== topic);
content = content.replace(hashtag, '');
} else {
if (selectedTopics.length >= 3) {
wx.showToast({ title: '最多选择3个话题', icon: 'none' });
return;
}
// Add topic and insert hashtag into content
selectedTopics.push(topic);
content = content + hashtag;
}
this.setData({
selectedTopics,
content,
canPublish: content.trim().length > 0
});
},
insertEmoji() {
// Simple emoji picker simulation
const emojis = ['🌱', '🌿', '🍀', '🌵', '🌻', '🌺', '🌸', '🌼', '🪴', '🌲'];
wx.showActionSheet({
itemList: emojis,
success: (res) => {
const emoji = emojis[res.tapIndex];
this.setData({
content: this.data.content + emoji,
canPublish: true
});
}
});
},
handleCancel() {
if (this.data.content || this.data.images.length > 0) {
wx.showModal({
title: '保存草稿',
content: '是否保存当前内容为草稿?',
cancelText: '不保存',
confirmText: '保存',
success: (res) => {
if (res.confirm) {
this.saveDraft();
wx.showToast({ title: '已保存草稿', icon: 'success' });
setTimeout(() => wx.navigateBack(), 500);
} else {
this.clearDraft();
wx.navigateBack();
}
}
});
} else {
wx.navigateBack();
}
},
handlePublish() {
if (!this.data.canPublish) {
wx.showToast({ title: '请输入内容', icon: 'none' });
return;
}
// Content already includes topics as hashtags
const finalContent = this.data.content.trim();
// Create new post
const newPost = {
id: Date.now().toString(),
user: '我的花园',
content: finalContent,
images: this.data.images,
time: '刚刚',
likes: [],
comments: []
};
// Add to global mock data (at the beginning)
MOCK_POSTS.unshift(newPost);
// Clear draft
this.clearDraft();
wx.showToast({ title: '发布成功', icon: 'success' });
setTimeout(() => {
wx.navigateBack();
}, 1000);
}
})
+9
View File
@@ -0,0 +1,9 @@
{
"navigationBarTitleText": "发布动态",
"navigationStyle": "custom",
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "tdesign-miniprogram/image/image",
"t-action-sheet": "tdesign-miniprogram/action-sheet/action-sheet"
}
}
+100
View File
@@ -0,0 +1,100 @@
<view class="create-post-page">
<!-- Content Area -->
<scroll-view scroll-y class="create-content" enhanced="{{true}}" show-scrollbar="{{false}}">
<!-- Action Row: Cancel + Publish -->
<view class="action-row">
<view class="cancel-btn" bindtap="handleCancel">
<text>取消</text>
</view>
<view class="publish-btn {{canPublish ? 'active' : ''}}" bindtap="handlePublish">
<text>发布</text>
</view>
</view>
<!-- Text Input -->
<textarea
class="post-textarea"
placeholder="分享你的植物养护心得..."
placeholder-class="textarea-placeholder"
value="{{content}}"
bindinput="onContentInput"
maxlength="500"
auto-height
focus="{{autoFocus}}"
/>
<!-- Character Counter -->
<view class="char-counter" wx:if="{{content.length > 0}}">
<text class="{{content.length >= 450 ? 'warning' : ''}}">{{content.length}}</text>
<text class="total">/500</text>
</view>
<!-- Image Preview Grid -->
<view wx:if="{{images.length > 0}}" class="image-section">
<view class="image-preview-grid">
<view wx:for="{{images}}" wx:key="*this" class="preview-item" bindlongpress="showImageMenu" data-index="{{index}}">
<t-image src="{{item}}" mode="aspectFill" width="100%" height="100%" />
<view class="remove-btn" catchtap="removeImage" data-index="{{index}}">
<t-icon name="close" size="24rpx" color="#fff" />
</view>
<view class="image-index">{{index + 1}}</view>
</view>
<!-- Add More Button -->
<view wx:if="{{images.length < 9}}" class="add-image-btn" bindtap="showImageSourceSheet">
<t-icon name="add" size="48rpx" color="#999" />
<text class="add-count">{{images.length}}/9</text>
</view>
</view>
</view>
<!-- Add Image Area (when no images) -->
<view wx:else class="add-first-image" bindtap="showImageSourceSheet">
<view class="image-upload-box">
<view class="upload-icon">
<t-icon name="photo" size="64rpx" color="#558B2F" />
</view>
<text class="upload-text">添加图片</text>
<text class="upload-hint">分享你的植物美照,最多9张</text>
</view>
</view>
<!-- Location Tag (Optional Feature) -->
<view class="location-section" bindtap="chooseLocation">
<view class="location-left">
<t-icon name="location" size="40rpx" color="#558B2F" />
<text class="location-text">{{location || '添加位置'}}</text>
</view>
<t-icon name="chevron-right" size="36rpx" color="#ccc" />
</view>
<!-- Topic Tags -->
<view class="topic-section">
<view class="section-title">
<t-icon name="hashtag" size="36rpx" color="#558B2F" />
<text>添加话题</text>
</view>
<view class="topic-list">
<view class="topic-tag {{selectedTopics.includes(item) ? 'selected' : ''}}"
wx:for="{{suggestedTopics}}"
wx:key="*this"
bindtap="toggleTopic"
data-topic="{{item}}">
<text>#{{item}}</text>
</view>
</view>
</view>
<!-- Bottom Spacer -->
<view style="height: 60rpx;"></view>
</scroll-view>
<!-- Image Source Action Sheet -->
<t-action-sheet
visible="{{showImageSheet}}"
items="{{imageSheetItems}}"
bind:selected="onImageSheetSelect"
bind:cancel="hideImageSheet"
show-cancel
/>
</view>
+316
View File
@@ -0,0 +1,316 @@
/** pages/community/create/index.wxss **/
page {
height: 100%;
background: #fff;
}
.create-post-page {
height: 100vh;
display: flex;
flex-direction: column;
background: #fff;
}
/* Content Area */
.create-content {
flex: 1;
padding: 0 32rpx;
padding-top: calc(env(safe-area-inset-top) + 100rpx);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.create-content::-webkit-scrollbar {
display: none;
}
/* Action Row */
.action-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
margin-bottom: 32rpx;
}
/* Cancel Button */
.cancel-btn {
font-size: 30rpx;
color: #666;
padding: 12rpx 0;
}
.cancel-btn:active {
opacity: 0.6;
}
/* Publish Button */
.publish-btn {
font-size: 28rpx;
color: #999;
background: #f0f0f0;
padding: 16rpx 40rpx;
border-radius: 32rpx;
transition: all 0.2s;
}
.publish-btn.active {
color: #fff;
background: linear-gradient(135deg, #689F38, #558B2F);
box-shadow: 0 8rpx 24rpx rgba(85, 139, 47, 0.3);
}
.publish-btn.active:active {
transform: scale(0.95);
}
/* Textarea */
.post-textarea {
width: 100%;
min-height: 200rpx;
font-size: 32rpx;
line-height: 1.7;
color: #333;
padding: 32rpx 0;
box-sizing: border-box;
}
.textarea-placeholder {
color: #bbb;
}
/* Character Counter */
.char-counter {
text-align: right;
font-size: 24rpx;
margin-bottom: 24rpx;
}
.char-counter text {
color: #999;
}
.char-counter .warning {
color: #FF9800;
}
.char-counter .total {
color: #ccc;
}
/* Image Section */
.image-section {
margin-bottom: 32rpx;
}
/* Image Preview Grid */
.image-preview-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
}
.preview-item {
position: relative;
aspect-ratio: 1;
border-radius: 12rpx;
overflow: hidden;
background: #f5f5f5;
}
.remove-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 44rpx;
height: 44rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.remove-btn:active {
background: rgba(0, 0, 0, 0.7);
}
.image-index {
position: absolute;
bottom: 8rpx;
left: 8rpx;
width: 36rpx;
height: 36rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
color: #fff;
}
.add-image-btn {
aspect-ratio: 1;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
background: #fafafa;
}
.add-image-btn:active {
background: #f0f0f0;
}
.add-count {
font-size: 22rpx;
color: #999;
}
/* Add First Image */
.add-first-image {
margin: 32rpx 0;
}
.image-upload-box {
padding: 64rpx 48rpx;
border: 2rpx dashed #C8E6C9;
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(180deg, #F8FCF8, #fff);
}
.image-upload-box:active {
background: #F0F7F0;
}
.upload-icon {
width: 120rpx;
height: 120rpx;
background: #E8F5E9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
}
.upload-text {
font-size: 32rpx;
color: #558B2F;
font-weight: 600;
margin-bottom: 8rpx;
}
.upload-hint {
font-size: 26rpx;
color: #90A4AE;
}
/* Location Section */
.location-section {
display: flex;
justify-content: space-between;
align-items: center;
padding: 28rpx 0;
border-top: 2rpx solid #f5f5f5;
border-bottom: 2rpx solid #f5f5f5;
}
.location-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.location-text {
font-size: 28rpx;
color: #666;
}
/* Topic Section */
.topic-section {
padding: 28rpx 0;
}
.section-title {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 20rpx;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.topic-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.topic-tag {
padding: 12rpx 24rpx;
background: #f5f5f5;
border-radius: 32rpx;
font-size: 26rpx;
color: #666;
transition: all 0.2s;
}
.topic-tag.selected {
background: #E8F5E9;
color: #558B2F;
font-weight: 500;
}
.topic-tag:active {
transform: scale(0.95);
}
/* Bottom Toolbar */
.create-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 32rpx;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
border-top: 2rpx solid #f5f5f5;
background: #fff;
flex-shrink: 0;
}
.toolbar-left {
display: flex;
gap: 24rpx;
}
.toolbar-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.toolbar-btn:active {
background: #f5f5f5;
}
.toolbar-right {
display: flex;
align-items: center;
}
.draft-hint {
font-size: 24rpx;
color: #999;
}