feat: 小程序连接服务器IP + 同步未提交开发
- miniprogram/config.js: BASE_URL 指向 http://192.227.237.8:3001 - 动作标题中文化(nameZh),搜索匹配 name/nameZh/nameEn - 新增计划/收藏/我的 等页面与组件 - 后端 Docker 化(Dockerfile/.dockerignore)与翻译脚本 - .gitignore 补充 .DS_Store
This commit is contained in:
88
miniprogram/pages/plan-edit/plan-edit.js
Normal file
88
miniprogram/pages/plan-edit/plan-edit.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const app = getApp();
|
||||
const planSvc = require('../../services/plan');
|
||||
|
||||
const EMOJIS = ['📋', '💪', '🔥', '🏋️', '🏃', '🧘', '🤸', '🥊', '🚴', '⚡', '🌟', '🎯'];
|
||||
const COLORS = ['#D9B36C', '#58C9B9', '#9B8CFF', '#F08A5D', '#3FBF7F', '#5B9DF9'];
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 20,
|
||||
mode: 'new',
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
emoji: '📋',
|
||||
color: '#D9B36C',
|
||||
emojis: EMOJIS,
|
||||
colors: COLORS,
|
||||
loading: false,
|
||||
dirty: false // 是否有未保存的修改
|
||||
},
|
||||
|
||||
onLoad(query) {
|
||||
const mode = query.mode === 'edit' ? 'edit' : 'new';
|
||||
const id = query.id || '';
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 20, mode, id });
|
||||
if (mode === 'edit' && id) {
|
||||
this.setData({ loading: true });
|
||||
planSvc.get(id).then((p) => {
|
||||
this.setData({
|
||||
name: p.name || '',
|
||||
description: p.description || '',
|
||||
emoji: p.emoji || '📋',
|
||||
color: p.color || '#D9B36C',
|
||||
loading: false
|
||||
});
|
||||
}).catch(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onName(e) { this.setData({ name: e.detail.value, dirty: true }); },
|
||||
onDesc(e) { this.setData({ description: e.detail.value, dirty: true }); },
|
||||
onPickEmoji(e) { this.setData({ emoji: e.currentTarget.dataset.emoji, dirty: true }); },
|
||||
onPickColor(e) { this.setData({ color: e.currentTarget.dataset.color, dirty: true }); },
|
||||
|
||||
// 点返回键(navbar 拦截模式)触发:有未保存改动时先确认
|
||||
onBackRequest() {
|
||||
if (!this.data.dirty) {
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
wx.showModal({
|
||||
title: '放弃编辑?',
|
||||
content: '当前修改尚未保存,确定要退出吗?',
|
||||
confirmText: '退出',
|
||||
cancelText: '继续编辑',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.navigateBack();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSave() {
|
||||
const name = (this.data.name || '').trim();
|
||||
if (!name) {
|
||||
wx.showToast({ title: '请填写计划名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const dto = {
|
||||
name,
|
||||
description: (this.data.description || '').trim(),
|
||||
emoji: this.data.emoji,
|
||||
color: this.data.color
|
||||
};
|
||||
wx.showLoading({ title: '保存中…', mask: true });
|
||||
const op = this.data.mode === 'edit'
|
||||
? planSvc.update(this.data.id, dto)
|
||||
: planSvc.create(dto);
|
||||
op.then(() => {
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '已保存', icon: 'success' });
|
||||
setTimeout(() => wx.navigateBack(), 500);
|
||||
}).catch(() => {
|
||||
wx.hideLoading();
|
||||
});
|
||||
}
|
||||
});
|
||||
6
miniprogram/pages/plan-edit/plan-edit.json
Normal file
6
miniprogram/pages/plan-edit/plan-edit.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"navbar": "/components/navbar/index"
|
||||
}
|
||||
}
|
||||
64
miniprogram/pages/plan-edit/plan-edit.wxml
Normal file
64
miniprogram/pages/plan-edit/plan-edit.wxml
Normal file
@@ -0,0 +1,64 @@
|
||||
<view class="page">
|
||||
<navbar title="{{mode === 'edit' ? '编辑计划' : '新建计划'}}" show-back intercept-back bind:back="onBackRequest" />
|
||||
|
||||
<view class="section">
|
||||
<view class="preview" style="background: linear-gradient(135deg, {{color}}26, var(--surface));">
|
||||
<view class="preview__emoji" style="background: {{color}}22; color: {{color}};">{{emoji}}</view>
|
||||
<view class="preview__name" style="color: {{color}};">{{name || '计划名称'}}</view>
|
||||
<view class="preview__desc">{{description || '计划描述(选填)'}}</view>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<view class="field__label">计划名称</view>
|
||||
<input
|
||||
class="field__input"
|
||||
value="{{name}}"
|
||||
placeholder="例如:居家增肌 12 周"
|
||||
placeholder-class="ph"
|
||||
maxlength="20"
|
||||
bindinput="onName" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<view class="field__label">计划描述</view>
|
||||
<textarea
|
||||
class="field__area"
|
||||
value="{{description}}"
|
||||
placeholder="简单描述你的训练目标(选填)"
|
||||
placeholder-class="ph"
|
||||
maxlength="120"
|
||||
auto-height
|
||||
bindinput="onDesc" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<view class="field__label">图标</view>
|
||||
<scroll-view scroll-x class="emoji-row">
|
||||
<view
|
||||
wx:for="{{emojis}}"
|
||||
wx:key="*this"
|
||||
class="emoji {{emoji === item ? 'emoji--active' : ''}}"
|
||||
style="{{emoji === item ? 'border-color:' + color + '; background:' + color + '22;' : ''}}"
|
||||
data-emoji="{{item}}"
|
||||
bindtap="onPickEmoji">{{item}}</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<view class="field__label">主题色</view>
|
||||
<view class="colors">
|
||||
<view
|
||||
wx:for="{{colors}}"
|
||||
wx:key="*this"
|
||||
class="swatch {{color === item ? 'swatch--active' : ''}}"
|
||||
style="background: {{item}};"
|
||||
data-color="{{item}}"
|
||||
bindtap="onPickColor"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="savebar">
|
||||
<view class="savebar__btn" bindtap="onSave">保存计划</view>
|
||||
</view>
|
||||
</view>
|
||||
54
miniprogram/pages/plan-edit/plan-edit.wxss
Normal file
54
miniprogram/pages/plan-edit/plan-edit.wxss
Normal file
@@ -0,0 +1,54 @@
|
||||
.page { min-height: 100vh; background: var(--bg-grad); padding-bottom: 160rpx; }
|
||||
|
||||
.preview {
|
||||
border-radius: var(--radius); padding: 36rpx;
|
||||
border: 1rpx solid var(--line); text-align: center;
|
||||
}
|
||||
.preview__emoji {
|
||||
width: 120rpx; height: 120rpx; border-radius: 28rpx;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 64rpx;
|
||||
}
|
||||
.preview__name { font-size: 38rpx; font-weight: 700; margin-top: 20rpx; }
|
||||
.preview__desc { font-size: 24rpx; color: var(--text-2); margin-top: 10rpx; }
|
||||
|
||||
.field { margin-top: 40rpx; }
|
||||
.field__label { font-size: 26rpx; color: var(--text-2); margin-bottom: 16rpx; }
|
||||
.field__input, .field__area {
|
||||
background: var(--surface); border: 1rpx solid var(--line);
|
||||
border-radius: var(--radius-sm); padding: 24rpx; color: var(--text);
|
||||
font-size: 30rpx; width: 100%;
|
||||
}
|
||||
.field__area { min-height: 140rpx; }
|
||||
.ph { color: var(--text-3); }
|
||||
|
||||
.emoji-row { white-space: nowrap; }
|
||||
.emoji {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 84rpx; height: 84rpx; margin-right: 14rpx;
|
||||
border-radius: 20rpx; background: var(--surface-2);
|
||||
border: 1rpx solid var(--line); font-size: 44rpx;
|
||||
}
|
||||
.emoji--active { border-width: 2rpx; }
|
||||
|
||||
.colors { display: flex; flex-wrap: wrap; gap: 24rpx; }
|
||||
.swatch {
|
||||
width: 72rpx; height: 72rpx; border-radius: 999rpx;
|
||||
border: 4rpx solid transparent; box-sizing: border-box;
|
||||
}
|
||||
.swatch--active { border-color: var(--text); box-shadow: 0 0 0 2rpx var(--bg); }
|
||||
|
||||
.savebar {
|
||||
position: fixed; left: 0; right: 0; bottom: 0;
|
||||
padding: 20rpx 32rpx;
|
||||
background: rgba(14,17,16,0.92);
|
||||
backdrop-filter: blur(12px);
|
||||
border-top: 1rpx solid var(--line);
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.savebar__btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%);
|
||||
color: #0E1110; font-weight: 700; font-size: 32rpx;
|
||||
border-radius: 999rpx; padding: 26rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user