diff --git a/src/composables/useHealthList.js b/src/composables/useHealthList.js new file mode 100644 index 0000000..ff9f6fd --- /dev/null +++ b/src/composables/useHealthList.js @@ -0,0 +1,126 @@ +import { ref } from 'vue' +import { onLoad, onShow } from '@dcloudio/uni-app' + +/** + * 健康模块列表页通用逻辑 composable + * + * @param {Function} listApi - 列表查询API函数,接收 { pageSize, pageNum, ...queryParams } + * @param {Object} options - 可选配置 + * @param {number} options.pageSize - 每页数量,默认 10 + * @param {Function} options.onLoadCallback - onLoad 时额外执行的回调 + * @param {Function} options.transformRows - 自定义数据转换函数 + * @returns {Object} + */ +export function useHealthList(listApi, options = {}) { + const { pageSize = 10, onLoadCallback = null, transformRows = null } = options + + const pageNum = ref(1) + const listData = ref([]) + const isShow = ref(false) + const status = ref('loadmore') + + /** + * 获取列表数据 + * @param {Object} queryParams - 查询参数 + */ + function getList(queryParams = {}) { + status.value = 'loading' + listApi({ pageSize, pageNum: pageNum.value, ...queryParams }) + .then(res => { + const rows = transformRows ? transformRows(res) : (res.rows || []) + listData.value = listData.value.concat(rows) + if (listData.value.length < res.total) { + status.value = 'loadmore' + } else { + status.value = 'nomore' + } + }) + .catch(() => { + status.value = 'nomore' + }) + } + + /** + * 加载更多 + * @param {Object} queryParams - 查询参数 + */ + function loadmore(queryParams = {}) { + pageNum.value += 1 + if (status.value === 'loadmore') { + getList(queryParams) + } + } + + /** + * 重置列表并重新加载 + * @param {Object} queryParams - 查询参数 + */ + function resetList(queryParams = {}) { + pageNum.value = 1 + listData.value = [] + getList(queryParams) + } + + /** + * 初始化列表页生命周期 + * @param {Function} getQueryParams - 返回当前查询参数的函数 + */ + function initLifecycle(getQueryParams) { + onLoad(() => { + if (onLoadCallback) onLoadCallback() + getList(getQueryParams ? getQueryParams() : {}) + }) + + onShow(() => { + if (isShow.value) { + resetList(getQueryParams ? getQueryParams() : {}) + isShow.value = false + } + }) + } + + /** + * 通用删除处理 + * @param {Object} item - 要删除的项 + * @param {Function} deleteApi - 删除API函数 + * @param {Function} getQueryParams - 返回当前查询参数的函数 + */ + function handleDeleteAction(item, deleteApi, getQueryParams) { + uni.showModal({ + title: '确认删除', + content: '确定要删除这条记录吗?', + confirmText: '删除', + cancelText: '取消', + confirmColor: '#f5576c', + cancelColor: '#909399', + success: function (res) { + if (res.confirm) { + deleteApi(item.id).then(() => { + uni.showToast({ title: '删除成功', icon: 'success' }) + resetList(getQueryParams ? getQueryParams() : {}) + }) + } + } + }) + } + + /** + * 标记进入编辑/新增页面,返回时刷新 + */ + function markForRefresh() { + isShow.value = true + } + + return { + pageNum, + listData, + isShow, + status, + getList, + loadmore, + resetList, + initLifecycle, + handleDeleteAction, + markForRefresh + } +} diff --git a/src/pages/health/activity/list.vue b/src/pages/health/activity/list.vue index 74cf48d..cfe1b84 100644 --- a/src/pages/health/activity/list.vue +++ b/src/pages/health/activity/list.vue @@ -413,348 +413,83 @@ function getList() { diff --git a/src/pages/health/doctorRecord/costList.vue b/src/pages/health/doctorRecord/costList.vue index 5b2b75c..8047d87 100644 --- a/src/pages/health/doctorRecord/costList.vue +++ b/src/pages/health/doctorRecord/costList.vue @@ -243,11 +243,9 @@ function handleUpdateCost() { diff --git a/src/pages/health/doctorRecord/list.vue b/src/pages/health/doctorRecord/list.vue index 4afadb7..c9e6ded 100644 --- a/src/pages/health/doctorRecord/list.vue +++ b/src/pages/health/doctorRecord/list.vue @@ -470,400 +470,10 @@ function settingCancel() { diff --git a/src/pages/health/healthRecord/list.vue b/src/pages/health/healthRecord/list.vue index 6f9b033..fb475d8 100644 --- a/src/pages/health/healthRecord/list.vue +++ b/src/pages/health/healthRecord/list.vue @@ -401,421 +401,39 @@ function settingCancel() { diff --git a/src/pages/health/heightWeightRecord/list.vue b/src/pages/health/heightWeightRecord/list.vue index 95c69a2..04a80da 100644 --- a/src/pages/health/heightWeightRecord/list.vue +++ b/src/pages/health/heightWeightRecord/list.vue @@ -25,7 +25,7 @@ 筛选 - + 新增 @@ -33,7 +33,7 @@ - 测量日期 + 测量日期 - - + + + 重置 + + + + 确定 + \ No newline at end of file diff --git a/src/pages/health/marRecord/list.vue b/src/pages/health/marRecord/list.vue index 0120fbe..dff8f6b 100644 --- a/src/pages/health/marRecord/list.vue +++ b/src/pages/health/marRecord/list.vue @@ -483,422 +483,64 @@ function settingCancel() { \ No newline at end of file diff --git a/src/pages/health/medicationPlan/list.vue b/src/pages/health/medicationPlan/list.vue index 8093aa5..0fd8f43 100644 --- a/src/pages/health/medicationPlan/list.vue +++ b/src/pages/health/medicationPlan/list.vue @@ -331,229 +331,26 @@ function handleDelete(item) { diff --git a/src/pages/health/medicationTask/list.vue b/src/pages/health/medicationTask/list.vue index 0b28c90..84d5d50 100644 --- a/src/pages/health/medicationTask/list.vue +++ b/src/pages/health/medicationTask/list.vue @@ -299,65 +299,31 @@ function handleDelete(item) { diff --git a/src/pages/health/medicineBasic/list.vue b/src/pages/health/medicineBasic/list.vue index 51d8044..8fa3e37 100644 --- a/src/pages/health/medicineBasic/list.vue +++ b/src/pages/health/medicineBasic/list.vue @@ -362,377 +362,10 @@ function settingCancel() { \ No newline at end of file diff --git a/src/pages/health/medicineStock/list.vue b/src/pages/health/medicineStock/list.vue index 0540848..7cfce7d 100644 --- a/src/pages/health/medicineStock/list.vue +++ b/src/pages/health/medicineStock/list.vue @@ -201,158 +201,5 @@ function settingCancel() { \ No newline at end of file diff --git a/src/pages/health/medicineStockIn/list.vue b/src/pages/health/medicineStockIn/list.vue index cf03ef5..d38d57f 100644 --- a/src/pages/health/medicineStockIn/list.vue +++ b/src/pages/health/medicineStockIn/list.vue @@ -381,372 +381,10 @@ function settingCancel() { \ No newline at end of file diff --git a/src/pages/health/milkPowderRecord/list.vue b/src/pages/health/milkPowderRecord/list.vue index 24f4705..2843847 100644 --- a/src/pages/health/milkPowderRecord/list.vue +++ b/src/pages/health/milkPowderRecord/list.vue @@ -312,368 +312,32 @@ function settingCancel() { \ No newline at end of file diff --git a/src/pages/health/processRecord/list.vue b/src/pages/health/processRecord/list.vue index 5892bae..137762f 100644 --- a/src/pages/health/processRecord/list.vue +++ b/src/pages/health/processRecord/list.vue @@ -381,398 +381,27 @@ function settingCancel() { \ No newline at end of file diff --git a/src/store/modules/health.ts b/src/store/modules/health.ts new file mode 100644 index 0000000..03afff4 --- /dev/null +++ b/src/store/modules/health.ts @@ -0,0 +1,74 @@ +import { defineStore } from 'pinia' +import { listPerson } from '@/api/health/person' + +/** + * 健康模块公共 Store + * 缓存频繁使用的数据(如成员列表),避免每个页面重复请求 + */ +const useHealthStore = defineStore('health', { + state: () => ({ + personList: [] as any[], + personListLoaded: false, + personListLoading: false + }), + actions: { + /** + * 获取成员列表(带缓存) + * @param {boolean} forceRefresh - 是否强制刷新 + * @returns {Promise} + */ + async fetchPersonList(forceRefresh = false) { + // 已加载且不强制刷新,直接返回缓存 + if (this.personListLoaded && !forceRefresh) { + return this.personList + } + + // 正在加载中,等待完成 + if (this.personListLoading) { + return new Promise((resolve) => { + const timer = setInterval(() => { + if (!this.personListLoading) { + clearInterval(timer) + resolve(this.personList) + } + }, 100) + }) + } + + this.personListLoading = true + try { + const res = await listPerson({ pageNum: 1, pageSize: 100 }) + this.personList = res.rows || [] + this.personListLoaded = true + return this.personList + } catch (error) { + console.error('获取成员列表失败:', error) + return [] + } finally { + this.personListLoading = false + } + }, + + /** + * 清除成员缓存(如新增/删除成员后调用) + */ + clearPersonCache() { + this.personList = [] + this.personListLoaded = false + } + }, + persist: { + key: 'health-store', + paths: ['personList', 'personListLoaded'], + storage: { + getItem(key) { + return uni.getStorageSync(key) + }, + setItem(key, value) { + uni.setStorageSync(key, value) + } + } + } +}) + +export default useHealthStore diff --git a/src/styles/health-common.scss b/src/styles/health-common.scss new file mode 100644 index 0000000..8d92f2e --- /dev/null +++ b/src/styles/health-common.scss @@ -0,0 +1,579 @@ +// ============================================================ +// 健康模块公共样式 - 统一列表页 UI 风格 +// ============================================================ + +// 主题色变量 +$health-primary: #667eea; +$health-primary-end: #764ba2; +$health-primary-bg: rgba(102, 126, 234, 0.08); +$health-primary-border: rgba(102, 126, 234, 0.3); +$health-danger: #f5576c; +$health-danger-bg: rgba(245, 87, 108, 0.1); +$health-danger-border: rgba(245, 87, 108, 0.3); +$health-warning: #fa8c16; +$health-warning-bg: rgba(250, 140, 22, 0.1); +$health-warning-border: rgba(250, 140, 22, 0.3); +$health-info: #13c2c2; +$health-info-bg: rgba(19, 194, 194, 0.1); +$health-info-border: rgba(19, 194, 194, 0.3); + +// ==================== 页面容器 ==================== +page { + height: 100%; + overflow: auto; +} + +// ==================== 搜索栏 - 单行模式 ==================== +.search-view { + padding: 12rpx 32rpx; + background-color: #ffffff; + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + z-index: 100; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); + gap: 10rpx; + + .search-input { + flex: 1; + } + + .search-input-wrapper { + flex: 1; + display: flex; + align-items: center; + position: relative; + + .search-icon { + position: absolute; + right: 20rpx; + z-index: 10; + cursor: pointer; + } + } + + .filter-btn, + .add-btn { + display: flex; + align-items: center; + gap: 6rpx; + padding: 12rpx 24rpx; + background: $health-primary-bg; + border-radius: 24rpx; + border: 2rpx solid $health-primary-border; + transition: all 0.3s ease; + flex-shrink: 0; + + &:active { + transform: scale(0.95); + background: rgba(102, 126, 234, 0.12); + } + + text { + color: $health-primary; + font-size: 28rpx; + font-weight: 600; + } + + uni-icons { + color: $health-primary; + } + } +} + +// ==================== 搜索栏 - 多行模式(人员+档案选择) ==================== +.search-container { + background-color: #ffffff; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); + position: relative; + z-index: 100; + + .search-row { + padding: 12rpx 32rpx; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10rpx; + + &:first-child { + padding-bottom: 8rpx; + } + + &:last-child { + padding-top: 8rpx; + } + + .search-input-wrapper { + flex: 1; + display: flex; + align-items: center; + position: relative; + + .search-icon { + position: absolute; + right: 20rpx; + z-index: 10; + cursor: pointer; + } + } + + .search-input { + background: $health-primary-bg; + color: #333333; + width: 100%; + border-radius: 24rpx; + border: 2rpx solid $health-primary-border; + height: 66rpx !important; + display: flex; + align-items: center; + + :deep(input::placeholder) { + color: #909399 !important; + font-size: 14px !important; + } + + :deep(input::-webkit-input-placeholder) { + color: #909399 !important; + font-size: 14px !important; + } + + :deep(input::-moz-placeholder) { + color: #909399 !important; + font-size: 14px !important; + } + + :deep(.u-input__content__field-wrapper__field) { + color: #333333 !important; + } + + :deep(input) { + color: #333333 !important; + } + } + + .filter-btn, + .add-btn { + display: flex; + align-items: center; + gap: 6rpx; + padding: 12rpx 24rpx; + background: $health-primary-bg; + border-radius: 24rpx; + border: 2rpx solid $health-primary-border; + transition: all 0.3s ease; + flex-shrink: 0; + + &:active { + transform: scale(0.95); + background: rgba(102, 126, 234, 0.12); + } + + text { + color: $health-primary; + font-size: 28rpx; + font-weight: 600; + } + + uni-icons { + color: $health-primary; + } + } + } +} + +// ==================== 筛选面板 ==================== +.filter-panel { + width: 100%; + position: absolute; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 999; + + .filter-panel-content { + background-color: #ffffff; + padding: 0 30rpx 30rpx; + border-radius: 0 0 16rpx 16rpx; + + .filter-title { + color: #2c3e50; + font-size: 32rpx; + font-weight: 600; + padding: 32rpx 0 24rpx 20rpx; + position: relative; + display: flex; + align-items: center; + + &::before { + content: ''; + width: 6rpx; + height: 32rpx; + background: linear-gradient(135deg, $health-primary 0%, $health-primary-end 100%); + border-radius: 3rpx; + margin-right: 12rpx; + flex-shrink: 0; + } + } + + .selcet-content { + display: flex; + flex-direction: column; + gap: 16rpx; + } + + .state-list { + display: flex; + flex-wrap: wrap; + gap: 16rpx; + + .state-item { + padding: 0 32rpx; + height: 68rpx; + border: 2rpx solid #e8edf3; + border-radius: 34rpx; + text-align: center; + line-height: 68rpx; + font-size: 28rpx; + color: #666666; + background: #ffffff; + transition: all 0.2s ease; + } + + .active { + background: linear-gradient(135deg, $health-primary 0%, $health-primary-end 100%); + border: 2rpx solid transparent; + color: #ffffff; + font-weight: 600; + } + } + } + + .btn-box { + display: flex; + gap: 20rpx; + padding: 24rpx 30rpx; + background-color: #fff; + box-shadow: 0rpx -10rpx 20rpx #EEEEEE; + + .btn-reset, + .btn-confirm { + display: flex; + align-items: center; + justify-content: center; + gap: 8rpx; + height: 88rpx; + border-radius: 12rpx; + font-size: 30rpx; + font-weight: 600; + flex: 1; + transition: all 0.3s ease; + + &:active { + transform: scale(0.98); + opacity: 0.9; + } + + text { + line-height: 1; + } + } + + .btn-reset { + background: #f5f7fa; + border: 2rpx solid #dcdfe6; + + text { + color: #606266; + } + } + + .btn-confirm { + background: $health-primary; + box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.2); + border: none; + + text { + color: #ffffff; + } + } + } +} + +// ==================== 列表卡片 ==================== +.list-item { + margin: 10rpx 24rpx; + background-color: #fff; + border-radius: 16rpx; + overflow: hidden; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); + transition: all 0.2s ease; + + &:active { + transform: scale(0.98); + box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.06); + } + + // 卡片头部 - 渐变背景 + .item-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16rpx 24rpx; + background: linear-gradient(135deg, $health-primary 0%, $health-primary-end 100%); + + .card-name-section { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + margin-right: 16rpx; + } + + .card-icon { + width: 40rpx; + height: 40rpx; + background: rgba(255, 255, 255, 0.25); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12rpx; + flex-shrink: 0; + } + + .card-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: row; + align-items: center; + gap: 12rpx; + + .card-name { + color: #ffffff; + font-size: 30rpx; + font-weight: 700; + line-height: 1.3; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 0; + letter-spacing: 0.5rpx; + } + + .card-time { + color: rgba(255, 255, 255, 0.9); + font-size: 26rpx; + font-weight: 500; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; + letter-spacing: 0.3rpx; + } + + .card-code { + color: rgba(255, 255, 255, 0.9); + font-size: 24rpx; + font-weight: 500; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; + letter-spacing: 0.3rpx; + } + } + + // 统计数据区(如身高体重) + .stats-section { + display: flex; + align-items: center; + gap: 16rpx; + flex-shrink: 0; + + .stats-item { + color: $health-warning; + font-size: 28rpx; + font-weight: 700; + line-height: 1; + white-space: nowrap; + + .stats-label { + color: rgba(255, 255, 255, 0.85); + font-size: 20rpx; + font-weight: 400; + margin-left: 4rpx; + } + } + } + + // 体温展示区 + .temperature-section { + display: flex; + align-items: center; + flex-shrink: 0; + + .temperature-value { + font-size: 32rpx; + font-weight: 700; + line-height: 1.3; + white-space: nowrap; + padding: 10rpx 20rpx; + background: rgba(255, 255, 255, 0.2); + border-radius: 10rpx; + letter-spacing: 0.5rpx; + + &.temp-normal { + color: rgba(255, 255, 255, 0.95); + } + + &.temp-warning { + color: #ff7875; + background: rgba(255, 120, 117, 0.15); + font-weight: 800; + } + + &.temp-danger { + color: #ff4d4f; + background: rgba(255, 77, 79, 0.15); + font-weight: 800; + } + } + } + } + + // 卡片主体 + .card-body { + padding: 24rpx; + background: #fafbfc; + border: 2rpx solid #f0f2f5; + margin: 15rpx 16rpx 2rpx; + border-radius: 12rpx; + } + + // 信息行 - 两列布局 + .info-row { + display: flex; + flex-wrap: wrap; + margin: 0 -12rpx; + + .info-item { + width: 50%; + padding: 0 12rpx; + box-sizing: border-box; + display: flex; + flex-direction: column; + margin-bottom: 20rpx; + + &.info-item-full { + width: 100%; + } + + .info-label { + font-size: 24rpx; + color: $health-primary; + font-weight: 500; + background: $health-primary-bg; + padding: 6rpx 12rpx; + border-radius: 8rpx; + align-self: flex-start; + margin-bottom: 8rpx; + } + + .info-value { + font-size: 26rpx; + color: #333; + font-weight: 500; + flex: 1; + line-height: 1.5; + word-break: break-all; + } + + &:not(.info-item-full) .info-value { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + + // 操作按钮区 + .operate { + display: flex; + justify-content: flex-end; + padding: 16rpx 24rpx 24rpx; + gap: 16rpx; + flex-wrap: wrap; + + .btn-edit, + .btn-delete, + .btn-copy, + .btn-detail { + display: flex; + align-items: center; + justify-content: center; + gap: 6rpx; + padding: 0 24rpx; + height: 64rpx; + border-radius: 12rpx; + font-size: 26rpx; + font-weight: 500; + transition: all 0.3s ease; + + &:active { + transform: scale(0.95); + } + } + + .btn-edit { + background: rgba(102, 126, 234, 0.1); + color: $health-primary; + border: 1rpx solid $health-primary-border; + } + + .btn-delete { + background: $health-danger-bg; + color: $health-danger; + border: 1rpx solid $health-danger-border; + } + + .btn-copy { + background: $health-warning-bg; + color: $health-warning; + border: 1rpx solid $health-warning-border; + } + + .btn-detail { + background: $health-info-bg; + color: $health-info; + border: 1rpx solid $health-info-border; + } + } +} + +// ==================== 状态标签 ==================== +.status-tag { + font-size: 22rpx; + padding: 4rpx 16rpx; + border-radius: 8rpx; + font-weight: 600; + + &.status-active, &.status-1 { + background: rgba(82, 196, 26, 0.2); + color: #52c41a; + } + + &.status-ended, &.status-2 { + background: rgba(144, 147, 153, 0.2); + color: #909399; + } + + &.status-paused, &.status-3 { + background: rgba(250, 140, 22, 0.2); + color: #fa8c16; + } +} diff --git a/src/uni.scss b/src/uni.scss index d23fbb2..a45b185 100644 --- a/src/uni.scss +++ b/src/uni.scss @@ -64,4 +64,13 @@ $uni-color-paragraph: #3F536E; // 文章段落颜色 $uni-font-size-paragraph:15px; +/* 健康模块主题色 */ +$health-primary: #667eea; +$health-primary-end: #764ba2; +$health-primary-bg: rgba(102, 126, 234, 0.08); +$health-primary-border: rgba(102, 126, 234, 0.3); +$health-danger: #f5576c; +$health-warning: #fa8c16; +$health-info: #13c2c2; + @import 'uview-plus/theme.scss'; \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 9109773..bf4a2de 100644 --- a/vite.config.js +++ b/vite.config.js @@ -5,7 +5,7 @@ export default defineConfig(() => { return { base: './', build: { - minify: true, + minify: 'terser', outDir: 'dist', // 小程序不支持 eval,需要关闭 terser 的 compress.drop_debugger terserOptions: {