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
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
+109
View File
@@ -0,0 +1,109 @@
/* 容器样式 */
.quiz-container {
padding: 40rpx 30rpx 230rpx;
box-sizing: border-box;
}
/* 题目卡片 */
.question-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
/* 题目标题 */
.question-title {
margin-bottom: 30rpx;
word-wrap: break-word;
white-space: normal;
}
.tag {
display: inline;
font-size: 28rpx;
margin-right: 16rpx;
border-radius: 6rpx;
font-size: 36rpx;
color: #04befe
}
.question-text {
font-size: 32rpx;
color: #333;
flex: 1;
width: 100%;
margin-top: 8rpx;
}
/* 选项区域 */
.options {
display: flex;
flex-direction: column;
gap: 20rpx;
}
/* 选项项 */
.option-item {
display: flex;
align-items: center;
padding: 20rpx;
border-radius: 12rpx;
background: #f8f8f8;
transition: all 0.3s ease;
}
.option-item:active {
transform: scale(0.98);
}
/* 选项文本 */
.option-item text {
margin-left: 16rpx;
font-size: 28rpx;
color: #333;
flex: 1;
}
/* 正确答案样式 */
.option-item.correct {
background: #f6ffed;
border: 2rpx solid #b7eb8f;
color: #52c41a;
}
.option-item.correct text {
color: #52c41a;
font-weight: 500;
}
/* 错误答案样式 */
.option-item.wrong {
background: #fff2f0;
border: 2rpx solid #ffccc7;
color: #ff4d4f;
}
.option-item.wrong text {
color: #ff4d4f;
font-weight: 500;
}
/* 选中状态样式 - 使用微信小程序兼容的方式 */
.option-item radio:checked + text,
.option-item checkbox:checked + text {
color: #1890ff;
font-weight: 500;
}
/* 由于微信小程序不支持:has()选择器,我们通过JS动态添加class来实现选中状态样式 */
.option-item.selected {
background: #e6f7ff;
// border: 2rpx solid #91d5ff;
}
.option-item.selected text {
color: #1890ff;
font-weight: 500;
}
+216
View File
@@ -0,0 +1,216 @@
// 定义接口类型
interface Question {
id: number;
type: number;
question: string;
option: Record<string, any>;
}
interface AnswerItem {
queId: string;
ans: string[];
suc?: boolean;
}
interface QuizComponent {
properties: {
questions: Question[];
answerList: AnswerItem[];
};
data: {
selectedAnswers: Record<number, string[]>;
renderQuestions: Array<
Question & { options: Array<{ key: string; value: string }> }
>;
};
setData: (data: Partial<Record<string, any>>, callback?: () => void) => void;
triggerEvent: (name: string, data?: any) => void;
isSelected: (qid: string | number, key: string) => boolean;
getSelectedAnswer: (qid: string | number, index: number) => string;
isCorrect: (qid: string, key: string) => boolean;
isWrong: (qid: string, key: string) => boolean;
handleRadioChange: (e: WechatMiniprogram.RadioGroupChange) => void;
handleCheckboxChange: (e: WechatMiniprogram.CheckboxGroupChange) => void;
getFormattedAnswers: () => Array<{
questionId: number;
answers: string[];
questionType: number;
}>;
getAllAnswers: () => Array<{
questionId: number;
answers: string[];
questionType: number;
}>;
isAllAnswered: () => boolean;
getProgress: () => { answered: number; total: number; percentage: number };
}
// 定义常量
const QUESTION_TYPE = {
SINGLE: 1,
MULTIPLE: 2,
};
Component({
properties: {
questions: {
type: Array,
value: [],
},
answerList: {
type: Array,
value: [],
},
},
observers: {
"questions, answerList": function (this: QuizComponent) {
const renderQuestions = (this.properties.questions || []).map(
(q: Question) => ({
...q,
options: Object.keys(q.option || {}).map((k) => ({
key: k,
value: q.option[k],
})),
})
);
const answerList = Array.isArray(this.properties.answerList)
? this.properties.answerList
: [];
const selectedAnswers = answerList.reduce((acc, item) => {
if (item && item.queId && item.ans) {
acc[Number(item.queId)] = Array.isArray(item.ans)
? item.ans
: [item.ans];
}
return acc;
}, {} as Record<number, string[]>);
this.setData({
renderQuestions,
selectedAnswers,
});
},
},
data: {
selectedAnswers: {} as Record<number, string[]>,
renderQuestions: [] as Array<
Question & { options: Array<{ key: string; value: string }> }
>,
},
methods: {
isSelected(this: QuizComponent, qid: string | number, key: string) {
const selected = this.data.selectedAnswers[Number(qid)];
return Array.isArray(selected) && selected.includes(key);
},
getSelectedAnswer(this: QuizComponent, qid: string | number, index: number) {
const selected = this.data.selectedAnswers[Number(qid)];
return Array.isArray(selected) ? selected[index] : "";
},
isCorrect(this: QuizComponent, qid: string, key: string) {
const answerList = Array.isArray(this.properties.answerList)
? this.properties.answerList
: [];
return answerList.some((item: AnswerItem) => {
const answers = Array.isArray(item.ans) ? item.ans : [];
return String(item.queId) === String(qid) && answers.includes(key) && item.suc;
});
},
isWrong(this: QuizComponent, qid: string, key: string) {
const answerList = Array.isArray(this.properties.answerList)
? this.properties.answerList
: [];
return answerList.some((item: AnswerItem) => {
const answers = Array.isArray(item.ans) ? item.ans : [];
return String(item.queId) === String(qid) && answers.includes(key) && !item.suc;
});
},
// 处理单选变化 { queId: q.id, ans: selectedAnswers }
handleRadioChange(
this: QuizComponent,
e: WechatMiniprogram.RadioGroupChange
) {
const { questionId } = e.currentTarget.dataset;
const value = e.detail.value;
if (!questionId) return;
const numericQuestionId = Number(questionId);
const newAnswers = value ? [value] : [];
this.setData({
[`selectedAnswers.${numericQuestionId}`]: newAnswers,
});
this.triggerEvent("change", {
queId: numericQuestionId,
ans: newAnswers,
});
},
// 处理多选变化
handleCheckboxChange(
this: QuizComponent,
e: WechatMiniprogram.CheckboxGroupChange
) {
const { questionId } = e.currentTarget.dataset;
const values = Array.isArray(e.detail.value) ? e.detail.value : [];
if (!questionId) return;
const numericQuestionId = Number(questionId);
this.setData({
[`selectedAnswers.${numericQuestionId}`]: values,
});
this.triggerEvent("change", {
queId: numericQuestionId,
ans: values,
});
},
getFormattedAnswers(this: QuizComponent) {
const answers = [];
for (const [questionId, selectedOptions] of Object.entries(
this.data.selectedAnswers
)) {
if (selectedOptions && selectedOptions.length > 0) {
answers.push({
queId: Number(questionId),
ans: selectedOptions,
});
}
}
return answers;
},
getAllAnswers(this: QuizComponent) {
return this.getFormattedAnswers();
},
isAllAnswered(this: QuizComponent) {
const totalQuestions = this.properties.questions.length;
const answeredQuestions = Object.values(this.data.selectedAnswers).filter(
(answers) => answers && answers.length > 0
).length;
return totalQuestions === answeredQuestions;
},
getProgress(this: QuizComponent) {
const totalQuestions = this.data.renderQuestions.length;
const answeredQuestions = Object.values(this.data.selectedAnswers).filter(
(answers) => answers && answers.length > 0
).length;
return {
answered: answeredQuestions,
total: totalQuestions,
percentage:
totalQuestions > 0
? Math.round((answeredQuestions / totalQuestions) * 100)
: 0,
};
},
},
});
@@ -0,0 +1,50 @@
<!--components/my-quiz/my-quiz.wxml-->
<view class="quiz-container">
<block wx:for="{{renderQuestions}}" wx:key="id" wx:for-item="q" wx:for-index="qi">
<view class="question-card">
<view class="question-title">
<text class="tag">{{qi + 1}}.</text>
<text class="tag" wx:if="{{q.type == 2}}">[ 多选题 ]</text>
<text class="tag" wx:else>[ 单选题 ]</text>
{{q.question}}
</view>
<view class="options">
<!-- 单选题使用 radio-group -->
<radio-group
wx:if="{{q.type == 1}}"
bindchange="handleRadioChange"
data-question-id="{{q.id}}"
value="{{getSelectedAnswer(q.id, 0)}}"
>
<block wx:for="{{q.options}}" wx:key="key" wx:for-item="op" wx:if="{{op.value}}">
<label class="option-item {{isCorrect(q.id, op.key) ? 'correct' : isWrong(q.id, op.key) ? 'wrong' : ''}} {{getSelectedAnswer(q.id, 0) === op.key ? 'selected' : ''}}">
<radio
value="{{op.key}}"
checked="{{getSelectedAnswer(q.id, 0) === op.key}}"
/>
<text>{{op.key}}. {{op.value}}</text>
</label>
</block>
</radio-group>
<!-- 多选题使用 checkbox-group -->
<checkbox-group
wx:if="{{q.type == 2}}"
bindchange="handleCheckboxChange"
data-question-id="{{q.id}}"
value="{{selectedAnswers[q.id] || []}}"
>
<block wx:for="{{q.options}}" wx:key="key" wx:for-item="op" wx:if="{{op.value}}">
<label class="option-item {{isCorrect(q.id, op.key) ? 'correct' : isWrong(q.id, op.key) ? 'wrong' : ''}} {{isSelected(q.id, op.key) ? 'selected' : ''}}">
<checkbox
value="{{op.key}}"
checked="{{isSelected(q.id, op.key)}}"
/>
<text>{{op.key}}. {{op.value}}</text>
</label>
</block>
</checkbox-group>
</view>
</view>
</block>
</view>