华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

631 lines
26 KiB

2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
  1. <template>
  2. <div>
  3. <LineLabel v-if="label" :label="label" />
  4. <div v-for="(item, index) in formConfig" :key="index">
  5. <template v-if="item.type === 'cardItem'">
  6. <div class="grid-container">
  7. <div v-for="(sItem, key) in item.config" class="form-item"
  8. :class="sItem.span == 1 ? 'full-row' : ''" :key="key">
  9. <template v-if="sItem.type === 'input'">
  10. <div class="form-title">{{ sItem.label }}</div>
  11. <HandleFormItem @blur="onBlur(key, $event)" :item="sItem" v-model="formFields[key]"
  12. @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false"
  13. :orange-bg="orangeBgFields[key]" />
  14. </template>
  15. <template v-else-if="sItem.type === 'inputNumber'">
  16. <div class="form-title">{{ sItem.label }}</div>
  17. <HandleFormItem type="inputNumber" @blur="onBlur(key, $event)" :item="sItem"
  18. @input="onInputNumberChange(key, $event)" v-model="formFields[key]"
  19. @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false"
  20. :orange-bg="orangeBgFields[key]" />
  21. </template>
  22. </div>
  23. </div>
  24. </template>
  25. <template v-else-if="item.type === 'conditionItem'">
  26. <div class="form-item ">
  27. <div class="form-title fs-16" v-if="item.label">{{ item.label }}</div>
  28. <div v-for="(sItem, key) in item.config" class="c-Item grid-container">
  29. <div class="p-r-20">
  30. <div class="form-title">{{ sItem.label }}</div>
  31. <div class="flex flex1">
  32. <HandleFormItem type="select" :item="sItem" v-model="formFields[key]"
  33. @copy="onCopy(sItem, key)" @change="onSelectChange(key, $event)"
  34. :error="errors[key]" @update:error="errors[key] = false"
  35. :orange-bg="orangeBgFields[key]" />
  36. </div>
  37. </div>
  38. <div class="p-l-20">
  39. <div v-show="isShowOther(formFields[key])">
  40. <div class="form-title">其他</div>
  41. <div class="flex flex1">
  42. <HandleFormItem @blur="onBlur(key, $event)" :item="getOtherItem(sItem)"
  43. v-model="formFields[sItem.otherCode]" @copy="onCopy(sItem, key)"
  44. :error="errors[sItem.otherCode]"
  45. @update:error="errors[sItem.otherCode] = false" />
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. </div>
  51. </template>
  52. <template v-else-if="item.type === 'cellItem'">
  53. <div class="form-item ">
  54. <div class="form-title fs-16" v-if="item.label">{{ item.label }}</div>
  55. <div class="grid-container gap2">
  56. <div v-for="(sItem, key) in item.config" class="c-Item" :class="getSpanClass(sItem)" :key="key">
  57. <div class="form-title" v-if="sItem.label">{{ sItem.label }}</div>
  58. <div v-if="sItem.type === 'dateTime'" class="flex1">
  59. <HandleFormItem type="dateTime" :item="sItem" v-model="formFields[key]"
  60. @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false"
  61. :orange-bg="orangeBgFields[key]" />
  62. </div>
  63. <div v-else-if="sItem.type === 'select'">
  64. <HandleFormItem type="select" :item="sItem" v-model="formFields[key]"
  65. @copy="onCopy(sItem, key)" @change="onSelectChange(key, $event)"
  66. :error="errors[key]" @update:error="errors[key] = false"
  67. :orange-bg="orangeBgFields[key]" />
  68. </div>
  69. <div v-else-if="sItem.type === 'input'">
  70. <HandleFormItem @blur="onBlur(key, $event)" :item="sItem" v-model="formFields[key]"
  71. @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false"
  72. :orange-bg="orangeBgFields[key]" />
  73. </div>
  74. <div v-else-if="sItem.type === 'textarea'">
  75. <HandleFormItem @blur="onBlur(key, $event)" type="textarea" :item="sItem"
  76. v-model="formFields[key]" @copy="onCopy(sItem, key)" :error="errors[key]"
  77. @update:error="errors[key] = false" :orange-bg="orangeBgFields[key]" />
  78. </div>
  79. <div v-else-if="sItem.type === 'clickable'" class="flex1">
  80. <HandleFormItem type="clickable" @clickable="handleClickable(sItem, $event)"
  81. :item="sItem" :value="formFields[key]" />
  82. </div>
  83. </div>
  84. </div>
  85. </div>
  86. </template>
  87. <template v-else-if="item.type === 'step'">
  88. <div class="grid-container gap2">
  89. <div v-for="(sItem, key) in item.config" class="c-Item flex item-center"
  90. :class="getSpanClass(sItem)" :key="key">
  91. <div class="step-form-title" v-if="sItem.label">{{ sItem.label }}</div>
  92. <div v-if="sItem.type === 'dateTime'" class="flex1">
  93. <HandleFormItem type="dateTime" :item="sItem" v-model="formFields[key]"
  94. @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false"
  95. :orange-bg="orangeBgFields[key]" />
  96. </div>
  97. <div v-else-if="sItem.type === 'select'" class="flex flex1">
  98. <HandleFormItem type="select" :item="sItem" style="width: auto;flex:1"
  99. v-model="formFields[key]" @copy="onCopy(sItem, key)"
  100. @change="onSelectChange(key, $event)" :error="errors[key]"
  101. @update:error="errors[key] = false" :orange-bg="orangeBgFields[key]" />
  102. <div v-show="isShowOther(formFields[key])" class="flex flex1">
  103. <div class="other-title">其他</div>
  104. <div class="flex">
  105. <HandleFormItem @blur="onBlur(key, $event)" :item="getOtherItem(sItem)"
  106. v-model="formFields[sItem.otherCode]" @copy="onCopy(sItem, key)"
  107. :error="errors[sItem.otherCode]" @update:error="errors[sItem.otherCode] = false"
  108. :orange-bg="orangeBgFields[sItem.otherCode]" />
  109. </div>
  110. </div>
  111. </div>
  112. <div v-else-if="sItem.type === 'input'" class="flex flex1">
  113. <HandleFormItem @blur="onBlur(key, $event)" class="flex1" :item="sItem"
  114. v-model="formFields[key]" @copy="onCopy(sItem, key)" :error="errors[key]"
  115. @update:error="errors[key] = false" :orange-bg="orangeBgFields[key]" />
  116. <HandleFormItem v-if="sItem.subType === 'select'" type="select" :item="getSubItem(sItem)"
  117. v-model="formFields[sItem.subKey]" @copy="onCopy(sItem, key)"
  118. @change="onSelectChange(sItem.subKey, $event)" :error="errors[sItem.subKey]"
  119. @update:error="errors[sItem.subKey] = false"
  120. :orange-bg="orangeBgFields[sItem.subKey]" />
  121. <div v-else-if="sItem.subType === 'span'">{{ formFields[sItem.subKey] }}</div>
  122. <HandleFormItem v-else-if="sItem.subType === 'clickable'" type="clickable"
  123. @clickable="handleClickable(sItem, $event)" :item="getClickableItem(sItem)"
  124. :value="formFields[sItem.subKey]" />
  125. <div v-show="isShowOther(formFields[sItem.subKey])" class="flex flex1">
  126. <div class="other-title">其他</div>
  127. <div class="flex">
  128. <HandleFormItem @blur="onBlur(key, $event)" :item="getOtherItem(sItem)"
  129. v-model="formFields[sItem.otherCode]" @copy="onCopy(sItem, key)"
  130. :error="errors[sItem.otherCode]" @update:error="errors[sItem.otherCode] = false"
  131. :orange-bg="orangeBgFields[sItem.otherCode]" />
  132. </div>
  133. </div>
  134. <!-- <div class="clickable" :class="getFillType(sItem.subFillType)" v-else-if = "sItem.subType ==='clickable'" @click="handleClickable(sItem,$event)">
  135. <span v-if="formFields[sItem.subKey]">{{ formFields[sItem.subKey] }}</span>
  136. <span v-else class="default-placeholder-text">请选择</span>
  137. </div> -->
  138. </div>
  139. <div v-else-if="sItem.type === 'inputNumber'" class="flex flex1">
  140. <HandleFormItem type="inputNumber" @blur="onBlur(key, $event)" class="flex1" :item="sItem"
  141. @input="onInputNumberChange(key, $event)" :value="formFields[key]"
  142. @copy="onCopy(sItem, key)" :error="errors[key]" @update:error="errors[key] = false"
  143. :orange-bg="orangeBgFields[key]" />
  144. <HandleFormItem v-if="sItem.subType === 'select'" type="select" :item="getSubItem(sItem)"
  145. v-model="formFields[sItem.subKey]" @copy="onCopy(sItem, key)"
  146. @change="onSelectChange(sItem.subKey, $event)" :error="errors[sItem.subKey]"
  147. @update:error="errors[sItem.subKey] = false"
  148. :orange-bg="orangeBgFields[sItem.subKey]" />
  149. <div v-else-if="sItem.subType === 'span'">{{ formFields[sItem.subKey] }}</div>
  150. <HandleFormItem v-else-if="sItem.subType === 'clickable'"
  151. @clickable="handleClickable(sItem, $event)" type="clickable"
  152. :item="getClickableItem(sItem)" :value="formFields[sItem.subKey]" />
  153. <!-- <div class="clickable" :class="getFillType(sItem.subFillType)" v-else-if = "sItem.subType ==='clickable'" @click="handleClickable(sItem,$event)">
  154. <span v-if="formFields[sItem.subKey]">{{ formFields[sItem.subKey] }}</span>
  155. <span v-else class="default-placeholder-text">请选择</span>
  156. </div> -->
  157. </div>
  158. <div v-else-if="sItem.type === 'clickable'" class="flex flex1">
  159. <HandleFormItem type="clickable" @clickable="handleClickable(sItem, $event)"
  160. :error="errors[key]" :item="sItem" :value="formFields[key]" />
  161. </div>
  162. </div>
  163. </div>
  164. </template>
  165. </div>
  166. </div>
  167. </template>
  168. <script>
  169. import HandleFormItem from "./HandleFormItem.vue";
  170. import LineLabel from "./LineLabel.vue";
  171. export default {
  172. components: {
  173. HandleFormItem,
  174. LineLabel
  175. },
  176. props: {
  177. label: {//当前表单的标题
  178. type: String,
  179. default: "",
  180. },
  181. formConfig: {
  182. type: Array,
  183. value: () => [],
  184. },
  185. formData: {
  186. type: Object,
  187. value: () => ({})
  188. }
  189. },
  190. data() {
  191. return {
  192. formFields: {},//表单绑定字段
  193. allFieldsConfig: {},//包含config的所有字段,主要用于校验表单是否填写
  194. errors: {},//存储表单错误信息,用于标红提示
  195. orangeBgFields: {},// 存储需要橙色背景的字段
  196. };
  197. },
  198. watch: {
  199. formData: {
  200. immediate: true,
  201. deep: true, // 深度监听,以便检测嵌套对象变化
  202. handler(v) {
  203. if (v) {
  204. this.handleFormField();
  205. }
  206. }
  207. },
  208. formConfig: {
  209. immediate: true,
  210. deep: true, // 深度监听,以便检测嵌套对象变化
  211. handler(v) {
  212. this.handleFormField();
  213. }
  214. }
  215. },
  216. mounted() {
  217. this.handleFormField();
  218. },
  219. unmounted() {
  220. console.log("unmounted")
  221. this.formFields = {};//清空当前填写的数据
  222. },
  223. methods: {
  224. getFillType(type) {
  225. const typeObj = {
  226. actFill: "orange-border",//实际填写的边框颜色
  227. green: "green-border",
  228. preFill: "blue-border",//预填写的边框颜色
  229. }
  230. return typeObj[type] || ""
  231. },
  232. onInputNumberChange(key, val) {
  233. this.formFields[key] = val;
  234. // 清除该表单项的错误状态
  235. if (this.errors[key]) {
  236. this.$set(this.errors, key, false);
  237. }
  238. },
  239. updateFormData(key, value) {
  240. this.formFields[key] = value;
  241. // 清除该表单项的错误状态
  242. if (this.errors[key]) {
  243. this.$set(this.errors, key, false);
  244. }
  245. },
  246. batchUpdateFormData(data) {
  247. Object.keys(data).forEach(key => {
  248. this.formFields[key] = data[key];
  249. // 清除该表单项的错误状态
  250. if (this.errors[key]) {
  251. this.$set(this.errors, key, false);
  252. }
  253. })
  254. },
  255. handleClickable(sItem, event) {
  256. console.log("clickable", sItem)
  257. if (this.$store.state.template.templateStatus !== 'actFill') {
  258. return
  259. }
  260. this.$emit("clickable", sItem)
  261. },
  262. //根据span判断一行显示几列
  263. getSpanClass(sItem) {
  264. const spanArr = ["full-row", "", "three-row"]
  265. if (sItem.span) {
  266. return spanArr[sItem.span - 1]
  267. }
  268. return ""
  269. },
  270. //获取其他下拉框的配置
  271. getOtherItem(sItem) {
  272. return {
  273. label: "其他",
  274. fillType: sItem.fillType,
  275. maxlength: sItem.otherMaxlength || 50,
  276. }
  277. },
  278. getClickableItem(sItem) {
  279. return {
  280. label: "",
  281. type: "clickable",
  282. fillType: sItem.subFillType || sItem.fillType,
  283. }
  284. },
  285. getSubItem(sItem) {
  286. return {
  287. label: "",
  288. options: sItem.subOptions || [],
  289. fillType: sItem.subFillType || sItem.fillType,
  290. }
  291. },
  292. isShowOther(v = []) {
  293. // 确保v是数组类型,以避免类型错误
  294. const arr = Array.isArray(v) ? v : [v];
  295. //和凡哥商量,只要value为负数都显示其他
  296. return arr.some(item => item < 0);
  297. },
  298. // 根据formConfig回填form表单数据
  299. handleFormField() {
  300. const result = {};
  301. let config = {};
  302. const { formConfig, formData, formFields } = this;
  303. // 遍历配置
  304. formConfig.forEach((item) => {
  305. if (item.config) {
  306. // 合并配置项
  307. config = { ...config, ...item.config }
  308. // 处理每个配置项
  309. Object.keys(item.config).forEach(key => {
  310. const currentConfig = item.config[key];
  311. let value = formData[key];
  312. // 如果formFields中已经有值,保持原值(用户输入或之前设置的值)
  313. if (formFields[key] !== null &&
  314. formFields[key] !== undefined &&
  315. formFields[key] !== '' &&
  316. typeof formFields[key] !== 'object'
  317. ) {
  318. // 保留原值,不使用formData中的值
  319. result[key] = formFields[key];
  320. } else {
  321. // 使用formData中的值
  322. result[key] = value;
  323. }
  324. // 处理特殊字段 - "其他"字段
  325. if (currentConfig.otherCode) {
  326. const { otherCode } = currentConfig;
  327. result[otherCode] = formData[otherCode] || '';
  328. config[otherCode] = { label: "其他", parentKey: key, type: "input", fillType: currentConfig.fillType }
  329. }
  330. if (currentConfig.subKey) {
  331. const { subKey } = currentConfig;
  332. result[subKey] = formData[subKey] || '';
  333. config[subKey] = { label: currentConfig.label, subKey, type: currentConfig.subType, fillType: currentConfig.subFillType || currentConfig.fillType }
  334. }
  335. });
  336. // 处理可能存在的直接otherCode字段
  337. if (item.config?.otherCode) {
  338. config[item.config?.otherCode] = item.config?.otherCode;
  339. }
  340. }
  341. });
  342. // 更新表单字段
  343. this.formFields = result;
  344. this.allFieldsConfig = config;
  345. },
  346. //判断是否禁用
  347. getDisabled() {
  348. const { item } = this;
  349. const { fillType } = item;
  350. if (item.hasOwnProperty("disabled")) {
  351. return item.disabled
  352. } else {
  353. const { templateStatus } = this.$store.state.template;
  354. if (fillType === "actFill") {//当模板状态是实际填写时,只有当fillType是actFill时才能填写
  355. return templateStatus !== "actFill"
  356. } else if (fillType === "preFill") {//当模板状态是预填写时,只有当fillType是preFill才能填写
  357. return templateStatus !== "preFill"
  358. } else {
  359. return true
  360. }
  361. }
  362. },
  363. // 表单数据校验
  364. validateFormData() {
  365. const { formFields, allFieldsConfig } = this;
  366. const { templateStatus } = this.$store.state.template;
  367. const errors = [];
  368. // 清空之前的错误状态
  369. this.errors = {};
  370. for (const key in allFieldsConfig) {
  371. const o = allFieldsConfig[key];
  372. if (o.otherCode) {//
  373. if (o.type === "select") {
  374. const isSelectedOther = this.isShowOther(formFields[key]);
  375. if (!isSelectedOther) {//如果其他选项没有被选择,清空其他字段
  376. formFields[o.otherCode] = "";
  377. }
  378. }else if(o.subType === "select"){
  379. const isSelectedOther = this.isShowOther(formFields[o.subKey]);
  380. if (!isSelectedOther) {//如果其他选项没有被选择,清空其他字段
  381. formFields[o.otherCode] = "";
  382. }
  383. }
  384. }
  385. if (this.isValueEmpty(formFields[key])) {
  386. // 其他字段需要判断是否显示再校验
  387. if (o.label === "其他" && !this.isShowOther(formFields[o.parentKey])) {
  388. continue
  389. }
  390. //span的字段不校验
  391. if (o.type === "span") {
  392. continue
  393. }
  394. if (o.fillType == templateStatus && !o.disabled) {
  395. let prefix = "";
  396. if (o.type === "input" || o.type === "inputNumber" || o.type === "textarea") {
  397. prefix = "填写";
  398. } else {
  399. prefix = "选择";
  400. }
  401. const errorItem = {
  402. field: key,
  403. label: o.label,
  404. error: `${prefix}${o.label}`
  405. };
  406. errors.push(errorItem);
  407. this.$set(this.errors, key, true);
  408. }
  409. }
  410. }
  411. return {
  412. valid: errors.length === 0,
  413. errors: errors
  414. };
  415. },
  416. // 判断值是否为空
  417. isValueEmpty(value) {
  418. if (value === null || value === undefined || value === '') {
  419. return true;
  420. }
  421. if (typeof value === 'string' && value.trim() === '') {
  422. return true;
  423. }
  424. if (Array.isArray(value) && value.length === 0) {
  425. return true;
  426. }
  427. return false;
  428. },
  429. getFormData() {
  430. // 数据校验
  431. const validateResult = this.validateFormData();
  432. return new Promise((resolve, reject) => {
  433. if (validateResult.valid) {
  434. resolve(this.formFields);
  435. } else {
  436. // this.$message.error("表单内容未填完,请填写后再提交");
  437. reject(validateResult.errors[0].error);
  438. }
  439. });
  440. },
  441. getFormDataByKey(key) {
  442. return this.formFields[key];
  443. },
  444. onBlur(key, val) {
  445. // compareTo 功能:当fillType==="actFill"时,判断当前值是否与compareTo字段的值一样,如果不一样则将当前input框的背景色标记成橙色
  446. const currentFieldConfig = this.allFieldsConfig[key];
  447. if (currentFieldConfig && currentFieldConfig.fillType === "actFill" && currentFieldConfig.compareTo) {
  448. const compareToKey = currentFieldConfig.compareTo;
  449. const compareToValue = this.formFields[compareToKey];
  450. // 比较当前值和compareTo值,如果不相等则设置橙色背景
  451. if (val !== compareToValue) {
  452. this.$set(this.orangeBgFields, key, true);
  453. } else {
  454. // 相等则移除橙色背景
  455. this.$set(this.orangeBgFields, key, false);
  456. }
  457. }
  458. this.$emit("blur", { key, value: val, ...this.formFields });
  459. },
  460. onSelectChange(key, val) {
  461. // 获取对应的配置
  462. const currentConfig = this.allFieldsConfig[key];
  463. // // 确保多选下拉框的值是数组类型
  464. // if (currentConfig && currentConfig.multiple) {
  465. // // 多选情况,确保值为数组类型
  466. // this.formFields[key] = Array.isArray(val) ? val : (val ? [val] : []);
  467. // } else {
  468. // 单选情况
  469. this.formFields[key] = val;
  470. // }
  471. this.$emit("select", { key, value: val });
  472. // 清除该表单项的错误状态
  473. if (this.errors[key]) {
  474. this.$set(this.errors, key, false);
  475. }
  476. },
  477. //复制
  478. onCopy(config, key) {
  479. const { formFields } = this;
  480. if (config.copyFrom) {
  481. formFields[key] = formFields[config.copyFrom]
  482. }
  483. },
  484. },
  485. }
  486. </script>
  487. <style lang="scss">
  488. .grid-container {
  489. display: grid;
  490. grid-template-columns: repeat(2, 1fr);
  491. /* 默认2列 */
  492. gap: 0 20px;
  493. }
  494. .gap2 {
  495. gap: 0 64px;
  496. }
  497. .w-100 {
  498. width: 100%;
  499. }
  500. .form-item {
  501. background: #fff;
  502. padding: 20px;
  503. border-radius: 8px;
  504. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
  505. margin-top: 20px;
  506. padding: 20px;
  507. border-radius: 5px 5px;
  508. }
  509. /* 或者使用 span 语法 */
  510. .full-row {
  511. grid-column: span 2;
  512. }
  513. .three-row {
  514. grid-column: span 3;
  515. }
  516. .c-Item {
  517. &:not(:last-child) {
  518. margin-bottom: 16px;
  519. }
  520. }
  521. .eo {
  522. &:nth-child(even) {
  523. padding-left: 20px;
  524. }
  525. &:nth-child(odd) {
  526. padding-right: 20px;
  527. }
  528. }
  529. .default-placeholder-text {
  530. color: #C0C4CC;
  531. }
  532. .form-title {
  533. margin-bottom: 12px;
  534. font-size: 14px;
  535. font-weight: normal;
  536. color: #606266;
  537. }
  538. .step-form-title {
  539. font-size: 14px;
  540. font-weight: normal;
  541. color: #606266;
  542. width: 150px;
  543. text-align: right;
  544. padding-right: 10px;
  545. }
  546. .p-r-20 {
  547. padding-right: 20px;
  548. }
  549. .p-l-20 {
  550. padding-left: 20px;
  551. }
  552. .fs-16 {
  553. font-size: 0.96rem;
  554. font-weight: bold;
  555. color: #464647
  556. }
  557. .flex1 {
  558. flex: 1;
  559. }
  560. .flex {
  561. display: flex;
  562. }
  563. .other-title {
  564. width: 50px;
  565. text-align: right;
  566. margin: 0 10px;
  567. font-size: 14px;
  568. font-weight: normal;
  569. color: #606266;
  570. }
  571. .mr-24 {
  572. margin-right: 24px;
  573. }
  574. .sub-select {
  575. width: 100px;
  576. margin-left: 10px;
  577. }
  578. .orange-border {
  579. border-color: #f9c588;
  580. }
  581. .green-border {
  582. border-color: green;
  583. }
  584. .blue-border {
  585. border-color: #4ea2ff;
  586. }
  587. </style>