Initial Commit
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
// add-anniversary.js
|
||||
const storage = require('../../utils/storage')
|
||||
const dateUtils = require('../../utils/date')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
anniversaryId: null,
|
||||
personId: null,
|
||||
personList: [],
|
||||
personIndex: 0,
|
||||
selectedPerson: '',
|
||||
typeList: ['公历生日', '农历生日', '结婚纪念日', '订婚纪念日', '其他纪念日'],
|
||||
typeIndex: 0,
|
||||
showCustomType: false,
|
||||
dateValue: '',
|
||||
remindDaysList: ['提前3天', '提前7天', '提前14天', '提前30天', '自定义'],
|
||||
remindDaysIndex: 0,
|
||||
formData: {
|
||||
isLunar: false,
|
||||
type: 'birthday',
|
||||
customTypeName: '',
|
||||
solarYear: '',
|
||||
solarMonth: '',
|
||||
solarDay: '',
|
||||
lunarYear: '',
|
||||
lunarMonth: '',
|
||||
lunarDay: '',
|
||||
importance: 'low',
|
||||
remindEnabled: true,
|
||||
remindDays: 7,
|
||||
remark: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 获取人员列表
|
||||
const persons = storage.getPersons()
|
||||
this.setData({ personList: persons })
|
||||
|
||||
if (options.personId) {
|
||||
// 从人员详情页进入
|
||||
const index = persons.findIndex(p => p.id === options.personId)
|
||||
if (index !== -1) {
|
||||
this.setData({
|
||||
personIndex: index,
|
||||
personId: options.personId,
|
||||
selectedPerson: persons[index].name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (options.id) {
|
||||
// 编辑模式
|
||||
this.setData({ anniversaryId: options.id })
|
||||
this.loadAnniversary(options.id)
|
||||
} else {
|
||||
// 设置默认日期为今天
|
||||
const today = new Date()
|
||||
this.setData({
|
||||
dateValue: dateUtils.formatDate(today, 'YYYY-MM-DD')
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载纪念日信息(编辑模式)
|
||||
*/
|
||||
loadAnniversary(id) {
|
||||
const anniversaries = storage.getAnniversaries()
|
||||
const anniversary = anniversaries.find(a => a.id === id)
|
||||
|
||||
if (anniversary) {
|
||||
const date = dateUtils.formatDate(
|
||||
new Date(anniversary.solarYear, anniversary.solarMonth - 1, anniversary.solarDay),
|
||||
'YYYY-MM-DD'
|
||||
)
|
||||
|
||||
// 设置类型
|
||||
const typeIndex = this.getTypeIndex(anniversary.type)
|
||||
|
||||
this.setData({
|
||||
formData: anniversary,
|
||||
dateValue: date,
|
||||
typeIndex,
|
||||
showCustomType: anniversary.type === 'other'
|
||||
})
|
||||
|
||||
wx.setNavigationBarTitle({ title: '编辑纪念日' })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取类型索引
|
||||
*/
|
||||
getTypeIndex(type) {
|
||||
const indexMap = {
|
||||
birthday: 0,
|
||||
lunar_birthday: 1,
|
||||
wedding: 2,
|
||||
engagement: 3,
|
||||
other: 4
|
||||
}
|
||||
return indexMap[type] || 0
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择人员
|
||||
*/
|
||||
onPersonChange(e) {
|
||||
const index = parseInt(e.detail.value)
|
||||
const person = this.data.personList[index]
|
||||
this.setData({
|
||||
personIndex: index,
|
||||
personId: person.id,
|
||||
selectedPerson: person.name
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择类型
|
||||
*/
|
||||
onTypeChange(e) {
|
||||
const index = parseInt(e.detail.value)
|
||||
const types = ['birthday', 'lunar_birthday', 'wedding', 'engagement', 'other']
|
||||
const isOther = index === 4
|
||||
|
||||
this.setData({
|
||||
typeIndex: index,
|
||||
showCustomType: isOther,
|
||||
'formData.type': types[index]
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 自定义类型输入
|
||||
*/
|
||||
onCustomTypeInput(e) {
|
||||
this.setData({
|
||||
'formData.customTypeName': e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 日期类型改变
|
||||
*/
|
||||
onDateTypeChange(e) {
|
||||
const isLunar = e.detail.value === 'lunar'
|
||||
this.setData({
|
||||
'formData.isLunar': isLunar
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 日期改变
|
||||
*/
|
||||
onDateChange(e) {
|
||||
const dateStr = e.detail.value
|
||||
const parts = dateStr.split('-')
|
||||
|
||||
this.setData({
|
||||
dateValue: dateStr,
|
||||
'formData.solarYear': parseInt(parts[0]),
|
||||
'formData.solarMonth': parseInt(parts[1]),
|
||||
'formData.solarDay': parseInt(parts[2])
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 重要程度改变
|
||||
*/
|
||||
onImportanceChange(e) {
|
||||
this.setData({
|
||||
'formData.importance': e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 提醒开关改变
|
||||
*/
|
||||
onRemindEnabledChange(e) {
|
||||
this.setData({
|
||||
'formData.remindEnabled': e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 提醒天数改变
|
||||
*/
|
||||
onRemindDaysChange(e) {
|
||||
const index = parseInt(e.detail.value)
|
||||
const days = [3, 7, 14, 30, 7][index]
|
||||
this.setData({
|
||||
remindDaysIndex: index,
|
||||
'formData.remindDays': days
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 备注改变
|
||||
*/
|
||||
onRemarkChange(e) {
|
||||
this.setData({
|
||||
'formData.remark': e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
onCancel() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
onSubmit() {
|
||||
const { formData, personId, anniversaryId } = this.data
|
||||
|
||||
// 验证关联人员
|
||||
if (!personId) {
|
||||
wx.showToast({ title: '请选择关联人员', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 验证日期
|
||||
if (!formData.solarYear || !formData.solarMonth || !formData.solarDay) {
|
||||
wx.showToast({ title: '请选择日期', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 验证自定义类型
|
||||
if (formData.type === 'other' && !formData.customTypeName) {
|
||||
wx.showToast({ title: '请输入自定义类型', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (anniversaryId) {
|
||||
// 编辑模式
|
||||
const success = storage.updateAnniversary(anniversaryId, {
|
||||
personId,
|
||||
...formData
|
||||
})
|
||||
|
||||
if (success) {
|
||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
} else {
|
||||
// 新增模式
|
||||
const newAnniversary = storage.addAnniversary({
|
||||
personId,
|
||||
...formData
|
||||
})
|
||||
|
||||
if (newAnniversary) {
|
||||
wx.showToast({ title: '添加成功', icon: 'success' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<!--add-anniversary.wxml-->
|
||||
<view class="container">
|
||||
<view class="form">
|
||||
<!-- 关联人员 -->
|
||||
<view class="form-item">
|
||||
<text class="label required">关联人员</text>
|
||||
<picker mode="selector" range="{{personList}}" range-key="name" value="{{personIndex}}" bindchange="onPersonChange">
|
||||
<view class="picker">
|
||||
<text wx:if="{{selectedPerson}}" class="picker-text">{{selectedPerson}}</text>
|
||||
<text wx:else class="picker-placeholder">请选择关联人员</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 纪念日类型 -->
|
||||
<view class="form-item">
|
||||
<text class="label required">纪念日类型</text>
|
||||
<picker mode="selector" range="{{typeList}}" value="{{typeIndex}}" bindchange="onTypeChange">
|
||||
<view class="picker">
|
||||
<text class="picker-text">{{typeList[typeIndex]}}</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
<input wx:if="{{showCustomType}}" class="input" placeholder="请输入自定义类型" value="{{formData.customTypeName}}" bindinput="onCustomTypeInput" />
|
||||
</view>
|
||||
|
||||
<!-- 日期类型 -->
|
||||
<view class="form-item">
|
||||
<text class="label required">日期类型</text>
|
||||
<radio-group class="radio-group" bindchange="onDateTypeChange">
|
||||
<label class="radio-item">
|
||||
<radio value="solar" checked="{{formData.isLunar === false}}" />
|
||||
<text>公历</text>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<radio value="lunar" checked="{{formData.isLunar === true}}" />
|
||||
<text>农历</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
</view>
|
||||
|
||||
<!-- 日期选择 -->
|
||||
<view class="form-item">
|
||||
<text class="label required">日期</text>
|
||||
<picker mode="date" value="{{dateValue}}" bindchange="onDateChange">
|
||||
<view class="picker">
|
||||
<text class="picker-text">{{dateValue || '请选择日期'}}</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 重要程度 -->
|
||||
<view class="form-item">
|
||||
<text class="label">重要程度</text>
|
||||
<radio-group class="radio-group" bindchange="onImportanceChange">
|
||||
<label class="radio-item">
|
||||
<radio value="high" checked="{{formData.importance === 'high'}}" />
|
||||
<text class="importance-high">非常重要</text>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<radio value="medium" checked="{{formData.importance === 'medium'}}" />
|
||||
<text class="importance-medium">重要</text>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<radio value="low" checked="{{formData.importance === 'low' || !formData.importance}}" />
|
||||
<text class="importance-low">一般</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
</view>
|
||||
|
||||
<!-- 提醒设置 -->
|
||||
<view class="form-item">
|
||||
<view class="switch-item">
|
||||
<text class="label">开启提醒</text>
|
||||
<switch checked="{{formData.remindEnabled}}" bindchange="onRemindEnabledChange" color="#07c160" />
|
||||
</view>
|
||||
|
||||
<picker wx:if="{{formData.remindEnabled}}" mode="selector" range="{{remindDaysList}}" value="{{remindDaysIndex}}" bindchange="onRemindDaysChange">
|
||||
<view class="picker">
|
||||
<text class="picker-text">提前{{formData.remindDays}}天提醒</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea class="textarea" placeholder="添加备注信息(可选)" value="{{formData.remark}}" bindinput="onRemarkChange" />
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="buttons">
|
||||
<button class="btn btn-cancel" bindtap="onCancel">取消</button>
|
||||
<button class="btn btn-submit" bindtap="onSubmit">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/**add-anniversary.wxss**/
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.label.required::after {
|
||||
content: ' *';
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.picker {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker-placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-top: 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.radio-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.radio-item text {
|
||||
font-size: 28rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.importance-high {
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.importance-medium {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.importance-low {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.switch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
min-height: 160rpx;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-cancel::after,
|
||||
.btn-submit::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// add-person.js
|
||||
const storage = require('../../utils/storage')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
personId: null,
|
||||
formData: {
|
||||
avatar: '',
|
||||
name: '',
|
||||
nickname: '',
|
||||
remark: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 如果是编辑模式
|
||||
if (options.id) {
|
||||
this.setData({ personId: options.id })
|
||||
this.loadPerson(options.id)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载人员信息(编辑模式)
|
||||
*/
|
||||
loadPerson(id) {
|
||||
const person = storage.getPersonById(id)
|
||||
if (person) {
|
||||
this.setData({ formData: person })
|
||||
wx.setNavigationBarTitle({ title: '编辑人员' })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 输入框变化
|
||||
*/
|
||||
onInputChange(e) {
|
||||
const field = e.currentTarget.dataset.field
|
||||
const value = e.detail.value
|
||||
this.setData({
|
||||
[`formData.${field}`]: value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 文本框变化
|
||||
*/
|
||||
onTextareaChange(e) {
|
||||
this.setData({
|
||||
'formData.remark': e.detail.value
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择头像
|
||||
*/
|
||||
chooseAvatar() {
|
||||
wx.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
// 这里可以上传到服务器,暂时使用本地路径
|
||||
this.setData({
|
||||
'formData.avatar': res.tempFilePaths[0]
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
onCancel() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
onSubmit() {
|
||||
const { formData, personId } = this.data
|
||||
|
||||
// 验证姓名
|
||||
if (!formData.name || formData.name.trim() === '') {
|
||||
wx.showToast({
|
||||
title: '请输入姓名',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (personId) {
|
||||
// 编辑模式
|
||||
const success = storage.updatePerson(personId, formData)
|
||||
if (success) {
|
||||
wx.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 新增模式
|
||||
const newPerson = storage.addPerson({
|
||||
name: formData.name,
|
||||
nickname: formData.nickname || '',
|
||||
avatar: formData.avatar || '',
|
||||
remark: formData.remark || ''
|
||||
})
|
||||
|
||||
if (newPerson) {
|
||||
wx.showToast({
|
||||
title: '添加成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '添加失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<!--add-person.wxml-->
|
||||
<view class="container">
|
||||
<view class="form">
|
||||
<!-- 头像上传 -->
|
||||
<view class="form-item">
|
||||
<text class="label">头像(可选)</text>
|
||||
<view class="avatar-upload" bindtap="chooseAvatar">
|
||||
<image wx:if="{{formData.avatar}}" class="avatar" src="{{formData.avatar}}" mode="aspectFill" />
|
||||
<view wx:else class="avatar-placeholder">
|
||||
<text class="icon">📷</text>
|
||||
<text class="text">点击上传头像</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 姓名 -->
|
||||
<view class="form-item">
|
||||
<text class="label required">姓名</text>
|
||||
<input class="input" placeholder="请输入姓名" value="{{formData.name}}" bindinput="onInputChange" data-field="name" />
|
||||
</view>
|
||||
|
||||
<!-- 昵称 -->
|
||||
<view class="form-item">
|
||||
<text class="label">昵称</text>
|
||||
<input class="input" placeholder="请输入昵称(可选)" value="{{formData.nickname}}" bindinput="onInputChange" data-field="nickname" />
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea class="textarea" placeholder="添加一些备注信息(可选)" value="{{formData.remark}}" bindinput="onTextareaChange" />
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="buttons">
|
||||
<button class="btn btn-cancel" bindtap="onCancel">取消</button>
|
||||
<button class="btn btn-submit" bindtap="onSubmit">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**add-person.wxss**/
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.label.required::after {
|
||||
content: ' *';
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
/* 头像上传 */
|
||||
.avatar-upload {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin: 0 auto;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px dashed #ddd;
|
||||
}
|
||||
|
||||
.avatar-placeholder .icon {
|
||||
font-size: 60rpx;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.avatar-placeholder .text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.input {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
min-height: 160rpx;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-cancel::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-submit::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
// calendar.js
|
||||
const storage = require('../../utils/storage')
|
||||
const dateUtils = require('../../utils/date')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
currentYear: 2024,
|
||||
currentMonth: 1,
|
||||
calendarDays: [],
|
||||
monthEvents: []
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const today = new Date()
|
||||
this.setData({
|
||||
currentYear: today.getFullYear(),
|
||||
currentMonth: today.getMonth() + 1
|
||||
})
|
||||
this.renderCalendar()
|
||||
this.loadMonthEvents()
|
||||
},
|
||||
|
||||
/**
|
||||
* 渲染日历
|
||||
*/
|
||||
renderCalendar() {
|
||||
const { currentYear, currentMonth } = this.data
|
||||
const anniversaries = storage.getAnniversaries()
|
||||
|
||||
// 获取本月第一天是星期几
|
||||
const firstDay = new Date(currentYear, currentMonth - 1, 1)
|
||||
const startWeekday = firstDay.getDay()
|
||||
|
||||
// 获取本月天数
|
||||
const daysInMonth = new Date(currentYear, currentMonth, 0).getDate()
|
||||
|
||||
// 获取上个月的天数
|
||||
const prevMonthDays = new Date(currentYear, currentMonth - 1, 0).getDate()
|
||||
|
||||
// 构建日历天数数组
|
||||
const calendarDays = []
|
||||
const today = new Date()
|
||||
|
||||
// 填充上个月的日期
|
||||
for (let i = startWeekday - 1; i >= 0; i--) {
|
||||
const day = prevMonthDays - i
|
||||
calendarDays.push({
|
||||
day,
|
||||
date: new Date(currentYear, currentMonth - 2, day),
|
||||
isToday: false,
|
||||
isOtherMonth: true,
|
||||
events: [],
|
||||
hasMore: false
|
||||
})
|
||||
}
|
||||
|
||||
// 填充本月的日期
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(currentYear, currentMonth - 1, day)
|
||||
const isToday = dateUtils.isToday(date)
|
||||
|
||||
// 查找该日期的纪念日
|
||||
const events = anniversaries
|
||||
.filter(a => this.isAnniversaryOnDate(a, date))
|
||||
.map(a => ({
|
||||
id: a.id,
|
||||
color: this.getEventColor(a)
|
||||
}))
|
||||
.slice(0, 3)
|
||||
|
||||
calendarDays.push({
|
||||
day,
|
||||
date,
|
||||
isToday,
|
||||
isOtherMonth: false,
|
||||
events,
|
||||
hasMore: events.length > 2,
|
||||
moreCount: anniversaries.filter(a => this.isAnniversaryOnDate(a, date)).length - 2
|
||||
})
|
||||
}
|
||||
|
||||
// 填充下个月的日期(补全6行)
|
||||
const remainingDays = 42 - calendarDays.length
|
||||
for (let day = 1; day <= remainingDays; day++) {
|
||||
calendarDays.push({
|
||||
day,
|
||||
date: new Date(currentYear, currentMonth, day),
|
||||
isToday: false,
|
||||
isOtherMonth: true,
|
||||
events: [],
|
||||
hasMore: false
|
||||
})
|
||||
}
|
||||
|
||||
this.setData({ calendarDays })
|
||||
},
|
||||
|
||||
/**
|
||||
* 判断纪念日是否在某日期
|
||||
*/
|
||||
isAnniversaryOnDate(anniversary, date) {
|
||||
return anniversary.solarYear === date.getFullYear() &&
|
||||
anniversary.solarMonth === date.getMonth() + 1 &&
|
||||
anniversary.solarDay === date.getDate()
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取事件颜色
|
||||
*/
|
||||
getEventColor(anniversary) {
|
||||
const colors = {
|
||||
high: '#ff5722',
|
||||
medium: '#ff9800',
|
||||
low: '#07c160'
|
||||
}
|
||||
return colors[anniversary.importance] || '#07c160'
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载本月事件
|
||||
*/
|
||||
loadMonthEvents() {
|
||||
const { currentYear, currentMonth } = this.data
|
||||
const persons = storage.getPersons()
|
||||
const anniversaries = storage.getAnniversaries()
|
||||
|
||||
// 筛选本月纪念日
|
||||
const monthEvents = anniversaries
|
||||
.filter(a => a.solarYear === currentYear && a.solarMonth === currentMonth)
|
||||
.map(a => {
|
||||
const person = persons.find(p => p.id === a.personId)
|
||||
const date = new Date(currentYear, currentMonth - 1, a.solarDay)
|
||||
const daysUntil = dateUtils.getDaysUntil(date)
|
||||
|
||||
return {
|
||||
...a,
|
||||
personName: person ? person.name : '未知',
|
||||
typeIcon: this.getTypeIcon(a.type),
|
||||
typeName: a.customTypeName || this.getTypeName(a.type),
|
||||
dateText: dateUtils.formatDate(date, 'MM月DD日'),
|
||||
daysUntil
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.solarDay - b.solarDay)
|
||||
|
||||
this.setData({ monthEvents })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取类型图标
|
||||
*/
|
||||
getTypeIcon(type) {
|
||||
const icons = {
|
||||
birthday: '🎂',
|
||||
lunar_birthday: '🌙',
|
||||
wedding: '💍',
|
||||
engagement: '💕',
|
||||
other: '📅'
|
||||
}
|
||||
return icons[type] || '📅'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取类型名称
|
||||
*/
|
||||
getTypeName(type) {
|
||||
const names = {
|
||||
birthday: '公历生日',
|
||||
lunar_birthday: '农历生日',
|
||||
wedding: '结婚纪念日',
|
||||
engagement: '订婚纪念日',
|
||||
other: '其他纪念日'
|
||||
}
|
||||
return names[type] || '其他'
|
||||
},
|
||||
|
||||
/**
|
||||
* 上一个月
|
||||
*/
|
||||
onPrevMonth() {
|
||||
let { currentYear, currentMonth } = this.data
|
||||
if (currentMonth === 1) {
|
||||
currentYear--
|
||||
currentMonth = 12
|
||||
} else {
|
||||
currentMonth--
|
||||
}
|
||||
this.setData({ currentYear, currentMonth })
|
||||
this.renderCalendar()
|
||||
this.loadMonthEvents()
|
||||
},
|
||||
|
||||
/**
|
||||
* 下一个月
|
||||
*/
|
||||
onNextMonth() {
|
||||
let { currentYear, currentMonth } = this.data
|
||||
if (currentMonth === 12) {
|
||||
currentYear++
|
||||
currentMonth = 1
|
||||
} else {
|
||||
currentMonth++
|
||||
}
|
||||
this.setData({ currentYear, currentMonth })
|
||||
this.renderCalendar()
|
||||
this.loadMonthEvents()
|
||||
},
|
||||
|
||||
/**
|
||||
* 回到今天
|
||||
*/
|
||||
onGoToday() {
|
||||
const today = new Date()
|
||||
this.setData({
|
||||
currentYear: today.getFullYear(),
|
||||
currentMonth: today.getMonth() + 1
|
||||
})
|
||||
this.renderCalendar()
|
||||
this.loadMonthEvents()
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击事件
|
||||
*/
|
||||
onEventTap(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const anniversary = storage.getAnniversaries().find(a => a.id === id)
|
||||
if (anniversary) {
|
||||
wx.navigateTo({
|
||||
url: `/pages/person-detail/person-detail?id=${anniversary.personId}`
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
<!--calendar.wxml-->
|
||||
<view class="container">
|
||||
<!-- 日历头部 -->
|
||||
<view class="calendar-header">
|
||||
<view class="month-navigation">
|
||||
<text class="nav-btn" bindtap="onPrevMonth">‹</text>
|
||||
<text class="month-title">{{currentYear}}年{{currentMonth}}月</text>
|
||||
<text class="nav-btn" bindtap="onNextMonth">›</text>
|
||||
</view>
|
||||
<button class="today-btn" bindtap="onGoToday">今天</button>
|
||||
</view>
|
||||
|
||||
<!-- 星期标题 -->
|
||||
<view class="week-header">
|
||||
<view wx:for="{{['日', '一', '二', '三', '四', '五', '六']}}" wx:key="*this" class="week-title">{{item}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 日历内容 -->
|
||||
<scroll-view class="calendar-content" scroll-y>
|
||||
<view class="calendar-grid">
|
||||
<view wx:for="{{calendarDays}}" wx:key="index" class="calendar-cell {{item.isToday ? 'today' : ''}} {{item.isOtherMonth ? 'other-month' : ''}}">
|
||||
<text class="date-num">{{item.day}}</text>
|
||||
<view class="events">
|
||||
<view wx:for="{{item.events}}" wx:key="id" wx:for-item="event" class="event-dot" style="background-color: {{event.color}};"></view>
|
||||
</view>
|
||||
<view wx:if="{{item.hasMore}}" class="more-text">+{{item.moreCount}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<view class="events-list">
|
||||
<view class="section-title">本月纪念日</view>
|
||||
<view wx:if="{{monthEvents.length === 0}}" class="empty-state">
|
||||
<text class="icon">📅</text>
|
||||
<text class="text">本月没有纪念日</text>
|
||||
</view>
|
||||
<view wx:for="{{monthEvents}}" wx:key="id" class="event-card" bindtap="onEventTap" data-id="{{item.id}}">
|
||||
<view class="event-header">
|
||||
<text class="event-icon">{{item.typeIcon}}</text>
|
||||
<text class="event-title">{{item.personName}} - {{item.typeName}}</text>
|
||||
<text class="event-date">{{item.dateText}}</text>
|
||||
</view>
|
||||
<view class="event-info">
|
||||
<text wx:if="{{item.isLunar}}" class="lunar-badge">农历</text>
|
||||
<text wx:if="{{item.daysUntil === 0}}" class="status urgent">今天</text>
|
||||
<text wx:elif="{{item.daysUntil === 1}}" class="status warning">明天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/**calendar.wxss**/
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
background-color: #fff;
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.month-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
font-size: 48rpx;
|
||||
color: #07c160;
|
||||
font-weight: 300;
|
||||
width: 60rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.month-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
min-width: 240rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.today-btn {
|
||||
padding: 12rpx 32rpx;
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.today-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.week-header {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.week-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.calendar-content {
|
||||
height: calc(100vh - 200rpx);
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.calendar-cell {
|
||||
min-height: 120rpx;
|
||||
border: 1px solid #f0f0f0;
|
||||
padding: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.calendar-cell.other-month {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.calendar-cell.today .date-num {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
line-height: 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.date-num {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.calendar-cell.other-month .date-num {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.events {
|
||||
display: flex;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.event-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.more-text {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* 事件列表 */
|
||||
.events-list {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 40rpx;
|
||||
}
|
||||
|
||||
.empty-state .icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-state .text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.event-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.event-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.event-date {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.event-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.lunar-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
border-radius: 4rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.status.urgent {
|
||||
background-color: #fff3f0;
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.status.warning {
|
||||
background-color: #fff8e6;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// index.js
|
||||
const storage = require('../../utils/storage')
|
||||
const dateUtils = require('../../utils/date')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
persons: [],
|
||||
originalPersons: [], // 原始数据备份
|
||||
searchKeyword: '',
|
||||
currentFilter: 'all'
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadPersons()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 每次显示页面时刷新数据
|
||||
this.loadPersons()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载人员列表
|
||||
*/
|
||||
loadPersons() {
|
||||
const persons = storage.getPersons()
|
||||
const anniversaries = storage.getAnniversaries()
|
||||
|
||||
// 为每个人员添加纪念日信息
|
||||
const personsWithAnniversaries = persons.map(person => {
|
||||
const personAnniversaries = anniversaries.filter(a => a.personId === person.id)
|
||||
|
||||
// 找到最近的纪念日
|
||||
let nextAnniversary = null
|
||||
if (personAnniversaries.length > 0) {
|
||||
const today = new Date()
|
||||
const upcoming = personAnniversaries
|
||||
.map(a => {
|
||||
// 如果是农历,需要特殊处理
|
||||
const date = new Date(a.solarYear, a.solarMonth - 1, a.solarDay)
|
||||
return {
|
||||
...a,
|
||||
date,
|
||||
daysUntil: dateUtils.getDaysUntil(date)
|
||||
}
|
||||
})
|
||||
.filter(a => a.daysUntil >= 0)
|
||||
.sort((a, b) => a.daysUntil - b.daysUntil)
|
||||
|
||||
if (upcoming.length > 0) {
|
||||
const next = upcoming[0]
|
||||
nextAnniversary = {
|
||||
type: next.type,
|
||||
dateText: dateUtils.formatDate(next.date, 'MM月DD日'),
|
||||
daysUntil: next.daysUntil,
|
||||
daysUntilText: this.formatDaysUntil(next.daysUntil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...person,
|
||||
anniversaryCount: personAnniversaries.length,
|
||||
nextAnniversary
|
||||
}
|
||||
})
|
||||
|
||||
// 按最近的纪念日排序
|
||||
const sorted = personsWithAnniversaries.sort((a, b) => {
|
||||
if (!a.nextAnniversary && !b.nextAnniversary) return 0
|
||||
if (!a.nextAnniversary) return 1
|
||||
if (!b.nextAnniversary) return -1
|
||||
return a.nextAnniversary.daysUntil - b.nextAnniversary.daysUntil
|
||||
})
|
||||
|
||||
this.setData({
|
||||
persons: sorted,
|
||||
originalPersons: sorted
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化剩余天数
|
||||
*/
|
||||
formatDaysUntil(days) {
|
||||
if (days === 0) return '今天'
|
||||
if (days === 1) return '明天'
|
||||
if (days < 7) return `${days}天后`
|
||||
if (days < 30) return `还有${Math.floor(days / 7)}周`
|
||||
return `还有${Math.floor(days / 30)}个月`
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
const keyword = e.detail.value
|
||||
this.setData({ searchKeyword: keyword })
|
||||
this.filterPersons()
|
||||
},
|
||||
|
||||
/**
|
||||
* 筛选切换
|
||||
*/
|
||||
onFilterTap(e) {
|
||||
const filter = e.currentTarget.dataset.filter
|
||||
this.setData({ currentFilter: filter })
|
||||
this.filterPersons()
|
||||
},
|
||||
|
||||
/**
|
||||
* 筛选人员
|
||||
*/
|
||||
filterPersons() {
|
||||
const { originalPersons, searchKeyword, currentFilter } = this.data
|
||||
|
||||
let filtered = [...originalPersons]
|
||||
|
||||
// 关键词搜索
|
||||
if (searchKeyword) {
|
||||
filtered = filtered.filter(p =>
|
||||
p.name.includes(searchKeyword) ||
|
||||
(p.nickname && p.nickname.includes(searchKeyword))
|
||||
)
|
||||
}
|
||||
|
||||
// 类型筛选(暂时保留,后续可以实现更精确的筛选)
|
||||
// if (currentFilter !== 'all') {
|
||||
// // 可以实现更精确的筛选逻辑
|
||||
// }
|
||||
|
||||
this.setData({ persons: filtered })
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击人员
|
||||
*/
|
||||
onPersonTap(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/person-detail/person-detail?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击添加按钮
|
||||
*/
|
||||
onAddTap() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['添加人员', '添加纪念日'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/add-person/add-person'
|
||||
})
|
||||
} else if (res.tapIndex === 1) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/add-anniversary/add-anniversary'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<!--index.wxml-->
|
||||
<view class="container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<input class="search-input" placeholder="搜索姓名" value="{{searchKeyword}}" bindinput="onSearchInput" />
|
||||
</view>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<view class="filter-bar">
|
||||
<text class="filter-label">筛选:</text>
|
||||
<scroll-view class="filter-scroll" scroll-x>
|
||||
<view class="filter-item {{currentFilter === 'all' ? 'active' : ''}}" bindtap="onFilterTap" data-filter="all">全部</view>
|
||||
<view class="filter-item {{currentFilter === 'birthday' ? 'active' : ''}}" bindtap="onFilterTap" data-filter="birthday">生日</view>
|
||||
<view class="filter-item {{currentFilter === 'anniversary' ? 'active' : ''}}" bindtap="onFilterTap" data-filter="anniversary">纪念日</view>
|
||||
<view class="filter-item {{currentFilter === 'upcoming' ? 'active' : ''}}" bindtap="onFilterTap" data-filter="upcoming">即将到来</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 人员列表 -->
|
||||
<scroll-view class="person-list" scroll-y>
|
||||
<view wx:if="{{persons.length === 0}}" class="empty-state">
|
||||
<text class="icon">📅</text>
|
||||
<text class="text">还没有添加任何人</text>
|
||||
<text class="hint">点击右下角 + 添加第一个</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{persons}}" wx:key="id" class="person-card" bindtap="onPersonTap" data-id="{{item.id}}">
|
||||
<view class="person-header">
|
||||
<image class="avatar" src="{{item.avatar || '/images/default-avatar.png'}}" mode="aspectFill" />
|
||||
<view class="person-info">
|
||||
<text class="person-name">{{item.name}}</text>
|
||||
<text wx:if="{{item.nickname}}" class="person-nickname">{{item.nickname}}</text>
|
||||
</view>
|
||||
<view class="person-count">
|
||||
<text wx:if="{{item.anniversaryCount}}" class="count-text">{{item.anniversaryCount}}</text>
|
||||
<text class="count-label">个纪念日</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近的纪念日 -->
|
||||
<view wx:if="{{item.nextAnniversary}}" class="next-anniversary">
|
||||
<text class="next-label">📌 </text>
|
||||
<text class="next-text">{{item.nextAnniversary.type}}: {{item.nextAnniversary.dateText}}</text>
|
||||
<text wx:if="{{item.nextAnniversary.daysUntil}}" class="days-text {{item.nextAnniversary.daysUntil <= 7 ? 'urgent' : ''}}">
|
||||
{{item.nextAnniversary.daysUntilText}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 浮动添加按钮 -->
|
||||
<view class="fab" bindtap="onAddTap">
|
||||
<text>+</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/**index.wxss**/
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-right: 16rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: inline-block;
|
||||
padding: 12rpx 32rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.filter-item.active {
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 人员列表 */
|
||||
.person-list {
|
||||
height: calc(100vh - 200rpx);
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.person-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.person-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 24rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.person-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.person-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.person-nickname {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.person-count {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.count-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #07c160;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.count-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 最近的纪念日 */
|
||||
.next-anniversary {
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.next-label {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.next-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.days-text {
|
||||
color: #07c160;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.days-text.urgent {
|
||||
color: #ff5722;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
padding: 160rpx 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state .icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-state .text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-state .hint {
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 浮动按钮 */
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 120rpx;
|
||||
right: 40rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 60rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
|
||||
z-index: 100;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// person-detail.js
|
||||
const storage = require('../../utils/storage')
|
||||
const dateUtils = require('../../utils/date')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
personId: null,
|
||||
person: {},
|
||||
anniversaries: []
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (!options.id) {
|
||||
wx.showToast({ title: '参数错误', icon: 'none' })
|
||||
wx.navigateBack()
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ personId: options.id })
|
||||
this.loadPerson()
|
||||
this.loadAnniversaries()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 重新加载数据
|
||||
this.loadPerson()
|
||||
this.loadAnniversaries()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载人员信息
|
||||
*/
|
||||
loadPerson() {
|
||||
const person = storage.getPersonById(this.data.personId)
|
||||
if (person) {
|
||||
this.setData({ person })
|
||||
wx.setNavigationBarTitle({ title: person.name })
|
||||
} else {
|
||||
wx.showToast({ title: '人员不存在', icon: 'none' })
|
||||
wx.navigateBack()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载纪念日
|
||||
*/
|
||||
loadAnniversaries() {
|
||||
const anniversaries = storage.getAnniversariesByPersonId(this.data.personId)
|
||||
|
||||
// 格式化纪念日数据
|
||||
const formatted = anniversaries.map(a => {
|
||||
const date = new Date(a.solarYear, a.solarMonth - 1, a.solarDay)
|
||||
const daysUntil = dateUtils.getDaysUntil(date)
|
||||
|
||||
return {
|
||||
...a,
|
||||
date,
|
||||
dateText: dateUtils.formatDate(date, 'YYYY年MM月DD日'),
|
||||
daysUntil,
|
||||
typeIcon: this.getTypeIcon(a.type),
|
||||
typeName: a.customTypeName || this.getTypeName(a.type),
|
||||
importanceText: this.getImportanceText(a.importance)
|
||||
}
|
||||
})
|
||||
|
||||
// 按日期排序
|
||||
formatted.sort((a, b) => a.daysUntil - b.daysUntil)
|
||||
|
||||
this.setData({ anniversaries: formatted })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取类型图标
|
||||
*/
|
||||
getTypeIcon(type) {
|
||||
const icons = {
|
||||
birthday: '🎂',
|
||||
lunar_birthday: '🌙',
|
||||
wedding: '💍',
|
||||
engagement: '💕',
|
||||
other: '📅'
|
||||
}
|
||||
return icons[type] || '📅'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取类型名称
|
||||
*/
|
||||
getTypeName(type) {
|
||||
const names = {
|
||||
birthday: '公历生日',
|
||||
lunar_birthday: '农历生日',
|
||||
wedding: '结婚纪念日',
|
||||
engagement: '订婚纪念日',
|
||||
other: '其他纪念日'
|
||||
}
|
||||
return names[type] || '其他'
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取重要程度文本
|
||||
*/
|
||||
getImportanceText(importance) {
|
||||
const texts = {
|
||||
high: '非常重要',
|
||||
medium: '重要',
|
||||
low: '一般'
|
||||
}
|
||||
return texts[importance] || '一般'
|
||||
},
|
||||
|
||||
/**
|
||||
* 编辑人员
|
||||
*/
|
||||
onEditPerson() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/add-person/add-person?id=${this.data.personId}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除人员
|
||||
*/
|
||||
onDeletePerson() {
|
||||
wx.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除"${this.data.person.name}"吗?相关纪念日也将被删除。`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 先删除相关纪念日
|
||||
const anniversaries = storage.getAnniversariesByPersonId(this.data.personId)
|
||||
anniversaries.forEach(a => storage.deleteAnniversary(a.id))
|
||||
|
||||
// 再删除人员
|
||||
const success = storage.deletePerson(this.data.personId)
|
||||
if (success) {
|
||||
wx.showToast({ title: '删除成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加纪念日
|
||||
*/
|
||||
onAddAnniversary() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/add-anniversary/add-anniversary?personId=${this.data.personId}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 编辑纪念日
|
||||
*/
|
||||
onEditAnniversary(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/add-anniversary/add-anniversary?personId=${this.data.personId}&id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除纪念日
|
||||
*/
|
||||
onDeleteAnniversary(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const anniversary = this.data.anniversaries.find(a => a.id === id)
|
||||
|
||||
wx.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条纪念日吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const success = storage.deleteAnniversary(id)
|
||||
if (success) {
|
||||
wx.showToast({ title: '删除成功', icon: 'success' })
|
||||
this.loadAnniversaries()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<!--person-detail.wxml-->
|
||||
<view class="container">
|
||||
<!-- 人员信息卡片 -->
|
||||
<view class="person-card">
|
||||
<view class="person-header">
|
||||
<image class="avatar" src="{{person.avatar || '/images/default-avatar.png'}}" mode="aspectFill" />
|
||||
<view class="person-info">
|
||||
<text class="person-name">{{person.name}}</text>
|
||||
<text wx:if="{{person.nickname}}" class="person-nickname">{{person.nickname}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text wx:if="{{person.remark}}" class="person-remark">{{person.remark}}</text>
|
||||
|
||||
<view class="actions">
|
||||
<button class="action-btn" bindtap="onEditPerson">编辑</button>
|
||||
<button class="action-btn danger" bindtap="onDeletePerson">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 纪念日列表 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">纪念日列表 ({{anniversaries.length}})</text>
|
||||
<button class="add-btn" bindtap="onAddAnniversary">+ 添加纪念日</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{anniversaries.length === 0}}" class="empty-state">
|
||||
<text class="icon">📅</text>
|
||||
<text class="text">还没有添加纪念日</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{anniversaries}}" wx:key="id" class="anniversary-card">
|
||||
<view class="anniversary-header">
|
||||
<view class="anniversary-type">
|
||||
<text class="type-icon">{{item.typeIcon}}</text>
|
||||
<text class="type-text">{{item.typeName}}</text>
|
||||
</view>
|
||||
<text class="importance-badge importance-{{item.importance}}">{{item.importanceText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="anniversary-date">
|
||||
<text class="date-label">日期:</text>
|
||||
<text class="date-text">{{item.dateText}}</text>
|
||||
<text wx:if="{{item.isLunar}}" class="lunar-badge">农历</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{item.daysUntil !== undefined}}" class="days-info">
|
||||
<text wx:if="{{item.daysUntil === 0}}" class="days-text urgent">今天</text>
|
||||
<text wx:else-if="{{item.daysUntil === 1}}" class="days-text warning">明天</text>
|
||||
<text wx:else-if="{{item.daysUntil > 0}}" class="days-text">{{item.daysUntil}}天后</text>
|
||||
<text wx:else class="days-text past">已过{{Math.abs(item.daysUntil)}}天</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{item.remark}}" class="anniversary-remark">{{item.remark}}</view>
|
||||
|
||||
<view class="anniversary-actions">
|
||||
<button class="edit-btn" bindtap="onEditAnniversary" data-id="{{item.id}}">编辑</button>
|
||||
<button class="delete-btn" bindtap="onDeleteAnniversary" data-id="{{item.id}}">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
/**person-detail.wxss**/
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.person-card {
|
||||
background-color: #fff;
|
||||
margin: 32rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.person-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 32rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.person-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.person-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.person-nickname {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.person-remark {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 24rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.danger {
|
||||
background-color: #fff3f0;
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.action-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 纪念日列表 */
|
||||
.section {
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.add-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 纪念日卡片 */
|
||||
.anniversary-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.anniversary-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.anniversary-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.type-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.importance-badge {
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.importance-high {
|
||||
background-color: #fff3f0;
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.importance-medium {
|
||||
background-color: #fff8e6;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.importance-low {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.anniversary-date {
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.date-label {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.lunar-badge {
|
||||
display: inline-block;
|
||||
padding: 4rpx 12rpx;
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
border-radius: 4rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.days-info {
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
|
||||
.days-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.days-text.urgent {
|
||||
color: #ff5722;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.days-text.warning {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.days-text.past {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.anniversary-remark {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
margin: 16rpx 0;
|
||||
padding: 16rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.anniversary-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 24rpx;
|
||||
padding-top: 24rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.edit-btn,
|
||||
.delete-btn {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #fff3f0;
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.edit-btn::after,
|
||||
.delete-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 120rpx 40rpx;
|
||||
}
|
||||
|
||||
.empty-state .icon {
|
||||
font-size: 120rpx;
|
||||
display: block;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-state .text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// settings.js
|
||||
const storage = require('../../utils/storage')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
dataCount: {
|
||||
persons: 0,
|
||||
anniversaries: 0
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadDataCount()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadDataCount()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据统计
|
||||
*/
|
||||
loadDataCount() {
|
||||
const persons = storage.getPersons()
|
||||
const anniversaries = storage.getAnniversaries()
|
||||
|
||||
this.setData({
|
||||
dataCount: {
|
||||
persons: persons.length,
|
||||
anniversaries: anniversaries.length
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出数据
|
||||
*/
|
||||
onExportData() {
|
||||
const data = storage.exportData()
|
||||
|
||||
// 转换为JSON字符串
|
||||
const jsonStr = JSON.stringify(data, null, 2)
|
||||
|
||||
// 在小程序中,可以通过提示用户复制
|
||||
wx.setClipboardData({
|
||||
data: jsonStr,
|
||||
success: () => {
|
||||
wx.showToast({
|
||||
title: '数据已复制到剪贴板',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 导入数据
|
||||
*/
|
||||
onImportData() {
|
||||
wx.showModal({
|
||||
title: '导入数据',
|
||||
content: '将从剪贴板读取数据并导入。注意:导入会覆盖现有数据,请先备份!',
|
||||
confirmText: '确认导入',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 读取剪贴板
|
||||
wx.getClipboardData({
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
const success = storage.importData(data)
|
||||
|
||||
if (success) {
|
||||
wx.showToast({
|
||||
title: '导入成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '导入失败,请检查数据格式',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({
|
||||
title: '数据格式错误',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空所有数据
|
||||
*/
|
||||
onClearData() {
|
||||
wx.showModal({
|
||||
title: '确认清空',
|
||||
content: '确定要清空所有数据吗?此操作不可恢复!',
|
||||
confirmText: '确认清空',
|
||||
confirmColor: '#ff5722',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const success = storage.clearAllData()
|
||||
|
||||
if (success) {
|
||||
wx.showToast({
|
||||
title: '已清空',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--settings.wxml-->
|
||||
<view class="container">
|
||||
<view class="settings-list">
|
||||
<!-- 数据备份 -->
|
||||
<view class="setting-section">
|
||||
<text class="section-title">数据管理</text>
|
||||
|
||||
<view class="setting-item" bindtap="onExportData">
|
||||
<text class="item-label">📤 导出数据</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="setting-item" bindtap="onImportData">
|
||||
<text class="item-label">📥 导入数据</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="setting-item" bindtap="onClearData">
|
||||
<text class="item-label danger">🗑️ 清空所有数据</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关于 -->
|
||||
<view class="setting-section">
|
||||
<text class="section-title">关于</text>
|
||||
|
||||
<view class="setting-item">
|
||||
<text class="item-label">版本号</text>
|
||||
<text class="item-value">v1.0.0</text>
|
||||
</view>
|
||||
|
||||
<view class="setting-item">
|
||||
<text class="item-label">开发作者</text>
|
||||
<text class="item-value">生日提醒团队</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view class="info-box">
|
||||
<text class="info-title">💡 温馨提示</text>
|
||||
<text class="info-text">1. 建议定期导出数据备份\n2. 农历转换目前使用简化算法\n3. 提醒功能需要小程序权限</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**settings.wxss**/
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.settings-list {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 32rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #f9f9f9;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-label.danger {
|
||||
color: #ff5722;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 2;
|
||||
white-space: pre-line;
|
||||
display: block;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user