8 Commits

Author SHA1 Message Date
tianyongbao
3294695dec fix: 金额数字输入,改成可输入小数。 2026-07-12 16:12:09 +08:00
tianyongbao
68da3c0101 fix: 版本号问题修复。 2026-07-12 14:48:55 +08:00
tianyongbao
88cf663855 fix: 功能优化完善。 2026-07-12 14:23:18 +08:00
tianyongbao
cc1ccad308 fix: 功能完善修复。 2026-07-12 12:49:10 +08:00
tianyongbao
aeb1e39920 fix: 文件上传压缩功能修复。 2026-07-08 10:10:38 +08:00
tianyongbao
6aaf564800 fix: 修改费用汇总为费用。 2026-07-06 00:47:31 +08:00
tianyongbao
6846ea3e0f fix: 功能修改完善。 2026-07-06 00:37:01 +08:00
tianyongbao
9027869e97 fix: 显示界面,功能优化。 2026-06-28 15:43:23 +08:00
46 changed files with 1091 additions and 463 deletions

3
package-lock.json generated
View File

@@ -7,6 +7,7 @@
"": {
"name": "uni-preset-vue",
"version": "0.0.0",
"hasInstallScript": true,
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4000820240401001",
"@dcloudio/uni-app-plus": "3.0.0-4000820240401001",
@@ -12490,7 +12491,7 @@
},
"node_modules/uview-plus": {
"version": "3.1.45",
"resolved": "https://registry.npmmirror.com/uview-plus/-/uview-plus-3.1.45.tgz",
"resolved": "https://registry.npmjs.org/uview-plus/-/uview-plus-3.1.45.tgz",
"integrity": "sha512-JHgLp2heaMciLdGimO/v4tMM8iwb2vTEOk6sXqn5X198AHjM5A/IGzH84GZPvUISFTEJbxGEHiGPxpv2K26AGw==",
"dependencies": {
"clipboard": "^2.0.11",

View File

@@ -39,7 +39,14 @@
"type-check": "vue-tsc --noEmit",
"clean:linux": "rm -rf dist || rm -rf node_modules",
"clean:windows": "rd /s /q dist || rd /s /q node_modules",
"postinstall": "node scripts/patch-u-input.js"
"patch:u-input": "node scripts/patch-u-input.js",
"postinstall": "node scripts/patch-u-input.js",
"predev:mp-weixin": "node scripts/patch-u-input.js",
"prebuild:mp-weixin": "node scripts/patch-u-input.js",
"predev:h5": "node scripts/patch-u-input.js",
"prebuild:h5": "node scripts/patch-u-input.js",
"predev:app": "node scripts/patch-u-input.js",
"prebuild:app": "node scripts/patch-u-input.js"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4000820240401001",

View File

@@ -2,10 +2,6 @@
* uView u-input 组件补丁
* 直接用预存的补丁文件覆盖原始文件
* 使下拉选择框readonly/disabled超长文字可自动换行完整显示
*
* 修改内容详见 scripts/u-input.vue已修改好的完整文件
*
* 通过 package.json 的 postinstall 自动执行
*/
const fs = require('fs');
const path = require('path');

View File

@@ -1,7 +1,7 @@
<template>
<view
class="u-input"
:class="[inputClass, isSelectLike ? 'u-input--selectlike' : '']"
:class="[inputClass, isSelectLike ? 'u-input--selectlike' : '', isSearchInput ? 'u-input--search-input' : '']"
:style="[wrapperStyle]"
>
<view class="u-input__content">
@@ -205,6 +205,14 @@ export default {
isSelectLike() {
return this.type === 'select' || this.disabled || this.readonly
},
isSearchInput() {
const customStyle = this.customStyle
if (typeof customStyle === 'string') {
return customStyle.includes('height: 66rpx') && customStyle.includes('border-radius: 24rpx')
}
const style = uni.$u.addStyle(customStyle)
return style.height === '66rpx' && (style.borderRadius === '24rpx' || style['border-radius'] === '24rpx')
},
// 是否显示清除控件
isShowClear() {
const { clearable, readonly, focused, innerValue } = this;
@@ -241,7 +249,15 @@ export default {
style.paddingLeft = "9px";
style.paddingRight = "9px";
}
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
const mergedStyle = uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
if (this.isSearchInput) {
mergedStyle.height = '66rpx';
mergedStyle.minHeight = '66rpx';
mergedStyle.boxSizing = 'border-box';
mergedStyle.paddingTop = '0';
mergedStyle.paddingBottom = '0';
}
return mergedStyle;
},
// 输入框的样式
inputStyle() {
@@ -354,18 +370,49 @@ export default {
flex: 1;
&--selectlike {
/* 覆盖 inline style 的固定 height让选择型模式高度自适应 */
/* 覆盖 inline style 的固定 height普通选择型内容可自适应换行 */
/* 短文字垂直居中,长文字撑开高度自动换行 */
height: auto !important;
min-height: 68rpx;
.u-input__content,
.u-input__content__field-wrapper,
/* 只让展示文本本身不抢事件,保留外层点击与 suffix 插槽事件 */
.u-input__content__field-wrapper__field {
pointer-events: none;
}
}
&--search-input {
/* 列表页顶部查询框必须同高;该 class 在组件内部计算,兼容小程序组件样式隔离 */
height: 66rpx !important;
min-height: 66rpx;
box-sizing: border-box;
padding-top: 0 !important;
padding-bottom: 0 !important;
.u-input__content,
.u-input__content__field-wrapper {
height: 100%;
align-items: center;
}
.u-input__content__field-wrapper__field--selectlike {
min-height: 0;
line-height: 32rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-break: normal;
}
.u-input__content__field-wrapper__field__text,
.u-input__content__field-wrapper__field__placeholder {
line-height: 32rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&--radius,
&--square {
border-radius: 4px;

View File

@@ -19,9 +19,12 @@ export function login(username, password, code, uuid) {
}
// 获取用户详细信息
export function getInfo() {
export function getInfo(silentAuth = false) {
return request({
'url': '/system/user/getInfo',
headers: silentAuth ? {
silentAuth: true
} : undefined,
'method': 'get'
})
}

35
src/mixins/share.js Normal file
View File

@@ -0,0 +1,35 @@
import config from '@/config.js'
const SHARE_HOME = '/pages/login'
const SHARE_TITLES = {
'intc-invest-app': '智聪记账管理平台',
'intc-health-app': '暖康档案管理平台'
}
function getShareTitle() {
return SHARE_TITLES[config.appInfo?.name] || config.appInfo?.name || '智聪管理平台'
}
function showShareMenu() {
// #ifdef MP-WEIXIN
uni.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage']
})
// #endif
}
export default {
onLoad() {
showShareMenu()
},
onShow() {
showShareMenu()
},
onShareAppMessage() {
return {
title: getShareTitle(),
path: SHARE_HOME
}
}
}

View File

@@ -62,12 +62,6 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/health/statistic/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/mine",
"style": {
@@ -75,160 +69,6 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/health/doctorRecord/list",
"style": {
"navigationBarTitleText": "就医记录"
}
}
,
{
"path": "pages/health/doctorRecord/addEdit",
"style": {
"navigationBarTitleText": "就医记录"
}
},
{
"path": "pages/health/doctorRecord/costList",
"style": {
"navigationBarTitleText": "费用明细"
}
},
{
"path": "pages/health/doctorRecord/costAddEdit",
"style": {
"navigationBarTitleText": "费用明细"
}
},
{
"path": "pages/health/healthRecord/list",
"style": {
"navigationBarTitleText": "健康档案"
}
} ,
{
"path": "pages/health/healthRecord/addEdit",
"style": {
"navigationBarTitleText": "健康档案"
}
},
{
"path": "pages/health/healthRecord/details",
"style": {
"navigationBarTitleText": "档案统计"
}
},
{
"path": "pages/health/marRecord/list",
"style": {
"navigationBarTitleText": "用药记录"
}
}
,
{
"path": "pages/health/marRecord/addEdit",
"style": {
"navigationBarTitleText": "用药记录"
}
},
{
"path": "pages/health/medicineBasic/list",
"style": {
"navigationBarTitleText": "药品基础信息"
}
}
,
{
"path": "pages/health/medicineBasic/details",
"style": {
"navigationBarTitleText": "药品基础信息详情"
}
}
,
{
"path": "pages/health/medicineBasic/addEdit",
"style": {
"navigationBarTitleText": "药品基础信息"
}
},
{
"path": "pages/health/medicineStockIn/list",
"style": {
"navigationBarTitleText": "药品入库清单"
}
}
,
{
"path": "pages/health/medicineStockIn/details",
"style": {
"navigationBarTitleText": "药品入库清单详情"
}
}
,
{
"path": "pages/health/medicineStockIn/addEdit",
"style": {
"navigationBarTitleText": "药品入库清单"
}
}, {
"path": "pages/health/medicineStock/list",
"style": {
"navigationBarTitleText": "药品实时库存"
}
}
,
{
"path": "pages/health/temperatureRecord/list",
"style": {
"navigationBarTitleText": "体温记录"
}
}
,
{
"path": "pages/health/temperatureRecord/addEdit",
"style": {
"navigationBarTitleText": "体温记录"
}
} ,
{
"path": "pages/health/processRecord/list",
"style": {
"navigationBarTitleText": "档案过程记录"
}
}
,
{
"path": "pages/health/processRecord/addEdit",
"style": {
"navigationBarTitleText": "过程记录"
}
},
{
"path": "pages/health/heightWeightRecord/list",
"style": {
"navigationBarTitleText": "身高体重记录"
}
}
,
{
"path": "pages/health/milkPowderRecord/addEdit",
"style": {
"navigationBarTitleText": "吃奶记录"
}
},
{
"path": "pages/health/milkPowderRecord/list",
"style": {
"navigationBarTitleText": "吃奶记录"
}
}
,
{
"path": "pages/health/heightWeightRecord/addEdit",
"style": {
"navigationBarTitleText": "身高体重记录"
}
},
{
"path": "pages/health/person/list",
"style": {
@@ -256,83 +96,44 @@
}
},
{
"path": "pages/health/statistic/doctorStatistic/index",
"path": "pages/health/temperatureRecord/list",
"style": {
"navigationBarTitleText": "就医统计"
"navigationBarTitleText": "体温记录"
}
} ,
}
,
{
"path": "pages/health/statistic/healthStatistic/index",
"path": "pages/health/temperatureRecord/addEdit",
"style": {
"navigationBarTitleText": "档案统计"
"navigationBarTitleText": "体温记录"
}
} ,
{
"path": "pages/health/heightWeightRecord/list",
"style": {
"navigationBarTitleText": "身高体重记录"
}
}
,
{
"path": "pages/health/milkPowderRecord/addEdit",
"style": {
"navigationBarTitleText": "吃奶记录"
}
},
{
"path": "pages/health/statistic/healthScreen/index",
"style": {
"navigationBarTitleText": "健康总览态势"
}
},
{
"path": "pages/health/statistic/recordScreen/index",
"style": {
"navigationBarTitleText": "健康档案态势"
}
} ,
{
"path": "pages/health/statistic/marStatistic/index",
"style": {
"navigationBarTitleText": "用药统计"
}
} ,
{
"path": "pages/health/statistic/temperatureStatistic/index",
"style": {
"navigationBarTitleText": "体温统计"
}
} ,
{
"path": "pages/health/statistic/milkPowderStatistic/index",
"style": {
"navigationBarTitleText": "吃奶量统计"
}
},
{
"path": "pages/health/medicationPlan/list",
"style": {
"navigationBarTitleText": "用药计划"
}
},
{
"path": "pages/health/medicationPlan/addEdit",
"style": {
"navigationBarTitleText": "用药计划"
}
},
{
"path": "pages/health/chronicDisease/list",
"style": {
"navigationBarTitleText": "慢性疾病档案"
}
},
{
"path": "pages/health/chronicDisease/addEdit",
"style": {
"navigationBarTitleText": "慢性疾病档案"
}
},
{
"path": "pages/health/chronicDisease/details",
"style": {
"navigationBarTitleText": "慢性疾病详情"
}
},
{
"path": "pages/health/medicationTask/list",
"style": {
"navigationBarTitleText": "服药任务"
}
},
"path": "pages/health/milkPowderRecord/list",
"style": {
"navigationBarTitleText": "吃奶记录"
}
}
,
{
"path": "pages/health/heightWeightRecord/addEdit",
"style": {
"navigationBarTitleText": "身高体重记录"
}
},
{
"path": "pages/health/healthTags/list",
"style": {
@@ -393,9 +194,212 @@
}
}
]
},
{
"root": "pages/health/statistic",
"name": "statistic",
"pages": [
{
"path": "index",
"style": { "navigationBarTitleText": "统计分析" }
},
{
"path": "doctorStatistic/index",
"style": { "navigationBarTitleText": "就医统计" }
},
{
"path": "healthStatistic/index",
"style": { "navigationBarTitleText": "档案统计" }
},
{
"path": "healthScreen/index",
"style": { "navigationBarTitleText": "健康总览态势" }
},
{
"path": "recordScreen/index",
"style": { "navigationBarTitleText": "健康档案态势" }
},
{
"path": "marStatistic/index",
"style": { "navigationBarTitleText": "用药统计" }
},
{
"path": "temperatureStatistic/index",
"style": { "navigationBarTitleText": "体温统计" }
},
{
"path": "milkPowderStatistic/index",
"style": { "navigationBarTitleText": "吃奶量统计" }
}
]
},
{
"root": "pages/health/medicineBasic",
"name": "medicineBasic",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "药品基础信息" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "药品基础信息详情" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "药品基础信息" }
}
]
},
{
"root": "pages/health/medicineStockIn",
"name": "medicineStockIn",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "药品入库清单" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "药品入库清单详情" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "药品入库清单" }
}
]
},
{
"root": "pages/health/medicineStock",
"name": "medicineStock",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "药品实时库存" }
}
]
},
{
"root": "pages/health/medicationPlan",
"name": "medicationPlan",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "用药计划" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "用药计划" }
}
]
},
{
"root": "pages/health/medicationTask",
"name": "medicationTask",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "服药任务" }
}
]
},
{
"root": "pages/health/marRecord",
"name": "marRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "用药记录" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "用药记录" }
}
]
},
{
"root": "pages/health/doctorRecord",
"name": "doctorRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "就医记录" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "就医记录" }
},
{
"path": "costList",
"style": { "navigationBarTitleText": "费用明细" }
},
{
"path": "costAddEdit",
"style": { "navigationBarTitleText": "费用明细" }
}
]
},
{
"root": "pages/health/healthRecord",
"name": "healthRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "健康档案" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "健康档案" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "档案统计" }
}
]
},
{
"root": "pages/health/processRecord",
"name": "processRecord",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "档案过程记录" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "过程记录" }
}
]
},
{
"root": "pages/health/chronicDisease",
"name": "chronicDisease",
"pages": [
{
"path": "list",
"style": { "navigationBarTitleText": "慢性疾病档案" }
},
{
"path": "addEdit",
"style": { "navigationBarTitleText": "慢性疾病档案" }
},
{
"path": "details",
"style": { "navigationBarTitleText": "慢性疾病详情" }
}
]
}
],
"preloadRule": {
"pages/health/index": {
"network": "all",
"packages": ["medicineBasic", "medicineStockIn", "medicineStock", "medicationPlan", "medicationTask", "marRecord", "doctorRecord", "healthRecord", "processRecord", "chronicDisease"]
},
"pages/health/statistic/index": {
"network": "all",
"packages": ["statistic"]
}
},
"tabBar": {
"color": "#000000",
"selectedColor": "#000000",

View File

@@ -50,7 +50,7 @@
</view>
</u-form-item>
<u-form-item label="总费用" prop="totalCost" >
<u--input v-model="form.totalCost" type="number" placeholder="请填写总费用"
<u--input v-model="form.totalCost" type="digit" placeholder="请填写总费用"
inputAlign="left" :customStyle="getInputStyle('totalCost')">
<template #suffix>
<up-text
@@ -125,6 +125,7 @@
<script setup>
import { getActivity, addActivity, updateActivity, uploadFile } from '@/api/health/activity'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
const { proxy } = getCurrentInstance()
import { getDicts } from '@/api/system/dict/data.js'
import dayjs from 'dayjs'
@@ -376,38 +377,56 @@ function chooseImage() {
proxy.$refs['uToast'].show({ message: '正在上传中,请稍候', type: 'warning' })
return
}
// H5 端传 camera 会触发 getUserMedia可能卡死微信小程序/APP 需要相机选项
let sourceType
// #ifdef H5
sourceType = ['album']
// #endif
// #ifndef H5
sourceType = ['album', 'camera']
// #endif
uni.chooseImage({
count: 9 - attachmentList.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
sourceType,
success: (res) => {
uploadImages(res.tempFilePaths, 0)
},
fail: (err) => {
// 用户取消选择不走 success也不会报错但某些浏览器会触发 fail
if (err && err.errMsg && err.errMsg.indexOf('cancel') === -1) {
proxy.$refs['uToast'].show({ message: '选择图片失败', type: 'warning' })
}
}
})
}
function uploadImages(paths, index) {
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
uni.showLoading({ title: `上传${index + 1}/${paths.length}`, mask: true })
uploadFile({
filePath: paths[index],
name: 'file'
}).then(res => {
uni.showLoading({ title: `处理${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
})
const url = (res.data && res.data.url) || res.fileName || res.url
if (url) {
attachmentList.value.push(normalizeAttachmentUrl(url))
}
uni.hideLoading()
uploadImages(paths, index + 1)
}).catch(() => {
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
})
}
}
function deleteImage(index) {

View File

@@ -103,7 +103,7 @@
<view class="operate" @click.stop>
<view class="btn-summary" @click="handleCostSummary(item)">
<uni-icons type="list" size="16" color="#13c2c2"></uni-icons>
<text>费用汇总</text>
<text>费用</text>
</view>
<view class="btn-edit" @click="handleEdit(item)">
<uni-icons type="compose" size="16" color="#667eea"></uni-icons>
@@ -454,21 +454,26 @@ function previewAttachment(urls, index) {
// 活动特有 - 卡片头部布局
.item-header {
gap: 12rpx;
/* 允许 header 内容换行,避免长地点/长名字被省略号截断 */
align-items: flex-start;
.place-info {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
align-items: flex-start;
.place-text {
color: #ffffff;
font-size: 30rpx;
font-weight: 500;
line-height: 1.3;
/* 长地点名称最多2行后省略 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
}
@@ -489,9 +494,10 @@ function previewAttachment(urls, index) {
.name-section {
display: flex;
align-items: center;
flex: 1;
align-items: flex-start;
flex: 0 1 auto;
min-width: 0;
max-width: 40%;
justify-content: flex-end;
.name-value {
@@ -499,9 +505,13 @@ function previewAttachment(urls, index) {
font-size: 30rpx;
font-weight: 700;
line-height: 1.3;
white-space: nowrap;
/* 长活动名最多2行后省略 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
text-align: right;
letter-spacing: 0.5rpx;
}
}
@@ -513,13 +523,6 @@ function previewAttachment(urls, index) {
font-weight: 600;
}
// 活动特有 - 汇总按钮
.operate .btn-summary {
background: rgba(19, 194, 194, 0.1);
color: #13c2c2;
border: 1rpx solid rgba(19, 194, 194, 0.3);
}
.attachment-list {
display: flex;
flex-wrap: wrap;

View File

@@ -50,7 +50,7 @@
</u-form-item>
<u-form-item label="总费用" prop="totalCost" required >
<u--input v-model="form.totalCost" placeholder="请填写总费用"
<u--input v-model="form.totalCost" type="digit" placeholder="请填写总费用"
inputAlign="left" :customStyle="getInputStyle('totalCost')">
<template #suffix>
<up-text
@@ -114,6 +114,7 @@
<script setup>
import { getDoctorRecord, addDoctorRecord, updateDoctorRecord, uploadFile } from '@/api/health/doctorRecord'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
import { listPerson } from '@/api/health/person'
import { listHealthRecord } from '@/api/health/healthRecord'
const { proxy } = getCurrentInstance()
@@ -171,7 +172,7 @@ rules: {
hospitalName: [{ required: true, message: '医院名称不能为空', trigger: ['change', 'blur'] }],
departments: [{ required: true, message: '科室不能为空', trigger: ['change', 'blur'] }],
doctor: [{ required: true, message: '大夫不能为空', trigger: ['change', 'blur'] }],
totalCost: [{ type: 'number', required: true, message: '总费用不能为空', trigger: ['change', 'blur'] }],
totalCost: [{ required: true, message: '总费用不能为空', trigger: ['change', 'blur'] }],
partner: [{ required: true, message: '陪同人不能为空', trigger: ['change', 'blur'] }],
prescribe: [{ required: true, message: '处理及医嘱不能为空', trigger: ['change', 'blur'] }],
diagnosis: [{ required: true, message: '诊断结果不能为空', trigger: ['change', 'blur'] }],
@@ -432,39 +433,57 @@ function chooseImage() {
proxy.$refs['uToast'].show({ message: '正在上传中,请稍候', type: 'warning' })
return
}
// H5 端传 camera 会触发 getUserMedia可能卡死微信小程序/APP 需要相机选项
let sourceType
// #ifdef H5
sourceType = ['album']
// #endif
// #ifndef H5
sourceType = ['album', 'camera']
// #endif
uni.chooseImage({
count: 9 - attachmentList.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
sourceType,
success: (res) => {
uploadImages(res.tempFilePaths, 0)
},
fail: (err) => {
// 用户取消选择不走 success也不会报错但某些浏览器会触发 fail
if (err && err.errMsg && err.errMsg.indexOf('cancel') === -1) {
proxy.$refs['uToast'].show({ message: '选择图片失败', type: 'warning' })
}
}
})
}
// 递归上传图片
function uploadImages(paths, index) {
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
uni.showLoading({ title: `上传${index + 1}/${paths.length}`, mask: true })
uploadFile({
filePath: paths[index],
name: 'file'
}).then(res => {
uni.showLoading({ title: `处理${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
})
const url = (res.data && res.data.url) || res.fileName || res.url
if (url) {
attachmentList.value.push(normalizeAttachmentUrl(url))
}
uni.hideLoading()
uploadImages(paths, index + 1)
}).catch(() => {
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
})
}
}
// 删除图片

