My_form.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. <template>
  2. <view class="form">
  3. <view
  4. v-for="(item, index) in form"
  5. :key="item.key"
  6. :style="{ opacity: isReadOnly || item.disabled ? 0.9 : 1 }"
  7. >
  8. <view class="form-row">
  9. <view class="label">
  10. <text class="required" v-if="item.required">*</text>
  11. {{ item.label }}
  12. <view
  13. class="file"
  14. v-if="item.type == 'file' && !item.disabled && !isReadOnly"
  15. >
  16. <upload
  17. accept="image"
  18. v-if="uploadIsShow('image', item.fileType)"
  19. @uploadCallback="uploadCallback"
  20. >
  21. <view hover-class="navigator-hover">
  22. <image
  23. class="image"
  24. src="../static/file/image.png"
  25. mode="heightFix"
  26. />
  27. </view>
  28. </upload>
  29. <upload
  30. accept="video"
  31. v-if="uploadIsShow('video', item.fileType)"
  32. @uploadCallback="uploadCallback"
  33. >
  34. <view hover-class="navigator-hover">
  35. <image
  36. class="image"
  37. src="../static/file/video.png"
  38. mode="heightFix"
  39. />
  40. </view>
  41. </upload>
  42. <upload
  43. accept="file"
  44. v-if="uploadIsShow('file', item.fileType)"
  45. @uploadCallback="uploadCallback"
  46. >
  47. <view hover-class="navigator-hover">
  48. <image
  49. class="image"
  50. src="../static/file/folder.png"
  51. mode="heightFix"
  52. />
  53. </view>
  54. </upload>
  55. </view>
  56. </view>
  57. <view v-if="item.type == 'radio'" class="value-box options">
  58. <view
  59. class="option"
  60. :class="option[item.showKey] == item.value ? 'active' : ''"
  61. v-for="option in item.options"
  62. :key="option[item.showKey]"
  63. :hover-class="isReadOnly || item.disabled ? '' : 'navigator-hover'"
  64. @click="
  65. isReadOnly || item.disabled
  66. ? ''
  67. : onRadio(option[item.showKey], index)
  68. "
  69. >
  70. {{ option[item.showKey] }}
  71. </view>
  72. </view>
  73. <view
  74. v-else-if="item.type == 'route'"
  75. class="value-box tag-box"
  76. style="justify-content: flex-start; padding-bottom: 6px"
  77. >
  78. <view
  79. class="tag"
  80. :hover-class="isReadOnly || item.disabled ? '' : 'navigator-hover'"
  81. v-for="(name, i) in item.value.showList"
  82. :key="name"
  83. @click="
  84. isReadOnly || item.disabled
  85. ? ''
  86. : routeHandleDetele(name, i, index)
  87. "
  88. >
  89. {{ name }}
  90. <text
  91. v-if="!(isReadOnly || item.disabled)"
  92. class="iconfont icon-dibu-diudan"
  93. />
  94. </view>
  95. <view
  96. class="tag"
  97. v-if="!(isReadOnly || item.disabled)"
  98. hover-class="navigator-hover"
  99. @click="routeToAdd(index)"
  100. >
  101. 去选择
  102. <text class="iconfont" style="font-weight: bold; font-size: 14px"
  103. >+</text
  104. >
  105. </view>
  106. <view
  107. v-if="
  108. (isReadOnly || item.disabled) && item.value.showList.length == 0
  109. "
  110. style="font-size: 14px"
  111. >
  112. 未添加{{ item.label }}
  113. </view>
  114. </view>
  115. <view
  116. v-else-if="item.type == 'file'"
  117. class="value-box"
  118. style="justify-content: flex-start"
  119. >
  120. <view v-if="!item.value.length" class="iconfont"> 暂未上传附件 </view>
  121. <My_Files
  122. v-else
  123. :ref="'My_Files' + index"
  124. :aDeletion="item.aDeletion"
  125. :isDelete="item.isDelete && !item.disabled && !isReadOnly"
  126. @onDeteleFiles="onDeteleFiles"
  127. />
  128. </view>
  129. <view
  130. v-else-if="item.type == 'location'"
  131. class="value-box"
  132. :hover-class="isReadOnly || item.disabled ? '' : 'navigator-hover'"
  133. style="justify-content: space-between"
  134. @click="geoLocation(item, index)"
  135. >
  136. <view v-if="item.value.address">
  137. {{ item.value.address }}
  138. <view style="color: #666; font-size: 12px; margin-top: 6px">
  139. {{ item.value.time }}
  140. </view>
  141. </view>
  142. <view v-else>
  143. <text class="iconfont icon-a-wodemendianxinxidizhi" />
  144. 获取当前位置
  145. </view>
  146. <text
  147. v-if="item.value.address && !item.disabled && !isReadOnly"
  148. class="iconfont icon-dibu-diudan clear"
  149. @click.stop="clearRowValue(index)"
  150. />
  151. </view>
  152. <label :for="item.label" v-else class="value-box">
  153. <textarea
  154. :id="item.label"
  155. v-if="item.type == 'textarea'"
  156. class="input textarea"
  157. :value="item.value"
  158. auto-height
  159. @input="onInput($event, index)"
  160. placeholder-class="placeholder-class"
  161. :type="item.type"
  162. :disabled="isReadOnly || item.disabled"
  163. :password="item.password"
  164. :placeholder="item.placeholder || '请填写' + item.label"
  165. :maxlength="item.maxlength || '-1'"
  166. />
  167. <input
  168. :id="item.label"
  169. v-else
  170. class="input"
  171. :value="item.value"
  172. @input="onInput($event, index)"
  173. placeholder-class="placeholder-class"
  174. :type="item.type"
  175. :disabled="isReadOnly || item.disabled"
  176. :password="item.password"
  177. :placeholder="item.placeholder || '请填写' + item.label"
  178. :maxlength="item.maxlength || '-1'"
  179. />
  180. <text
  181. v-if="item.value && !item.disabled && !isReadOnly"
  182. class="iconfont icon-dibu-diudan clear"
  183. @click.stop="clearRowValue(index)"
  184. />
  185. </label>
  186. </view>
  187. </view>
  188. </view>
  189. </template>
  190. <script>
  191. let form示例 = [
  192. {
  193. label: "工序说明", //标题
  194. disabled: false, //禁用
  195. type: "text", //类型
  196. password: false, //是否密码类型
  197. placeholder: "",
  198. value: "",
  199. maxlength: -1, //最大长度
  200. required: false, //是否必填
  201. },
  202. {
  203. label: "备注",
  204. disabled: false, //禁用
  205. type: "textarea", //类型
  206. placeholder: "",
  207. value: "",
  208. maxlength: -1, //最大长度
  209. required: false, //是否必填
  210. },
  211. {
  212. label: "是否确认",
  213. disabled: false, //禁用
  214. type: "radio", //类型
  215. value: "",
  216. options: [
  217. {
  218. name: "是",
  219. value: 1,
  220. },
  221. {
  222. name: "否",
  223. value: 0,
  224. },
  225. ],
  226. showKey: "name", //必传 唯一值
  227. selectKey: "", //传值key返回该key的值,不传返回整个选择数据
  228. required: false, //是否必填
  229. },
  230. ];
  231. import upload from "./my-upload.vue";
  232. import { formatTime } from "../utils/getTime.js";
  233. export default {
  234. name: "My_form",
  235. components: { upload },
  236. computed: {
  237. uploadIsShow() {
  238. return function (name, list) {
  239. return list.some((v) => v == name);
  240. };
  241. },
  242. },
  243. props: {
  244. isReadOnly: Boolean,
  245. onConfirm: Function,
  246. },
  247. data() {
  248. return {
  249. form: [],
  250. attachmentids: [], //待绑定附件ID列表
  251. linksids: [], //待删除附件列表
  252. };
  253. },
  254. methods: {
  255. render(form, init = false) {
  256. try {
  257. if (init) {
  258. this.form = form.map((v) => {
  259. if (v.type == "location") {
  260. v.value = init[v.key] || {
  261. address: "",
  262. };
  263. } else if (v.type == "route") {
  264. v.value = init[v.key] || {
  265. showList: [],
  266. value: [],
  267. };
  268. } else {
  269. v.value = init[v.key] || v.value;
  270. }
  271. return v;
  272. });
  273. this.filesIndex = this.form.findIndex((v) => v.type == "file");
  274. if (this.filesIndex != -1 && this.form[this.filesIndex].value.length)
  275. setTimeout(() => {
  276. this.$refs["My_Files" + this.filesIndex][0].handleFiles(
  277. this.form[this.filesIndex].value,
  278. true
  279. );
  280. }, 500);
  281. } else {
  282. this.filesIndex = form.findIndex((v) => v.type == "file");
  283. this.form = form;
  284. }
  285. } catch (error) {
  286. console.log("init报错", error);
  287. this.form = form;
  288. }
  289. this.onComplete();
  290. },
  291. onInput(e, index) {
  292. this.$set(this.form[index], "value", e.detail.value);
  293. this.onComplete();
  294. },
  295. //清空行内容
  296. clearRowValue(index) {
  297. let data = this.form[index],
  298. that = this;
  299. uni.showModal({
  300. title: "提示",
  301. content: `是否确定清空“${data.label}”内容`,
  302. success: ({ confirm }) => {
  303. if (confirm) {
  304. if (data.type == "location") {
  305. that.form[index].value = {
  306. address: "",
  307. };
  308. } else {
  309. that.form[index].value = "";
  310. }
  311. that.onComplete();
  312. }
  313. },
  314. });
  315. },
  316. onRadio(value, index) {
  317. this.$set(this.form[index], "value", value);
  318. console.log(this.form[index]);
  319. this.onComplete();
  320. },
  321. //是否完成表单必填
  322. onComplete() {
  323. this.$emit(
  324. "onConfirm",
  325. this.form
  326. .filter((v) => v.required)
  327. .map((v) => {
  328. if (v.type == "file" && v.required) {
  329. return v.value.length !== 0;
  330. } else if (v.type == "location" && v.required) {
  331. return v.value.address ? v.value.address.length : 0;
  332. } else if (v.type == "route" && v.required) {
  333. return v.value.value.length;
  334. } else {
  335. return v.required && !(v.value == "");
  336. }
  337. })
  338. .some((v) => !v)
  339. );
  340. },
  341. uploadCallback(attachmentids) {
  342. this.handleFileLink(attachmentids);
  343. },
  344. handleFileLink(attachmentids, ownertable = "temporary", ownerid = 1, data) {
  345. return new Promise((resolve, reject) => {
  346. if (!attachmentids || attachmentids.length == 0) return resolve(true);
  347. this.$Http
  348. .basic({
  349. classname: "system.attachment.Attachment",
  350. method: "createFileLink",
  351. content: {
  352. ownertable,
  353. ownerid,
  354. usetype: "default",
  355. attachmentids,
  356. },
  357. })
  358. .then((res) => {
  359. console.log("跟进记录绑定附件", res);
  360. if (this.cutoff(res.msg)) return;
  361. resolve(res.data);
  362. if (ownertable == "temporary") {
  363. //临时文件
  364. console.log(this.form);
  365. console.log(this.filesIndex);
  366. if (this.filesIndex != -1)
  367. this.form[this.filesIndex].value = this.form[
  368. this.filesIndex
  369. ].value.concat(res.data);
  370. this.attachmentids = this.attachmentids.concat(
  371. res.data.map((v) => v.attachmentid)
  372. );
  373. setTimeout(() => {
  374. this.$refs["My_Files" + this.filesIndex][0].handleFiles(
  375. this.form[this.filesIndex].value,
  376. true
  377. );
  378. }, 50);
  379. } else {
  380. //绑定数据
  381. this.attachmentids = [];
  382. }
  383. this.onComplete();
  384. });
  385. });
  386. },
  387. //删除附件
  388. onDeteleFiles({ deleteid, attachmentid }) {
  389. this.attachmentids = this.attachmentids.filter((v) => v != attachmentid);
  390. this.form[this.filesIndex].value = this.form[
  391. this.filesIndex
  392. ].value.filter((v) => v.attachmentid != attachmentid);
  393. this.$refs["My_Files" + this.filesIndex][0].handleFiles(
  394. this.form[this.filesIndex].value,
  395. true
  396. );
  397. if (this.form[this.filesIndex].aDeletion || false)
  398. this.linksids.push(deleteid);
  399. this.onComplete();
  400. },
  401. //去添加
  402. routeToAdd(index) {
  403. let data = this.form[index];
  404. this.$Http.route = { data, index };
  405. this.$Http.handleAdd = this.routeHandleAdd.bind(this);
  406. uni.navigateTo({ url: data.path });
  407. },
  408. routeHandleAdd(result) {
  409. const { data, index } = this.$Http.route;
  410. data.value = result;
  411. this.form[index] = data;
  412. uni.navigateBack();
  413. delete this.$Http.route;
  414. delete this.$Http.handleAdd;
  415. this.onComplete();
  416. },
  417. //移除route选项
  418. routeHandleDetele(name, i, index) {
  419. let that = this;
  420. uni.showModal({
  421. title: "提示",
  422. content: `是否确定移除"${name}"`,
  423. success: ({ confirm }) => {
  424. if (confirm) {
  425. that.form[index].value.showList.splice(i, 1);
  426. that.form[index].value.value.splice(i, 1);
  427. that.onComplete();
  428. }
  429. },
  430. });
  431. },
  432. //异步删除文件
  433. handleDeteleFiles(linksids) {
  434. return new Promise((resolve, reject) => {
  435. if (!linksids || linksids.length == 0) return resolve(true);
  436. this.$Http
  437. .basic({
  438. classname: "system.attachment.Attachment",
  439. method: "deleteFileLink",
  440. content: {
  441. linksids,
  442. },
  443. })
  444. .then((res) => {
  445. console.log("处理删除附件", res);
  446. if (this.cutoff(res.msg)) return;
  447. resolve(res);
  448. });
  449. });
  450. },
  451. geoLocation(item, index) {
  452. let that = this;
  453. if (item.value.longitude) {
  454. uni.showModal({
  455. title: "提示",
  456. content: "是否确定重新获取定位",
  457. success: ({ confirm }) => {
  458. if (confirm) handle();
  459. },
  460. });
  461. } else {
  462. handle();
  463. }
  464. function handle() {
  465. uni.showLoading({
  466. title: "定位中...",
  467. mask: true,
  468. });
  469. that.getLocation(true).then((res) => {
  470. console.log("定位", res);
  471. uni.hideLoading();
  472. if (res.errMsg != "getLocation:ok")
  473. return uni.showToast({
  474. title: "定位失败",
  475. icon: "none",
  476. });
  477. that.form[index].value = {
  478. longitude: res.longitude,
  479. latitude: res.latitude,
  480. address: res.result.formatted_address,
  481. time: formatTime(),
  482. result: res.result,
  483. };
  484. that.onComplete();
  485. });
  486. }
  487. },
  488. onSubmit() {
  489. let res = {};
  490. //是否存在附件
  491. if (this.filesIndex != -1) {
  492. res.attachmentids = this.attachmentids;
  493. res.linksids = this.linksids;
  494. }
  495. this.form.forEach((v) => {
  496. if (v.type == "radio") {
  497. v.value = v.options.find((s) => s[v.showKey] == v.value);
  498. if (v.selectKey) v.value = v.value[v.selectKey];
  499. }
  500. if (v.key) res[v.key] = v.value;
  501. });
  502. return res;
  503. },
  504. },
  505. };
  506. </script>
  507. <style lang="scss" scoped>
  508. .form {
  509. width: 355px;
  510. margin: 0 auto;
  511. .form-row {
  512. margin-top: 10px;
  513. .label {
  514. display: flex;
  515. color: #ddd;
  516. font-size: 12px;
  517. .required {
  518. color: #fa3534;
  519. margin-right: 3px;
  520. }
  521. .file {
  522. display: flex;
  523. flex: 1;
  524. justify-content: flex-end;
  525. /deep/ .u-upload {
  526. flex: 0 !important;
  527. }
  528. .image {
  529. height: 18px;
  530. margin-right: 10px;
  531. }
  532. }
  533. }
  534. .value-box {
  535. display: flex;
  536. justify-content: center;
  537. align-items: center;
  538. width: 355px;
  539. background: #fff;
  540. margin-top: 10px;
  541. box-sizing: border-box;
  542. padding: 10px;
  543. border-radius: 4px;
  544. overflow: hidden;
  545. min-height: 40px;
  546. .input {
  547. flex: 1;
  548. font-size: 14px;
  549. }
  550. .iconfont {
  551. font-size: 14px;
  552. }
  553. .textarea {
  554. padding: 0 !important;
  555. margin: 0 !important;
  556. }
  557. .placeholder-class {
  558. font-size: 14px;
  559. }
  560. .clear {
  561. padding-left: 4px;
  562. font-size: 14px;
  563. opacity: 0.9;
  564. }
  565. .tag {
  566. display: flex;
  567. align-items: center;
  568. padding: 4px 6px;
  569. background: #0054e1;
  570. color: #fff;
  571. border-radius: 4px;
  572. font-size: 13px;
  573. margin-right: 6px;
  574. margin-bottom: 4px;
  575. .iconfont {
  576. margin-left: 5px;
  577. font-size: 12px !important;
  578. }
  579. }
  580. }
  581. .tag-box {
  582. flex-wrap: wrap;
  583. .tag {
  584. flex-shrink: 0;
  585. }
  586. }
  587. .options {
  588. flex-wrap: wrap;
  589. justify-content: flex-start;
  590. .option {
  591. font-size: 12px;
  592. padding: 4px 6px;
  593. border-radius: 4px;
  594. border: 1px solid #ddd;
  595. margin-right: 6px;
  596. }
  597. .active {
  598. border: 0;
  599. background: #0054e1;
  600. color: #fff;
  601. }
  602. }
  603. }
  604. }
  605. </style>