diff --git a/pages/institutionalStructure/donationRecord.vue b/pages/institutionalStructure/donationRecord.vue index 0bf5ea3..3ef2ca7 100644 --- a/pages/institutionalStructure/donationRecord.vue +++ b/pages/institutionalStructure/donationRecord.vue @@ -30,7 +30,7 @@ /> - + { + console.log('建制数据加载更多成功:', data.length, '条') + this.institutionalData = this.dataList + } + }) + } else { + await this.refreshData({ + apiCall: getInstitutionalList, + dataTransformer: InstitutionalDataFormatter.transformData, + onSuccess: (data, response) => { + console.log('建制数据刷新成功:', data.length, '条') + this.institutionalData = this.dataList + } + }) + } + console.log('建制数据已更新, 数量:', this.institutionalData.length) } catch (error) { console.error('获取建制数据失败:', error) @@ -84,11 +96,6 @@ export default { } }, - // 刷新数据 - refreshData() { - this.getInstitutionalData() - }, - // 处理查看详细 handleViewDetail(data) { console.log('查看详细:', data.item) diff --git a/pages/institutionalStructure/mixins/README.md b/pages/institutionalStructure/mixins/README.md new file mode 100644 index 0000000..1fedd12 --- /dev/null +++ b/pages/institutionalStructure/mixins/README.md @@ -0,0 +1,216 @@ +# 数据管理 Mixin 重构说明 + +## 问题分析 + +原来的 `data-manager.js` 存在以下复用性问题: + +1. **硬编码的响应结构**:假设所有API都返回 `response.rows` 格式 +2. **固定的分页参数**:只能传递 `pageNum` 和 `pageSize`,无法处理复杂查询条件 +3. **缺乏灵活性**:无法处理搜索、筛选等场景 +4. **错误处理过于简单**:统一的错误提示可能不适合所有场景 +5. **缺乏扩展性**:难以添加缓存、状态管理等功能 + +## 重构改进 + +### 1. 灵活的配置选项 + +```javascript +// 重构前 +await this.fetchData(false, apiCall, dataTransformer) + +// 重构后 +await this.fetchData({ + isLoadMore: false, + apiCall, + dataTransformer, + params: { keyword: '搜索词' }, + dataPath: 'data.list.rows', + totalPath: 'data.total', + onSuccess: (data, response) => console.log('成功'), + onError: (errorMsg) => console.error('失败'), + showLoading: true, + showError: true +}) +``` + +### 2. 支持多种数据格式 + +```javascript +// 支持不同的响应结构 +dataPath: 'data.list.rows' // 嵌套结构 +dataPath: 'data' // 简单结构 +dataPath: 'rows' // 直接结构 +``` + +### 3. 增强的功能 + +- **搜索功能**:`searchData()` 方法 +- **状态管理**:`getDataState()` 方法 +- **分页重置**:`resetPagination()` 方法 +- **参数管理**:自动保存当前查询参数 +- **回调支持**:成功和失败回调函数 + +### 4. 更好的错误处理 + +```javascript +// 可配置是否显示错误提示 +showError: false, // 自定义错误处理 +onError: (errorMsg, response) => { + // 自定义错误处理逻辑 +} +``` + +## 使用示例 + +### 基础使用 + +```javascript +import { dataManagerMixin } from './data-manager.js' + +export default { + mixins: [dataManagerMixin], + + methods: { + async loadData() { + await this.refreshData({ + apiCall: this.getListAPI, + dataPath: 'data.list', + onSuccess: (data) => { + console.log('数据加载成功:', data.length) + } + }) + } + } +} +``` + +### 带搜索的使用 + +```javascript +async searchData(keyword) { + await this.searchData( + { keyword, status: 'active' }, + { + apiCall: this.searchAPI, + dataPath: 'data.items', + onSuccess: (data) => { + console.log('搜索完成:', data.length) + } + } + ) +} +``` + +### 加载更多 + +```javascript +async loadMore() { + await this.loadMoreData({ + apiCall: this.getListAPI, + dataPath: 'data.list', + onSuccess: (data) => { + console.log('加载更多:', data.length) + } + }) +} +``` + +## 高级功能 + +### 1. 缓存支持 + +```javascript +import { cachedDataMixin } from './data-manager-example.js' + +export default { + mixins: [cachedDataMixin], + + methods: { + async loadDataWithCache() { + await this.fetchDataWithCache({ + apiCall: this.getListAPI, + params: { category: 'news' }, + forceRefresh: false // 使用缓存 + }) + } + } +} +``` + +### 2. 状态管理 + +```javascript +// 获取当前数据状态 +const state = this.getDataState() +console.log('当前页码:', state.pageNum) +console.log('总数据量:', state.total) +console.log('查询参数:', state.currentParams) +``` + +### 3. 自定义数据转换 + +```javascript +transformData(dataArray) { + return dataArray.map(item => ({ + id: item.id, + title: item.name, + date: this.formatDate(item.createTime), + status: item.status === 1 ? '启用' : '禁用' + })) +} +``` + +## 迁移指南 + +### 从旧版本迁移 + +1. **更新 mixin 引用**: +```javascript +// 旧版本 +export const myMixin = { + // 直接定义分页逻辑 +} + +// 新版本 +import { dataManagerMixin } from './data-manager.js' +export const myMixin = { + mixins: [dataManagerMixin], + // 使用通用的分页逻辑 +} +``` + +2. **更新方法调用**: +```javascript +// 旧版本 +await this.fetchData(false, apiCall, transformer) + +// 新版本 +await this.refreshData({ + apiCall, + dataTransformer: transformer +}) +``` + +3. **更新数据引用**: +```javascript +// 旧版本 +this.myList = [] + +// 新版本 +// 使用 this.dataList (来自 dataManagerMixin) +``` + +## 优势总结 + +1. **高复用性**:一个 mixin 适用于所有列表页面 +2. **强扩展性**:支持缓存、状态管理、自定义回调等 +3. **易维护性**:统一的错误处理和状态管理 +4. **灵活性**:支持多种数据格式和查询条件 +5. **开发效率**:减少重复代码,提高开发速度 + +## 注意事项 + +1. 确保 API 返回格式包含 `code` 字段用于判断成功状态 +2. 数据路径 `dataPath` 要根据实际 API 响应格式设置 +3. 使用 `dataTransformer` 时注意保持数据结构的统一性 +4. 合理使用缓存功能,避免内存泄漏 \ No newline at end of file diff --git a/pages/institutionalStructure/mixins/data-manager.js b/pages/institutionalStructure/mixins/data-manager.js index ce60296..4951be0 100644 --- a/pages/institutionalStructure/mixins/data-manager.js +++ b/pages/institutionalStructure/mixins/data-manager.js @@ -1,6 +1,7 @@ /** * 数据管理 Mixin * 提供通用的数据获取、分页加载功能 + * 支持多种数据格式和灵活的配置 */ export const dataManagerMixin = { data() { @@ -12,85 +13,221 @@ export const dataManagerMixin = { // 分页参数 pageNum: 1, pageSize: 10, - hasMore: true + hasMore: true, + // 总数据量 + total: 0, + // 当前查询参数 + currentParams: {} } }, methods: { /** * 获取数据 - * @param {boolean} isLoadMore 是否为加载更多 - * @param {Function} apiCall API调用函数 - * @param {Function} dataTransformer 数据转换函数 + * @param {Object} options 配置选项 + * @param {boolean} options.isLoadMore 是否为加载更多 + * @param {Function} options.apiCall API调用函数 + * @param {Function} options.dataTransformer 数据转换函数 + * @param {Object} options.params 查询参数 + * @param {string} options.dataPath 数据路径,如 'data.list.rows' + * @param {string} options.totalPath 总数路径,如 'data.total' + * @param {Function} options.onSuccess 成功回调 + * @param {Function} options.onError 错误回调 + * @param {boolean} options.showLoading 是否显示loading + * @param {boolean} options.showError 是否显示错误提示 */ - async fetchData(isLoadMore = false, apiCall, dataTransformer) { - // 只在加载更多时设置loading状态,初始加载使用页面级别loading - if (isLoadMore) { - this.loading = true + async fetchData(options = {}) { + const { + isLoadMore = false, + apiCall, + dataTransformer, + params = {}, + dataPath = 'rows', + totalPath = 'total', + onSuccess, + onError, + showLoading = true, + showError = true + } = options + + if (!apiCall) { + console.error('fetchData: apiCall 是必需的') + return + } + + // 设置loading状态 + if (showLoading) { + this.loading = true } try { + // 更新页码 if (isLoadMore) { this.pageNum++ } else { this.pageNum = 1 } - // 调用API - const response = await apiCall({ + // 构建请求参数 + const requestParams = { pageNum: this.pageNum, - pageSize: this.pageSize - }) + pageSize: this.pageSize, + ...this.currentParams, + ...params + } + + // 调用API + const response = await apiCall(requestParams) + + console.log('API响应:', response) + console.log('数据路径:', dataPath) + console.log('总数路径:', totalPath) if (response.code === 200) { - // 转换数据 - const newData = dataTransformer ? dataTransformer(response.rows) : response.rows || [] + // 根据路径获取数据 + const dataArray = this.getNestedValue(response, dataPath) || [] + const total = this.getNestedValue(response, totalPath) || 0 + console.log('提取的数据数组:', dataArray) + console.log('总数:', total) + + // 转换数据 + const newData = dataTransformer ? dataTransformer(dataArray) : dataArray + + console.log('转换后的数据:', newData) + + // 更新数据 if (isLoadMore) { this.dataList = [...this.dataList, ...newData] } else { this.dataList = newData } - // 判断是否还有更多数据 - this.hasMore = response.rows.length === this.pageSize + console.log('更新后的 dataList:', this.dataList) + + // 更新状态 + this.total = total + this.hasMore = newData.length === this.pageSize + this.currentParams = { ...requestParams } + + // 成功回调 + if (onSuccess) { + onSuccess(newData, response) + } } else { - uni.showToast({ - title: response.msg || '获取数据失败', - icon: 'none' - }) + const errorMsg = response.msg || '获取数据失败' + if (showError) { + uni.showToast({ + title: errorMsg, + icon: 'none' + }) + } + if (onError) { + onError(errorMsg, response) + } } } catch (error) { console.error('获取数据失败:', error) - uni.showToast({ - title: '网络错误', - icon: 'none' - }) + const errorMsg = '网络错误' + if (showError) { + uni.showToast({ + title: errorMsg, + icon: 'none' + }) + } + if (onError) { + onError(errorMsg, error) + } } finally { - // 只在加载更多时清除loading状态 - if (isLoadMore) { - this.loading = false + if (showLoading) { + this.loading = false } } }, /** * 刷新数据 - * @param {Function} apiCall API调用函数 - * @param {Function} dataTransformer 数据转换函数 + * @param {Object} options 配置选项 */ - refreshData(apiCall, dataTransformer) { - this.fetchData(false, apiCall, dataTransformer) + refreshData(options = {}) { + return this.fetchData({ + isLoadMore: false, + ...options + }) }, /** * 加载更多数据 - * @param {Function} apiCall API调用函数 - * @param {Function} dataTransformer 数据转换函数 + * @param {Object} options 配置选项 */ - loadMoreData(apiCall, dataTransformer) { + loadMoreData(options = {}) { if (this.hasMore && !this.loading) { - this.fetchData(true, apiCall, dataTransformer) + return this.fetchData({ + isLoadMore: true, + ...options + }) + } + }, + + /** + * 搜索数据 + * @param {Object} searchParams 搜索参数 + * @param {Object} options 配置选项 + */ + searchData(searchParams = {}, options = {}) { + return this.fetchData({ + isLoadMore: false, + params: searchParams, + ...options + }) + }, + + /** + * 重置分页状态 + */ + resetPagination() { + this.pageNum = 1 + this.hasMore = true//可能就单页 + this.dataList = [] + this.total = 0 + this.currentParams = {} + }, + + /** + * 获取嵌套对象的值 + * @param {Object} obj 对象 + * @param {string} path 路径,如 'data.list.rows' + * @returns {*} 值 + */ + getNestedValue(obj, path) { + if (!path) return obj + return path.split('.').reduce((current, key) => { + return current && current[key] !== undefined ? current[key] : null + }, obj) + }, + + /** + * 设置分页大小 + * @param {number} pageSize 分页大小 + */ + setPageSize(pageSize) { + this.pageSize = pageSize + this.resetPagination() + }, + + /** + * 获取当前数据状态 + * @returns {Object} 数据状态 + */ + getDataState() { + return { + dataList: this.dataList, + loading: this.loading, + pageNum: this.pageNum, + pageSize: this.pageSize, + hasMore: this.hasMore, + total: this.total, + currentParams: this.currentParams } } } diff --git a/pages/institutionalStructure/mixins/donation-mixin.js b/pages/institutionalStructure/mixins/donation-mixin.js index 44aedc6..026741f 100644 --- a/pages/institutionalStructure/mixins/donation-mixin.js +++ b/pages/institutionalStructure/mixins/donation-mixin.js @@ -1,34 +1,34 @@ /** * 捐款记录相关 Mixin * 提供捐款记录的数据获取、搜索、分页等功能 + * 基于重构后的 data-manager.js */ +import { dataManagerMixin } from './data-manager.js' import { getDonorList } from '@/api/donor/donor.js' import { getInstitutionalDetail } from '@/api/institutionalStructure/institutionalStructureDetail.js' export const donationMixin = { + mixins: [dataManagerMixin], + data() { return { - // 捐款记录相关数据 - donationList: [], + // 项目信息 projectInfo: {}, - loading: false, + // 搜索关键词 searchKeyword: '', - formedId: '', - // 分页参数 - pageNum: 1, - pageSize: 10, - hasMore: true + // 项目ID + formedId: '' } }, computed: { // 计算总造价(从项目详情获取,如果没有则计算捐款总和) totalAmount() { - return this.projectInfo.totalAmount || this.donationList.reduce((sum, item) => sum + item.amount, 0) + return this.projectInfo.totalAmount || this.dataList.reduce((sum, item) => sum + item.amount, 0) }, // 计算参与捐款人次(从项目详情获取,如果没有则计算捐款记录数量) participantCount() { - return this.projectInfo.donorCount || this.donationList.length + return this.projectInfo.donorCount || this.dataList.length } }, @@ -59,87 +59,17 @@ export const donationMixin = { }) } }, - /** - * 获取捐款记录 - * @param {string} keyword 搜索关键词 - */ - async loadDonationRecords(keyword = '') { - this.loading = true - try { - const params = { - formedId: this.formedId, - pageNum: this.pageNum, - pageSize: this.pageSize, - minAmount: 1, - maxAmount: 10000, - sortAmount: 'amount', - orderAmount: 'asc', - sortTime: 'time', - orderTime: 'desc' - } - - // 如果有搜索关键词,添加姓名搜索 - if (keyword) { - params.realName = keyword - } - - const response = await getDonorList(params) - console.log('捐款记录API响应:', response) - - if (response.code === 200) { - // 根据实际后端数据结构获取数据数组 - let dataArray = response.data.list.rows - - // 转换数据格式 - const newData = dataArray.map(item => ({ - id: item.id, - name: item.realName, - amount: item.amount, - time: this.formatDate(item.donationDate) - })) - - // 如果是第一页,直接替换数据 - if (this.pageNum === 1) { - this.donationList = newData - } else { - // 如果是加载更多,追加数据 - this.donationList = [...this.donationList, ...newData] - } - - // 判断是否还有更多数据 - this.hasMore = newData.length === this.pageSize - } else { - uni.showToast({ - title: response.msg || '获取数据失败', - icon: 'none' - }) - } - - } catch (error) { - console.error('获取捐款记录失败:', error) - uni.showToast({ - title: '获取数据失败', - icon: 'none' - }) - } finally { - this.loading = false - } - }, /** - * 搜索捐款记录 - * @param {string} val 搜索关键词 + * 捐款记录数据转换器 */ - onSearch(val) { - this.pageNum = 1 // 重置页码 - this.loadDonationRecords(val) - }, - - /** - * 筛选功能 - */ - onFilter() { - uni.showToast({ title: '筛选功能开发中', icon: 'none' }) + transformDonationData(dataArray) { + return dataArray.map(item => ({ + id: item.id, + name: item.realName, + amount: item.amount, + time: this.formatDate(item.donationDate) + })) }, /** @@ -156,6 +86,93 @@ export const donationMixin = { return `${year}/${month}/${day}` }, + /** + * 获取基础查询参数 + */ + getBaseParams() { + return { + formedId: this.formedId, + minAmount: 1, + maxAmount: 10000, + sortAmount: 'amount', + orderAmount: 'asc', + sortTime: 'time', + orderTime: 'desc' + } + }, + + /** + * 获取捐款记录 + * @param {string} keyword 搜索关键词 + */ + async loadDonationRecords(keyword = '') { + const baseParams = this.getBaseParams() + + // 如果有搜索关键词,添加姓名搜索 + if (keyword) { + baseParams.realName = keyword + } + + await this.refreshData({ + apiCall: getDonorList, + dataTransformer: this.transformDonationData, + params: baseParams, + dataPath: 'data.list.rows', + totalPath: 'data.total', + onSuccess: (data, response) => { + console.log('捐款记录加载成功:', data.length, '条') + }, + onError: (errorMsg) => { + console.error('捐款记录加载失败:', errorMsg) + } + }) + }, + + /** + * 搜索捐款记录 + * @param {string} val 搜索关键词 + */ + async onSearch(val) { + this.searchKeyword = val + await this.searchData( + { ...this.getBaseParams(), realName: val }, + { + apiCall: getDonorList, + dataTransformer: this.transformDonationData, + dataPath: 'data.list.rows', + totalPath: 'data.total', + onSuccess: (data, response) => { + console.log('搜索完成,找到:', data.length, '条记录') + } + } + ) + }, + + /** + * 筛选功能 + */ + onFilter() { + uni.showToast({ title: '筛选功能开发中', icon: 'none' }) + }, + + /** + * 加载更多捐款记录 + */ + async loadMoreDonationRecords() { + const currentParams = this.getDataState().currentParams + + await this.loadMoreData({ + apiCall: getDonorList, + dataTransformer: this.transformDonationData, + params: currentParams, + dataPath: 'data.list.rows', + totalPath: 'data.total', + onSuccess: (data, response) => { + console.log('加载更多完成,新增:', data.length, '条记录') + } + }) + }, + /** * 初始化数据 * @param {string} formedId 建制ID @@ -175,6 +192,21 @@ export const donationMixin = { icon: 'none' }) } + }, + + /** + * 重置搜索 + */ + resetSearch() { + this.searchKeyword = '' + this.loadDonationRecords() + }, + + /** + * 刷新数据 + */ + async refreshDonationData() { + await this.loadDonationRecords(this.searchKeyword) } } } \ No newline at end of file