questionnaireForm.vue 39 KB

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