View File

@@ -51,7 +51,7 @@
<u--input v-model="form.costName" placeholder="请填写费用名称" inputAlign="left" :customStyle="getInputStyle('costName')"></u--input>
</u-form-item>
<u-form-item label="单价" prop="price" required>
<u--input v-model="form.price" type="number" placeholder="请填写单价" inputAlign="left"
<u--input v-model="form.price" type="digit" placeholder="请填写单价" inputAlign="left"
:customStyle="getInputStyle('price')" @change="handlePriceChange">
<template #suffix>
<up-text text="元"></up-text>
@@ -59,7 +59,7 @@
</u--input>
</u-form-item>
<u-form-item label="数量" prop="count" required>
<u--input v-model="form.count" type="number" placeholder="请填写数量" inputAlign="left"
<u--input v-model="form.count" type="digit" placeholder="请填写数量" inputAlign="left"
:customStyle="getInputStyle('count')" @change="handleCountChange"></u--input>
</u-form-item>
<u-form-item label="单位" prop="unitName" required @click="handleUnit">
@@ -70,7 +70,7 @@
</view>
</u-form-item>
<u-form-item label="总价" prop="totalCost" required>
<u--input v-model="form.totalCost" type="number" placeholder="请填写总价" inputAlign="left"
<u--input v-model="form.totalCost" type="digit" placeholder="请填写总价" inputAlign="left"
:customStyle="getInputStyle('totalCost')">
<template #suffix>
<up-text text="元"></up-text>

