feat: 电子药箱小程序 - 4Tab药品管理

- 电子药箱: 药品库存管理 + 手动录入 + 今日用药打卡
- 药品百科: 搜索 + 分类筛选 + 20种药品静态数据
- 惠教中心: 4条药品使用指南课程
- 我的: 家庭成员信息管理
- 自定义TabBar + navigation-bar组件
- SVG药品分类插图
This commit is contained in:
陈誉午
2026-07-29 11:20:52 +08:00
commit 994b267f5c
683 changed files with 43849 additions and 0 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"component":true,"usingComponents":{"Drawer":"../../../pubComponents/drawer/drawer","mp-icon":"weui-miniprogram/icon/icon"}}
@@ -0,0 +1,145 @@
<Drawer
id="questionnaire-drawer"
custom-drawer="questionnaire-drawer"
needCancel="{{false}}"
mask="{{effectiveClosable}}"
bind:myCancel="onDrawerCancel"
>
<view
class="questionnaire-drawer-wrap {{useDarkTheme ? 'questionnaire-drawer-wrap-dark' : ''}}"
>
<view class="questionnaire-header" wx:if="{{currentQuestionnaire}}">
<view class="questionnaire-title-row">
<view class="questionnaire-title">
{{currentQuestionnaire.title}}
</view>
<mp-icon
wx:if="{{effectiveClosable}}"
class="questionnaire-close-icon drawer-close-shadow"
bindtap="onDrawerCancel"
icon="close"
color="{{useDarkTheme ? '#A6A6A7' : '#939aa3'}}"
size="{{20}}"
/>
</view>
<view class="questionnaire-desc" wx:if="{{currentQuestionnaire.description}}">
{{currentQuestionnaire.description}}
</view>
<view
class="questionnaire-count"
wx:if="{{currentQuestionnaire.questions && currentQuestionnaire.questions.length}}"
>
共{{currentQuestionnaire.questions.length}}题
</view>
</view>
<view class="questionnaire-body">
<view wx:if="{{!currentQuestionnaire}}">
<view class="questionnaire-empty">暂无法获取问卷内容</view>
</view>
<view wx:elif="{{isFilled}}">
<view class="filled-status-container">
<image
class="filled-status-icon"
mode="aspectFit"
src="https://p1-live.byteimg.com/tos-cn-i-gjr78lqtd0/f3590f83cc9d49fc963b8763319752e5~tplv-gjr78lqtd0-image.image"
/>
<view class="filled-status-text">您已填写过该问卷,感谢您的参与</view>
</view>
</view>
<scroll-view
wx:else
scroll-y="true"
class="questionnaire-scroll"
>
<block wx:for="{{currentQuestionnaire.questions}}" wx:key="key">
<view class="question-item">
<view class="question-title">
<text class="question-required" wx:if="{{item.isRequired}}">*</text>
<text class="question-index">{{index + 1}}.</text>
<text class="question-text">{{item.title}}</text>
<text class="question-type-tag" wx:if="{{item.typeTag}}">{{item.typeTag}}</text>
</view>
<!-- 文本题 -->
<view wx:if="{{item.isInput}}" class="question-input {{item.disabled ? 'question-input-disabled' : ''}}">
<input
class="question-textarea {{item.disabled ? 'question-textarea-disabled' : ''}}"
placeholder="{{item.placeholder}}"
maxlength="{{item.maxlength}}"
type="{{item.inputType}}"
value="{{item.value}}"
disabled="{{item.disabled}}"
data-index="{{index}}"
bindinput="handleInputChange"
bindblur="handleInputBlur"
/>
</view>
<!-- 单选题 -->
<view wx:elif="{{item.isSingle}}" class="question-select">
<radio-group
data-index="{{index}}"
bindchange="handleRadioChange"
>
<label
wx:for="{{item.options}}"
wx:for-item="option"
wx:key="id"
class="question-option"
>
<radio
class="question-radio"
value="{{option.id}}"
checked="{{option.checked}}"
color="#3370ff"
/>
<view class="question-option-text">{{option.label}}</view>
</label>
</radio-group>
</view>
<!-- 多选题 -->
<view wx:elif="{{item.isMulti}}" class="question-select">
<checkbox-group
data-index="{{index}}"
bindchange="handleCheckboxChange"
>
<label
wx:for="{{item.options}}"
wx:for-item="option"
wx:key="id"
class="question-option"
>
<checkbox
class="question-checkbox"
value="{{option.id}}"
checked="{{option.checked}}"
color="#3370ff"
/>
<view class="question-option-text">{{option.label}}</view>
</label>
</checkbox-group>
</view>
<view wx:if="{{item.errorMessage}}" class="error-msg">{{item.errorMessage}}</view>
</view>
</block>
</scroll-view>
</view>
<view class="questionnaire-footer" wx:if="{{!isFilled}}">
<button
class="questionnaire-submit-btn {{isFilled || isSubmitting ? 'questionnaire-submit-btn-disabled' : ''}}"
type="primary"
loading="{{isSubmitting}}"
disabled="{{isFilled || isSubmitting}}"
bindtap="onSubmit"
>
{{isFilled ? '已提交' : '提交'}}
</button>
</view>
</view>
</Drawer>
@@ -0,0 +1 @@
.questionnaire-drawer .frame{overflow:hidden;padding:0!important}.questionnaire-drawer-wrap{background-color:#fff;padding-bottom:constant(safe-area-inset-bottom);padding-bottom:env(safe-area-inset-bottom)}.questionnaire-header{padding:24rpx 40rpx 16rpx;background:transparent linear-gradient(180deg,#e7edff,#fff)}.questionnaire-title-row{display:flex;align-items:center;justify-content:space-between}.questionnaire-title{font-size:calc(34rpx + .5 * (1rem - 16px));font-weight:700;color:#1d2129;flex:1;margin-right:24rpx}.questionnaire-close-icon{flex-shrink:0;box-sizing:border-box;display:flex;align-items:center;justify-content:center;width:56rpx;height:56rpx;border-radius:50%;background:0 0}.drawer-close-shadow{box-shadow:none}.questionnaire-desc{margin-top:8rpx;font-size:calc(26rpx + .5 * (1rem - 16px));color:#6f7680;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.questionnaire-count{margin-top:8rpx;font-size:calc(24rpx + .5 * (1rem - 16px));color:#86909c}.questionnaire-body{padding:16rpx 40rpx}.questionnaire-scroll{max-height:500rpx}.question-item{margin-bottom:24rpx}.question-title{display:flex;flex-wrap:wrap;font-size:calc(28rpx + .5 * (1rem - 16px));color:#1d2129;margin-bottom:12rpx}.question-index{margin-right:8rpx}.question-required{margin-right:8rpx;color:#f53f3f}.question-type-tag{margin-left:8rpx;font-size:calc(24rpx + .5 * (1rem - 16px));color:#86909c}.question-input{background-color:#f6f8fa;border-radius:8rpx;padding:20rpx 24rpx}.question-textarea{height:calc(44rpx + .5 * (1rem - 16px));line-height:calc(44rpx + .5 * (1rem - 16px));width:100%;font-size:calc(28rpx + .5 * (1rem - 16px));color:#1d2129}.question-input-disabled{background-color:#f2f3f5}.question-textarea-disabled{color:#86909c}.question-select{background-color:#f6f8fa;border-radius:8rpx;padding:8rpx 0}.error-msg{margin-top:12rpx;line-height:calc(34rpx + .5 * (1rem - 16px));font-size:calc(24rpx + .5 * (1rem - 16px));color:#ff4d4f}.question-option{display:flex;align-items:flex-start;padding:16rpx 24rpx}.question-option-text{flex:1;font-size:calc(28rpx + .5 * (1rem - 16px));color:#1d2129}.questionnaire-footer{padding:20rpx 40rpx 40rpx}.questionnaire-submit-btn{width:100%!important;border-radius:100rpx;background-color:#3370ff!important;font-size:calc(28rpx + .5 * (1rem - 16px));font-weight:400}.questionnaire-submit-btn-disabled{background-color:#8baeff!important;color:#fff!important}.filled-status-container{min-height:600rpx;padding:40rpx 0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}.filled-status-icon{width:calc(240rpx + .5 * (1rem - 16px));height:calc(240rpx + .5 * (1rem - 16px))}.filled-status-text{margin-top:24rpx;font-size:calc(28rpx + .5 * (1rem - 16px));line-height:calc(40rpx + .5 * (1rem - 16px));color:#999}.questionnaire-filled{padding:80rpx 60rpx;text-align:center}.questionnaire-filled-title{margin-top:16rpx;font-size:calc(30rpx + .5 * (1rem - 16px));color:#1d2129}.questionnaire-filled-sub{margin-top:8rpx;font-size:calc(24rpx + .5 * (1rem - 16px));color:#86909c}.questionnaire-empty{padding:80rpx 0;text-align:center;font-size:calc(26rpx + .5 * (1rem - 16px));color:#86909c}.questionnaire-drawer-wrap-dark{background-color:#202124}.questionnaire-drawer-wrap-dark .questionnaire-header{background:#202124}.questionnaire-drawer-wrap-dark .questionnaire-title{color:#fff}.questionnaire-drawer-wrap-dark .questionnaire-desc{color:#909090}.questionnaire-drawer-wrap-dark .question-index,.questionnaire-drawer-wrap-dark .question-option-text,.questionnaire-drawer-wrap-dark .question-text{color:#fff}.questionnaire-drawer-wrap-dark .question-input,.questionnaire-drawer-wrap-dark .question-select{background-color:#2c2d30}.questionnaire-drawer-wrap-dark .question-input-disabled{background-color:#26272a}.questionnaire-drawer-wrap-dark .question-textarea{color:#fff}.questionnaire-drawer-wrap-dark .question-textarea-disabled{color:#909090}.questionnaire-drawer-wrap-dark .questionnaire-filled-title{color:#fff}.questionnaire-drawer-wrap-dark .filled-status-text{color:#909090}.questionnaire-drawer-wrap-dark .questionnaire-count,.questionnaire-drawer-wrap-dark .questionnaire-filled-sub{color:#909090}
@@ -0,0 +1 @@
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateQuestionnaireQuestions=exports.validateQuestionnaireQuestion=void 0;var _validator=require("../../../../utils/validator"),isEmptyTextValue=function(e){return null==e||""===String(e).trim()},getQuestionErrorMessage=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.value,i=isEmptyTextValue(t),r=Array.isArray(t)&&0===t.length,a="";if(e.isRequired&&("input"===e.type&&i||"single"===e.type&&(null==t||""===t)||"multi"===e.type&&r)&&(a="此项为必填项"),!a&&"input"===e.type&&!i){var n=String(t).trim(),s=String(e.subTag||"");"phone_number"!==s||(0,_validator.isValidPhoneNumber)(n)?"email"!==s||(0,_validator.isValidEmail)(n)||(a="邮箱格式不正确"):a="手机号格式不正确"}return a},validateQuestionnaireQuestion=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=getQuestionErrorMessage(e),r="currentQuestionnaire.questions[".concat(t,"].errorMessage"),a={};return(i||e.errorMessage)&&(a[r]=i),{ok:!i,updates:a,errorMessage:i}};exports.validateQuestionnaireQuestion=validateQuestionnaireQuestion;var validateQuestionnaireQuestions=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t={},i=!1,r=0;r<e.length;r++){var a=e[r]||{},n=validateQuestionnaireQuestion(a,r),s=n.errorMessage,o=n.updates;s&&(i=!0),Object.assign(t,o)}return{ok:!i,updates:t}};exports.validateQuestionnaireQuestions=validateQuestionnaireQuestions;