my_form.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <template>
  2. <view>
  3. <block v-for="(item, index) in list " :key="item.key">
  4. <!-- 文本输入 -->
  5. <view class="input-box" v-if="item.type == 'text'" :style="{ marginTop: tovw(item.marginTop || 0) }"
  6. @click="focusLabel = item.label">
  7. <view class="box" :class="item.unBorBot ? '' : 'borBot'">
  8. <view class="label">
  9. <text class="must" v-if="item.isMust">*</text>
  10. {{ item.label }}:
  11. </view>
  12. <view class="content-box">
  13. <view class="content">
  14. <input v-if="item.inputmode == 'number'" type="number"
  15. placeholder-style="color: #BBBBBB;font-family: Source Han Sans SC, Source Han Sans SC;"
  16. :focus="focusLabel == item.label" :placeholder="item.placeholder || '请填写' + item.label"
  17. :value="item.value" :style="{ width: item.value ? '220px' : '240px' }"
  18. @input="onInput($event, index)" :maxlength="item.maxlength || '499'" confirm-type="done" />
  19. <textarea v-else
  20. placeholder-style="color: #BBBBBB;font-family: Source Han Sans SC, Source Han Sans SC;"
  21. auto-height type="text" :focus="focusLabel == item.label"
  22. :placeholder="item.placeholder || '请填写' + item.label" :value="item.value"
  23. :style="{ width: item.value ? '220px' : '240px' }" @input="onInput($event, index)"
  24. :maxlength="item.maxlength || '499'" confirm-type="done" />
  25. <icon v-if="item.value" class="icon" type="clear" size="3.733vw" @click="onClearInput(index)" />
  26. </view>
  27. <view v-if="item.errText" class="err-text">
  28. <icon class="icon" color="#E3041F" type="clear" size="2.733vw" />
  29. {{ item.errText }}
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. <!-- 文本域 -->
  35. <view class="textarea-box" v-else-if="item.type == 'textarea'">
  36. <textarea class="textarea" :class="item.unBorBot ? '' : 'borBot'"
  37. placeholder-style="color: #BBBBBB;font-family: Source Han Sans SC, Source Han Sans SC;" type="text"
  38. :placeholder="item.placeholder || '请填写' + item.label" :value="item.value"
  39. @input="onInput($event, index)" :maxlength="item.maxlength || '499'" confirm-type="done" />
  40. </view>
  41. <!-- 自定义选项分类 -->
  42. <view class="custom-class-box" v-else-if="item.type == 'customClass'"
  43. :style="{ marginTop: tovw(item.marginTop || 0) }">
  44. <view class="head">
  45. <view class="label">
  46. <text class="must" v-if="item.isMust">*</text>
  47. {{ item.label }}
  48. </view>
  49. <view class="state">
  50. {{ item.isMultipleChoice ? '可多选' : '仅单选' }}
  51. </view>
  52. </view>
  53. <view class="options">
  54. <view class="option"
  55. :class="item.isMultipleChoice ? (item.value.includes(option.value) ? 'active' : '') : (item.value == option.value ? 'active' : '')"
  56. v-for=" option in item.list " :key="option.value" hover-class="navigator-hover"
  57. @click="changOptions(option.value, index)">
  58. {{ option.remarks }}
  59. </view>
  60. </view>
  61. </view>
  62. <!-- 选择所在地区 -->
  63. <picker class="region" @change="selectRegion($event, index)" mode='region' :disabled="item.disabled"
  64. :value="item.value" v-else-if="item.type == 'region'"
  65. :style="{ marginTop: tovw(item.marginTop || 0), opacity: item.disabled ? 0.7 : 1 }">
  66. <view class="box" :class="item.unBorBot ? '' : 'borBot'">
  67. <view class="label">
  68. <text class="must" v-if="item.isMust">*</text>
  69. {{ item.label }}:
  70. </view>
  71. <view class="content-box">
  72. <view class="value" v-if="item.value.length">
  73. {{ item.value.join(",") }}
  74. </view>
  75. <view v-else class="placeholder" hover-class="none">
  76. {{ item.placeholder || '请选择' + item.label }}
  77. </view>
  78. <view v-if="!item.disabled" class="iconfont icon-a-wodetiaozhuan" />
  79. </view>
  80. </view>
  81. </picker>
  82. <!-- 上传附件 -->
  83. <view class="custom-class-box" v-else-if="item.type == 'upload'"
  84. :style="{ marginTop: tovw(item.marginTop || 0) }">
  85. <view class="head">
  86. <view class="label">
  87. <text class="must" v-if="item.isMust">*</text>
  88. {{ item.label }}
  89. </view>
  90. <view class="state">
  91. {{ item.placeholder }}
  92. </view>
  93. </view>
  94. <view class="content">
  95. <view class="file-box" v-for="file in item.value" :key="file.attachmentid">
  96. <image class="image" v-if="file.fileType == 'image'" :src="file.url" mode="aspectFill"
  97. lazy-load="true" @click="previewImg(file)" />
  98. <video v-else-if="file.fileType == 'video'" class="video" :poster="file.subfiles[0].url"
  99. :src="file.url" />
  100. <image class="delete" @click.stop="deleteFile(file, index)"
  101. src="https://yossys06593.obs.cn-east-3.myhuaweicloud.com:443/202404241713944430197B47af9b2f.png"
  102. mode="widthFix" />
  103. </view>
  104. <my-upload v-if="item.allowUpload" :showLoading="false" :accept="item.accept"
  105. @uploadCallback="uploadCallback($event, index)" @onLoading="onUploadLoading($event, index)">
  106. <view class="upload-box" hover-class="navigator-hover">
  107. <u-loading-icon v-if="item.loading" />
  108. <text v-else class="iconfont icon-xiazai" />
  109. <text style="margin-left: 5px;">上传</text>
  110. </view>
  111. </my-upload>
  112. </view>
  113. </view>
  114. <!-- 开关 -->
  115. <view class="region" v-else-if="item.type == 'switch'">
  116. <view class="box" :class="item.unBorBot ? '' : 'borBot'">
  117. <view class="label">
  118. <text class="must" v-if="item.isMust">*</text>
  119. {{ item.label }}:
  120. </view>
  121. <view class="content-box">
  122. <view />
  123. <u-switch activeColor="#70D95D" v-model="item.value" :disabled="item.disabled"
  124. @change="switchChange($event, index)" />
  125. </view>
  126. </view>
  127. </view>
  128. </block>
  129. </view>
  130. </template>
  131. <script>
  132. import myUpload from "./my-upload.vue";
  133. import { formattedFiles, viewImage } from "../utils/settleFiles.js"
  134. export default {
  135. name: "my_form",
  136. components: { myUpload },
  137. props: {
  138. form: {
  139. type: Array,
  140. default: []
  141. },
  142. isUncomplete: {
  143. type: Function
  144. },
  145. onUploading: {
  146. type: Function
  147. }
  148. },
  149. data() {
  150. return {
  151. list: [],
  152. focusLabel: ""
  153. }
  154. },
  155. watch: {
  156. form: {
  157. handler: function (newVal) {
  158. if (newVal) {
  159. this.list = JSON.parse(JSON.stringify(newVal));
  160. setTimeout(() => {
  161. this.verify()
  162. }, 200);
  163. }
  164. },
  165. immediate: true,
  166. }
  167. },
  168. async created() {
  169. /* let list = [{
  170. key: "name",
  171. type: "text",
  172. label: "标题",
  173. isMust: true,//是否必填
  174. value: "",
  175. inputmode:"", //https://uniapp.dcloud.net.cn/component/input.html#type
  176. marginTop: 10,
  177. verify:[],
  178. }, {
  179. key: "Class",
  180. type: "customClass",
  181. label: "标题",
  182. isMust: false,//是否必填
  183. isMultipleChoice: true,//是否多选
  184. value: [],// 多选[] 单选 ""
  185. isMust: true,//是否必填
  186. list: await this.getCustomClass('picturespace'),
  187. marginTop: 10
  188. },{
  189. key: "attachmentids",
  190. type: "upload",
  191. label: "图片/视频",
  192. accept:"all",
  193. placeholder: "可上传多个视频或图片",
  194. ownertable: "temporary",
  195. ownerid: 999,
  196. usetype: 'default',
  197. allowUpload: true,
  198. allowDelete: true,
  199. value:[],
  200. marginTop: 10
  201. }, {
  202. key: "region",
  203. type: "region",
  204. label: "所在地区",
  205. isMust: true,//是否必填
  206. value: [],
  207. }, {
  208. key: "address",
  209. type: "textarea",
  210. label: "详细地址",
  211. isMust: true,//是否必填
  212. value: '',
  213. }, {
  214. key: "isdefault",
  215. type: "switch",
  216. label: "设为默认地址",
  217. isMust: false,//是否必填
  218. value: false,
  219. isNum: true,
  220. unBorBot: true
  221. }] */
  222. },
  223. methods: {
  224. onInput(e, index) {
  225. let item = this.list[index];
  226. item.errText = "";
  227. this.$set(this.list[index], 'value', e.detail.value)
  228. if (item.verify && item.verify.length && item.value != '') {
  229. let err = item.verify.find(r => !new RegExp(r.reg).test(item.value));
  230. if (err) this.$set(this.list[index], 'errText', err.errText)
  231. }
  232. this.verify()
  233. },
  234. onClearInput(index) {
  235. let item = this.list[index];
  236. item.errText = ''
  237. this.$set(this.list[index], 'value', '')
  238. this.verify()
  239. },
  240. switchChange(e, index) {
  241. this.$set(this.list[index], 'value', e)
  242. this.verify()
  243. },
  244. changOptions(value, index) {
  245. let item = this.list[index];
  246. if (item.isMultipleChoice) {
  247. let i = -1;
  248. try {
  249. i = item.value.findIndex(v => v == value)
  250. } catch (error) {
  251. }
  252. if (i == -1) {
  253. item.value.push(value)
  254. } else {
  255. item.value.splice(i, 1)
  256. }
  257. this.$set(this.list[index], 'value', item.value)
  258. } else {
  259. this.$set(this.list[index], 'value', value)
  260. }
  261. this.verify()
  262. },
  263. verify() {
  264. let list = this.list.filter(v => v.isMust);
  265. let Uncomplete = false;
  266. if (list.length) Uncomplete = list.some(v => {
  267. let res = false;
  268. if (v.type == 'customClass') {
  269. if (v.isMultipleChoice) {
  270. res = v.value.length == 0;
  271. } else {
  272. res = v.value == "";
  273. }
  274. } else if (v.type == 'upload') {
  275. res = v.value.length == 0;
  276. } else if (v.type == 'text') {
  277. res = v.value == "";
  278. if (v.errText) res = true;
  279. } else {
  280. res = v.value == "";
  281. }
  282. return res
  283. })
  284. if (!Uncomplete) Uncomplete = this.list.filter(v => !v.isMust).some(v => v.errText);
  285. this.$emit("isUncomplete", Uncomplete)
  286. },
  287. previewImg(item) {
  288. viewImage(item.url)
  289. },
  290. uploadCallback(attachmentids, index) {
  291. let item = this.list[index];
  292. this.$Http.basic({
  293. "classname": "system.attachment.Attachment",
  294. "method": "createFileLink",
  295. "content": {
  296. ownertable: item.ownertable,
  297. ownerid: item.ownerid,
  298. usetype: item.usetype,
  299. attachmentids
  300. }
  301. }).then(res => {
  302. console.log('绑定附件', res)
  303. if (this.cutoff(res.msg)) return;
  304. res.data = formattedFiles(res.data)
  305. item.value.push(res.data[0]);
  306. //临时文件
  307. if (res.data[0].ownertable == "temporary") try {
  308. item.temporarys.push(attachmentids[0])
  309. } catch (error) {
  310. item.temporarys = [attachmentids[0]]
  311. }
  312. this.$set(this.list[index], 'value', item.value)
  313. this.verify()
  314. })
  315. },
  316. deleteFiles() {
  317. this.list.forEach(v => {
  318. if (v.type == 'upload') {
  319. let linksids = v.value.filter(v => v.ownertable == "temporary").map(v => v.linksid)
  320. if (linksids.length) this.$Http.basic({
  321. "classname": "system.attachment.Attachment",
  322. "method": "deleteFileLink",
  323. "content": {
  324. linksids
  325. }
  326. }).then(res => {
  327. console.log("处理删除附件", res)
  328. if (this.cutoff(res.msg)) return;
  329. });
  330. }
  331. });
  332. },
  333. onUploadLoading(e, index) {
  334. this.$set(this.list[index], 'loading', e)
  335. this.$emit("onUploading", e)
  336. },
  337. selectRegion({ detail }, index) {
  338. this.$set(this.list[index], 'value', detail.value)
  339. this.verify()
  340. },
  341. deleteFile(flie, index) {
  342. let item = this.list[index];
  343. item.value = item.value.filter(v => v.attachmentid != flie.attachmentid)
  344. //临时文件
  345. if (flie.ownertable == "temporary") {
  346. item.temporarys = item.temporarys.filter(v => v != flie.attachmentid)
  347. this.$Http.basic({
  348. "classname": "system.attachment.Attachment",
  349. "method": "deleteFileLink",
  350. "content": {
  351. linksids: [flie.linksid]
  352. }
  353. }).then(res => {
  354. console.log("处理删除附件", res)
  355. if (this.cutoff(res.msg)) return;
  356. });
  357. } else {
  358. try {
  359. item.linksids.push(flie.linksid)
  360. } catch (error) {
  361. item.linksids = [flie.linksid]
  362. }
  363. }
  364. this.$set(this.list[index], 'value', item.value)
  365. this.verify()
  366. },
  367. submit() {
  368. return new Promise((resolve, reject) => {
  369. let res = {};
  370. this.list.forEach(v => {
  371. if (v.type == 'upload') {
  372. res.files = {
  373. temporarys: [],
  374. linksids: [],
  375. }
  376. try {
  377. res.files.temporarys = v.temporarys || []
  378. } catch (error) {
  379. }
  380. try {
  381. res.files.linksids = v.linksids || []
  382. } catch (error) {
  383. }
  384. } else if (v.type == 'region') {
  385. if (v.value.length) {
  386. res.province = v.value[0];
  387. res.city = v.value[1];
  388. res.county = v.value[2];
  389. } else {
  390. res.province = ''
  391. res.city = ''
  392. res.county = ''
  393. }
  394. } else if (v.type == 'switch') {
  395. if (v.isNum) {
  396. res[v.key] = v.value ? 1 : 0;
  397. } else {
  398. res[v.key] = v.value;
  399. }
  400. } else {
  401. try {
  402. res[v.key] = v.value.trim();
  403. } catch (error) {
  404. res[v.key] = v.value;
  405. }
  406. }
  407. })
  408. resolve(res)
  409. })
  410. }
  411. },
  412. }
  413. </script>
  414. <style lang="scss">
  415. .borBot {
  416. border-bottom: 1px #DDDDDD solid;
  417. }
  418. .custom-class-box {
  419. width: 100%;
  420. background: #fff;
  421. padding: 15px 0 15px 10px;
  422. box-sizing: border-box;
  423. .head {
  424. width: 355px;
  425. height: 20px;
  426. display: flex;
  427. justify-content: space-between;
  428. align-items: flex-end;
  429. .label {
  430. font-size: 14px;
  431. color: #333333;
  432. line-height: 20px;
  433. }
  434. .state {
  435. font-family: Source Han Sans SC, Source Han Sans SC;
  436. font-size: 12px;
  437. color: #999999;
  438. }
  439. }
  440. .options {
  441. display: flex;
  442. flex-wrap: wrap;
  443. .option {
  444. padding: 6px 10px;
  445. text-align: center;
  446. min-width: 81px;
  447. font-family: PingFang SC, PingFang SC;
  448. font-size: 14px;
  449. color: #333333;
  450. border-radius: 5px;
  451. background: #F2F2F2;
  452. margin-top: 10px;
  453. margin-right: 10px;
  454. box-sizing: border-box;
  455. }
  456. .active {
  457. background: #C30D23;
  458. color: #fff;
  459. }
  460. }
  461. .content {
  462. .file-box {
  463. position: relative;
  464. width: 355px;
  465. height: 240px;
  466. margin-top: 10px;
  467. .video,
  468. .image {
  469. width: 355px;
  470. height: 240px;
  471. border-radius: 5px;
  472. }
  473. .delete {
  474. position: absolute;
  475. width: 30px;
  476. top: 0;
  477. right: 0;
  478. z-index: 1;
  479. }
  480. }
  481. .upload-box {
  482. display: flex;
  483. justify-content: center;
  484. align-items: center;
  485. width: 355px;
  486. height: 45px;
  487. background: #FFFFFF;
  488. border-radius: 5px;
  489. border: 1px dashed #C30D23;
  490. font-family: Source Han Sans SC, Source Han Sans SC;
  491. font-size: 14px;
  492. color: #C30D23;
  493. margin-top: 10px;
  494. }
  495. }
  496. }
  497. .textarea-box {
  498. width: 100%;
  499. .textarea {
  500. width: 355px;
  501. height: 160px;
  502. padding: 15px 10px;
  503. box-sizing: border-box;
  504. margin: 0 auto;
  505. }
  506. }
  507. .input-box {
  508. width: 100vw;
  509. background: #fff;
  510. box-sizing: border-box;
  511. padding-left: 10px;
  512. .box {
  513. display: flex;
  514. width: 100%;
  515. min-height: 54.4px;
  516. padding: 15px 0;
  517. box-sizing: border-box;
  518. align-items: center;
  519. }
  520. .label {
  521. width: 100px;
  522. margin-right: 10px;
  523. line-height: 20px;
  524. font-family: Source Han Sans SC, Source Han Sans SC;
  525. font-size: 14px;
  526. color: #666666;
  527. flex-shrink: 0;
  528. .must {
  529. color: #E3041F;
  530. margin-right: 5px;
  531. }
  532. }
  533. .content-box {
  534. padding-right: 10px;
  535. .content {
  536. flex: 1;
  537. display: flex;
  538. align-items: center;
  539. box-sizing: border-box;
  540. .icon {
  541. padding: 5px;
  542. }
  543. }
  544. .err-text {
  545. font-size: 12px;
  546. color: #E3041F;
  547. margin-bottom: -12px;
  548. .icon {
  549. margin-right: 2px;
  550. }
  551. }
  552. }
  553. }
  554. .region {
  555. width: 100vw;
  556. background: #fff;
  557. box-sizing: border-box;
  558. padding: 15px 10px 0;
  559. .box {
  560. display: flex;
  561. padding-bottom: 15px;
  562. .label {
  563. width: 100px;
  564. margin-right: 10px;
  565. line-height: 20px;
  566. font-family: Source Han Sans SC, Source Han Sans SC;
  567. font-size: 14px;
  568. color: #666666;
  569. flex-shrink: 0;
  570. .must {
  571. color: #E3041F;
  572. margin-right: 5px;
  573. }
  574. }
  575. .content-box {
  576. flex: 1;
  577. display: flex;
  578. align-items: center;
  579. justify-content: space-between;
  580. height: 20px;
  581. line-height: 20px;
  582. .placeholder {
  583. font-family: Source Han Sans SC, Source Han Sans SC;
  584. font-size: 14px;
  585. color: #BBBBBB;
  586. }
  587. }
  588. }
  589. }
  590. </style>