index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. <template>
  2. <el-form
  3. ref="formRef"
  4. :model="formData"
  5. :rules="formRules"
  6. :label-width="labelWidth"
  7. :label-position="labelPosition"
  8. :size="size"
  9. :disabled="disabled"
  10. :inline="inline"
  11. v-bind="$attrs"
  12. >
  13. <!-- 内联表单 -->
  14. <template v-if="inline">
  15. <el-form-item
  16. v-for="(item, index) in formConfig"
  17. :key="index"
  18. :label="item.label"
  19. :prop="item.name"
  20. :required="item.req"
  21. :rules="getItemRules(item)"
  22. >
  23. <!-- 输入框 -->
  24. <el-input
  25. v-if="item.type === 'text'"
  26. v-model="formData[item.name]"
  27. :placeholder="item.placeholder || `请输入${item.label}`"
  28. :clearable="item.clearable !== false"
  29. :disabled="item.disabled"
  30. :maxlength="item.maxlength"
  31. :show-word-limit="item.showWordLimit"
  32. v-bind="item.props"
  33. />
  34. <!-- 文本域 -->
  35. <el-input
  36. v-else-if="item.type === 'textarea'"
  37. v-model="formData[item.name]"
  38. type="textarea"
  39. :placeholder="item.placeholder || `请输入${item.label}`"
  40. :rows="item.rows || 3"
  41. :clearable="item.clearable !== false"
  42. :disabled="item.disabled"
  43. :maxlength="item.maxlength"
  44. :show-word-limit="item.showWordLimit"
  45. v-bind="item.props"
  46. />
  47. <!-- 数字输入框 -->
  48. <el-input-number
  49. v-else-if="item.type === 'number'"
  50. v-model="formData[item.name]"
  51. :placeholder="item.placeholder || `请输入${item.label}`"
  52. :min="item.min"
  53. :max="item.max"
  54. :step="item.step"
  55. :precision="item.precision"
  56. :disabled="item.disabled"
  57. :controls="item.controls !== false"
  58. v-bind="item.props"
  59. />
  60. <!-- 选择器 -->
  61. <el-select
  62. v-else-if="item.type === 'select'"
  63. v-model="formData[item.name]"
  64. :placeholder="item.placeholder || `请选择${item.label}`"
  65. :clearable="item.clearable !== false"
  66. :disabled="item.disabled"
  67. :multiple="item.multiple"
  68. :filterable="item.filterable"
  69. :remote="item.remote"
  70. :remote-method="item.remoteMethod"
  71. :loading="item.loading"
  72. v-bind="item.props"
  73. :style="inline ? 'width: 180px' : 'width: 100%'"
  74. >
  75. <el-option
  76. v-for="option in getOptions(item)"
  77. :key="getOptionValue(option, item)"
  78. :label="getOptionLabel(option, item)"
  79. :value="getOptionValue(option, item)"
  80. />
  81. </el-select>
  82. <!-- 日期选择器 -->
  83. <el-date-picker
  84. v-else-if="item.type === 'date'"
  85. v-model="formData[item.name]"
  86. :type="item.dateType || 'date'"
  87. :placeholder="item.placeholder || `请选择${item.label}`"
  88. :format="item.format"
  89. :value-format="item.valueFormat"
  90. :clearable="item.clearable !== false"
  91. :disabled="item.disabled"
  92. :show-time="item.showTime"
  93. :range-separator="item.rangeSeparator || '至'"
  94. :start-placeholder="item.startPlaceholder"
  95. :end-placeholder="item.endPlaceholder"
  96. v-bind="item.props"
  97. style="width: 180px"
  98. />
  99. <!-- 时间选择器 -->
  100. <el-time-picker
  101. v-else-if="item.type === 'time'"
  102. v-model="formData[item.name]"
  103. :placeholder="item.placeholder || `请选择${item.label}`"
  104. :format="item.format"
  105. :value-format="item.valueFormat"
  106. :clearable="item.clearable !== false"
  107. :disabled="item.disabled"
  108. v-bind="item.props"
  109. style="width: 180px"
  110. />
  111. <!-- 开关 -->
  112. <el-switch
  113. v-else-if="item.type === 'switch'"
  114. v-model="formData[item.name]"
  115. :disabled="item.disabled"
  116. :active-text="item.activeText"
  117. :inactive-text="item.inactiveText"
  118. :active-value="item.activeValue"
  119. :inactive-value="item.inactiveValue"
  120. v-bind="item.props"
  121. />
  122. <!-- 单选框组 -->
  123. <el-radio-group
  124. v-else-if="item.type === 'radio'"
  125. v-model="formData[item.name]"
  126. :disabled="item.disabled"
  127. v-bind="item.props"
  128. >
  129. <el-radio
  130. v-for="option in getOptions(item)"
  131. :key="getOptionValue(option, item)"
  132. :label="getOptionValue(option, item)"
  133. >
  134. {{ getOptionLabel(option, item) }}
  135. </el-radio>
  136. </el-radio-group>
  137. <!-- 复选框组 -->
  138. <el-checkbox-group
  139. v-else-if="item.type === 'checkbox'"
  140. v-model="formData[item.name]"
  141. :disabled="item.disabled"
  142. v-bind="item.props"
  143. >
  144. <el-checkbox
  145. v-for="option in getOptions(item)"
  146. :key="getOptionValue(option, item)"
  147. :label="getOptionValue(option, item)"
  148. >
  149. {{ getOptionLabel(option, item) }}
  150. </el-checkbox>
  151. </el-checkbox-group>
  152. <!-- 自定义插槽 -->
  153. <slot
  154. v-else-if="item.type === 'slot'"
  155. :name="item.slotName || item.name"
  156. :item="item"
  157. :value="formData[item.name]"
  158. :formData="formData"
  159. />
  160. <!-- 范围选择器 -->
  161. <div
  162. v-else-if="item.type === 'range'"
  163. class="range-input"
  164. style="display: flex; align-items: center; gap: 10px"
  165. >
  166. <el-input
  167. v-model="formData[item.beginField || 'begin']"
  168. :placeholder="item.startPlaceholder || '开始值'"
  169. :clearable="item.clearable !== false"
  170. :disabled="item.disabled"
  171. style="flex: 1"
  172. />
  173. <span style="color: #999; font-size: 14px">至</span>
  174. <el-input
  175. v-model="formData[item.endField || 'end']"
  176. :placeholder="item.endPlaceholder || '结束值'"
  177. :clearable="item.clearable !== false"
  178. :disabled="item.disabled"
  179. style="flex: 1"
  180. />
  181. </div>
  182. <!-- 默认文本显示 -->
  183. <span v-else>{{ formData[item.name] }}</span>
  184. </el-form-item>
  185. </template>
  186. <!-- 栅格布局表单 -->
  187. <el-row v-else :gutter="gutter">
  188. <el-col
  189. v-for="(item, index) in formConfig"
  190. :key="index"
  191. :span="item.span || 4"
  192. :xs="item.xs || 12"
  193. :sm="item.sm || 8"
  194. :md="item.md || 6"
  195. :lg="item.lg || 4"
  196. :xl="item.xl || 4"
  197. >
  198. <el-form-item
  199. :label="item.label"
  200. :prop="item.name"
  201. :required="item.req"
  202. :rules="getItemRules(item)"
  203. >
  204. <!-- 输入框 -->
  205. <el-input
  206. v-if="item.type === 'text'"
  207. v-model="formData[item.name]"
  208. :placeholder="item.placeholder || `请输入${item.label}`"
  209. :clearable="item.clearable !== false"
  210. :disabled="item.disabled"
  211. :maxlength="item.maxlength"
  212. :show-word-limit="item.showWordLimit"
  213. v-bind="item.props"
  214. />
  215. <!-- 文本域 -->
  216. <el-input
  217. v-else-if="item.type === 'textarea'"
  218. v-model="formData[item.name]"
  219. type="textarea"
  220. :placeholder="item.placeholder || `请输入${item.label}`"
  221. :rows="item.rows || 3"
  222. :clearable="item.clearable !== false"
  223. :disabled="item.disabled"
  224. :maxlength="item.maxlength"
  225. :show-word-limit="item.showWordLimit"
  226. v-bind="item.props"
  227. />
  228. <!-- 数字输入框 -->
  229. <el-input-number
  230. v-else-if="item.type === 'number'"
  231. v-model="formData[item.name]"
  232. :placeholder="item.placeholder || `请输入${item.label}`"
  233. :min="item.min"
  234. :max="item.max"
  235. :step="item.step"
  236. :precision="item.precision"
  237. :disabled="item.disabled"
  238. :controls="item.controls !== false"
  239. v-bind="item.props"
  240. style="width: 100%"
  241. />
  242. <!-- 选择器 -->
  243. <el-select
  244. v-else-if="item.type === 'select'"
  245. v-model="formData[item.name]"
  246. :placeholder="item.placeholder || `请选择${item.label}`"
  247. :clearable="item.clearable !== false"
  248. :disabled="item.disabled"
  249. :multiple="item.multiple"
  250. :filterable="item.filterable"
  251. :remote="item.remote"
  252. :remote-method="item.remoteMethod"
  253. :loading="item.loading"
  254. v-bind="item.props"
  255. style="width: 100%"
  256. >
  257. <el-option
  258. v-for="option in getOptions(item)"
  259. :key="getOptionValue(option, item)"
  260. :label="getOptionLabel(option, item)"
  261. :value="getOptionValue(option, item)"
  262. />
  263. </el-select>
  264. <!-- 日期选择器 -->
  265. <el-date-picker
  266. v-else-if="item.type === 'date'"
  267. v-model="formData[item.name]"
  268. :type="item.dateType || 'date'"
  269. :placeholder="item.placeholder || `请选择${item.label}`"
  270. :format="item.format"
  271. :value-format="item.valueFormat"
  272. :clearable="item.clearable !== false"
  273. :disabled="item.disabled"
  274. :show-time="item.showTime"
  275. :range-separator="item.rangeSeparator || '至'"
  276. :start-placeholder="item.startPlaceholder"
  277. :end-placeholder="item.endPlaceholder"
  278. v-bind="item.props"
  279. style="width: 100%"
  280. />
  281. <!-- 时间选择器 -->
  282. <el-time-picker
  283. v-else-if="item.type === 'time'"
  284. v-model="formData[item.name]"
  285. :placeholder="item.placeholder || `请选择${item.label}`"
  286. :format="item.format"
  287. :value-format="item.valueFormat"
  288. :clearable="item.clearable !== false"
  289. :disabled="item.disabled"
  290. v-bind="item.props"
  291. style="width: 100%"
  292. />
  293. <!-- 开关 -->
  294. <el-switch
  295. v-else-if="item.type === 'switch'"
  296. v-model="formData[item.name]"
  297. :disabled="item.disabled"
  298. :active-text="item.activeText"
  299. :inactive-text="item.inactiveText"
  300. :active-value="item.activeValue"
  301. :inactive-value="item.inactiveValue"
  302. v-bind="item.props"
  303. />
  304. <!-- 单选框组 -->
  305. <el-radio-group
  306. v-else-if="item.type === 'radio'"
  307. v-model="formData[item.name]"
  308. :disabled="item.disabled"
  309. v-bind="item.props"
  310. >
  311. <el-radio
  312. v-for="option in getOptions(item)"
  313. :key="getOptionValue(option, item)"
  314. :label="getOptionValue(option, item)"
  315. >
  316. {{ getOptionLabel(option, item) }}
  317. </el-radio>
  318. </el-radio-group>
  319. <!-- 复选框组 -->
  320. <el-checkbox-group
  321. v-else-if="item.type === 'checkbox'"
  322. v-model="formData[item.name]"
  323. :disabled="item.disabled"
  324. v-bind="item.props"
  325. >
  326. <el-checkbox
  327. v-for="option in getOptions(item)"
  328. :key="getOptionValue(option, item)"
  329. :label="getOptionValue(option, item)"
  330. >
  331. {{ getOptionLabel(option, item) }}
  332. </el-checkbox>
  333. </el-checkbox-group>
  334. <!-- 自定义插槽 -->
  335. <slot
  336. v-else-if="item.type === 'slot'"
  337. :name="item.slotName || item.name"
  338. :item="item"
  339. :value="formData[item.name]"
  340. :formData="formData"
  341. />
  342. <!-- 范围选择器 -->
  343. <div
  344. v-else-if="item.type === 'range'"
  345. class="range-input"
  346. style="display: flex; align-items: center; gap: 10px"
  347. >
  348. <el-input
  349. v-model="formData[item.beginField || 'begin']"
  350. :placeholder="item.startPlaceholder || '开始值'"
  351. :clearable="item.clearable !== false"
  352. :disabled="item.disabled"
  353. style="flex: 1"
  354. />
  355. <span style="color: #999; font-size: 14px">至</span>
  356. <el-input
  357. v-model="formData[item.endField || 'end']"
  358. :placeholder="item.endPlaceholder || '结束值'"
  359. :clearable="item.clearable !== false"
  360. :disabled="item.disabled"
  361. style="flex: 1"
  362. />
  363. </div>
  364. <!-- 默认文本显示 -->
  365. <span v-else>{{ formData[item.name] }}</span>
  366. </el-form-item>
  367. </el-col>
  368. </el-row>
  369. </el-form>
  370. </template>
  371. <script setup>
  372. import { ref, reactive, watch, computed, onMounted } from "vue";
  373. const props = defineProps({
  374. // 表单配置
  375. config: {
  376. type: Array,
  377. default: () => [],
  378. },
  379. // 表单数据
  380. modelValue: {
  381. type: Object,
  382. default: () => ({}),
  383. },
  384. // 标签宽度
  385. labelWidth: {
  386. type: String,
  387. default: "120px",
  388. },
  389. // 标签位置
  390. labelPosition: {
  391. type: String,
  392. default: "right",
  393. },
  394. // 尺寸
  395. size: {
  396. type: String,
  397. default: "default",
  398. },
  399. // 是否禁用
  400. disabled: {
  401. type: Boolean,
  402. default: false,
  403. },
  404. // 栅格间距
  405. gutter: {
  406. type: Number,
  407. default: 20,
  408. },
  409. // 是否内联表单
  410. inline: {
  411. type: Boolean,
  412. default: false,
  413. },
  414. });
  415. const emit = defineEmits(["update:modelValue", "change", "validate"]);
  416. const formRef = ref();
  417. const formData = reactive({});
  418. const formRules = reactive({});
  419. // 监听父组件传入的modelValue变化,同步到formData
  420. watch(
  421. () => props.modelValue,
  422. (newValue) => {
  423. if (newValue) {
  424. Object.assign(formData, newValue);
  425. }
  426. },
  427. { immediate: true, deep: true }
  428. );
  429. // 监听formData变化,同步回父组件
  430. watch(
  431. formData,
  432. (newData) => {
  433. emit("update:modelValue", { ...newData });
  434. },
  435. { deep: true }
  436. );
  437. // 计算属性
  438. const formConfig = computed(() => props.config);
  439. // 初始化表单数据
  440. const initFormData = () => {
  441. formConfig.value.forEach((item) => {
  442. if (formData[item.name] === undefined) {
  443. formData[item.name] = item.value || getDefaultValue(item.type);
  444. }
  445. });
  446. };
  447. // 获取默认值
  448. const getDefaultValue = (type) => {
  449. const defaults = {
  450. text: "",
  451. textarea: "",
  452. number: null,
  453. select: null,
  454. date: null,
  455. time: null,
  456. switch: false,
  457. radio: null,
  458. checkbox: [],
  459. };
  460. return defaults[type] || "";
  461. };
  462. // 初始化表单验证规则
  463. const initFormRules = () => {
  464. formConfig.value.forEach((item) => {
  465. if (item.req) {
  466. formRules[item.name] = [
  467. { required: true, message: `请输入${item.label}`, trigger: "blur" },
  468. ];
  469. }
  470. if (item.rules) {
  471. formRules[item.name] = formRules[item.name] || [];
  472. formRules[item.name].push(...item.rules);
  473. }
  474. });
  475. };
  476. // 获取表单项验证规则
  477. const getItemRules = (item) => {
  478. const rules = [];
  479. if (item.req) {
  480. rules.push({
  481. required: true,
  482. message: `请输入${item.label}`,
  483. trigger: "blur",
  484. });
  485. }
  486. if (item.rules) {
  487. rules.push(...item.rules);
  488. }
  489. return rules;
  490. };
  491. // 获取选项数据
  492. const getOptions = (item) => {
  493. return item.option || [];
  494. };
  495. // 获取选项标签
  496. const getOptionLabel = (option, item) => {
  497. if (typeof option === "string") return option;
  498. return option[item.optionLabel] || option.label || option.name || option;
  499. };
  500. // 获取选项值
  501. const getOptionValue = (option, item) => {
  502. if (typeof option === "string") return option;
  503. return option[item.optionValue] || option.value || option.id || option;
  504. };
  505. // 监听外部数据变化
  506. watch(
  507. () => props.modelValue,
  508. (newVal) => {
  509. Object.assign(formData, newVal);
  510. },
  511. { deep: true, immediate: true }
  512. );
  513. // 监听表单数据变化
  514. watch(
  515. formData,
  516. (newVal) => {
  517. emit("update:modelValue", { ...newVal });
  518. emit("change", { ...newVal });
  519. },
  520. { deep: true }
  521. );
  522. // 表单验证
  523. const validate = (callback) => {
  524. return formRef.value.validate(callback);
  525. };
  526. // 重置表单
  527. const resetFields = () => {
  528. formRef.value.resetFields();
  529. // 重置为初始值
  530. formConfig.value.forEach((item) => {
  531. formData[item.name] = item.value || getDefaultValue(item.type);
  532. });
  533. };
  534. // 清除验证
  535. const clearValidate = (props) => {
  536. formRef.value.clearValidate(props);
  537. };
  538. // 暴露方法
  539. defineExpose({
  540. validate,
  541. resetFields,
  542. clearValidate,
  543. formRef,
  544. });
  545. // 初始化
  546. onMounted(() => {
  547. initFormData();
  548. initFormRules();
  549. });
  550. </script>
  551. <style scoped>
  552. .el-form-item {
  553. margin-bottom: 18px;
  554. }
  555. </style>