commit 1a6280c998326b4a33e2a557d2687a889b1dd27f
Author: wangsai <1534599297@qq.com>
Date: Wed Sep 24 10:23:37 2025 +0800
初始提交
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..7c72f39
--- /dev/null
+++ b/app.js
@@ -0,0 +1,43 @@
+App({
+ onLaunch() {
+ this.checkLoginStatus()
+ // 初始化本地存储
+ if (!wx.getStorageSync('records')) {
+ wx.setStorageSync('records', []);
+ }
+
+ const defaultCategories = [
+ { id: 1, name: '餐饮', type: 'out', icon: 'cutlery' },
+ { id: 2, name: '交通', type: 'out', icon: 'car' },
+ { id: 3, name: '购物', type: 'out', icon: 'shopping-cart' },
+ { id: 4, name: '娱乐', type: 'out', icon: 'film' },
+ { id: 5, name: '工资', type: 'in', icon: 'money' },
+ { id: 6, name: '奖金', type: 'in', icon: 'gift' }
+ ];
+ wx.setStorageSync('categories', defaultCategories);
+ },
+ // 检查登录状态
+ checkLoginStatus() {
+ // 从本地存储获取token
+ const token = wx.getStorageSync('token')
+ // const tokenExpire = wx.getStorageSync('tokenExpire') // 过期时间(可选)
+
+ // // 检查token是否存在且未过期
+ // const isLogin = token && (!tokenExpire || Date.now() < tokenExpire)
+
+ if (token) {
+ // 已登录,跳转首页
+ wx.switchTab({
+ url: '/pages/index/index' // 首页路径
+ })
+ } else {
+ // 未登录或token过期,跳转登录页
+ wx.redirectTo({
+ url: '/pages/login/login' // 登录页路径
+ })
+ }
+ },
+ globalData: {
+ userInfo: null
+ }
+})
diff --git a/app.json b/app.json
new file mode 100644
index 0000000..fdca928
--- /dev/null
+++ b/app.json
@@ -0,0 +1,49 @@
+{
+ "pages": [
+ "pages/login/login",
+ "pages/index/index",
+ "pages/add/add",
+ "pages/statistics/statistics",
+ "pages/category/category",
+ "pages/profile/profile"
+ ],
+ "window": {
+ "backgroundTextStyle": "light",
+ "navigationBarBackgroundColor": "#4CAF50",
+ "navigationBarTitleText": "记账账啊",
+ "navigationBarTextStyle": "white"
+ },
+ "tabBar": {
+ "color": "#7A7E83",
+ "selectedColor": "#4CAF50",
+ "borderStyle": "black",
+ "backgroundColor": "#ffffff",
+ "list": [
+ {
+ "pagePath": "pages/index/index",
+ "text": "首页",
+ "iconPath": "/images/icon/home.png",
+ "selectedIconPath": "/images/icon/home-selected.png"
+ },
+ {
+ "pagePath": "pages/add/add",
+ "text": "记账",
+ "iconPath": "/images/icon/add.png",
+ "selectedIconPath": "/images/icon/add-selected.png"
+ },
+ {
+ "pagePath": "pages/statistics/statistics",
+ "text": "统计",
+ "iconPath": "/images/icon/chart.png",
+ "selectedIconPath": "/images/icon/chart-selected.png"
+ },
+ {
+ "pagePath": "pages/profile/profile",
+ "text": "我的",
+ "iconPath": "/images/icon/user.png",
+ "selectedIconPath": "/images/icon/user-selected.png"
+ }
+ ]
+ },
+ "sitemapLocation": "sitemap.json"
+}
diff --git a/images/app-logo.png b/images/app-logo.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/app-logo.png differ
diff --git a/images/icon/add-selected.png b/images/icon/add-selected.png
new file mode 100644
index 0000000..7288211
Binary files /dev/null and b/images/icon/add-selected.png differ
diff --git a/images/icon/add-selected2.png b/images/icon/add-selected2.png
new file mode 100644
index 0000000..9352905
Binary files /dev/null and b/images/icon/add-selected2.png differ
diff --git a/images/icon/add.png b/images/icon/add.png
new file mode 100644
index 0000000..7288211
Binary files /dev/null and b/images/icon/add.png differ
diff --git a/images/icon/add1.png b/images/icon/add1.png
new file mode 100644
index 0000000..9352905
Binary files /dev/null and b/images/icon/add1.png differ
diff --git a/images/icon/char1t.png b/images/icon/char1t.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/icon/char1t.png differ
diff --git a/images/icon/chart-selected.png b/images/icon/chart-selected.png
new file mode 100644
index 0000000..9e89c5e
Binary files /dev/null and b/images/icon/chart-selected.png differ
diff --git a/images/icon/chart-selected11.png b/images/icon/chart-selected11.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/icon/chart-selected11.png differ
diff --git a/images/icon/chart.png b/images/icon/chart.png
new file mode 100644
index 0000000..9e89c5e
Binary files /dev/null and b/images/icon/chart.png differ
diff --git a/images/icon/home-selected.png b/images/icon/home-selected.png
new file mode 100644
index 0000000..0bfb67d
Binary files /dev/null and b/images/icon/home-selected.png differ
diff --git a/images/icon/home-selected2.png b/images/icon/home-selected2.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/icon/home-selected2.png differ
diff --git a/images/icon/home.png b/images/icon/home.png
new file mode 100644
index 0000000..0bfb67d
Binary files /dev/null and b/images/icon/home.png differ
diff --git a/images/icon/home1.png b/images/icon/home1.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/icon/home1.png differ
diff --git a/images/icon/user-selected.png b/images/icon/user-selected.png
new file mode 100644
index 0000000..3d40a39
Binary files /dev/null and b/images/icon/user-selected.png differ
diff --git a/images/icon/user-selected1.png b/images/icon/user-selected1.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/icon/user-selected1.png differ
diff --git a/images/icon/user.png b/images/icon/user.png
new file mode 100644
index 0000000..3d40a39
Binary files /dev/null and b/images/icon/user.png differ
diff --git a/images/icon/user1.png b/images/icon/user1.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/icon/user1.png differ
diff --git a/images/wechat.png b/images/wechat.png
new file mode 100644
index 0000000..bacb4bd
Binary files /dev/null and b/images/wechat.png differ
diff --git a/pages/add/add.js b/pages/add/add.js
new file mode 100644
index 0000000..6b83d36
--- /dev/null
+++ b/pages/add/add.js
@@ -0,0 +1,215 @@
+const request = require('../utils/request')
+Page({
+ data: {
+ type: 'out', // 默认为支出
+ amount: '',
+ selectedCategoryId: null,
+ categories: [],
+ filteredCategories: [],
+ note: '',
+ useDate: '',
+ maxDate: '',
+// 控制弹窗显示
+showPopup: false,
+
+// 一级分类数据
+firstLevelCategories: [
+
+],
+
+// 二级分类数据
+secondLevelCategories: [
+
+],
+
+// 选中状态
+selectedFirstLevelId: null,
+selectedSecondLevelId: null,
+selectedCategoryName: '',
+filteredSecondLevelCategories: []
+ },
+// 显示分类选择弹窗
+showCategoryPopup() {
+ this.setData({
+ showPopup: true
+ });
+},
+
+// 隐藏分类选择弹窗
+hideCategoryPopup() {
+ this.setData({
+ showPopup: false
+ });
+},
+
+// 选择一级分类
+selectFirstLevel(e) {
+ const parentId = e.currentTarget.dataset.id;
+
+ // 筛选对应的二级分类
+ const filteredSeconds = this.data.secondLevelCategories.filter(
+ item => item.parentId == parentId
+ );
+
+ this.setData({
+ selectedFirstLevelId: parentId,
+ filteredSecondLevelCategories: filteredSeconds,
+ // 重置二级分类选中状态
+ selectedSecondLevelId: null
+ });
+},
+
+// 选择二级分类
+selectSecondLevel(e) {
+ const secondLevelId = e.currentTarget.dataset.id;
+ const secondLevelName = e.currentTarget.dataset.name;
+
+ // 如果点击的是已选中的,则取消选中
+ if (this.data.selectedSecondLevelId === secondLevelId) {
+ this.setData({
+ selectedSecondLevelId: null
+ });
+ } else {
+ this.setData({
+ selectedSecondLevelId: secondLevelId,
+ selectedCategoryName: secondLevelName
+ });
+ }
+},
+
+// 确认选择
+confirmSelection() {
+ if (this.data.selectedSecondLevelId) {
+ // 这里可以处理选中后的逻辑
+ console.log('选中的二级分类ID:', this.data.selectedSecondLevelId);
+ console.log('选中的二级分类名称:', this.data.selectedCategoryName);
+
+ // 关闭弹窗
+ this.hideCategoryPopup();
+ }
+},
+async getClassification() {
+ const res=await request.get('/admin-api/book/classification/oneTwoLevelList',{})
+ console.log('resss:',res)
+ if(res.code==0){
+ this.setData(
+ {
+ firstLevelCategories:res.data.outOne,
+ secondLevelCategories:res.data.outTwo,
+ categories:res.data.inOne,
+ filteredCategories: res.data.inOne
+ }
+ )
+ }
+},
+onShow(){
+ this.getClassification()
+},
+ onLoad() {
+ // 获取当前日期
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, '0');
+ const day = String(today.getDate()).padStart(2, '0');
+ const currentDate = `${year}-${month}-${day}`;
+
+ this.setData({
+ useDate: currentDate,
+ maxDate: currentDate
+ });
+
+
+ },
+
+ setType(e) {
+ const type = e.currentTarget.dataset.type;
+ this.setData({
+ type,
+ selectedCategoryId: null
+ });
+ console.log('type:',type)
+ },
+
+ onAmountChange(e) {
+ let amount = e.detail.value;
+
+ // 处理金额输入格式
+ if (amount) {
+ // 只保留数字和一个小数点
+ amount = amount.replace(/[^\d.]/g, '');
+ // 确保只有一个小数点
+ const dotIndex = amount.indexOf('.');
+ if (dotIndex !== -1) {
+ amount = amount.substring(0, dotIndex + 3); // 限制两位小数
+ }
+ }
+
+ this.setData({ amount });
+ },
+
+ selectCategory(e) {
+ const categoryId = e.currentTarget.dataset.id;
+ this.setData({ selectedCategoryId: categoryId });
+ },
+
+ onNoteChange(e) {
+ this.setData({ note: e.detail.value });
+ },
+
+ onDateChange(e) {
+ this.setData({ useDate: e.detail.value });
+ },
+
+ async saveRecord() {
+ if (!this.data.amount ) {
+ wx.showToast({
+ title: '请填写金额和选择分类',
+ icon: 'none',
+ duration: 2000
+ });
+ return;
+ }
+
+
+
+ // 创建新记录
+ const newRecord = {
+ type: this.data.type,
+ money: parseFloat(parseFloat(this.data.amount).toFixed(2)),
+ classId: this.data.type=='out'?this.data.selectedSecondLevelId:this.data.selectedCategoryId,
+
+ remark: this.data.note,
+ useDate: this.data.useDate
+ };
+ // 自动携带token
+ const res = await request.post('/admin-api/book/inout/create',newRecord)
+ console.log("新增结果:",res)
+ if(res.code==0){
+ // 显示成功提示
+ wx.showToast({
+ title: '记录保存成功',
+ icon: 'success',
+ duration: 1500
+ });
+ this.setData({
+ amount: '',
+ selectedCategoryId: '',
+ note: '',
+ selectedSecondLevelId: null,
+ });
+ console.log('this.data:',this.data)
+ }else{
+ wx.showToast({
+ title: '记录保存失败',
+ icon: 'none',
+ duration: 2000
+ });
+ }
+ // 返回首页
+ setTimeout(() => {
+ wx.navigateBack({
+ delta: 1
+ });
+ }, 1500);
+ }
+})
diff --git a/pages/add/add.wxml b/pages/add/add.wxml
new file mode 100644
index 0000000..03bd807
--- /dev/null
+++ b/pages/add/add.wxml
@@ -0,0 +1,142 @@
+
+
+
+
+
+ 支出
+
+
+
+ 收入
+
+
+
+
+
+ ¥
+
+
+
+
+
+ 选择分类
+
+
+
+ {{item.classificationName}}
+
+
+
+
+
+
+
+ 选择分类
+
+ {{selectedCategoryName || '请选择分类'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 备注 (可选)
+
+
+
+
+
+ 日期
+
+
+
+ {{useDate}}
+
+
+
+
+
+
+
diff --git a/pages/add/add.wxss b/pages/add/add.wxss
new file mode 100644
index 0000000..cb19886
--- /dev/null
+++ b/pages/add/add.wxss
@@ -0,0 +1,313 @@
+.container {
+ padding: 20px 15px;
+ background-color: #f5f5f5;
+ min-height: 100vh;
+}
+
+.type-selector {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 30px;
+}
+
+.type-btn {
+ width: 120px;
+ height: 50px;
+ border-radius: 25px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 10px;
+ font-size: 18px;
+ font-weight: 500;
+ border: 2px solid #ddd;
+ transition: all 0.3s;
+}
+
+.type-btn.active {
+ border-color: #4CAF50;
+ color: #4CAF50;
+}
+
+.type-btn .iconfont {
+ margin-right: 8px;
+ font-size: 20px;
+}
+
+.amount-input {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 40px;
+}
+
+.currency-symbol {
+ font-size: 36px;
+ color: #333;
+ margin-right: 10px;
+}
+
+.amount-input input {
+ font-size: 48px;
+ text-align: left;
+ color: #333;
+ width: 250px;
+ padding: 0;
+ height: auto;
+}
+
+.section-title {
+ display: block;
+ font-size: 16px;
+ color: #666;
+ margin-bottom: 15px;
+}
+
+.category-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-bottom: 30px;
+}
+
+.category-item {
+ width: 70px;
+ height: 70px;
+ border-radius: 10px;
+ background-color: white;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ transition: all 0.2s;
+}
+
+.category-item .iconfont {
+ font-size: 24px;
+ margin-bottom: 5px;
+ color: #666;
+}
+
+.category-item.selected {
+ background-color: #e8f5e9;
+ color: #4CAF50;
+}
+
+.category-item.selected .iconfont {
+ color: #4CAF50;
+}
+
+.note-section, .date-section {
+ margin-bottom: 30px;
+}
+
+.note-section input, .date-picker {
+ width: 100%;
+ height: 50px;
+ background-color: white;
+ border-radius: 10px;
+ padding: 0 15px;
+ font-size: 16px;
+ box-sizing: border-box;
+}
+
+.date-picker {
+ display: flex;
+ align-items: center;
+ color: #333;
+}
+
+.date-picker .iconfont {
+ margin-right: 10px;
+ color: #999;
+}
+
+.save-btn {
+ width: 100%;
+ height: 55px;
+ background-color: #4CAF50;
+ color: white;
+ border-radius: 27.5px;
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 55px;
+ margin-top: 20px;
+}
+
+.save-btn[disabled] {
+ background-color: #a5d6a7;
+ color: #e8f5e9;
+}
+
+/************************/
+/* 触发区域样式 */
+.category-trigger {
+ margin-bottom: 30px;
+
+
+}
+
+.note-section, .date-section {
+ margin-bottom: 30px;
+}
+
+.section-title {
+ font-size: 34rpx;
+ font-weight: 600;
+ color: #333;
+ display: block;
+ margin-bottom: 20rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.selected-value {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20rpx;
+ background-color: #f9f9f9;
+ border-radius: 12rpx;
+ font-size: 28rpx;
+ color: #666;
+}
+
+/* 遮罩层样式 */
+.category-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 998;
+}
+
+/* 弹窗样式 */
+.category-popup {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #fff;
+ border-top-left-radius: 24rpx;
+ border-top-right-radius: 24rpx;
+ z-index: 999;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* 弹窗头部 */
+.popup-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 24rpx 30rpx;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.popup-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #333;
+}
+
+/* 弹窗内容区 */
+.popup-content {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+/* 一级分类列表 */
+.first-level-list {
+ width: 35%;
+ background-color: #f9f9f9;
+ overflow-y: auto;
+}
+
+.first-level-item {
+ display: flex;
+ align-items: center;
+ padding: 28rpx 20rpx;
+ font-size: 28rpx;
+ color: #555;
+ border-left: 6rpx solid transparent;
+}
+
+.first-level-item.selected {
+ background-color: #fff;
+ color: #387ef5;
+ border-left-color: #387ef5;
+ font-weight: 500;
+}
+
+.first-level-item .iconfont {
+ font-size: 36rpx;
+ margin-right: 16rpx;
+ width: 40rpx;
+ text-align: center;
+}
+
+/* 二级分类列表 */
+.second-level-list {
+ width: 65%;
+ padding: 20rpx;
+ overflow-y: auto;
+}
+
+.no-selection {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #999;
+ font-size: 28rpx;
+}
+
+.second-level-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 22rpx 20rpx;
+ margin-bottom: 8rpx;
+ background-color: #f5f5f5;
+ border-radius: 12rpx;
+ font-size: 26rpx;
+ color: #555;
+}
+
+.second-level-item.selected {
+ background-color: #edf4ff;
+ color: #387ef5;
+}
+
+.second-level-item .icon-check {
+ font-size: 28rpx;
+}
+
+/* 弹窗底部 */
+.popup-footer {
+ padding: 20rpx 30rpx;
+ border-top: 1px solid #f5f5f5;
+}
+
+.confirm-btn {
+ width: 100%;
+ padding: 20rpx 0;
+ background-color: #387ef5;
+ color: #fff;
+ font-size: 30rpx;
+ border-radius: 12rpx;
+ line-height: normal;
+}
+
+.confirm-btn:disabled {
+ background-color: #b3d1ff;
+ color: #e6f0ff;
+}
+
+
\ No newline at end of file
diff --git a/pages/category/category.js b/pages/category/category.js
new file mode 100644
index 0000000..f6fa8ed
--- /dev/null
+++ b/pages/category/category.js
@@ -0,0 +1,157 @@
+Page({
+ data: {
+ activeType: 'expense',
+ categories: [],
+ filteredCategories: [],
+ showModal: false,
+ isEditing: false,
+ editingId: null,
+ categoryName: '',
+ selectedIcon: '',
+ icons: [
+ 'cutlery', 'car', 'shopping-cart', 'home', 'gift', 'money',
+ 'book', 'film', 'plane', 'medkit', 'coffee', 'shirt',
+ 'graduation-cap', 'briefcase', 'gamepad', 'heart', 'pet'
+ ]
+ },
+
+ onLoad() {
+ this.loadCategories();
+ },
+
+ loadCategories() {
+ const categories = wx.getStorageSync('categories') || [];
+ this.setData({
+ categories,
+ filteredCategories: categories.filter(cat => cat.type === this.data.activeType)
+ });
+ },
+
+ setActiveType(e) {
+ const type = e.currentTarget.dataset.type;
+ this.setData({
+ activeType: type,
+ filteredCategories: this.data.categories.filter(cat => cat.type === type)
+ });
+ },
+
+ addCategory() {
+ this.setData({
+ showModal: true,
+ isEditing: false,
+ editingId: null,
+ categoryName: '',
+ selectedIcon: this.data.icons[0]
+ });
+ },
+
+ editCategory(e) {
+ const categoryId = e.currentTarget.dataset.id;
+ const category = this.data.categories.find(cat => cat.id === categoryId);
+
+ if (category) {
+ this.setData({
+ showModal: true,
+ isEditing: true,
+ editingId: categoryId,
+ categoryName: category.name,
+ selectedIcon: category.icon
+ });
+ }
+ },
+
+ deleteCategory(e) {
+ const categoryId = e.currentTarget.dataset.id;
+
+ wx.showModal({
+ title: '确认删除',
+ content: '删除分类后,该分类下的记录将不受影响,是否继续?',
+ cancelText: '取消',
+ confirmText: '删除',
+ success: (res) => {
+ if (res.confirm) {
+ let categories = this.data.categories;
+ categories = categories.filter(cat => cat.id !== categoryId);
+
+ wx.setStorageSync('categories', categories);
+ this.loadCategories();
+
+ wx.showToast({
+ title: '分类已删除',
+ icon: 'success',
+ duration: 1500
+ });
+ }
+ }
+ });
+ },
+
+ hideModal() {
+ this.setData({ showModal: false });
+ },
+
+ onNameChange(e) {
+ this.setData({ categoryName: e.detail.value });
+ },
+
+ selectIcon(e) {
+ const icon = e.currentTarget.dataset.icon;
+ this.setData({ selectedIcon: icon });
+ },
+
+ saveCategory() {
+ const { categoryName, selectedIcon, isEditing, editingId, activeType, categories } = this.data;
+
+ if (!categoryName.trim()) {
+ wx.showToast({
+ title: '请输入分类名称',
+ icon: 'none',
+ duration: 2000
+ });
+ return;
+ }
+
+ if (!selectedIcon) {
+ wx.showToast({
+ title: '请选择图标',
+ icon: 'none',
+ duration: 2000
+ });
+ return;
+ }
+
+ let updatedCategories = [...categories];
+
+ if (isEditing) {
+ // 编辑现有分类
+ const index = updatedCategories.findIndex(cat => cat.id === editingId);
+ if (index !== -1) {
+ updatedCategories[index] = {
+ ...updatedCategories[index],
+ name: categoryName,
+ icon: selectedIcon
+ };
+ }
+ } else {
+ // 添加新分类
+ const newId = Date.now();
+ updatedCategories.push({
+ id: newId,
+ name: categoryName,
+ type: activeType,
+ icon: selectedIcon
+ });
+ }
+
+ // 保存分类
+ wx.setStorageSync('categories', updatedCategories);
+ this.loadCategories();
+ this.hideModal();
+
+ wx.showToast({
+ title: isEditing ? '分类已更新' : '分类已添加',
+ icon: 'success',
+ duration: 1500
+ });
+ }
+})
diff --git a/pages/category/category.wxml b/pages/category/category.wxml
new file mode 100644
index 0000000..6ef32e3
--- /dev/null
+++ b/pages/category/category.wxml
@@ -0,0 +1,75 @@
+
+
+ 支出分类
+ 收入分类
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+ 暂无分类,请添加
+
+
+
+
+
+
+
+
+ {{isEditing ? '编辑分类' : '添加分类'}}
+
+
+
+ 分类名称
+
+
+
+
+ 选择图标
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/category/category.wxss b/pages/category/category.wxss
new file mode 100644
index 0000000..bc5ab0e
--- /dev/null
+++ b/pages/category/category.wxss
@@ -0,0 +1,230 @@
+.container {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ padding-bottom: 80px;
+}
+
+.type-tabs {
+ display: flex;
+ background-color: white;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.tab {
+ flex: 1;
+ text-align: center;
+ padding: 15px 0;
+ font-size: 16px;
+ color: #666;
+ position: relative;
+}
+
+.tab.active {
+ color: #4CAF50;
+}
+
+.tab.active::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 3px;
+ background-color: #4CAF50;
+}
+
+.category-list {
+ margin: 15px;
+}
+
+.category-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: white;
+ border-radius: 10px;
+ padding: 15px;
+ margin-bottom: 10px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+}
+
+.category-info {
+ display: flex;
+ align-items: center;
+}
+
+.category-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background-color: #e8f5e9;
+ color: #4CAF50;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+}
+
+.category-icon .iconfont {
+ font-size: 20px;
+}
+
+.category-name {
+ font-size: 16px;
+ color: #333;
+}
+
+.category-actions {
+ display: flex;
+}
+
+.edit-btn, .delete-btn {
+ padding: 5px 12px;
+ font-size: 14px;
+ border-radius: 4px;
+ margin-left: 10px;
+ height: auto;
+ line-height: normal;
+}
+
+.edit-btn {
+ color: #4CAF50;
+ background-color: #e8f5e9;
+}
+
+.delete-btn {
+ color: #f44336;
+ background-color: #ffebee;
+}
+
+.no-categories {
+ text-align: center;
+ padding: 40px 0;
+ color: #999;
+ font-size: 14px;
+ background-color: white;
+ border-radius: 10px;
+}
+
+.add-btn {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 90%;
+ height: 50px;
+ background-color: #4CAF50;
+ color: white;
+ border-radius: 25px;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
+}
+
+.add-btn .iconfont {
+ margin-right: 8px;
+}
+
+/* 弹窗样式 */
+.modal-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+}
+
+.modal-dialog {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: white;
+ border-top-left-radius: 15px;
+ border-top-right-radius: 15px;
+ padding: 20px;
+ z-index: 1001;
+}
+
+.modal-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.form-item {
+ margin-bottom: 20px;
+}
+
+.form-label {
+ display: block;
+ font-size: 14px;
+ color: #666;
+ margin-bottom: 10px;
+}
+
+.form-item input {
+ width: 100%;
+ height: 45px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 0 15px;
+ font-size: 16px;
+ box-sizing: border-box;
+}
+
+.icon-selector {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ max-height: 200px;
+ overflow-y: auto;
+}
+
+.icon-item {
+ width: 50px;
+ height: 50px;
+ border-radius: 8px;
+ background-color: #f5f5f5;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.icon-item.selected {
+ background-color: #e8f5e9;
+ color: #4CAF50;
+}
+
+.icon-item .iconfont {
+ font-size: 24px;
+}
+
+.modal-footer {
+ display: flex;
+ margin-top: 10px;
+}
+
+.cancel-btn, .confirm-btn {
+ flex: 1;
+ height: 45px;
+ border-radius: 8px;
+ font-size: 16px;
+ margin: 0 5px;
+}
+
+.cancel-btn {
+ background-color: #f5f5f5;
+ color: #666;
+}
+
+.confirm-btn {
+ background-color: #4CAF50;
+ color: white;
+}
diff --git a/pages/index/index.js b/pages/index/index.js
new file mode 100644
index 0000000..99c7f11
--- /dev/null
+++ b/pages/index/index.js
@@ -0,0 +1,69 @@
+const request = require('../utils/request')
+Page({
+ data: {
+ currentMonth: '',
+ totalBalance: '0.00',
+ totalIncome: '0.00',
+ totalExpense: '0.00',
+ recentRecords: []
+ },
+
+ onShow() {
+ this.updateData();
+ },
+
+ async updateData() {
+ // 更新当前月份
+ const date = new Date();
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ this.setData({
+ currentMonth: `${year}年${month}月`
+ });
+
+ // 获取所有记录
+ let records = [];
+
+ // 筛选本月记录
+ const currentMonthRecords = records.filter(record => {
+ const recordDate = new Date(record.date);
+ return recordDate.getFullYear() === year && recordDate.getMonth() + 1 === month;
+ });
+
+ // 计算收支和余额
+ let totalIncome = 0;
+ let totalExpense = 0;
+
+ currentMonthRecords.forEach(record => {
+ if (record.type === 'income') {
+ totalIncome += parseFloat(record.amount);
+ } else {
+ totalExpense += parseFloat(record.amount);
+ }
+ });
+
+ const totalBalance = totalIncome - totalExpense;
+
+ try {
+ // 自动携带token
+ const res = await request.get('/admin-api/book/inout/myList?pageNo=1&pageSize=10')
+ console.log('获取数据成功:', res)
+ if(res.code==0){
+ records=res.data.list
+ }
+ // 处理数据...
+ } catch (error) {
+ console.error('加载失败:', error)
+ }
+ console.log('records:',records)
+ this.setData({
+ totalBalance: totalBalance.toFixed(2),
+ totalIncome: totalIncome.toFixed(2),
+ totalExpense: totalExpense.toFixed(2),
+ // 按日期排序,取最近10条
+ recentRecords: [...records]
+ // .sort((a, b) => new Date(b.date) - new Date(a.date))
+ .slice(0, 10)
+ });
+ }
+})
diff --git a/pages/index/index.wxml b/pages/index/index.wxml
new file mode 100644
index 0000000..9a679dd
--- /dev/null
+++ b/pages/index/index.wxml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+ 收入
+ ¥ {{totalIncome}}
+
+
+
+ 支出
+ ¥ {{totalExpense}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.categoryName}}
+ {{item.useDate}}
+
+
+ {{item.type === 'in' ? '+' : '-'}}¥ {{item.money}}
+
+
+
+
+ 暂无记录,开始添加吧~
+
+
+
+
diff --git a/pages/index/index.wxss b/pages/index/index.wxss
new file mode 100644
index 0000000..50860d4
--- /dev/null
+++ b/pages/index/index.wxss
@@ -0,0 +1,165 @@
+.container {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ padding-bottom: 60px;
+}
+
+.balance-card {
+ background: linear-gradient(135deg, #4CAF50 0%, #8BC34A 100%);
+ color: white;
+ padding: 20px 15px;
+ border-radius: 15px;
+ margin: 15px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.balance-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+}
+
+.balance-title {
+ font-size: 16px;
+ opacity: 0.9;
+}
+
+.balance-date {
+ font-size: 14px;
+ opacity: 0.8;
+}
+
+.balance-amount {
+ font-size: 32px;
+ font-weight: bold;
+ margin-bottom: 20px;
+}
+
+.balance-stats {
+ display: flex;
+ justify-content: space-around;
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+ padding-top: 15px;
+}
+
+.balance-item {
+ text-align: center;
+}
+
+.income-label, .expense-label {
+ font-size: 14px;
+ opacity: 0.8;
+ display: block;
+ margin-bottom: 5px;
+}
+
+.income-amount, .expense-amount {
+ font-size: 18px;
+ font-weight: 500;
+}
+
+.balance-divider {
+ width: 1px;
+ background-color: rgba(255, 255, 255, 0.2);
+}
+
+.records-section {
+ margin: 15px;
+ background-color: white;
+ border-radius: 15px;
+ padding: 15px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.section-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+}
+
+.see-all {
+ font-size: 14px;
+ color: #4CAF50;
+}
+
+.records-list {
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.record-item {
+ display: flex;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.record-item:last-child {
+ border-bottom: none;
+}
+
+.record-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+}
+
+.income-icon {
+ background-color: #e8f5e9;
+ color: #4CAF50;
+}
+
+.expense-icon {
+ background-color: #ffebee;
+ color: #f44336;
+}
+
+.iconfont {
+ font-size: 20px;
+}
+
+.record-details {
+ flex: 1;
+}
+
+.record-title {
+ font-size: 16px;
+ color: #333;
+ margin-bottom: 3px;
+}
+
+.record-date {
+ font-size: 12px;
+ color: #999;
+}
+
+.record-amount {
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.income {
+ color: #4CAF50;
+}
+
+.expense {
+ color: #f44336;
+}
+
+.no-records {
+ text-align: center;
+ padding: 40px 0;
+ color: #999;
+ font-size: 14px;
+}
diff --git a/pages/launch/launch.js b/pages/launch/launch.js
new file mode 100644
index 0000000..cb157a6
--- /dev/null
+++ b/pages/launch/launch.js
@@ -0,0 +1,9 @@
+{
+ onLoad() {
+ // 延迟500ms跳转(可选,可用于显示小程序logo/slogan)
+ setTimeout(() => {
+ // 调用app.js中的登录检查方法
+ getApp().checkLoginStatus()
+ }, 500)
+ }
+}
diff --git a/pages/launch/launch.json b/pages/launch/launch.json
new file mode 100644
index 0000000..8835af0
--- /dev/null
+++ b/pages/launch/launch.json
@@ -0,0 +1,3 @@
+{
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/pages/launch/launch.wxml b/pages/launch/launch.wxml
new file mode 100644
index 0000000..1cfc408
--- /dev/null
+++ b/pages/launch/launch.wxml
@@ -0,0 +1,7 @@
+
+
+
+
+
+ 智能记账本
+
diff --git a/pages/launch/launch.wxss b/pages/launch/launch.wxss
new file mode 100644
index 0000000..203d60b
--- /dev/null
+++ b/pages/launch/launch.wxss
@@ -0,0 +1,18 @@
+.launch-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ background-color: #f5f7fa;
+}
+
+.logo {
+ margin-bottom: 20rpx;
+}
+
+.slogan {
+ font-size: 36rpx;
+ color: #3B82F6;
+ font-weight: bold;
+}
diff --git a/pages/login/login.js b/pages/login/login.js
new file mode 100644
index 0000000..dc5fa51
--- /dev/null
+++ b/pages/login/login.js
@@ -0,0 +1,213 @@
+const request = require('../utils/request')
+Page({
+ data: {
+ // 账号密码
+ account: '',
+ password: '',
+ // 显示密码切换
+ showPassword: false,
+ // 错误状态
+ accountError: false,
+ accountErrorMsg: '',
+ passwordError: false,
+ passwordErrorMsg: '',
+ // 加载状态
+ isLoading: false
+ },
+
+ // 处理账号输入
+ handleAccountInput(e) {
+ const account = e.detail.value.trim();
+ this.setData({
+ account,
+ accountError: false,
+ accountErrorMsg: ''
+ });
+ },
+
+ // 处理密码输入
+ handlePasswordInput(e) {
+ const password = e.detail.value;
+ this.setData({
+ password,
+ passwordError: false,
+ passwordErrorMsg: ''
+ });
+ },
+
+ // 切换密码显示状态
+ togglePassword() {
+ this.setData({
+ showPassword: !this.data.showPassword
+ });
+ },
+
+ // 表单验证
+ validateForm() {
+ let isValid = true;
+ const { account, password } = this.data;
+
+ // 验证账号
+ if (!account) {
+ this.setData({
+ accountError: true,
+ accountErrorMsg: '请输入账号'
+ });
+ isValid = false;
+ } else if (account.length < 4) {
+ this.setData({
+ accountError: true,
+ accountErrorMsg: '账号长度不能少于4位'
+ });
+ isValid = false;
+ }
+
+ // 验证密码
+ if (!password) {
+ this.setData({
+ passwordError: true,
+ passwordErrorMsg: '请输入密码'
+ });
+ isValid = false;
+ } else if (password.length < 6) {
+ this.setData({
+ passwordError: true,
+ passwordErrorMsg: '密码长度不能少于6位'
+ });
+ isValid = false;
+ }
+
+ return isValid;
+ },
+
+ // 处理登录
+ handleLogin() {
+ // 表单验证
+ if (!this.validateForm()) {
+ return;
+ }
+
+ const { account, password } = this.data;
+
+ // 显示加载状态
+ this.setData({
+ isLoading: true
+ });
+
+ wx.request({
+ url: 'http://localhost:48080/admin-api/system/auth/login', // 仅为示例,并非真实的接口地址
+ method: 'POST', // 请求方法,可以是 GET, POST, PUT, DELETE 等
+ data: {
+ "tenantName": "芋道源码",
+ "username": account,
+ "password": password,
+ "rememberMe": true
+ },
+ header: {
+ 'Tenant-Id':1,
+ 'content-type': 'application/json' // 默认值,也可以根据后端要求设置其他值,如 'application/x-www-form-urlencoded'
+ },
+ success :(res)=> {
+ console.log(res.data)
+ // 处理返回数据
+ if(res.data.code==0){
+
+ wx.setStorageSync('token', res.data.data.accessToken);
+ console.log(wx.getStorageSync('token'))
+ // 跳转到主界面
+ wx.switchTab({
+ url: '/pages/index/index',
+ success: () => {
+ console.log('执行成功')
+ this.setData({
+ isLoading: false
+ });
+ }
+ });
+ }else{
+ this.setData({
+ passwordError: true,
+ passwordErrorMsg: res.data.msg,
+ isLoading: false
+ });
+ // 震动反馈
+ wx.vibrateShort();
+ }
+ },
+ fail (err) {
+ console.error(err)
+
+ },
+ complete () {
+ // 请求完成时执行,无论成功或失败
+ }
+ })
+ },
+
+ // 微信快捷登录
+ wxLogin(e) {
+ // 检查用户是否授权
+ if (e.detail.userInfo) {
+ // 用户同意授权
+ this.setData({
+ isLoading: true
+ });
+
+ // 模拟微信登录过程
+ setTimeout(() => {
+ // 保存用户信息
+ wx.setStorageSync('userInfo', {
+ nickname: e.detail.userInfo.nickName,
+ avatar: e.detail.userInfo.avatarUrl,
+ loginTime: new Date().getTime(),
+ isWechatLogin: true
+ });
+
+ // 跳转到主界面
+ wx.redirectTo({
+ url: '/pages/index/index',
+ success: () => {
+ this.setData({
+ isLoading: false
+ });
+ }
+ });
+ }, 1500);
+ } else {
+ // 用户拒绝授权
+ wx.showToast({
+ title: '授权失败,无法登录',
+ icon: 'none',
+ duration: 2000
+ });
+ }
+ },
+
+ // 跳转到忘记密码页面
+ navigateToForgot() {
+ wx.navigateTo({
+ url: '/pages/forgot-password/forgot-password'
+ });
+ },
+
+ // 跳转到注册页面
+ navigateToRegister() {
+ wx.navigateTo({
+ url: '/pages/register/register'
+ });
+ },
+
+ onLoad() {
+ // 检查是否已登录
+ const userInfo = wx.getStorageSync('userInfo');
+ if (userInfo && userInfo.loginTime) {
+ // 如果30天内登录过,自动跳转
+ const now = new Date().getTime();
+ if (now - userInfo.loginTime < 30 * 24 * 60 * 60 * 1000) {
+ wx.redirectTo({
+ url: '/pages/index/index'
+ });
+ }
+ }
+ }
+});
diff --git a/pages/login/login.json b/pages/login/login.json
new file mode 100644
index 0000000..ca1ac3b
--- /dev/null
+++ b/pages/login/login.json
@@ -0,0 +1,7 @@
+{
+ "navigationBarTitleText": "登录",
+ "navigationBarBackgroundColor": "#f5f7fa",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f5f7fa",
+ "usingComponents": {}
+}
diff --git a/pages/login/login.wxml b/pages/login/login.wxml
new file mode 100644
index 0000000..f731f5c
--- /dev/null
+++ b/pages/login/login.wxml
@@ -0,0 +1,83 @@
+
+
+
+
+ 财务管家
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{accountErrorMsg}}
+
+
+
+
+
+
+
+
+
+
+
+ {{passwordErrorMsg}}
+
+
+
+ 忘记密码?
+
+
+
+
+
+
+
+ 还没有账号?
+ 立即注册
+
+
+
+
+
+
+ 其他登录方式
+
+
+
+
+
+
+
diff --git a/pages/login/login.wxss b/pages/login/login.wxss
new file mode 100644
index 0000000..b1d4313
--- /dev/null
+++ b/pages/login/login.wxss
@@ -0,0 +1,180 @@
+.login-container {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4eaf1 100%);
+ padding: 0 30rpx;
+ box-sizing: border-box;
+}
+
+/* 应用图标区域 */
+.app-icon {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 120rpx;
+ margin-bottom: 80rpx;
+}
+
+.logo-img {
+ width: 160rpx;
+ height: 160rpx;
+ border-radius: 24rpx;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
+ margin-bottom: 30rpx;
+}
+
+.app-name {
+ font-size: 36rpx;
+ font-weight: 600;
+ color: #333;
+ letter-spacing: 2rpx;
+}
+
+/* 登录卡片 */
+.login-card {
+ background-color: #fff;
+ border-radius: 24rpx;
+ padding: 60rpx 40rpx;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.08);
+ margin-bottom: 50rpx;
+}
+
+/* 输入框样式 */
+.input-group {
+ display: flex;
+ align-items: center;
+ border-bottom: 2rpx solid #eee;
+ padding: 25rpx 0;
+ position: relative;
+ transition: all 0.3s ease;
+}
+
+.input-group.error {
+ border-bottom-color: #ff4d4f;
+}
+
+.input-icon {
+ margin-right: 20rpx;
+}
+
+.input-field {
+ flex: 1;
+ font-size: 30rpx;
+ color: #333;
+ height: 40rpx;
+}
+
+.placeholder {
+ color: #c9c9c9;
+}
+
+.toggle-password {
+ margin-left: 20rpx;
+}
+
+.error-icon {
+ margin-left: 20rpx;
+}
+
+.error-message {
+ color: #ff4d4f;
+ font-size: 24rpx;
+ margin-top: 10rpx;
+ margin-bottom: 10rpx;
+ height: 28rpx;
+ line-height: 28rpx;
+}
+
+/* 忘记密码 */
+.forgot-password {
+ display: flex;
+ justify-content: flex-end;
+ margin: 15rpx 0 40rpx 0;
+}
+
+.forgot-password text {
+ font-size: 26rpx;
+ color: #5a89e7;
+}
+
+/* 登录按钮 */
+.login-button {
+ width: 100%;
+ height: 90rpx;
+ line-height: 90rpx;
+ background: linear-gradient(90deg, #5a89e7 0%, #3b6fe4 100%);
+ color: #fff;
+ font-size: 32rpx;
+ border-radius: 45rpx;
+ margin: 20rpx 0;
+ box-shadow: 0 6rpx 16rpx rgba(90, 137, 231, 0.4);
+ letter-spacing: 4rpx;
+}
+
+.login-button::after {
+ border: none;
+}
+
+/* 注册区域 */
+.register-section {
+ display: flex;
+ justify-content: center;
+ margin-top: 40rpx;
+ font-size: 26rpx;
+ color: #666;
+}
+
+.register-link {
+ color: #5a89e7;
+ margin-left: 10rpx;
+ font-weight: 500;
+}
+
+/* 其他登录方式 */
+.other-login {
+ display: flex;
+ align-items: center;
+ margin: 30rpx 0;
+}
+
+.line {
+ flex: 1;
+ height: 1rpx;
+ background-color: #ddd;
+}
+
+.other-login-text {
+ padding: 0 20rpx;
+ font-size: 24rpx;
+ color: #999;
+}
+
+/* 社交登录 */
+.social-login {
+ display: flex;
+ justify-content: center;
+ margin-top: 20rpx;
+}
+
+.social-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f0f7ff;
+ color: #52c41a;
+ font-size: 28rpx;
+ padding: 0 30rpx;
+ height: 80rpx;
+ border-radius: 40rpx;
+}
+
+.social-btn::after {
+ border: none;
+}
+
+.social-icon {
+ width: 36rpx;
+ height: 36rpx;
+ margin-right: 15rpx;
+}
diff --git a/pages/profile/profile.js b/pages/profile/profile.js
new file mode 100644
index 0000000..ac2f5f9
--- /dev/null
+++ b/pages/profile/profile.js
@@ -0,0 +1,146 @@
+Page({
+ data: {
+ totalRecords: 0,
+ firstRecordDate: '',
+ consecutiveDays: 0
+ },
+
+ onShow() {
+ this.updateStats();
+ },
+
+ updateStats() {
+ const records = wx.getStorageSync('records') || [];
+ const totalRecords = records.length;
+
+ let firstRecordDate = '';
+ let consecutiveDays = 0;
+
+ if (totalRecords > 0) {
+ // 计算首次记账日期
+ const sortedRecords = [...records].sort((a, b) => new Date(a.date) - new Date(b.date));
+ const firstDate = new Date(sortedRecords[0].date);
+ firstRecordDate = `${firstDate.getFullYear()}-${(firstDate.getMonth() + 1).toString().padStart(2, '0')}-${firstDate.getDate().toString().padStart(2, '0')}`;
+
+ // 计算连续记账天数
+ consecutiveDays = this.calculateConsecutiveDays(records);
+ }
+
+ this.setData({
+ totalRecords,
+ firstRecordDate,
+ consecutiveDays
+ });
+ },
+
+ calculateConsecutiveDays(records) {
+ // 获取去重的日期列表
+ const dateSet = new Set();
+ records.forEach(record => {
+ dateSet.add(record.date);
+ });
+
+ const dateList = Array.from(dateSet).sort((a, b) => new Date(b) - new Date(a));
+
+ if (dateList.length === 0) {
+ return 0;
+ }
+
+ // 检查是否包含今天
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const lastDate = new Date(dateList[0]);
+ lastDate.setHours(0, 0, 0, 0);
+
+ if (lastDate.getTime() !== today.getTime()) {
+ return 0;
+ }
+
+ // 计算连续天数
+ let consecutiveDays = 1;
+ for (let i = 0; i < dateList.length - 1; i++) {
+ const currentDate = new Date(dateList[i]);
+ currentDate.setHours(0, 0, 0, 0);
+
+ const nextDate = new Date(dateList[i + 1]);
+ nextDate.setHours(0, 0, 0, 0);
+
+ // 检查是否连续(相差一天)
+ const diffTime = currentDate.getTime() - nextDate.getTime();
+ const diffDays = diffTime / (1000 * 60 * 60 * 24);
+
+ if (diffDays === 1) {
+ consecutiveDays++;
+ } else {
+ break;
+ }
+ }
+
+ return consecutiveDays;
+ },
+
+ exportData() {
+ const records = wx.getStorageSync('records') || [];
+ if (records.length === 0) {
+ wx.showToast({
+ title: '暂无数据可导出',
+ icon: 'none',
+ duration: 2000
+ });
+ return;
+ }
+
+ // 转换为CSV格式
+ let csvContent = "日期,类型,分类,金额,备注\n";
+ records.forEach(record => {
+ const type = record.type === 'income' ? '收入' : '支出';
+ csvContent += `${record.date},${type},${record.categoryName},${record.amount},${record.note || ''}\n`;
+ });
+
+ // 这里仅做演示,实际导出功能需要后端支持或使用微信的文件系统API
+ wx.showModal({
+ title: '导出成功',
+ content: '数据已导出为CSV格式',
+ showCancel: false
+ });
+ },
+
+ clearData() {
+ wx.showModal({
+ title: '确认清空',
+ content: '确定要清空所有记账数据吗?此操作不可恢复。',
+ cancelText: '取消',
+ confirmText: '确认',
+ success: (res) => {
+ if (res.confirm) {
+ wx.setStorageSync('records', []);
+ wx.showToast({
+ title: '数据已清空',
+ icon: 'success',
+ duration: 2000
+ });
+ this.updateStats();
+ }
+ }
+ });
+ },
+
+ showAbout() {
+ wx.showModal({
+ title: '关于简易记账',
+ content: '简易记账是一款简单实用的记账工具,帮助你记录和管理个人财务。',
+ showCancel: false,
+ confirmText: '我知道了'
+ });
+ },
+
+ outLogin() {
+ wx.showModal({
+ title: '关于简易记账',
+ content: '简易记账是一款简单实用的记账工具,帮助你记录和管理个人财务。',
+ showCancel: false,
+ confirmText: '我知道了'
+ });
+ }
+
+})
diff --git a/pages/profile/profile.wxml b/pages/profile/profile.wxml
new file mode 100644
index 0000000..4caa26c
--- /dev/null
+++ b/pages/profile/profile.wxml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+ 记账用户
+ 开始你的记账之旅
+
+
+
+
+
+
+
+
+
+ 分类管理
+
+
+
+
+
+
+
+ 导出数据
+
+
+
+
+
+
+
+ 清空数据
+
+
+
+
+
+
+
+ 关于我们
+
+
+
+
+
+
+
+ 退出登录
+
+
+
+
+
+
+
+ 记账统计
+
+
+ {{totalRecords}}
+ 总记录数
+
+
+ {{firstRecordDate || '-'}}
+ 首次记账
+
+
+ {{consecutiveDays}}
+ 连续记账(天)
+
+
+
+
diff --git a/pages/profile/profile.wxss b/pages/profile/profile.wxss
new file mode 100644
index 0000000..fa65c79
--- /dev/null
+++ b/pages/profile/profile.wxss
@@ -0,0 +1,128 @@
+.container {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ padding-bottom: 15px;
+}
+
+.user-info {
+ background: linear-gradient(135deg, #4CAF50 0%, #8BC34A 100%);
+ padding: 30px 20px;
+ display: flex;
+ align-items: center;
+}
+
+.avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background-color: white;
+ padding: 5px;
+ margin-right: 15px;
+}
+
+.avatar image {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+}
+
+.user-details {
+ color: white;
+}
+
+.username {
+ font-size: 20px;
+ font-weight: 600;
+ display: block;
+ margin-bottom: 5px;
+}
+
+.user-desc {
+ font-size: 14px;
+ opacity: 0.9;
+}
+
+.function-list {
+ background-color: white;
+ margin: 15px;
+ border-radius: 15px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.function-item {
+ display: flex;
+ align-items: center;
+ padding: 15px 20px;
+ border-bottom: 1px solid #f5f5f5;
+ color: #333;
+}
+
+.function-item:last-child {
+ border-bottom: none;
+}
+
+.function-icon {
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background-color: #e8f5e9;
+ color: #4CAF50;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+}
+
+.function-icon .iconfont {
+ font-size: 18px;
+}
+
+.function-name {
+ flex: 1;
+ font-size: 16px;
+}
+
+.function-item .icon-arrow-right {
+ color: #ccc;
+ font-size: 16px;
+}
+
+.stats-card {
+ background-color: white;
+ margin: 0 15px;
+ border-radius: 15px;
+ padding: 20px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.stats-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ display: block;
+ margin-bottom: 15px;
+}
+
+.stats-grid {
+ display: flex;
+ justify-content: space-around;
+}
+
+.stats-item {
+ text-align: center;
+ flex: 1;
+}
+
+.stats-value {
+ font-size: 22px;
+ font-weight: 600;
+ color: #333;
+ display: block;
+ margin-bottom: 5px;
+}
+
+.stats-label {
+ font-size: 14px;
+ color: #666;
+}
diff --git a/pages/statistics/statistics.js b/pages/statistics/statistics.js
new file mode 100644
index 0000000..6cce705
--- /dev/null
+++ b/pages/statistics/statistics.js
@@ -0,0 +1,209 @@
+Page({
+ data: {
+ selectedMonth: '',
+ selectedMonthText: '',
+ summary: {
+ income: '0.00',
+ expense: '0.00',
+ balance: '0.00'
+ },
+ activeType: 'expense',
+ categoryStats: [],
+ monthRecords: []
+ },
+
+ onLoad() {
+ // 初始化当前月份
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, '0');
+ const selectedMonth = `${year}-${month}`;
+
+ this.setData({
+ selectedMonth,
+ selectedMonthText: `${year}年${month}月`
+ });
+
+ this.updateStatistics();
+ },
+
+ onMonthChange(e) {
+ const selectedMonth = e.detail.value;
+ const [year, month] = selectedMonth.split('-');
+ this.setData({
+ selectedMonth,
+ selectedMonthText: `${year}年${month}月`
+ });
+
+ this.updateStatistics();
+ },
+
+ setActiveType(e) {
+ const type = e.currentTarget.dataset.type;
+ this.setData({ activeType: type }, () => {
+ this.calculateCategoryStats();
+ this.drawPieChart();
+ });
+ },
+
+ updateStatistics() {
+ // 获取所有记录
+ const records = wx.getStorageSync('records') || [];
+ const [year, month] = this.data.selectedMonth.split('-');
+
+ // 筛选当月记录
+ const monthRecords = records.filter(record => {
+ const recordDate = new Date(record.date);
+ return recordDate.getFullYear() === parseInt(year) &&
+ (recordDate.getMonth() + 1) === parseInt(month);
+ });
+
+ // 按日期倒序排列
+ monthRecords.sort((a, b) => new Date(b.date) - new Date(a.date));
+
+ // 计算收支汇总
+ let income = 0;
+ let expense = 0;
+
+ monthRecords.forEach(record => {
+ if (record.type === 'income') {
+ income += parseFloat(record.amount);
+ } else {
+ expense += parseFloat(record.amount);
+ }
+ });
+
+ const balance = income - expense;
+
+ this.setData({
+ monthRecords,
+ summary: {
+ income: income.toFixed(2),
+ expense: expense.toFixed(2),
+ balance: balance.toFixed(2)
+ }
+ }, () => {
+ this.calculateCategoryStats();
+ this.drawPieChart();
+ });
+ },
+
+ calculateCategoryStats() {
+ const { monthRecords, activeType } = this.data;
+ const typeRecords = monthRecords.filter(record => record.type === activeType);
+
+ // 按分类汇总
+ const categoryMap = {};
+
+ typeRecords.forEach(record => {
+ if (!categoryMap[record.categoryId]) {
+ categoryMap[record.categoryId] = {
+ categoryId: record.categoryId,
+ categoryName: record.categoryName,
+ amount: 0,
+ count: 0
+ };
+ }
+
+ categoryMap[record.categoryId].amount += parseFloat(record.amount);
+ categoryMap[record.categoryId].count += 1;
+ });
+
+ // 转换为数组并排序
+ let categoryStats = Object.values(categoryMap);
+ categoryStats.sort((a, b) => b.amount - a.amount);
+
+ // 计算百分比
+ const total = activeType === 'income'
+ ? parseFloat(this.data.summary.income)
+ : parseFloat(this.data.summary.expense);
+
+ if (total > 0) {
+ categoryStats.forEach(item => {
+ item.percentage = Math.round((item.amount / total) * 100);
+ item.amount = item.amount.toFixed(2);
+ });
+ }
+
+ // 分配颜色
+ const colors = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39'];
+ categoryStats.forEach((item, index) => {
+ item.color = colors[index % colors.length];
+ });
+
+ this.setData({ categoryStats });
+ },
+
+ drawPieChart() {
+ const { categoryStats } = this.data;
+
+ if (categoryStats.length === 0) {
+ return;
+ }
+
+ const ctx = wx.createCanvasContext('pieChart', this);
+ const centerX = 120; // 饼图中心X坐标
+ const centerY = 120; // 饼图中心Y坐标
+ const radius = 90; // 饼图半径
+
+ let startAngle = 0;
+
+ categoryStats.forEach(item => {
+ const percentage = parseFloat(item.percentage);
+ const endAngle = startAngle + 2 * Math.PI * (percentage / 100);
+
+ // 绘制扇形
+ ctx.beginPath();
+ ctx.setFillStyle(item.color);
+ ctx.moveTo(centerX, centerY);
+ ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);
+ ctx.closePath();
+ ctx.fill();
+
+ // 计算文本位置
+ const midAngle = startAngle + (endAngle - startAngle) / 2;
+ const textRadius = radius * 0.6; // 文本距离中心的距离
+ const textX = centerX + Math.cos(midAngle) * textRadius;
+ const textY = centerY + Math.sin(midAngle) * textRadius;
+
+ // 绘制百分比文本
+ ctx.setFontSize(14);
+ ctx.setFillStyle('#333');
+ ctx.setTextAlign('center');
+ ctx.setTextBaseline('middle');
+ ctx.fillText(`${percentage}%`, textX, textY);
+
+ startAngle = endAngle;
+ });
+
+ // 绘制中心空白区域
+ ctx.beginPath();
+ ctx.setFillStyle('#ffffff');
+ ctx.arc(centerX, centerY, radius * 0.4, 0, 2 * Math.PI, false);
+ ctx.fill();
+
+ // 绘制中心文本
+ const total = this.data.activeType === 'income'
+ ? this.data.summary.income
+ : this.data.summary.expense;
+
+ ctx.setFontSize(16);
+ ctx.setFillStyle('#333');
+ ctx.setTextAlign('center');
+ ctx.setTextBaseline('middle');
+ ctx.fillText(`总计`, centerX, centerY - 10);
+
+ ctx.setFontSize(18);
+ ctx.setFillStyle(this.data.activeType === 'income' ? '#4CAF50' : '#f44336');
+ ctx.fillText(`¥${total}`, centerX, centerY + 15);
+
+ ctx.draw();
+ },
+
+ formatDate(dateStr) {
+ const date = new Date(dateStr);
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+ return `${month}月${day}日`;
+ }
+})
diff --git a/pages/statistics/statistics.wxml b/pages/statistics/statistics.wxml
new file mode 100644
index 0000000..0f43ccb
--- /dev/null
+++ b/pages/statistics/statistics.wxml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ {{selectedMonthText}}
+
+
+
+
+
+
+
+
+ 收入
+ ¥ {{summary.income}}
+
+
+ 支出
+ ¥ {{summary.expense}}
+
+
+ 结余
+ ¥ {{summary.balance}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.categoryName}}
+
+
+ ¥ {{item.amount}}
+ {{item.percentage}}%
+
+
+
+
+ 本月暂无{{activeType === 'expense' ? '支出' : '收入'}}记录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.categoryName}}
+ {{item.note || '无备注'}}
+ {{formatDate(item.date)}}
+
+
+ {{item.type === 'income' ? '+' : '-'}}¥ {{item.amount}}
+
+
+
+
+ 本月暂无记录
+
+
+
diff --git a/pages/statistics/statistics.wxss b/pages/statistics/statistics.wxss
new file mode 100644
index 0000000..4c7695a
--- /dev/null
+++ b/pages/statistics/statistics.wxss
@@ -0,0 +1,234 @@
+.container {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ padding-bottom: 60px;
+}
+
+.month-picker {
+ padding: 15px;
+ background-color: white;
+}
+
+.picker-view {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 40px;
+ background-color: #f5f5f5;
+ border-radius: 20px;
+ color: #333;
+ font-size: 16px;
+}
+
+.picker-view .iconfont {
+ margin: 0 8px;
+ color: #666;
+}
+
+.summary-card {
+ display: flex;
+ justify-content: space-around;
+ background-color: white;
+ margin: 15px;
+ border-radius: 15px;
+ padding: 20px 10px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.summary-item {
+ text-align: center;
+ flex: 1;
+}
+
+.summary-label {
+ display: block;
+ font-size: 14px;
+ color: #666;
+ margin-bottom: 5px;
+}
+
+.summary-amount {
+ font-size: 22px;
+ font-weight: 600;
+}
+
+.income {
+ color: #4CAF50;
+}
+
+.expense {
+ color: #f44336;
+}
+
+.balance {
+ color: #2196F3;
+}
+
+.category-stats, .records-section {
+ background-color: white;
+ margin: 15px;
+ border-radius: 15px;
+ padding: 15px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.section-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+}
+
+.type-tabs {
+ display: flex;
+}
+
+.tab {
+ padding: 5px 12px;
+ font-size: 14px;
+ border-radius: 15px;
+ margin-left: 10px;
+}
+
+.tab.active {
+ background-color: #e8f5e9;
+ color: #4CAF50;
+}
+
+.chart-container {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 20px;
+}
+
+.pie-chart {
+ width: 240px;
+ height: 240px;
+}
+
+.category-details {
+ margin-bottom: 10px;
+}
+
+.category-detail-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.category-detail-item:last-child {
+ border-bottom: none;
+}
+
+.category-info {
+ display: flex;
+ align-items: center;
+}
+
+.category-color {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ margin-right: 10px;
+}
+
+.category-name {
+ font-size: 16px;
+ color: #333;
+}
+
+.category-amount {
+ text-align: right;
+}
+
+.category-amount text:first-child {
+ font-size: 16px;
+ font-weight: 500;
+ margin-right: 10px;
+}
+
+.category-percentage {
+ font-size: 14px;
+ color: #999;
+}
+
+.records-list {
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.record-item {
+ display: flex;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.record-item:last-child {
+ border-bottom: none;
+}
+
+.record-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+}
+
+.income-icon {
+ background-color: #e8f5e9;
+ color: #4CAF50;
+}
+
+.expense-icon {
+ background-color: #ffebee;
+ color: #f44336;
+}
+
+.iconfont {
+ font-size: 20px;
+}
+
+.record-details {
+ flex: 1;
+}
+
+.record-title {
+ font-size: 16px;
+ color: #333;
+ margin-bottom: 3px;
+}
+
+.record-note {
+ font-size: 12px;
+ color: #999;
+ margin-bottom: 2px;
+}
+
+.record-date {
+ font-size: 12px;
+ color: #ccc;
+}
+
+.record-amount {
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.no-data, .no-records {
+ text-align: center;
+ padding: 30px 0;
+ color: #999;
+ font-size: 14px;
+}
diff --git a/pages/utils/request.js b/pages/utils/request.js
new file mode 100644
index 0000000..80de07a
--- /dev/null
+++ b/pages/utils/request.js
@@ -0,0 +1,111 @@
+/**
+ * 封装微信小程序请求,自动处理token
+ */
+const baseUrl='http://localhost:48080'
+
+const request = (url, options = {}) => {
+ // 返回Promise,方便使用async/await
+ return new Promise((resolve, reject) => {
+ // 默认请求头
+ const header = {
+ 'Content-Type': 'application/json',
+ ...options.header
+ }
+
+ // 从本地存储获取token并添加到请求头
+ const token = wx.getStorageSync('token')
+ if (token) {
+ header['Authorization'] = `Bearer ${token}`
+ }
+ // 合并请求配置
+ const config = {
+ url:baseUrl+url,
+ method: options.method || 'GET',
+ data: options.data || {},
+ header,
+ // 成功回调
+ success: (res) => {
+ if (res.statusCode === 200) {
+ // 业务成功,返回数据
+ if(res.data&&res.data.code==401){
+ console.log("登录已过期,请重新登录")
+ // token失效或未登录,清除token并跳转登录页
+ wx.removeStorageSync('token')
+ wx.showToast({
+ title: '登录已过期,请重新登录',
+ icon: 'none'
+ })
+
+ // 记录当前页面,登录后返回
+ const pages = getCurrentPages()
+ const currentPage = pages[pages.length - 1]
+ wx.redirectTo({
+ url: `/pages/login/login?redirect=${currentPage.route}`
+ })
+
+ reject(new Error('未授权或token已过期'))
+ }else{
+ console.log("111")
+ resolve(res.data)
+ }
+ } else if (res.statusCode === 401) {
+ console.log("登录已过期,请重新登录")
+ // token失效或未登录,清除token并跳转登录页
+ wx.removeStorageSync('token')
+ wx.showToast({
+ title: '登录已过期,请重新登录',
+ icon: 'none'
+ })
+
+ // 记录当前页面,登录后返回
+ const pages = getCurrentPages()
+ const currentPage = pages[pages.length - 1]
+ wx.redirectTo({
+ url: `/pages/login/login?redirect=${currentPage.route}`
+ })
+
+ reject(new Error('未授权或token已过期'))
+ } else {
+ // 其他错误(如400、500等)
+ wx.showToast({
+ title: res.data?.message || '请求失败',
+ icon: 'none'
+ })
+ reject(new Error(`请求错误: ${res.statusCode}`))
+ }
+ },
+ // 失败回调(如网络错误)
+ fail: (err) => {
+ wx.showToast({
+ title: '网络异常,请稍后再试',
+ icon: 'none'
+ })
+ reject(err)
+ },
+ ...options
+ }
+ console.log('config.url',config.url)
+ // 发起原生请求
+ wx.request(config)
+ })
+}
+
+// 快捷方法
+request.get = (url, data, options = {}) => {
+ return request(url, { ...options, method: 'GET', data })
+}
+
+request.post = (url, data, options = {}) => {
+ return request(url, { ...options, method: 'POST', data })
+}
+
+request.put = (url, data, options = {}) => {
+ return request(url, { ...options, method: 'PUT', data })
+}
+
+request.delete = (url, data, options = {}) => {
+ return request(url, { ...options, method: 'DELETE', data })
+}
+
+module.exports = request
+
diff --git a/project.config.json b/project.config.json
new file mode 100644
index 0000000..39e75ee
--- /dev/null
+++ b/project.config.json
@@ -0,0 +1,25 @@
+{
+ "setting": {
+ "es6": true,
+ "postcss": true,
+ "minified": true,
+ "uglifyFileName": false,
+ "enhance": true,
+ "packNpmRelationList": [],
+ "babelSetting": {
+ "ignore": [],
+ "disablePlugins": [],
+ "outputPath": ""
+ },
+ "useCompilerPlugins": false,
+ "minifyWXML": true
+ },
+ "compileType": "miniprogram",
+ "simulatorPluginLibVersion": {},
+ "packOptions": {
+ "ignore": [],
+ "include": []
+ },
+ "appid": "wx8002cc7a350eb380",
+ "editorSetting": {}
+}
\ No newline at end of file
diff --git a/project.private.config.json b/project.private.config.json
new file mode 100644
index 0000000..8d2d774
--- /dev/null
+++ b/project.private.config.json
@@ -0,0 +1,14 @@
+{
+ "libVersion": "3.10.1",
+ "projectname": "accounting-wechat-miniprogram%20(1)",
+ "setting": {
+ "urlCheck": false,
+ "coverView": true,
+ "lazyloadPlaceholderEnable": false,
+ "skylineRenderEnable": false,
+ "preloadBackgroundData": false,
+ "autoAudits": false,
+ "showShadowRootInWxmlPanel": true,
+ "compileHotReLoad": true
+ }
+}
\ No newline at end of file