Browse Source

组件化2

master
张泽亮 2 months ago
parent
commit
ca28b7150f
3 changed files with 382 additions and 128 deletions
  1. +5
    -2
      src/api/app/index.js
  2. +362
    -110
      src/components/form/CommonUpload.vue
  3. +15
    -16
      src/views/app/project/operate_edit.vue

+ 5
- 2
src/api/app/index.js View File

@@ -24,7 +24,10 @@ export function commonUpload(data) {
return request({
url: '/common/upload',
method: 'post',
header: { "Content-Type": 'application/x-www-form-urlencoded' },
data: data
data: data,
timeout: 60000, // 延长超时时间
headers: {
// 不设置 Content-Type,让浏览器自动设置
}
})
}

+ 362
- 110
src/components/form/CommonUpload.vue View File

@@ -1,147 +1,399 @@
<!-- 通用上传组件 zhao -->

<template>
<van-uploader v-model="fileList" :multiple="multiple" :after-read="afterRead" :show-upload="showUpload"
:deletable="deletable" @delete="deleteFile" :accept="accept || null"/>
<div class="component-upload-image">
<!-- 上传区域 -->
<el-upload
multiple
:disabled="disabled"
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:data="data"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
ref="imageUpload"
:on-remove="handleDelete"
:show-file-list="true"
:headers="headers"
:file-list="fileList"
:on-preview="handlePictureCardPreview"
:class="{hide: fileList.length >= limit}"
style="grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); gap: 12px; padding: 0;"
>
<!-- 上传按钮 -->
<div class="upload-btn">
<i class="el-icon-plus" style="font-size: 20px;"></i>
</div>
</el-upload>

<!-- 上传提示:关键调整 - 减小与上传框的间距 -->
<div class="el-upload__tip" slot="tip" v-if="showTip && !disabled" style="font-size: 12px; margin-top: 4px; line-height: 1.4;">
<template v-if="fileSize"> 大小: ≤ <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
<template v-if="fileType && fileSize">,</template>
<template v-if="fileType"> 格式: <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
</div>

<!-- 预览弹窗 -->
<el-dialog :visible.sync="dialogVisible" title="预览" :width="dialogWidth" append-to-body :close-on-click-modal="true">
<img :src="dialogImageUrl" style="display: block; max-width: 100%; max-height: 70vh; margin: 0 auto;" @load="handleImageLoad"/>
</el-dialog>
</div>
</template>

<script>

import {commonUpload} from "@/api/app/index";
import { getToken } from "@/utils/auth";
import { isExternal } from "@/utils/validate";
import Sortable from 'sortablejs';
import {Dialog} from "vant";

