index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <template>
  2. <a-config-provider :locale="locale">
  3. <a-form
  4. v-if="
  5. typeof value.list !== 'undefined' && typeof value.config !== 'undefined'
  6. "
  7. :layout="value.config.layout"
  8. :model="formData"
  9. @submit="handleSubmit"
  10. :style="value.config.customStyle"
  11. :hideRequiredMark="value.config.hideRequiredMark"
  12. ref="formRef"
  13. >
  14. <buildBlocks
  15. ref="buildBlocksRef"
  16. @handleReset="reset"
  17. v-for="(record, index) in value.list"
  18. :record="record"
  19. :dynamicData="getDynamicData"
  20. :config="config"
  21. :formData="formData"
  22. :disabled="disabled"
  23. :formConfig="value.config"
  24. :validatorError="validatorError"
  25. :key="index"
  26. @change="handleChange"
  27. @btn-click="$emit('btn-click',$event)"
  28. />
  29. </a-form>
  30. </a-config-provider>
  31. <a-modal v-model:open="requiredOpen" title="表单校验不通过" :footer="null" :afterClose="requiredClose">
  32. <div v-for="(item,index) in requireComputed">
  33. <p>{{ item }}</p>
  34. </div>
  35. </a-modal>
  36. </template>
  37. <script setup>
  38. import {ref, reactive, toRefs, watch, onMounted, nextTick, computed} from "vue";
  39. import buildBlocks from "./buildBlocks.vue";
  40. import zhCN from "ant-design-vue/es/locale/zh_CN";
  41. const requiredOpen = ref(false)
  42. const requiredContents = ref([])
  43. const requiredChildContents = ref([])
  44. const requireComputed = computed(() => {
  45. const result = []
  46. for (let item of requiredContents.value) {
  47. if (Array.isArray(item.name) && Array.isArray(item.errors)
  48. && item.name.length > 0 && item.errors.length > 0) {
  49. if (item.errors[0] == '必填项') {
  50. result.push(props.fieldMap[item.name[0]]?.name + ' 是【' + item.errors[0] + '】')
  51. } else {
  52. result.push(props.fieldMap[item.name[0]]?.name + ' 应【' + item.errors[0] + '】')
  53. }
  54. }
  55. }
  56. for (let item of requiredChildContents.value) {
  57. if (Array.isArray(item.name) && Array.isArray(item.errors)
  58. && item.name.length > 2 && item.errors.length > 0) {
  59. for (const key in props.fieldMap) {
  60. if (props.fieldMap.hasOwnProperty(key) && props.fieldMap[key].type == 'CHILDREN') { // 检查是否是对象自身的属性
  61. const find = props.fieldMap[key].children.find(f => f.code === item.name[2]);
  62. if (find) {
  63. result.push('子表【' + props.fieldMap[key].name + '】的第' + (item.name[1] + 1) + '行:' + find.name + ' 是【' + item.errors[0] + '】')
  64. }
  65. }
  66. }
  67. }
  68. }
  69. return result
  70. });
  71. const requiredClose = () => {
  72. requiredContents.value = []
  73. requiredChildContents.value = []
  74. }
  75. const formRef = ref(null);
  76. const buildBlocksRef = ref(null);
  77. const formData = reactive({});
  78. const props = defineProps({
  79. value: {
  80. type: Object,
  81. required: true,
  82. },
  83. dynamicData: {
  84. type: Object,
  85. default: () => ({}),
  86. },
  87. config: {
  88. type: Object,
  89. default: () => ({}),
  90. },
  91. disabled: {
  92. type: Boolean,
  93. default: false,
  94. },
  95. outputString: {
  96. type: Boolean,
  97. default: false,
  98. },
  99. defaultValue: {
  100. type: Object,
  101. default: () => ({}),
  102. },
  103. fieldMap: {
  104. type: Object,
  105. default: () => ({}),
  106. },
  107. });
  108. const emit = defineEmits(["submit", "change"]);
  109. const locale = zhCN;
  110. const validatorError = ref({});
  111. const defaultDynamicData = ref({});
  112. // 计算属性
  113. const getDynamicData = computed(() => {
  114. return typeof props.dynamicData === "object" && Object.keys(props.dynamicData).length > 0
  115. ? props.dynamicData
  116. : window.$stb_dynamicData || {};
  117. });
  118. // 方法
  119. const handleSubmit = (e) => {
  120. e.preventDefault();
  121. emit("submit", getData);
  122. };
  123. const reset = () => {
  124. return new Promise((resolve, reject) => {
  125. formRef.value.resetFields();
  126. // 防止富文本重置失败
  127. setTimeout(() => {
  128. // 重置数据
  129. for (const key in formData) {
  130. formData[key] = props.defaultValue[key] || undefined;
  131. }
  132. resolve(true);
  133. }, 100)
  134. })
  135. };
  136. const getData = () => {
  137. return new Promise((resolve, reject) => {
  138. formRef.value.validateFields().then(res => {
  139. {
  140. //富文本的必填单独处理
  141. for (let key of Object.keys(res)) {
  142. if (res[key] == '<p><br></p>') {
  143. const eerr = {}
  144. eerr.errorFields = []
  145. eerr.errorFields.push({
  146. errors: ['必填项'],
  147. name: [key],
  148. warnings: []
  149. })
  150. requiredOpen.value = open
  151. requiredContents.value = eerr.errorFields
  152. validatorError.value = eerr;
  153. reject(eerr);
  154. }
  155. }
  156. }
  157. validatorError.value = {};
  158. if (props.outputString) {
  159. for (const key in formData) {
  160. const type = typeof formData[key];
  161. if (type === "string" || type === "undefined") {
  162. continue;
  163. } else if (type === "object") {
  164. formData[key] = `st-form-design#${type}#${JSON.stringify(
  165. formData[key]
  166. )}`;
  167. } else {
  168. formData[key] = `st-form-design#${type}#${String(formData[key])}`;
  169. }
  170. }
  171. resolve(formData);
  172. } else {
  173. return Promise.all(buildBlocksRef.value.map(item => item.validationSubform())).then(res => {
  174. resolve(formData);
  175. }).catch(err => {
  176. requiredOpen.value = open
  177. validatorError.value = err;
  178. requiredChildContents.value = err.errorFields
  179. reject(err);
  180. });
  181. }
  182. }).catch(err => {
  183. requiredOpen.value = open
  184. requiredContents.value = err.errorFields
  185. validatorError.value = err;
  186. reject(err);
  187. })
  188. })
  189. };
  190. const setData = (json) => {
  191. return new Promise((resolve, reject) => {
  192. try {
  193. if (props.outputString) {
  194. // 将非string数据还原
  195. for (const key in json) {
  196. if (!json[key].startsWith("st-form-design#")) {
  197. continue;
  198. }
  199. const array = json[key].split("#");
  200. if (array[1] === "object") {
  201. json[key] = JSON.parse(array[2]);
  202. } else if (array[1] === "number") {
  203. json[key] = Number(array[2]);
  204. } else if (array[1] === "boolean") {
  205. json[key] = Boolean(array[2]);
  206. }
  207. }
  208. setFormData(json);
  209. } else {
  210. setFormData(json);
  211. }
  212. resolve(true);
  213. } catch (err) {
  214. console.error(err);
  215. reject(err);
  216. }
  217. });
  218. };
  219. const setOptions = (fields, optionName, value) => {
  220. // ... 和原来一样的逻辑 ...
  221. fields = new Set(fields);
  222. // 递归遍历控件树
  223. const traverse = array => {
  224. array.forEach(element => {
  225. if (fields.has(element.model)) {
  226. // this.$set(element.options, optionName, value);
  227. element.options[optionName] = value;
  228. }
  229. if (element.type === "grid" || element.type === "tabs") {
  230. // 栅格布局 and 标签页
  231. element.columns.forEach(item => {
  232. traverse(item.list);
  233. });
  234. } else if (element.type === "card" || element.type === "batch") {
  235. // 卡片布局 and 动态表格
  236. traverse(element.list);
  237. } else if (element.type === "table") {
  238. // 表格布局
  239. element.trs.forEach(item => {
  240. item.tds.forEach(val => {
  241. traverse(val.list);
  242. });
  243. });
  244. }
  245. });
  246. };
  247. traverse(props.value.list);
  248. };
  249. const hide = (field) => {
  250. const model = getFieldByModel(field)
  251. if (!model) {
  252. console.log('未找到field:' + field)
  253. } else {
  254. if (!model.options) {
  255. model.options = {}
  256. }
  257. model.options.hidden = true;
  258. }
  259. };
  260. const show = (field) => {
  261. const model = getFieldByModel(field)
  262. if (!model) {
  263. console.log('未找到field:' + field)
  264. } else {
  265. if (!model.options) {
  266. model.options = {}
  267. }
  268. model.options.hidden = false;
  269. }
  270. };
  271. const disable = (field) => {
  272. const model = getFieldByModel(field)
  273. if (!model) {
  274. console.log('未找到field:' + field)
  275. } else {
  276. if (!model.options) {
  277. model.options = {}
  278. }
  279. model.options.disabled = true;
  280. }
  281. };
  282. const enable = (field) => {
  283. const model = getFieldByModel(field)
  284. if (!model) {
  285. console.log('未找到field:' + field)
  286. } else {
  287. if (!model.options) {
  288. model.options = {}
  289. }
  290. model.options.disabled = false;
  291. }
  292. };
  293. const handleChange = (value, key, record, tableInfo) => {
  294. if (!key) return;
  295. if (record && record.type == 'uploadFile') {
  296. formRef.value.validateFields([key])//手动校验,解决附件一直显示必填问题
  297. }
  298. emit("change", value, key, record, tableInfo);
  299. };
  300. const setFormData = (data) => {
  301. Object.keys(data).forEach((key) => {
  302. formData[key] = data[key];
  303. });
  304. };
  305. const getFieldByModel = (model) => {
  306. return findObjectByModel(props.value, model);
  307. };
  308. const findObjectByModel = (data, model) => {
  309. if (data.model === model) {
  310. return data;
  311. }
  312. if (data.list && Array.isArray(data.list)) {
  313. for (const item of data.list) {
  314. const result = findObjectByModel(item, model);
  315. if (result) {
  316. return result;
  317. }
  318. }
  319. }
  320. if (data.columns && Array.isArray(data.columns)) {
  321. for (const column of data.columns) {
  322. for (const item of column.list) {
  323. const result = findObjectByModel(item, model);
  324. if (result) {
  325. return result;
  326. }
  327. }
  328. }
  329. }
  330. if (data.type === 'batch' && data.list && Array.isArray(data.list)) {
  331. for (const item of data.list) {
  332. const result = findObjectByModel(item, model);
  333. if (result) {
  334. return result;
  335. }
  336. }
  337. }
  338. // If no match is found, return null
  339. return null;
  340. };
  341. import cloneDeep from 'lodash.clonedeep';
  342. onMounted(() => {
  343. nextTick(() => {
  344. let executed = false;
  345. let count = 0; // 添加计数器
  346. const maxCount = 50; // 设置最大执行次数
  347. const intervalId = setInterval(() => {
  348. if (executed || count > maxCount) {
  349. clearInterval(intervalId);
  350. } else {
  351. if (props.dynamicData && props.value && Object.keys(props.dynamicData).length > 0 && Object.keys(props.value).length > 0) {
  352. executed = true
  353. const LinkFields = findObjectsByOption(props.value, 'linkage', '1')
  354. if (LinkFields.length > 0) {
  355. LinkFields.forEach(function (field) {
  356. const linkMap = {}
  357. linkMap.linkageField = field.options.linkageField
  358. const toField = getFieldByModel(field.options.linkageField)
  359. const key = toField?.options?.dynamicKey
  360. toField.options.dynamicKey = key + toField.model
  361. props.dynamicData[key + toField.model] = cloneDeep(props.dynamicData[key])
  362. linkMap.linkedKey = toField.options.dynamicKey
  363. linkMap.initialValue = props.dynamicData[linkMap.linkedKey]//列表的原始信息
  364. sessionStorage.setItem('_linkMap_' + field.model, JSON.stringify(linkMap))
  365. props.dynamicData[linkMap.linkedKey] = linkMap.initialValue
  366. .filter(f => f.parentValue == formData[field.model])
  367. });
  368. }
  369. }
  370. }
  371. count++;
  372. }, 100);
  373. });
  374. });
  375. let formulaFields = reactive(null);
  376. watch(() => {
  377. return {...formData}
  378. }, async (newFormData, oldFormData) => {
  379. //首次启动,获取所有有公式的字段
  380. if (formulaFields == null) {
  381. formulaFields = findObjectsByOption(props.value, 'formula')
  382. } else { //增加else避免首次赋值就更新数据
  383. //如果有公式的字段存在
  384. if (formulaFields.length > 0) {
  385. Object.keys(newFormData).forEach(key => {
  386. //(typeof newFormData[key] == 'number' || typeof newFormData[key] == 'string')这句限制了,仅对主表有效。子表建议还是用子表的全局值改变事件
  387. if ((typeof newFormData[key] == 'number' || typeof newFormData[key] == 'string') && newFormData[key] !== oldFormData[key]) {
  388. //如果改变的是一个数字类型
  389. for (let index in formulaFields) {
  390. const formula = formulaFields[index]['options']['formula']
  391. if (formula.indexOf('{' + key + '}') >= 0) {
  392. const field = formulaFields[index]['model']
  393. let resultStr = formula.replace(/{(\w+)}/g, (match, key) => {
  394. return newFormData[key];
  395. });
  396. resultStr = resultStr.replace(/undefined/g, "0")
  397. const result = eval(resultStr)
  398. if (!isNaN(result)) {
  399. const data = {}
  400. data[field] = result
  401. setData(data)
  402. }
  403. }
  404. }
  405. }
  406. });
  407. }
  408. }
  409. }, {deep: true});
  410. //通过form中的Option里面的内容,找到对应的字段
  411. const findObjectsByOption = (json, option, value) => {
  412. let results = [];
  413. function recurse(items) {
  414. items?.forEach(item => {
  415. // 如果当前项是一个对象,并且包含 linkageField 属性,则将其添加到结果中
  416. if (typeof item === 'object' && item !== null && item.options != undefined && typeof item.options === 'object' && item.options !== null
  417. && item.options.hasOwnProperty(option) && item.options[option] != null && item.options[option] != '') {
  418. if (results.find(f => f.model == item.model) == undefined) {
  419. if (value != undefined) {
  420. if (item.options[option] == value) {
  421. results.push(item);
  422. }
  423. } else {
  424. results.push(item);
  425. }
  426. }
  427. }
  428. // 如果当前项包含 list 属性,递归地搜索该属性
  429. if (Array.isArray(item.list)) {
  430. recurse(item.list);
  431. }
  432. // 如果当前项是对象数组,递归地搜索每个对象
  433. if (Array.isArray(item) && typeof item[0] === 'object') {
  434. item.forEach(subItem => {
  435. if (typeof subItem === 'object' && subItem !== null) {
  436. recurse([subItem]);
  437. }
  438. });
  439. }
  440. // 如果当前项是对象,递归地搜索其子属性
  441. if (typeof item === 'object' && item !== null) {
  442. Object.values(item).forEach(subItem => {
  443. if (typeof subItem === 'object' && subItem !== null) {
  444. recurse([subItem]);
  445. }
  446. });
  447. }
  448. });
  449. }
  450. recurse(json.list); // 从最外层的 list 开始递归
  451. return results;
  452. }
  453. // 暴露给模板的响应式数据和方法
  454. const exposed = {
  455. handleSubmit,
  456. reset,
  457. getData,
  458. setData,
  459. hide,
  460. show,
  461. disable,
  462. enable,
  463. handleChange,
  464. formData,
  465. getFieldByModel
  466. };
  467. defineExpose(exposed);
  468. </script>