dynamic-form.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <view class="dynamic-form-page">
  3. <view class="debug-info">
  4. <text>调试信息:{{ JSON.stringify(formData, null, 2) }}</text>
  5. </view>
  6. <!-- 加载状态 -->
  7. <view v-if="loading" class="loading">
  8. <text>正在加载数据...</text>
  9. </view>
  10. <!-- 动态表单 -->
  11. <Form v-else :rules="fieldConfigs" v-model="formData" ref="formRef">
  12. <!-- 动态渲染区域 -->
  13. <template v-for="area in areaList" :key="area.id">
  14. <table-title>{{ area.name }}</table-title>
  15. <up-table>
  16. <!-- 动态渲染字段 -->
  17. <up-tr v-for="field in area.fields" :key="field.key">
  18. <up-th>{{ field.label }}</up-th>
  19. <Td :field="`${area.id}.${field.key}`">
  20. <!-- 根据字段类型渲染不同组件 -->
  21. <SsInput
  22. v-if="field.type === 'input' || !field.type"
  23. v-model="formData[area.id][field.key]"
  24. :placeholder="`请输入${field.label}`"
  25. />
  26. <!-- 这里可以扩展其他组件类型 -->
  27. <!--
  28. <ss-select
  29. v-else-if="field.type === 'select'"
  30. v-model="formData[area.id][field.key]"
  31. :options="field.options"
  32. :placeholder="`请选择${field.label}`"
  33. />
  34. -->
  35. </Td>
  36. </up-tr>
  37. </up-table>
  38. </template>
  39. <!-- 提交按钮 -->
  40. <view class="submit-section">
  41. <up-button @click="handleSubmit" type="primary">提交表单</up-button>
  42. <up-button @click="handleReset" style="margin-left: 20rpx;">重置</up-button>
  43. </view>
  44. </Form>
  45. </view>
  46. </template>
  47. <script setup>
  48. import { ref, onMounted } from 'vue'
  49. import Form from '@/components/Form/index.vue'
  50. import Td from '@/components/Td/index.vue'
  51. import SsInput from '@/components/SsInput/index.vue'
  52. import TableTitle from '@/components/SsTableTitle/index.vue'
  53. // 响应式数据
  54. const formData = ref({})
  55. const fieldConfigs = ref({})
  56. const areaList = ref([])
  57. const loading = ref(true)
  58. const formRef = ref(null)
  59. // 模拟API调用
  60. const fetchAreaData = async () => {
  61. // 模拟网络延迟
  62. await new Promise(resolve => setTimeout(resolve, 1500))
  63. return {
  64. areas: [
  65. {
  66. id: 'campus_activity',
  67. name: '校园活动区',
  68. fields: [
  69. {
  70. key: 'description',
  71. label: '情况描述',
  72. type: 'input',
  73. required: true,
  74. defaultValue: '正常运行'
  75. },
  76. {
  77. key: 'handler',
  78. label: '处理人',
  79. type: 'input',
  80. required: true,
  81. defaultValue: '张三'
  82. },
  83. {
  84. key: 'remark',
  85. label: '备注',
  86. type: 'input',
  87. required: false,
  88. defaultValue: ''
  89. }
  90. ]
  91. },
  92. {
  93. id: 'living_area',
  94. name: '生活区',
  95. fields: [
  96. {
  97. key: 'description',
  98. label: '情况描述',
  99. type: 'input',
  100. required: true,
  101. defaultValue: '环境良好'
  102. },
  103. {
  104. key: 'cleaner',
  105. label: '清洁人员',
  106. type: 'input',
  107. required: true,
  108. defaultValue: '李四'
  109. }
  110. ]
  111. },
  112. {
  113. id: 'study_area',
  114. name: '学习区',
  115. fields: [
  116. {
  117. key: 'description',
  118. label: '情况描述',
  119. type: 'input',
  120. required: true,
  121. defaultValue: '设备完好'
  122. },
  123. {
  124. key: 'equipment_count',
  125. label: '设备数量',
  126. type: 'input',
  127. required: false,
  128. defaultValue: '10'
  129. }
  130. ]
  131. }
  132. ]
  133. }
  134. }
  135. // 初始化数据
  136. const initFormData = async () => {
  137. try {
  138. loading.value = true
  139. // 1. 获取区域数据
  140. const response = await fetchAreaData()
  141. areaList.value = response.areas
  142. // 2. 动态构建formData和校验规则
  143. const newFormData = {}
  144. const newFieldConfigs = {}
  145. areaList.value.forEach(area => {
  146. newFormData[area.id] = {}
  147. area.fields.forEach(field => {
  148. // 初始化字段值
  149. newFormData[area.id][field.key] = field.defaultValue || ''
  150. // 构建校验规则 - 使用嵌套字段名
  151. const fieldName = `${area.id}.${field.key}`
  152. newFieldConfigs[fieldName] = {
  153. rules: field.required ? [
  154. { required: true, message: `${field.label}不能为空` }
  155. ] : []
  156. }
  157. })
  158. })
  159. // 3. 更新响应式数据
  160. formData.value = newFormData
  161. fieldConfigs.value = newFieldConfigs
  162. console.log('初始化完成:', {
  163. formData: formData.value,
  164. fieldConfigs: fieldConfigs.value
  165. })
  166. } catch (error) {
  167. console.error('初始化失败:', error)
  168. uni.showToast({
  169. title: '数据加载失败',
  170. icon: 'error'
  171. })
  172. } finally {
  173. loading.value = false
  174. }
  175. }
  176. // 提交表单
  177. const handleSubmit = async () => {
  178. if (formRef.value) {
  179. try {
  180. const isValid = formRef.value.validateForm(formData.value, fieldConfigs.value)
  181. console.log('校验结果:', isValid)
  182. if (isValid) {
  183. console.log('提交数据:', formData.value)
  184. uni.showToast({
  185. title: '提交成功',
  186. icon: 'success'
  187. })
  188. // 这里可以调用API提交数据
  189. } else {
  190. uni.showToast({
  191. title: '请检查表单信息',
  192. icon: 'none'
  193. })
  194. }
  195. } catch (error) {
  196. console.error('提交失败:', error)
  197. uni.showToast({
  198. title: '提交失败',
  199. icon: 'error'
  200. })
  201. }
  202. }
  203. }
  204. // 重置表单
  205. const handleReset = () => {
  206. initFormData()
  207. }
  208. // 组件挂载时初始化
  209. onMounted(() => {
  210. initFormData()
  211. })
  212. </script>
  213. <style lang="scss" scoped>
  214. .dynamic-form-page {
  215. padding: 20rpx;
  216. }
  217. .debug-info {
  218. background: #f5f5f5;
  219. padding: 20rpx;
  220. margin-bottom: 20rpx;
  221. border-radius: 10rpx;
  222. text {
  223. font-size: 24rpx;
  224. color: #666;
  225. white-space: pre-wrap;
  226. }
  227. }
  228. .loading {
  229. text-align: center;
  230. padding: 100rpx 0;
  231. text {
  232. color: #999;
  233. }
  234. }
  235. .submit-section {
  236. margin-top: 40rpx;
  237. padding: 20rpx;
  238. text-align: center;
  239. }
  240. </style>