index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. <template>
  2. <div class="by-form">
  3. <el-form :model="formData" :label-width="formOption.labelWidth || '100px'" :inline="formOption.inline || false" :rules="rules"
  4. :labelPosition="formOption.labelPosition || 'right'" ref="byForm" :disabled="formOption.disabled || false">
  5. <template v-for="i in formConfig" :key="i.model">
  6. <el-form-item :label="i.label" :prop="i.prop" v-if="(Object.keys(i).length>0)&& (i.isShow || i.isShow == undefined)" :style="
  7. (i.type == 'title'||i.type == 'title1')
  8. ? 'width:100%'
  9. : i.itemWidth
  10. ? 'width:' + i.itemWidth + '%'
  11. : formOption.itemWidth
  12. ? 'width:' + formOption.itemWidth + '%'
  13. : '100%'
  14. " :class="
  15. (i.type == 'title'||i.type == 'title1')?
  16. 'formTitle'
  17. : ''
  18. ">
  19. <el-input v-if="i.type == 'input'" v-model="formData[i.prop]" :placeholder="i.placeholder || $t('common.pleaseEnter')"
  20. @input="(e) => commonsEmit(e, i)" @change="(e) => commonsEmitChange(e, i)" :type="i.itemType ? i.itemType : 'text'"
  21. :disabled="i.disabled ? i.disabled : false" :max="i.max" :min="i.min" :maxlength="i.maxlength"
  22. :readonly="i.readonly ? i.readonly : false" :style="i.style" :rows="i.rows||2" />
  23. <el-input v-if="i.type == 'selectInput'" v-model="formData[i.prop]" :placeholder="i.placeholder || $t('common.pleaseEnter')"
  24. @input="(e) => commonsEmit(e, i)" :type="i.itemType ? i.itemType : 'text'" :disabled="i.disabled ? i.disabled : false"
  25. :max="i.max" :min="i.min" :maxlength="i.maxlength" :readonly="i.readonly ? i.readonly : false">
  26. <template #prepend>
  27. <el-select v-model="formData[i.selectProp]" :placeholder="i.selectPlaceholder || $t('common.pleaseSelect')"
  28. @change="(e) => commonsEmit(e, i)" :disabled="i.disabledSelect ? i.disabledSelect : false"
  29. :readonly="i.readonly ? i.readonly : false" style="width: 80px">
  30. <el-option :label="j.dictValue || j.name || j.label" :value="j.dictKey||j.id || j.value" v-for="j in i.data"
  31. :key="j.id || j.dictKey || j.value">
  32. </el-option>
  33. </el-select>
  34. </template>
  35. </el-input>
  36. <div v-else-if="i.type == 'text'">
  37. {{formData[i.prop]}}
  38. </div>
  39. <el-select v-model="formData[i.prop]" :multiple="i.multiple || false" v-else-if="i.type == 'select'"
  40. :placeholder="i.placeholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)"
  41. :disabled="i.disabled ? i.disabled : false" :clearable="i.clearable != undefined ? i.clearable : true"
  42. :filterable="i.filterable != undefined ? i.filterable : true" :style="i.style?i.style:{width:'100%'}"
  43. :readonly="i.readonly ? i.readonly : false">
  44. <el-option :label="j.dictValue || j.name || j.label" :value="j.dictKey||j.id || j.value" v-for="j in i.data"
  45. :key="j.id || j.dictKey || j.value" :disabled="j.disabled ?j.disabled:false ">
  46. </el-option>
  47. </el-select>
  48. <el-tree-select v-model="formData[i.prop]" :multiple="i.multiple || false" v-else-if="i.type == 'treeSelect'" :data="i.data"
  49. :readonly="i.readonly ? i.readonly : false" :props="{
  50. value: i.propsTreeValue || 'id',
  51. label: i.propsTreeLabel || 'label',
  52. children: i.propsTreeChildren || 'children',
  53. disabled:i.propsTreeDisabled || 'disabled'
  54. }" value-key="id" :filterable="i.filterable ? true : true" :clearable="i.clearable ? i.clearable : false"
  55. :placeholder="i.placeholder || $t('common.pleaseSelect')" :disabled="i.disabled ? i.disabled : false" check-strictly
  56. :style="i.style?i.style:{width:'100%'}" default-expand-all @change="(e) => commonsEmit(e, i)" />
  57. <el-date-picker v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false" v-else-if="i.type == 'date'" :type="i.itemType"
  58. :placeholder="i.placeholder || $t('common.pleaseSelectTime')" @change="(e) => commonsEmit(e, i)"
  59. :disabled="i.disabled ? i.disabled : false" :format="i.format ? i.format : dateFormatInit(i.itemType)"
  60. :value-format="i.format ? i.format : dateFormatInit(i.itemType)" :style="i.style?i.style:{width:'100%'}"
  61. :disabled-date="i.disabledFn ? i.disabledFn :()=>false" />
  62. <el-switch :disabled="i.disabled ? i.disabled : false" v-else-if="i.type == 'switch'" :readonly="i.readonly ? i.readonly : false"
  63. v-model="formData[i.prop]" />
  64. <el-checkbox-group v-else-if="i.type == 'checkbox'" v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false"
  65. :disabled="i.disabled ? i.disabled : false">
  66. <el-checkbox v-for="j in i.data" :key="j.id || j.value" :label="j.id || j.value" name="type">
  67. {{ j.name || j.label }}
  68. </el-checkbox>
  69. </el-checkbox-group>
  70. <el-radio-group v-else-if="i.type == 'radio'" v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false"
  71. :disabled="i.disabled ? i.disabled : false" @change="(e) => commonsEmit(e, i)">
  72. <el-radio :border="i.border ? i.border : false" v-for="j in i.data" :key="j.id || j.value ||j.dictKey"
  73. :label="j.id ||j.dictKey || j.value" name="type">
  74. {{j.dictValue || j.name || j.label }}
  75. </el-radio>
  76. </el-radio-group>
  77. <el-input-number v-else-if="i.type == 'number'" v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false"
  78. :placeholder="i.placeholder || $t('common.pleaseEnter')" @change="(e) => commonsEmit(e, i)"
  79. :disabled="i.disabled ? i.disabled : false" :min="i.min ? i.min : 0" :max="i.max ? i.max : 9999999999"
  80. :step="i.step ? i.step : 1" :precision="i.precision !== '' ? i.precision : 2"
  81. :controls="i.controls === false ? false : true" :style="i.style?i.style:{width:'100%'}" onmousewheel="return false;">
  82. </el-input-number>
  83. <el-tree v-else-if="i.type == 'tree'" :data="i.data" :props="i.props" :readonly="i.readonly ? i.readonly : false"
  84. :show-checkbox="i.showCheckbox || true">
  85. </el-tree>
  86. <el-cascader v-else-if="i.type == 'cascader'" :options="i.data" :props="i.props" :readonly="i.readonly ? i.readonly : false"
  87. :placeholder="i.placeholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)"
  88. :disabled="i.disabled ? i.disabled : false" :style="i.style">
  89. </el-cascader>
  90. <!-- <div class="form-title" v-else-if="i.type == 'title'">
  91. {{ i.title }}
  92. </div> -->
  93. <div v-else-if="i.type == 'title'" style="width:100%">
  94. <div v-if="i.haveLine" style="width:calc(100% + 80px);margin-left:-45px;background:#F0F2F5;height:15px"></div>
  95. <div :style="{marginTop:i.haveLine?'15px':''}" style="margin-left:-15px;">
  96. <TitleInfo :content="i.title"></TitleInfo>
  97. </div>
  98. </div>
  99. <div v-else-if="i.type == 'title1'" style="width:100%">
  100. <div>
  101. <TitleInfo :content="i.title"></TitleInfo>
  102. </div>
  103. </div>
  104. <slot :name="i.slotName" v-else-if="i.type == 'slot'">
  105. {{ i.slotName }}插槽占位符
  106. </slot>
  107. <div class="upload" v-else-if="i.type == 'upload'">
  108. <el-upload :file-list="formData[i.prop]?formData[i.prop]:[]" multiple :action="uploadUrl" :data="uploadData"
  109. :list-type="i.listType ? i.listType : 'text'" :accept="i.accept?i.accept :''" :limit="i.limit?i.limit:3"
  110. :before-upload="(file)=>handleBeforeUpload(file,i)" :on-success="()=>handleSuccess(i)"
  111. :on-remove="(file)=>handleRemove(file,i)" :on-exceed="()=>handleExceed(i)" :on-preview="onPreviewFile">
  112. <el-icon v-if="i.listType=='picture-card'">
  113. <Plus />
  114. </el-icon>
  115. <!-- <span v-else-if="formOption.disabled" style="color:#409EFF">已上传:</span> -->
  116. <el-button type="primary" plain v-else>点击上传</el-button>
  117. </el-upload>
  118. </div>
  119. <div class="upload" v-else-if="i.type == 'uploadImg'">
  120. <el-upload :action="uploadUrl" accept=".gif, .jpeg, .jpg, .png" :show-file-list="false" :data="uploadData"
  121. :before-upload="(file)=>handleBeforeUploadOne(file,i.prop,i.imgProp)" :on-success="()=>handleSuccessOne(i.imgProp)">
  122. <div v-loading="imgLoading">
  123. <img v-if="formData[i.imgProp]" :src="formData[i.imgProp]" class="picOne" />
  124. <el-icon v-else class="avatar-uploader-icon">
  125. <Plus />
  126. </el-icon>
  127. </div>
  128. </el-upload>
  129. </div>
  130. <div v-else-if="i.type == 'table'" class="by-form-table" style="width: 100%">
  131. <el-table :data="formData[i.prop]" style="width: 100%">
  132. <el-table-column :prop="j.prop" :label="j.label" :width="i.width" v-for="(j, jindex) in i.column">
  133. <template #default="scope" v-if="j.type">
  134. <component @change="(e) => formTableChange(e, scope, j)" v-model="scope.row[j.prop]" :is="formTableObj[j.type]"
  135. :placeholder="j.placeholder || $t('common.pleaseEnter')" :type="j.type == 'number' ? 'number' : 'text'">
  136. <el-option :label="n.title || n.name || n.label" :value="n.id || n.value" v-for="n in j.data" :key="n.id"
  137. v-if="j.type == 'select'">
  138. </el-option>
  139. </component>
  140. </template>
  141. </el-table-column>
  142. </el-table>
  143. </div>
  144. <!-- <div v-else-if="i.type == 'json'">
  145. <byForm :formConfig="i.json" :formOption="formOption" v-model="formData[i.prop]" ref="byform" :rules="rules">
  146. </byForm>
  147. </div> -->
  148. </el-form-item>
  149. </template>
  150. </el-form>
  151. </div>
  152. </template>
  153. <script>
  154. export default {
  155. name: "byForm",
  156. };
  157. </script>
  158. <script setup>
  159. import { set } from "@vueuse/shared";
  160. import { reactive } from "vue";
  161. import TitleInfo from "@/components/TitleInfo/index.vue";
  162. defineProps({
  163. modelValue: {
  164. type: Object,
  165. default: false,
  166. },
  167. formConfig: {
  168. type: Array,
  169. default: false,
  170. },
  171. disabled: {
  172. type: Boolean,
  173. default: false,
  174. },
  175. formOption: {
  176. type: Object,
  177. default: false,
  178. },
  179. rules: {
  180. type: Object,
  181. default: false,
  182. },
  183. });
  184. const formTableChange = (e, scope, column) => {
  185. if (column.fn) {
  186. column.fn(e, scope);
  187. }
  188. console.log(formData);
  189. console.log(e, scope);
  190. };
  191. const formTableObj = {
  192. number: "el-input",
  193. text: "el-input",
  194. select: "el-select",
  195. input: "el-input",
  196. };
  197. const fileList = ref([]);
  198. const fileListCopy = ref([]);
  199. const uploadData = ref({});
  200. const fileData = ref({});
  201. //文件上传相关方法
  202. const handleSuccess = (item) => {
  203. if (fileData.value && fileData.value.fileUrl) {
  204. formData.value[item.prop].push({
  205. id: fileData.value.id,
  206. fileName: fileData.value.fileName,
  207. name: fileData.value.fileName,
  208. url: fileData.value.fileUrl,
  209. fileUrl: fileData.value.fileUrl,
  210. });
  211. }
  212. emit("update:modelValue", formData.value);
  213. };
  214. const handleRemove = (file, item) => {
  215. let index = formData.value[item.prop].findIndex(
  216. (x) => x.id == file.id || x.id == file.raw.id
  217. );
  218. if (index > -1) {
  219. formData.value[item.prop].splice(index, 1);
  220. }
  221. };
  222. const handleBeforeUpload = async (file, item) => {
  223. fileData.value = {};
  224. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  225. file.id = res.id;
  226. file.fileUrl = res.fileUrl;
  227. uploadData.value = res.uploadBody;
  228. fileData.value = res;
  229. return true;
  230. };
  231. const handleExceed = (item) => {
  232. let limit = item.limit || 3;
  233. return proxy.msgTip(`上传文件数量不可大于${limit}`, 2);
  234. };
  235. const onPreviewFile = (file, a) => {
  236. if (file && file.fileUrl) {
  237. window.open(file.fileUrl, "_blank");
  238. } else {
  239. window.open(file.raw.fileUrl, "_blank");
  240. }
  241. };
  242. const imgLoading = ref(false);
  243. const handleBeforeUploadOne = async (file, prop, imgProp) => {
  244. imgLoading.value = true;
  245. fileData.value = {};
  246. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  247. uploadData.value = res.uploadBody;
  248. formData.value[prop] = [
  249. {
  250. id: res.id,
  251. fileName: res.fileName,
  252. fileUrl: res.fileUrl,
  253. },
  254. ];
  255. fileData.value = res;
  256. return true;
  257. };
  258. const handleSuccessOne = (imgProp) => {
  259. if (fileData.value && fileData.value.fileUrl) {
  260. formData.value[imgProp] = fileData.value.fileUrl;
  261. emit("update:modelValue", formData.value);
  262. }
  263. setTimeout(() => {
  264. imgLoading.value = false;
  265. }, 400);
  266. };
  267. const isInit = ref(false);
  268. const { proxy } = getCurrentInstance();
  269. const emit = defineEmits(["update:modelValue"]);
  270. const formData = computed(() => {
  271. return proxy.modelValue;
  272. });
  273. const formDataReset = ref({ ...proxy.modelValue });
  274. const commonsEmit = (prop, item) => {
  275. if (item.type == "input" && item.itemType == "number") {
  276. console.log(formData.value);
  277. }
  278. if (item.fn) {
  279. item.fn(prop);
  280. }
  281. emit("update:modelValue", formData.value);
  282. };
  283. const commonsEmitChange = (prop, item) => {
  284. if (item.type == "input") {
  285. formData.value[item.prop] = prop.trim();
  286. }
  287. if (item.fn) {
  288. item.fn(prop);
  289. }
  290. emit("update:modelValue", formData.value);
  291. };
  292. const loadInit = () => {
  293. const v = this;
  294. for (let i = 0; i < proxy.formConfig.length; i++) {
  295. const element = proxy.formConfig[i];
  296. if (element.isLoad) {
  297. commonGetdata(element.isLoad, i);
  298. }
  299. }
  300. };
  301. const dateFormatInit = (itemType) => {
  302. const formatObj = {
  303. year: "YYYY",
  304. month: "YYYY-MM",
  305. date: "YYYY-MM-DD",
  306. dates: "YYYY-MM-DD",
  307. datetime: "YYYY-MM-DD HH:mm:ss",
  308. monthrange: "YYYY-MM-DD HH:mm:ss",
  309. datetimerange: "YYYY-MM-DD HH:mm:ss",
  310. daterange: "YYYY-MM-DD HH:mm:ss",
  311. };
  312. return formatObj[itemType];
  313. };
  314. //公用递归,保证key,val统一
  315. const commonRecursive = (arr, labelKey, labelVal, childrenName) => {
  316. for (let i = 0; i < arr.length; i++) {
  317. if (labelKey == "stringArray") {
  318. arr[i] = {
  319. label: arr[i],
  320. value: arr[i],
  321. id: arr[i],
  322. title: arr[i],
  323. };
  324. } else {
  325. arr[i].title = arr[i].label = arr[i][labelKey];
  326. arr[i].id = arr[i].value = arr[i][labelVal];
  327. }
  328. if (childrenName) {
  329. arr[i].children = arr[i][childrenName];
  330. }
  331. arr[i].checked = false;
  332. typeof arr[i][labelVal] == String
  333. ? (arr[i].key = arr[i][labelVal])
  334. : (arr[i].key = JSON.stringify(arr[i][labelVal]));
  335. if (childrenName) {
  336. this.commonRecursive(
  337. arr[i][childrenName],
  338. labelKey,
  339. labelVal,
  340. childrenName
  341. );
  342. }
  343. }
  344. };
  345. //请求form表单所需数据字典
  346. const commonGetdata = (isLoad, i) => {
  347. proxy[isLoad.method](isLoad.url, isLoad.req).then((message) => {
  348. console.log(message);
  349. if (getFormat(isLoad.resUrl, message) == undefined) {
  350. console.log("请查看isLoad配置是否正确url:" + isLoad.url);
  351. return;
  352. }
  353. proxy.formConfig[i].data = getFormat(isLoad.resUrl, message);
  354. if (isLoad.labelKey) {
  355. commonRecursive(
  356. proxy.formConfig[i].data,
  357. isLoad.labelKey,
  358. isLoad.labelVal,
  359. isLoad.childrenName
  360. );
  361. }
  362. console.log(proxy.formConfig[i].data);
  363. });
  364. };
  365. //根据resurl获取数据
  366. const getFormat = (formatStr, props) => {
  367. if (!formatStr) return props;
  368. return formatStr
  369. .split(".")
  370. .reduce((total, cur) => (!total ? "" : total[cur]), props);
  371. };
  372. //初始化所有表单
  373. const formDataInit = () => {
  374. var map = {
  375. input: "",
  376. radio: null,
  377. select: null,
  378. checkbox: [],
  379. date: "",
  380. datetime: "",
  381. daterange: [],
  382. datetimerange: [],
  383. year: null,
  384. month: null,
  385. switch: false,
  386. inputNumber: 0,
  387. cascader: [],
  388. Solt: null,
  389. Transfer: [],
  390. Upload: { path: null, id: null, name: null },
  391. password: "",
  392. treeSelect: "",
  393. json: {},
  394. };
  395. const formDataCopy = { ...formData.value };
  396. for (let i = 0; i < proxy.formConfig.length; i++) {
  397. const element = proxy.formConfig[i];
  398. if (formDataCopy[element.prop] || element.type === "slot") {
  399. continue;
  400. }
  401. if (map[element.itemType] != undefined) {
  402. formData.value[element.prop] = map[element.itemType];
  403. } else {
  404. formData.value[element.prop] = element.multiple ? [] : map[element.type];
  405. }
  406. }
  407. emit("update:modelValue", formData.value);
  408. };
  409. const handleSubmit = async (onSubmit) => {
  410. try {
  411. const flag = await proxy.$refs["byForm"].validate();
  412. if (flag) {
  413. const form = { ...formData.value };
  414. proxy.formConfig.map((item) => {
  415. // if (item.type == "json") {
  416. // form[item.prop] = JSON.stringify(form[item.prop]);
  417. // }
  418. // if (item.type == 'input') {
  419. // form[item.prop] = form[item.prop].trim();
  420. // }
  421. });
  422. emit("update:modelValue", form);
  423. onSubmit();
  424. return true;
  425. } else {
  426. }
  427. } catch (err) {
  428. setTimeout(() => {
  429. const errorDiv = document.getElementsByClassName("is-error");
  430. if (errorDiv && errorDiv[0]) {
  431. errorDiv[0].scrollIntoView({
  432. behavior: "smooth",
  433. block: "center",
  434. inline: "nearest",
  435. });
  436. }
  437. }, 0);
  438. console.log("请检查表单!", err);
  439. return false;
  440. }
  441. };
  442. const byform = ref(null); // 延迟使用,因为还没有返回跟挂载
  443. onMounted(() => {});
  444. defineExpose({
  445. handleSubmit,
  446. });
  447. formDataInit();
  448. loadInit();
  449. </script>
  450. <style scope>
  451. .form-title {
  452. font-size: 14px;
  453. font-weight: bold;
  454. margin-top: 22px;
  455. color: #333333;
  456. }
  457. .by-form .el-form--inline .el-form-item {
  458. margin-right: 0px;
  459. box-sizing: border-box;
  460. }
  461. .by-form .el-form--inline > .el-form-item {
  462. padding: 0 15px 0 0px;
  463. }
  464. .by-form .el-form--inline .formTitle {
  465. padding: 0px !important;
  466. }
  467. /* .el-form--inline.el-form--label-top{
  468. justify-content: space-between;
  469. } */
  470. .dn {
  471. display: none !important;
  472. }
  473. /* .by-form-json .by-form .el-form .el-form-item {
  474. margin-bottom: 18px;
  475. } */
  476. /* .by-form-json {
  477. padding: 0px !important;
  478. } */
  479. .picOne {
  480. object-fit: contain;
  481. width: 80px;
  482. height: 80px;
  483. cursor: pointer;
  484. vertical-align: middle;
  485. }
  486. .el-icon.avatar-uploader-icon {
  487. font-size: 20px;
  488. color: #8c939d;
  489. width: 80px;
  490. height: 80px;
  491. text-align: center;
  492. border: 1px dashed var(--el-border-color);
  493. }
  494. </style>