questionnaireForm.vue 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362
  1. <template>
  2. <view class="container">
  3. <image class="bg" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/images/bg_qestion.png" mode="widthFix"></image>
  4. <view class="fixed-top-box" :style="{background: bgColor }">
  5. <view class="status_bar" :style="{height: statusBarHeight}"></view>
  6. <view class="back-box" @click="goBack">
  7. <image src="@/static/image/back.png" mode=""></image>
  8. <text class="title">问卷调查</text>
  9. <text></text>
  10. </view>
  11. </view>
  12. <scroll-view class="content" scroll-y>
  13. <!-- 表单标题 -->
  14. <view class="form-header">
  15. <view class="form-title">{{ formTitle }}</view>
  16. <view class="form-tips">
  17. <view class="tip-item">请您根据患者真实情况选择并填写</view>
  18. <view class="tip-item">我们承诺对您及患者所提供的所有信息严格保密</view>
  19. </view>
  20. </view>
  21. <!-- 动态表单内容 -->
  22. <view class="form-section">
  23. <view class="form-item"
  24. v-for="(field, index) in formFields"
  25. :key="getFieldKey(field, index)">
  26. <view class="form-label" v-if="showFieldLabel(field)">
  27. <text class="required" v-if="field.__config__.required">*</text>
  28. <text>{{ getDisplayIndex(index) }}、{{ field.__config__.label }}</text>
  29. </view>
  30. <!-- 单行文本输入 -->
  31. <template v-if="field.__config__.tag === 'el-input' && field.type !== 'textarea'">
  32. <input
  33. class="form-input"
  34. :value="formData[field.__vModel__] || ''"
  35. @input="(e) => onInputChange(field, e)"
  36. :placeholder="field.placeholder || '请输入' + field.__config__.label"
  37. type="text"
  38. :disabled="field.disabled"
  39. placeholder-class="text-placeholder"
  40. />
  41. </template>
  42. <!-- 多行文本输入 -->
  43. <template v-else-if="field.__config__.tag === 'el-input' && field.type === 'textarea'">
  44. <textarea
  45. class="form-textarea"
  46. :value="formData[field.__vModel__] || ''"
  47. @input="(e) => onInputChange(field, e)"
  48. :placeholder="field.placeholder || '请输入' + field.__config__.label"
  49. :maxlength="field.maxlength"
  50. :disabled="field.disabled"
  51. :auto-height="field.autosize ? true : false"
  52. placeholder-class="text-placeholder"
  53. />
  54. </template>
  55. <!-- 计数器 -->
  56. <template v-else-if="field.__config__.tag === 'el-input-number'">
  57. <view class="input-number-wrapper">
  58. <view class="input-number-btn" @click="(e) => decreaseNumber(field, index)" :class="{ disabled: isNumberMin(field) }">-</view>
  59. <input
  60. class="form-input input-number-input"
  61. :value="formData[field.__vModel__] || ''"
  62. @input="(e) => onNumberInputChange(field, e)"
  63. type="number"
  64. :placeholder="field.placeholder || '请输入' + field.__config__.label"
  65. :disabled="field.disabled"
  66. placeholder-class="text-placeholder"
  67. />
  68. <view class="input-number-btn" @click="(e) => increaseNumber(field, index)" :class="{ disabled: isNumberMax(field) }">+</view>
  69. </view>
  70. </template>
  71. <!-- 下拉选择 -->
  72. <template v-else-if="field.__config__.tag === 'el-select'">
  73. <picker
  74. mode="selector"
  75. :range="getSelectOptions(field)"
  76. range-key="label"
  77. :value="getSelectIndex(field)"
  78. @change="(e) => onSelectChange(field, e)">
  79. <view class="form-input picker-input" :class="{ placeholder: !formData[field.__vModel__] }">
  80. {{ getSelectLabel(field) || field.placeholder || '请选择' + field.__config__.label }}
  81. <image class="w48 h48" src="@/static/image/icon_more.png" mode=""></image>
  82. </view>
  83. </picker>
  84. </template>
  85. <!-- 滑块 -->
  86. <template v-else-if="field.__config__.tag === 'el-slider'">
  87. <view class="slider-wrapper">
  88. <slider
  89. :value="formData[field.__vModel__] || field.__config__.defaultValue || 0"
  90. :min="field.min || 0"
  91. :max="field.max || 100"
  92. :step="field.step || 1"
  93. :show-value="true"
  94. activeColor="#388BFF"
  95. @change="(e) => onSliderChange(field, e)"
  96. />
  97. </view>
  98. </template>
  99. <!-- 多选框组 -->
  100. <template v-else-if="field.__config__.tag === 'el-checkbox-group'">
  101. <checkbox-group @change="(e) => onCheckboxChange(field, e)" class="checkbox-group">
  102. <label
  103. class="checkbox-item"
  104. v-for="(option, optIndex) in getSelectOptions(field)"
  105. :key="optIndex">
  106. <checkbox
  107. :value="option.value"
  108. :checked="isCheckboxChecked(field, option.value)"
  109. color="#388BFF"
  110. />
  111. <text>{{ option.label }}</text>
  112. </label>
  113. </checkbox-group>
  114. </template>
  115. <!-- 单选框组 -->
  116. <template v-else-if="field.__config__.tag === 'el-radio-group'">
  117. <radio-group @change="(e) => onRadioChange(field, e)" class="radio-group">
  118. <label
  119. class="radio-item"
  120. v-for="(option, optIndex) in getSelectOptions(field)"
  121. :key="optIndex">
  122. <radio
  123. :value="option.value"
  124. :checked="formData[field.__vModel__] == option.value"
  125. color="#388BFF"
  126. />
  127. <text>{{ option.label }}</text>
  128. </label>
  129. </radio-group>
  130. </template>
  131. <!-- 文件上传 -->
  132. <template v-else-if="field.__config__.tag === 'el-upload'">
  133. <view class="upload-section">
  134. <view class="upload-item" v-for="(file, fileIndex) in getUploadFiles(field)" :key="fileIndex">
  135. <image class="uploaded-image" :src="file" mode="aspectFill" v-if="isImageFile(file)"></image>
  136. <view class="uploaded-file" v-else>
  137. <text class="file-name">{{ getFileName(file) }}</text>
  138. </view>
  139. <view class="delete-btn" @click="(e) => removeUploadFile(field, fileIndex, index)">×</view>
  140. </view>
  141. <view class="upload-item upload-placeholder" @click="(e) => chooseUploadFile(field, index)">
  142. <image class="w48 h48" src="@/static/image/icon_camera1.png" mode=""></image>
  143. <text class="upload-text">{{ field.__config__.buttonText || '点击上传' }}</text>
  144. </view>
  145. </view>
  146. </template>
  147. <!-- 开关 -->
  148. <template v-else-if="field.__config__.tag === 'el-switch'">
  149. <switch
  150. :checked="formData[field.__vModel__] || false"
  151. :disabled="field.disabled"
  152. :color="field['active-color'] || '#388BFF'"
  153. @change="(e) => onSwitchChange(field, e)"
  154. />
  155. </template>
  156. <!-- 时间选择 -->
  157. <template v-else-if="field.__config__.tag === 'el-time-picker' && !field['is-range']">
  158. <picker
  159. mode="time"
  160. :value="formData[field.__vModel__] || ''"
  161. @change="(e) => onTimePickerChange(field, e)">
  162. <view class="form-input picker-input" :class="{ placeholder: !formData[field.__vModel__] }">
  163. {{ formData[field.__vModel__] || field.placeholder || '请选择' + field.__config__.label }}
  164. <!-- <image class="w48 h48" src="@/static/image/icon_my_more.png" mode=""></image> -->
  165. </view>
  166. </picker>
  167. </template>
  168. <!-- 时间范围 -->
  169. <template v-else-if="field.__config__.tag === 'el-time-picker' && field['is-range']">
  170. <view class="time-range-wrapper">
  171. <picker
  172. mode="time"
  173. :value="getTimeRangeStart(field)"
  174. @change="(e) => onTimeRangeStartChange(field, e)">
  175. <view class="form-input picker-input time-range-input" :class="{ placeholder: !getTimeRangeStart(field) }">
  176. {{ getTimeRangeStart(field) || field['start-placeholder'] || '开始时间' }}
  177. <!-- <image class="w48 h48" src="@/static/image/icon_my_more.png" mode=""></image> -->
  178. </view>
  179. </picker>
  180. <text class="time-range-separator">{{ field['range-separator'] || '至' }}</text>
  181. <picker
  182. mode="time"
  183. :value="getTimeRangeEnd(field)"
  184. @change="(e) => onTimeRangeEndChange(field, e)">
  185. <view class="form-input picker-input time-range-input" :class="{ placeholder: !getTimeRangeEnd(field) }">
  186. {{ getTimeRangeEnd(field) || field['end-placeholder'] || '结束时间' }}
  187. <!-- <image class="w48 h48" src="@/static/image/icon_my_more.png" mode=""></image> -->
  188. </view>
  189. </picker>
  190. </view>
  191. </template>
  192. <!-- 日期选择 -->
  193. <template v-else-if="field.__config__.tag === 'el-date-picker' && field.type !== 'daterange'">
  194. <picker
  195. mode="date"
  196. :value="formData[field.__vModel__] || ''"
  197. :start="field['start-date'] || ''"
  198. :end="field['end-date'] || ''"
  199. @change="(e) => onDatePickerChange(field, e)">
  200. <view class="form-input picker-input" :class="{ placeholder: !formData[field.__vModel__] }">
  201. {{ formData[field.__vModel__] || field.placeholder || '请选择' + field.__config__.label }}
  202. <!-- <image class="w48 h48" src="@/static/image/icon_my_more.png" mode=""></image> -->
  203. </view>
  204. </picker>
  205. </template>
  206. <!-- 日期范围 -->
  207. <template v-else-if="field.__config__.tag === 'el-date-picker' && field.type === 'daterange'">
  208. <view class="date-range-wrapper">
  209. <picker
  210. mode="date"
  211. :value="getDateRangeStart(field)"
  212. @change="(e) => onDateRangeStartChange(field, e)">
  213. <view class="form-input picker-input date-range-input" :class="{ placeholder: !getDateRangeStart(field) }">
  214. {{ getDateRangeStart(field) || field['start-placeholder'] || '开始日期' }}
  215. <!-- <image class="w48 h48" src="@/static/image/icon_my_more.png" mode=""></image> -->
  216. </view>
  217. </picker>
  218. <text class="date-range-separator">{{ field['range-separator'] || '至' }}</text>
  219. <picker
  220. mode="date"
  221. :value="getDateRangeEnd(field)"
  222. @change="(e) => onDateRangeEndChange(field, e)">
  223. <view class="form-input picker-input date-range-input" :class="{ placeholder: !getDateRangeEnd(field) }">
  224. {{ getDateRangeEnd(field) || field['end-placeholder'] || '结束日期' }}
  225. <!-- <image class="w48 h48" src="@/static/image/icon_my_more.png" mode=""></image> -->
  226. </view>
  227. </picker>
  228. </view>
  229. </template>
  230. <!-- 评分 -->
  231. <template v-else-if="field.__config__.tag === 'el-rate'">
  232. <view class="rate-wrapper">
  233. <view
  234. class="rate-star"
  235. v-for="(star, starIndex) in (field.max || 5)"
  236. :key="starIndex"
  237. @click="(e) => setRate(field, starIndex + 1, index)"
  238. :class="{ active: (formData[field.__vModel__] || 0) >= (starIndex + 1) }">
  239. </view>
  240. <text class="rate-text" v-if="field['show-score']">{{ formData[field.__vModel__] || 0 }}分</text>
  241. </view>
  242. </template>
  243. <!-- 文本描述 -->
  244. <template v-else-if="field.__config__.tag === 'span'">
  245. <view :style="field.style">
  246. {{ getSpanText(field) }}
  247. </view>
  248. </template>
  249. </view>
  250. </view>
  251. </scroll-view>
  252. <!-- 提交按钮 -->
  253. <view v-if="caseDetail.periodNum!==0" class="submit-btn" @click="handleSubmit">提交</view>
  254. </view>
  255. </template>
  256. <script>
  257. import { submitQuestionnaire, getQuestionnaireDetail} from '@/api/questionnaire'
  258. export default {
  259. data() {
  260. return {
  261. top: 0,
  262. statusBarHeight: uni.getSystemInfoSync().statusBarHeight + 'px',
  263. questionnaireId: '',
  264. finishAuditStatus:'',
  265. formTitle: '病例收集',
  266. formFields: [],
  267. formData: {},
  268. caseDetail: null ,// 保存详情数据,用于获取 company_id, table_name 等
  269. }
  270. },
  271. onLoad(options) {
  272. if (options.id) {
  273. this.questionnaireId = options.id
  274. }
  275. if (options.finishAuditStatus) {
  276. this.finishAuditStatus = options.finishAuditStatus
  277. }
  278. if (options.title) {
  279. this.formTitle = decodeURIComponent(options.title)
  280. }
  281. this.loadDetail()
  282. },
  283. onPageScroll(e) {
  284. this.top = e.scrollTop
  285. },
  286. computed: {
  287. bgColor() {
  288. const opacity = Math.min(this.top / 30, 1)
  289. return `rgba(255, 255, 255, ${opacity})`
  290. }
  291. },
  292. methods: {
  293. showFieldLabel(field) {
  294. const config = (field && field.__config__) || {}
  295. if (config.tag === 'span') {
  296. return false;
  297. }
  298. return true
  299. },
  300. getDisplayIndex(index) {
  301. if (!Array.isArray(this.formFields) || index < 0) {
  302. return 0
  303. }
  304. let displayIndex = 0
  305. for (let i = 0; i <= index; i++) {
  306. const field = this.formFields[i]
  307. if (this.showFieldLabel(field)) {
  308. displayIndex++
  309. }
  310. }
  311. return displayIndex
  312. },
  313. getSpanText(field) {
  314. if (!field) return ''
  315. if (field.__slot__ && typeof field.__slot__.default === 'string') {
  316. return field.__slot__.default
  317. }
  318. return (field.__config__ && field.__config__.label) || ''
  319. },
  320. getSpanStyle(field) {
  321. const style = (field && field.style) || {}
  322. return {
  323. fontSize: style.fontSize || '28rpx',
  324. color: style.color || '#333333',
  325. fontWeight: style.fontWeight || 'normal'
  326. }
  327. },
  328. getFieldKey(field, index) {
  329. // 在非 H5 平台,:key 不支持表达式,需要使用方法来生成 key
  330. return field && field.__config__ && field.__config__.formId ? field.__config__.formId : `field_${index}`
  331. },
  332. goBack() {
  333. uni.navigateBack()
  334. },
  335. initFormData() {
  336. // 初始化表单数据
  337. this.formFields.forEach(field => {
  338. const vModel = field.__vModel__
  339. if (!vModel) {
  340. console.warn('字段缺少 __vModel__ 属性', field)
  341. return
  342. }
  343. // 如果字段已存在值,跳过初始化(保留已有数据)
  344. if (this.formData[vModel] !== undefined && this.formData[vModel] !== null) {
  345. return
  346. }
  347. const tag = field.__config__.tag
  348. const defaultValue = field.__config__.defaultValue
  349. // 根据字段类型设置默认值
  350. switch (tag) {
  351. // 单行文本输入
  352. case 'el-input':
  353. if (field.type === 'textarea') {
  354. // 多行文本
  355. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  356. } else {
  357. // 单行文本
  358. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  359. }
  360. break
  361. // 数字输入(计数器)
  362. case 'el-input-number':
  363. if (defaultValue !== undefined) {
  364. this.$set(this.formData, vModel, Number(defaultValue))
  365. } else {
  366. // 根据 min 值设置默认值,如果没有 min 则默认为 0
  367. const minValue = field.min !== undefined ? Number(field.min) : null
  368. this.$set(this.formData, vModel, minValue)
  369. }
  370. break
  371. // 下拉选择
  372. case 'el-select':
  373. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  374. break
  375. // 滑块
  376. case 'el-slider':
  377. if (defaultValue !== undefined) {
  378. this.$set(this.formData, vModel, Number(defaultValue))
  379. } else {
  380. // 默认值为 min 值,如果没有 min 则默认为 0
  381. const minValue = field.min !== undefined ? Number(field.min) : 0
  382. this.$set(this.formData, vModel, minValue)
  383. }
  384. break
  385. // 多选框组
  386. case 'el-checkbox-group':
  387. if (defaultValue !== undefined && Array.isArray(defaultValue)) {
  388. this.$set(this.formData, vModel, [...defaultValue])
  389. } else {
  390. this.$set(this.formData, vModel, [])
  391. }
  392. break
  393. // 单选框组
  394. case 'el-radio-group':
  395. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  396. break
  397. // 文件上传
  398. case 'el-upload':
  399. if (defaultValue !== undefined && Array.isArray(defaultValue)) {
  400. this.$set(this.formData, vModel, [...defaultValue])
  401. } else if (defaultValue !== undefined && typeof defaultValue === 'string') {
  402. // 如果是字符串(逗号分隔),转换为数组
  403. this.$set(this.formData, vModel, defaultValue.split(',').filter(item => item.trim()))
  404. } else {
  405. this.$set(this.formData, vModel, [])
  406. }
  407. break
  408. // 开关
  409. case 'el-switch':
  410. this.$set(this.formData, vModel, defaultValue !== undefined ? Boolean(defaultValue) : false)
  411. break
  412. // 时间选择
  413. case 'el-time-picker':
  414. if (field['is-range']) {
  415. // 时间范围
  416. if (defaultValue !== undefined && Array.isArray(defaultValue)) {
  417. this.$set(this.formData, vModel, [...defaultValue])
  418. } else {
  419. this.$set(this.formData, vModel, ['', ''])
  420. }
  421. } else {
  422. // 单个时间
  423. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  424. }
  425. break
  426. // 日期选择
  427. case 'el-date-picker':
  428. if (field.type === 'daterange') {
  429. // 日期范围
  430. if (defaultValue !== undefined && Array.isArray(defaultValue)) {
  431. this.$set(this.formData, vModel, [...defaultValue])
  432. } else {
  433. this.$set(this.formData, vModel, ['', ''])
  434. }
  435. } else {
  436. // 单个日期
  437. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  438. }
  439. break
  440. // 评分
  441. case 'el-rate':
  442. if (defaultValue !== undefined) {
  443. this.$set(this.formData, vModel, Number(defaultValue))
  444. } else {
  445. this.$set(this.formData, vModel, 0)
  446. }
  447. break
  448. // 默认情况:字符串类型
  449. default:
  450. this.$set(this.formData, vModel, defaultValue !== undefined ? defaultValue : '')
  451. break
  452. }
  453. })
  454. },
  455. async loadDetail() {
  456. try {
  457. uni.showLoading({ title: '加载中...' })
  458. const res = await getQuestionnaireDetail({ id: this.questionnaireId ,finishAuditStatus:this.finishAuditStatus})
  459. uni.hideLoading()
  460. if (res.code === 200 && res.data) {
  461. // 保存详情数据
  462. this.caseDetail = res.data
  463. // 设置表单标题
  464. this.formTitle = res.data.questionnaireName || this.formTitle
  465. let question = res.data.surveyQuestionnaireVersion
  466. let formConfigData = question.formJson || ''
  467. // 如果 formConfigData 是字符串,需要解析 JSON
  468. if (typeof formConfigData === 'string') {
  469. try {
  470. formConfigData = JSON.parse(formConfigData)
  471. } catch (e) {
  472. console.error('解析表单配置 JSON 失败', e)
  473. formConfigData = null
  474. }
  475. }
  476. // 设置表单字段
  477. if (formConfigData && formConfigData.fields && Array.isArray(formConfigData.fields)) {
  478. this.formFields = formConfigData.fields
  479. this.initFormData()
  480. } else {
  481. // 接口返回的数据格式不正确,使用默认配置
  482. console.warn('表单配置格式不正确,使用默认配置', formConfigData)
  483. uni.showToast({
  484. icon: 'none',
  485. title: '表单配置加载失败'
  486. })
  487. }
  488. // 如果有已保存的数据,填充表单
  489. if (res.data.formData) {
  490. Object.assign(this.formData, res.data.formData)
  491. }
  492. } else {
  493. uni.showToast({
  494. icon: 'none',
  495. title: res.msg
  496. })
  497. }
  498. } catch (e) {
  499. uni.hideLoading()
  500. console.error('加载详情失败,使用默认配置', e)
  501. uni.showToast({
  502. icon: 'none',
  503. title: '表单配置加载失败'
  504. })
  505. }
  506. },
  507. getSelectOptions(field) {
  508. return field.__slot__?.options || []
  509. },
  510. getSelectIndex(field) {
  511. const value = this.formData[field.__vModel__]
  512. const options = this.getSelectOptions(field)
  513. const index = options.findIndex(opt => opt.value == value)
  514. return index >= 0 ? index : 0
  515. },
  516. getSelectLabel(field) {
  517. const value = this.formData[field.__vModel__]
  518. const options = this.getSelectOptions(field)
  519. const option = options.find(opt => opt.value == value)
  520. return option ? option.label : ''
  521. },
  522. onInputChange(field, e) {
  523. const value = e.detail.value || e.target.value || ''
  524. this.$set(this.formData, field.__vModel__, value)
  525. },
  526. onNumberInputChange(field, e) {
  527. const value = e.detail.value || e.target.value || ''
  528. const numValue = value === '' ? null : Number(value)
  529. // 如果输入的不是有效数字,保持原值
  530. if (value !== '' && isNaN(numValue)) {
  531. return
  532. }
  533. this.$set(this.formData, field.__vModel__, numValue)
  534. },
  535. onSelectChange(field, e) {
  536. const options = this.getSelectOptions(field)
  537. const index = e.detail.value
  538. if (options[index]) {
  539. this.$set(this.formData, field.__vModel__, options[index].value)
  540. }
  541. },
  542. onSliderChange(field, e) {
  543. this.$set(this.formData, field.__vModel__, e.detail.value)
  544. },
  545. onCheckboxChange(field, e) {
  546. this.$set(this.formData, field.__vModel__, e.detail.value)
  547. },
  548. onRadioChange(field, e) {
  549. this.$set(this.formData, field.__vModel__, e.detail.value)
  550. },
  551. isCheckboxChecked(field, value) {
  552. const checkedValues = this.formData[field.__vModel__] || []
  553. return checkedValues.includes(value)
  554. },
  555. getUploadFiles(field) {
  556. if (!field || !field.__vModel__) return []
  557. const files = this.formData[field.__vModel__] || []
  558. return Array.isArray(files) ? files : []
  559. },
  560. isImageFile(file) {
  561. if (typeof file === 'string') {
  562. return /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file) || file.startsWith('data:image')
  563. }
  564. return false
  565. },
  566. getFileName(file) {
  567. if (typeof file === 'string') {
  568. const parts = file.split('/')
  569. return parts[parts.length - 1]
  570. }
  571. return '文件'
  572. },
  573. chooseUploadFile(field, formIndex) {
  574. // 如果 field 未定义,通过索引获取
  575. if (!field && formIndex !== undefined) {
  576. field = this.formFields[formIndex]
  577. }
  578. if (!field || !field.__vModel__) {
  579. console.error('上传字段未找到')
  580. return
  581. }
  582. const maxCount = field.multiple ? 9 : 3
  583. const currentFiles = this.getUploadFiles(field)
  584. const remaining = maxCount - currentFiles.length
  585. if (remaining <= 0) {
  586. uni.showToast({
  587. icon: 'none',
  588. title: '已达到最大上传数量'
  589. })
  590. return
  591. }
  592. const vm = this
  593. uni.chooseImage({
  594. //count: remaining,
  595. sizeType: ['compressed'],
  596. sourceType: ['album', 'camera'],
  597. success: (res) => {
  598. if (!res.tempFilePaths || res.tempFilePaths.length === 0) {
  599. uni.showToast({
  600. icon: 'none',
  601. title: '未选择文件'
  602. })
  603. return
  604. }
  605. uni.showLoading({ title: '上传中...' })
  606. const files = vm.formData[field.__vModel__] || []
  607. const requestPath = uni.getStorageSync('requestPath')
  608. const totalCount = res.tempFilePaths.length
  609. let completedCount = 0
  610. const uploadedUrls = []
  611. // 循环上传所有选择的图片
  612. res.tempFilePaths.forEach((filePath, index) => {
  613. uni.uploadFile({
  614. url: `${requestPath}/app/common/uploadOSS`,
  615. filePath: filePath,
  616. name: 'file',
  617. formData: {},
  618. success: (uploadRes) => {
  619. completedCount++
  620. try {
  621. const result = typeof uploadRes.data === 'string' ? JSON.parse(uploadRes.data) : uploadRes.data
  622. if (result.code == 200 && result.data && result.data.url) {
  623. uploadedUrls.push(result.data.url)
  624. } else if (result.code == 200 && result.url) {
  625. // 兼容不同的返回格式
  626. uploadedUrls.push(result.url)
  627. }
  628. } catch (e) {
  629. console.error('解析上传结果失败', e)
  630. }
  631. // 所有文件上传完成后,更新表单数据
  632. if (completedCount === totalCount) {
  633. uni.hideLoading()
  634. if (uploadedUrls.length > 0) {
  635. vm.$set(vm.formData, field.__vModel__, [...files, ...uploadedUrls])
  636. uni.showToast({
  637. icon: 'success',
  638. title: uploadedUrls.length === totalCount ? '上传成功' : `成功上传${uploadedUrls.length}/${totalCount}个文件`
  639. })
  640. } else {
  641. uni.showToast({
  642. icon: 'none',
  643. title: '上传失败'
  644. })
  645. }
  646. }
  647. },
  648. fail: (err) => {
  649. completedCount++
  650. console.error('上传失败', err)
  651. // 所有文件上传完成后,更新表单数据
  652. if (completedCount === totalCount) {
  653. uni.hideLoading()
  654. if (uploadedUrls.length > 0) {
  655. vm.$set(vm.formData, field.__vModel__, [...files, ...uploadedUrls])
  656. uni.showToast({
  657. icon: 'none',
  658. title: `成功上传${uploadedUrls.length}/${totalCount}个文件`
  659. })
  660. } else {
  661. uni.showToast({
  662. icon: 'none',
  663. title: '上传失败'
  664. })
  665. }
  666. }
  667. }
  668. })
  669. })
  670. },
  671. fail: (err) => {
  672. console.error('选择文件失败', err)
  673. uni.showToast({
  674. icon: 'none',
  675. title: '选择文件失败'
  676. })
  677. }
  678. })
  679. },
  680. removeUploadFile(field, fileIndex, formIndex) {
  681. // 如果 field 未定义,通过索引获取
  682. if (!field && formIndex !== undefined) {
  683. field = this.formFields[formIndex]
  684. }
  685. if (!field || !field.__vModel__) {
  686. console.error('上传字段未找到')
  687. return
  688. }
  689. const files = this.formData[field.__vModel__] || []
  690. if (fileIndex >= 0 && fileIndex < files.length) {
  691. files.splice(fileIndex, 1)
  692. this.$set(this.formData, field.__vModel__, files)
  693. }
  694. },
  695. // 计数器相关方法
  696. decreaseNumber(field, index) {
  697. // 如果 field 未定义,通过索引获取
  698. if (!field && index !== undefined) {
  699. field = this.formFields[index]
  700. }
  701. if (!field || !field.__vModel__) {
  702. console.error('计数器字段未找到')
  703. return
  704. }
  705. if (this.isNumberMin(field)) return
  706. const current = Number(this.formData[field.__vModel__] || 0)
  707. const step = field.step || 1
  708. const min = field.min !== undefined ? field.min : 0
  709. const newValue = Math.max(min, current - step)
  710. this.$set(this.formData, field.__vModel__, newValue)
  711. },
  712. increaseNumber(field, index) {
  713. // 如果 field 未定义,通过索引获取
  714. if (!field && index !== undefined) {
  715. field = this.formFields[index]
  716. }
  717. if (!field || !field.__vModel__) {
  718. console.error('计数器字段未找到')
  719. return
  720. }
  721. if (this.isNumberMax(field)) return
  722. const current = Number(this.formData[field.__vModel__] || 0)
  723. const step = field.step || 1
  724. const max = field.max !== undefined ? field.max : Infinity
  725. const newValue = Math.min(max, current + step)
  726. this.$set(this.formData, field.__vModel__, newValue)
  727. },
  728. isNumberMin(field) {
  729. if (!field || !field.__vModel__) return false
  730. const current = Number(this.formData[field.__vModel__] || 0)
  731. const min = field.min !== undefined ? field.min : 0
  732. return current <= min
  733. },
  734. isNumberMax(field) {
  735. if (!field || !field.__vModel__) return false
  736. const current = Number(this.formData[field.__vModel__] || 0)
  737. const max = field.max !== undefined ? field.max : Infinity
  738. return current >= max
  739. },
  740. // 开关相关方法
  741. onSwitchChange(field, e) {
  742. this.$set(this.formData, field.__vModel__, e.detail.value)
  743. },
  744. // 时间选择相关方法
  745. onTimePickerChange(field, e) {
  746. this.$set(this.formData, field.__vModel__, e.detail.value)
  747. },
  748. getTimeRangeStart(field) {
  749. const value = this.formData[field.__vModel__]
  750. if (Array.isArray(value) && value.length > 0) {
  751. return value[0]
  752. }
  753. return ''
  754. },
  755. getTimeRangeEnd(field) {
  756. const value = this.formData[field.__vModel__]
  757. if (Array.isArray(value) && value.length > 1) {
  758. return value[1]
  759. }
  760. return ''
  761. },
  762. onTimeRangeStartChange(field, e) {
  763. const value = this.formData[field.__vModel__] || []
  764. value[0] = e.detail.value
  765. this.$set(this.formData, field.__vModel__, [...value])
  766. },
  767. onTimeRangeEndChange(field, e) {
  768. const value = this.formData[field.__vModel__] || []
  769. value[1] = e.detail.value
  770. this.$set(this.formData, field.__vModel__, [...value])
  771. },
  772. // 日期选择相关方法
  773. onDatePickerChange(field, e) {
  774. this.$set(this.formData, field.__vModel__, e.detail.value)
  775. },
  776. getDateRangeStart(field) {
  777. const value = this.formData[field.__vModel__]
  778. if (Array.isArray(value) && value.length > 0) {
  779. return value[0]
  780. }
  781. return ''
  782. },
  783. getDateRangeEnd(field) {
  784. const value = this.formData[field.__vModel__]
  785. if (Array.isArray(value) && value.length > 1) {
  786. return value[1]
  787. }
  788. return ''
  789. },
  790. onDateRangeStartChange(field, e) {
  791. const value = this.formData[field.__vModel__] || []
  792. value[0] = e.detail.value
  793. this.$set(this.formData, field.__vModel__, [...value])
  794. },
  795. onDateRangeEndChange(field, e) {
  796. const value = this.formData[field.__vModel__] || []
  797. value[1] = e.detail.value
  798. this.$set(this.formData, field.__vModel__, [...value])
  799. },
  800. // 评分相关方法
  801. setRate(field, value, formIndex) {
  802. // 如果 field 未定义,通过索引获取
  803. if (!field && formIndex !== undefined) {
  804. field = this.formFields[formIndex]
  805. }
  806. if (!field || !field.__vModel__) {
  807. console.error('评分字段未找到')
  808. return
  809. }
  810. if (field.disabled) return
  811. this.$set(this.formData, field.__vModel__, value)
  812. },
  813. validateForm() {
  814. for (let field of this.formFields) {
  815. const value = this.formData[field.__vModel__]
  816. const fieldLabel = field.__config__.label || '该字段'
  817. const tag = field.__config__.tag
  818. const isRequired = field.__config__.required
  819. // 必填校验
  820. if (isRequired) {
  821. // 数组类型(多选框、文件上传、范围选择)
  822. if (tag === 'el-checkbox-group' || tag === 'el-upload') {
  823. const arr = tag === 'el-upload' ? this.getUploadFiles(field) : value
  824. if (!arr || !Array.isArray(arr) || arr.length === 0) {
  825. return { valid: false, message: tag === 'el-upload' ? `请上传${fieldLabel}` : `请选择${fieldLabel}` }
  826. }
  827. }
  828. // 范围选择(时间范围、日期范围)
  829. else if ((tag === 'el-time-picker' && field['is-range']) || (tag === 'el-date-picker' && field.type === 'daterange')) {
  830. const range = value || []
  831. if (!Array.isArray(range) || range.length < 2 || !range[0] || !range[1]) {
  832. return { valid: false, message: `请选择${fieldLabel}` }
  833. }
  834. }
  835. // 选择类型(下拉、单选框、时间、日期)
  836. else if (tag === 'el-select' || tag === 'el-radio-group' ||
  837. (tag === 'el-time-picker' && !field['is-range']) ||
  838. (tag === 'el-date-picker' && field.type !== 'daterange')) {
  839. if (!value && value !== 0 && value !== '0' && value !== false) {
  840. return { valid: false, message: `请选择${fieldLabel}` }
  841. }
  842. }
  843. // 文本输入
  844. else if (tag === 'el-input') {
  845. if (!value || (typeof value === 'string' && value.trim() === '')) {
  846. return { valid: false, message: `请输入${fieldLabel}` }
  847. }
  848. }
  849. // 数字类型(数字输入、滑块)
  850. else if (tag === 'el-input-number' || tag === 'el-slider') {
  851. if (value === null || value === undefined || value === '') {
  852. return { valid: false, message: tag === 'el-slider' ? `请设置${fieldLabel}` : `请输入${fieldLabel}` }
  853. }
  854. }
  855. // 评分
  856. else if (tag === 'el-rate') {
  857. if (!value || value === 0) {
  858. return { valid: false, message: `请为${fieldLabel}评分` }
  859. }
  860. }
  861. // 其他类型
  862. else if (!value && value !== 0 && value !== false) {
  863. return { valid: false, message: `请填写${fieldLabel}` }
  864. }
  865. }
  866. // 格式校验(必填和非必填都校验)
  867. if (value !== null && value !== undefined && value !== '') {
  868. // 文本长度校验
  869. if (tag === 'el-input' && typeof value === 'string') {
  870. if (field.maxlength && value.length > field.maxlength) {
  871. return { valid: false, message: `${fieldLabel}不能超过${field.maxlength}个字符` }
  872. }
  873. if (field.minlength && value.length < field.minlength) {
  874. return { valid: false, message: `${fieldLabel}不能少于${field.minlength}个字符` }
  875. }
  876. }
  877. // 数字范围校验
  878. else if ((tag === 'el-input-number' || tag === 'el-slider')) {
  879. const numValue = Number(value)
  880. if (!isNaN(numValue)) {
  881. if (field.min !== undefined && numValue < field.min) {
  882. return { valid: false, message: `${fieldLabel}不能小于${field.min}` }
  883. }
  884. if (field.max !== undefined && numValue > field.max) {
  885. return { valid: false, message: `${fieldLabel}不能大于${field.max}` }
  886. }
  887. }
  888. }
  889. }
  890. }
  891. return { valid: true }
  892. },
  893. async handleSubmit() {
  894. // 表单验证
  895. const validation = this.validateForm()
  896. if (!validation.valid) {
  897. uni.showToast({
  898. icon: 'none',
  899. title: validation.message
  900. })
  901. return
  902. }
  903. try {
  904. uni.showLoading({ title: '提交中...' })
  905. // 处理表单数据:将数组类型字段转换为逗号分隔的字符串
  906. const submitData = { ...this.formData }
  907. this.formFields.forEach(field => {
  908. const vModel = field.__vModel__
  909. const value = submitData[vModel]
  910. // 文件上传字段:数组转逗号分隔字符串
  911. if (field.__config__.tag === 'el-upload') {
  912. if (Array.isArray(value) && value.length > 0) {
  913. submitData[vModel] = value.join(',')
  914. } else if (value) {
  915. // 如果已经是字符串,保持不变
  916. submitData[vModel] = value
  917. } else {
  918. // 如果没有文件,设置为空字符串
  919. submitData[vModel] = ''
  920. }
  921. }
  922. // 多选框组:数组转逗号分隔字符串
  923. else if (field.__config__.tag === 'el-checkbox-group') {
  924. if (Array.isArray(value) && value.length > 0) {
  925. submitData[vModel] = value.join(',')
  926. } else if (value) {
  927. // 如果已经是字符串,保持不变
  928. submitData[vModel] = value
  929. } else {
  930. // 如果没有选择,设置为空字符串
  931. submitData[vModel] = ''
  932. }
  933. }
  934. })
  935. // 获取用户信息
  936. const userInfo = JSON.parse(uni.getStorageSync('userInfo') || '{}')
  937. const userName = userInfo.doctorName|| ''
  938. // 获取详情数据中的信息
  939. const companyId = this.caseDetail?.companyId
  940. const taskId = this.caseDetail?.taskId
  941. const periodNum = this.caseDetail?.periodNum
  942. const tableName = this.caseDetail?.surveyQuestionnaireVersion.tableName || ''
  943. const version = this.caseDetail?.surveyQuestionnaireVersion.versionNo || 0
  944. // 添加公用字段
  945. submitData.company_id = Number(companyId) || 0
  946. submitData.table_name = tableName
  947. submitData.user_id = userInfo.id || 0
  948. submitData.user_name = userName
  949. submitData.version_id = Number(version) || 0
  950. submitData.periodNum = periodNum
  951. submitData.taskId = taskId
  952. const res = await submitQuestionnaire(submitData)
  953. uni.hideLoading()
  954. if (res.code === 200) {
  955. uni.showToast({
  956. icon: 'success',
  957. title: '提交成功'
  958. })
  959. setTimeout(() => {
  960. uni.navigateBack()
  961. }, 1500)
  962. } else {
  963. uni.showToast({
  964. icon: 'none',
  965. title: res.msg || '提交失败'
  966. })
  967. }
  968. } catch (e) {
  969. uni.hideLoading()
  970. uni.showToast({
  971. icon: 'none',
  972. title: '提交失败'
  973. })
  974. }
  975. }
  976. }
  977. }
  978. </script>
  979. <style lang="stylus">
  980. .text-placeholder {
  981. color: #C8C9CC !important;
  982. }
  983. checkbox .wx-checkbox-input {
  984. border-radius: 0 !important;
  985. }
  986. checkbox .wx-checkbox-input.wx-checkbox-input-checked {
  987. border-radius: 0 !important;
  988. }
  989. </style>
  990. <style lang="scss" scoped>
  991. .container {
  992. min-height: 100vh;
  993. display: flex;
  994. flex-direction: column;
  995. .bg {
  996. width: 100%;
  997. position: absolute;
  998. top: 0;
  999. left: 0;
  1000. }
  1001. }
  1002. .status-bar {
  1003. width: 100%;
  1004. background: transparent;
  1005. }
  1006. .fixed-top-box {
  1007. width: 100%;
  1008. position: fixed;
  1009. top: 0;
  1010. left: 0;
  1011. z-index: 1000;
  1012. .back-box {
  1013. height: 88upx;
  1014. padding: 0 24upx;
  1015. display: flex;
  1016. align-items: center;
  1017. justify-content: space-between;
  1018. image {
  1019. width: 40upx;
  1020. height: 40upx;
  1021. }
  1022. .title {
  1023. font-family: PingFang SC, PingFang SC;
  1024. font-weight: 600;
  1025. font-size: 36rpx;
  1026. color: #333333;
  1027. }
  1028. }
  1029. }
  1030. .content {
  1031. margin-top: 168rpx;
  1032. flex: 1;
  1033. padding: 24rpx;
  1034. box-sizing: border-box;
  1035. }
  1036. .form-header {
  1037. margin-bottom: 32rpx;
  1038. .form-title {
  1039. font-family: PingFang SC, PingFang SC;
  1040. font-weight: 600;
  1041. font-size: 40rpx;
  1042. color: #333333;
  1043. margin-bottom: 32rpx;
  1044. text-align: center;
  1045. }
  1046. .form-tips {
  1047. .tip-item {
  1048. font-family: PingFang SC, PingFang SC;
  1049. font-weight: 400;
  1050. font-size: 28rpx;
  1051. color: #666666;
  1052. line-height: 44rpx;
  1053. text-align: center;
  1054. }
  1055. }
  1056. }
  1057. .form-section {
  1058. padding-bottom: 160rpx;
  1059. }
  1060. .form-item {
  1061. background: #fff;
  1062. padding: 24rpx 32rpx;
  1063. margin-bottom: 20rpx;
  1064. border-radius: 16rpx;
  1065. &:last-child {
  1066. margin-bottom: 0;
  1067. }
  1068. .form-label {
  1069. display: flex;
  1070. align-items: center;
  1071. font-size: 28rpx;
  1072. color: #333;
  1073. margin-bottom: 16rpx;
  1074. .required {
  1075. color: #FF5030;
  1076. margin-right: 4rpx;
  1077. }
  1078. }
  1079. .form-input {
  1080. width: 100%;
  1081. height: 72rpx;
  1082. padding: 0 24rpx;
  1083. border-radius: 12rpx;
  1084. border: 2rpx solid #F5F5F5;
  1085. font-size: 28rpx;
  1086. color: #333;
  1087. box-sizing: border-box;
  1088. &.placeholder {
  1089. color: #C8C9CC;
  1090. }
  1091. &.picker-input {
  1092. display: flex;
  1093. align-items: center;
  1094. justify-content: space-between;
  1095. }
  1096. }
  1097. .slider-wrapper {
  1098. padding: 20rpx 0;
  1099. }
  1100. .radio-group,
  1101. .checkbox-group {
  1102. display: flex;
  1103. flex-direction: column;
  1104. gap: 48rpx;
  1105. .radio-item,
  1106. .checkbox-item {
  1107. display: flex;
  1108. align-items: center;
  1109. gap: 16rpx;
  1110. font-size: 28rpx;
  1111. color: #333;
  1112. }
  1113. }
  1114. .form-textarea {
  1115. width: 100%;
  1116. min-height: 200rpx;
  1117. padding: 24rpx;
  1118. border-radius: 12rpx;
  1119. border: 2rpx solid #F5F5F5;
  1120. font-size: 28rpx;
  1121. color: #333;
  1122. box-sizing: border-box;
  1123. line-height: 1.6;
  1124. }
  1125. .form-span-text {
  1126. display: block;
  1127. width: 100%;
  1128. line-height: 1.6;
  1129. word-break: break-word;
  1130. }
  1131. .input-number-wrapper {
  1132. display: flex;
  1133. align-items: center;
  1134. gap: 16rpx;
  1135. .input-number-btn {
  1136. width: 60rpx;
  1137. height: 60rpx;
  1138. display: flex;
  1139. align-items: center;
  1140. justify-content: center;
  1141. border: 2rpx solid #F5F5F5;
  1142. border-radius: 8rpx;
  1143. font-size: 32rpx;
  1144. color: #333;
  1145. background: #fff;
  1146. &.disabled {
  1147. opacity: 0.5;
  1148. color: #C8C9CC;
  1149. }
  1150. }
  1151. .input-number-input {
  1152. flex: 1;
  1153. }
  1154. }
  1155. .time-range-wrapper,
  1156. .date-range-wrapper {
  1157. display: flex;
  1158. align-items: center;
  1159. gap: 16rpx;
  1160. .time-range-input,
  1161. .date-range-input {
  1162. flex: 1;
  1163. }
  1164. .time-range-separator,
  1165. .date-range-separator {
  1166. font-size: 28rpx;
  1167. color: #666;
  1168. }
  1169. }
  1170. .rate-wrapper {
  1171. display: flex;
  1172. align-items: center;
  1173. gap: 16rpx;
  1174. .rate-star {
  1175. font-size: 48rpx;
  1176. color: #E0E0E0;
  1177. line-height: 1;
  1178. cursor: pointer;
  1179. &.active {
  1180. color: #FFD700;
  1181. }
  1182. }
  1183. .rate-text {
  1184. font-size: 28rpx;
  1185. color: #666;
  1186. }
  1187. }
  1188. .upload-section {
  1189. display: flex;
  1190. gap: 16rpx;
  1191. flex-wrap: wrap;
  1192. padding: 10rpx;
  1193. width: 100%;
  1194. .upload-item {
  1195. flex: 0 0 calc((100% - 32rpx) / 3);
  1196. width: calc((100% - 32rpx) / 3);
  1197. // height: calc((100% - 32rpx) / 3);
  1198. min-height: 200rpx;
  1199. border-radius: 8rpx;
  1200. overflow: hidden;
  1201. position: relative;
  1202. box-sizing: border-box;
  1203. .uploaded-image {
  1204. width: 100%;
  1205. height: 100%;
  1206. }
  1207. .uploaded-file {
  1208. width: 100%;
  1209. height: 100%;
  1210. background: #f5f5f5;
  1211. display: flex;
  1212. align-items: center;
  1213. justify-content: center;
  1214. padding: 16rpx;
  1215. box-sizing: border-box;
  1216. .file-name {
  1217. font-size: 24rpx;
  1218. color: #666;
  1219. text-align: center;
  1220. word-break: break-all;
  1221. }
  1222. }
  1223. .delete-btn {
  1224. position: absolute;
  1225. top: 0;
  1226. right:0;
  1227. width: 40rpx;
  1228. height: 40rpx;
  1229. background: rgba(0, 0, 0, 0.5);
  1230. border-radius: 50%;
  1231. display: flex;
  1232. align-items: center;
  1233. justify-content: center;
  1234. font-size: 32rpx;
  1235. color: #fff;
  1236. line-height: 1;
  1237. }
  1238. &.upload-placeholder {
  1239. background: #f5f5f5;
  1240. display: flex;
  1241. flex-direction: column;
  1242. align-items: center;
  1243. justify-content: center;
  1244. gap: 8rpx;
  1245. .upload-text {
  1246. font-size: 24rpx;
  1247. color: #999;
  1248. }
  1249. }
  1250. }
  1251. }
  1252. }
  1253. .submit-btn {
  1254. position: fixed;
  1255. bottom: 0;
  1256. left: 0;
  1257. right: 0;
  1258. height: 88rpx;
  1259. background: #388BFF;
  1260. border-radius: 200rpx;
  1261. display: flex;
  1262. align-items: center;
  1263. justify-content: center;
  1264. font-size: 32rpx;
  1265. color: #fff;
  1266. font-weight: 500;
  1267. z-index: 100;
  1268. margin: 40rpx 32rpx;
  1269. }
  1270. </style>