export default {
name: "commonUpload",
props: {
name: String,
value: { // 绑定值 字符串 ,分隔 可监听
value: [String, Object, Array],
action: {
type: String,
default: null,
default: "/common/upload"
},
accept: { // 上传类型限制: 默认图片, * = 任意类型
type: String,
},
multiple: { // 多文件上传
type: Boolean,
default: false,
},
deletable: { // 允许删除
type: Boolean,
default: true,
data: {
type: Object
},
showUpload: { // 显示上传按钮
type: Boolean,
default: true,
limit: {
type: Number,
default: 5
},
formData: { // 额外请求参数
type: Object,
default: function () {
return {};
},
fileSize: {
type: Number,
default: 3
},
file: { // 上传文件字段名
type: String,
default: 'file',
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"]
},
host: {
type: String, // 文件地址前缀
default: '/api',
isShowTip: {
type: Boolean,
default: true
},
},
watch: {
value: function (newVal, oldVal) {
if (newVal != this.internalValue)
this.setInternalValue(newVal);
disabled: {
type: Boolean,
default: false
},
},
created() {
this.parseValue(this.value);
drag: {
type: Boolean,
default: false
}
},
data() {
return {
internalValue: this.value,
fileList: [],
pathList: [],
number: 0,
uploadList: [],
dialogImageUrl: "",
dialogVisible: false,
dialogWidth: "90%",
baseUrl: process.env.VUE_APP_BASE_API,
uploadImgUrl: process.env.VUE_APP_BASE_API + this.action,
headers: {
Authorization: "Bearer " + getToken(),
},
fileList: []
};
},
mounted() {
if (this.drag && !this.disabled) {
this.$nextTick(() => {
const element = this.$refs.imageUpload?.$el?.querySelector('.el-upload-list');
if (element) {
Sortable.create(element, {
onEnd: (evt) => {
const movedItem = this.fileList.splice(evt.oldIndex, 1)[0];
this.fileList.splice(evt.newIndex, 0, movedItem);
this.$emit("input", this.listToString(this.fileList));
},
handle: ".el-upload-list__item",
animation: 150
});
}
});
}
window.addEventListener("resize", this.adjustDialogWidth);
},
beforeDestroy() {
window.removeEventListener("resize", this.adjustDialogWidth);
},
watch: {
value: {
handler(val) {
if (val) {
const list = Array.isArray(val) ? val : val.split(',');
this.fileList = list.map(item => {
if (typeof item === "string") {
const imgUrl = !isExternal(item) && item.indexOf(this.baseUrl) === -1
? `${this.baseUrl}${item}`
: item;
return {
name: imgUrl,
url: imgUrl,
response: { url: imgUrl }
};
}
return item;
});
} else {
this.fileList = [];
}
},
deep: true,
immediate: true
}
},
computed: {
showTip() {
return this.isShowTip && (this.fileType.length || this.fileSize);
}
},
methods: {
setInternalValue(newVal) {
this.parseValue(newVal);
this.internalValue = newVal;
adjustDialogWidth() {
const screenWidth = window.innerWidth;
this.dialogWidth = screenWidth < 375 ? "95%" : "90%";
},
parseValue(data) {
if (data) {
this.pathList = data.split(',');
handleBeforeUpload(file) {
let isImg = false;
const fileExtension = file.name.lastIndexOf(".") > -1
? file.name.slice(file.name.lastIndexOf(".") + 1).toLowerCase()
: "";

if (this.fileType.length) {
isImg = this.fileType.includes(fileExtension) || file.type.includes(fileExtension);
} else {
this.pathList = [];
isImg = file.type.indexOf("image") > -1;
}
this.fileList = this.pathList.map((x) => {
return {
url: this.host + x,
};
});
},
makeFormData() {
let fd = new FormData();
if (this.formData) {
for (let k of Object.keys(this.formData)) {
fd.set(k, this.formData[k]);

if (!isImg) {
this.$modal.msgError(`请上传${this.fileType.join("/")}格式图片`);
return false;
}
if (file.name.includes(',')) {
this.$modal.msgError("文件名不能包含英文逗号");
return false;
}
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$modal.msgError(`图片大小不能超过${this.fileSize}MB`);
return false;
}
}
return fd;
},
upload(file) {
let params1 = this.makeFormData();
params1.append(this.file, file.file);
return commonUpload(params1).then((resp) => {
this.pathList.push(resp.fileName);
this.updateInternalValue();
this.$emit('upload', resp.fileName);
});
},
afterRead(file) {
this.$toast.loading({

this.$modal.loading({
message: "上传中...",
forbidClick: true,
duration: 0,
lock: false
});
// 此时可以自行将文件上传至服务器
if (file instanceof Array) {//判断是否为数组,单张图片为array,多张为数组,数组返回true否则为false
if (file.length > 0) {
let index = 0;
const f = () => {
if (index >= file.length)
return;
let up = file[index];
//console.log(up);
//console.log(`上传文件: ${index} -> ${up.file.name}`);
this.upload(up).then(() => {
index++;
if (index < file.length)
f();
});
};
f();
}
this.number++;
},
handleExceed() {
this.$modal.msgError(`最多可上传${this.limit}张图片`);
},
handleUploadSuccess(res, file) {
if (res.code === 200) {
this.uploadList.push({
name: res.fileName,
url: res.fileName,
response: { url: `${this.baseUrl}${res.fileName}` }
});
this.uploadedSuccessfully();
} else {
this.upload(file);
this.number--;
this.$modal.closeLoading();
this.$modal.msgError(res.msg || "上传失败");
this.$refs.imageUpload?.handleRemove(file);
}
},
deleteFile(detail) {
this.pathList.splice(detail.index, 1);
this.updateInternalValue();
this.$emit('remove', detail.index);
handleDelete(file, list) {
Dialog.confirm({
title: '提示',
message: '确定要移除这张图片吗?',
})
.then(() => {
const findex = this.fileList.map(f => f.name).indexOf(file.name);
if (findex > -1) {
this.fileList.splice(findex, 1);
this.$emit("input", this.listToString(this.fileList));
}
})
.catch(() => {
// on cancel
});
},
updateInternalValue() {
let files = this.pathList.join(',');
//console.log(files);
this.internalValue = files;
if (this.internalValue != this.value)
this.$emit('input', this.internalValue);
handleUploadError() {
this.$modal.closeLoading();
this.$modal.msgError("上传失败,请重试");
},
},
}
uploadedSuccessfully() {
if (this.number > 0 && this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
this.$emit("input", this.listToString(this.fileList));
this.$modal.closeLoading();
this.$modal.msgSuccess("全部上传完成");
}
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url || file.response?.url;
this.adjustDialogWidth();
this.dialogVisible = true;
},
handleImageLoad(e) {
const img = e.target;
const maxHeight = window.innerHeight * 0.7;
if (img.height > maxHeight) {
img.style.height = `${maxHeight}px`;
img.style.width = "auto";
}
},
listToString(list, separator) {
separator = separator || ",";
return list
.filter(item => item.url)
.map(item => item.url.replace(this.baseUrl, ""))
.join(separator);
}
}
};
</script>

<style scoped>
<style scoped lang="scss">
// 上传按钮样式
.upload-btn {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #dcdcdc;
border-radius: 4px;
background-color: #fafafa;
margin: 0;
transition: all 0.2s;

&:hover {
border-color: #409eff;
background-color: #f5faff;
}

&:active {
background-color: #f0f7ff;
}
}

// 已上传图片项样式
::v-deep .el-upload-list--picture-card .el-upload-list__item {
width: 60px;
height: 60px;
border-radius: 4px;
overflow: hidden;
position: relative;
border: 1px solid #e5e7eb;

.el-upload-list__item-thumbnail {
object-fit: cover;
width: 100%;
height: 100%;
}

.el-upload-list__item-delete {
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
font-size: 12px;
background-color: #f56c6c;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 10;

&:hover, &:active {
width: 22px;
height: 22px;
background-color: #e4393c;
top: -9px;
right: -9px;
}
}

&:hover {
border-color: #409eff;
}

&:hover .el-upload-list__item-delete {
opacity: 1;
}
}

// 移除外层默认边框
::v-deep .el-upload--picture-card {
border: none !important;
background: transparent !important;
margin-bottom: 0 !important; /* 移除上传组件底部默认边距 */
}

// 隐藏上传按钮(数量达限时)
::v-deep.hide .el-upload--picture-card {
display: none;
}

// 禁用状态样式
::v-deep .el-upload-list--picture-card.is-disabled + .el-upload--picture-card {
display: none !important;
}

// 列表动画优化
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
transition: all 0s;
}

::v-deep .el-list-enter,
::v-deep .el-list-leave-active {
opacity: 0;
transform: translateY(0);
}

// 小屏幕适配
@media screen and (max-width: 375px) {
.upload-btn,
::v-deep .el-upload-list--picture-card .el-upload-list__item {
width: 55px;
height: 55px;
}

.el-upload--picture-card {
grid-template-columns: repeat(auto-fill, minmax(55px, 1fr));
gap: 8px;
}

// 提示文字进一步缩小间距
.el-upload__tip {
margin-top: 3px !important;
font-size: 11px !important;
}

::v-deep .el-upload-list__item-delete {
width: 18px;
height: 18px;
top: -7px;
right: -7px;

&:hover, &:active {
width: 20px;
height: 20px;
top: -8px;
right: -8px;
}
}
}
</style>

+ 15
- 16
src/views/app/project/operate_edit.vue View File

@@ -6,42 +6,41 @@
<div class="main">
<p class="title"><i></i>经营信息</p>
<van-field readonly v-model="form.deptName" label="区域位置名称" placeholder="请输入" input-align="right" label-width="auto" />
<van-field readonly required :rules="[{ required: true }]" v-model="form.dkbm" label="地块编码" placeholder="请输入" input-align="right" label-width="auto" />
<van-field readonly :rules="[{ required: true }]" v-model="form.dkmc" label="地块名称" placeholder="请输入" input-align="right" label-width="auto" required />
<van-field readonly v-model="form.dkbm" label="地块编码" placeholder="请输入" input-align="right" label-width="auto" required :rules="[{ required: true }]"/>
<van-field readonly v-model="form.dkmc" label="地块名称" placeholder="请输入" input-align="right" label-width="auto" required :rules="[{ required: true }]"/>
<van-field readonly v-model="form.dkdz" label="地块东至" placeholder="请输入" input-align="right" label-width="auto" />
<van-field readonly v-model="form.dkxz" label="地块西至" placeholder="请输入" input-align="right" label-width="auto" />
<van-field readonly v-model="form.dknz" label="地块南至" placeholder="请输入" input-align="right" label-width="auto" />
<van-field readonly v-model="form.dkbz" label="地块北至" placeholder="请输入" input-align="right" label-width="auto" />

<van-field required :rules="[{ required: true }]" v-model="form.jymj" label="经营面积" placeholder="请输入" input-align="right" label-width="auto" />
<van-field v-model="form.jymj" label="经营面积" placeholder="请输入" type="number" input-align="right" label-width="auto" required :rules="[{ required: true }]"/>

<field-select v-model="form.jyfs" label="经营方式" value-key="dictLabel" data-key="dictValue" placeholder="请选择" requiredx remote-url="/system/dict/data/type/jyfs" :on-remote-response="'data'" required/>
<field-select v-model="form.jyfs" label="经营方式" placeholder="请选择" value-key="dictLabel" data-key="dictValue" remote-url="/system/dict/data/type/jyfs" :on-remote-response="'data'" required/>

<field-select v-model="form.jydxlx" label="经营对象类型" value-key="dictLabel" data-key="dictValue" placeholder="请选择" requiredx remote-url="/system/dict/data/type/jydxlx" :on-remote-response="'data'" required/>
<field-select v-model="form.jydxlx" label="经营对象类型" placeholder="请选择" value-key="dictLabel" data-key="dictValue" remote-url="/system/dict/data/type/jydxlx" :on-remote-response="'data'" required/>

<van-field required :rules="[{ required: true }]" v-model="form.jydxmc" label="经营对象名称" placeholder="请输入" input-align="right" label-width="auto" />
<van-field v-model="form.jydxmc" label="经营对象名称" placeholder="请输入" input-align="right" label-width="auto" required :rules="[{ required: true }]"/>

<field-select v-model="form.jydxlx" label="经营对象证件类型" value-key="dictLabel" data-key="dictValue" placeholder="请选择" requiredx remote-url="/system/dict/data/type/zjlx" :on-remote-response="'data'"/>
<field-select v-model="form.jydxlx" label="经营对象证件类型" placeholder="请选择" value-key="dictLabel" data-key="dictValue" remote-url="/system/dict/data/type/zjlx" :on-remote-response="'data'"/>

<van-field v-model="form.jydxzjhm" label="经营对象证件号码" placeholder="请输入" input-align="right" label-width="auto" />

<field-radio v-model="form.sfqdht" label="是否签订合同" value-key="dictLabel" data-key="dictValue" remote-url="/system/dict/data/type/is_common" :on-remote-response="'data'" required/>

<field-date-picker name="jykssj" class="field_no-label" v-model="form.jykssj" label="经营开始时间" placeholder="请选择" formatter="yyyy-MM-dd" input-align="right" type="date" :required="true" size="large"/>
<field-date-picker v-model="form.jykssj" label="经营开始时间" placeholder="请选择" formatter="yyyy-MM-dd" input-align="right" type="date" :required="true" size="large"/>

<field-date-picker name="jyjssj" class="field_no-label" v-model="form.jyjssj" label="经营结束时间" placeholder="请选择" formatter="yyyy-MM-dd" input-align="right" type="date" :required="true" size="large"/>
<field-date-picker v-model="form.jyjssj" label="经营结束时间" placeholder="请选择" formatter="yyyy-MM-dd" input-align="right" type="date" :required="true" size="large"/>

<van-field required :rules="[{ required: true }]" v-model="form.cbje" label="承包金额(元)" placeholder="请输入" input-align="right" label-width="auto" />
<van-field v-model="form.dxje" label="兑现金额(元)" placeholder="请输入" input-align="right" label-width="auto" />
<van-field v-model="form.sqje" label="尚欠金额(元)" placeholder="请输入" input-align="right" label-width="auto" />
<van-field v-model="form.nsy" label="年收益(元)" placeholder="请输入" input-align="right" label-width="auto" />
<van-field v-model="form.cbje" label="承包金额(元)" placeholder="请输入" type="number" input-align="right" label-width="auto" required :rules="[{ required: true }]"/>
<van-field v-model="form.dxje" label="兑现金额(元)" placeholder="请输入" type="number" input-align="right" label-width="auto" />
<van-field v-model="form.sqje" label="尚欠金额(元)" placeholder="请输入" type="number" input-align="right" label-width="auto" />
<van-field v-model="form.nsy" label="年收益(元)" placeholder="请输入" type="number" input-align="right" label-width="auto" />
<van-field v-model="form.bzxx" label="备注信息" placeholder="请输入" input-align="right" label-width="auto" />

<field-radio v-model="form.surveyStatus" label="调查状态" value-key="dictLabel" data-key="dictValue" remote-url="/system/dict/data/type/survey_status" :on-remote-response="'data'" required/>

<van-field readonly label="实物图" placeholder="" input-align="right" label-width="auto" />
<!--<image-upload v-model="form.dkImg"/>-->
<CommonUpload name="dkImg" v-model="form.dkImg" accept="image/*" multiple :deletable="true" :show-upload="true"/>
<van-field label="实物图" placeholder="" input-align="right" label-width="auto" />
<CommonUpload v-model="form.dkImg" accept="image/*" multiple :deletable="true" :show-upload="true" fileField="file" />

</div>



Loading…
Cancel
Save