|
@@ -0,0 +1,486 @@
|
|
|
+<template>
|
|
|
+ <view class="form">
|
|
|
+ <view v-for="item, index in form" :key="item.key" :style="{ opacity: isReadOnly || item.disabled ? .9 : 1 }">
|
|
|
+ <view class="form-row">
|
|
|
+ <view class="label">
|
|
|
+ <text class="required" v-if="item.required">*</text>
|
|
|
+ {{ item.label }}
|
|
|
+ <view class="file" v-if="item.type == 'file' && !item.disabled && !isReadOnly">
|
|
|
+ <upload accept="image" v-if="uploadIsShow('image', item.fileType)" @uploadCallback="uploadCallback">
|
|
|
+ <view hover-class="navigator-hover">
|
|
|
+ <image class="image" src="../static/file/image.png" mode="heightFix" />
|
|
|
+ </view>
|
|
|
+ </upload>
|
|
|
+ <upload accept="video" v-if="uploadIsShow('video', item.fileType)" @uploadCallback="uploadCallback">
|
|
|
+ <view hover-class="navigator-hover">
|
|
|
+ <image class="image" src="../static/file/video.png" mode="heightFix" />
|
|
|
+ </view>
|
|
|
+ </upload>
|
|
|
+ <upload accept="file" v-if="uploadIsShow('file', item.fileType)" @uploadCallback="uploadCallback">
|
|
|
+ <view hover-class="navigator-hover">
|
|
|
+ <image class="image" src="../static/file/folder.png" mode="heightFix" />
|
|
|
+ </view>
|
|
|
+ </upload>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="item.type == 'radio'" class="value-box options">
|
|
|
+ <view class="option" :class="option[item.showKey] == item.value ? 'active' : ''"
|
|
|
+ v-for="option in item.options" :key="option[item.showKey]"
|
|
|
+ :hover-class="isReadOnly || item.disabled ? '' : 'navigator-hover'"
|
|
|
+ @click=" isReadOnly || item.disabled ? '' : onRadio(option[item.showKey], index)">
|
|
|
+ {{ option[item.showKey] }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="item.type == 'route'" class="value-box tag-box"
|
|
|
+ style="justify-content: flex-start;padding-bottom: 6px;">
|
|
|
+ <view class="tag" :hover-class="isReadOnly || item.disabled ? '' : 'navigator-hover'"
|
|
|
+ v-for="(name, i) in item.value.showList" :key="name" @click="routeHandleDetele(name, i, index)">
|
|
|
+ {{ name }}
|
|
|
+ <text v-if="!(isReadOnly || item.disabled)" class="iconfont icon-dibu-diudan" />
|
|
|
+ </view>
|
|
|
+ <view class="tag" v-if="!(isReadOnly || item.disabled)" hover-class="navigator-hover"
|
|
|
+ @click="routeToAdd(index)">
|
|
|
+ 去添加
|
|
|
+ <text class="iconfont" style="font-weight: bold;font-size: 14px;">+</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="(isReadOnly || item.disabled) && item.value.showList.length == 0" style="font-size: 14px;">
|
|
|
+ 未添加{{ item.label }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="item.type == 'file'" class="value-box" style="justify-content: flex-start;">
|
|
|
+ <view v-if="!item.value.length" style="font-size: 14px;">
|
|
|
+ 暂未上传附件
|
|
|
+ </view>
|
|
|
+ <My_Files v-else :ref="'My_Files' + index" :aDeletion="item.aDeletion"
|
|
|
+ :isDelete="item.isDelete && !item.disabled && !isReadOnly" @onDeteleFiles="onDeteleFiles" />
|
|
|
+ </view>
|
|
|
+ <view v-else-if="item.type == 'location'" class="value-box"
|
|
|
+ :hover-class="isReadOnly || item.disabled ? '' : 'navigator-hover'"
|
|
|
+ style="justify-content: space-between;" @click="geoLocation(item, index)">
|
|
|
+ <view v-if="item.value.address">
|
|
|
+ {{ item.value.address }}
|
|
|
+ <view style="color: #666; font-size: 12px;margin-top: 6px;">
|
|
|
+ {{ item.value.time }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else>
|
|
|
+ <text class="iconfont icon-a-wodemendianxinxidizhi" />
|
|
|
+ 获取当前位置
|
|
|
+ </view>
|
|
|
+ <text v-if="item.value.address && !item.disabled && !isReadOnly" class="iconfont icon-dibu-diudan clear"
|
|
|
+ @click.stop="clearRowValue(index)" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <label :for="item.label" v-else class="value-box">
|
|
|
+ <textarea :id="item.label" v-if="item.type == 'textarea'" class="input textarea" :value="item.value"
|
|
|
+ auto-height @input="onInput($event, index)" placeholder-class="placeholder-class" :type="item.type"
|
|
|
+ :disabled="isReadOnly || item.disabled" :password="item.password"
|
|
|
+ :placeholder="item.placeholder || '请填写' + item.label" :maxlength="item.maxlength || '-1'" />
|
|
|
+ <input :id="item.label" v-else class="input" :value="item.value" @input="onInput($event, index)"
|
|
|
+ placeholder-class="placeholder-class" :type="item.type" :disabled="isReadOnly || item.disabled"
|
|
|
+ :password="item.password" :placeholder="item.placeholder || '请填写' + item.label"
|
|
|
+ :maxlength="item.maxlength || '-1'" />
|
|
|
+ <text v-if="item.value && !item.disabled && !isReadOnly" class="iconfont icon-dibu-diudan clear"
|
|
|
+ @click.stop="clearRowValue(index)" />
|
|
|
+ </label>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+let form示例 = [{
|
|
|
+ label: "工序说明",//标题
|
|
|
+ disabled: false,//禁用
|
|
|
+ type: 'text',//类型
|
|
|
+ password: false,//是否密码类型
|
|
|
+ placeholder: "",
|
|
|
+ value: "",
|
|
|
+ maxlength: -1,//最大长度
|
|
|
+ required: false,//是否必填
|
|
|
+}, {
|
|
|
+ label: "备注",
|
|
|
+ disabled: false,//禁用
|
|
|
+ type: 'textarea',//类型
|
|
|
+ placeholder: "",
|
|
|
+ value: "",
|
|
|
+ maxlength: -1,//最大长度
|
|
|
+ required: false,//是否必填
|
|
|
+}, {
|
|
|
+ label: "是否确认",
|
|
|
+ disabled: false,//禁用
|
|
|
+ type: 'radio',//类型
|
|
|
+ value: "",
|
|
|
+ options: [{
|
|
|
+ name: "是",
|
|
|
+ value: 1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "否",
|
|
|
+ value: 0
|
|
|
+ }],
|
|
|
+ showKey: 'name',//必传 唯一值
|
|
|
+ selectKey: "",//传值key返回该key的值,不传返回整个选择数据
|
|
|
+ required: false,//是否必填
|
|
|
+}];
|
|
|
+import upload from "./my-upload.vue";
|
|
|
+import My_Files from "./My_Files.vue";
|
|
|
+import { formatTime } from "../utils/getTime.js"
|
|
|
+export default {
|
|
|
+ name: "My_form",
|
|
|
+ components: { upload, My_Files },
|
|
|
+ computed: {
|
|
|
+ uploadIsShow() {
|
|
|
+ return function (name, list) {
|
|
|
+ return list.some(v => v == name)
|
|
|
+ };
|
|
|
+ }
|
|
|
+ },
|
|
|
+ props: {
|
|
|
+ isReadOnly: Boolean,
|
|
|
+ onConfirm: Function
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ form: [],
|
|
|
+ attachmentids: [],//待绑定附件ID列表
|
|
|
+ linksids: [],//待删除附件列表
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ render(form, init = false) {
|
|
|
+ try {
|
|
|
+ if (init) {
|
|
|
+ this.form = form.map(v => {
|
|
|
+ if (v.type == 'location') {
|
|
|
+ v.value = init[v.key] || {
|
|
|
+ address: ""
|
|
|
+ };
|
|
|
+ } else if (v.type == 'route') {
|
|
|
+ v.value = init[v.key] || {
|
|
|
+ showList: [],
|
|
|
+ value: []
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ v.value = init[v.key] || v.value;
|
|
|
+ }
|
|
|
+ return v
|
|
|
+ });
|
|
|
+ this.filesIndex = this.form.findIndex(v => v.type == 'file');
|
|
|
+ if (this.form[this.filesIndex].value.length) setTimeout(() => {
|
|
|
+ this.$refs['My_Files' + this.filesIndex][0].handleFiles(this.form[this.filesIndex].value, true)
|
|
|
+ }, 500)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log("init报错", error)
|
|
|
+ }
|
|
|
+ this.form = form;
|
|
|
+ this.onComplete()
|
|
|
+ },
|
|
|
+ onInput(e, index) {
|
|
|
+ this.$set(this.form[index], 'value', e.detail.value)
|
|
|
+ this.onComplete()
|
|
|
+ },
|
|
|
+ //清空行内容
|
|
|
+ clearRowValue(index) {
|
|
|
+ let data = this.form[index],
|
|
|
+ that = this;
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: `是否确定清空“${data.label}”内容`,
|
|
|
+ success: ({ confirm }) => {
|
|
|
+ if (confirm) {
|
|
|
+ if (data.type == 'location') {
|
|
|
+ that.form[index].value = {
|
|
|
+ address: ""
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ that.form[index].value = "";
|
|
|
+ }
|
|
|
+ that.onComplete()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onRadio(value, index) {
|
|
|
+ this.$set(this.form[index], 'value', value)
|
|
|
+ console.log(this.form[index])
|
|
|
+ this.onComplete()
|
|
|
+ },
|
|
|
+ //是否完成表单必填
|
|
|
+ onComplete() {
|
|
|
+ this.$emit("onConfirm", this.form.filter(v => v.required).map(v => {
|
|
|
+ if (v.type == "file" && v.required) {
|
|
|
+ return v.value.length !== 0
|
|
|
+ } else if (v.type == "location" && v.required) {
|
|
|
+ return v.value.address.length
|
|
|
+ } else if (v.type == "route" && v.required) {
|
|
|
+ return v.value.value.length
|
|
|
+ } else {
|
|
|
+ return v.required && !(v.value == "")
|
|
|
+ }
|
|
|
+ }).some(v => !v));
|
|
|
+ },
|
|
|
+ uploadCallback(attachmentids) {
|
|
|
+ this.handleFileLink(attachmentids)
|
|
|
+ },
|
|
|
+ handleFileLink(attachmentids, ownertable = "temporary", ownerid = 1, data) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (attachmentids.length == 0) return resolve(true);
|
|
|
+ this.$Http.basic({
|
|
|
+ "classname": "system.attachment.Attachment",
|
|
|
+ "method": "createFileLink",
|
|
|
+ "content": {
|
|
|
+ ownertable,
|
|
|
+ ownerid,
|
|
|
+ usetype: 'default',
|
|
|
+ attachmentids
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ console.log('跟进记录绑定附件', res)
|
|
|
+ if (this.cutoff(res.msg)) return;
|
|
|
+ resolve(res.data)
|
|
|
+ if (ownertable == "temporary") {
|
|
|
+ //临时文件
|
|
|
+ if (this.filesIndex != -1) this.form[this.filesIndex].value = this.form[this.filesIndex].value.concat(res.data)
|
|
|
+ this.attachmentids = this.attachmentids.concat(res.data.map(v => v.attachmentid))
|
|
|
+ setTimeout(() => {
|
|
|
+ this.$refs['My_Files' + this.filesIndex][0].handleFiles(this.form[this.filesIndex].value, true)
|
|
|
+ }, 50)
|
|
|
+ } else {
|
|
|
+ //绑定数据
|
|
|
+ this.attachmentids = [];
|
|
|
+ }
|
|
|
+ this.onComplete()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ //删除附件
|
|
|
+ onDeteleFiles({ deleteid, attachmentid }) {
|
|
|
+ this.attachmentids = this.attachmentids.filter(v => v != attachmentid);
|
|
|
+ this.form[this.filesIndex].value = this.form[this.filesIndex].value.filter(v => v.attachmentid != attachmentid);
|
|
|
+ this.$refs['My_Files' + this.filesIndex][0].handleFiles(this.form[this.filesIndex].value, true);
|
|
|
+ if (this.form[this.filesIndex].aDeletion || false) this.linksids.push(deleteid);
|
|
|
+ this.onComplete()
|
|
|
+ },
|
|
|
+ //去添加
|
|
|
+ routeToAdd(index) {
|
|
|
+ let data = this.form[index];
|
|
|
+ this.$Http.route = { data, index };
|
|
|
+ this.$Http.handleAdd = this.routeHandleAdd.bind(this);
|
|
|
+ uni.navigateTo({ url: data.path })
|
|
|
+ },
|
|
|
+ routeHandleAdd(result) {
|
|
|
+ const { data, index } = this.$Http.route;
|
|
|
+ data.value = result
|
|
|
+ this.form[index] = data;
|
|
|
+ uni.navigateBack();
|
|
|
+ delete this.$Http.route;
|
|
|
+ delete this.$Http.handleAdd;
|
|
|
+ this.onComplete()
|
|
|
+ },
|
|
|
+ //移除route选项
|
|
|
+ routeHandleDetele(name, i, index) {
|
|
|
+ let that = this;
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: `是否确定移除"${name}"`,
|
|
|
+ success: ({ confirm }) => {
|
|
|
+ if (confirm) {
|
|
|
+ that.form[index].value.showList.splice(i, 1)
|
|
|
+ that.form[index].value.value.splice(i, 1)
|
|
|
+ that.onComplete()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ },
|
|
|
+ //异步删除文件
|
|
|
+ handleDeteleFiles(linksids) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (linksids.length == 0) return resolve(true);
|
|
|
+ this.$Http.basic({
|
|
|
+ "classname": "system.attachment.Attachment",
|
|
|
+ "method": "deleteFileLink",
|
|
|
+ "content": {
|
|
|
+ linksids
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ console.log("处理删除附件", res)
|
|
|
+ if (this.cutoff(res.msg)) return;
|
|
|
+ resolve(res)
|
|
|
+ });
|
|
|
+ })
|
|
|
+ },
|
|
|
+ geoLocation(item, index) {
|
|
|
+ let that = this;
|
|
|
+ if (item.value.longitude) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '是否确定重新获取定位',
|
|
|
+ success: ({ confirm }) => {
|
|
|
+ if (confirm) handle();
|
|
|
+ },
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ handle();
|
|
|
+ }
|
|
|
+ function handle() {
|
|
|
+ uni.showLoading({
|
|
|
+ title: "定位中...",
|
|
|
+ mask: true,
|
|
|
+ })
|
|
|
+ that.getLocation(true).then(res => {
|
|
|
+ uni.hideLoading()
|
|
|
+ if (res.errMsg != "getLocation:ok") return uni.showToast({
|
|
|
+ title: '定位失败',
|
|
|
+ icon: "none"
|
|
|
+ });
|
|
|
+ that.form[index].value = {
|
|
|
+ longitude: res.longitude,
|
|
|
+ latitude: res.latitude,
|
|
|
+ address: res.result.address,
|
|
|
+ time: formatTime()
|
|
|
+ };
|
|
|
+ that.onComplete()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onSubmit() {
|
|
|
+ let res = {};
|
|
|
+ //是否存在附件
|
|
|
+ if (this.filesIndex != -1) {
|
|
|
+ res.attachmentids = this.attachmentids;
|
|
|
+ res.linksids = this.linksids;
|
|
|
+ }
|
|
|
+ this.form.forEach(v => {
|
|
|
+ if (v.type == "radio") {
|
|
|
+ v.value = v.options.find(s => s[v.showKey] == v.value);
|
|
|
+ if (v.selectKey) v.value = v.value[v.selectKey]
|
|
|
+ console.log(v)
|
|
|
+ } else {
|
|
|
+ if (v.key) res[v.key] = v.value;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return res
|
|
|
+ }
|
|
|
+ },
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.form {
|
|
|
+ width: 355px;
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ .form-row {
|
|
|
+ margin-top: 10px;
|
|
|
+
|
|
|
+ .label {
|
|
|
+ display: flex;
|
|
|
+ color: #ddd;
|
|
|
+ font-size: 12px;
|
|
|
+
|
|
|
+ .required {
|
|
|
+ color: #FA3534;
|
|
|
+ margin-right: 3px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ justify-content: flex-end;
|
|
|
+
|
|
|
+ /deep/ .u-upload {
|
|
|
+ flex: 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image {
|
|
|
+ height: 18px;
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .value-box {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ width: 355px;
|
|
|
+ background: #fff;
|
|
|
+ margin-top: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding: 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 40px;
|
|
|
+
|
|
|
+ .input {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .textarea {
|
|
|
+ padding: 0 !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .placeholder-class {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .clear {
|
|
|
+ padding-left: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ opacity: .9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tag {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 4px 6px;
|
|
|
+ background: #0054E1;
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 13px;
|
|
|
+ margin-right: 6px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+
|
|
|
+ .iconfont {
|
|
|
+ margin-left: 5px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .tag-box {
|
|
|
+ flex-wrap: wrap;
|
|
|
+
|
|
|
+ .tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .options {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ justify-content: flex-start;
|
|
|
+
|
|
|
+ .option {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 4px 6px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .active {
|
|
|
+ border: 0;
|
|
|
+ background: #0054E1;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|