fix 添加附件管理器,代码生成支持附件选择器,人员选择器,部门选择器,移除旧版大文件上传,大文件上传与附件管理器合并

This commit is contained in:
yxh 2024-11-19 10:24:20 +08:00
parent 08fa5817f3
commit bf3bec9b63
31 changed files with 2187 additions and 960 deletions

547
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
"echarts": "^5.5.0",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
"element-plus": "^2.8.7",
"js-cookie": "^3.0.5",
"jsplumb": "^2.15.6",
"lodash": "^4.17.21",
@ -57,8 +57,8 @@
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"prettier": "^3.2.5",
"sass": "^1.72.0",
"sass-loader": "^13.0.2",
"sass": "^1.80.7",
"sass-loader": "^16.0.3",
"typescript": "^5.4.2",
"vite": "5.4.6",
"vite-plugin-cdn-import": "^0.3.5",

View File

@ -937,21 +937,18 @@
if (!_this.listEnd && !this.isLoadingData) {
this.isLoadingData = true;
var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),
isJsonp = utils.isCrossDomainUrl(url);
var url = editor.getActionUrl(editor.getOpt('imageManagerActionName'));
ajax.request(url, {
'timeout': 100000,
'dataType': isJsonp ? 'jsonp' : '',
'headers': editor.options.serverHeaders || {},
'data': utils.extend({
timeout: 100000,
data: utils.extend({
start: this.listIndex,
size: this.listSize
}, editor.queryCommandValue('serverparam')),
'method': 'get',
headers: editor.options.serverHeaders || {},
method: 'get',
'onsuccess': function (r) {
try {
var json = isJsonp ? r : eval('(' + r.responseText + ')');
json = editor.options.serverResponsePrepare(json);
var json = eval('(' + r.responseText + ')');
if (json.state === 'SUCCESS') {
_this.pushData(json.list);
_this.listIndex = parseInt(json.start) + parseInt(json.list.length);

View File

@ -5,7 +5,6 @@
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<CloseFull v-if="!themeConfig.isLockScreen" />
</el-config-provider>
<BigUploader></BigUploader>
</template>
<script lang="ts">
@ -20,11 +19,10 @@ import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue';
import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
import BigUploader from '/@/components/bigUploader/index.vue'
export default defineComponent({
name: 'app',
components: { LockScreen, Setings, CloseFull,BigUploader },
components: { LockScreen, Setings, CloseFull},
setup() {
const { proxy } = <any>getCurrentInstance();
const setingsRef = ref();

View File

@ -0,0 +1,90 @@
import request from '/@/utils/request'
import axios, {AxiosProgressEvent, CancelTokenSource} from "axios";
import {baseURL} from "/@/utils/gfast";
// 查询附件管理列表
export function listSysAttachment(query:object) {
return request({
url: '/api/v1/system/sysAttachment/list',
method: 'get',
params: query
})
}
// 查询附件管理详细
export function getSysAttachment(id:number) {
return request({
url: '/api/v1/system/sysAttachment/get',
method: 'get',
params: {
id: id.toString()
}
})
}
// 新增附件管理
export function addSysAttachment(data:object) {
return request({
url: '/api/v1/system/sysAttachment/add',
method: 'post',
data: data
})
}
// 修改附件管理
export function updateSysAttachment(data:object) {
return request({
url: '/api/v1/system/sysAttachment/edit',
method: 'put',
data: data
})
}
// 删除附件管理
export function delSysAttachment(ids:number[]) {
return request({
url: '/api/v1/system/sysAttachment/delete',
method: 'delete',
data:{
ids:ids
}
})
}
// 附件管理状态修改
export function changeSysAttachmentStatus(id:number,status:boolean) {
const data = {
id,
status
}
return request({
url: '/api/v1/system/sysAttachment/changeStatus',
method: 'put',
data:data
})
}
// 检查文件分片
export function checkMultipart(data:Object) {
return request({
url: '/api/v1/system/upload/checkMultipart',
method: 'post',
data:data
})
}
type Callback = (result: AxiosProgressEvent) => void;
//上传分片
export function uploadPart(data:Object,progress:Callback,cancelToken:CancelTokenSource) {
/*return axios.post(baseURL+'/api/v1/system/upload/uploadPart', data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})*/
return axios({
url: baseURL+'/api/v1/system/upload/uploadPart',
method: 'post',
data:data,
headers: {
'Content-Type': 'multipart/form-data'
},
cancelToken:cancelToken.token,
onUploadProgress: function (e) {
progress(e)
}
})
}

View File

@ -1,371 +0,0 @@
<template>
<div class="uploader-min" v-show="!panelShow" @click="openHandle">
<el-icon style="vertical-align: middle"><ele-DArrowLeft/></el-icon>
</div>
<div class="uploader-container" v-show="panelShow">
<div class="header">
<span>文件列表</span>
<span class="shrink" @click="closeHandle">
<el-icon style="vertical-align: middle"><ele-DArrowRight /></el-icon>
</span>
</div>
<div class="box">
<uploader :options="options"
:file-status-text="handleFileStatusText"
:autoStart="false"
@file-added="onFileAdded"
@file-success="onFileSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
class="uploader-example">
<uploader-unsupport></uploader-unsupport>
<div v-show="false">
<uploader-btn ref="uploadBtn" :attrs="all">选择文件</uploader-btn>
<uploader-btn :attrs="image">选择图片</uploader-btn>
<uploader-btn :directory="true">选择文件夹</uploader-btn>
</div>
<uploader-list>
<template v-slot="props">
<ul class="file-list">
<li v-for="file in props.fileList" :key="file.id">
<uploader-file :ref="'file_' + file.id" :class="'file_' + file.id" :file="file" :list="true"></uploader-file>
</li>
<div class="no-file" v-if="!props.fileList.length">暂无待上传文件</div>
</ul>
</template>
</uploader-list>
</uploader>
</div>
</div>
</template>
<script>
import SparkMD5 from 'spark-md5';
import axios from "axios"
import {bigUpload} from "/@/stores/bigUpload";
import {getToken} from "/@/utils/gfast"
const stores = bigUpload();
const baseURL = import.meta.env.VITE_API_URL
const instance = axios.create({
baseURL: baseURL,
timeout: 1000 * 30,
});
const acceptConfig = {
image: ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'webp'],
video: ['mp4', 'm3u8', 'rmvb', 'avi', 'swf', '3gp', 'mkv', 'flv'],
audio: ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac'],
document: ['doc', 'txt', 'docx', 'pages', 'epub', 'pdf', 'numbers', 'csv', 'xls', 'xlsx', 'keynote', 'ppt', 'pptx'],
all() {
return [...this.image, ...this.video, ...this.audio, ...this.document]
}
}
export default {
name: "bigUploader",
data () {
return {
options: {
target: baseURL + 'api/v1/system/bigUpload/upload', // URL
chunkSize: '2048000', //
fileParameterName: 'upfile', // file
maxChunkRetries: 3, //
testChunks: true, //
//
// XHR Uploader.Chunk
//
checkChunkUploadedByResponse: function (chunk, message) {
try {
let objMessage = JSON.parse(message)
const {code, data} = objMessage
if (code === 0) {
if (data.skipUpload) {
return true
}
return (data.uploaded || []).indexOf(chunk.offset + 1) >= 0
}
} catch (e) {
console.error(e)
}
return false
},
headers: {
Authorization:null
},
query: (file, chunk) => {
return {
...file.params,
}
},
},
image: {
accept: 'image/*'
},
all: {
accept: acceptConfig.all()
},
statusText: {
success: '成功了',
error: '出错了',
uploading: '上传中',
paused: '暂停中',
waiting: '等待中'
}
}
},
mounted() {
this.$nextTick(()=> {
this.mittBus.on("bigUploader.uploadFile", this.uploadFile)
})
},
computed:{
panelShow() {
return stores.panelShow
},
uploadBtn () {
return this.$refs.uploadBtn && this.$refs.uploadBtn.btn
}
},
methods: {
openHandle () {
stores.setPanelShow(true)
},
closeHandle() {
stores.setPanelShow(false)
},
uploadFile () {
// console.log("uploadFile")
if(!this.options.headers.Authorization){
this.options.headers.Authorization = "Bearer "+getToken()
}
if (this.uploadBtn) {
this.uploadBtn.click()
}
},
handleFileStatusText (status, response) {
//console.log(status, response)
if (status === "success") {
const {code, data} = response
if (code === 0 && data.needMerge === true) {
return "文件合并中..."
} else {
return this.statusText[status]
}
} else {
return this.statusText[status]
}
},
//
onFileError(rootFile, file, response, chunk) {
console.error(rootFile, file, response, chunk)
},
//
setStatus (id, text) {
this.$nextTick(()=> {
const el = this.$refs['file_'+ id][0].$el.getElementsByClassName("uploader-file-status")[0].getElementsByTagName("span")[0]
const parent = el.parentNode
const para = document.createElement("span")
para.appendChild(document.createTextNode(text));
para.className = "para"
el.style.display = 'none'
parent.appendChild(para)
})
},
//
removeStatus (id) {
try{
const els = this.$refs['file_'+ id][0].$el.getElementsByClassName("uploader-file-status")[0].getElementsByClassName("para")
if (els && els.length > 0) {
const parent = els[0].parentNode
for (let i = 0; i < els.length; i++) {
parent.removeChild(els[i])
}
}
const firstSpan = this.$refs['file_'+ id][0].$el.getElementsByClassName("uploader-file-status")[0].getElementsByTagName("span")[0]
firstSpan.style.display = ''
} catch (e) {
console.error(e)
}
},
/**
文件上传成功事件
第一个参数 rootFile 就是成功上传的文件所属的根 Uploader.File 对象它应该包含或者等于成功上传文件
第二个参数 file 就是当前成功的 Uploader.File 对象本身
第三个参数 message 就是服务端响应内容永远都是字符串
第四个参数 chunk 就是 Uploader.Chunk 实例 它就是该文件的最后一个块实例如果你想得到请求响应码的话chunk.xhr.status 就是
*/
onFileSuccess(rootFile, file, response, chunk) {
try{
// console.log(rootFile, file, response, chunk)
let res = JSON.parse(response);
// console.log(res)
const {code, data} = res
if (code === 0) {
//
if (data.needMerge) {
//
instance.post(baseURL+'api/v1/system/bigUpload/uploadMerge', {
identifier: data.identifier,
totalChunks: data.totalChunks,
totalSize: data.totalSize,
filename: data.filename,
token:getToken()
}).then(res=> {
const {status, data} = res
return data
}).then(res => {
let {code, data} = res
if(code === 0) {
this.setStatus(file.id, this.statusText["success"])
this.mittBus.emit("bigUploader.uploadFileSuccess", {...data, fileType: file.fileType})
}
})
} else {
//
//
this.mittBus.emit("bigUploader.uploadFileSuccess", {...data, fileType: file.fileType})
}
}
} catch (e) {
console.error(e)
}
},
//
onFileProgress(rootFile, file, chunk) {
console.log(`上传中 ${file.name}chunk${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
},
// false
onFileAdded(file) {
this.computeMD5(file);
// 2022/1/10 使params
file.params = this.params
this.openHandle()
},
computeMD5(file) {
let fileReader = new FileReader();
let time = new Date().getTime();
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
//
let currentChunk = 0;
//
const chunkSize = 10 * 1024 * 1000;
//
let chunks = Math.ceil(file.size / chunkSize);
let spark = new SparkMD5.ArrayBuffer();
this.setStatus(file.id, "md5计算中")
file.pause();
loadNext();
fileReader.onload = (e => {
spark.append(e.target.result);
if (currentChunk < chunks) {
currentChunk++;
loadNext();
// MD5
this.$nextTick(() => {
console.log('校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%')
})
} else {
let md5 = spark.end();
this.computeMD5Success(md5, file);
this.removeStatus(file.id)
console.log(`MD5计算完毕${file.name} \nMD5${md5} \n分片${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
}
});
fileReader.onerror = function () {
console.error(`文件${file.name}读取出错,请检查该文件`)
file.cancel();
};
function loadNext() {
let start = currentChunk * chunkSize;
let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
}
},
// md5
computeMD5Success(md5, file) {
file.uniqueIdentifier = md5;
file.resume();
}
}
}
</script>
<style scoped lang="scss">
.uploader-min {
background-color:#fff;
z-index: 9999;
position: absolute;
right: 0;
bottom:10px;
box-shadow: 0 0 10px rgba(0, 0, 0, .4);
padding:15px;
cursor: pointer;
}
.uploader-container {
background-color:#fff;
z-index: 9999;
position: absolute;
right: 0;
bottom:10px;
box-shadow: 0 0 10px rgba(0, 0, 0, .4);
}
.header {
background-color:#fff;
height:35px;
line-height: 35px;
display: flex;
padding: 0 10px;
justify-content:space-between;
.shrink {
cursor: pointer;
}
}
.box {
max-height: 200px;
overflow-y: scroll;
}
.uploader-example {
width: 550px;
padding: 15px;
font-size: 12px;
}
.uploader-example .uploader-btn {
margin-right: 4px;
}
.uploader-example .uploader-list {
overflow-x: hidden;
overflow-y: hidden;
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<el-cascader
filterable
:options="deptData"
:props="{ checkStrictly: true,emitPath: false, value: 'deptId', label: 'deptName',multiple: multiple }"
placeholder="请选择部门"
clearable
class="w100"
v-model="deptIds"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</template>
<script setup lang="ts">
import {computed, onMounted, ref} from "vue";
import {getDeptTree} from "/@/api/system/user";
defineOptions({ name: "selectDept"})
const props = defineProps({
multiple:{
type:Boolean,
default:false
},
modelValue:{
type:[Array,Number],
default:()=>[]
}
})
const deptIds = computed({
get: () => {
if(props.multiple){
return props.modelValue as number[]
}else{
return props.modelValue instanceof Array ? props.modelValue[0] : props.modelValue
}
},
set: (value) => {
emit('update:modelValue', value)
}
})
const emit = defineEmits(['update:modelValue'])
const deptData = ref([])
onMounted(()=>{
getDeptTree().then((res)=>{
deptData.value = res.data.deps
})
})
</script>
<style scoped lang="scss">
</style>

View File

@ -1,5 +1,13 @@
<template>
<div>
<div v-if="deptUser.length > 0">
<el-tag closable :disable-transitions="false" class="u-m-r-10" v-for="(item,index) in deptUser" :key="'sel_'+index" @close="handleClose(index)" >{{item.userNickname}}</el-tag>
</div>
<el-button
style="padding-left: 10px;"
type="primary"
link
@click="handleSelectUser" >请选择</el-button>
<el-dialog title="选择用户" v-model="visible" width="80%" top="5vh" append-to-body :close-on-click-modal="false">
<div class="system-user-container">
<el-row :gutter="10" style="width: 100%;">
@ -61,7 +69,7 @@
</el-form-item>
</el-form>
</div>
<UserList ref="userListRef" :dept-data="deptData" :gender-data="sys_user_sex" :param="param" :multiple="multiple" @ok="handleSelectUserOk"/>
<UserList ref="userListRef" :dept-data="deptData" :gender-data="sys_user_sex" :param="param" @ok="handleSelectUserOk"/>
</el-card>
</el-col>
<el-col :span="6">
@ -97,11 +105,11 @@
</template>
<script lang="ts">
import {toRefs, reactive, ref, defineComponent, watch, getCurrentInstance, nextTick, computed} from 'vue';
import {toRefs, reactive, ref, defineComponent, watch, getCurrentInstance, nextTick, computed, onMounted} from 'vue';
import {ElTree,FormInstance} from 'element-plus';
import { Search } from '@element-plus/icons-vue'
import UserList from './component/userList.vue';
import {getDeptTree} from '/@/api/system/user/index';
import {getDeptTree, getUserByIds} from '/@/api/system/user/index';
interface QueryParam {
ids:number[];
@ -122,14 +130,14 @@ export default defineComponent({
props:{
multiple:{
type:Boolean,
default:false
default:true
},
selectedUsers:{
type:Array,
modelValue:{
type:Array<number>,
default:()=>[]
}
},
emits:['selectUser','okBack'],
emits:['update:modelValue'],
setup(prop,{emit}) {
const selectedUsersPage = ref(1)
const visible = ref(false)
@ -140,6 +148,39 @@ export default defineComponent({
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
const search = Search
const deptUser = ref<any>([]);
const selectedUsers = computed({
get: ()=>{
return prop.modelValue??[]
},
set:(val:any[])=>{
emit('update:modelValue',val)
}
})
const selectedUserInfo = computed(()=>{
let start = (selectedUsersPage.value-1)*10
let end = start+10
return deptUser.value.slice(start,end)
});
const initData = ()=>{
if(prop.modelValue&&prop.modelValue.length>0){
getUserByIds({ids:prop.modelValue}).then((res:any)=>{
if(res.code === 0){
deptUser.value = res.data.userList;
}
});
}else{
deptUser.value = []
}
};
onMounted(()=>{
initData()
})
watch(selectedUsers,(value, oldValue)=>{
if(value!=oldValue){
initData()
}
})
const state = reactive<QueryParam>({
ids:[],
deptProps:{
@ -156,16 +197,6 @@ export default defineComponent({
dateRange:[]
},
});
const selectedUserInfo = computed({
get:()=>{
let start = (selectedUsersPage.value-1)*10
let end = start+10
return prop.selectedUsers.slice(start,end)
} ,
set:(v)=>{
emit("selectUser",v);
}
});
const getUserList = ()=>{
userListRef.value.setUserList();
};
@ -202,21 +233,35 @@ export default defineComponent({
})
}
const handleSelectUserOk = (row:any)=>{
selectedUserInfo.value = [...selectedUserInfo.value,row]
if(!prop.multiple){
selectedUsers.value = [row.id]
}else{
if(!selectedUsers.value.includes(row.id)){
selectedUsers.value = [...selectedUsers.value!,row.id]
}
}
}
const goBack = ()=>{
visible.value = false;
emit("okBack");
}
const removeAll = ()=>{
selectedUserInfo.value = []
selectedUsers.value = []
}
const remove = (index:number)=>{
index = (selectedUsersPage.value-1)*10+index
let newSel:any = [...selectedUserInfo.value]
selectedUserInfo.value = []
let newSel:any = [...selectedUsers.value!]
selectedUsers.value = []
newSel.splice(index,1)
selectedUserInfo.value = newSel
selectedUsers.value = newSel
}
const handleSelectUser = ()=>{
openDialog()
}
const handleClose = (index:number)=>{
let newSel:any = [...selectedUsers.value!]
newSel.splice(index, 1);
selectedUsers.value = newSel
}
return {
selectedUsersPage,
@ -228,6 +273,8 @@ export default defineComponent({
treeRef,
search,
sys_user_sex,
selectedUsers,
deptUser,
selectedUserInfo,
openDialog,
getUserList,
@ -237,8 +284,15 @@ export default defineComponent({
goBack,
removeAll,
remove,
handleClose,
handleSelectUser,
...toRefs(state),
};
},
});
</script>
<style scoped>
.u-m-r-10{
margin-right: 8px;
}
</style>

View File

@ -30,7 +30,7 @@ export default defineComponent({
initialFrameHeight: 400,
maximumWords: 5000,
topOffset: 80,
zIndex:2020
zIndex:2050
}
}
},
@ -41,6 +41,7 @@ export default defineComponent({
}
}
},
emits:['update:modelValue'],
setup(props,{emit}){
const config = Object.assign({
elementPathEnabled: false,
@ -59,7 +60,7 @@ export default defineComponent({
return props.modelValue
},
set:(newVal)=>{
emit('setEditContent',newVal)
emit('update:modelValue',newVal)
}
})
return {

View File

@ -0,0 +1,273 @@
<template>
<el-upload
ref="upBigFileRef"
v-model:file-list="dataFileList"
class="upload-demo"
:multiple="multiple"
:drag="drag"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:limit="limit"
:on-exceed="handleExceed"
:on-change="handleChange"
:on-preview="handlePreview"
:http-request="handleRequest"
:disabled="uploadStatus!==0"
>
<template v-if="uploadStatus===0">
<el-icon class="el-icon--upload"><ele-UploadFilled /></el-icon>
<div class="el-upload__text">
拖拽文件至此 <em>点击上传</em>
</div>
</template>
<template v-if="uploadStatus===1">
<el-icon class="el-icon--upload"><ele-Loading /></el-icon>
<div class="el-upload__text">
文件解析中...{{uploadStatusContent}}
</div>
</template>
<template v-if="uploadStatus===2">
<el-icon class="el-icon--upload"><ele-Loading /></el-icon>
<div class="el-upload__text">
文件上传中...{{progress}}%文件大小{{fileFormatSize}}
</div>
</template>
<template #tip>
<div class="progress-show" v-if="uploadStatus===2">
<el-progress :percentage="progress" />
</div>
<div class="el-upload__tip">
大文件上传支持分片上传断点续传及秒传功能
</div>
</template>
</el-upload>
</template>
<script setup lang="ts">
import SparkMD5 from 'spark-md5';
import {computed, getCurrentInstance, reactive, ref} from "vue";
import type {UploadFile, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile} from 'element-plus'
import { ElMessage ,ElMessageBox} from 'element-plus'
import {getToken} from "/@/utils/gfast";
import _ from 'lodash'
import {checkMultipart, uploadPart} from "/@/api/system/sysAttachment";
import axios, {AxiosProgressEvent} from "axios";
defineOptions({ name: "uploadBigFile"})
const props = defineProps({
name: { type : String, default : 'file' },//
method: { type : String, default : 'post' },//
multiple: { type : Boolean, default : true },//
showFileList: { type : Boolean, default : true },//
drag: { type : Boolean, default : true },//
disabled: { type : Boolean, default : false },//
listType: { type : String, default : 'picture-card' },//
limit: { type : Number, default : 5 },//
modelValue:{
type:Array,
default:function(){
return []
}
}
})
const emit = defineEmits(['update:modelValue'])
const upBigFileRef = ref()
const uploadStatus = ref(0); // 0 1 2 3
const uploadStatusContent= ref('')
const progress = ref(0)
const fileFormatSize = ref('0byte')
let uploadedFile:Array<any> = [] ;
const {proxy} = <any>getCurrentInstance();
const dataFileList = computed({
get: () => {
let value:Array<UploadUserFile> = props.modelValue as UploadUserFile[]|| [];
value.map((item: UploadUserFile)=>{
if(item.url){
item.url = proxy.getUpFileUrl(item.url)
}
return item
})
uploadedFile = _.cloneDeep(value)
return value
},
set: val => {
emit('update:modelValue', val)
}
});
const handleRequest = (options: UploadRequestOptions) => {}
const cancelToken = axios.CancelToken.source();
const handleChange: UploadProps['onChange'] = async (uploadFile: UploadFile) => {
const {name,size,raw:file} = uploadFile
fileFormatSize.value = formatFileSize(size as number)
try{
//md5
uploadStatus.value = 1 //
const md5 = await calculateMD5(file as File)
uploadStatus.value = 2
let currentChunkIndex = 1
//
const chunkSize = 1024 * 1024 * 2; // 2M
//
const shards = Array.from({length: Math.ceil(size as number / chunkSize)}, (_, index) => index+1);
//
const checkPartRes:any = await checkMultipart({
fileName: name,
size: size,
md5: md5,
shardsCount: shards.length,
})
// ()
if (!checkPartRes.data.waitUploadIndex || checkPartRes.data.waitUploadIndex.length == 0) {
upOver(checkPartRes.data.attachment,uploadFile)
return;
}
//
const upShards = shards.filter((shard) => checkPartRes.data.waitUploadIndex.includes(shard));
if (upShards.length == 0) {
upOver(checkPartRes.data.attachment,uploadFile)
return;
}
let uploadedSize = (shards.length-upShards.length-1)*chunkSize;
for (const index of upShards) {
if (uploadStatus.value == 3) {
break;
}
let start = (index-1) * chunkSize;
let end = index * chunkSize;
uploadedSize+=chunkSize
const progressFn = (result: AxiosProgressEvent)=>{
//
progress.value = Math.round((uploadedSize+result.loaded)/size!*100);
}
const res = await uploadPart({
fileName: name,
size: size,
md5: md5,
shardsCount: shards.length,
index: index,
file: file!.slice(start, end),
token:getToken(),
},progressFn,cancelToken);
currentChunkIndex = index;
if (res.data.data.finish){
upOver(res.data.data.attachment,uploadFile)
}
}
}catch (e:any){
if(e.code==="ERR_CANCELED"){
ElMessage.error("已关闭上传,下次上传将继续从断点处上传")
}else{
console.log('error:',e)
}
clearUpStatus();
}
};
const upOver = (attachment:any,uploadFile: UploadFile)=>{
uploadedFile=uploadedFile.filter((item:UploadUserFile)=>{
return item.raw?.uid != uploadFile.raw?.uid
})
uploadedFile.push({
name:attachment.name,
url:attachment.path,
fullUrl:attachment.fullPath,
fileType:attachment.type,
size:attachment.size
})
setDataFileList();
clearUpStatus();
}
const handleExceed = () => {
ElMessage.error('最多可上传'+props.limit+'个文件,已超出最大限制数。');
}
const beforeRemove: UploadProps['beforeRemove'] = (uploadFile) => {
return ElMessageBox.confirm(
`您确定要删除 ${uploadFile.name} ?`,
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(
() => true,
() => false
)
};
const handleRemove: UploadProps['onRemove'] = (file) => {//
uploadedFile.splice(uploadedFile.findIndex((item: any) => item.name === file.name),1)
setDataFileList()
};
const setDataFileList = () => {
dataFileList.value = uploadedFile
};
const handlePreview = (file:UploadUserFile)=>{
window.open(file.url)
}
const stopUpBigFile = ()=>{
uploadStatus.value = 3
cancelToken.cancel()
}
const calculateMD5 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const chunkSize = 1024 * 1024; // 1 MB
const chunks = Math.ceil(file.size / chunkSize);
const spark = new SparkMD5.ArrayBuffer();
let currentChunk = 0;
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
spark.append(e.target!.result as ArrayBuffer);
currentChunk++;
if (currentChunk < chunks) {
uploadStatusContent.value = '校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%'
loadNextChunk();
} else {
// MD5
resolve(spark.end());
}
};
reader.onerror = (error) => {
reject(error);
};
function loadNextChunk() {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const blob = file.slice(start, end);
reader.readAsArrayBuffer(blob);
}
loadNextChunk(); //
});
}
const formatFileSize = (bytes:number):string => {
if (bytes === 0) return '0 Bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const index = Math.floor(Math.log(bytes) / Math.log(1024)); //
const value = (bytes / Math.pow(1024, index)).toFixed(2); //
return `${value} ${sizes[index]}`;
}
const clearUpStatus = ()=>{
uploadStatus.value = 0
uploadStatusContent.value = ''
progress.value = 0
fileFormatSize.value = '0byte'
}
defineExpose({stopUpBigFile})
</script>
<style>
.el-upload.is-drag {
display: block;
width: 200px;height: 200px;
}
.progress-show{
margin-top: 8px;
}
</style>

View File

@ -14,6 +14,7 @@
:on-success="handleSuccess"
:data="dataParam"
:on-preview="handlePreview"
ref="upFileRef"
>
<el-icon class="el-icon--upload"><ele-UploadFilled /></el-icon>
<div class="el-upload__text">
@ -54,9 +55,11 @@ export default defineComponent({
}
},
},
emits:['update:modelValue'],
setup(props,{ emit }) {
let uploadedFile:Array<any> = [] ;
const {proxy} = <any>getCurrentInstance();
const upFileRef = ref()
const dataParam = reactive({
token:getToken(),
})
@ -73,7 +76,7 @@ export default defineComponent({
return value
},
set: val => {
emit('upFileData', val)
emit('update:modelValue', val)
}
});
const beforeUpload: UploadProps['beforeUpload'] = () => {
@ -130,6 +133,9 @@ export default defineComponent({
const handlePreview = (file:UploadUserFile)=>{
window.open(file.url)
}
const stopUpFile = ()=>{
upFileRef.value.abort()
}
return {
dataFileList,
handleSuccess,
@ -139,6 +145,8 @@ export default defineComponent({
handleChange,
handleExceed,
handlePreview,
upFileRef,
stopUpFile,
dataParam
};
},

View File

@ -12,6 +12,7 @@
:on-exceed = "handleExceed"
:before-upload="beforeAvatarUpload"
:data="dataParam"
ref="upImageRef"
>
<el-icon><ele-Plus /></el-icon>
</el-upload>
@ -66,7 +67,9 @@ export default defineComponent({
}
},
},
emits:['update:modelValue'],
setup(props,{ emit }) {
const upImageRef = ref()
const baseURL:string|undefined|boolean = import.meta.env.VITE_API_URL
const {proxy} = <any>getCurrentInstance();
const dialogImageUrl = ref('')
@ -93,7 +96,7 @@ export default defineComponent({
return value
},
set: val => {
emit('uploadData', val)
emit('update:modelValue', val)
}
});
@ -144,7 +147,11 @@ export default defineComponent({
const setDataFileList = () => {
dataFileList.value = uploadedFile
};
const stopUpImage = ()=>{
upImageRef.value.abort()
}
return {
upImageRef,
dataFileList,
imageUrl,
baseURL,
@ -155,6 +162,7 @@ export default defineComponent({
handleRemove,
handlePictureCardPreview,
handleAvatarSuccess,
stopUpImage,
dataParam
};
},

View File

@ -0,0 +1,400 @@
<template>
<div class="system-sysAttachment-container">
<div class="up-selector" @click="openDialog">
<el-icon class="uploader-icon"><ele-Plus /></el-icon>
</div>
<div class="img-list" v-if="dataFileList.length>0">
<el-upload
v-model:file-list="dataFileList"
ref="upImageRef"
:list-type="fileType=='image'?'picture-card':'text'"
></el-upload>
</div>
<el-dialog title="选择文件" v-model="isShowDialog" width="1000px" :close-on-click-modal="false" :destroy-on-close="true">
<el-card shadow="hover">
<div class="system-sysAttachment-search mb15">
<el-form :model="tableData.param" ref="queryRef" :inline="true" label-width="100px">
<el-row>
<el-col :span="12" class="form-item">
<el-form-item label="文件原始名" prop="name">
<el-input
v-model="tableData.param.name"
placeholder="请输入文件原始名"
clearable
@keyup.enter.native="sysAttachmentList"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="form-item">
<el-form-item label="扩展类型" prop="mimeType">
<el-input
v-model="tableData.param.mimeType"
placeholder="请输入扩展类型"
clearable
@keyup.enter.native="sysAttachmentList"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="form-item">
<el-form-item label="创建时间" prop="createdAt">
<el-date-picker
clearable style="width: 200px"
v-model="tableData.param.createdAt"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8" class="form-item">
<el-form-item>
<el-button type="primary" @click="sysAttachmentList"><el-icon><ele-Search /></el-icon>搜索</el-button>
<el-button @click="resetQuery(queryRef)"><el-icon><ele-Refresh /></el-icon>重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row :gutter="10" class="mb8" style="margin-top: 8px;">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd('file')"
><el-icon><ele-Upload /></el-icon>上传文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button
color="#626aef"
:dark="true"
type="success"
@click="handleAdd('image')"
><el-icon><ele-PictureFilled /></el-icon>上传图片</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
@click="handleAdd('bigFile')"
><i class="iconfont icon-shangchuan"></i>上传大文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleConfirm()"
v-auth="'api/v1/system/sysAttachment/delete'"
><el-icon><ele-Delete /></el-icon>确认返回</el-button>
</el-col>
</el-row>
</div>
<el-table ref="uploadTableRef" v-loading="loading" :data="tableData.data" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="文件原始名" align="center" prop="name" show-overflow-tooltip
min-width="260px"
/>
<el-table-column label="上传类型" align="center" prop="kind" :formatter="kindFormat"
min-width="100px"
/>
<el-table-column label="本地路径" align="center" prop="path" min-width="100px">
<template #default="scope">
<el-image
v-if="scope.row.kind=='image'"
style="width: 60px; height: 60px"
:src="proxy.getUpFileUrl(scope.row.path)"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="[proxy.getUpFileUrl(scope.row.path)]"
:initial-index="0"
preview-teleported
hide-on-click-modal
fit="fill"
/>
<el-image
v-else
style="width: 60px; height: 60px">
<template #error>
<div class="image-slot">
{{getExt(scope.row.name)}}
</div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column label="文件大小" align="center" prop="size" :formatter="formatFileSize"
min-width="100px"
/>
<el-table-column label="上传时间" align="center" prop="updatedAt"
min-width="150px"
>
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.updatedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding" min-width="100px" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
@click="handleSelect(scope.row)"
><el-icon><ele-Select /></el-icon>选择</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.total>0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="sysAttachmentList"
/>
</el-card>
<ApiV1SystemSysAttachmentEdit
ref="editRef"
@sysAttachmentList="sysAttachmentList"
></ApiV1SystemSysAttachmentEdit>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {toRefs, reactive, ref, computed,getCurrentInstance} from 'vue';
import {FormInstance, UploadUserFile} from 'element-plus';
import {
listSysAttachment,
} from "/@/api/system/sysAttachment";
import {
SysAttachmentTableColumns,
SysAttachmentInfoData,
SysAttachmentTableDataState,
} from "/@/views/system/sysAttachment/list/component/model"
import ApiV1SystemSysAttachmentEdit from "/@/views/system/sysAttachment/list/component/edit.vue"
defineOptions({ name: "apiV1SystemSysAttachmentList"})
const props = defineProps({
fileType:{
type:String,
default:function(){
return 'image'
}
},
modelValue:{
type:Array,
default:function(){
return []
}
},
limit:{
type:Number,
default:function(){
return 10
}
}
})
const emit = defineEmits(['update:modelValue'])
const dataFileList = computed({
get: () => {
let value:Array<UploadUserFile> = props.modelValue as UploadUserFile[]|| []
value.map((item: UploadUserFile)=>{
if(item.url){
item.url = proxy.getUpFileUrl(item.url)
}
return item
})
return value
},
set: val => {
console.log('setVal',val)
emit('update:modelValue', val)
}
});
const uploadTableRef = ref()
const {proxy} = <any>getCurrentInstance()
const isShowDialog = ref(false)
const loading = ref(false)
const queryRef = ref()
const editRef = ref();
//
const showAll = ref(false)
//
const single = ref(true)
//
const multiple =ref(true)
//
const {sys_upload_file_type} = proxy.useDict( 'sys_upload_file_type')
const state = reactive<SysAttachmentTableDataState>({
ids:[],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
appId: undefined,
drive: undefined,
name: undefined,
kind: props.fileType=='image'?'image':undefined,
mimeType: undefined,
status: undefined,
createdAt: [],
dateRange: []
},
},
});
const { tableData } = toRefs(state);
//
const initTableData = () => {
sysAttachmentList()
};
/** 重置按钮操作 */
const resetQuery = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
state.tableData.param.status = true
sysAttachmentList()
};
//
const sysAttachmentList = ()=>{
loading.value = true
listSysAttachment(state.tableData.param).then((res:any)=>{
state.tableData.data = res.data.list??[];
state.tableData.total = res.data.total;
loading.value = false
})
};
//
const kindFormat = (row:SysAttachmentTableColumns) => {
return proxy.selectDictLabel(sys_upload_file_type.value, row.kind);
}
//
const handleSelectionChange = (selection:Array<SysAttachmentInfoData>) => {
state.ids = selection.map(item => item.id)
single.value = selection.length!=1
multiple.value = !selection.length
}
const handleAdd = (upType:string)=>{
editRef.value.openDialog(upType)
}
const handleSelect = (row: SysAttachmentTableColumns) => {
let selected = true
if(state.ids.includes(row.id)){
selected = false
state.ids.splice(state.ids.indexOf(row.id),1)
}else{
state.ids.push(row.id)
}
uploadTableRef.value.toggleRowSelection(row,selected)
};
const handleConfirm = () => {
let ids:number[] = Array.from(new Set(state.ids));
if(dataFileList.value.length+ids.length>props.limit){
proxy.$message.error('最多只能选择'+props.limit+'个文件')
return
}
let list :UploadUserFile[] = []
state.tableData.data.map((item: SysAttachmentTableColumns)=>{
if(ids.includes(item.id)){
list.push({
name: item.name,
url:item.path
})
}
})
console.log('dataFileList.value=',dataFileList.value)
dataFileList.value.push(...list)
closeDialog()
}
const getExt = (fileName:string):string=>{
//
const lastDotIndex = fileName.lastIndexOf('.');
//
if (lastDotIndex === -1 || lastDotIndex === 0) {
return '';
}
//
return fileName.slice(lastDotIndex + 1);
}
const formatFileSize = (row:SysAttachmentTableColumns):string =>{
const bytes:number = row.size;
if (bytes === 0) return '0 Bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const index = Math.floor(Math.log(bytes) / Math.log(1024)); //
const value = (bytes / Math.pow(1024, index)).toFixed(2); //
return `${value} ${sizes[index]}`;
}
const openDialog = ()=>{
reset()
initTableData();
isShowDialog.value = true
}
const closeDialog = ()=>{
isShowDialog.value = false
}
const reset = ()=>{
resetQuery(queryRef.value)
state.ids = []
}
</script>
<style lang="scss" scoped>
.system-sysAttachment-container{
width: 100%;
}
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2{margin: 3px;}
.form-item{
margin-bottom: 8px;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: var(--el-fill-color-light);
color: var(--el-text-color-secondary);
font-size: 20px;
}
.up-selector {
width: 100px;
height: 100px;
border-radius: 6px;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
background-color: var(--el-fill-color-lighter);
border: 1px dashed var(--el-border-color-darker);
box-sizing: border-box;
cursor: pointer;
display: inline-flex;
justify-content: center;
vertical-align: top;
.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 50px;
height: 96px;
text-align: center;
}
}
.up-selector:hover {
border-color: var(--el-color-primary);
}
.img-list{
width: 100%;
margin-top: 8px;
}
.img-list :deep(.el-upload.el-upload--picture-card){
display: none;
}
.img-list:deep(.el-upload.el-upload--text){
display: none;
}
</style>

View File

@ -152,8 +152,9 @@ const getData = (barName: number | undefined) => {
})*/
let noticeParam = {
pageNum: 1,
pageSize: 5,
pageSize: 10,
type: barName,
isTrim:true
}
listShowNotice(noticeParam).then((res: any) => {
state.noticeList = res.data.list || []
@ -225,12 +226,14 @@ const handleRead = (item: SysNoticeInfoData) => {
getData(item.type)
ElMessage.success("已读");
})
getUnReadCount()
}
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user-news {
height: calc(100vh - 150px);
overflow-y: auto;
.content-box {
font-size: 13px;

View File

@ -60,38 +60,6 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
roles: ['admin'],
icon: 'iconfont icon-diannao',
},
},
{
path: '/bigUpload',
name: 'bigUpload',
component: () => import('/@/layout/routerView/parent.vue'),
meta:{
title: '大文件上传',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: ['admin'],
icon: 'iconfont icon-diannao',
},
children:[
{
path: '/bigUpload/list',
name: 'bigUploadList',
component: () => import('/@/views/bigUpload/index.vue'),
meta: {
title: '大文件上传',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: ['admin', 'common'],
icon: 'iconfont icon-shouye',
},
},
]
}
],
},

View File

@ -1,13 +0,0 @@
import { defineStore } from 'pinia';
import {bigUploadStates} from "/@/stores/interface";
export const bigUpload = defineStore('bigUpload', {
state:():bigUploadStates=>({
panelShow:false
}),
actions: {
async setPanelShow(bool: boolean) {
this.panelShow = bool;
}
},
})

View File

@ -1,80 +0,0 @@
<template>
<el-dialog v-model="isShowDialog" width="769px">
<template #header>修改</template>
<el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="90px">
<el-form-item label="标题" prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="备注" prop="describe">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入描述" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeDialog" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default">修改</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {ref, reactive, defineComponent} from 'vue';
import {getBigFile, editBigFile} from "/@/api/system/bigFile";
import {ElMessage} from "element-plus";
defineOptions({ name: "editBigUpload"})
const emit = defineEmits(["success"])
const formRef = ref<HTMLElement | null>(null);
const isShowDialog = ref<boolean>(false)
const ruleForm = reactive<any>({id:0, name:"", describe:""})
const rules = reactive<any>({
name: [
{ required: true, message: "标题不能为空", trigger: "blur" }
],
})
const openDialog = async (id:number) => {
resetForm()
const result = await getBigFile(id).then((res:any) => res.code === 0? res.data || {} : {})
const {name, describe} = result
ruleForm.id = result.id
ruleForm.name = name
ruleForm.describe = describe
isShowDialog.value = true
}
const closeDialog = () => {
isShowDialog.value = false
}
defineExpose({
openDialog
})
const resetForm = () => {
ruleForm.id = 0
ruleForm.name = ""
ruleForm.describe = ""
}
const onSubmit = async () => {
const formWrap = formRef.value as any
if (!formWrap) return;
formWrap.validate(async (valid: boolean) => {
if (valid) {
const result:any = await editBigFile(ruleForm)
if (result.code === 0) {
ElMessage.success('修改成功');
closeDialog()
emit('success')
} else {
ElMessage.error("修改失败")
}
}
});
}
</script>
<style scoped>
</style>

View File

@ -1,175 +0,0 @@
<template>
<div>
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="文件名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入文件名称"
clearable
size="default"
@keyup.enter.native="getBigFileList"
/>
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" class="ml10" @click="getBigFileList">
<el-icon>
<ele-Search/>
</el-icon>
查询
</el-button>
<el-button size="default" @click="resetQuery(queryRef)">
<el-icon>
<ele-Refresh/>
</el-icon>
重置
</el-button>
<el-button size="default" type="success" class="ml10" @click="uploadHandle">
<el-icon>
<ele-FolderAdd/>
</el-icon>
文件上传
</el-button>
<el-button size="default" type="danger" class="ml10" @click="delMult">
<el-icon>
<ele-Delete/>
</el-icon>
删除文件
</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="标题" align="center" prop="name"/>
<el-table-column label="大小" align="center" prop="size">
<template #default="scope">
{{byteText(scope.row.size)}}
</template>
</el-table-column>
<el-table-column label="文件类型" align="center" prop="mimeType"/>
<!-- <el-table-column label="文件描述" align="center" prop="describe"/>-->
<el-table-column label="创建时间" align="center" prop="createdAt"/>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" text type="primary" @click="edit(scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="del(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getBigFileList"
/>
<EditBigUpload ref="editBigUploadRef" @success="getBigFileList"></EditBigUpload>
</el-card>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, defineComponent, getCurrentInstance} from 'vue';
import getTableData from '/@/views/bigUpload/tableData'
import {addBigFile, deleteBigFile} from "/@/api/system/bigFile";
import {ElMessage, ElMessageBox} from "element-plus";
import EditBigUpload from '/@/views/bigUpload/component/editBigUpload.vue'
defineOptions({ name: "bigUpload"})
const {
total,
queryParams,
tableData,
queryRef,
resetQuery,
getBigFileList,
resetBigFileList
} = getTableData()
const editBigUploadRef = ref();
const {proxy} = <any>getCurrentInstance();
const selected = ref<number[]>([])
//
const uploadHandle = function () {
proxy.mittBus.emit("bigUploader.uploadFile")
}
onMounted(()=> {
proxy.mittBus.on("bigUploader.uploadFileSuccess", (res:any) => {
//console.log(res)
if (res.skipUpload === true) {
//
}
const {filename, totalSize, url, identifier, fileType} = res
addBigFile({
name: filename,
size: totalSize,
path: url,
mimeType: fileType,
source: 0,
md5: identifier
}).then(() => {
resetBigFileList()
})
})
})
const handleSelectionChange = (selection:any[]) => {
selected.value = selection.map(item => item.id)
}
const edit = function (row:any) {
editBigUploadRef.value.openDialog(row.id)
}
const del = function (row:any) {
ElMessageBox.confirm(`是否删除文件:${row.name}`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(()=> {
deleteBigFile([row.id]).then(()=> {
getBigFileList()
})
})
}
const delMult = function () {
if(selected.value.length===0) {
ElMessage.error('请选择要删除的数据。');
return
}
ElMessageBox.confirm(`确定删除所选数据?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(()=> {
deleteBigFile([...selected.value]).then(()=> {
getBigFileList()
})
})
}
const byteText = function (value:number) :string {
if (value > 1048576 ) {
return (value / 1024 / 1024).toFixed(2) + "MB"
} else if (value > 0) {
return (value / 1024).toFixed(2) + "KB"
} else {
return ""
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -1,48 +0,0 @@
import {onMounted, reactive, ref} from "vue";
import {getBigFileList as apiGetBigFileList} from "/@/api/system/bigFile";
import {FormInstance} from "element-plus/es";
export default function () {
const tableData = ref<any[]>([])
let total = ref<number>(0)
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
name: '',
orderBy:'created_at desc'
})
const queryRef = ref<FormInstance>();
const resetQuery = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
getBigFileList()
}
const getBigFileList = async () => {
const result:any = await apiGetBigFileList(queryParams).then((res:any) => res.code === 0 ? res.data : {})
tableData.value = Array.isArray(result.list) ? result.list : []
total.value = result.total || 0
}
const resetBigFileList = async () => {
queryParams.pageNum = 1
queryParams.pageSize = 10
queryParams.name = ''
await getBigFileList()
}
onMounted(getBigFileList)
return {
total,
queryParams,
tableData,
queryRef,
resetQuery,
getBigFileList,
resetBigFileList
}
}

View File

@ -1,16 +0,0 @@
import {defineComponent,h} from "vue";
export default defineComponent({
name:"baiduMap",
props:{
src:{
type:String,
default:'',
}
},
setup(prop){
return ()=>{
return h('script',{src:prop.src,type:'text/javascript'})
}
},
})

View File

@ -1,6 +1,6 @@
<template>
<div>
<gf-ueditor v-if="show" editorId="demoEdit01" v-model="content" @setEditContent="setEditContent"></gf-ueditor>
<gf-ueditor v-if="show" editorId="demoEdit01" v-model="content"></gf-ueditor>
<h3>同步获取编辑器内容如下</h3>
<div v-html="content"></div>
</div>

View File

@ -16,7 +16,7 @@
<el-radio
v-for="dict in sysYesNoOptions"
:key="dict.value"
:label="dict.value"
:value="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>

View File

@ -27,15 +27,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" >
<el-form-item label="负责人">
<div v-if="deptUser.length > 0">
<el-tag closable :disable-transitions="false" class="u-m-r-10" v-for="(item,index) in deptUser" :key="index" @close="handleClose(item,index)" >{{item.userNickname}}</el-tag>
</div>
<!-- <el-input v-model="ruleForm.leader" placeholder="请输入负责人" clearable></el-input>-->
<el-button
style="padding-left: 10px;"
type="primary"
link
@click="handleSelectUser" >请选择</el-button>
<select-user v-model="ruleForm.leader"></select-user>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" >
@ -68,13 +60,11 @@
</template>
</el-dialog>
</div>
<select-user ref="selectUserRef" @selectUser="confirmUser" :selectedUsers="deptUser"></select-user>
</template>
<script setup lang="ts">
import {reactive, toRefs, defineComponent, getCurrentInstance,ref,unref} from 'vue';
import {reactive, toRefs, getCurrentInstance,ref,unref} from 'vue';
import {addDept,editDept, getDeptList} from "/@/api/system/dept";
import {getUserByIds} from '/@/api/system/user';
import {ElMessage} from "element-plus";
import selectUser from "/@/components/selectUser/index.vue"
@ -105,8 +95,6 @@ defineOptions({ name: "systemEditDept"})
const emit = defineEmits(['deptList'])
const {proxy} = getCurrentInstance() as any;
const formRef = ref<HTMLElement | null>(null);
const selectUserRef = ref();
const deptUser = ref([]);
const state = reactive<DeptSate>({
isShowDialog: false,
ruleForm: {
@ -135,24 +123,8 @@ const openDialog = (row?: RuleFormState|number) => {
});
if(row && typeof row === "object"){
state.ruleForm = row;
let leaders = row.leader??[]
if (leaders.length > 0){
//
getUserByIds({ids:leaders}).then((res:any)=>{
if(res.code === 0){
deptUser.value = res.data.userList;
}
});
}else{
// deptUser,
deptUser.value = [];
}
}else if(row && typeof row === 'number'){
}else if(row){
state.ruleForm.parentId = row
deptUser.value = [];
}else{
deptUser.value = [];
}
state.isShowDialog = true;
};
@ -201,31 +173,6 @@ const resetForm = ()=>{
status: 1,
}
};
const handleClose = (data:any,key:number) => {
deptUser.value.splice(key, 1);
state.ruleForm.leader = deptUser.value.map((item:any) => item.id)
};
const confirmUser = (data:any[]) => {
let leaderArr = state.ruleForm.leader??[];
if(data.length>0){
data.map((item:any)=>{
//
if (!leaderArr.includes(item.id)){
deptUser.value.push(item as never)
leaderArr.push(item.id)
}
})
state.ruleForm.leader = leaderArr;
}else{
deptUser.value = []
state.ruleForm.leader = []
}
};
//
const handleSelectUser = () =>{
selectUserRef.value.openDialog()
};
</script>
<style>
.u-m-r-10{

View File

@ -73,12 +73,12 @@
<el-dialog :title="selectRow.name+'-用户列表'" v-model="isShowDialog" width="70vw">
<UserList v-if="isShowDialog" ref="userListRef" :dept-data="deptData" :gender-data="sys_user_sex" :param="userListParam" @getUserList="userList"/>
</el-dialog>
<select-user ref="selectUserRef" @selectUser="confirmUser" @okBack="setRoleUser" :selectedUsers="roleUsers"></select-user>
<select-user v-show="false" ref="selectUserRef" v-model="roleUsers"></select-user>
</div>
</template>
<script setup lang="ts">
import {toRefs, reactive, onMounted, ref, defineComponent, toRaw,getCurrentInstance} from 'vue';
import {toRefs, reactive, onMounted, ref, defineComponent, toRaw, getCurrentInstance, watch} from 'vue';
import { ElMessageBox, ElMessage,ElLoading } from 'element-plus';
import EditRole from '/@/views/system/role/component/editRole.vue';
import DataScope from '/@/views/system/role/component/dataScope.vue';
@ -257,26 +257,10 @@ const handleCommand = (command: string )=>{
break
}
}
const confirmUser = (data:any[]) => {
if(data.length>0){
const ids = roleUsers.value.map((item:any)=>{
return item.id
})
console.log('ids = ',ids)
data.map((item:any)=>{
//
if (!ids.includes(item.id)){
roleUsers.value.push(item as never)
}
})
}else{
roleUsers.value = []
}
};
const setRoleUser = ()=>{
const ids = roleUsers.value.map((item:any)=>{
return item.id
watch(roleUsers,(newVal) => {
setRoleUser(newVal)
})
const setRoleUser = (ids:any)=>{
//
setRoleUsers({roleId:setRole.value,userIds:ids}).then((res:any)=>{
roleList()

View File

@ -0,0 +1,249 @@
<template>
<!-- 附件管理详情抽屉 -->
<div class="system-sysAttachment-detail">
<el-drawer v-model="isShowDialog" size="80%" direction="ltr">
<template #header>
<h4>附件管理详情</h4>
</template>
<el-descriptions
class="margin-top"
:column="3"
border
style="margin: 8px;"
>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
文件ID
</div>
</template>
{{ formData.id }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
应用ID
</div>
</template>
{{ formData.appId }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
上传驱动
</div>
</template>
{{ proxy.getOptionValue(formData.drive, driveOptions,'value','label') }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
文件原始名
</div>
</template>
{{ formData.name }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
上传类型
</div>
</template>
{{ proxy.getOptionValue(formData.kind, kindOptions,'value','label') }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
扩展类型
</div>
</template>
{{ formData.mimeType }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
本地路径
</div>
</template>
{{ formData.path }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
文件大小
</div>
</template>
{{ formData.size }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
扩展名
</div>
</template>
{{ formData.ext }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
md5校验码
</div>
</template>
{{ formData.md5 }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
上传人ID
</div>
</template>
{{ formData.createdBy }}
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
状态
</div>
</template>
<el-switch v-model="formData.status" class="ml-2" disabled />
</el-descriptions-item>
<el-descriptions-item :span="1">
<template #label>
<div class="cell-item">
创建时间
</div>
</template>
{{ proxy.parseTime(formData.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}
</el-descriptions-item>
</el-descriptions>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { reactive, toRefs, defineComponent,ref,unref,getCurrentInstance,computed } from 'vue';
import {ElMessageBox, ElMessage, FormInstance,UploadProps} from 'element-plus';
import {
listSysAttachment,
getSysAttachment,
delSysAttachment,
addSysAttachment,
updateSysAttachment,
} from "/@/api/system/sysAttachment";
import {
SysAttachmentTableColumns,
SysAttachmentInfoData,
SysAttachmentTableDataState,
SysAttachmentEditState
} from "/@/views/system/sysAttachment/list/component/model"
defineOptions({ name: "ApiV1SystemSysAttachmentDetail"})
const props = defineProps({
driveOptions:{
type:Array,
default:()=>[]
},
kindOptions:{
type:Array,
default:()=>[]
},
})
const {proxy} = <any>getCurrentInstance()
const formRef = ref<HTMLElement | null>(null);
const menuRef = ref();
const state = reactive<SysAttachmentEditState>({
loading:false,
isShowDialog: false,
formData: {
id: undefined,
appId: undefined,
drive: undefined,
name: undefined,
kind: undefined,
mimeType: undefined,
path: undefined,
size: undefined,
ext: undefined,
md5: undefined,
createdBy: undefined,
status: undefined,
createdAt: undefined,
updatedAt: undefined,
},
//
rules: {
id : [
{ required: true, message: "文件ID不能为空", trigger: "blur" }
],
appId : [
{ required: true, message: "应用ID不能为空", trigger: "blur" }
],
name : [
{ required: true, message: "文件原始名不能为空", trigger: "blur" }
],
status : [
{ required: true, message: "状态不能为空", trigger: "blur" }
],
}
});
const { isShowDialog,formData } = toRefs(state);
//
const openDialog = (row?: SysAttachmentInfoData) => {
resetForm();
if(row) {
getSysAttachment(row.id!).then((res:any)=>{
const data = res.data;
data.createdBy = data.createdUser?.userNickname
state.formData = data;
})
}
state.isShowDialog = true;
};
//
const closeDialog = () => {
state.isShowDialog = false;
};
defineExpose({
openDialog,
});
//
const onCancel = () => {
closeDialog();
};
const resetForm = ()=>{
state.formData = {
id: undefined,
appId: undefined,
drive: undefined,
name: undefined,
kind: undefined,
mimeType: undefined,
path: undefined,
size: undefined,
ext: undefined,
md5: undefined,
createdBy: undefined,
status: undefined,
createdAt: undefined,
updatedAt: undefined,
}
};
</script>
<style scoped>
.system-sysAttachment-detail :deep(.el-form-item--large .el-form-item__label){
font-weight: bolder;
}
.pic-block{
margin-right: 8px;
}
.file-block{
width: 100%;
border: 1px solid var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
margin-bottom: 5px;
padding: 3px 6px;
}
.ml-2{margin-right: 5px;}
</style>

View File

@ -0,0 +1,76 @@
<template>
<div class="system-sysAttachment-edit">
<!-- 添加或修改附件管理对话框 -->
<el-dialog v-model="isShowDialog" width="800px" :close-on-click-modal="false" :destroy-on-close="true">
<template #header>
<div v-drag="['.system-sysAttachment-edit .el-dialog', '.system-sysAttachment-edit .el-dialog__header']">上传</div>
</template>
<upload-img ref="upImageRef" v-if="upType==='image'" :action="baseURL+'api/v1/system/upload/singleImg'" v-model="images" :limit="10"></upload-img>
<upload-file ref="upFileRef" v-else-if="upType==='file'" :action="baseURL+'api/v1/system/upload/singleFile'" v-model="files" :limit="10" :uploadSize="50"></upload-file>
<upload-big-file ref="upBigFileRef" v-else-if="upType==='bigFile'" v-model="files" :limit="10"></upload-big-file>
<template #footer>
<div class="dialog-footer">
<el-button type="danger" @click="onCancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import uploadImg from "/@/components/uploadImg/index.vue"
import uploadFile from "/@/components/uploadFile/index.vue"
import uploadBigFile from "/@/components/uploadBigFile/index.vue"
import {reactive, toRefs, ref, watch} from 'vue';
import {
SysAttachmentEditState
} from "/@/views/system/sysAttachment/list/component/model"
defineOptions({ name: "ApiV1SystemSysAttachmentEdit"})
const baseURL:string|undefined|boolean = import.meta.env.VITE_API_URL
const emit = defineEmits(['sysAttachmentList'])
const upType = ref('')
const upFileRef = ref()
const upImageRef = ref()
const upBigFileRef = ref()
const state = reactive<SysAttachmentEditState>({
isShowDialog: false,
images: [],
files:[]
});
const { isShowDialog,images,files } = toRefs(state);
//
const openDialog = (upTp:string) => {
reset()
upType.value = upTp
state.isShowDialog = true;
};
//
const closeDialog = () => {
state.isShowDialog = false;
if(upType.value=='image'){
upImageRef.value.stopUpImage()
}else if(upType.value=='file'){
upFileRef.value.stopUpFile()
}else if(upType.value=='bigFile'){
upBigFileRef.value.stopUpBigFile()
}
};
defineExpose({
openDialog,
});
//
const onCancel = () => {
closeDialog();
};
watch(()=>[state.images,state.files],()=>{
emit('sysAttachmentList')
})
const reset = ()=>{
state.images = []
state.files = []
}
</script>
<style scoped>
.kv-label{margin-bottom: 15px;font-size: 14px;}
.mini-btn i.el-icon{margin: unset;}
.kv-row{margin-bottom: 12px;}
</style>

View File

@ -0,0 +1,59 @@
export interface SysAttachmentTableColumns {
id:number; // 文件ID
appId:string; // 应用ID
drive:string; // 上传驱动
name:string; // 文件原始名
kind:string; // 上传类型
path:string; // 本地路径
size:number; // 文件大小
ext:string; // 扩展名
status:boolean; // 状态
createdAt:string; // 创建时间
}
export interface SysAttachmentInfoData {
id:number|undefined; // 文件ID
appId:string|undefined; // 应用ID
drive:string|undefined; // 上传驱动
name:string|undefined; // 文件原始名
kind:string|undefined; // 上传类型
mimeType:string|undefined; // 扩展类型
path:string|undefined; // 本地路径
size:number|undefined; // 文件大小
ext:string|undefined; // 扩展名
md5:string|undefined; // md5校验码
createdBy:number|undefined; // 上传人ID
status:boolean; // 状态
createdAt:string|undefined; // 创建时间
updatedAt:string|undefined; // 修改时间
}
export interface SysAttachmentTableDataState {
ids:any[];
tableData: {
data: Array<SysAttachmentTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
appId: string|undefined;
drive: string|undefined;
name: string|undefined;
kind: string|undefined;
mimeType: string|undefined;
status: boolean|undefined;
createdAt: string[];
dateRange: string[];
};
};
}
export interface SysAttachmentEditState{
isShowDialog: boolean;
images:any[];
files:any[];
}

View File

@ -0,0 +1,405 @@
<template>
<div class="system-sysAttachment-container">
<el-card shadow="hover">
<div class="system-sysAttachment-search mb15">
<el-form :model="tableData.param" ref="queryRef" :inline="true" label-width="100px">
<el-row>
<el-col :span="8" class="colBlock">
<el-form-item label="应用ID" prop="appId">
<el-input
v-model="tableData.param.appId"
placeholder="请输入应用ID"
clearable
@keyup.enter.native="sysAttachmentList"
/>
</el-form-item>
</el-col>
<el-col :span="8" class="colBlock">
<el-form-item label="上传驱动" prop="drive">
<el-select v-model="tableData.param.drive" placeholder="请选择上传驱动" clearable style="width:200px;">
<el-option
v-for="dict in sys_upload_drive"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" :class="!showAll ? 'colBlock' : 'colNone'">
<el-form-item>
<el-button type="primary" @click="sysAttachmentList"><el-icon><ele-Search /></el-icon>搜索</el-button>
<el-button @click="resetQuery(queryRef)"><el-icon><ele-Refresh /></el-icon>重置</el-button>
<el-button type="primary" link @click="toggleSearch">
{{ word }}
<el-icon v-show="showAll"><ele-ArrowUp/></el-icon>
<el-icon v-show="!showAll"><ele-ArrowDown /></el-icon>
</el-button>
</el-form-item>
</el-col>
<el-col :span="8" :class="showAll ? 'colBlock' : 'colNone'">
<el-form-item label="文件原始名" prop="name">
<el-input
v-model="tableData.param.name"
placeholder="请输入文件原始名"
clearable
@keyup.enter.native="sysAttachmentList"
/>
</el-form-item>
</el-col>
<el-col :span="8" :class="showAll ? 'colBlock' : 'colNone'">
<el-form-item label="上传类型" prop="kind">
<el-select v-model="tableData.param.kind" placeholder="请选择上传类型" clearable style="width:200px;">
<el-option
v-for="dict in sys_upload_file_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" :class="showAll ? 'colBlock' : 'colNone'">
<el-form-item label="扩展类型" prop="mimeType">
<el-input
v-model="tableData.param.mimeType"
placeholder="请输入扩展类型"
clearable
@keyup.enter.native="sysAttachmentList"
/>
</el-form-item>
</el-col>
<el-col :span="8" :class="showAll ? 'colBlock' : 'colNone'">
<el-form-item label="状态" prop="status">
<el-switch v-model="tableData.param.status" class="ml-2" />
</el-form-item>
</el-col>
<el-col :span="8" :class="showAll ? 'colBlock' : 'colNone'">
<el-form-item label="创建时间" prop="createdAt">
<el-date-picker
clearable style="width: 200px"
v-model="tableData.param.createdAt"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8" :class="showAll ? 'colBlock' : 'colNone'">
<el-form-item>
<el-button type="primary" @click="sysAttachmentList"><el-icon><ele-Search /></el-icon>搜索</el-button>
<el-button @click="resetQuery(queryRef)"><el-icon><ele-Refresh /></el-icon>重置</el-button>
<el-button type="primary" link @click="toggleSearch">
{{ word }}
<el-icon v-show="showAll"><ele-ArrowUp/></el-icon>
<el-icon v-show="!showAll"><ele-ArrowDown /></el-icon>
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd('file')"
><el-icon><ele-Upload /></el-icon>上传文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button
color="#626aef"
:dark="true"
type="success"
@click="handleAdd('image')"
><el-icon><ele-PictureFilled /></el-icon>上传图片</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
@click="handleAdd('bigFile')"
><i class="iconfont icon-shangchuan"></i>上传大文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/system/sysAttachment/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button>
</el-col>
</el-row>
</div>
<el-table v-loading="loading" :data="tableData.data" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="文件ID" align="center" prop="id"
min-width="80px"
/>
<el-table-column label="应用ID" align="center" prop="appId"
min-width="100px"
/>
<el-table-column label="上传驱动" align="center" prop="drive" :formatter="driveFormat"
min-width="100px"
/>
<el-table-column label="文件原始名" align="center" prop="name" show-overflow-tooltip
min-width="260px"
/>
<el-table-column label="上传类型" align="center" prop="kind" :formatter="kindFormat"
min-width="100px"
/>
<el-table-column label="本地路径" align="center" prop="path" min-width="100px">
<template #default="scope">
<el-image
v-if="scope.row.kind=='image'"
style="width: 60px; height: 60px"
:src="proxy.getUpFileUrl(scope.row.path)"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="[proxy.getUpFileUrl(scope.row.path)]"
:initial-index="0"
preview-teleported
hide-on-click-modal
fit="fill"
/>
<el-image
v-else
style="width: 60px; height: 60px">
<template #error>
<div class="image-slot">
{{getExt(scope.row.name)}}
</div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column label="文件大小" align="center" prop="size" :formatter="formatFileSize"
min-width="100px"
/>
<el-table-column label="状态" align="center" prop="status"
min-width="150px"
>
<template #default="scope">
<el-switch v-model="scope.row.status" class="ml-2" @change="changeStatus(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="updatedAt"
min-width="150px"
>
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.updatedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding" min-width="160px" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
@click="handleDownload(scope.row)"
><el-icon><ele-Download /></el-icon>下载</el-button>
<el-button
type="primary"
link
@click="handleDelete(scope.row)"
v-auth="'api/v1/system/sysAttachment/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.total>0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="sysAttachmentList"
/>
</el-card>
<ApiV1SystemSysAttachmentEdit
ref="editRef"
@sysAttachmentList="sysAttachmentList"
></ApiV1SystemSysAttachmentEdit>
</div>
</template>
<script setup lang="ts">
import {toRefs, reactive, onMounted, ref, computed,getCurrentInstance,toRaw} from 'vue';
import {ElMessageBox, ElMessage, FormInstance} from 'element-plus';
import {
listSysAttachment,
delSysAttachment,
changeSysAttachmentStatus,
} from "/@/api/system/sysAttachment";
import {
SysAttachmentTableColumns,
SysAttachmentInfoData,
SysAttachmentTableDataState,
} from "/@/views/system/sysAttachment/list/component/model"
import ApiV1SystemSysAttachmentEdit from "/@/views/system/sysAttachment/list/component/edit.vue"
defineOptions({ name: "apiV1SystemSysAttachmentList"})
const {proxy} = <any>getCurrentInstance()
const loading = ref(false)
const queryRef = ref()
const editRef = ref();
//
const showAll = ref(false)
//
const single = ref(true)
//
const multiple =ref(true)
const word = computed(()=>{
if(showAll.value === false) {
//
return "展开搜索";
} else {
return "收起搜索";
}
})
//
const {
sys_upload_drive,
sys_upload_file_type,
} = proxy.useDict(
'sys_upload_drive',
'sys_upload_file_type',
)
const state = reactive<SysAttachmentTableDataState>({
ids:[],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
appId: undefined,
drive: undefined,
name: undefined,
kind: undefined,
mimeType: undefined,
status: undefined,
createdAt: [],
dateRange: []
},
},
});
const { tableData } = toRefs(state);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
sysAttachmentList()
};
/** 重置按钮操作 */
const resetQuery = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
sysAttachmentList()
};
//
const sysAttachmentList = ()=>{
loading.value = true
listSysAttachment(state.tableData.param).then((res:any)=>{
let list = res.data.list??[];
state.tableData.data = list;
state.tableData.total = res.data.total;
loading.value = false
})
};
const toggleSearch = () => {
showAll.value = !showAll.value;
}
//
const driveFormat = (row:SysAttachmentTableColumns) => {
return proxy.selectDictLabel(sys_upload_drive.value, row.drive);
}
//
const kindFormat = (row:SysAttachmentTableColumns) => {
return proxy.selectDictLabel(sys_upload_file_type.value, row.kind);
}
const changeStatus = (row:SysAttachmentTableColumns) => {
changeSysAttachmentStatus(row.id,row.status)
.catch(()=>{
setTimeout(()=>{
row.status = !row.status
},300)
})
}
//
const handleSelectionChange = (selection:Array<SysAttachmentInfoData>) => {
state.ids = selection.map(item => item.id)
single.value = selection.length!=1
multiple.value = !selection.length
}
const handleAdd = (upType:string)=>{
editRef.value.openDialog(upType)
}
const handleDownload = (row: SysAttachmentTableColumns) => {
window.open(proxy.getUpFileUrl(row.path))
};
const handleDelete = (row: SysAttachmentTableColumns|null) => {
let msg = '你确定要删除所选数据?';
let id:number[] = [] ;
if(row){
msg = `此操作将永久删除数据,是否继续?`
id = [row.id]
}else{
id = state.ids
}
if(id.length===0){
ElMessage.error('请选择要删除的数据。');
return
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delSysAttachment(id).then(()=>{
ElMessage.success('删除成功');
sysAttachmentList();
})
})
.catch(() => {});
}
const getExt = (fileName:string):string=>{
//
const lastDotIndex = fileName.lastIndexOf('.');
//
if (lastDotIndex === -1 || lastDotIndex === 0) {
return '';
}
//
return fileName.slice(lastDotIndex + 1);
}
const formatFileSize = (row:SysAttachmentTableColumns):string =>{
const bytes:number = row.size;
if (bytes === 0) return '0 Bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const index = Math.floor(Math.log(bytes) / Math.log(1024)); //
const value = (bytes / Math.pow(1024, index)).toFixed(2); //
return `${value} ${sizes[index]}`;
}
</script>
<style lang="scss" scoped>
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2{margin: 3px;}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: var(--el-fill-color-light);
color: var(--el-text-color-secondary);
font-size: 20px;
}
</style>

View File

@ -50,8 +50,7 @@
</el-select>
</el-form-item>
<el-form-item label="内容">
<gf-ueditor editorId="ueSysNoticeContent" v-model="formData.content"
@setEditContent="setContentEditContent"></gf-ueditor>
<gf-ueditor editorId="ueSysNoticeContent" v-model="formData.content"></gf-ueditor>
</el-form-item>
<el-form-item label="状态" prop="status">
<!-- <el-radio-group v-model="formData.status">

View File

@ -130,6 +130,12 @@
<el-option label="多图上传" value="images" />
<el-option label="单文件上传" value="file" />
<el-option label="多文件上传" value="files" />
<el-option label="图片选择器" value="imageSelector" />
<el-option label="附件选择器" value="fileSelector" />
<el-option label="用户选择器(单选)" value="userSelectorSingle" />
<el-option label="用户选择器(多选)" value="userSelectorMultiple" />
<el-option label="部门选择器(单选)" value="deptSelectorSingle" />
<el-option label="部门选择器(多选)" value="deptSelectorMultiple" />
<el-option label="键值对" value="keyValue" />
</el-select>
</template>