Files
zhxg_app/pages/finance/aid/apply.vue

1625 lines
51 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="app-container" style="padding-bottom: 70px;">
<!-- 顶部导航栏 -->
<!-- <view class="nav-bar" @click="goBack">
<uni-icons type="back" size="20" color="#fff"></uni-icons>
<text class="nav-title">助学金申请</text>
</view> -->
<!-- 选项卡容器 -->
<view class="tabs-container">
<view class="tabs-header">
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="switchTab(0)">
<uni-icons type="contact" size="12" :color="activeTab === 0 ? '#409EFF' : '#666'"></uni-icons>
<text class="tab-text">基本信息</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="switchTab(1)">
<uni-icons type="home" size="12" :color="activeTab === 1 ? '#409EFF' : '#666'"></uni-icons>
<text class="tab-text">家庭情况</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 2 }" @click="switchTab(2)">
<uni-icons type="team" size="12" :color="activeTab === 2 ? '#409EFF' : '#666'"></uni-icons>
<text class="tab-text">家庭成员</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 3 }" @click="switchTab(3)">
<uni-icons type="stats" size="12" :color="activeTab === 3 ? '#409EFF' : '#666'"></uni-icons>
<text class="tab-text">银行信息</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 4 }" @click="switchTab(4)">
<uni-icons type="compose" size="12" :color="activeTab === 4 ? '#409EFF' : '#666'"></uni-icons>
<text class="tab-text">申请理由</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 5 }" @click="switchTab(5)">
<uni-icons type="chat" size="12" :color="activeTab === 5 ? '#409EFF' : '#666'"></uni-icons>
<text class="tab-text">审核意见</text>
</view>
</view>
</view>
<!-- 表单内容区域 -->
<scroll-view scroll-y class="form-scroll" :style="{ height: formScrollHeight }">
<view class="form-wrapper">
<!-- 1. 基本信息标签页 -->
<view v-show="activeTab === 0" class="tab-panel">
<view class="form-card">
<view class="card-title">学生基本信息</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>学号</label>
<input v-model="formData.xh" placeholder="请输入学号" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>姓名</label>
<input v-model="formData.xm" placeholder="请输入姓名" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>性别</label>
<picker mode="selector" :range="genderOptions" :range-key="'text'" v-model="formData.xb" @change="handleGenderChange" :disabled="detailMode">
<view class="picker-input">
{{ formData.xb ? getGenderText(formData.xb) : '请选择性别' }}
</view>
</picker>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>入学时间</label>
<picker mode="date" fields="year-month" v-model="datePickerValue" @change="handleRxsjChange" :disabled="detailMode">
<view class="form-input">
{{ formData.rxsj ? formData.rxsj : '请选择入学时间' }}
</view>
</picker>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>民族</label>
<picker mode="selector" :range="nationOptions" v-model="formData.mz" @change="handleNationChange" :disabled="detailMode">
<view class="picker-input">
{{ formData.mz ? formData.mz : '请选择民族' }}
</view>
</picker>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>政治面貌</label>
<picker mode="selector" :range="zzmmOptions" v-model="zzmmIndex" @change="handleZzmmChange" :disabled="detailMode">
<view class="picker-input">
{{ formData.zzmm ? formData.zzmm : '请选择政治面貌' }}
</view>
</picker>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>出生年月</label>
<input v-model="formData.csny" placeholder="请输入出生年月格式YYYY-MM" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>电话</label>
<input v-model="formData.dh" placeholder="请输入电话" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>身份证号码</label>
<input v-model="formData.sfzhm" placeholder="请输入身份证号码" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>电子版一寸照</label>
<view class="example-body">
<uni-file-picker
@select="handlePhotoUpload"
@delete="deletePhoto"
:auto-upload="false"
limit="1"
:disabled="detailMode"
mode="grid"
></uni-file-picker>
</view>
<view class="upload-tip" v-if="!detailMode">支持上传jpg/png格式照片单个文件不超过5MB</view>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>二级学院</label>
<input v-model="formData.xy" placeholder="请输入二级学院" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>年级</label>
<input v-model="formData.nj" placeholder="请输入年级" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>班级</label>
<input v-model="formData.bj" placeholder="请输入班级" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>邮编</label>
<input v-model="formData.yb" placeholder="请输入邮编" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>曾获何种资助</label>
<input v-model="formData.zzls" placeholder="请输入曾获何种资助" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>参加过何种公益劳动(活动)</label>
<input v-model="formData.gyhd" placeholder="请输入参加过何种公益劳动(活动)" class="form-input" :disabled="detailMode"></input>
</view>
</view>
</view>
<!-- 2. 家庭情况标签页 -->
<view v-show="activeTab === 1" class="tab-panel">
<view class="form-card">
<view class="card-title">家庭基本情况</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>家庭人口总数</label>
<input v-model="formData.rkzs" placeholder="请输入家庭人口总数" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>家庭人均年收入()</label>
<input v-model="formData.rjnsr" placeholder="请输入家庭人均年收入" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>收入来源</label>
<input v-model="formData.srly" placeholder="请输入收入来源" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>家庭详细地址</label>
<input v-model="formData.dz" placeholder="请输入家庭详细地址" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>困难类型</label>
<view class="radio-container">
<view class="radio-item" @click="!detailMode && setDifficultyType('1')">
<view class="custom-radio" :class="{ 'radio-checked': formData.kndj === '1', 'radio-disabled': detailMode }">
<text v-if="formData.kndj === '1'" class="check-icon"></text>
</view>
<text class="radio-text">特别困难</text>
</view>
<view class="radio-item" @click="!detailMode && setDifficultyType('2')">
<view class="custom-radio" :class="{ 'radio-checked': formData.kndj === '2', 'radio-disabled': detailMode }">
<text v-if="formData.kndj === '2'" class="check-icon"></text>
</view>
<text class="radio-text">比较困难</text>
</view>
<view class="radio-item" @click="!detailMode && setDifficultyType('3')">
<view class="custom-radio" :class="{ 'radio-checked': formData.kndj === '3', 'radio-disabled': detailMode }">
<text v-if="formData.kndj === '3'" class="check-icon"></text>
</view>
<text class="radio-text">一般困难</text>
</view>
</view>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>困难标签</label>
<input v-model="formData.knlx" placeholder="请输入困难标签" class="form-input" disabled></input>
</view>
</view>
</view>
<!-- 3. 家庭成员标签页 -->
<view v-show="activeTab === 2" class="tab-panel">
<view class="form-card">
<view class="card-title">家庭成员情况</view>
<view class="family-list">
<view v-for="(member, index) in formData.jtcyObj" :key="index" class="family-item">
<view class="item-header">
<text class="item-title">家庭成员 {{ index + 1 }}</text>
<uni-icons type="close" size="20" color="#ff4d4f" @click="deleteFamilyMember(index)" class="delete-btn" v-if="!detailMode"></uni-icons>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>姓名</label>
<input v-model="member.xm" placeholder="请输入姓名" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>年龄</label>
<input v-model="member.nl" placeholder="请输入年龄" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>与学生关系</label>
<input v-model="member.gx" placeholder="请输入与学生关系" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label">学习或工作单位</label>
<input v-model="member.dw" placeholder="请输入工作单位" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label">联系电话</label>
<input v-model="member.lxdh" placeholder="请输入联系电话" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label">职业</label>
<input v-model="member.zy" placeholder="请输入职业" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label">年收入</label>
<input v-model="member.nsr" placeholder="请输入年收入" class="form-input" type="number" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label">健康状况</label>
<input v-model="member.jkzk" placeholder="请输入健康状况" class="form-input" :disabled="detailMode"></input>
</view>
</view>
</view>
<view class="add-family-btn" @click="addFamilyMember" v-if="!detailMode">
<uni-icons type="plus" size="20" color="#409EFF"></uni-icons>
<text>新增家庭成员</text>
</view>
</view>
</view>
<!-- 4. 经济状况标签页 -->
<view v-show="activeTab === 3" class="tab-panel">
<view class="form-card">
<view class="card-title">银行信息</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>农业银行卡号</label>
<input v-model="formData.bankCard" placeholder="请输入农业银行卡号" class="form-input" :disabled="detailMode"></input>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>开户行</label>
<input v-model="formData.bankAddr" placeholder="请输入开户行" class="form-input" :disabled="detailMode"></input>
</view>
</view>
</view>
<!-- 5. 申请理由标签页 -->
<view v-show="activeTab === 4" class="tab-panel">
<view class="form-card">
<view class="card-title">申请理由与承诺</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>申请理由</label>
<textarea v-model="formData.sqly" placeholder="请详细说明家庭经济困难情况50-150字" class="form-textarea" rows="6" maxlength="150" show-word-limit :disabled="detailMode"></textarea>
</view>
<view class="form-item">
<label class="form-label">困难佐证材料</label>
<view class="upload-btn" @click="handleUpload" :class="{ 'upload-disabled': detailMode }">
<text class="upload-icon">+</text>
<text>{{ detailMode ? '已上传佐证材料' : '上传文件' }}</text>
</view>
<view class="file-list" v-if="affixFiles.length">
<view class="file-item" v-for="(file, index) in affixFiles" :key="index">
<text class="file-name" @click="previewImage(file.savePath)">{{ file.attachmentName || file.trueName }}</text>
<uni-icons type="trash-filled" size="30" @click="deleteFile(index)" v-if="!detailMode" class="delete-btn"></uni-icons>
</view>
</view>
<view class="upload-tip" v-if="!detailMode">支持上传jpg/png/pdf格式文件单个文件不超过10MB如病例住房证明等</view>
</view>
<view class="form-item">
<label class="form-label"><span class="red-tip">*</span>手写签字</label>
<view class="sign-img" v-if="signImg != ''">
<image :src="signImg" mode="aspectFit" style="width: 200px; height: 100px;"></image>
<text @tap="signToggle" class="re-sign-text">重新签名</text>
</view>
<view v-else class="sign" @tap="signToggle" :class="{ 'sign-disabled': detailMode }">
点击签名
</view>
</view>
</view>
</view>
<!-- 6. 审核意见标签页 -->
<view v-show="activeTab === 5" class="tab-panel">
<view class="form-card">
<view class="card-title">审核意见</view>
<!-- 班级审核意见辅导员意见 -->
<view class="approval-section">
<view class="section-title">班级意见辅导员意见</view>
<view class="approval-content">
<text v-if="formData.bjyj || formData.bjyjdj || formData.fdymc || formData.fdyqmrq" class="approval-text">{{ formData.bjyj === '1' ? '同意' : formData.bjyj === '0' ? '不同意' : '已审核' }}</text>
<text v-else class="no-content">暂无审核意见</text>
</view>
<view class="approval-info">
<text v-if="formData.bjyjdj">确认助学金等级:<text class="difficulty-level">{{ getLevelText(formData.bjyjdj) }}</text></text>
<text v-if="formData.fdymc">审核人{{ formData.fdymc }}</text>
<text v-if="formData.fdyqmrq">审核时间{{ formData.fdyqmrq }}</text>
</view>
</view>
<!-- 二级学院审核意见 -->
<view class="approval-section">
<view class="section-title">二级学院意见</view>
<view class="approval-content">
<text v-if="formData.ejxyyj || formData.ejxyyjdj || formData.ejxyldmc || formData.ejxyldqmrq" class="approval-text">{{ formData.ejxyyj === '1' ? '同意' : formData.ejxyyj === '0' ? '不同意' : '已审核' }}</text>
<text v-else class="no-content">暂无审核意见</text>
</view>
<view class="approval-info">
<text v-if="formData.ejxyyjdj">确认助学金等级:<text class="difficulty-level">{{ getLevelText(formData.ejxyyjdj) }}</text></text>
<text v-if="formData.ejxyldmc">审核人{{ formData.ejxyldmc }}</text>
<text v-if="formData.ejxyldqmrq">审核时间{{ formData.ejxyldqmrq }}</text>
</view>
</view>
<!-- 学校审核意见 -->
<view class="approval-section">
<view class="section-title">学校意见</view>
<view class="approval-content">
<text v-if="formData.zzdj || formData.xxyj || formData.xxmc || formData.xxyjrq" class="approval-text">已审核</text>
<text v-else class="no-content">暂无审核意见</text>
</view>
<view class="approval-info">
<text v-if="formData.zzdj">确认助学金等级:<text class="difficulty-level">{{ getLevelText(formData.zzdj) }}</text></text>
<text v-if="formData.xxyj">学校意见{{ formData.xxyj }}</text>
<text v-if="formData.xxmc">审核人{{ formData.xxmc }}</text>
<text v-if="formData.xxyjrq">审核时间{{ formData.xxyjrq }}</text>
</view>
</view>
</view>
</view>
<!-- 提交按钮区域 -->
<view class="submit-container" v-if="!detailMode">
<button type="primary" @click="saveForm" class="submit-btn">
<uni-icons type="folder" size="16"></uni-icons> 保存
</button>
<button type="primary" @click="submitForm" class="submit-btn submit-primary">
<uni-icons type="checkmark" size="16"></uni-icons> 提交申请
</button>
</view>
</view>
</scroll-view>
<!-- 上传文件弹窗 -->
<uni-popup ref="filePopup" type="bottom">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">上传文件</text>
<uni-icons type="close" size="20" color="#999" @click="$refs.filePopup.close()" class="close-btn"></uni-icons>
</view>
<view class="popup-body">
<view class="upload-option" @click="chooseImage">
<uni-icons type="image" size="30" color="#409EFF"></uni-icons>
<text>拍照上传</text>
</view>
<view class="upload-option" @click="chooseFile">
<uni-icons type="document" size="30" color="#409EFF"></uni-icons>
<text>选择文件</text>
</view>
</view>
</view>
</uni-popup>
<!-- 签名组件 -->
<jp-signature-popup ref="jpSignature" :required="true" popup @change="uploadSign" />
</view>
</template>
<script>
import { addApply, updateApply, getApply, getStuInfo, getExtraInfo, getFamilyInfo, getCurrentYear, getAidLevels, getZxjStudentInfo, getOwnStudentInfo, findByXhAndApplyYear } from '@/api/finance/financialaid'
import uploadFile from "@/plugins/upload.js"
import { queryAffixs, getAffixItems, uploadFiles, deleteAffix } from '@/api/affix'
import { getUserProfile } from '@/api/system/user'
export default {
data() {
return {
activeTab: 0,
formScrollHeight: '600px',
detailMode: false, // 是否为查看详情模式
applyId: null, // 申请ID
zzmmIndex: 0, // 政治面貌索引
datePickerValue: '', // 时间选择器值
formData: {
step: 1,
xh: '',
xm: '',
xb: '',
rxsj: '',
mz: '',
zzmm: '',
csny: '',
dh: '',
sfzhm: '',
zp: '',
xy: '',
nj: '',
bj: '',
yb: '',
zzls: '',
gyhd: '',
rkzs: '',
rjnsr: '',
srly: '',
dz: '',
kndj: '',
knlx: '',
bankCard: '',
bankAddr: '',
jtcyObj: [
{
xm: '',
nl: '',
gx: '',
dw: '',
lxdh: '',
zy: '',
nsr: '',
jkzk: ''
}
],
sqly: '',
affixId: null,
bjyj: '',
bjyjdj: '',
ejxyyj: '',
ejxyyjdj: ''
},
genderOptions: [
{ value: '男', text: '男' },
{ value: '女', text: '女' }
],
nationOptions: ['汉族', '蒙古族', '回族', '藏族', '维吾尔族', '苗族', '彝族', '壮族', '布依族', '朝鲜族', '满族', '侗族', '瑶族', '白族', '土家族', '哈尼族', '哈萨克族', '傣族', '黎族', '傈僳族', '佤族', '畲族', '高山族', '拉祜族', '水族', '东乡族', '纳西族', '景颇族', '柯尔克孜族', '土族', '达斡尔族', '仫佬族', '羌族', '布朗族', '撒拉族', '毛南族', '仡佬族', '锡伯族', '阿昌族', '普米族', '塔吉克族', '怒族', '乌孜别克族', '俄罗斯族', '鄂温克族', '德昂族', '保安族', '裕固族', '京族', '塔塔尔族', '独龙族', '鄂伦春族', '赫哲族', '门巴族', '珞巴族', '基诺族'],
zzmmOptions: ['群众', '共青团员', '党员'],
affixFiles: [],
loading: false,
signImg: '',
baseUrl: uni.getStorageSync('baseUrl') || ''
}
},
onLoad(option) {
// 接收从主页面传递过来的学年信息
if (option && option.stuYearId) {
this.formData.stuYearId = option.stuYearId;
this.formData.applyYear = option.yearName;
}
// 接收从主页面传递过来的申请ID和类型
if (option && option.id) {
this.applyId = option.id;
if (option.type === 'detail') {
this.detailMode = true;
}
}
this.calculateFormHeight()
this.initData()
},
onShow() {
this.calculateFormHeight()
},
methods: {
// 计算表单滚动区域高度
calculateFormHeight() {
const systemInfo = uni.getSystemInfoSync()
const navHeight = 44 // 导航栏高度
const tabsHeight = 50 // 选项卡高度
const submitBtnHeight = 60 // 提交按钮高度
const padding = 20 // 页面内边距
this.formScrollHeight = (systemInfo.windowHeight - navHeight - tabsHeight - submitBtnHeight - padding) + 'px'
},
// 初始化数据
initData() {
if (this.applyId) {
// 无论是否为详情模式只要有applyId就获取申请详情
this.getApplyDetail()
} else {
// 如果没有applyId可能是新建申请的情况
// 可以在这里获取学生的基本信息并填充到表单中
this.getStudentInfo()
}
},
// 获取申请详情
getApplyDetail() {
this.loading = true
getApply(this.applyId).then(res => {
if (res.code === 200) {
this.formData = res.data
// 处理家庭成员数据
if (this.formData.jtcy) {
try {
this.formData.jtcyObj = JSON.parse(this.formData.jtcy)
} catch (e) {
console.error('解析家庭成员数据失败:', e)
this.formData.jtcyObj = [{
xm: '',
nl: '',
gx: '',
dw: '',
lxdh: '',
zy: '',
nsr: '',
jkzk: ''
}]
}
} else if (!this.formData.jtcyObj || this.formData.jtcyObj.length === 0) {
this.formData.jtcyObj = [{
xm: '',
nl: '',
gx: '',
dw: '',
lxdh: '',
zy: '',
nsr: '',
jkzk: ''
}]
}
// 加载附件信息
if (this.formData.affixId) {
this.loadAffixFiles()
}
// 处理手写签名回显
if (this.formData.xsqm) {
this.signImg = this.baseUrl + this.formData.xsqm;
}
}
}).finally(() => {
this.loading = false
})
},
// 加载附件信息
loadAffixFiles() {
getAffixItems({ affixId: this.formData.affixId }).then(res => {
if (res.code === 200) {
this.affixFiles = res.data;
}
});
},
// 获取学生基本信息
getStudentInfo() {
this.loading = true
// 同时请求所有需要的接口获取学生信息
Promise.all([
getZxjStudentInfo(), // /comprehensive/zxj/apply/getStudentInfo
getUserProfile(), // /system/user/profile
getFamilyInfo(), // /comprehensive/member/getOwnFamily
getOwnStudentInfo(), // /system/student/getOwnInfo
getStuInfo(), // /comprehensive/stuInfoView/getOwnInfo
getExtraInfo() // /comprehensive/extraInfo/getOwnInfo
]).then(([zxjStuRes, userProfileRes, familyRes, ownStudentRes, stuInfoRes, extraRes]) => {
// 处理助学金学生信息
if (zxjStuRes.code === 200 && zxjStuRes.data) {
// 根据接口返回的数据结构进行处理
this.formData.xh = zxjStuRes.data.stuNo || this.formData.xh || ''
this.formData.xm = zxjStuRes.data.name || this.formData.xm || ''
this.formData.xb = zxjStuRes.data.gender || this.formData.xb || ''
this.formData.xy = zxjStuRes.data.dept?.deptName || this.formData.xy || ''
this.formData.zy = zxjStuRes.data.srsMajors?.majorName || this.formData.zy || ''
this.formData.bj = zxjStuRes.data.srsClass?.className || this.formData.bj || ''
this.formData.csny = zxjStuRes.data.birthday || this.formData.csny || ''
this.formData.dh = zxjStuRes.data.phone || this.formData.dh || ''
}
// 处理用户个人信息
if (userProfileRes.code === 200 && userProfileRes.data) {
// 可以获取用户相关信息
}
// 处理家庭成员信息
if (familyRes.code === 200 && familyRes.data && familyRes.data.length > 0) {
this.formData.jtcyObj = []
for (let i = 0; i < familyRes.data.length; i++) {
let familyMember = familyRes.data[i]
let mappedMember = {
xm: familyMember.familyName || '',
nl: familyMember.age || '',
gx: familyMember.familyRelation || '',
dw: familyMember.workPlace || '',
lxdh: familyMember.phone || '',
zy: familyMember.job || '',
nsr: familyMember.yearMoney || '',
jkzk: familyMember.health || ''
}
this.formData.jtcyObj.push(mappedMember)
}
// 设置家庭人口总数
this.formData.rkzs = familyRes.data.length + 1
} else {
// 如果没有家庭成员信息,设置一个默认的空对象
this.formData.jtcyObj = [{ xm: '', nl: '', gx: '', dw: '', lxdh: '', zy: '', nsr: '', jkzk: '' }]
}
// 处理学生个人信息
if (ownStudentRes.code === 200 && ownStudentRes.data) {
// 补充学生基本信息
this.formData.bankAddr = ownStudentRes.data.bankAddr || this.formData.bankAddr || ''
}
// 处理学生信息视图
if (stuInfoRes.code === 200 && stuInfoRes.data) {
// 补充学生基本信息
this.formData.nj = stuInfoRes.data.gradeName || this.formData.nj || ''
this.formData.bankCard = stuInfoRes.data.bankCard || this.formData.bankCard || ''
this.formData.sfzhm = stuInfoRes.data.idCard || this.formData.sfzhm || ''
}
// 处理学生扩展信息
if (extraRes.code === 200 && extraRes.data) {
// 补充学生扩展信息
this.formData.mz = extraRes.data.mz || this.formData.mz || ''
this.formData.zzmm = extraRes.data.zzmm || this.formData.zzmm || ''
this.formData.dz = extraRes.data.xxlxdz || this.formData.dz || ''
this.formData.yb = extraRes.data.jtyzbm || this.formData.yb || ''
}
// 查询贫困申请信息
if (this.formData.xh && this.formData.stuYearId) {
findByXhAndApplyYear({
xh: this.formData.xh,
stuYearId: this.formData.stuYearId
}).then(knrdRes => {
if (knrdRes.code === 200 && knrdRes.data) {
// 处理贫困申请信息
this.formData.rjnsr = knrdRes.data.rjnsr || this.formData.rjnsr || ''
this.formData.srly = knrdRes.data.jtzysr || this.formData.srly || ''
this.formData.knlx = knrdRes.data.knlx || this.formData.knlx || ''
}
}).catch(error => {
console.error('查询贫困申请信息失败:', error)
})
}
}).catch(error => {
console.error('获取学生信息失败:', error)
}).finally(() => {
this.loading = false
})
},
// 切换标签页
switchTab(tabIndex) {
this.activeTab = tabIndex
// 等待DOM更新后再滚动到顶部
this.$nextTick(() => {
uni.pageScrollTo({
scrollTop: 0,
duration: 300
})
})
},
// 性别选择处理
handleGenderChange(e) {
this.formData.xb = this.genderOptions[e.detail.value].value
},
// 获取性别文本
getGenderText(value) {
const option = this.genderOptions.find(item => item.value === value)
return option ? option.text : ''
},
// 民族选择处理
handleNationChange(e) {
this.formData.mz = this.nationOptions[e.detail.value]
},
// 入学时间选择处理
handleRxsjChange(e) {
// 将选择的日期格式化为YYYY-MM
const date = new Date(e.detail.value)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
this.formData.rxsj = `${year}-${month}`
},
// 政治面貌选择处理
handleZzmmChange(e) {
this.formData.zzmm = this.zzmmOptions[e.detail.value]
},
// 设置困难类型
setDifficultyType(type) {
this.formData.kndj = type
},
// 新增家庭成员
addFamilyMember() {
this.formData.jtcyObj.push({
xm: '',
nl: '',
gx: '',
dw: '',
lxdh: '',
zy: '',
nsr: '',
jkzk: ''
})
},
// 删除家庭成员
deleteFamilyMember(index) {
if (this.formData.jtcyObj.length > 1) {
this.formData.jtcyObj.splice(index, 1)
}
},
// 处理文件上传
handleUpload() {
if (this.detailMode) {
return;
}
console.log('handleUpload方法被调用');
// 打开文件选择弹窗
this.$refs.filePopup.open()
},
// 处理照片上传
handlePhotoUpload(e) {
if (this.detailMode) {
return;
}
console.log('handlePhotoUpload方法被调用');
const tempFile = e.tempFiles[0];
// 移除图片格式校验,允许上传任意格式的照片
// 验证文件大小5MB
if (tempFile.size > 5 * 1024 * 1024) {
uni.showToast({
title: '照片大小不能超过5MB',
icon: 'none'
});
return;
}
// 上传照片
this.uploadPhoto(tempFile.path, tempFile)
},
// 拍照上传
chooseImage() {
this.$refs.filePopup.close()
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'],
success: (res) => {
// 为拍照上传的文件生成一个有意义的文件名
const timestamp = new Date().getTime();
const tempFile = res.tempFiles[0];
const fileInfo = {
name: `photo_${timestamp}.jpg`,
size: tempFile.size
};
this.handleFileUpload(tempFile.path, fileInfo)
}
})
},
// 选择文件上传
chooseFile() {
this.$refs.filePopup.close()
uni.chooseFile({
count: 10,
extension: ['.jpg', '.png', '.pdf'],
success: (res) => {
for (const file of res.tempFiles) {
this.handleFileUpload(file.path, file)
}
}
})
},
// 上传照片到服务器
uploadPhoto(filePath, fileInfo = null) {
// 调试信息
console.log('baseUrl:', this.baseUrl);
// 构造上传参数
const formDataObj = {
fileName: fileInfo ? fileInfo.name : '未命名照片'
};
// 上传文件
uploadFile('/affix/upload', filePath, formDataObj).then((uploadRes) => {
const result = typeof uploadRes === 'string' ? JSON.parse(uploadRes) : uploadRes;
// 上传结果校验
if (result && result.code === 200) {
// 调试信息
console.log('上传成功,返回结果:', result);
console.log('savePath:', result.savePath);
console.log('url:', result.url);
// 保存照片路径,参考签名图片的保存方式
this.formData.zp = result.url || result.savePath;
uni.showToast({
title: '照片上传成功',
icon: 'success'
});
} else {
// 上传失败处理
uni.showToast({
title: '照片上传失败',
icon: 'none'
});
}
}).catch(err => {
console.error('照片上传失败:', err)
uni.showToast({
title: '照片上传失败',
icon: 'none'
})
})
},
// 上传文件到服务器
handleFileUpload(filePath, fileInfo = null) {
// 1. 初始化affixId如果为空则生成唯一ID
if (!this.formData.affixId) {
this.formData.affixId = this.generateUUID();
}
// 2. 构造上传参数
const formDataObj = {
affixId: this.formData.affixId,
fileName: fileInfo ? fileInfo.name : '未命名文件'
};
// 3. 上传文件
uploadFile('/affix/upload', filePath, formDataObj).then((uploadRes) => {
const result = typeof uploadRes === 'string' ? JSON.parse(uploadRes) : uploadRes;
// 4. 上传结果校验
if (result && result.code === 200) {
// 5. 构造文件信息对象
const newFileInfo = {
attachmentName: fileInfo ? fileInfo.name : '未命名文件',
attachmentUrl: result.savePath,
serverUrl: result.savePath || '',
fileId: result.id || '',
fileSize: fileInfo ? fileInfo.size : 0,
fileSuffix: fileInfo ? fileInfo.name.split('.').pop().toLowerCase() : '',
savePath: result.savePath
};
// 6. 添加到文件列表
this.affixFiles.push(newFileInfo);
uni.showToast({
title: '文件上传成功',
icon: 'success'
});
} else {
// 上传失败处理
uni.showToast({
title: '文件上传失败',
icon: 'none'
});
}
}).catch(err => {
console.error('文件上传失败:', err)
uni.showToast({
title: '文件上传失败',
icon: 'none'
})
})
},
// 预览文件
previewImage(savePath) {
// 处理URL拼接避免重复斜杠
const baseUrlClean = this.baseUrl.replace(/\/$/, '');
const pathClean = savePath.replace(/^\//, '');
const fullUrl = `${baseUrlClean}/${pathClean}`;
uni.previewImage({
urls: [fullUrl]
});
},
// 删除照片
deletePhoto(e) {
if (this.detailMode) {
return;
}
uni.showModal({
title: '提示',
content: '确定要删除这张照片吗?',
success: (res) => {
if (res.confirm) {
this.formData.zp = '';
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
}
});
},
// 获取完整图片URL
getFullImageUrl(path) {
if (!path) return '';
// 调试信息
console.log('getFullImageUrl调用path:', path);
// 如果已经是完整URL则直接返回
if (path.startsWith('http://') || path.startsWith('https://')) {
return path;
}
// 直接从存储中获取最新的baseUrl
const currentBaseUrl = uni.getStorageSync('baseUrl') || '';
console.log('当前baseUrl:', currentBaseUrl);
// 处理baseUrl确保结尾没有斜杠
const baseUrlClean = currentBaseUrl.replace(/\/$/, '');
// 处理path确保开头没有斜杠
const pathClean = path.replace(/^\//, '');
// 拼接URL
const fullUrl = `${baseUrlClean}/${pathClean}`;
console.log('拼接后的完整URL:', fullUrl);
return fullUrl;
},
// 删除文件
deleteFile(index) {
const deletedFile = this.affixFiles[index];
this.affixFiles.splice(index, 1);
if (this.affixFiles.length === 0) {
this.formData.affixId = null;
}
uni.showToast({
title: '删除成功',
icon: 'success'
});
},
// 生成UUID
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
},
// 打开签名组件
signToggle() {
this.$refs.jpSignature.toPop()
},
// 上传签名
uploadSign(imgSrc) {
uploadFile('/common/upload', imgSrc).then((res) => {
const data = JSON.parse(res);
this.formData.xsqm = data.fileName;
this.signImg = data.url || (this.baseUrl + data.fileName);
})
},
// 保存表单
saveForm() {
if (!this.validateForm()) {
return
}
this.loading = true
const submitData = { ...this.formData }
// 如果有申请ID则更新申请否则新增申请
const request = this.applyId ? updateApply(submitData) : addApply(submitData)
request.then(res => {
if (res.code === 200) {
uni.showToast({ title: '保存成功', icon: 'success' })
// 如果是新增申请保存成功后获取申请ID
if (!this.applyId) {
this.applyId = res.data.id
}
}
}).finally(() => {
this.loading = false
})
},
// 提交申请
submitForm() {
if (!this.validateForm()) {
return
}
uni.showModal({
title: '提示',
content: '确定要提交申请吗?提交后将无法修改。',
confirmText: '确定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.loading = true
const submitData = { ...this.formData }
// 设置申请状态为已提交
submitData.status = 1
// 如果有申请ID则更新申请否则新增申请
const request = this.applyId ? updateApply(submitData) : addApply(submitData)
request.then(res => {
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
// 提交成功后返回列表页面
setTimeout(() => {
this.goBack()
}, 1500)
}
}).finally(() => {
this.loading = false
})
}
}
})
},
// 表单验证
validateForm() {
// 基本信息验证
if (!this.formData.xh) {
uni.showToast({ title: '请输入学号', icon: 'none' })
return false
}
if (!this.formData.xm) {
uni.showToast({ title: '请输入姓名', icon: 'none' })
return false
}
if (!this.formData.xb) {
uni.showToast({ title: '请选择性别', icon: 'none' })
return false
}
if (!this.formData.rxsj) {
uni.showToast({ title: '请输入入学时间', icon: 'none' })
return false
}
if (!this.formData.mz) {
uni.showToast({ title: '请选择民族', icon: 'none' })
return false
}
if (!this.formData.zzmm) {
uni.showToast({ title: '请选择政治面貌', icon: 'none' })
return false
}
if (!this.formData.csny) {
uni.showToast({ title: '请输入出生年月', icon: 'none' })
return false
}
if (!this.formData.dh) {
uni.showToast({ title: '请输入电话', icon: 'none' })
return false
}
if (!this.formData.sfzhm) {
uni.showToast({ title: '请输入身份证号码', icon: 'none' })
return false
}
if (!this.formData.xy) {
uni.showToast({ title: '请输入二级学院', icon: 'none' })
return false
}
if (!this.formData.nj) {
uni.showToast({ title: '请输入年级', icon: 'none' })
return false
}
if (!this.formData.bj) {
uni.showToast({ title: '请输入班级', icon: 'none' })
return false
}
if (!this.formData.yb) {
uni.showToast({ title: '请输入邮编', icon: 'none' })
return false
}
if (!this.formData.zzls) {
uni.showToast({ title: '请输入曾获何种资助', icon: 'none' })
return false
}
if (!this.formData.gyhd) {
uni.showToast({ title: '请输入参加过何种公益劳动(活动)', icon: 'none' })
return false
}
// 家庭情况验证
if (!this.formData.rkzs) {
uni.showToast({ title: '请输入家庭人口总数', icon: 'none' })
return false
}
if (!this.formData.rjnsr) {
uni.showToast({ title: '请输入家庭人均年收入', icon: 'none' })
return false
}
if (!this.formData.srly) {
uni.showToast({ title: '请输入收入来源', icon: 'none' })
return false
}
if (!this.formData.dz) {
uni.showToast({ title: '请输入家庭详细地址', icon: 'none' })
return false
}
if (!this.formData.kndj) {
uni.showToast({ title: '请选择困难类型', icon: 'none' })
return false
}
if (!this.formData.knlx) {
uni.showToast({ title: '请输入困难标签', icon: 'none' })
return false
}
// 家庭成员验证
for (let i = 0; i < this.formData.jtcyObj.length; i++) {
const member = this.formData.jtcyObj[i]
if (!member.xm) {
uni.showToast({ title: `请输入第${i + 1}位家庭成员的姓名`, icon: 'none' })
return false
}
if (!member.nl) {
uni.showToast({ title: `请输入第${i + 1}位家庭成员的年龄`, icon: 'none' })
return false
}
if (!member.gx) {
uni.showToast({ title: `请输入第${i + 1}位家庭成员与学生的关系`, icon: 'none' })
return false
}
}
// 经济状况验证
if (!this.formData.bankCard) {
uni.showToast({ title: '请输入农业银行卡号', icon: 'none' })
return false
}
if (!this.formData.bankAddr) {
uni.showToast({ title: '请输入开户行', icon: 'none' })
return false
}
// 申请理由验证
if (!this.formData.sqly) {
uni.showToast({ title: '请输入申请理由', icon: 'none' })
return false
}
if (this.formData.sqly.length < 50) {
uni.showToast({ title: '申请理由不能少于50字', icon: 'none' })
return false
}
if (this.formData.sqly.length > 150) {
uni.showToast({ title: '申请理由不能超过150字', icon: 'none' })
return false
}
// 签名验证
if (!this.signImg) {
uni.showToast({ title: '请完成手写签字', icon: 'none' })
return false
}
return true
},
// 获取助学金等级文本
getLevelText(level) {
const levelMap = {
'1': '一等',
'2': '二等',
'3': '三等'
}
return levelMap[level] || ''
},
// 保存表单
saveForm() {
this.submitForm(false)
},
// 提交表单
submitForm(submit = true) {
// 表单验证
if (!this.validateForm()) {
return
}
this.loading = true
// 准备提交数据
const data = {
...this.formData,
// 将家庭成员数组转换为JSON字符串
jtcy: JSON.stringify(this.formData.jtcyObj),
// 设置操作类型
step: submit ? 1 : 0,
// 明确添加困难标签字段,确保传递到后端
knlx: this.formData.knlx
}
if (this.applyId) {
// 更新申请
updateApply(data).then(res => {
if (res.code === 200) {
uni.showToast({
title: submit ? '申请提交成功' : '表单保存成功',
icon: 'success'
})
// 如果是提交申请,跳转到列表页面
if (submit) {
uni.redirectTo({
url: '/pages/finance/aid/index'
})
}
} else {
uni.showToast({
title: res.msg || (submit ? '申请提交失败' : '表单保存失败'),
icon: 'none'
})
}
this.loading = false
}).catch(err => {
console.error('更新申请失败:', err)
uni.showToast({
title: submit ? '申请提交失败' : '表单保存失败',
icon: 'none'
})
this.loading = false
})
} else {
// 新增申请
addApply(data).then(res => {
if (res.code === 200) {
uni.showToast({
title: submit ? '申请提交成功' : '表单保存成功',
icon: 'success'
})
// 如果是提交申请,跳转到列表页面
if (submit) {
uni.redirectTo({
url: '/pages/finance/aid/index'
})
} else {
// 保存成功后获取申请ID
this.applyId = res.data.id
}
} else {
uni.showToast({
title: res.msg || (submit ? '申请提交失败' : '表单保存失败'),
icon: 'none'
})
}
this.loading = false
}).catch(err => {
console.error('新增申请失败:', err)
uni.showToast({
title: submit ? '申请提交失败' : '表单保存失败',
icon: 'none'
})
this.loading = false
})
}
},
// 返回列表页面
goBack() {
uni.redirectTo({
url: '/pages/finance/aid/index'
});
}
}
}
</script>
<style scoped>
.app-container {
height: 100vh;
background-color: #f5f7fa;
}
.nav-bar {
height: 44px;
background-color: #409EFF;
display: flex;
align-items: center;
padding: 0 15px;
color: #fff;
position: relative;
z-index: 100;
}
.nav-title {
font-size: 18px;
font-weight: bold;
margin-left: 15px;
}
.tabs-container {
background-color: #fff;
border-bottom: 1px solid #e4e7ed;
}
.tabs-header {
display: flex;
overflow-x: auto;
white-space: nowrap;
padding: 0 10px;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 15px;
cursor: pointer;
}
.tab-item.active {
color: #409EFF;
border-bottom: 2px solid #409EFF;
}
.tab-text {
font-size: 12px;
margin-top: 2px;
}
.form-scroll {
flex: 1;
}
.form-wrapper {
padding: 10px;
}
.tab-panel {
padding-bottom: 20px;
}
.form-card {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.card-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #303133;
border-left: 3px solid #409EFF;
padding-left: 10px;
}
.form-item {
display: flex;
flex-direction: column;
margin-bottom: 15px;
}
.form-label {
font-size: 14px;
color: #606266;
margin-bottom: 5px;
font-weight: 500;
}
.red-tip {
color: #f56c6c;
margin-right: 4px;
}
.form-input {
width: 100%;
height: 40px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 10px;
font-size: 14px;
color: #606266;
}
.form-textarea {
width: 100%;
min-height: 120px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
font-size: 14px;
color: #606266;
resize: none;
}
.picker-input {
width: 100%;
height: 40px;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 10px;
font-size: 14px;
color: #606266;
display: flex;
align-items: center;
}
.radio-container {
width: 100%;
display: flex;
flex-wrap: wrap;
}
.radio-item {
display: flex;
align-items: center;
margin-right: 20px;
margin-bottom: 10px;
}
.custom-radio {
width: 20px;
height: 20px;
border: 1px solid #dcdfe6;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 5px;
cursor: pointer;
}
.radio-checked {
border-color: #409EFF;
background-color: #409EFF;
}
.check-icon {
color: #fff;
font-size: 12px;
}
.radio-disabled {
opacity: 0.5;
cursor: not-allowed;
}
.family-list {
margin-bottom: 15px;
}
.family-item {
background-color: #f5f7fa;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.item-title {
font-size: 14px;
font-weight: bold;
color: #303133;
}
.delete-btn {
cursor: pointer;
}
.add-family-btn {
display: flex;
align-items: center;
justify-content: center;
color: #409EFF;
cursor: pointer;
padding: 10px;
border: 1px dashed #409EFF;
border-radius: 4px;
margin-top: 10px;
}
.upload-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
width: 150px;
height: 40px;
border: 1px solid #409EFF;
border-radius: 4px;
background-color: #f5f7fa;
color: #409EFF;
cursor: pointer;
transition: all 0.3s;
}
.upload-icon {
font-size: 20px;
font-weight: bold;
}
.upload-disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: #f5f7fa;
border-color: #dcdfe6;
color: #909399;
}
.file-list {
margin-top: 15px;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
margin-bottom: 10px;
}
.file-name {
flex: 1;
font-size: 14px;
color: #606266;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.upload-tip {
font-size: 12px;
color: #909399;
margin-top: 5px;
}
.photo-upload-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.photo-preview {
width: 120px;
height: 160px;
object-fit: cover;
border-radius: 4px;
border: 1px solid #dcdfe6;
}
.delete-btn {
margin-left: 10px;
color: #f56c6c;
cursor: pointer;
align-self: center;
}
.sign-img {
flex: 1;
display: flex;
align-items: center;
}
.re-sign-text {
margin-left: 10px;
color: #409EFF;
cursor: pointer;
font-size: 14px;
}
.sign {
flex: 1;
height: 100px;
border: 1px dashed #dcdfe6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
cursor: pointer;
}
.sign-disabled {
opacity: 0.5;
cursor: not-allowed;
}
.submit-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
padding: 10px;
background-color: #fff;
border-top: 1px solid #e4e7ed;
gap: 10px;
z-index: 100;
}
.submit-btn {
flex: 1;
height: 40px;
border-radius: 4px;
border: none;
background-color: #ecf5ff;
color: #409EFF;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.submit-primary {
background-color: #409EFF;
color: #fff;
}
.popup-content {
background-color: #fff;
border-radius: 10px 10px 0 0;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #e4e7ed;
}
.popup-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.close-btn {
cursor: pointer;
}
.popup-body {
display: flex;
justify-content: space-around;
padding: 30px 0;
}
.upload-option {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.upload-option text {
margin-top: 10px;
font-size: 14px;
color: #606266;
}
.approval-section {
margin-bottom: 25px;
padding: 20px;
background-color: #fafafa;
border-radius: 8px;
border: 1px solid #e8e8e8;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 2px solid #1989fa;
}
.approval-content {
background-color: #f5f7fa;
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
font-size: 14px;
color: #606266;
line-height: 1.6;
min-height: 60px;
}
.approval-text {
font-size: 14px;
color: #303133;
line-height: 1.8;
}
.no-content {
font-size: 14px;
color: #909399;
font-style: italic;
text-align: center;
display: block;
padding: 10px 0;
}
.approval-info {
font-size: 12px;
color: #909399;
display: flex;
flex-wrap: wrap;
gap: 15px;
padding: 0 5px;
}
.highlight {
color: #409EFF;
font-weight: bold;
}
.difficulty-level {
color: #409EFF;
font-weight: bold;
}
</style>