构建框架

This commit is contained in:
龙运模
2024-06-24 18:52:21 +08:00
commit 33942cbe95
328 changed files with 50716 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
<template>
<div class="sc-upload-file">
<el-upload
:disabled="disabled"
:auto-upload="autoUpload"
:action="action"
:name="name"
:data="data"
:http-request="request"
v-model:file-list="defaultFileList"
:show-file-list="showFileList"
:drag="drag"
:accept="accept"
:multiple="multiple"
:limit="limit"
:before-upload="before"
:on-success="success"
:on-error="error"
:on-preview="handlePreview"
:on-exceed="handleExceed">
<slot>
<el-button type="primary" :disabled="disabled">Click to upload</el-button>
</slot>
<template #tip>
<div v-if="tip" class="el-upload__tip">{{tip}}</div>
</template>
</el-upload>
<span style="display:none!important"><el-input v-model="value"></el-input></span>
</div>
</template>
<script>
import config from "@/config/upload"
export default {
props: {
modelValue: { type: [String, Array], default: "" },
tip: { type: String, default: "" },
action: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
name: { type: String, default: config.filename },
data: { type: Object, default: () => {} },
accept: { type: String, default: "" },
maxSize: { type: Number, default: config.maxSizeFile },
limit: { type: Number, default: 0 },
autoUpload: { type: Boolean, default: true },
showFileList: { type: Boolean, default: true },
drag: { type: Boolean, default: false },
multiple: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
onSuccess: { type: Function, default: () => { return true } }
},
data() {
return {
value: "",
defaultFileList: []
}
},
watch:{
modelValue(val){
if(Array.isArray(val)){
if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
this.defaultFileList = val
this.value = val
}
}else{
if (val != this.toStr(this.defaultFileList)) {
this.defaultFileList = this.toArr(val)
this.value = val
}
}
},
defaultFileList: {
handler(val){
this.$emit('update:modelValue', Array.isArray(this.modelValue) ? this.formatArr(val) : this.toStr(val))
this.value = this.toStr(val)
},
deep: true
}
},
mounted() {
this.defaultFileList = Array.isArray(this.modelValue) ? this.modelValue : this.toArr(this.modelValue)
this.value = this.modelValue
},
methods: {
//默认值转换为数组
toArr(str){
var _arr = []
var arr = str.split(",")
arr.forEach(item => {
if(item){
var urlArr = item.split('/');
var fileName = urlArr[urlArr.length - 1]
_arr.push({
name: fileName,
url: item
})
}
})
return _arr
},
//数组转换为原始值
toStr(arr){
return arr.map(v => v.url).join(",")
},
//格式化数组值
formatArr(arr){
var _arr = []
arr.forEach(item => {
if(item){
_arr.push({
name: item.name,
url: item.url
})
}
})
return _arr
},
before(file){
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
return false;
}
},
success(res, file){
var os = this.onSuccess(res, file)
if(os!=undefined && os==false){
return false
}
var response = config.parseData(res)
file.name = response.fileName
file.url = response.src
},
error(err){
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
beforeRemove(uploadFile){
return this.$confirm(`是否移除 ${uploadFile.name} ?`, '提示', {
type: 'warning',
}).then(() => {
return true
}).catch(() => {
return false
})
},
handleExceed(){
this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`)
},
handlePreview(uploadFile){
window.open(uploadFile.url)
},
request(param){
var apiObj = config.apiObjFile;
if(this.apiObj){
apiObj = this.apiObj;
}
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
var response = config.parseData(res);
if(response.code == config.successCode){
param.onSuccess(res)
}else{
param.onError(response.msg || "未知错误")
}
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style scoped>
.el-form-item.is-error .sc-upload-file:deep(.el-upload-dragger) {border-color: var(--el-color-danger);}
.sc-upload-file {width: 100%;}
.sc-upload-file:deep(.el-upload-list__item) {transition: none !important;}
</style>
+281
View File
@@ -0,0 +1,281 @@
<template>
<div class="sc-upload" :class="{'sc-upload-round':round}" :style="style">
<div v-if="file && file.status != 'success'" class="sc-upload__uploading">
<div class="sc-upload__progress">
<el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
</div>
<el-image class="image" :src="file.tempFile" fit="cover"></el-image>
</div>
<div v-if="file && file.status=='success'" class="sc-upload__img">
<el-image class="image" :src="file.url" :preview-src-list="[file.url]" fit="cover" hide-on-click-modal append-to-body :z-index="9999">
<template #placeholder>
<div class="sc-upload__img-slot">
Loading...
</div>
</template>
</el-image>
<div class="sc-upload__img-actions" v-if="!disabled">
<span class="del" @click="handleRemove()"><el-icon><el-icon-delete /></el-icon></span>
</div>
</div>
<el-upload v-if="!file" class="uploader" ref="uploader"
:auto-upload="cropper?false:autoUpload"
:disabled="disabled"
:show-file-list="showFileList"
:action="action"
:name="name"
:data="data"
:accept="accept"
:limit="1"
:http-request="request"
:on-change="change"
:before-upload="before"
:on-success="success"
:on-error="error"
:on-exceed="handleExceed">
<slot>
<div class="el-upload--picture-card">
<div class="file-empty">
<el-icon><component :is="icon" /></el-icon>
<h4 v-if="title">{{title}}</h4>
</div>
</div>
</slot>
</el-upload>
<span style="display:none!important"><el-input v-model="value"></el-input></span>
<el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="580" @closed="cropperClosed" destroy-on-close>
<sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio" ref="cropper"></sc-cropper>
<template #footer>
<el-button @click="cropperDialogVisible=false" >取 消</el-button>
<el-button type="primary" @click="cropperSave">确 定</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import { genFileId } from 'element-plus'
const scCropper = defineAsyncComponent(() => import('@/components/scCropper'))
import config from "@/config/upload"
export default {
props: {
modelValue: { type: String, default: "" },
height: {type: Number, default: 148},
width: {type: Number, default: 148},
title: { type: String, default: "" },
icon: { type: String, default: "el-icon-plus" },
action: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
name: { type: String, default: config.filename },
data: { type: Object, default: () => {} },
accept: { type: String, default: "image/gif, image/jpeg, image/png" },
maxSize: { type: Number, default: config.maxSizeFile },
limit: { type: Number, default: 1 },
autoUpload: { type: Boolean, default: true },
showFileList: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
round: { type: Boolean, default: false },
onSuccess: { type: Function, default: () => { return true } },
cropper: { type: Boolean, default: false },
compress: {type: Number, default: 1},
aspectRatio: {type: Number, default: NaN}
},
components: {
scCropper
},
data() {
return {
value: "",
file: null,
style: {
width: this.width + "px",
height: this.height + "px"
},
cropperDialogVisible: false,
cropperFile: null
}
},
watch:{
modelValue(val){
this.value = val
this.newFile(val)
},
value(val){
this.$emit('update:modelValue', val)
}
},
mounted() {
this.value = this.modelValue
this.newFile(this.modelValue)
},
methods: {
newFile(url){
if(url){
this.file = {
status: "success",
url: url
}
}else{
this.file = null
}
},
cropperSave(){
this.$refs.cropper.getCropFile(file => {
file.uid = this.cropperFile.uid
this.cropperFile.raw = file
this.file = this.cropperFile
this.file.tempFile = URL.createObjectURL(this.file.raw)
this.$refs.uploader.submit()
}, this.cropperFile.name, this.cropperFile.type)
this.cropperDialogVisible = false
},
cropperClosed(){
URL.revokeObjectURL(this.cropperFile.tempCropperFile)
delete this.cropperFile.tempCropperFile
},
handleRemove(){
this.clearFiles()
},
clearFiles(){
URL.revokeObjectURL(this.file.tempFile)
this.value = ""
this.file = null
this.$nextTick(()=>{
this.$refs.uploader.clearFiles()
})
},
change(file,files){
if(files.length > 1){
files.splice(0, 1)
}
if(this.cropper && file.status=='ready'){
const acceptIncludes = ["image/gif", "image/jpeg", "image/png"].includes(file.raw.type)
if(!acceptIncludes){
this.$notify.warning({
title: '上传文件警告',
message: '选择的文件非图像类文件'
})
return false
}
this.cropperFile = file
this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw)
this.cropperDialogVisible = true
return false
}
this.file = file
if(file.status=='ready'){
file.tempFile = URL.createObjectURL(file.raw)
}
},
before(file){
const acceptIncludes = this.accept.replace(/\s/g,"").split(",").includes(file.type)
if(!acceptIncludes){
this.$notify.warning({
title: '上传文件警告',
message: '选择的文件非图像类文件'
})
this.clearFiles()
return false
}
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
this.clearFiles()
return false
}
},
handleExceed(files){
const file = files[0]
file.uid = genFileId()
this.$refs.uploader.handleStart(file)
},
success(res, file){
//释放内存删除blob
URL.revokeObjectURL(file.tempFile)
delete file.tempFile
var os = this.onSuccess(res, file)
if(os!=undefined && os==false){
this.$nextTick(() => {
this.file = null
this.value = ""
})
return false
}
var response = config.parseData(res)
file.url = response.src
this.value = file.url
},
error(err){
this.$nextTick(()=>{
this.clearFiles()
})
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
request(param){
var apiObj = config.apiObj;
if(this.apiObj){
apiObj = this.apiObj;
}
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
var response = config.parseData(res);
if(response.code == config.successCode){
param.onSuccess(res)
}else{
param.onError(response.msg || "未知错误")
}
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style scoped>
.el-form-item.is-error .sc-upload .el-upload--picture-card {border-color: var(--el-color-danger);}
.sc-upload .el-upload--picture-card {border-radius: 0;}
.sc-upload .uploader,.sc-upload:deep(.el-upload) {width: 100%;height: 100%;}
.sc-upload__img {width: 100%;height: 100%;position: relative;}
.sc-upload__img .image {width: 100%;height: 100%;}
.sc-upload__img-actions {position: absolute;top:0;right: 0;display: none;}
.sc-upload__img-actions span {display: flex;justify-content: center;align-items: center;width: 25px;height:25px;cursor: pointer;color: #fff;}
.sc-upload__img-actions span i {font-size: 12px;}
.sc-upload__img-actions .del {background: #F56C6C;}
.sc-upload__img:hover .sc-upload__img-actions {display: block;}
.sc-upload__img-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;background-color: var(--el-fill-color-lighter);}
.sc-upload__uploading {width: 100%;height: 100%;position: relative;}
.sc-upload__progress {position: absolute;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: var(--el-overlay-color-lighter);z-index: 1;padding:10px;}
.sc-upload__progress .el-progress {width: 100%;}
.sc-upload__uploading .image {width: 100%;height: 100%;}
.sc-upload .file-empty {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;flex-direction: column;}
.sc-upload .file-empty i {font-size: 28px;}
.sc-upload .file-empty h4 {font-size: 12px;font-weight: normal;color: #8c939d;margin-top: 8px;}
.sc-upload.sc-upload-round {border-radius: 50%;overflow: hidden;}
.sc-upload.sc-upload-round .el-upload--picture-card {border-radius: 50%;}
.sc-upload.sc-upload-round .sc-upload__img-actions {top: auto;left: 0;right: 0;bottom: 0;}
.sc-upload.sc-upload-round .sc-upload__img-actions span {width: 100%;}
</style>
+249
View File
@@ -0,0 +1,249 @@
<template>
<div class="sc-upload-multiple">
<el-upload ref="uploader" list-type="picture-card"
:auto-upload="autoUpload"
:disabled="disabled"
:action="action"
:name="name"
:data="data"
:http-request="request"
v-model:file-list="defaultFileList"
:show-file-list="showFileList"
:accept="accept"
:multiple="multiple"
:limit="limit"
:before-upload="before"
:on-success="success"
:on-error="error"
:on-preview="handlePreview"
:on-exceed="handleExceed">
<slot>
<el-icon><el-icon-plus/></el-icon>
</slot>
<template #tip>
<div v-if="tip" class="el-upload__tip">{{tip}}</div>
</template>
<template #file="{ file }">
<div class="sc-upload-list-item">
<el-image class="el-upload-list__item-thumbnail" :src="file.url" fit="cover" :preview-src-list="preview" :initial-index="preview.findIndex(n=>n==file.url)" hide-on-click-modal append-to-body :z-index="9999">
<template #placeholder>
<div class="sc-upload-multiple-image-slot">
Loading...
</div>
</template>
</el-image>
<div v-if="!disabled && file.status=='success'" class="sc-upload__item-actions">
<span class="del" @click="handleRemove(file)"><el-icon><el-icon-delete /></el-icon></span>
</div>
<div v-if="file.status=='ready' || file.status=='uploading'" class="sc-upload__item-progress">
<el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
</div>
</div>
</template>
</el-upload>
<span style="display:none!important"><el-input v-model="value"></el-input></span>
</div>
</template>
<script>
import config from "@/config/upload"
import Sortable from 'sortablejs'
export default {
props: {
modelValue: { type: [String, Array], default: "" },
tip: { type: String, default: "" },
action: { type: String, default: "" },
apiObj: { type: Object, default: () => {} },
name: { type: String, default: config.filename },
data: { type: Object, default: () => {} },
accept: { type: String, default: "image/gif, image/jpeg, image/png" },
maxSize: { type: Number, default: config.maxSizeFile },
limit: { type: Number, default: 0 },
autoUpload: { type: Boolean, default: true },
showFileList: { type: Boolean, default: true },
multiple: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
draggable: { type: Boolean, default: false },
onSuccess: { type: Function, default: () => { return true } }
},
data(){
return {
value: "",
defaultFileList: []
}
},
watch:{
modelValue(val){
if(Array.isArray(val)){
if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
this.defaultFileList = val
this.value = val
}
}else{
if (val != this.toStr(this.defaultFileList)) {
this.defaultFileList = this.toArr(val)
this.value = val
}
}
},
defaultFileList: {
handler(val){
this.$emit('update:modelValue', Array.isArray(this.modelValue) ? this.formatArr(val) : this.toStr(val))
this.value = this.toStr(val)
},
deep: true
}
},
computed: {
preview(){
return this.defaultFileList.map(v => v.url)
}
},
mounted() {
this.defaultFileList = Array.isArray(this.modelValue) ? this.modelValue : this.toArr(this.modelValue)
this.value = this.modelValue
if(!this.disabled && this.draggable){
this.rowDrop()
}
},
methods: {
//默认值转换为数组
toArr(str){
var _arr = [];
var arr = str.split(",");
arr.forEach(item => {
if(item){
var urlArr = item.split('/');
var fileName = urlArr[urlArr.length - 1]
_arr.push({
name: fileName,
url: item
})
}
})
return _arr;
},
//数组转换为原始值
toStr(arr){
return arr.map(v => v.url).join(",")
},
//格式化数组值
formatArr(arr){
var _arr = []
arr.forEach(item => {
if(item){
_arr.push({
name: item.name,
url: item.url
})
}
})
return _arr
},
//拖拽
rowDrop(){
const _this = this
const itemBox = this.$refs.uploader.$el.querySelector('.el-upload-list')
Sortable.create(itemBox, {
handle: ".el-upload-list__item",
animation: 200,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
const tableData = _this.defaultFileList
const currRow = tableData.splice(oldIndex, 1)[0]
tableData.splice(newIndex, 0, currRow)
}
})
},
before(file){
if(!['image/jpeg','image/png','image/gif'].includes(file.type)){
this.$message.warning(`选择的文件类型 ${file.type} 非图像类文件`);
return false;
}
const maxSize = file.size / 1024 / 1024 < this.maxSize;
if (!maxSize) {
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
return false;
}
},
success(res, file){
var os = this.onSuccess(res, file)
if(os!=undefined && os==false){
return false
}
var response = config.parseData(res)
file.name = response.fileName
file.url = response.src
},
error(err){
this.$notify.error({
title: '上传文件未成功',
message: err
})
},
beforeRemove(uploadFile){
return this.$confirm(`是否移除 ${uploadFile.name} ?`, '提示', {
type: 'warning',
}).then(() => {
return true
}).catch(() => {
return false
})
},
handleRemove(file){
this.$refs.uploader.handleRemove(file)
//this.defaultFileList.splice(this.defaultFileList.findIndex(item => item.uid===file.uid), 1)
},
handleExceed(){
this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`)
},
handlePreview(uploadFile){
window.open(uploadFile.url)
},
request(param){
var apiObj = config.apiObj;
if(this.apiObj){
apiObj = this.apiObj;
}
const data = new FormData();
data.append(param.filename, param.file);
for (const key in param.data) {
data.append(key, param.data[key]);
}
apiObj.post(data, {
onUploadProgress: e => {
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
param.onProgress({percent: complete})
}
}).then(res => {
var response = config.parseData(res);
if(response.code == config.successCode){
param.onSuccess(res)
}else{
param.onError(response.msg || "未知错误")
}
}).catch(err => {
param.onError(err)
})
}
}
}
</script>
<style scoped>
.el-form-item.is-error .sc-upload-multiple:deep(.el-upload--picture-card) {border-color: var(--el-color-danger);}
:deep(.el-upload-list__item) {transition:none;border-radius: 0;}
.sc-upload-multiple:deep(.el-upload-list__item.el-list-leave-active) {position: static!important;}
.sc-upload-multiple:deep(.el-upload--picture-card) {border-radius: 0;}
.sc-upload-list-item {width: 100%;height: 100%;position: relative;}
.sc-upload-multiple .el-image {display: block;}
.sc-upload-multiple .el-image:deep(img) {-webkit-user-drag: none;}
.sc-upload-multiple-image-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;}
.sc-upload-multiple .el-upload-list__item:hover .sc-upload__item-actions{display: block;}
.sc-upload__item-actions {position: absolute;top:0;right: 0;display: none;}
.sc-upload__item-actions span {display: flex;justify-content: center;align-items: center;;width: 25px;height:25px;cursor: pointer;color: #fff;}
.sc-upload__item-actions span i {font-size: 12px;}
.sc-upload__item-actions .del {background: #F56C6C;}
.sc-upload__item-progress {position: absolute;width: 100%;height: 100%;top: 0;left: 0;background-color: var(--el-overlay-color-lighter);}
</style>