|
@@ -0,0 +1,1185 @@
|
|
|
+# 日历记录API接口 - 前端开发指南
|
|
|
+
|
|
|
+## 版本信息
|
|
|
+- **版本**: v1.0.0
|
|
|
+- **更新日期**: 2024-01-15
|
|
|
+- **适用范围**: DataOps平台日历记录功能
|
|
|
+
|
|
|
+## 概述
|
|
|
+
|
|
|
+本文档提供日历记录相关API接口的详细说明,包括保存日历记录和获取日历记录功能。这些接口用于管理用户的月度日历内容,支持插入新记录和更新现有记录。
|
|
|
+
|
|
|
+## 重要说明
|
|
|
+
|
|
|
+1. **认证方式**: 使用微信用户的 `openid` 作为用户标识
|
|
|
+2. **数据格式**: 所有请求和响应均使用 JSON 格式
|
|
|
+3. **字符编码**: UTF-8
|
|
|
+4. **时区**: 所有时间字段均为东八区时间 (UTC+8)
|
|
|
+5. **月份格式**: 统一使用 `YYYY-MM` 格式(如:2024-01)
|
|
|
+
|
|
|
+## API接口列表
|
|
|
+
|
|
|
+| 接口名称 | 请求方法 | 路径 | 功能描述 |
|
|
|
+|---------|---------|------|---------|
|
|
|
+| 保存日历记录 | POST | `/api/parse/save-calendar-record` | 保存或更新用户日历记录 |
|
|
|
+| 获取日历记录 | GET | `/api/parse/get-calendar-record` | 查询用户指定月份的日历记录 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 保存日历记录接口
|
|
|
+
|
|
|
+### 基本信息
|
|
|
+- **接口路径**: `/api/parse/save-calendar-record`
|
|
|
+- **请求方法**: `POST`
|
|
|
+- **Content-Type**: `application/json`
|
|
|
+- **功能**: 保存用户的日历内容记录,如果记录已存在则更新,不存在则创建新记录
|
|
|
+
|
|
|
+### 请求参数
|
|
|
+
|
|
|
+#### 请求头 (Headers)
|
|
|
+```http
|
|
|
+Content-Type: application/json
|
|
|
+```
|
|
|
+
|
|
|
+#### 请求体 (Request Body)
|
|
|
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
|
|
+|--------|------|------|------|--------|
|
|
|
+| openid | string | 是 | 微信用户openid,28位字符串 | "wx_openid_abcd1234567890123456" |
|
|
|
+| month_key | string | 是 | 月份标识,格式为YYYY-MM | "2024-01" |
|
|
|
+| calendar_content | array | 是 | 日历内容,JSON数组格式 | 见下方示例 |
|
|
|
+
|
|
|
+#### calendar_content 数组元素结构
|
|
|
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
|
|
|
+|--------|------|------|------|--------|
|
|
|
+| date | string | 否 | 日期,格式为YYYY-MM-DD | "2024-01-01" |
|
|
|
+| events | array | 否 | 事件列表,字符串数组 | ["会议", "约会"] |
|
|
|
+| notes | string | 否 | 备注信息 | "重要日程" |
|
|
|
+
|
|
|
+### 请求示例
|
|
|
+
|
|
|
+#### JavaScript (Fetch API)
|
|
|
+```javascript
|
|
|
+async function saveCalendarRecord(openid, monthKey, calendarContent) {
|
|
|
+ const url = '/api/parse/save-calendar-record';
|
|
|
+
|
|
|
+ const requestData = {
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey,
|
|
|
+ calendar_content: calendarContent
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(url, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify(requestData)
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ console.log('保存成功:', result.result);
|
|
|
+ return { success: true, data: result.result };
|
|
|
+ } else {
|
|
|
+ console.error('保存失败:', result.error);
|
|
|
+ return { success: false, error: result.error };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ return { success: false, error: '网络请求失败' };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+const calendarData = [
|
|
|
+ {
|
|
|
+ date: "2024-01-01",
|
|
|
+ events: ["元旦节"],
|
|
|
+ notes: "新年快乐"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ date: "2024-01-15",
|
|
|
+ events: ["会议", "约会"],
|
|
|
+ notes: "重要日程"
|
|
|
+ }
|
|
|
+];
|
|
|
+
|
|
|
+saveCalendarRecord("wx_openid_abcd1234567890123456", "2024-01", calendarData);
|
|
|
+```
|
|
|
+
|
|
|
+#### jQuery Ajax
|
|
|
+```javascript
|
|
|
+function saveCalendarRecord(openid, monthKey, calendarContent) {
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/parse/save-calendar-record',
|
|
|
+ type: 'POST',
|
|
|
+ contentType: 'application/json',
|
|
|
+ data: JSON.stringify({
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey,
|
|
|
+ calendar_content: calendarContent
|
|
|
+ }),
|
|
|
+ success: function(result) {
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ console.log('保存成功:', result.result);
|
|
|
+ } else {
|
|
|
+ console.error('保存失败:', result.error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: function(xhr, status, error) {
|
|
|
+ console.error('请求失败:', error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 微信小程序
|
|
|
+```javascript
|
|
|
+// 微信小程序保存日历记录
|
|
|
+function saveCalendarRecord(openid, monthKey, calendarContent) {
|
|
|
+ wx.request({
|
|
|
+ url: 'https://your-domain.com/api/parse/save-calendar-record',
|
|
|
+ method: 'POST',
|
|
|
+ header: {
|
|
|
+ 'content-type': 'application/json'
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey,
|
|
|
+ calendar_content: calendarContent
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ if (res.data.return_code === 200) {
|
|
|
+ console.log('保存成功:', res.data.result);
|
|
|
+ wx.showToast({
|
|
|
+ title: '保存成功',
|
|
|
+ icon: 'success'
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ console.error('保存失败:', res.data.error);
|
|
|
+ wx.showToast({
|
|
|
+ title: '保存失败',
|
|
|
+ icon: 'error'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function(error) {
|
|
|
+ console.error('请求失败:', error);
|
|
|
+ wx.showToast({
|
|
|
+ title: '网络错误',
|
|
|
+ icon: 'error'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 响应结果
|
|
|
+
|
|
|
+#### 成功响应 (HTTP 200)
|
|
|
+```json
|
|
|
+{
|
|
|
+ "reason": "successed",
|
|
|
+ "return_code": 200,
|
|
|
+ "result": {
|
|
|
+ "id": 1,
|
|
|
+ "openid": "wx_openid_abcd1234567890123456",
|
|
|
+ "month_key": "2024-01",
|
|
|
+ "calendar_content": [
|
|
|
+ {
|
|
|
+ "date": "2024-01-01",
|
|
|
+ "events": ["元旦节"],
|
|
|
+ "notes": "新年快乐"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "date": "2024-01-15",
|
|
|
+ "events": ["会议", "约会"],
|
|
|
+ "notes": "重要日程"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "created_at": "2024-01-15T10:30:00+08:00",
|
|
|
+ "updated_at": "2024-01-15T14:30:00+08:00"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 错误响应
|
|
|
+```json
|
|
|
+{
|
|
|
+ "reason": "failed",
|
|
|
+ "return_code": 400,
|
|
|
+ "result": null,
|
|
|
+ "error": "缺少必填参数: openid"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 返回码说明
|
|
|
+| 返回码 | 说明 | 处理建议 |
|
|
|
+|--------|------|---------|
|
|
|
+| 200 | 成功 | 继续后续操作 |
|
|
|
+| 400 | 请求参数错误 | 检查请求参数格式和必填项 |
|
|
|
+| 500 | 服务器内部错误 | 联系技术支持或稍后重试 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 获取日历记录接口
|
|
|
+
|
|
|
+### 基本信息
|
|
|
+- **接口路径**: `/api/parse/get-calendar-record`
|
|
|
+- **请求方法**: `GET`
|
|
|
+- **功能**: 查询用户指定月份的日历记录
|
|
|
+
|
|
|
+### 请求参数
|
|
|
+
|
|
|
+#### 查询参数 (Query Parameters)
|
|
|
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
|
|
+|--------|------|------|------|--------|
|
|
|
+| openid | string | 是 | 微信用户openid | wx_openid_abcd1234567890123456 |
|
|
|
+| month_key | string | 是 | 月份标识,格式为YYYY-MM | 2024-01 |
|
|
|
+
|
|
|
+### 请求示例
|
|
|
+
|
|
|
+#### JavaScript (Fetch API)
|
|
|
+```javascript
|
|
|
+async function getCalendarRecord(openid, monthKey) {
|
|
|
+ const url = `/api/parse/get-calendar-record?openid=${encodeURIComponent(openid)}&month_key=${encodeURIComponent(monthKey)}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(url, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ console.log('查询成功:', result.result);
|
|
|
+ return { success: true, data: result.result };
|
|
|
+ } else {
|
|
|
+ console.error('查询失败:', result.error);
|
|
|
+ return { success: false, error: result.error };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ return { success: false, error: '网络请求失败' };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+getCalendarRecord("wx_openid_abcd1234567890123456", "2024-01")
|
|
|
+ .then(result => {
|
|
|
+ if (result.success) {
|
|
|
+ // 处理成功结果
|
|
|
+ const calendarData = result.data;
|
|
|
+ if (calendarData.id) {
|
|
|
+ console.log('找到日历记录:', calendarData.calendar_content);
|
|
|
+ } else {
|
|
|
+ console.log('该月份暂无记录');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 处理错误
|
|
|
+ console.error('获取失败:', result.error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+#### jQuery Ajax
|
|
|
+```javascript
|
|
|
+function getCalendarRecord(openid, monthKey) {
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/parse/get-calendar-record',
|
|
|
+ type: 'GET',
|
|
|
+ data: {
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey
|
|
|
+ },
|
|
|
+ success: function(result) {
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ console.log('查询成功:', result.result);
|
|
|
+ if (result.result.id) {
|
|
|
+ // 有记录
|
|
|
+ displayCalendarData(result.result.calendar_content);
|
|
|
+ } else {
|
|
|
+ // 无记录
|
|
|
+ displayEmptyCalendar();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error('查询失败:', result.error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: function(xhr, status, error) {
|
|
|
+ console.error('请求失败:', error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 微信小程序
|
|
|
+```javascript
|
|
|
+// 微信小程序获取日历记录
|
|
|
+function getCalendarRecord(openid, monthKey) {
|
|
|
+ wx.request({
|
|
|
+ url: 'https://your-domain.com/api/parse/get-calendar-record',
|
|
|
+ method: 'GET',
|
|
|
+ data: {
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ if (res.data.return_code === 200) {
|
|
|
+ console.log('查询成功:', res.data.result);
|
|
|
+ const calendarData = res.data.result;
|
|
|
+
|
|
|
+ if (calendarData.id) {
|
|
|
+ // 有记录,显示日历内容
|
|
|
+ this.setData({
|
|
|
+ calendarContent: calendarData.calendar_content,
|
|
|
+ hasRecord: true
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 无记录,显示空日历
|
|
|
+ this.setData({
|
|
|
+ calendarContent: [],
|
|
|
+ hasRecord: false
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error('查询失败:', res.data.error);
|
|
|
+ wx.showToast({
|
|
|
+ title: '查询失败',
|
|
|
+ icon: 'error'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function(error) {
|
|
|
+ console.error('请求失败:', error);
|
|
|
+ wx.showToast({
|
|
|
+ title: '网络错误',
|
|
|
+ icon: 'error'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 响应结果
|
|
|
+
|
|
|
+#### 有记录时的成功响应 (HTTP 200)
|
|
|
+```json
|
|
|
+{
|
|
|
+ "reason": "successed",
|
|
|
+ "return_code": 200,
|
|
|
+ "result": {
|
|
|
+ "id": 1,
|
|
|
+ "openid": "wx_openid_abcd1234567890123456",
|
|
|
+ "month_key": "2024-01",
|
|
|
+ "calendar_content": [
|
|
|
+ {
|
|
|
+ "date": "2024-01-01",
|
|
|
+ "events": ["元旦节"],
|
|
|
+ "notes": "新年快乐"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "date": "2024-01-15",
|
|
|
+ "events": ["会议", "约会"],
|
|
|
+ "notes": "重要日程"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "created_at": "2024-01-15T10:30:00+08:00",
|
|
|
+ "updated_at": "2024-01-15T14:30:00+08:00"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 无记录时的成功响应 (HTTP 200)
|
|
|
+```json
|
|
|
+{
|
|
|
+ "reason": "successed",
|
|
|
+ "return_code": 200,
|
|
|
+ "result": {
|
|
|
+ "id": null,
|
|
|
+ "openid": "wx_openid_abcd1234567890123456",
|
|
|
+ "month_key": "2024-01",
|
|
|
+ "calendar_content": [],
|
|
|
+ "created_at": null,
|
|
|
+ "updated_at": null
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 错误响应
|
|
|
+```json
|
|
|
+{
|
|
|
+ "reason": "failed",
|
|
|
+ "return_code": 400,
|
|
|
+ "result": null,
|
|
|
+ "error": "缺少必填参数: month_key"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 前端集成指南
|
|
|
+
|
|
|
+### 1. 完整的日历管理类
|
|
|
+
|
|
|
+```javascript
|
|
|
+class CalendarRecordManager {
|
|
|
+ constructor(baseUrl = '') {
|
|
|
+ this.baseUrl = baseUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存日历记录
|
|
|
+ * @param {string} openid - 微信用户openid
|
|
|
+ * @param {string} monthKey - 月份标识 (YYYY-MM)
|
|
|
+ * @param {Array} calendarContent - 日历内容数组
|
|
|
+ * @returns {Promise<Object>} 返回操作结果
|
|
|
+ */
|
|
|
+ async saveRecord(openid, monthKey, calendarContent) {
|
|
|
+ const url = `${this.baseUrl}/api/parse/save-calendar-record`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(url, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey,
|
|
|
+ calendar_content: calendarContent
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+ return {
|
|
|
+ success: result.return_code === 200,
|
|
|
+ data: result.result,
|
|
|
+ error: result.error,
|
|
|
+ code: result.return_code
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ data: null,
|
|
|
+ error: error.message,
|
|
|
+ code: -1
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取日历记录
|
|
|
+ * @param {string} openid - 微信用户openid
|
|
|
+ * @param {string} monthKey - 月份标识 (YYYY-MM)
|
|
|
+ * @returns {Promise<Object>} 返回查询结果
|
|
|
+ */
|
|
|
+ async getRecord(openid, monthKey) {
|
|
|
+ const url = `${this.baseUrl}/api/parse/get-calendar-record?openid=${encodeURIComponent(openid)}&month_key=${encodeURIComponent(monthKey)}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(url, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+ return {
|
|
|
+ success: result.return_code === 200,
|
|
|
+ data: result.result,
|
|
|
+ error: result.error,
|
|
|
+ code: result.return_code,
|
|
|
+ hasRecord: result.result && result.result.id !== null
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ data: null,
|
|
|
+ error: error.message,
|
|
|
+ code: -1,
|
|
|
+ hasRecord: false
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化月份键
|
|
|
+ * @param {Date} date - 日期对象
|
|
|
+ * @returns {string} 格式化的月份键 (YYYY-MM)
|
|
|
+ */
|
|
|
+ formatMonthKey(date = new Date()) {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ return `${year}-${month}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证月份键格式
|
|
|
+ * @param {string} monthKey - 月份键
|
|
|
+ * @returns {boolean} 是否有效
|
|
|
+ */
|
|
|
+ validateMonthKey(monthKey) {
|
|
|
+ const pattern = /^\d{4}-\d{2}$/;
|
|
|
+ return pattern.test(monthKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证openid格式
|
|
|
+ * @param {string} openid - 微信openid
|
|
|
+ * @returns {boolean} 是否有效
|
|
|
+ */
|
|
|
+ validateOpenid(openid) {
|
|
|
+ return openid && typeof openid === 'string' && openid.length === 28;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+const calendarManager = new CalendarRecordManager();
|
|
|
+
|
|
|
+// 保存记录
|
|
|
+async function saveCurrentMonthRecord(openid, events) {
|
|
|
+ const monthKey = calendarManager.formatMonthKey(new Date());
|
|
|
+ const result = await calendarManager.saveRecord(openid, monthKey, events);
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ console.log('保存成功:', result.data);
|
|
|
+ } else {
|
|
|
+ console.error('保存失败:', result.error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取记录
|
|
|
+async function loadCurrentMonthRecord(openid) {
|
|
|
+ const monthKey = calendarManager.formatMonthKey(new Date());
|
|
|
+ const result = await calendarManager.getRecord(openid, monthKey);
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ if (result.hasRecord) {
|
|
|
+ console.log('找到记录:', result.data.calendar_content);
|
|
|
+ return result.data.calendar_content;
|
|
|
+ } else {
|
|
|
+ console.log('暂无记录');
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error('获取失败:', result.error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 2. Vue.js 集成示例
|
|
|
+
|
|
|
+```vue
|
|
|
+<template>
|
|
|
+ <div class="calendar-container">
|
|
|
+ <h2>{{ currentMonthText }}日历</h2>
|
|
|
+
|
|
|
+ <!-- 日历内容显示 -->
|
|
|
+ <div v-if="hasRecord" class="calendar-content">
|
|
|
+ <div v-for="item in calendarContent" :key="item.date" class="calendar-item">
|
|
|
+ <div class="date">{{ item.date }}</div>
|
|
|
+ <div class="events">
|
|
|
+ <span v-for="event in item.events" :key="event" class="event-tag">
|
|
|
+ {{ event }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="notes">{{ item.notes }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 无记录提示 -->
|
|
|
+ <div v-else class="no-record">
|
|
|
+ <p>本月暂无日历记录</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 操作按钮 -->
|
|
|
+ <div class="actions">
|
|
|
+ <button @click="loadRecord" :disabled="loading">刷新</button>
|
|
|
+ <button @click="saveRecord" :disabled="loading">保存</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'CalendarRecord',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ openid: 'wx_openid_abcd1234567890123456', // 从登录状态获取
|
|
|
+ currentMonth: new Date(),
|
|
|
+ calendarContent: [],
|
|
|
+ hasRecord: false,
|
|
|
+ loading: false
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ currentMonthText() {
|
|
|
+ return `${this.currentMonth.getFullYear()}年${this.currentMonth.getMonth() + 1}月`;
|
|
|
+ },
|
|
|
+ monthKey() {
|
|
|
+ const year = this.currentMonth.getFullYear();
|
|
|
+ const month = String(this.currentMonth.getMonth() + 1).padStart(2, '0');
|
|
|
+ return `${year}-${month}`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.loadRecord();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async loadRecord() {
|
|
|
+ this.loading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/parse/get-calendar-record?openid=${this.openid}&month_key=${this.monthKey}`);
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ this.hasRecord = result.result.id !== null;
|
|
|
+ this.calendarContent = result.result.calendar_content || [];
|
|
|
+ } else {
|
|
|
+ this.$message.error('获取日历记录失败: ' + result.error);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('网络请求失败: ' + error.message);
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async saveRecord() {
|
|
|
+ // 这里应该从表单或编辑器获取要保存的数据
|
|
|
+ const dataToSave = [
|
|
|
+ {
|
|
|
+ date: "2024-01-15",
|
|
|
+ events: ["会议", "约会"],
|
|
|
+ notes: "重要日程"
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ this.loading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/parse/save-calendar-record', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ openid: this.openid,
|
|
|
+ month_key: this.monthKey,
|
|
|
+ calendar_content: dataToSave
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ this.$message.success('保存成功');
|
|
|
+ this.loadRecord(); // 重新加载数据
|
|
|
+ } else {
|
|
|
+ this.$message.error('保存失败: ' + result.error);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('网络请求失败: ' + error.message);
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+### 3. React 集成示例
|
|
|
+
|
|
|
+```jsx
|
|
|
+import React, { useState, useEffect, useCallback } from 'react';
|
|
|
+
|
|
|
+const CalendarRecord = ({ openid }) => {
|
|
|
+ const [calendarContent, setCalendarContent] = useState([]);
|
|
|
+ const [hasRecord, setHasRecord] = useState(false);
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [currentMonth, setCurrentMonth] = useState(new Date());
|
|
|
+
|
|
|
+ // 格式化月份键
|
|
|
+ const formatMonthKey = useCallback((date) => {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ return `${year}-${month}`;
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const monthKey = formatMonthKey(currentMonth);
|
|
|
+
|
|
|
+ // 加载日历记录
|
|
|
+ const loadRecord = useCallback(async () => {
|
|
|
+ setLoading(true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/parse/get-calendar-record?openid=${openid}&month_key=${monthKey}`);
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ setHasRecord(result.result.id !== null);
|
|
|
+ setCalendarContent(result.result.calendar_content || []);
|
|
|
+ } else {
|
|
|
+ console.error('获取失败:', result.error);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('网络错误:', error);
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ }, [openid, monthKey]);
|
|
|
+
|
|
|
+ // 保存日历记录
|
|
|
+ const saveRecord = async (dataToSave) => {
|
|
|
+ setLoading(true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/parse/save-calendar-record', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ openid: openid,
|
|
|
+ month_key: monthKey,
|
|
|
+ calendar_content: dataToSave
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.return_code === 200) {
|
|
|
+ console.log('保存成功');
|
|
|
+ loadRecord(); // 重新加载
|
|
|
+ } else {
|
|
|
+ console.error('保存失败:', result.error);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('网络错误:', error);
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (openid) {
|
|
|
+ loadRecord();
|
|
|
+ }
|
|
|
+ }, [openid, loadRecord]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="calendar-container">
|
|
|
+ <h2>{currentMonth.getFullYear()}年{currentMonth.getMonth() + 1}月日历</h2>
|
|
|
+
|
|
|
+ {hasRecord ? (
|
|
|
+ <div className="calendar-content">
|
|
|
+ {calendarContent.map((item, index) => (
|
|
|
+ <div key={index} className="calendar-item">
|
|
|
+ <div className="date">{item.date}</div>
|
|
|
+ <div className="events">
|
|
|
+ {item.events?.map((event, idx) => (
|
|
|
+ <span key={idx} className="event-tag">{event}</span>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ <div className="notes">{item.notes}</div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="no-record">
|
|
|
+ <p>本月暂无日历记录</p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div className="actions">
|
|
|
+ <button onClick={loadRecord} disabled={loading}>
|
|
|
+ {loading ? '加载中...' : '刷新'}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default CalendarRecord;
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 错误处理指南
|
|
|
+
|
|
|
+### 常见错误码及处理方案
|
|
|
+
|
|
|
+| 错误码 | 错误信息 | 原因 | 解决方案 |
|
|
|
+|--------|---------|------|---------|
|
|
|
+| 400 | 请求体不能为空 | POST请求未提供请求体 | 检查请求体格式 |
|
|
|
+| 400 | 缺少必填参数: openid | 未提供openid参数 | 确保传递有效的openid |
|
|
|
+| 400 | 缺少必填参数: month_key | 未提供month_key参数 | 确保传递正确格式的月份键 |
|
|
|
+| 400 | 缺少必填参数: calendar_content | 未提供calendar_content参数 | 确保传递日历内容数组 |
|
|
|
+| 400 | 无效的openid格式 | openid格式不正确 | 检查openid是否为28位字符串 |
|
|
|
+| 400 | 无效的月份格式 | month_key格式错误 | 确保使用YYYY-MM格式 |
|
|
|
+| 500 | 数据库连接失败 | 服务器数据库问题 | 联系技术支持 |
|
|
|
+| 500 | 服务器内部错误 | 其他服务器错误 | 稍后重试或联系技术支持 |
|
|
|
+
|
|
|
+### 错误处理最佳实践
|
|
|
+
|
|
|
+```javascript
|
|
|
+async function handleApiCall(apiFunction) {
|
|
|
+ try {
|
|
|
+ const result = await apiFunction();
|
|
|
+
|
|
|
+ switch (result.code) {
|
|
|
+ case 200:
|
|
|
+ return { success: true, data: result.data };
|
|
|
+
|
|
|
+ case 400:
|
|
|
+ // 参数错误,显示具体错误信息
|
|
|
+ console.warn('参数错误:', result.error);
|
|
|
+ return { success: false, error: result.error, needsRetry: false };
|
|
|
+
|
|
|
+ case 500:
|
|
|
+ // 服务器错误,可以重试
|
|
|
+ console.error('服务器错误:', result.error);
|
|
|
+ return { success: false, error: '服务器暂时不可用,请稍后重试', needsRetry: true };
|
|
|
+
|
|
|
+ default:
|
|
|
+ console.error('未知错误:', result.error);
|
|
|
+ return { success: false, error: '操作失败,请重试', needsRetry: true };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('网络错误:', error);
|
|
|
+ return { success: false, error: '网络连接失败,请检查网络', needsRetry: true };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 带重试的API调用
|
|
|
+async function apiCallWithRetry(apiFunction, maxRetries = 3) {
|
|
|
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
|
+ const result = await handleApiCall(apiFunction);
|
|
|
+
|
|
|
+ if (result.success || !result.needsRetry) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (attempt < maxRetries) {
|
|
|
+ console.log(`第${attempt}次尝试失败,${1000 * attempt}ms后重试...`);
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return { success: false, error: '多次重试失败,请稍后再试' };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 开发环境配置
|
|
|
+
|
|
|
+### 环境变量配置
|
|
|
+
|
|
|
+在开发环境中,需要配置以下环境变量:
|
|
|
+
|
|
|
+```bash
|
|
|
+# .env.development
|
|
|
+REACT_APP_API_BASE_URL=http://localhost:5000
|
|
|
+VUE_APP_API_BASE_URL=http://localhost:5000
|
|
|
+
|
|
|
+# .env.production
|
|
|
+REACT_APP_API_BASE_URL=https://your-production-domain.com
|
|
|
+VUE_APP_API_BASE_URL=https://your-production-domain.com
|
|
|
+```
|
|
|
+
|
|
|
+### 代理配置
|
|
|
+
|
|
|
+#### Webpack Dev Server (React/Vue)
|
|
|
+```javascript
|
|
|
+// webpack.config.js 或 vue.config.js
|
|
|
+module.exports = {
|
|
|
+ devServer: {
|
|
|
+ proxy: {
|
|
|
+ '/api': {
|
|
|
+ target: 'http://localhost:5000',
|
|
|
+ changeOrigin: true,
|
|
|
+ secure: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+#### Create React App
|
|
|
+```javascript
|
|
|
+// package.json
|
|
|
+{
|
|
|
+ "name": "your-app",
|
|
|
+ "proxy": "http://localhost:5000",
|
|
|
+ // ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 测试用例
|
|
|
+
|
|
|
+### 单元测试示例
|
|
|
+
|
|
|
+```javascript
|
|
|
+// calendar-api.test.js
|
|
|
+import { CalendarRecordManager } from './calendar-manager';
|
|
|
+
|
|
|
+describe('CalendarRecordManager', () => {
|
|
|
+ let manager;
|
|
|
+ const mockOpenid = 'wx_openid_test1234567890123456';
|
|
|
+ const mockMonthKey = '2024-01';
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ manager = new CalendarRecordManager();
|
|
|
+ // Mock fetch
|
|
|
+ global.fetch = jest.fn();
|
|
|
+ });
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ jest.resetAllMocks();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('保存日历记录 - 成功', async () => {
|
|
|
+ const mockResponse = {
|
|
|
+ reason: 'successed',
|
|
|
+ return_code: 200,
|
|
|
+ result: {
|
|
|
+ id: 1,
|
|
|
+ openid: mockOpenid,
|
|
|
+ month_key: mockMonthKey,
|
|
|
+ calendar_content: [
|
|
|
+ {
|
|
|
+ date: '2024-01-01',
|
|
|
+ events: ['测试事件'],
|
|
|
+ notes: '测试备注'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetch.mockResolvedValueOnce({
|
|
|
+ json: async () => mockResponse
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await manager.saveRecord(mockOpenid, mockMonthKey, [
|
|
|
+ {
|
|
|
+ date: '2024-01-01',
|
|
|
+ events: ['测试事件'],
|
|
|
+ notes: '测试备注'
|
|
|
+ }
|
|
|
+ ]);
|
|
|
+
|
|
|
+ expect(result.success).toBe(true);
|
|
|
+ expect(result.data.id).toBe(1);
|
|
|
+ expect(fetch).toHaveBeenCalledWith(
|
|
|
+ '/api/parse/save-calendar-record',
|
|
|
+ expect.objectContaining({
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ openid: mockOpenid,
|
|
|
+ month_key: mockMonthKey,
|
|
|
+ calendar_content: [
|
|
|
+ {
|
|
|
+ date: '2024-01-01',
|
|
|
+ events: ['测试事件'],
|
|
|
+ notes: '测试备注'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ })
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ test('获取日历记录 - 有记录', async () => {
|
|
|
+ const mockResponse = {
|
|
|
+ reason: 'successed',
|
|
|
+ return_code: 200,
|
|
|
+ result: {
|
|
|
+ id: 1,
|
|
|
+ openid: mockOpenid,
|
|
|
+ month_key: mockMonthKey,
|
|
|
+ calendar_content: [
|
|
|
+ {
|
|
|
+ date: '2024-01-01',
|
|
|
+ events: ['测试事件'],
|
|
|
+ notes: '测试备注'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetch.mockResolvedValueOnce({
|
|
|
+ json: async () => mockResponse
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await manager.getRecord(mockOpenid, mockMonthKey);
|
|
|
+
|
|
|
+ expect(result.success).toBe(true);
|
|
|
+ expect(result.hasRecord).toBe(true);
|
|
|
+ expect(result.data.calendar_content).toHaveLength(1);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('获取日历记录 - 无记录', async () => {
|
|
|
+ const mockResponse = {
|
|
|
+ reason: 'successed',
|
|
|
+ return_code: 200,
|
|
|
+ result: {
|
|
|
+ id: null,
|
|
|
+ openid: mockOpenid,
|
|
|
+ month_key: mockMonthKey,
|
|
|
+ calendar_content: [],
|
|
|
+ created_at: null,
|
|
|
+ updated_at: null
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetch.mockResolvedValueOnce({
|
|
|
+ json: async () => mockResponse
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await manager.getRecord(mockOpenid, mockMonthKey);
|
|
|
+
|
|
|
+ expect(result.success).toBe(true);
|
|
|
+ expect(result.hasRecord).toBe(false);
|
|
|
+ expect(result.data.calendar_content).toEqual([]);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('参数验证 - 无效的openid', async () => {
|
|
|
+ const isValid = manager.validateOpenid('invalid_openid');
|
|
|
+ expect(isValid).toBe(false);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('参数验证 - 有效的openid', async () => {
|
|
|
+ const isValid = manager.validateOpenid(mockOpenid);
|
|
|
+ expect(isValid).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('月份键格式化', () => {
|
|
|
+ const date = new Date('2024-01-15');
|
|
|
+ const monthKey = manager.formatMonthKey(date);
|
|
|
+ expect(monthKey).toBe('2024-01');
|
|
|
+ });
|
|
|
+
|
|
|
+ test('月份键验证 - 有效格式', () => {
|
|
|
+ expect(manager.validateMonthKey('2024-01')).toBe(true);
|
|
|
+ expect(manager.validateMonthKey('2024-12')).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('月份键验证 - 无效格式', () => {
|
|
|
+ expect(manager.validateMonthKey('2024-1')).toBe(false);
|
|
|
+ expect(manager.validateMonthKey('24-01')).toBe(false);
|
|
|
+ expect(manager.validateMonthKey('2024/01')).toBe(false);
|
|
|
+ });
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 性能优化建议
|
|
|
+
|
|
|
+### 1. 请求优化
|
|
|
+- 使用防抖(debounce)避免频繁保存
|
|
|
+- 实现本地缓存减少重复请求
|
|
|
+- 使用 Loading 状态提升用户体验
|
|
|
+
|
|
|
+### 2. 数据处理优化
|
|
|
+- 对大量日历数据进行分页处理
|
|
|
+- 使用虚拟滚动处理长列表
|
|
|
+- 实现增量更新而非全量替换
|
|
|
+
|
|
|
+### 3. 缓存策略
|
|
|
+```javascript
|
|
|
+class CachedCalendarManager extends CalendarRecordManager {
|
|
|
+ constructor(baseUrl = '', cacheTimeout = 5 * 60 * 1000) { // 5分钟缓存
|
|
|
+ super(baseUrl);
|
|
|
+ this.cache = new Map();
|
|
|
+ this.cacheTimeout = cacheTimeout;
|
|
|
+ }
|
|
|
+
|
|
|
+ getCacheKey(openid, monthKey) {
|
|
|
+ return `${openid}:${monthKey}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ async getRecord(openid, monthKey) {
|
|
|
+ const cacheKey = this.getCacheKey(openid, monthKey);
|
|
|
+ const cached = this.cache.get(cacheKey);
|
|
|
+
|
|
|
+ // 检查缓存是否有效
|
|
|
+ if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
|
|
|
+ return cached.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从服务器获取数据
|
|
|
+ const result = await super.getRecord(openid, monthKey);
|
|
|
+
|
|
|
+ // 缓存成功结果
|
|
|
+ if (result.success) {
|
|
|
+ this.cache.set(cacheKey, {
|
|
|
+ data: result,
|
|
|
+ timestamp: Date.now()
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ async saveRecord(openid, monthKey, calendarContent) {
|
|
|
+ const result = await super.saveRecord(openid, monthKey, calendarContent);
|
|
|
+
|
|
|
+ // 保存成功后清除缓存
|
|
|
+ if (result.success) {
|
|
|
+ const cacheKey = this.getCacheKey(openid, monthKey);
|
|
|
+ this.cache.delete(cacheKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ clearCache(openid = null, monthKey = null) {
|
|
|
+ if (openid && monthKey) {
|
|
|
+ // 清除特定缓存
|
|
|
+ const cacheKey = this.getCacheKey(openid, monthKey);
|
|
|
+ this.cache.delete(cacheKey);
|
|
|
+ } else {
|
|
|
+ // 清除所有缓存
|
|
|
+ this.cache.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 联系与支持
|
|
|
+
|
|
|
+如有技术问题或需要支持,请联系:
|
|
|
+
|
|
|
+- **技术支持邮箱**: support@dataops-platform.com
|
|
|
+- **开发文档**: https://docs.dataops-platform.com
|
|
|
+- **API更新日志**: https://docs.dataops-platform.com/changelog
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 更新日志
|
|
|
+
|
|
|
+### v1.0.0 (2024-01-15)
|
|
|
+- 初始版本发布
|
|
|
+- 支持保存日历记录功能
|
|
|
+- 支持获取日历记录功能
|
|
|
+- 提供完整的前端集成示例
|
|
|
+- 包含错误处理和性能优化建议
|