View File

@@ -221,6 +221,35 @@ function handleDelete(item) {
})
}
function buildCostDetailSummary() {
if (!listData.value.length) return ''
// 按费用类型分组汇总
const groupMap = new Map()
listData.value.forEach(item => {
const typeKey = item.type || '__other__'
const typeLabel = item.type ? (dictStr(item.type, costTypeList.value) || '未分类') : '未分类'
if (!groupMap.has(typeKey)) {
groupMap.set(typeKey, { label: typeLabel, total: 0, count: 0 })
}
const g = groupMap.get(typeKey)
g.total += Number(item.totalCost || 0)
g.count += 1
})
// 拼接汇总字符串,每行一个类型,末行合计
const lines = []
let grandTotal = 0
let grandCount = 0
groupMap.forEach(g => {
lines.push(`${g.label}${g.total.toFixed(2)}元(${g.count}笔)`)
grandTotal += g.total
grandCount += g.count
})
lines.push(`合计:${grandTotal.toFixed(2)}元(${grandCount}笔)`)
return lines.join('\n')
}
function handleUpdateCost() {
if (!doctorRecord.value.id) {
uni.showToast({
@@ -229,15 +258,38 @@ function handleUpdateCost() {
})
return
}
updateDoctorRecord({
...doctorRecord.value,
totalCost: Number(detailTotalCost.value)
}).then(() => {
doctorRecord.value.totalCost = Number(detailTotalCost.value)
if (!listData.value.length) {
uni.showToast({
title: '汇总成功',
icon: 'success'
title: '暂无费用明细,无法汇总',
icon: 'none'
})
return
}
const total = Number(detailTotalCost.value)
const costDetail = buildCostDetailSummary()
// 会覆盖主表原有的费用明细字段,所以弹窗确认
uni.showModal({
title: '确认汇总',
content: `总费用:${total.toFixed(2)}\n费用明细将按类型汇总并覆盖原有内容是否继续`,
confirmText: '确认汇总',
cancelText: '取消',
success: function (res) {
if (res.confirm) {
updateDoctorRecord({
...doctorRecord.value,
totalCost: total,
costDetail: costDetail
}).then(() => {
doctorRecord.value.totalCost = total
doctorRecord.value.costDetail = costDetail
uni.showToast({
title: '汇总成功',
icon: 'success'
})
})
}
}
})
}
</script>

View File

@@ -3,7 +3,7 @@
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-container">
<view class="search-row">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -18,8 +18,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="add-btn" @click="handleAdd()">
@@ -28,7 +27,7 @@
</view>
</view>
<view class="search-row">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handleHealthRecord">
<u--input
v-model="queryParams.healthRecordName"
border="surround"
@@ -43,8 +42,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handleHealthRecord">
class="search-icon">
</uni-icons>
</view>
<view class="filter-btn" @click="filterPanel = !filterPanel">
@@ -63,24 +61,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -284,9 +284,13 @@ onLoad(() => {
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function dictStr(val, arr) {
let str = ''
arr.map(item => {

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,8 +17,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="filter-btn" @click="filterPanel = !filterPanel">
@@ -44,24 +43,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -258,9 +259,13 @@ onLoad(() => {
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -20,7 +20,7 @@
</view>
</u-form-item>
<u-form-item label="身高" required prop="height" >
<u--input v-model="form.height" type="number" placeholder="请填写身高"
<u--input v-model="form.height" type="digit" placeholder="请填写身高"
inputAlign="left" :customStyle="getInputStyle('height')">
<template #suffix>
<up-text
@@ -30,7 +30,7 @@
</u--input>
</u-form-item>
<u-form-item label="体重" required prop="weight" >
<u--input v-model="form.weight" type="number" placeholder="请填写体重过"
<u--input v-model="form.weight" type="digit" placeholder="请填写体重过"
inputAlign="left" :customStyle="getInputStyle('weight')">
<template #suffix>
<up-text

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,8 +17,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="filter-btn" @click="filterPanel = !filterPanel">
@@ -39,24 +38,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -190,9 +191,13 @@ onLoad(() => {
}
});
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -259,6 +259,8 @@
</div>
</div>
</view>
<!-- 悬停按钮返回工作台-->
<refresh></refresh>
</template>
<script setup>
import { ref, onMounted } from 'vue';

View File

@@ -55,7 +55,7 @@
</view>
</u-form-item>
<u-form-item label="用药剂量" prop="dosage" required>
<u--input v-model="form.dosage" placeholder="请填写用药剂量"
<u--input v-model="form.dosage" placeholder="请填写用药剂量" type="digit"
inputAlign="left" :customStyle="getInputStyle('dosage')"></u--input>
</u-form-item>
<u-form-item label="用药单位" prop="unitName" required @click="handleUnit">

View File

@@ -3,7 +3,7 @@
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-container">
<view class="search-row">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -18,8 +18,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="add-btn" @click="handleAdd()">
@@ -51,8 +50,8 @@
<view class="filter-panel" :style="{ height: `${windowHeight - 42}px` }">
<view class="filter-panel-content">
<view class="filter-title">健康档案</view>
<view class="health-record-input">
<u--input v-model="queryParams.healthRecordName" border="surround" type="select" @click="handleHealthRecord" placeholder="请选择健康档案"
<view class="health-record-input" @click="handleHealthRecord">
<u--input v-model="queryParams.healthRecordName" border="surround" type="select" placeholder="请选择健康档案"
suffixIcon="search" suffixIconStyle="color: #909399">
</u--input>
</view>
@@ -69,24 +68,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -284,9 +285,13 @@ onLoad(() => {
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -32,7 +32,7 @@
</u-form-item>
<u-form-item label="每次剂量" prop="dosagePerTime" required>
<view style="display:flex;align-items:center;gap:10rpx;width:100%">
<u--input v-model="form.dosagePerTime" type="number" placeholder="剂量"
<u--input v-model="form.dosagePerTime" type="digit" placeholder="剂量"
inputAlign="left" :customStyle="inputBaseStyle"></u--input>
<view @click="handleUnitPick" style="min-width:120rpx">
<u--input v-model="form.dosageUnit" readonly disabledColor="#ffffff" placeholder="单位"

View File

@@ -177,6 +177,7 @@
<script setup>
import {getMedicineBasic, addMedicineBasic, updateMedicineBasic, uploadFile } from '@/api/health/medicineBasic'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
const { proxy } = getCurrentInstance()
import { getDicts } from '@/api/system/dict/data.js'
import dayjs from 'dayjs'
@@ -592,28 +593,46 @@ function chooseImage() {
proxy.$refs['uToast'].show({ message: '正在上传中,请稍候', type: 'warning' })
return
}
// H5 端传 camera 会触发 getUserMedia可能卡死微信小程序/APP 需要相机选项
let sourceType
// #ifdef H5
sourceType = ['album']
// #endif
// #ifndef H5
sourceType = ['album', 'camera']
// #endif
uni.chooseImage({
count: 9 - attachmentList.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
sourceType,
success: (res) => {
uploadImages(res.tempFilePaths, 0)
},
fail: (err) => {
// 用户取消选择不走 success也不会报错但某些浏览器会触发 fail
if (err && err.errMsg && err.errMsg.indexOf('cancel') === -1) {
proxy.$refs['uToast'].show({ message: '选择图片失败', type: 'warning' })
}
}
})
}
// 递归上传图片
function uploadImages(paths, index) {
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
uni.showLoading({ title: `上传${index + 1}/${paths.length}`, mask: true })
uploadFile({
filePath: paths[index],
name: 'file'
}).then(res => {
uni.showLoading({ title: `处理${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
})
// 打印返回数据,便于调试
console.log('==== upload response ====', JSON.stringify(res))
// 兼容多种返回格式
@@ -633,11 +652,11 @@ function uploadImages(paths, index) {
}
uni.hideLoading()
uploadImages(paths, index + 1)
}).catch(() => {
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
})
}
}
// 删除图片

View File

@@ -36,24 +36,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -254,9 +256,13 @@ onLoad(() => {
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -85,11 +85,11 @@
inputAlign="left" :customStyle="getInputStyle('ageWeight')"></u--input>
</u-form-item>
<u-form-item label="药品单价" prop="purchasePrice" >
<u--input v-model="form.purchasePrice" placeholder="请填写药品单价"
<u--input v-model="form.purchasePrice" type="digit" placeholder="请填写药品单价"
inputAlign="left" :customStyle="getInputStyle('purchasePrice')"></u--input>
</u-form-item>
<u-form-item label="药品总价" prop="totalPrice" >
<u--input v-model="form.totalPrice" placeholder="请填写药品总价"
<u--input v-model="form.totalPrice" type="digit" placeholder="请填写药品总价"
inputAlign="left" :customStyle="getInputStyle('totalPrice')"></u--input>
</u-form-item>
<u-form-item label="购买地址" prop="purchaseAddress" labelPosition="top">

View File

@@ -37,24 +37,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -239,9 +241,13 @@ onLoad(() => {
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -20,7 +20,7 @@
</view>
</u-form-item>
<u-form-item label="吃奶量" required prop="consumption" >
<u--input v-model="form.consumption" type="number" placeholder="请填写吃奶量"
<u--input v-model="form.consumption" type="digit" placeholder="请填写吃奶量"
inputAlign="left" :customStyle="getInputStyle('consumption')">
<template #suffix>
<up-text

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,8 +17,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="filter-btn" @click="filterPanel = !filterPanel">
@@ -38,24 +37,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -187,9 +188,13 @@ onLoad(() => {
}
});
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -39,7 +39,7 @@
inputAlign="left" :customStyle="getInputStyle('identityCard')"></u--input>
</u-form-item>
<u-form-item label="身高" prop="height">
<u--input v-model="form.height" placeholder="请填写身高" type="number"
<u--input v-model="form.height" placeholder="请填写身高" type="digit"
inputAlign="left" :customStyle="getInputStyle('height')">
<template #suffix>
<up-text text="CM"></up-text>
@@ -47,7 +47,7 @@
</u--input>
</u-form-item>
<u-form-item label="体重" prop="weight">
<u--input v-model="form.weight" placeholder="请填写体重" type="number"
<u--input v-model="form.weight" placeholder="请填写体重" type="digit"
inputAlign="left" :customStyle="getInputStyle('weight')">
<template #suffix>
<up-text text="KG"></up-text>

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,8 +17,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="filter-btn" @click="filterPanel = !filterPanel">
@@ -34,9 +33,9 @@
<view class="filter-panel" :style="{ height: `${windowHeight - 42}px` }">
<view class="filter-panel-content">
<view class="filter-title">健康档案</view>
<view class="health-record-input">
<view class="health-record-input" @click="handleHealthRecord">
<u--input v-model="queryParams.healthRecordName" border="surround" placeholder="请选择健康档案"
@click="handleHealthRecord" suffixIcon="search" suffixIconStyle="color: #909399">
suffixIcon="search" suffixIconStyle="color: #909399">
</u--input>
</view>
@@ -46,24 +45,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -213,9 +214,13 @@ onLoad(() => {
}
});
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,13 +17,12 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
</view>
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handleHealthRecord">
<u--input
v-model="queryParams.healthRecordName"
border="surround"
@@ -38,8 +37,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handleHealthRecord">
class="search-icon">
</uni-icons>
</view>
</view>
@@ -151,24 +149,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -260,7 +260,7 @@
</div> -->
<!-- 曲线图展示 -->
<view class="chart-container" v-if="listData.length>0 && viewMode === 'line'">
<view class="chart-container" v-if="chartVisible && listData.length>0 && viewMode === 'line'">
<qiun-data-charts
type="line"
canvasId="doctorLineChart"
@@ -273,7 +273,7 @@
</view>
<!-- 柱状图展示 -->
<view class="chart-container" v-if="listData.length>0 && viewMode === 'column'">
<view class="chart-container" v-if="chartVisible && listData.length>0 && viewMode === 'column'">
<qiun-data-charts
type="column"
canvasId="doctorColumnChart"
@@ -803,6 +803,7 @@ const { filterPanel, queryPersonParams, queryHealthRecordParams, queryParams} =
const windowHeight = computed(() => {
uni.getSystemInfoSync().windowHeight - 50
})
const chartVisible = computed(() => !filterPanel.value && !timeShow.value && !settingPickShow.value && !showPerson.value && !showHealthRecord.value)
onLoad(() => {
getDict()
// getList()
@@ -831,9 +832,13 @@ function dictStr(val, arr) {
return str
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function confirm(e) {
const date = timeHandler(new Date(e.value), '-', ':')
let formatValue = 'YYYY-MM-DD'

View File

@@ -46,7 +46,7 @@
</view>
<view class="chart-box tall">
<qiun-data-charts
v-if="hasChartData(trendChartData)"
v-if="chartVisible && hasChartData(trendChartData)"
type="line"
:canvasId="`${canvasIdPrefix}_trend`"
:chartData="trendChartData"
@@ -68,7 +68,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="hasChartData(recordChartData)"
v-if="chartVisible && hasChartData(recordChartData)"
type="column"
:canvasId="`${canvasIdPrefix}_record`"
:chartData="recordChartData"
@@ -110,6 +110,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="chartVisible"
type="ring"
:canvasId="`${canvasIdPrefix}_temp_ring`"
:chartData="temperatureRingData"
@@ -136,7 +137,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="hasChartData(medicineChartData)"
v-if="chartVisible && hasChartData(medicineChartData)"
type="bar"
:canvasId="`${canvasIdPrefix}_medicine`"
:chartData="medicineChartData"
@@ -168,7 +169,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="hasChartData(doctorCostChartData)"
v-if="chartVisible && hasChartData(doctorCostChartData)"
type="column"
:canvasId="`${canvasIdPrefix}_doctor`"
:chartData="doctorCostChartData"
@@ -208,7 +209,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="hasChartData(activityChartData)"
v-if="chartVisible && hasChartData(activityChartData)"
type="mix"
:canvasId="`${canvasIdPrefix}_activity`"
:chartData="activityChartData"
@@ -288,6 +289,7 @@ const selectedPersonId = ref(null)
const screenData = ref({})
const dictMap = ref({})
const canvasIdPrefix = `h5_health_${Date.now()}`
const chartVisible = computed(() => !showPerson.value)
const summary = computed(() => screenData.value.summary || {})
const record = computed(() => screenData.value.record || {})

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,13 +17,12 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
</view>
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handleHealthRecord">
<u--input
v-model="queryParams.healthRecordName"
border="surround"
@@ -38,8 +37,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handleHealthRecord">
class="search-icon">
</uni-icons>
</view>
</view>
@@ -216,24 +214,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -264,7 +264,7 @@
></u-datetime-picker>
<!-- 曲线图展示 -->
<view class="chart-container" v-if="secondListData.length>0 && viewMode === 'line'">
<view class="chart-container" v-if="chartVisible && secondListData.length>0 && viewMode === 'line'">
<qiun-data-charts
type="line"
canvasId="healthLineChart"
@@ -277,7 +277,7 @@
</view>
<!-- 柱状图展示 -->
<view class="chart-container" v-if="secondListData.length>0 && viewMode === 'column'">
<view class="chart-container" v-if="chartVisible && secondListData.length>0 && viewMode === 'column'">
<qiun-data-charts
type="column"
canvasId="healthColumnChart"
@@ -657,6 +657,7 @@ const { filterPanel, queryPersonParams,queryHealthRecordParams, queryParams} = t
const windowHeight = computed(() => {
uni.getSystemInfoSync().windowHeight - 50
})
const chartVisible = computed(() => !filterPanel.value && !timeShow.value && !settingPickShow.value && !showPerson.value && !showHealthRecord.value)
onLoad(() => {
getDict()
// getList()
@@ -688,9 +689,13 @@ if (data != null) {
}
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function confirm(e) {
const date = timeHandler(new Date(e.value), '-', ':')
let formatValue = 'YYYY-MM-DD'

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,13 +17,12 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
</view>
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handleHealthRecord">
<u--input
v-model="queryParams.healthRecordName"
border="surround"
@@ -38,8 +37,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handleHealthRecord">
class="search-icon">
</uni-icons>
</view>
</view>
@@ -68,7 +66,8 @@
</view>
</view>
<view class="app-container">
<scroll-view class="summary-scroll" scroll-y :style="{ height: summaryScrollHeight }">
<view class="app-container">
<view class="header-con" ref="searchHeightRef">
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
@@ -270,6 +269,7 @@
</view>
</view>
</view>
</scroll-view>
<!-- 视图切换 -->
<view class="section-title" v-show="listData.length>0">
@@ -311,24 +311,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -358,7 +360,7 @@
></u-datetime-picker>
<!-- 曲线图展示 -->
<view class="chart-container" v-if="listData.length>0 && viewMode === 'line'">
<view class="chart-container" v-if="chartVisible && listData.length>0 && viewMode === 'line'">
<qiun-data-charts
type="line"
canvasId="marLineChart"
@@ -371,7 +373,7 @@
</view>
<!-- 柱状图展示 -->
<view class="chart-container" v-if="listData.length>0 && viewMode === 'column'">
<view class="chart-container" v-if="chartVisible && listData.length>0 && viewMode === 'column'">
<qiun-data-charts
type="column"
canvasId="marColumnChart"
@@ -476,6 +478,21 @@ const timeShow= ref(false)
const time =ref( Number(new Date()))
const flag= ref(true)
const mar = ref({})
const summaryRowCount = computed(() => {
const hasValue = (key) => mar.value[key] != null
const dynamicRows = [
['top2Name', 'top3Name'],
['top4Name', 'top5Name'],
['top6Name', 'top7Name'],
['top8Name', 'top9Name'],
['top10Name', 'top11Name'],
['top12Name', 'top13Name'],
['top14Name', 'top15Name'],
['top16Name', 'top17Name']
].filter(([left, right]) => hasValue(left) || hasValue(right)).length
return 2 + dynamicRows
})
const summaryScrollHeight = computed(() => `${Math.min(summaryRowCount.value * 120, 480)}rpx`)
const tabShow = ref(false)
const viewMode = ref('list') // 'list', 'line', 'column'
@@ -687,6 +704,7 @@ const { filterPanel, queryPersonParams,queryHealthRecordParams, queryParams} = t
const windowHeight = computed(() => {
uni.getSystemInfoSync().windowHeight - 50
})
const chartVisible = computed(() => !filterPanel.value && !timeShow.value && !settingPickShow.value && !showPerson.value && !showHealthRecord.value)
onLoad(() => {
getDict()
// getList()
@@ -718,9 +736,13 @@ if (data != null) {
}
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function confirm(e) {
const date = timeHandler(new Date(e.value), '-', ':')
let formatValue = 'YYYY-MM-DD'
@@ -858,6 +880,12 @@ page {
height: 100%;
overflow: auto;
}
.summary-scroll {
width: 100%;
background-color: #f5f7fa;
flex-shrink: 0;
overflow: hidden;
}
.app-container {
background-color: #f5f7fa;
padding: 0;

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,8 +17,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
</view>
@@ -190,24 +189,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -238,7 +239,7 @@
></u-datetime-picker>
<!-- 曲线图展示 -->
<view class="chart-container" v-if="listData.length>0 && viewMode === 'line'">
<view class="chart-container" v-if="chartVisible && listData.length>0 && viewMode === 'line'">
<qiun-data-charts
type="line"
canvasId="milkLineChart"
@@ -251,7 +252,7 @@
</view>
<!-- 柱状图展示 -->
<view class="chart-container" v-if="listData.length>0 && viewMode === 'column'">
<view class="chart-container" v-if="chartVisible && listData.length>0 && viewMode === 'column'">
<qiun-data-charts
type="column"
canvasId="milkColumnChart"
@@ -519,6 +520,7 @@ const { filterPanel, queryPersonParams, queryParams} = toRefs(data)
const windowHeight = computed(() => {
uni.getSystemInfoSync().windowHeight - 50
})
const chartVisible = computed(() => !filterPanel.value && !timeShow.value && !settingPickShow.value && !showPerson.value)
onLoad(() => {
getDict()
// getList()
@@ -538,9 +540,13 @@ if (data != null) {
}
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function confirm(e) {
const date = timeHandler(new Date(e.value), '-', ':')
let formatValue = 'YYYY-MM-DD'

View File

@@ -95,7 +95,7 @@
</view>
<view class="chart-box tall">
<qiun-data-charts
v-if="hasChartData(temperatureChartData)"
v-if="chartVisible && hasChartData(temperatureChartData)"
type="line"
:canvasId="`${canvasIdPrefix}_temp`"
:chartData="temperatureChartData"
@@ -117,6 +117,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="chartVisible"
type="ring"
:canvasId="`${canvasIdPrefix}_mix`"
:chartData="recordMixChartData"
@@ -152,7 +153,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="hasChartData(medicineChartData)"
v-if="chartVisible && hasChartData(medicineChartData)"
type="bar"
:canvasId="`${canvasIdPrefix}_medicine`"
:chartData="medicineChartData"
@@ -184,7 +185,7 @@
</view>
<view class="chart-box">
<qiun-data-charts
v-if="hasChartData(doctorCostChartData)"
v-if="chartVisible && hasChartData(doctorCostChartData)"
type="column"
:canvasId="`${canvasIdPrefix}_doctor`"
:chartData="doctorCostChartData"
@@ -293,6 +294,7 @@ const screenData = ref({})
const activeTab = ref('process')
const dictMap = ref({})
const canvasIdPrefix = `h5_record_${Date.now()}`
const chartVisible = computed(() => !showPerson.value && !showRecord.value)
const record = computed(() => screenData.value.record || {})
const summary = computed(() => screenData.value.summary || {})

View File

@@ -2,7 +2,7 @@
<view class="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -17,13 +17,12 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
</view>
<view class="search-view">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handleHealthRecord">
<u--input
v-model="queryParams.healthRecordName"
border="surround"
@@ -38,8 +37,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handleHealthRecord">
class="search-icon">
</uni-icons>
</view>
</view>
@@ -192,24 +190,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -240,7 +240,7 @@
></u-datetime-picker>
<!-- 曲线图展示 -->
<view class="chart-container" v-if="hasSecondListData && viewMode === 'line'">
<view class="chart-container" v-if="chartVisible && hasSecondListData && viewMode === 'line'">
<qiun-data-charts
type="line"
:canvasId="canvasIdPrefix + '_line'"
@@ -253,7 +253,7 @@
</view>
<!-- 柱状图展示 -->
<view class="chart-container" v-if="hasSecondListData && viewMode === 'column'">
<view class="chart-container" v-if="chartVisible && hasSecondListData && viewMode === 'column'">
<qiun-data-charts
type="column"
:canvasId="canvasIdPrefix + '_column'"
@@ -634,6 +634,7 @@ uni.getSystemInfo({
windowHeight.value = res.windowHeight - 50
}
})
const chartVisible = computed(() => !filterPanel.value && !timeShow.value && !settingPickShow.value && !showPerson.value && !showHealthRecord.value)
onLoad(() => {
getDict()
// getList()
@@ -666,9 +667,13 @@ if (data != null) {
}
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function confirm(e) {
const date = timeHandler(new Date(e.value), '-', ':')
let formatValue = 'YYYY-MM-DD'

View File

@@ -26,7 +26,7 @@
</view>
</u-form-item>
<u-form-item label="体温" required prop="temperature" >
<u--input v-model="form.temperature" type="number" placeholder="请填写体温"
<u--input v-model="form.temperature" type="digit" placeholder="请填写体温"
inputAlign="left" :customStyle="getInputStyle('temperature')">
<template #suffix>
<up-text
@@ -103,7 +103,7 @@ queryHealthRecordParams: {
rules: {
healthRecordName: [{ required: true, message: '健康档案不能为空', trigger:['change', 'blur'] }],
personName: [{ required: true, message: '人员不能为空', trigger: ['change', 'blur'] }],
temperature: [{ type: 'number',required: true, message: '体温不能为空', trigger: ['change', 'blur'] }],
temperature: [{ required: true, message: '体温不能为空', trigger: ['change', 'blur'] }],
measureTime: [{ required: true, message: '测量时间不能为空', trigger: ['change', 'blur'] }]
}
})

View File

@@ -3,7 +3,7 @@
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-container">
<view class="search-row">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handlePerson">
<u--input
v-model="queryParams.personName"
border="surround"
@@ -18,8 +18,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handlePerson">
class="search-icon">
</uni-icons>
</view>
<view class="add-btn" @click="handleAdd()">
@@ -28,7 +27,7 @@
</view>
</view>
<view class="search-row">
<view class="search-input-wrapper">
<view class="search-input-wrapper" @click="handleHealthRecord">
<u--input
v-model="queryParams.healthRecordName"
border="surround"
@@ -43,8 +42,7 @@
type="search"
size="18"
color="#667eea"
class="search-icon"
@click="handleHealthRecord">
class="search-icon">
</uni-icons>
</view>
<view class="filter-btn" @click="filterPanel = !filterPanel">
@@ -63,24 +61,26 @@
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(true)"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
@click="openOrCloseDate(false)"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
<u-icon name="calendar" @click.stop="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
@@ -227,9 +227,13 @@ onLoad(() => {
}
});
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
if (typeof data === 'boolean') {
flag.value = data
timeShow.value = true
} else {
timeShow.value = false
}
}
function loadmore() {
pageNum.value += 1
if (status.value == 'loadmore') {

View File

@@ -1,5 +1,12 @@
<template>
<view class="login-container">
<view v-if="checkingToken" class="token-transition-screen">
<view class="token-loading-card">
<view class="token-loading-spinner"></view>
<text class="token-loading-text">正在加载中...</text>
</view>
</view>
<view v-else class="login-container">
<!-- Logo和标题区域 -->
<view class="header-section">
<view class="logo-box">
@@ -69,7 +76,7 @@
<!-- 版权信息 -->
<view class="copyright">
<text>Copyright © 2026 qdintc All Rights Reserved.</text>
<text>Copyright © {{ currentYear }} qdintc All Rights Reserved.</text>
</view>
</view>
</template>
@@ -82,9 +89,12 @@ import config from '@/config.js'
import useUserStore from '@/store/modules/user'
import { getWxCode } from '@/utils/geek';
import { wxLogin } from '@/api/oauth';
import { setToken } from '@/utils/auth';
import { getToken, setToken } from '@/utils/auth';
import { encrypt, decrypt } from '@/utils/jsencrypt'
// 动态显示当前年份
const currentYear = new Date().getFullYear()
// ========== 平台判断(条件编译)==========
// H5 端为 true其他端为 false。条件编译保证仅 H5 打包对应逻辑
let isH5 = false
@@ -101,6 +111,7 @@ const useWxLogin = ref(false); // 是否使用微信登录
const globalConfig = ref(config);
const agree = ref(isH5 ? true : false); // H5 端默认勾选同意协议
const rememberPwd = ref(false); // 记住密码
const checkingToken = ref(true); // 是否正在校验本地 token
const loginForm = ref({
username: "",
password: "",
@@ -119,11 +130,36 @@ if (useWxLogin.value) {
});
})
}
// 页面加载时检查是否记住了密码
// 页面加载时先校验本地 token仍有效则直接进入系统无效时再显示账号密码登录
onMounted(() => {
loadRememberedLogin()
initLoginPage()
});
async function initLoginPage() {
const token = getToken()
if (token) {
checkingToken.value = true
let tokenValid = false
try {
await userStore.getInfo(true)
tokenValid = true
} catch (error) {
if (error && error.code === 401) {
userStore.resetToken()
}
}
if (tokenValid) {
goHome()
return
}
}
checkingToken.value = false
loadRememberedLogin()
getCode()
}
// 读取本地记住的登录信息(仅 H5
function loadRememberedLogin() {
if (!isH5) return
@@ -209,12 +245,16 @@ async function pwdLogin() {
function loginSuccess(result) {
// 设置用户信息
userStore.getInfo().then(res => {
uni.switchTab({
url: '/pages/health/homepage/index'
});
goHome()
})
}
function goHome() {
uni.switchTab({
url: '/pages/health/homepage/index'
});
}
function agreeChange(){
agree.value = !agree.value;
}
@@ -244,7 +284,6 @@ function handleUserAgrement() {
url: '/pages/common/agreement/index'
});
};
getCode();
</script>
<style lang="scss" scoped>
@@ -262,6 +301,55 @@ page {
padding: 0 48rpx;
}
.token-transition-screen {
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg, #FFF3D6 0%, #FFE0A0 60%, #FFCC6E 100%);
}
.token-loading-card {
min-width: 220rpx;
min-height: 180rpx;
padding: 40rpx 48rpx;
border-radius: 32rpx;
background: rgba(255, 255, 255, 0.72);
border: 1rpx solid rgba(255, 255, 255, 0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 16rpx 48rpx rgba(139, 69, 0, 0.14);
}
.token-loading-spinner {
width: 58rpx;
height: 58rpx;
border-radius: 50%;
border: 6rpx solid rgba(232, 132, 26, 0.2);
border-top-color: #E8841A;
animation: token-loading-spin 0.8s linear infinite;
margin-bottom: 24rpx;
}
.token-loading-text {
font-size: 28rpx;
color: #8B4500;
font-weight: 500;
}
@keyframes token-loading-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.header-section {
padding-top: 120rpx;
display: flex;

View File

@@ -59,7 +59,7 @@
</view>
<view class="footer-section">
<text class="copyright-text">Copyright © 2026 qdintc All Rights Reserved.</text>
<text class="copyright-text">Copyright © {{ currentYear }} qdintc All Rights Reserved.</text>
</view>
<u-toast ref="uToast"></u-toast>
@@ -72,6 +72,8 @@
export default {
data() {
return {
// 动态显示当前年份
currentYear: new Date().getFullYear(),
user: {
username: '',
password: '',

View File

@@ -2,7 +2,7 @@
<view class="about-container">
<view class="header-section">
<view class="logo-wrapper">
<image class="logo-image" src="/static/logo.png" mode="aspectFit"></image>
<image class="logo-image" src="/static/logo1.png" mode="aspectFit"></image>
<view class="logo-shine"></view>
</view>
<text class="app-name">智聪网络科技</text>
@@ -25,7 +25,7 @@
</view>
<view class="copyright">
<text class="copyright-text">Copyright &copy; 2026 qdintc</text>
<text class="copyright-text">Copyright &copy; {{ currentYear }} qdintc</text>
<text class="copyright-text">All Rights Reserved.</text>
</view>
</view>
@@ -36,7 +36,25 @@ import { computed } from 'vue'
import config from '@/config.js'
const url = config.appInfo.site_url
const version = config.appInfo.version
// 动态获取版本号
// - 微信小程序:优先读取小程序后台提交发布时填写的版本号,为空时回退到 config
// 注:在微信开发者工具中调用时 miniProgram.version 通常为空字符串,
// 只有体验版/正式版发布后才有真实值
// - 其他端H5/APP使用 config 中的版本号
let version = config.appInfo.version
// #ifdef MP-WEIXIN
try {
const accountInfo = uni.getAccountInfoSync()
const mpVersion = accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.version
if (mpVersion) {
version = mpVersion
}
} catch (e) {
console.warn('getAccountInfoSync 调用失败,使用 config 中的版本号:', e)
}
// #endif
// 动态显示当前年份
const currentYear = new Date().getFullYear()
const infoList = computed(() => [
{

View File

@@ -40,9 +40,9 @@ const useUserStore = defineStore("user", {
});
},
// 获取用户信息
getInfo() {
getInfo(silentAuth = false) {
return new Promise((resolve, reject) => {
getInfo()
getInfo(silentAuth)
.then((res: any) => {
const user = res.user;
const avatar =
@@ -86,6 +86,16 @@ const useUserStore = defineStore("user", {
});
});
},
// 清理本地登录状态,不调用后端退出接口(用于本地 token 校验失败)
resetToken() {
this.token = "";
this.roles = [];
this.permissions = [];
this.name = "";
this.userId = "";
this.avatar = "";
removeToken();
},
},
persist: {
key: 'user-store',

View File

@@ -354,10 +354,12 @@ page {
font-size: 30rpx;
font-weight: 700;
line-height: 1.3;
white-space: nowrap;
/* 超长药品名称自动换行,最多 3 行后省略 */
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
word-break: break-all;
letter-spacing: 0.5rpx;
}
@@ -459,6 +461,8 @@ page {
.info-row {
display: flex;
flex-wrap: wrap;
/* 关键:使用 flex-start 顶部对齐,避免同行两个 item 超长换行后高度不齐 */
align-items: flex-start;
margin: 0 -12rpx;
.info-item {
@@ -491,12 +495,16 @@ page {
flex: 1;
line-height: 1.5;
word-break: break-all;
white-space: normal;
}
/* 两列布局下也允许换行(内容起长时撑开多行) */
&:not(.info-item-full) .info-value {
/* 超过 4 行后省略号截断,避免某个超长字段把整个卡片撑得太高 */
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
@@ -512,7 +520,8 @@ page {
.btn-edit,
.btn-delete,
.btn-copy,
.btn-detail {
.btn-detail,
.btn-summary {
display: flex;
align-items: center;
justify-content: center;
@@ -552,6 +561,13 @@ page {
color: $health-info;
border: 1rpx solid $health-info-border;
}
/* 汇总按钮:与 btn-detail 同一色调(信息色),避免各页面重复定义 */
.btn-summary {
background: $health-info-bg;
color: $health-info;
border: 1rpx solid $health-info-border;
}
}
}

View File

@@ -1,7 +1,9 @@
interface BaseRequestConfig {
headers?: {
/** 是否在请求头中添加token 默认是 */
isToken: boolean
isToken?: boolean,
/** token 失效时是否静默返回错误,不弹重新登录确认框 */
silentAuth?: boolean
},
/** 请求头配置 */
header?: any,

171
src/utils/imageCompress.ts Normal file
View File

@@ -0,0 +1,171 @@
/**
* 跨端图片压缩工具
*
* 用途:上传附件前压缩图片,避免大图(特别是手机原图 4MB+)导致上传卡死
*
* - H5用 Canvas 重绘 + toDataURL('image/jpeg', quality)
* - 小程序/APP用 uni.compressImage APIquality + compressedWidth
*
* 策略:
* - 原图小于 500KB 不压(避免无谓耗时)
* - 最大宽度限制 1280px横屏照片或按比例缩放
* - JPEG 质量 0.75(体积约 1/5 ~ 1/10肉眼几乎无差
* - 透明 PNG 也压成 JPEG牺牲透明通道换取体积如需透明可改 quality 调用
*
* 用法:
* import { compressImage } from '@/utils/imageCompress'
* const compressed = await compressImage(filePath)
* uploadFile({ filePath: compressed, ... })
*/
/** 不压缩的体积阈值字节800KB小图直接跳过避免无谓耗时 */
const SKIP_COMPRESS_SIZE = 800 * 1024
/** 最大边长超过按比例缩放保留宽高比。1920px 兼顾清晰度与体积 */
const MAX_SIDE = 1920
/** JPEG 压缩质量 0~1。0.85 可保证医疗单据/药品说明书文字清晰 */
const JPEG_QUALITY = 0.85
/**
* H5 端压缩Canvas 重绘 + toBlob
*/
function compressH5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
try {
// 计算缩放后的尺寸
let { width, height } = img
const longSide = Math.max(width, height)
if (longSide > MAX_SIDE) {
const scale = MAX_SIDE / longSide
width = Math.round(width * scale)
height = Math.round(height * scale)
}
// 创建 canvas 重绘
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
if (!ctx) {
resolve(filePath) // 极端情况降级到原图
return
}
// 白底(防止 PNG 透明通道转 JPEG 后变黑)
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, width, height)
ctx.drawImage(img, 0, 0, width, height)
// 转成 Blob 返回 ObjectURL避免 base64 体积膨胀)
canvas.toBlob(
(blob) => {
if (!blob) {
resolve(filePath)
return
}
// 释放旧的 ObjectURL如果是 blob: URL
if (filePath.startsWith('blob:')) {
try { URL.revokeObjectURL(filePath) } catch (e) { /* ignore */ }
}
resolve(URL.createObjectURL(blob))
},
'image/jpeg',
JPEG_QUALITY
)
} catch (err) {
console.warn('[imageCompress] H5 压缩失败,降级原图', err)
resolve(filePath)
}
}
img.onerror = (err) => {
console.warn('[imageCompress] H5 图片加载失败,降级原图', err)
resolve(filePath)
}
img.src = filePath
})
}
/**
* 小程序/APP 端压缩uni.compressImage
*/
function compressMP(filePath: string): Promise<string> {
return new Promise((resolve) => {
uni.compressImage({
src: filePath,
quality: Math.round(JPEG_QUALITY * 100), // 75
compressedWidth: MAX_SIDE,
success: (res) => resolve(res.tempFilePath),
fail: (err) => {
console.warn('[imageCompress] uni.compressImage 失败,降级原图', err)
resolve(filePath) // 失败降级
}
})
})
}
/**
* 获取文件大小(字节)。
* - H5: blob: / data: URL 用 fetch 解析
* - 小程序/APP: uni.getFileInfo
*/
function getFileSize(filePath: string): Promise<number> {
return new Promise((resolve) => {
// #ifdef H5
try {
if (filePath.startsWith('blob:') || filePath.startsWith('data:')) {
fetch(filePath)
.then(r => r.blob())
.then(b => resolve(b.size))
.catch(() => resolve(0))
return
}
} catch (e) { /* fallthrough */ }
resolve(0) // 无法获取则不压缩(保守策略)
return
// #endif
// #ifndef H5
uni.getFileInfo({
filePath,
success: (res) => resolve(res.size),
fail: () => resolve(0)
})
// #endif
})
}
/**
* 压缩图片入口(跨端)
* @param filePath 图片临时路径
* @returns 压缩后的图片路径(如失败则返回原路径)
*/
export async function compressImage(filePath: string): Promise<string> {
if (!filePath) return filePath
try {
// 检查体积,小图直接跳过
const size = await getFileSize(filePath)
if (size > 0 && size < SKIP_COMPRESS_SIZE) {
return filePath
}
// #ifdef H5
return await compressH5(filePath)
// #endif
// #ifndef H5
return await compressMP(filePath)
// #endif
} catch (err) {
console.warn('[imageCompress] 压缩过程异常,降级原图', err)
return filePath
}
}
/**
* 批量压缩图片(串行执行,避免一次性占用过多内存)
* @param filePaths 图片路径数组
* @returns 压缩后的路径数组(与入参顺序一致)
*/
export async function compressImages(filePaths: string[]): Promise<string[]> {
const result: string[] = []
for (const p of filePaths) {
result.push(await compressImage(p))
}
return result
}

View File

@@ -9,8 +9,10 @@ let timeout = 10000
const baseUrl = config.baseUrl
const request = <T>(config: RequestConfig): Promise<ResponseData<T>> => {
const metaHeaders = config.headers || {}
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
const isToken = metaHeaders.isToken === false
const silentAuth = metaHeaders.silentAuth === true
config.header = config.header || {}
if (getToken() && !isToken) {
config.header['Authorization'] = 'Bearer ' + getToken()
@@ -42,20 +44,23 @@ const request = <T>(config: RequestConfig): Promise<ResponseData<T>> => {
// @ts-ignore
const msg: string = errorCode[code] || data.msg || errorCode['default']
if (code === 401) {
showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
if (res.confirm) {
useUserStore().logOut().then(res => {
uni.reLaunch({ url: '/pages/login' })
})
}
})
reject('无效的会话,或者会话已过期,请重新登录。')
const authError = { code: 401, msg: '无效的会话,或者会话已过期,请重新登录' }
if (!silentAuth) {
showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
if (res.confirm) {
useUserStore().logOut().then(res => {
uni.reLaunch({ url: '/pages/login' })
})
}
})
}
return reject(silentAuth ? authError : authError.msg)
} else if (code === 500) {
toast(msg)
reject('500')
return reject('500')
} else if (code !== 200) {
toast(msg)
reject(code)
return reject(code)
}
resolve(data)
})
@@ -68,7 +73,9 @@ const request = <T>(config: RequestConfig): Promise<ResponseData<T>> => {
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
toast(message)
if (!silentAuth) {
toast(message)
}
reject(error)
})
})

View File

@@ -31,7 +31,16 @@ const upload = <T>(config: RequestUploadConfig): Promise<ResponseData<T>> => {
header: config.header,
formData: config.formData,
success: (res) => {
let result = JSON.parse(res.data)
let result
// 后端可能返回 JSON 字符串(正常)或 HTML 错误页(如 413 Payload Too Large
// 必须包 try-catch否则 JSON.parse 招错后 Promise 永远不 settlemask loading 永不关闭
try {
result = JSON.parse(res.data)
} catch (e) {
toast('上传失败:服务器响应非 JSON 格式(可能文件过大)')
reject('上传失败:响应解析失败')
return
}
const code = result.code || 200
// @ts-ignore
const msg = errorCode[code] || result.msg || errorCode['default']